Overhauled library UI to work on small screens

This commit is contained in:
Thaum Rystra
2020-05-25 16:43:28 +02:00
parent a55c1382b1
commit 3ec0f9500c
15 changed files with 784 additions and 540 deletions

View File

@@ -1,193 +1,232 @@
<template lang="html">
<v-menu
:close-on-content-click="false"
<v-menu
v-model="opened"
:close-on-content-click="false"
transition="slide-y-transition"
lazy
v-model="opened"
>
<v-btn
slot="activator"
icon
>
<v-icon>format_paint</v-icon>
</v-btn>
<v-card class="overflow-hidden">
<v-card-text>
<v-item-group v-model="color" mandatory>
<v-layout row wrap>
<v-item
v-for="kebabColorOption in colors"
:key="kebabColorOption"
:value="kebabToCamelCase(kebabColorOption)"
>
<div
slot-scope="{ active, toggle }"
:class="[kebabColorOption, kebabShade]"
class="color-swatch d-flex align-center"
@click="toggle"
>
<v-scroll-y-transition>
<v-icon
v-if="active"
:class="{dark: isDark(kebabColorOption, kebabShade)}"
>check</v-icon>
</v-scroll-y-transition>
</div>
</v-item>
<div class="spacer" v-for="i in 8"/>
</v-layout>
</v-item-group>
<v-item-group class="mt-2" v-model="shade" mandatory>
<v-layout wrap>
<v-item
v-for="kebabShadeOption in shades"
:key="kebabShadeOption"
:value="kebabToCamelCase(kebabShadeOption)"
>
<div
slot-scope="{ active, toggle }"
:class="[kebabColor, kebabShadeOption]"
class="shade-swatch d-flex align-center"
@click="toggle"
>
<v-scroll-y-transition>
<v-icon
v-if="active"
:class="{dark: isDark(kebabColor, kebabShadeOption)}"
>check</v-icon>
</v-scroll-y-transition>
</div>
</v-item>
<div class="spacer" v-for="i in 8"/>
</v-layout>
</v-item-group>
</v-card-text>
<v-card-actions>
<v-spacer/>
<v-btn flat @click="opened = false">Done</v-btn>
</v-card-actions>
</v-card>
lazy
left
>
<v-btn
slot="activator"
icon
>
<v-icon>format_paint</v-icon>
</v-btn>
<v-card class="overflow-hidden">
<v-card-text>
<v-layout
row
wrap
>
<div
v-for="colorOption in colors"
:key="colorOption"
:class="[colorOption, shade]"
class="color-swatch d-flex align-center"
@click="color = colorOption"
>
<v-scroll-y-transition>
<v-icon
v-if="colorOption === color"
:class="{dark: isDark(colorOption, shade)}"
>
check
</v-icon>
</v-scroll-y-transition>
</div>
<div
v-for="i in 8"
:key="i"
class="spacer"
/>
</v-layout>
<v-fade-transition>
<v-layout
v-show="color"
wrap
class="mt-2"
>
<div
v-for="shadeOption in shades"
:key="shadeOption"
:class="[color, shadeOption]"
class="shade-swatch d-flex align-center"
@click="shade = shadeOption"
>
<v-scroll-y-transition>
<v-icon
v-if="shade === shadeOption"
:class="{dark: isDark(color, shade)}"
>
check
</v-icon>
</v-scroll-y-transition>
</div>
<div
v-for="i in 8"
:key="i"
class="spacer"
/>
</v-layout>
</v-fade-transition>
</v-card-text>
<v-card-actions>
<v-btn
flat
@click="$emit('input')"
>
Clear
</v-btn>
<v-spacer />
<v-btn
flat
@click="opened = false"
>
Done
</v-btn>
</v-card-actions>
</v-card>
</v-menu>
</template>
<script>
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
import vuetifyColors from 'vuetify/es5/util/colors';
import { kebabToCamelCase, camelToKebabCase } from '/imports/ui/utility/swapCase.js';
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
import vuetifyColors from 'vuetify/es5/util/colors';
import { kebabToCamelCase, camelToKebabCase } from '/imports/ui/utility/swapCase.js';
function colorToHex(color, shade = 'base'){
color = kebabToCamelCase(color);
shade = kebabToCamelCase(shade);
return vuetifyColors[color][shade];
};
function colorToHex(color, shade = 'base'){
if (!color) return;
color = kebabToCamelCase(color);
shade = kebabToCamelCase(shade);
return vuetifyColors[color] && vuetifyColors[color][shade];
}
// Create an index of hex colors and what color/shade combination makes them
let colorIndex = {};
for (let color in vuetifyColors){
for (let shade in vuetifyColors[color]){
colorIndex[vuetifyColors[color][shade]] = {color, shade};
}
}
function hexToColor(hex){
return colorIndex[hex.toLowerCase()];
};
// Create an index of hex colors and what color/shade combination makes them
let colorIndex = {};
for (let color in vuetifyColors){
color = camelToKebabCase(color);
for (let shade in vuetifyColors[color]){
shade = camelToKebabCase(shade);
colorIndex[vuetifyColors[color][shade]] = {color, shade};
}
}
function hexToColor(hex){
if (!hex) return undefined;
return colorIndex[hex.toLowerCase()];
}
export default {
props: {
value: String, //hex string
},
data(){ return {
colors: [
'red',
'pink',
'purple',
'deep-purple',
'indigo',
'blue',
'light-blue',
'cyan',
'teal',
'green',
'light-green',
'lime',
'yellow',
'amber',
'orange',
'deep-orange',
'brown',
'grey',
],
shades: [
'lighten-4',
'lighten-3',
'lighten-2',
'lighten-1',
'base',
'darken-1',
'darken-2',
'darken-3',
'darken-4',
],
opened: false,
}},
methods: {
isDark(kebabColor, kebabShade){
color = colorToHex(kebabColor, kebabShade);
return isDarkColor(color);
},
kebabToCamelCase,
},
computed: {
combination (){
return hexToColor(this.value) || {};
},
color: {
get(){
return this.combination.color;
},
set(newColor){
this.$emit('input', colorToHex(newColor, this.shade));
},
},
shade: {
get(){
return this.combination.shade;
},
set(newShade){
this.$emit('input', colorToHex(this.color, newShade));
},
},
kebabColor(){
return camelToKebabCase(this.color);
},
kebabShade(){
return camelToKebabCase(this.shade);
},
},
};
export default {
props: {
//hex string
value: {
type: String,
default: undefined,
},
},
data(){ return {
colors: [
'red',
'pink',
'purple',
'deep-purple',
'indigo',
'blue',
'light-blue',
'cyan',
'teal',
'green',
'light-green',
'lime',
'yellow',
'amber',
'orange',
'deep-orange',
'brown',
'grey',
],
shades: [
'lighten-4',
'lighten-3',
'lighten-2',
'lighten-1',
'base',
'darken-1',
'darken-2',
'darken-3',
'darken-4',
],
opened: false,
}},
computed: {
combination (){
if (!this.value) return;
return hexToColor(this.value) || {};
},
color: {
get(){
return this.combination && this.combination.color;
},
set(newColor){
console.log(newColor, colorToHex(newColor, this.shade));
this.$emit('input', colorToHex(newColor, this.shade));
},
},
shade: {
get(){
return this.combination && this.combination.shade;
},
set(newShade){
console.log(newShade, colorToHex(this.color, newShade));
this.$emit('input', colorToHex(this.color, newShade));
},
},
kebabColor(){
return camelToKebabCase(this.color);
},
kebabShade(){
return camelToKebabCase(this.shade);
},
},
methods: {
isDark(kebabColor, kebabShade){
let color = colorToHex(kebabColor, kebabShade);
return isDarkColor(color);
},
kebabToCamelCase,
},
};
</script>
<style lang="css" scoped>
.color-swatch, .shade-swatch {
height: 30px;
width: 30px;
flex-grow: 1;
}
.v-icon {
height: 30px;
}
.v-icon {
color: black;
}
.dark.v-icon {
color: white;
}
.layout {
max-width: 270px;
}
.spacer {
width: 30px;
height: 0;
flex-grow: 1;
}
.color-swatch, .shade-swatch {
height: 30px;
width: 30px;
flex-grow: 1;
cursor: pointer;
transition: all 0.2s linear;
}
.color-swatch:hover{
z-index: 1;
transform: scale(1.1);
box-shadow: 0px 2px 1px -1px rgba(0,0,0,0.2),
0px 1px 1px 0px rgba(0,0,0,0.14),
0px 1px 3px 0px rgba(0,0,0,0.12);
}
.v-icon {
height: 30px;
}
.v-icon {
color: black;
}
.dark.v-icon {
color: white;
}
.layout {
max-width: 270px;
}
.spacer {
width: 30px;
height: 0;
flex-grow: 1;
}
</style>

View File

@@ -0,0 +1,43 @@
<template lang="html">
<div
class="layout row"
style="height: 100%;"
>
<div
class="layout column justify-start"
:style="computedTreeStyle"
>
<slot name="tree" />
</div>
<template v-if="$vuetify.breakpoint.mdAndUp">
<v-divider vertical />
<div
class="flex layout column"
style="background-color: inherit; overflow: hidden;"
data-id="selected-node-card"
>
<slot name="detail" />
</div>
</template>
</div>
</template>
<script>
export default {
computed:{
computedTreeStyle(){
if (this.$vuetify.breakpoint.smAndDown) return;
let style = 'flex-shrink: 0; flex-grow: 0; ';
if (this.$vuetify.breakpoint.xlOnly){
style += 'width: 400px;'
} else {
style += 'width: 320px;'
}
return style;
},
}
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -21,7 +21,7 @@
props: {
autoGrow: {
type: Boolean,
default: true,
default: false,
},
},
};

View File

@@ -0,0 +1,116 @@
<template lang="html">
<v-toolbar
:color="color || 'secondary'"
:dark="isDark"
:flat="flat"
>
<property-icon
:type="model && model.type"
class="mr-2"
/>
<v-toolbar-title v-if="model">
{{ model.name || getPropertyName(model.type) }}
</v-toolbar-title>
<v-spacer />
<v-slide-y-transition
hide-on-leave
>
<v-layout
v-if="editing && model"
key="edit-buttons"
>
<v-spacer />
<color-picker
v-if="false"
:value="model.color"
@input="colorChanged"
/>
<v-menu
bottom
left
transition="slide-y-transition"
>
<template #activator="{ on }">
<v-btn
icon
v-on="on"
>
<v-icon>more_vert</v-icon>
</v-btn>
</template>
<v-list>
<v-list-tile @click="$emit('remove')">
<v-list-tile-title>
Delete <v-icon>delete</v-icon>
</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
</v-layout>
<v-layout
v-else
key="blank"
/>
</v-slide-y-transition>
<v-btn
icon
@click="$emit('toggle-editing')"
>
<v-slide-y-transition
hide-on-leave
>
<v-icon
v-if="editing"
key="doneIcon"
>
done
</v-icon>
<v-icon
v-else
key="createIcon"
>
create
</v-icon>
</v-slide-y-transition>
</v-btn>
</v-toolbar>
</template>
<script>
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
import ColorPicker from '/imports/ui/components/ColorPicker.vue';
export default {
components: {
PropertyIcon,
ColorPicker,
},
props: {
model: {
type: Object,
default: undefined,
},
flat: Boolean,
editing: Boolean,
},
computed: {
isDark(){
return isDarkColor(this.color);
},
color(){
return this.model && this.model.color || this.$vuetify.theme.secondary;
}
},
methods: {
colorChanged(value){
this.$emit('color-changed', value);
},
getPropertyName,
}
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -46,7 +46,7 @@
v-show="showExpanded"
class="pl-3"
>
<v-fade-transition leave-absolute>
<v-fade-transition hide-on-leave>
<tree-node-list
v-if="showExpanded"
:node="node"

View File

@@ -2,6 +2,7 @@
<v-tabs
v-if="creature"
slot="extension"
color="secondary"
:value="value"
centered
grow

View File

@@ -1,106 +1,100 @@
<template lang="html">
<div
class="tree-tab pa-4"
class="tree-tab pa-4 layout column align-center"
style="height: calc(100vh - 96px); display: flex;"
>
<v-card
class="layout row"
style="height: 100%;"
style="height: 100%; width: 100%; max-width: 1800px;"
data-id="creature-tree-card"
>
<div
class="layout column justify-start"
:style="
$vuetify.breakpoint.mdAndUp &&
'width: 320px; flex-shrink: 0; flex-grow: 0;'
"
>
<v-toolbar
flat
dense
>
<v-spacer />
<v-switch
v-if="context.editPermission !== false"
v-model="organize"
label="Organize"
class="mx-3"
:disabled="organizeDisabled"
style="flex-grow: 0; height: 32px;"
/>
<v-combobox
ref="searchBox"
slot="extension"
v-model="filterString"
:items="filterOptions"
prepend-inner-icon="search"
class="mx-4"
hide-no-data
hide-selected
multiple
clearable
small-chips
deletable-chips
/>
</v-toolbar>
<creature-properties-tree
class="pt-2 flex"
style="overflow-y: auto;"
:root="{collection: 'creatures', id: creatureId}"
:organize="organize"
:selected-node-id="selected"
:filter="filter"
@selected="clickNode"
/>
</div>
<template v-if="$vuetify.breakpoint.mdAndUp">
<v-divider vertical />
<div
class="flex layout column"
style="background-color: inherit; overflow: hidden;"
data-id="selected-node-card"
>
<tree-detail-layout>
<template slot="tree">
<v-toolbar
dense
flat
extended
dense
>
<v-fade-transition mode="out-in">
<div
:key="selectedProperty && selectedProperty._id"
class="title"
>
<property-icon
:key="selectedProperty && selectedProperty._id"
:type="selectedProperty && selectedProperty.type"
class="mr-2"
/>
{{ getPropertyName(selectedProperty && selectedProperty.type) }}
</div>
</v-fade-transition>
<v-spacer />
<v-btn
v-if="selectedProperty"
flat
icon
@click="editCreatureProperty"
>
<v-icon>create</v-icon>
</v-btn>
<v-switch
v-if="context.editPermission !== false"
v-model="organize"
label="Organize"
class="mx-3"
:disabled="organizeDisabled"
style="flex-grow: 0; height: 32px;"
/>
<v-combobox
ref="searchBox"
slot="extension"
v-model="filterString"
:items="filterOptions"
prepend-inner-icon="search"
class="mx-4"
hide-no-data
hide-selected
multiple
clearable
small-chips
deletable-chips
/>
</v-toolbar>
<v-card-text
class="flex"
style="overflow-y: auto"
<creature-properties-tree
class="pt-2 flex"
style="overflow-y: auto;"
:root="{collection: 'creatures', id: creatureId}"
:organize="organize"
:selected-node-id="selected"
:filter="filter"
@selected="clickNode"
/>
</template>
<template slot="detail">
<div
class="flex layout column"
style="background-color: inherit; overflow: hidden;"
data-id="selected-node-card"
>
<v-fade-transition mode="out-in">
<property-viewer
:key="selectedProperty && selectedProperty._id"
:model="selectedProperty"
/>
</v-fade-transition>
</v-card-text>
</div>
</template>
<v-toolbar
dense
flat
extended
>
<v-fade-transition mode="out-in">
<div
:key="selectedProperty && selectedProperty._id"
class="title"
>
<property-icon
:key="selectedProperty && selectedProperty._id"
:type="selectedProperty && selectedProperty.type"
class="mr-2"
/>
{{ getPropertyName(selectedProperty && selectedProperty.type) }}
</div>
</v-fade-transition>
<v-spacer />
<v-btn
v-if="selectedProperty"
flat
icon
@click="editCreatureProperty"
>
<v-icon>create</v-icon>
</v-btn>
</v-toolbar>
<v-card-text
class="flex"
style="overflow-y: auto"
>
<v-fade-transition mode="out-in">
<property-viewer
:key="selectedProperty && selectedProperty._id"
:model="selectedProperty"
/>
</v-fade-transition>
</v-card-text>
</div>
</template>
</tree-detail-layout>
</v-card>
<v-speed-dial
v-model="fab"
@@ -136,19 +130,22 @@
</template>
<script>
import TreeDetailLayout from '/imports/ui/components/TreeDetailLayout.vue';
import CreaturePropertiesTree from '/imports/ui/creature/creatureProperties/CreaturePropertiesTree.vue';
import PropertyViewer from '/imports/ui/properties/shared/PropertyViewer.vue';
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
import LabeledFab from '/imports/ui/components/LabeledFab.vue';
import CreatureProperties, {
insertProperty,
insertPropertyFromLibraryNode
} from '/imports/api/creature/CreatureProperties.js';
import PropertyViewer from '/imports/ui/properties/shared/PropertyViewer.vue';
import { setDocToLastOrder } from '/imports/api/parenting/order.js';
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
import LabeledFab from '/imports/ui/components/LabeledFab.vue';
export default {
components: {
TreeDetailLayout,
CreaturePropertiesTree,
PropertyViewer,
PropertyIcon,

View File

@@ -1,64 +1,72 @@
<template>
<v-layout column style="height: 100%;">
<v-toolbar :color="color || 'secondary'" dark class="base-dialog-toolbar" :flat="!offsetTop">
<v-btn icon flat @click="back">
<v-icon>arrow_back</v-icon>
</v-btn>
<slot name="toolbar"/>
<template v-if="$slots.edit">
<v-spacer/>
<v-btn icon flat @click="$emit('remove')" v-if="isEditing">
<v-icon>delete</v-icon>
</v-btn>
<v-btn icon flat @click="isEditing = !isEditing">
<v-icon>{{isEditing ? 'check' : 'create'}}</v-icon>
</v-btn>
</template>
</v-toolbar>
<template v-if="breadcrumbs">
<v-card-text>
example > bread > crumb
</v-card-text>
</template>
<div
v-if="$slots['unwrapped-content']"
class="unwrapped-content"
>
<slot name="unwrapped-content"/>
</div>
<v-card-text
v-if="!$slots['unwrapped-content']"
id="base-dialog-body"
v-scroll:#base-dialog-body="onScroll"
>
<v-tabs-items :value="isEditing ? 1 : 0" touchless>
<v-tab-item>
<slot/>
</v-tab-item>
<v-tab-item lazy>
<slot name="edit"/>
</v-tab-item>
</v-tabs-items>
</v-card-text>
<v-card-actions>
<slot name="actions"/>
</v-card-actions>
</v-layout>
<v-layout
column
style="height: 100%;"
>
<slot
name="replace-toolbar"
:flat="!offsetTop"
>
<v-toolbar
:color="computedColor"
:dark="isDark"
class="base-dialog-toolbar"
:flat="!offsetTop"
>
<v-btn
icon
flat
@click="back"
>
<v-icon>arrow_back</v-icon>
</v-btn>
<slot name="toolbar" />
</v-toolbar>
</slot>
<div
v-if="$slots['unwrapped-content']"
class="unwrapped-content"
>
<slot name="unwrapped-content" />
</div>
<v-card-text
v-if="!$slots['unwrapped-content']"
id="base-dialog-body"
v-scroll:#base-dialog-body="onScroll"
>
<slot />
</v-card-text>
<v-card-actions v-if="$slots.actions">
<slot name="actions" />
</v-card-actions>
</v-layout>
</template>
<script>
import store from "/imports/ui/vuexStore.js";
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
export default {
props: {
color: String,
breadcrumbs: Object,
overrideBackButton: Function,
color: {
type: String,
default: undefined,
},
overrideBackButton: {
type: Function,
default: undefined,
},
},
data(){ return {
offsetTop: 0,
isEditing: false,
}},
computed: {
isDark(){
return isDarkColor(this.computedColor);
},
computedColor(){
return this.color || this.$vuetify.theme.secondary;
}
},
methods: {
onScroll(e){
this.offsetTop = e.target.scrollTop
@@ -71,7 +79,7 @@
}
},
close(){
store.dispatch("popDialogStack");
this.$store.dispatch('popDialogStack');
},
},
}

View File

@@ -7,7 +7,7 @@ import InviteDialog from '/imports/ui/user/InviteDialog.vue';
import LibraryCreationDialog from '/imports/ui/library/LibraryCreationDialog.vue';
import LibraryEditDialog from '/imports/ui/library/LibraryEditDialog.vue';
import LibraryNodeCreationDialog from '/imports/ui/library/LibraryNodeCreationDialog.vue';
import LibraryNodeEditDialog from '/imports/ui/library/LibraryNodeEditDialog.vue';
import LibraryNodeDialog from '/imports/ui/library/LibraryNodeDialog.vue';
import ShareDialog from '/imports/ui/sharing/ShareDialog.vue';
import TierTooLowDialog from '/imports/ui/user/TierTooLowDialog.vue';
import UsernameDialog from '/imports/ui/user/UsernameDialog.vue';
@@ -23,7 +23,7 @@ export default {
LibraryCreationDialog,
LibraryEditDialog,
LibraryNodeCreationDialog,
LibraryNodeEditDialog,
LibraryNodeDialog,
ShareDialog,
TierTooLowDialog,
UsernameDialog,

View File

@@ -1,20 +1,20 @@
<template lang="html">
<div
class="layout row"
style="background-color: inherit;"
>
<tree-detail-layout>
<div
slot="tree"
class="layout column"
style="
background-color: inherit;
width: initial;
max-width: 100%;
min-width: 320px;
"
background-color: inherit;
width: initial;
max-width: 100%;
min-width: 320px;
height: 100%;
"
>
<v-toolbar
dense
flat
:color="selectedNode && selectedNode.color || 'secondary'"
:dark="isDarkColor(selectedNode && selectedNode.color || $vuetify.theme.secondary)"
>
<v-spacer />
<v-switch
@@ -28,91 +28,87 @@
edit-mode
:organize-mode="organize"
:selected-node-id="selected"
@selected="e => selected = e"
style="overflow-y: auto;"
@selected="clickNode"
/>
</div>
<v-divider vertical />
<div
style="width: 100%; background-color: inherit;"
slot="detail"
data-id="selected-node-card"
>
<v-toolbar
dense
flat
>
<property-icon
:type="selectedNode && selectedNode.type"
class="mr-2"
/>
<div class="title">
{{ getPropertyName(selectedNode && selectedNode.type) }}
</div>
<v-spacer />
<v-btn
v-if="selectedNode"
flat
icon
@click="editLibraryNode"
>
<v-icon>create</v-icon>
</v-btn>
</v-toolbar>
<v-card-text style="overflow-y: auto;">
<property-viewer :model="selectedNode" />
</v-card-text>
<library-node-dialog
:_id="selected"
embedded
@removed="selected = undefined"
/>
</div>
</div>
</tree-detail-layout>
</template>
<script>
import TreeDetailLayout from '/imports/ui/components/TreeDetailLayout.vue';
import LibraryBrowser from '/imports/ui/library/LibraryBrowser.vue';
import PropertyViewer from '/imports/ui/properties/shared/PropertyViewer.vue';
import LibraryNodeDialog from '/imports/ui/library/LibraryNodeDialog.vue';
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
import Libraries from '/imports/api/library/Libraries.js';
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
export default {
components: {
LibraryBrowser,
PropertyViewer,
PropertyIcon,
},
data(){ return {
organize: false,
selected: undefined,
};},
watch:{
selectedNode(val){
this.$emit('selected', val)
},
},
methods: {
editLibraryNode(){
this.$store.commit('pushDialogStack', {
component: 'library-node-edit-dialog',
elementId: 'selected-node-card',
data: {_id: this.selected},
});
},
getPropertyName,
},
meteor: {
$subscribe: {
'libraries': [],
},
libraries(){
return Libraries.find({}, {
sort: {name: 1}
}).fetch();
},
selectedNode(){
return LibraryNodes.findOne({
_id: this.selected,
removed: {$ne: true}
});
}
}
components: {
TreeDetailLayout,
LibraryBrowser,
LibraryNodeDialog,
},
data(){ return {
organize: false,
selected: undefined,
};},
watch:{
selectedNode(val){
this.$emit('selected', val)
},
},
methods: {
editLibraryNode(){
this.$store.commit('pushDialogStack', {
component: 'library-node-edit-dialog',
elementId: 'selected-node-card',
data: {_id: this.selected},
});
},
clickNode(id){
if (this.$vuetify.breakpoint.mdAndUp){
this.selected = id;
} else {
this.$store.commit('pushDialogStack', {
component: 'library-node-dialog',
elementId: `tree-node-${id}`,
data: {
_id: id,
},
});
}
},
getPropertyName,
isDarkColor,
},
meteor: {
$subscribe: {
'libraries': [],
},
libraries(){
return Libraries.find({}, {
sort: {name: 1}
}).fetch();
},
selectedNode(){
return LibraryNodes.findOne({
_id: this.selected,
removed: {$ne: true}
});
},
}
};
</script>

View File

@@ -0,0 +1,151 @@
<template lang="html">
<dialog-base :key="_id">
<template #replace-toolbar="{flat}">
<property-toolbar
:model="model"
:editing="editing"
:flat="flat"
@remove="remove"
@toggle-editing="editing = !editing"
@color-changed="value => change({path: ['color'], value})"
/>
</template>
<template v-if="model">
<component
:is="model.type + 'Form'"
v-if="editing"
class="library-node-form"
:model="model"
@change="change"
@push="push"
@pull="pull"
/>
<component
:is="model.type + 'Viewer'"
v-else-if="!editing && $options.components[model.type + 'Viewer']"
class="creature-property-viewer"
:model="model"
/>
<p v-else>
This property can't be viewed yet.
</p>
</template>
<div
v-if="!embedded"
slot="actions"
class="layout row justify-end"
>
<v-btn
flat
@click="$store.dispatch('popDialogStack')"
>
Done
</v-btn>
</div>
</dialog-base>
</template>
<script>
import LibraryNodes, {
updateLibraryNode,
pushToLibraryNode,
pullFromLibraryNode,
softRemoveLibraryNode,
} from '/imports/api/library/LibraryNodes.js';
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
import PropertyToolbar from '/imports/ui/components/propertyToolbar.vue';
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
import propertyFormIndex from '/imports/ui/properties/forms/shared/propertyFormIndex.js';
import propertyViewerIndex from '/imports/ui/properties/viewers/shared/propertyViewerIndex.js';
import { get } from 'lodash';
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
let formIndex = {};
for (let key in propertyFormIndex){
formIndex[key + 'Form'] = propertyFormIndex[key];
}
let viewerIndex = {};
for (let key in propertyViewerIndex){
formIndex[key + 'Viewer'] = propertyViewerIndex[key];
}
export default {
components: {
PropertyToolbar,
PropertyIcon,
DialogBase,
...formIndex,
...viewerIndex,
},
props: {
_id: String,
startInEditTab: Boolean,
embedded: Boolean, //This dialog is embedded in a page
},
reactiveProvide: {
name: 'context',
include: ['editPermission'],
},
data(){return {
editing: !!this.startInEditTab,
}},
meteor: {
model(){
return LibraryNodes.findOne(this._id);
},
editPermission(){
try {
assertDocEditPermission(this.model, Meteor.userId());
return true;
} catch (e) {
return false;
}
},
},
methods: {
getPropertyName,
change({path, value, ack}){
updateLibraryNode.call({_id: this._id, path, value}, (error) =>{
if (ack){
ack(error && error.reason || error);
} else if (error){
console.error(error);
}
});
},
push({path, value, ack}){
pushToLibraryNode.call({_id: this._id, path, value}, (error) =>{
if (ack){
ack(error && error.reason || error);
} else if (error){
console.error(error);
}
});
},
pull({path, ack}){
let itemId = get(this.model, path)._id;
path.pop();
pullFromLibraryNode.call({_id: this._id, path, itemId}, (error) =>{
if (ack){
ack(error && error.reason || error);
} else if (error){
console.error(error);
}
});
},
remove(){
softRemoveLibraryNode.call({_id: this._id});
if (this.embedded){
this.$emit('removed');
} else {
this.$store.dispatch('popDialogStack');
}
},
}
};
</script>
<style lang="css" scoped>
</style>

View File

@@ -1,114 +0,0 @@
<template lang="html">
<dialog-base>
<template slot="toolbar">
<property-icon
:type="model.type"
class="mr-2"
/>
<v-toolbar-title>
{{ getPropertyName(model.type) }}
</v-toolbar-title>
<v-spacer />
<v-btn
icon
flat
@click="remove"
>
<v-icon>delete</v-icon>
</v-btn>
</template>
<component
:is="model.type"
v-if="model"
class="library-node-form"
:model="model"
@change="change"
@push="push"
@pull="pull"
/>
<div
slot="actions"
class="layout row justify-end"
>
<v-btn
flat
@click="$store.dispatch('popDialogStack')"
>
Done
</v-btn>
</div>
</dialog-base>
</template>
<script>
import LibraryNodes, {
updateLibraryNode,
pushToLibraryNode,
pullFromLibraryNode,
softRemoveLibraryNode,
} from '/imports/api/library/LibraryNodes.js';
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
import propertyFormIndex from '/imports/ui/properties/forms/shared/propertyFormIndex.js';
import { get } from 'lodash';
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
export default {
components: {
...propertyFormIndex,
PropertyIcon,
DialogBase,
},
props: {
_id: String,
},
reactiveProvide: {
name: 'context',
include: ['debounceTime', 'editPermission'],
},
data(){return {
debounceTime: 0,
}},
meteor: {
model(){
return LibraryNodes.findOne(this._id);
},
editPermission(){
try {
assertDocEditPermission(this.model, Meteor.userId());
return true;
} catch (e) {
return false;
}
},
},
methods: {
getPropertyName,
change({path, value, ack}){
updateLibraryNode.call({_id: this._id, path, value}, (error) =>{
ack && ack(error && error.reason || error);
});
},
push({path, value, ack}){
pushToLibraryNode.call({_id: this._id, path, value}, (error) =>{
ack && ack(error && error.reason || error);
});
},
pull({path, ack}){
let itemId = get(this.model, path)._id;
path.pop();
pullFromLibraryNode.call({_id: this._id, path, itemId}, (error) =>{
ack && ack(error && error.reason || error);
});
},
remove(){
softRemoveLibraryNode.call({_id: this._id});
this.$store.dispatch('popDialogStack');
},
}
};
</script>
<style lang="css" scoped>
</style>

View File

@@ -1,6 +1,11 @@
<template lang="html">
<div>
<v-card class="ma-4">
<div
class="pa-4 layout column align-center"
style="height: calc(100vh - 96px); display: flex;"
>
<v-card
style="height: 100%; width: 100%; max-width: 1800px;"
>
<library-and-node />
</v-card>
</div>

View File

@@ -1,21 +1,21 @@
const theme = {
primary: "#B71C1C",
secondary: "#424242",
accent: "#B71C1C",
error: "#FF6D00",
warning: "#FFB300",
info: "#5C6BC0",
success: "#43A047",
primary: '#B71C1C',
secondary: '#424242',
accent: '#B71C1C',
error: '#FF6D00',
warning: '#FFB300',
info: '#5C6BC0',
success: '#43A047',
};
const darkTheme = {
primary: "#f44336",
secondary: "#424242",
accent: "#f44336",
error: "#FF6D00",
warning: "#FFB300",
info: "#5C6BC0",
success: "#43A047",
primary: '#f44336',
secondary: '#757575',
accent: '#f44336',
error: '#FF6D00',
warning: '#FFB300',
info: '#5C6BC0',
success: '#43A047',
};
export {theme, darkTheme};

View File

@@ -1,7 +1,9 @@
export function kebabToCamelCase(string){
if (!string) return string;
return string.replace(/-([a-z0-9])/g, g => g[1].toUpperCase());
};
}
export function camelToKebabCase(string){
if (!string) return string;
return string.replace(/([a-z][A-Z0-9])/g, g => g[0] + '-' + g[1].toLowerCase());
};
}