Compare commits
12 Commits
2.0-beta.3
...
2.0-beta.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a41b267364 | ||
|
|
dfb144b8dc | ||
|
|
2859bf0e00 | ||
|
|
469822d4d7 | ||
|
|
c7de96c8c3 | ||
|
|
f7cbee27f9 | ||
|
|
b61dd6e81a | ||
|
|
add0cac31d | ||
|
|
e9c643699c | ||
|
|
3ec0f9500c | ||
|
|
a55c1382b1 | ||
|
|
7571806cd0 |
@@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor';
|
|||||||
import { Mongo } from 'meteor/mongo';
|
import { Mongo } from 'meteor/mongo';
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema.js';
|
||||||
import ChildSchema, { RefSchema } from '/imports/api/parenting/ChildSchema.js';
|
import ChildSchema, { RefSchema } from '/imports/api/parenting/ChildSchema.js';
|
||||||
import { recomputeCreature } from '/imports/api/creature/computation/recomputeCreature.js';
|
import { recomputeCreature } from '/imports/api/creature/computation/recomputeCreature.js';
|
||||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||||
@@ -41,6 +42,7 @@ for (let key in propertySchemasIndex){
|
|||||||
let schema = new SimpleSchema({});
|
let schema = new SimpleSchema({});
|
||||||
schema.extend(propertySchemasIndex[key]);
|
schema.extend(propertySchemasIndex[key]);
|
||||||
schema.extend(CreaturePropertySchema);
|
schema.extend(CreaturePropertySchema);
|
||||||
|
schema.extend(ColorSchema);
|
||||||
schema.extend(ChildSchema);
|
schema.extend(ChildSchema);
|
||||||
schema.extend(SoftRemovableSchema);
|
schema.extend(SoftRemovableSchema);
|
||||||
CreatureProperties.attachSchema(schema, {
|
CreatureProperties.attachSchema(schema, {
|
||||||
@@ -72,6 +74,7 @@ const insertProperty = new ValidatedMethod({
|
|||||||
name: 'CreatureProperties.methods.insert',
|
name: 'CreatureProperties.methods.insert',
|
||||||
validate: null,
|
validate: null,
|
||||||
run({creatureProperty}) {
|
run({creatureProperty}) {
|
||||||
|
delete creatureProperty._id;
|
||||||
assertPropertyEditPermission(creatureProperty, this.userId);
|
assertPropertyEditPermission(creatureProperty, this.userId);
|
||||||
let _id = CreatureProperties.insert(creatureProperty);
|
let _id = CreatureProperties.insert(creatureProperty);
|
||||||
let property = CreatureProperties.findOne(_id);
|
let property = CreatureProperties.findOne(_id);
|
||||||
@@ -79,6 +82,23 @@ const insertProperty = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const duplicateProperty = new ValidatedMethod({
|
||||||
|
name: 'CreatureProperties.methods.duplicate',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
_id: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
}
|
||||||
|
}).validator(),
|
||||||
|
run({_id}) {
|
||||||
|
let creatureProperty = CreatureProperties.findOne(_id);
|
||||||
|
assertPropertyEditPermission(creatureProperty, this.userId);
|
||||||
|
delete creatureProperty._id;
|
||||||
|
CreatureProperties.insert(creatureProperty);
|
||||||
|
recomputeCreatures(creatureProperty);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const insertPropertyFromLibraryNode = new ValidatedMethod({
|
const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||||
name: 'CreatureProperties.methods.insertPropertyFromLibraryNode',
|
name: 'CreatureProperties.methods.insertPropertyFromLibraryNode',
|
||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
@@ -288,6 +308,7 @@ export default CreatureProperties;
|
|||||||
export {
|
export {
|
||||||
CreaturePropertySchema,
|
CreaturePropertySchema,
|
||||||
insertProperty,
|
insertProperty,
|
||||||
|
duplicateProperty,
|
||||||
insertPropertyFromLibraryNode,
|
insertPropertyFromLibraryNode,
|
||||||
updateProperty,
|
updateProperty,
|
||||||
damageProperty,
|
damageProperty,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function combineSkill(stat, aggregator, memo){
|
|||||||
|
|
||||||
if (typeof profBonus !== 'number' && memo.statsByVariableName['level']){
|
if (typeof profBonus !== 'number' && memo.statsByVariableName['level']){
|
||||||
let level = memo.statsByVariableName['level'].value;
|
let level = memo.statsByVariableName['level'].value;
|
||||||
profBonus = Math.floor(level / 4 + 1.75);
|
profBonus = Math.ceil(level / 4) + 1;
|
||||||
}
|
}
|
||||||
// Multiply the proficiency bonus by the actual proficiency
|
// Multiply the proficiency bonus by the actual proficiency
|
||||||
profBonus *= stat.proficiency;
|
profBonus *= stat.proficiency;
|
||||||
@@ -60,7 +60,6 @@ function combineSkill(stat, aggregator, memo){
|
|||||||
if (result < aggregator.min) result = aggregator.min;
|
if (result < aggregator.min) result = aggregator.min;
|
||||||
if (result > aggregator.max) result = aggregator.max;
|
if (result > aggregator.max) result = aggregator.max;
|
||||||
result = Math.floor(result);
|
result = Math.floor(result);
|
||||||
if (aggregator.base > result) result = aggregator.base;
|
|
||||||
stat.value = result;
|
stat.value = result;
|
||||||
// Advantage/disadvantage
|
// Advantage/disadvantage
|
||||||
if (aggregator.advantage && !aggregator.disadvantage){
|
if (aggregator.advantage && !aggregator.disadvantage){
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor';
|
|||||||
import { Mongo } from 'meteor/mongo';
|
import { Mongo } from 'meteor/mongo';
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema.js';
|
||||||
import ChildSchema from '/imports/api/parenting/ChildSchema.js';
|
import ChildSchema from '/imports/api/parenting/ChildSchema.js';
|
||||||
import propertySchemasIndex from '/imports/api/properties/propertySchemasIndex.js';
|
import propertySchemasIndex from '/imports/api/properties/propertySchemasIndex.js';
|
||||||
import Libraries from '/imports/api/library/Libraries.js';
|
import Libraries from '/imports/api/library/Libraries.js';
|
||||||
@@ -28,6 +29,7 @@ let LibraryNodeSchema = new SimpleSchema({
|
|||||||
for (let key in propertySchemasIndex){
|
for (let key in propertySchemasIndex){
|
||||||
let schema = new SimpleSchema({});
|
let schema = new SimpleSchema({});
|
||||||
schema.extend(LibraryNodeSchema);
|
schema.extend(LibraryNodeSchema);
|
||||||
|
schema.extend(ColorSchema);
|
||||||
schema.extend(propertySchemasIndex[key]);
|
schema.extend(propertySchemasIndex[key]);
|
||||||
schema.extend(ChildSchema);
|
schema.extend(ChildSchema);
|
||||||
schema.extend(SoftRemovableSchema);
|
schema.extend(SoftRemovableSchema);
|
||||||
@@ -52,11 +54,28 @@ const insertNode = new ValidatedMethod({
|
|||||||
name: 'LibraryNodes.methods.insert',
|
name: 'LibraryNodes.methods.insert',
|
||||||
validate: null,
|
validate: null,
|
||||||
run(libraryNode) {
|
run(libraryNode) {
|
||||||
|
delete libraryNode._id;
|
||||||
assertNodeEditPermission(libraryNode, this.userId);
|
assertNodeEditPermission(libraryNode, this.userId);
|
||||||
return LibraryNodes.insert(libraryNode);
|
return LibraryNodes.insert(libraryNode);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const duplicateNode = new ValidatedMethod({
|
||||||
|
name: 'LibraryNodes.methods.duplicate',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
_id: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
}
|
||||||
|
}).validator(),
|
||||||
|
run({_id}) {
|
||||||
|
let libraryNode = LibraryNodes.findOne(_id);
|
||||||
|
assertNodeEditPermission(libraryNode, this.userId);
|
||||||
|
delete libraryNode._id;
|
||||||
|
return LibraryNodes.insert(libraryNode);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const updateLibraryNode = new ValidatedMethod({
|
const updateLibraryNode = new ValidatedMethod({
|
||||||
name: 'LibraryNodes.methods.update',
|
name: 'LibraryNodes.methods.update',
|
||||||
validate({_id, path}){
|
validate({_id, path}){
|
||||||
@@ -132,6 +151,7 @@ export default LibraryNodes;
|
|||||||
export {
|
export {
|
||||||
LibraryNodeSchema,
|
LibraryNodeSchema,
|
||||||
insertNode,
|
insertNode,
|
||||||
|
duplicateNode,
|
||||||
updateLibraryNode,
|
updateLibraryNode,
|
||||||
pullFromLibraryNode,
|
pullFromLibraryNode,
|
||||||
pushToLibraryNode,
|
pushToLibraryNode,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ let ChildSchema = new SimpleSchema({
|
|||||||
ancestors: {
|
ancestors: {
|
||||||
type: Array,
|
type: Array,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
|
max: 100,
|
||||||
},
|
},
|
||||||
'ancestors.$': {
|
'ancestors.$': {
|
||||||
type: RefSchema,
|
type: RefSchema,
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export function reorderDocs({collection, ancestorId}){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (Meteor.isServer){
|
if (Meteor.isServer && bulkWrite.length){
|
||||||
collection.rawCollection().bulkWrite(
|
collection.rawCollection().bulkWrite(
|
||||||
bulkWrite,
|
bulkWrite,
|
||||||
{ordered : false},
|
{ordered : false},
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ const userSchema = new SimpleSchema({
|
|||||||
profile: {
|
profile: {
|
||||||
type: Object,
|
type: Object,
|
||||||
blackbox: true,
|
blackbox: true,
|
||||||
|
optional: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,193 +1,230 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-menu
|
<v-menu
|
||||||
:close-on-content-click="false"
|
v-model="opened"
|
||||||
|
:close-on-content-click="false"
|
||||||
transition="slide-y-transition"
|
transition="slide-y-transition"
|
||||||
lazy
|
lazy
|
||||||
v-model="opened"
|
left
|
||||||
>
|
>
|
||||||
<v-btn
|
<v-btn
|
||||||
slot="activator"
|
slot="activator"
|
||||||
icon
|
icon
|
||||||
>
|
>
|
||||||
<v-icon>format_paint</v-icon>
|
<v-icon>format_paint</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-card class="overflow-hidden">
|
<v-card class="overflow-hidden">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-item-group v-model="color" mandatory>
|
<v-layout
|
||||||
<v-layout row wrap>
|
row
|
||||||
<v-item
|
wrap
|
||||||
v-for="kebabColorOption in colors"
|
>
|
||||||
:key="kebabColorOption"
|
<div
|
||||||
:value="kebabToCamelCase(kebabColorOption)"
|
v-for="colorOption in colors"
|
||||||
>
|
:key="colorOption"
|
||||||
<div
|
:class="[colorOption, shade]"
|
||||||
slot-scope="{ active, toggle }"
|
class="color-swatch d-flex align-center"
|
||||||
:class="[kebabColorOption, kebabShade]"
|
@click="color = colorOption"
|
||||||
class="color-swatch d-flex align-center"
|
>
|
||||||
@click="toggle"
|
<v-scroll-y-transition>
|
||||||
>
|
<v-icon
|
||||||
<v-scroll-y-transition>
|
v-if="kebabColor === colorOption"
|
||||||
<v-icon
|
:class="{dark: isDark(colorOption, shade)}"
|
||||||
v-if="active"
|
>
|
||||||
:class="{dark: isDark(kebabColorOption, kebabShade)}"
|
check
|
||||||
>check</v-icon>
|
</v-icon>
|
||||||
</v-scroll-y-transition>
|
</v-scroll-y-transition>
|
||||||
</div>
|
</div>
|
||||||
</v-item>
|
<div
|
||||||
<div class="spacer" v-for="i in 8"/>
|
v-for="i in 8"
|
||||||
</v-layout>
|
:key="i"
|
||||||
</v-item-group>
|
class="spacer"
|
||||||
<v-item-group class="mt-2" v-model="shade" mandatory>
|
/>
|
||||||
<v-layout wrap>
|
</v-layout>
|
||||||
<v-item
|
<v-fade-transition>
|
||||||
v-for="kebabShadeOption in shades"
|
<v-layout
|
||||||
:key="kebabShadeOption"
|
v-show="color"
|
||||||
:value="kebabToCamelCase(kebabShadeOption)"
|
wrap
|
||||||
>
|
class="mt-2"
|
||||||
<div
|
>
|
||||||
slot-scope="{ active, toggle }"
|
<div
|
||||||
:class="[kebabColor, kebabShadeOption]"
|
v-for="shadeOption in shades"
|
||||||
class="shade-swatch d-flex align-center"
|
:key="shadeOption"
|
||||||
@click="toggle"
|
:class="[kebabColor, shadeOption]"
|
||||||
>
|
class="shade-swatch d-flex align-center"
|
||||||
<v-scroll-y-transition>
|
@click="shade = shadeOption"
|
||||||
<v-icon
|
>
|
||||||
v-if="active"
|
<v-scroll-y-transition>
|
||||||
:class="{dark: isDark(kebabColor, kebabShadeOption)}"
|
<v-icon
|
||||||
>check</v-icon>
|
v-if="kebabShade === shadeOption"
|
||||||
</v-scroll-y-transition>
|
:class="{dark: isDark(color, shade)}"
|
||||||
</div>
|
>
|
||||||
</v-item>
|
check
|
||||||
<div class="spacer" v-for="i in 8"/>
|
</v-icon>
|
||||||
</v-layout>
|
</v-scroll-y-transition>
|
||||||
</v-item-group>
|
</div>
|
||||||
</v-card-text>
|
<div
|
||||||
<v-card-actions>
|
v-for="i in 8"
|
||||||
<v-spacer/>
|
:key="i"
|
||||||
<v-btn flat @click="opened = false">Done</v-btn>
|
class="spacer"
|
||||||
</v-card-actions>
|
/>
|
||||||
</v-card>
|
</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>
|
</v-menu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||||
import vuetifyColors from 'vuetify/es5/util/colors';
|
import vuetifyColors from 'vuetify/es5/util/colors';
|
||||||
import { kebabToCamelCase, camelToKebabCase } from '/imports/ui/utility/swapCase.js';
|
import { kebabToCamelCase, camelToKebabCase } from '/imports/ui/utility/swapCase.js';
|
||||||
|
|
||||||
function colorToHex(color, shade = 'base'){
|
function colorToHex(color, shade = 'base'){
|
||||||
color = kebabToCamelCase(color);
|
if (!color) return;
|
||||||
shade = kebabToCamelCase(shade);
|
color = kebabToCamelCase(color);
|
||||||
return vuetifyColors[color][shade];
|
shade = kebabToCamelCase(shade);
|
||||||
};
|
return vuetifyColors[color] && vuetifyColors[color][shade];
|
||||||
|
}
|
||||||
|
|
||||||
// Create an index of hex colors and what color/shade combination makes them
|
// Create an index of hex colors and what color/shade combination makes them
|
||||||
let colorIndex = {};
|
let colorIndex = {};
|
||||||
for (let color in vuetifyColors){
|
for (let color in vuetifyColors){
|
||||||
for (let shade in vuetifyColors[color]){
|
color = kebabToCamelCase(color);
|
||||||
colorIndex[vuetifyColors[color][shade]] = {color, shade};
|
for (let shade in vuetifyColors[color]){
|
||||||
}
|
shade = kebabToCamelCase(shade);
|
||||||
}
|
colorIndex[vuetifyColors[color][shade]] = {color, shade};
|
||||||
function hexToColor(hex){
|
}
|
||||||
return colorIndex[hex.toLowerCase()];
|
}
|
||||||
};
|
function hexToColor(hex){
|
||||||
|
if (!hex) return undefined;
|
||||||
|
return colorIndex[hex.toLowerCase()];
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
value: String, //hex string
|
//hex string
|
||||||
},
|
value: {
|
||||||
data(){ return {
|
type: String,
|
||||||
colors: [
|
default: undefined,
|
||||||
'red',
|
},
|
||||||
'pink',
|
},
|
||||||
'purple',
|
data(){ return {
|
||||||
'deep-purple',
|
colors: [
|
||||||
'indigo',
|
'red',
|
||||||
'blue',
|
'pink',
|
||||||
'light-blue',
|
'purple',
|
||||||
'cyan',
|
'deep-purple',
|
||||||
'teal',
|
'indigo',
|
||||||
'green',
|
'blue',
|
||||||
'light-green',
|
'light-blue',
|
||||||
'lime',
|
'cyan',
|
||||||
'yellow',
|
'teal',
|
||||||
'amber',
|
'green',
|
||||||
'orange',
|
'light-green',
|
||||||
'deep-orange',
|
'lime',
|
||||||
'brown',
|
'yellow',
|
||||||
'grey',
|
'amber',
|
||||||
],
|
'orange',
|
||||||
shades: [
|
'deep-orange',
|
||||||
'lighten-4',
|
'brown',
|
||||||
'lighten-3',
|
'grey',
|
||||||
'lighten-2',
|
],
|
||||||
'lighten-1',
|
shades: [
|
||||||
'base',
|
'lighten-4',
|
||||||
'darken-1',
|
'lighten-3',
|
||||||
'darken-2',
|
'lighten-2',
|
||||||
'darken-3',
|
'lighten-1',
|
||||||
'darken-4',
|
'base',
|
||||||
],
|
'darken-1',
|
||||||
opened: false,
|
'darken-2',
|
||||||
}},
|
'darken-3',
|
||||||
methods: {
|
'darken-4',
|
||||||
isDark(kebabColor, kebabShade){
|
],
|
||||||
color = colorToHex(kebabColor, kebabShade);
|
opened: false,
|
||||||
return isDarkColor(color);
|
}},
|
||||||
},
|
computed: {
|
||||||
kebabToCamelCase,
|
combination (){
|
||||||
},
|
if (!this.value) return;
|
||||||
computed: {
|
return hexToColor(this.value) || {};
|
||||||
combination (){
|
},
|
||||||
return hexToColor(this.value) || {};
|
color: {
|
||||||
},
|
get(){
|
||||||
color: {
|
return this.combination && this.combination.color;
|
||||||
get(){
|
},
|
||||||
return this.combination.color;
|
set(newColor){
|
||||||
},
|
this.$emit('input', colorToHex(newColor, this.shade));
|
||||||
set(newColor){
|
},
|
||||||
this.$emit('input', colorToHex(newColor, this.shade));
|
},
|
||||||
},
|
shade: {
|
||||||
},
|
get(){
|
||||||
shade: {
|
return this.combination && this.combination.shade;
|
||||||
get(){
|
},
|
||||||
return this.combination.shade;
|
set(newShade){
|
||||||
},
|
this.$emit('input', colorToHex(this.color, newShade));
|
||||||
set(newShade){
|
},
|
||||||
this.$emit('input', colorToHex(this.color, newShade));
|
},
|
||||||
},
|
kebabColor(){
|
||||||
},
|
return camelToKebabCase(this.color);
|
||||||
kebabColor(){
|
},
|
||||||
return camelToKebabCase(this.color);
|
kebabShade(){
|
||||||
},
|
return camelToKebabCase(this.shade);
|
||||||
kebabShade(){
|
},
|
||||||
return camelToKebabCase(this.shade);
|
},
|
||||||
},
|
methods: {
|
||||||
},
|
isDark(kebabColor, kebabShade){
|
||||||
};
|
let color = colorToHex(kebabColor, kebabShade);
|
||||||
|
return isDarkColor(color);
|
||||||
|
},
|
||||||
|
kebabToCamelCase,
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
.color-swatch, .shade-swatch {
|
.color-swatch, .shade-swatch {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
cursor: pointer;
|
||||||
.v-icon {
|
transition: all 0.2s linear;
|
||||||
height: 30px;
|
}
|
||||||
}
|
.color-swatch:hover{
|
||||||
.v-icon {
|
z-index: 1;
|
||||||
color: black;
|
transform: scale(1.1);
|
||||||
}
|
box-shadow: 0px 2px 1px -1px rgba(0,0,0,0.2),
|
||||||
.dark.v-icon {
|
0px 1px 1px 0px rgba(0,0,0,0.14),
|
||||||
color: white;
|
0px 1px 3px 0px rgba(0,0,0,0.12);
|
||||||
}
|
}
|
||||||
.layout {
|
.v-icon {
|
||||||
max-width: 270px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
.spacer {
|
.v-icon {
|
||||||
width: 30px;
|
color: black;
|
||||||
height: 0;
|
}
|
||||||
flex-grow: 1;
|
.dark.v-icon {
|
||||||
}
|
color: white;
|
||||||
|
}
|
||||||
|
.layout {
|
||||||
|
max-width: 270px;
|
||||||
|
}
|
||||||
|
.spacer {
|
||||||
|
width: 30px;
|
||||||
|
height: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
43
app/imports/ui/components/TreeDetailLayout.vue
Normal file
43
app/imports/ui/components/TreeDetailLayout.vue
Normal 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>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
props: {
|
props: {
|
||||||
autoGrow: {
|
autoGrow: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
156
app/imports/ui/components/propertyToolbar.vue
Normal file
156
app/imports/ui/components/propertyToolbar.vue
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<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="$listeners && $listeners['color-changed']"
|
||||||
|
:value="model.color"
|
||||||
|
@input="colorChanged"
|
||||||
|
/>
|
||||||
|
<v-menu
|
||||||
|
v-if="$listeners && (
|
||||||
|
$listeners.move ||
|
||||||
|
$listeners.duplicate ||
|
||||||
|
$listeners.remove
|
||||||
|
)"
|
||||||
|
bottom
|
||||||
|
left
|
||||||
|
transition="slide-y-transition"
|
||||||
|
>
|
||||||
|
<template #activator="{ on }">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
data-id="property-toolbar-menu-button"
|
||||||
|
v-on="on"
|
||||||
|
>
|
||||||
|
<v-icon>more_vert</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-list>
|
||||||
|
<v-list-tile
|
||||||
|
v-if="$listeners && $listeners.duplicate"
|
||||||
|
@click="$emit('duplicate')"
|
||||||
|
>
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title>
|
||||||
|
Duplicate
|
||||||
|
</v-list-tile-title>
|
||||||
|
</v-list-tile-content>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-icon>file_copy</v-icon>
|
||||||
|
</v-list-tile-action>
|
||||||
|
</v-list-tile>
|
||||||
|
<v-list-tile
|
||||||
|
v-if="$listeners && $listeners.move"
|
||||||
|
@click="$emit('move')"
|
||||||
|
>
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title>
|
||||||
|
Move
|
||||||
|
</v-list-tile-title>
|
||||||
|
</v-list-tile-content>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-icon>send</v-icon>
|
||||||
|
</v-list-tile-action>
|
||||||
|
</v-list-tile>
|
||||||
|
<v-list-tile
|
||||||
|
v-if="$listeners && $listeners.remove"
|
||||||
|
@click="$emit('remove')"
|
||||||
|
>
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title>
|
||||||
|
Delete
|
||||||
|
</v-list-tile-title>
|
||||||
|
</v-list-tile-content>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-icon>delete</v-icon>
|
||||||
|
</v-list-tile-action>
|
||||||
|
</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>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
v-show="showExpanded"
|
v-show="showExpanded"
|
||||||
class="pl-3"
|
class="pl-3"
|
||||||
>
|
>
|
||||||
<v-fade-transition leave-absolute>
|
<v-fade-transition hide-on-leave>
|
||||||
<tree-node-list
|
<tree-node-list
|
||||||
v-if="showExpanded"
|
v-if="showExpanded"
|
||||||
:node="node"
|
:node="node"
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
class="fill-height"
|
class="fill-height"
|
||||||
>
|
>
|
||||||
<v-tabs-items
|
<v-tabs-items
|
||||||
v-model="tabs"
|
v-model="activeTab"
|
||||||
>
|
>
|
||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
<stats-tab :creature-id="creatureId" />
|
<stats-tab :creature-id="creatureId" />
|
||||||
@@ -103,6 +103,16 @@
|
|||||||
'creature.name'(value){
|
'creature.name'(value){
|
||||||
this.$store.commit('setPageTitle', value || 'Character Sheet');
|
this.$store.commit('setPageTitle', value || 'Character Sheet');
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
activeTab: {
|
||||||
|
get(){
|
||||||
|
return this.tabs;
|
||||||
|
},
|
||||||
|
set(newTab){
|
||||||
|
this.$emit('update:tabs', newTab);
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
$subscribe: {
|
$subscribe: {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<v-tabs
|
<v-tabs
|
||||||
v-if="creature"
|
v-if="creature"
|
||||||
slot="extension"
|
slot="extension"
|
||||||
|
color="secondary"
|
||||||
:value="value"
|
:value="value"
|
||||||
centered
|
centered
|
||||||
grow
|
grow
|
||||||
|
|||||||
@@ -1,106 +1,60 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div
|
<div
|
||||||
class="tree-tab pa-4"
|
class="tree-tab pa-4 layout column align-center"
|
||||||
style="height: calc(100vh - 96px); display: flex;"
|
style="height: calc(100vh - 96px); display: flex;"
|
||||||
>
|
>
|
||||||
<v-card
|
<v-card
|
||||||
class="layout row"
|
style="height: 100%; width: 100%; max-width: 1800px;"
|
||||||
style="height: 100%;"
|
|
||||||
data-id="creature-tree-card"
|
data-id="creature-tree-card"
|
||||||
>
|
>
|
||||||
<div
|
<tree-detail-layout>
|
||||||
class="layout column justify-start"
|
<template slot="tree">
|
||||||
: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"
|
|
||||||
>
|
|
||||||
<v-toolbar
|
<v-toolbar
|
||||||
dense
|
|
||||||
flat
|
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-spacer />
|
||||||
<v-btn
|
<v-switch
|
||||||
v-if="selectedProperty"
|
v-if="context.editPermission !== false"
|
||||||
flat
|
v-model="organize"
|
||||||
icon
|
label="Organize"
|
||||||
@click="editCreatureProperty"
|
class="mx-3"
|
||||||
>
|
:disabled="organizeDisabled"
|
||||||
<v-icon>create</v-icon>
|
style="flex-grow: 0; height: 32px;"
|
||||||
</v-btn>
|
/>
|
||||||
|
<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-toolbar>
|
||||||
<v-card-text
|
<creature-properties-tree
|
||||||
class="flex"
|
class="pt-2 flex"
|
||||||
style="overflow-y: auto"
|
style="overflow-y: auto;"
|
||||||
>
|
:root="{collection: 'creatures', id: creatureId}"
|
||||||
<v-fade-transition mode="out-in">
|
:organize="organize"
|
||||||
<property-viewer
|
:selected-node-id="selected"
|
||||||
:key="selectedProperty && selectedProperty._id"
|
:filter="filter"
|
||||||
:model="selectedProperty"
|
@selected="clickNode"
|
||||||
/>
|
/>
|
||||||
</v-fade-transition>
|
</template>
|
||||||
</v-card-text>
|
<template slot="detail">
|
||||||
</div>
|
<creature-property-dialog
|
||||||
</template>
|
embedded
|
||||||
|
:_id="selected"
|
||||||
|
@removed="selected = undefined"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</tree-detail-layout>
|
||||||
</v-card>
|
</v-card>
|
||||||
<v-speed-dial
|
<v-speed-dial
|
||||||
v-model="fab"
|
v-model="fab"
|
||||||
@@ -136,22 +90,23 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import TreeDetailLayout from '/imports/ui/components/TreeDetailLayout.vue';
|
||||||
import CreaturePropertiesTree from '/imports/ui/creature/creatureProperties/CreaturePropertiesTree.vue';
|
import CreaturePropertiesTree from '/imports/ui/creature/creatureProperties/CreaturePropertiesTree.vue';
|
||||||
|
import CreaturePropertyDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue';
|
||||||
|
import LabeledFab from '/imports/ui/components/LabeledFab.vue';
|
||||||
|
|
||||||
import CreatureProperties, {
|
import CreatureProperties, {
|
||||||
insertProperty,
|
insertProperty,
|
||||||
insertPropertyFromLibraryNode
|
insertPropertyFromLibraryNode
|
||||||
} from '/imports/api/creature/CreatureProperties.js';
|
} from '/imports/api/creature/CreatureProperties.js';
|
||||||
import PropertyViewer from '/imports/ui/properties/shared/PropertyViewer.vue';
|
|
||||||
import { setDocToLastOrder } from '/imports/api/parenting/order.js';
|
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 { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
import LabeledFab from '/imports/ui/components/LabeledFab.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
TreeDetailLayout,
|
||||||
CreaturePropertiesTree,
|
CreaturePropertiesTree,
|
||||||
PropertyViewer,
|
CreaturePropertyDialog,
|
||||||
PropertyIcon,
|
|
||||||
LabeledFab,
|
LabeledFab,
|
||||||
},
|
},
|
||||||
inject: {
|
inject: {
|
||||||
|
|||||||
@@ -1,79 +1,40 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<dialog-base>
|
<dialog-base>
|
||||||
<template slot="toolbar">
|
<template #replace-toolbar="{flat}">
|
||||||
<property-icon
|
<property-toolbar
|
||||||
:type="model.type"
|
:model="model"
|
||||||
class="mr-2"
|
:editing="editing"
|
||||||
|
:flat="flat"
|
||||||
|
@duplicate="duplicate"
|
||||||
|
@remove="remove"
|
||||||
|
@toggle-editing="editing = !editing"
|
||||||
|
@color-changed="value => change({path: ['color'], value})"
|
||||||
/>
|
/>
|
||||||
<v-toolbar-title>
|
|
||||||
{{ model.name || getPropertyName(model.type) }}
|
|
||||||
</v-toolbar-title>
|
|
||||||
<v-spacer />
|
|
||||||
<v-menu
|
|
||||||
v-if="editing"
|
|
||||||
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="remove">
|
|
||||||
<v-list-tile-title>
|
|
||||||
Delete <v-icon>delete</v-icon>
|
|
||||||
</v-list-tile-title>
|
|
||||||
</v-list-tile>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
@click="editing = !editing"
|
|
||||||
>
|
|
||||||
<v-slide-y-transition
|
|
||||||
leave-absolute
|
|
||||||
mode="out-in"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-if="model">
|
<template v-if="model">
|
||||||
<component
|
<v-fade-transition
|
||||||
:is="model.type + 'Form'"
|
mode="out-in"
|
||||||
v-if="editing"
|
>
|
||||||
class="creature-property-form"
|
<component
|
||||||
:model="model"
|
:is="model.type + 'Form'"
|
||||||
@change="change"
|
v-if="editing"
|
||||||
@push="push"
|
class="creature-property-form"
|
||||||
@pull="pull"
|
:model="model"
|
||||||
/>
|
@change="change"
|
||||||
<component
|
@push="push"
|
||||||
:is="model.type + 'Viewer'"
|
@pull="pull"
|
||||||
v-else-if="!editing && $options.components[model.type + 'Viewer']"
|
/>
|
||||||
class="creature-property-viewer"
|
<component
|
||||||
:model="model"
|
:is="model.type + 'Viewer'"
|
||||||
/>
|
v-else-if="!editing && $options.components[model.type + 'Viewer']"
|
||||||
<p v-else>
|
class="creature-property-viewer"
|
||||||
This property can't be viewed yet.
|
:model="model"
|
||||||
</p>
|
/>
|
||||||
<template v-if="!editing">
|
<p v-else>
|
||||||
|
This property can't be viewed yet.
|
||||||
|
</p>
|
||||||
|
</v-fade-transition>
|
||||||
|
<template v-if="!editing && !embedded">
|
||||||
<v-divider />
|
<v-divider />
|
||||||
<creature-properties-tree
|
<creature-properties-tree
|
||||||
v-if="!editing"
|
v-if="!editing"
|
||||||
@@ -83,6 +44,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
|
v-if="!embedded"
|
||||||
slot="actions"
|
slot="actions"
|
||||||
class="layout row justify-end"
|
class="layout row justify-end"
|
||||||
>
|
>
|
||||||
@@ -100,11 +62,13 @@
|
|||||||
import CreatureProperties, {
|
import CreatureProperties, {
|
||||||
updateProperty,
|
updateProperty,
|
||||||
damageProperty,
|
damageProperty,
|
||||||
|
duplicateProperty,
|
||||||
pushToProperty,
|
pushToProperty,
|
||||||
pullFromProperty,
|
pullFromProperty,
|
||||||
softRemoveProperty,
|
softRemoveProperty,
|
||||||
} from '/imports/api/creature/CreatureProperties.js';
|
} from '/imports/api/creature/CreatureProperties.js';
|
||||||
import Creatures from '/imports/api/creature/Creatures.js';
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
|
import PropertyToolbar from '/imports/ui/components/propertyToolbar.vue';
|
||||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
||||||
@@ -130,10 +94,12 @@ export default {
|
|||||||
...viewerIndex,
|
...viewerIndex,
|
||||||
PropertyIcon,
|
PropertyIcon,
|
||||||
DialogBase,
|
DialogBase,
|
||||||
|
PropertyToolbar,
|
||||||
CreaturePropertiesTree,
|
CreaturePropertiesTree,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
_id: String,
|
_id: String,
|
||||||
|
embedded: Boolean, // This dialog is embedded in a page
|
||||||
startInEditTab: Boolean,
|
startInEditTab: Boolean,
|
||||||
},
|
},
|
||||||
data(){ return {
|
data(){ return {
|
||||||
@@ -169,8 +135,26 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getPropertyName,
|
getPropertyName,
|
||||||
|
duplicate(){
|
||||||
|
duplicateProperty.call({_id: this._id}, (error) => {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
if (this.embedded){
|
||||||
|
this.$emit('duplicated');
|
||||||
|
} else {
|
||||||
|
this.$store.dispatch('popDialogStack');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
change({path, value, ack}){
|
change({path, value, ack}){
|
||||||
updateProperty.call({_id: this._id, path, value}, (error) =>{
|
updateProperty.call({_id: this._id, path, value}, (error) =>{
|
||||||
|
if (error) console.warn(error);
|
||||||
|
ack && ack(error && error.reason || error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
damage({operation, value, ack}){
|
||||||
|
damageProperty.call({_id: this._id, operation, value}, (error) =>{
|
||||||
if (error) console.warn(error);
|
if (error) console.warn(error);
|
||||||
ack && ack(error && error.reason || error);
|
ack && ack(error && error.reason || error);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<library-and-node
|
<library-and-node
|
||||||
slot="unwrapped-content"
|
slot="unwrapped-content"
|
||||||
style="height: 100%;"
|
style="height: 100%;"
|
||||||
|
selection
|
||||||
@selected="val => node = val"
|
@selected="val => node = val"
|
||||||
/>
|
/>
|
||||||
<template slot="actions">
|
<template slot="actions">
|
||||||
|
|||||||
@@ -1,64 +1,72 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-layout column style="height: 100%;">
|
<v-layout
|
||||||
<v-toolbar :color="color || 'secondary'" dark class="base-dialog-toolbar" :flat="!offsetTop">
|
column
|
||||||
<v-btn icon flat @click="back">
|
style="height: 100%;"
|
||||||
<v-icon>arrow_back</v-icon>
|
>
|
||||||
</v-btn>
|
<slot
|
||||||
<slot name="toolbar"/>
|
name="replace-toolbar"
|
||||||
<template v-if="$slots.edit">
|
:flat="!offsetTop"
|
||||||
<v-spacer/>
|
>
|
||||||
<v-btn icon flat @click="$emit('remove')" v-if="isEditing">
|
<v-toolbar
|
||||||
<v-icon>delete</v-icon>
|
:color="computedColor"
|
||||||
</v-btn>
|
:dark="isDark"
|
||||||
<v-btn icon flat @click="isEditing = !isEditing">
|
class="base-dialog-toolbar"
|
||||||
<v-icon>{{isEditing ? 'check' : 'create'}}</v-icon>
|
:flat="!offsetTop"
|
||||||
</v-btn>
|
>
|
||||||
</template>
|
<v-btn
|
||||||
</v-toolbar>
|
icon
|
||||||
<template v-if="breadcrumbs">
|
flat
|
||||||
<v-card-text>
|
@click="back"
|
||||||
example > bread > crumb
|
>
|
||||||
</v-card-text>
|
<v-icon>arrow_back</v-icon>
|
||||||
</template>
|
</v-btn>
|
||||||
<div
|
<slot name="toolbar" />
|
||||||
v-if="$slots['unwrapped-content']"
|
</v-toolbar>
|
||||||
class="unwrapped-content"
|
</slot>
|
||||||
>
|
<div
|
||||||
<slot name="unwrapped-content"/>
|
v-if="$slots['unwrapped-content']"
|
||||||
</div>
|
class="unwrapped-content"
|
||||||
<v-card-text
|
>
|
||||||
v-if="!$slots['unwrapped-content']"
|
<slot name="unwrapped-content" />
|
||||||
id="base-dialog-body"
|
</div>
|
||||||
v-scroll:#base-dialog-body="onScroll"
|
<v-card-text
|
||||||
>
|
v-if="!$slots['unwrapped-content']"
|
||||||
<v-tabs-items :value="isEditing ? 1 : 0" touchless>
|
id="base-dialog-body"
|
||||||
<v-tab-item>
|
v-scroll:#base-dialog-body="onScroll"
|
||||||
<slot/>
|
>
|
||||||
</v-tab-item>
|
<slot />
|
||||||
<v-tab-item lazy>
|
</v-card-text>
|
||||||
<slot name="edit"/>
|
<v-card-actions v-if="$slots.actions">
|
||||||
</v-tab-item>
|
<slot name="actions" />
|
||||||
</v-tabs-items>
|
</v-card-actions>
|
||||||
</v-card-text>
|
</v-layout>
|
||||||
<v-card-actions>
|
|
||||||
<slot name="actions"/>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-layout>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import store from "/imports/ui/vuexStore.js";
|
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
color: String,
|
color: {
|
||||||
breadcrumbs: Object,
|
type: String,
|
||||||
overrideBackButton: Function,
|
default: undefined,
|
||||||
|
},
|
||||||
|
overrideBackButton: {
|
||||||
|
type: Function,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data(){ return {
|
data(){ return {
|
||||||
offsetTop: 0,
|
offsetTop: 0,
|
||||||
isEditing: false,
|
|
||||||
}},
|
}},
|
||||||
|
computed: {
|
||||||
|
isDark(){
|
||||||
|
return isDarkColor(this.computedColor);
|
||||||
|
},
|
||||||
|
computedColor(){
|
||||||
|
return this.color || this.$vuetify.theme.secondary;
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onScroll(e){
|
onScroll(e){
|
||||||
this.offsetTop = e.target.scrollTop
|
this.offsetTop = e.target.scrollTop
|
||||||
@@ -71,7 +79,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
close(){
|
close(){
|
||||||
store.dispatch("popDialogStack");
|
this.$store.dispatch('popDialogStack');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import InviteDialog from '/imports/ui/user/InviteDialog.vue';
|
|||||||
import LibraryCreationDialog from '/imports/ui/library/LibraryCreationDialog.vue';
|
import LibraryCreationDialog from '/imports/ui/library/LibraryCreationDialog.vue';
|
||||||
import LibraryEditDialog from '/imports/ui/library/LibraryEditDialog.vue';
|
import LibraryEditDialog from '/imports/ui/library/LibraryEditDialog.vue';
|
||||||
import LibraryNodeCreationDialog from '/imports/ui/library/LibraryNodeCreationDialog.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 MoveLibraryNodeDialog from '/imports/ui/library/MoveLibraryNodeDialog.vue'
|
||||||
import ShareDialog from '/imports/ui/sharing/ShareDialog.vue';
|
import ShareDialog from '/imports/ui/sharing/ShareDialog.vue';
|
||||||
import TierTooLowDialog from '/imports/ui/user/TierTooLowDialog.vue';
|
import TierTooLowDialog from '/imports/ui/user/TierTooLowDialog.vue';
|
||||||
import UsernameDialog from '/imports/ui/user/UsernameDialog.vue';
|
import UsernameDialog from '/imports/ui/user/UsernameDialog.vue';
|
||||||
@@ -23,7 +24,8 @@ export default {
|
|||||||
LibraryCreationDialog,
|
LibraryCreationDialog,
|
||||||
LibraryEditDialog,
|
LibraryEditDialog,
|
||||||
LibraryNodeCreationDialog,
|
LibraryNodeCreationDialog,
|
||||||
LibraryNodeEditDialog,
|
LibraryNodeDialog,
|
||||||
|
MoveLibraryNodeDialog,
|
||||||
ShareDialog,
|
ShareDialog,
|
||||||
TierTooLowDialog,
|
TierTooLowDialog,
|
||||||
UsernameDialog,
|
UsernameDialog,
|
||||||
|
|||||||
@@ -54,20 +54,10 @@
|
|||||||
</v-fade-transition>
|
</v-fade-transition>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
<v-content>
|
<v-content>
|
||||||
<v-alert
|
|
||||||
v-if="$route.path !== '/countdown'"
|
|
||||||
icon="priority_high"
|
|
||||||
type="error"
|
|
||||||
dismissible
|
|
||||||
:value="true"
|
|
||||||
>
|
|
||||||
This version of DiceCloud is in beta. Some data stored here may be destroyed by
|
|
||||||
future updates.
|
|
||||||
</v-alert>
|
|
||||||
<v-fade-transition
|
<v-fade-transition
|
||||||
mode="out-in"
|
mode="out-in"
|
||||||
>
|
>
|
||||||
<router-view :tabs="tabs" />
|
<router-view :tabs.sync="tabs" />
|
||||||
</v-fade-transition>
|
</v-fade-transition>
|
||||||
</v-content>
|
</v-content>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
|
<v-alert
|
||||||
|
v-if="$route.path !== '/countdown'"
|
||||||
|
icon="priority_high"
|
||||||
|
type="error"
|
||||||
|
dismissible
|
||||||
|
:value="true"
|
||||||
|
>
|
||||||
|
This version of DiceCloud is in beta. Some data stored here may be destroyed by
|
||||||
|
future updates.
|
||||||
|
</v-alert>
|
||||||
<v-layout
|
<v-layout
|
||||||
v-if="!signedIn"
|
v-if="!signedIn"
|
||||||
row
|
row
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div
|
<tree-detail-layout>
|
||||||
class="layout row"
|
|
||||||
style="background-color: inherit;"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
|
slot="tree"
|
||||||
class="layout column"
|
class="layout column"
|
||||||
style="
|
style="
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
width: initial;
|
width: initial;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
"
|
height: 100%;
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<v-toolbar
|
<v-toolbar
|
||||||
dense
|
|
||||||
flat
|
flat
|
||||||
|
:color="selectedNode && selectedNode.color || 'secondary'"
|
||||||
|
:dark="isDarkColor(selectedNode && selectedNode.color || $vuetify.theme.secondary)"
|
||||||
>
|
>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-switch
|
<v-switch
|
||||||
@@ -28,91 +28,97 @@
|
|||||||
edit-mode
|
edit-mode
|
||||||
:organize-mode="organize"
|
:organize-mode="organize"
|
||||||
:selected-node-id="selected"
|
:selected-node-id="selected"
|
||||||
@selected="e => selected = e"
|
style="overflow-y: auto;"
|
||||||
|
@selected="clickNode"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<v-divider vertical />
|
|
||||||
<div
|
<div
|
||||||
style="width: 100%; background-color: inherit;"
|
slot="detail"
|
||||||
data-id="selected-node-card"
|
data-id="selected-node-card"
|
||||||
>
|
>
|
||||||
<v-toolbar
|
<library-node-dialog
|
||||||
dense
|
:_id="selected"
|
||||||
flat
|
embedded
|
||||||
>
|
@removed="selected = undefined"
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</tree-detail-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import TreeDetailLayout from '/imports/ui/components/TreeDetailLayout.vue';
|
||||||
import LibraryBrowser from '/imports/ui/library/LibraryBrowser.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 LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||||
import Libraries from '/imports/api/library/Libraries.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 { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
|
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
LibraryBrowser,
|
TreeDetailLayout,
|
||||||
PropertyViewer,
|
LibraryBrowser,
|
||||||
PropertyIcon,
|
LibraryNodeDialog,
|
||||||
},
|
},
|
||||||
data(){ return {
|
props: {
|
||||||
organize: false,
|
selection: Boolean,
|
||||||
selected: undefined,
|
},
|
||||||
};},
|
data(){ return {
|
||||||
watch:{
|
organize: false,
|
||||||
selectedNode(val){
|
selected: undefined,
|
||||||
this.$emit('selected', val)
|
};},
|
||||||
},
|
watch:{
|
||||||
},
|
selectedNode(val){
|
||||||
methods: {
|
this.$emit('selected', val)
|
||||||
editLibraryNode(){
|
},
|
||||||
this.$store.commit('pushDialogStack', {
|
},
|
||||||
component: 'library-node-edit-dialog',
|
methods: {
|
||||||
elementId: 'selected-node-card',
|
editLibraryNode(){
|
||||||
data: {_id: this.selected},
|
this.$store.commit('pushDialogStack', {
|
||||||
});
|
component: 'library-node-edit-dialog',
|
||||||
},
|
elementId: 'selected-node-card',
|
||||||
getPropertyName,
|
data: {_id: this.selected},
|
||||||
},
|
});
|
||||||
meteor: {
|
},
|
||||||
$subscribe: {
|
clickNode(id){
|
||||||
'libraries': [],
|
if (this.$vuetify.breakpoint.mdAndUp){
|
||||||
},
|
this.selected = id;
|
||||||
libraries(){
|
} else {
|
||||||
return Libraries.find({}, {
|
this.$store.commit('pushDialogStack', {
|
||||||
sort: {name: 1}
|
component: 'library-node-dialog',
|
||||||
}).fetch();
|
elementId: `tree-node-${id}`,
|
||||||
},
|
data: {
|
||||||
selectedNode(){
|
_id: id,
|
||||||
return LibraryNodes.findOne({
|
selection: this.selection,
|
||||||
_id: this.selected,
|
},
|
||||||
removed: {$ne: true}
|
callback: result => {
|
||||||
});
|
console.log(result)
|
||||||
}
|
if (result){
|
||||||
}
|
this.selected = 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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ import LibraryNodes, { insertNode } from '/imports/api/library/LibraryNodes.js';
|
|||||||
import Libraries, { insertLibrary } from '/imports/api/library/Libraries.js';
|
import Libraries, { insertLibrary } from '/imports/api/library/Libraries.js';
|
||||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||||
|
import { getAncestry } from '/imports/api/parenting/parenting.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -139,13 +140,20 @@ export default {
|
|||||||
},
|
},
|
||||||
insertLibraryNode(libraryId){
|
insertLibraryNode(libraryId){
|
||||||
if (this.paidBenefits){
|
if (this.paidBenefits){
|
||||||
|
let parentRef;
|
||||||
|
if (this.organizeMode && this.selectedNodeId){
|
||||||
|
parentRef = {collection: 'libraryNodes', id: this.selectedNodeId}
|
||||||
|
} else {
|
||||||
|
parentRef = {collection: 'libraries', id: libraryId};
|
||||||
|
}
|
||||||
|
let {ancestors} = getAncestry({parentRef});
|
||||||
this.$store.commit('pushDialogStack', {
|
this.$store.commit('pushDialogStack', {
|
||||||
component: 'library-node-creation-dialog',
|
component: 'library-node-creation-dialog',
|
||||||
elementId: `insert-node-${libraryId}`,
|
elementId: `insert-node-${libraryId}`,
|
||||||
callback(libraryNode){
|
callback(libraryNode){
|
||||||
if (!libraryNode) return;
|
if (!libraryNode) return;
|
||||||
libraryNode.parent = {collection: 'libraries', id: libraryId};
|
libraryNode.parent = parentRef;
|
||||||
libraryNode.ancestors = [ {collection: 'libraries', id: libraryId}];
|
libraryNode.ancestors = ancestors;
|
||||||
setDocToLastOrder({collection: LibraryNodes, doc: libraryNode});
|
setDocToLastOrder({collection: LibraryNodes, doc: libraryNode});
|
||||||
let libraryNodeId = insertNode.call(libraryNode);
|
let libraryNodeId = insertNode.call(libraryNode);
|
||||||
return `tree-node-${libraryNodeId}`;
|
return `tree-node-${libraryNodeId}`;
|
||||||
|
|||||||
209
app/imports/ui/library/LibraryNodeDialog.vue
Normal file
209
app/imports/ui/library/LibraryNodeDialog.vue
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<dialog-base>
|
||||||
|
<template #replace-toolbar="{flat}">
|
||||||
|
<property-toolbar
|
||||||
|
:model="model"
|
||||||
|
:editing="editing"
|
||||||
|
:flat="flat"
|
||||||
|
@duplicate="duplicate"
|
||||||
|
@move="move"
|
||||||
|
@remove="remove"
|
||||||
|
@toggle-editing="editing = !editing"
|
||||||
|
@color-changed="value => change({path: ['color'], value})"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-if="model">
|
||||||
|
<v-fade-transition
|
||||||
|
mode="out-in"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</v-fade-transition>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
v-if="!embedded"
|
||||||
|
slot="actions"
|
||||||
|
class="layout row justify-end"
|
||||||
|
>
|
||||||
|
<template v-if="selection">
|
||||||
|
<v-btn
|
||||||
|
flat
|
||||||
|
@click="$store.dispatch('popDialogStack', false)"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</v-btn>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
flat
|
||||||
|
@click="$store.dispatch('popDialogStack', true)"
|
||||||
|
>
|
||||||
|
Select
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-btn
|
||||||
|
v-else
|
||||||
|
flat
|
||||||
|
@click="$store.dispatch('popDialogStack')"
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</dialog-base>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import LibraryNodes, {
|
||||||
|
duplicateNode,
|
||||||
|
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';
|
||||||
|
import { organizeDoc } from '/imports/api/parenting/organizeMethods.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
|
||||||
|
selection: Boolean, // This dialog is being used to select a node
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
duplicate(){
|
||||||
|
duplicateNode.call({_id: this._id}, (error) => {
|
||||||
|
console.error(error);
|
||||||
|
if (this.embedded){
|
||||||
|
this.$emit('duplicated');
|
||||||
|
} else {
|
||||||
|
this.$store.dispatch('popDialogStack');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
move(){
|
||||||
|
let that = this;
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'move-library-node-dialog',
|
||||||
|
elementId: 'property-toolbar-menu-button',
|
||||||
|
callback(parentId){
|
||||||
|
if (!parentId) return;
|
||||||
|
organizeDoc.call({
|
||||||
|
docRef: {
|
||||||
|
collection: 'libraryNodes',
|
||||||
|
id: that._id,
|
||||||
|
},
|
||||||
|
parentRef: {
|
||||||
|
collection: 'libraryNodes',
|
||||||
|
id: parentId
|
||||||
|
},
|
||||||
|
order: -0.5
|
||||||
|
}, (error) => {
|
||||||
|
if (error) console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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>
|
||||||
@@ -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>
|
|
||||||
37
app/imports/ui/library/MoveLibraryNodeDialog.vue
Normal file
37
app/imports/ui/library/MoveLibraryNodeDialog.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<dialog-base>
|
||||||
|
<v-toolbar-title slot="toolbar">
|
||||||
|
Select new location
|
||||||
|
</v-toolbar-title>
|
||||||
|
<library-and-node
|
||||||
|
slot="unwrapped-content"
|
||||||
|
style="height: 100%;"
|
||||||
|
selection
|
||||||
|
@selected="val => node = val"
|
||||||
|
/>
|
||||||
|
<template slot="actions">
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
flat
|
||||||
|
color="primary"
|
||||||
|
@click="$store.dispatch('popDialogStack', node._id)"
|
||||||
|
>
|
||||||
|
Move
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</dialog-base>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
|
import LibraryAndNode from '/imports/ui/library/LibraryAndNode.vue';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
DialogBase,
|
||||||
|
LibraryAndNode,
|
||||||
|
},
|
||||||
|
data(){return {
|
||||||
|
node: undefined,
|
||||||
|
};},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<character-sheet
|
<character-sheet
|
||||||
show-menu-button
|
show-menu-button
|
||||||
:creature-id="$route.params.id"
|
:creature-id="$route.params.id"
|
||||||
:tabs="tabs"
|
:tabs.sync="activeTab"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -18,5 +18,15 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
activeTab: {
|
||||||
|
get(){
|
||||||
|
return this.tabs;
|
||||||
|
},
|
||||||
|
set(newTab){
|
||||||
|
this.$emit('update:tabs', newTab);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div>
|
<div
|
||||||
<v-card class="ma-4">
|
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 />
|
<library-and-node />
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-icon>{{icon}}</v-icon>
|
<v-icon :color="color">
|
||||||
|
{{ icon }}
|
||||||
|
</v-icon>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -8,6 +10,7 @@ import { getPropertyIcon } from '/imports/constants/PROPERTIES.js';
|
|||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
type: String,
|
type: String,
|
||||||
|
color: String,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
icon(){
|
icon(){
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
class="mr-2"
|
class="mr-2"
|
||||||
:type="model.type"
|
:type="model.type"
|
||||||
:class="selected && 'primary--text'"
|
:class="selected && 'primary--text'"
|
||||||
|
:color="model.color"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="text-no-wrap text-truncate"
|
class="text-no-wrap text-truncate"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
class="mr-2"
|
class="mr-2"
|
||||||
:type="model.type"
|
:type="model.type"
|
||||||
:class="selected && 'primary--text'"
|
:class="selected && 'primary--text'"
|
||||||
|
:color="model.color"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="text-no-wrap text-truncate"
|
class="text-no-wrap text-truncate"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<property-icon
|
<property-icon
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
:type="model.type"
|
:type="model.type"
|
||||||
|
:color="model.color"
|
||||||
:class="selected && 'primary--text'"
|
:class="selected && 'primary--text'"
|
||||||
/>
|
/>
|
||||||
<div class="text-no-wrap text-truncate">
|
<div class="text-no-wrap text-truncate">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<v-icon
|
<v-icon
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
:class="selected && 'primary--text'"
|
:class="selected && 'primary--text'"
|
||||||
|
:color="model.color"
|
||||||
>
|
>
|
||||||
{{ effectIcon }}
|
{{ effectIcon }}
|
||||||
</v-icon>
|
</v-icon>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<v-icon
|
<v-icon
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
:class="selected && 'primary--text'"
|
:class="selected && 'primary--text'"
|
||||||
|
:color="model.color"
|
||||||
>
|
>
|
||||||
{{ model.equipped ? 'check_box' : 'check_box_outline_blank' }}
|
{{ model.equipped ? 'check_box' : 'check_box_outline_blank' }}
|
||||||
</v-icon>
|
</v-icon>
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
const theme = {
|
const theme = {
|
||||||
primary: "#B71C1C",
|
primary: '#B71C1C',
|
||||||
secondary: "#424242",
|
secondary: '#424242',
|
||||||
accent: "#B71C1C",
|
accent: '#B71C1C',
|
||||||
error: "#FF6D00",
|
error: '#FF6D00',
|
||||||
warning: "#FFB300",
|
warning: '#FFB300',
|
||||||
info: "#5C6BC0",
|
info: '#5C6BC0',
|
||||||
success: "#43A047",
|
success: '#43A047',
|
||||||
};
|
};
|
||||||
|
|
||||||
const darkTheme = {
|
const darkTheme = {
|
||||||
primary: "#f44336",
|
primary: '#f44336',
|
||||||
secondary: "#424242",
|
secondary: '#757575',
|
||||||
accent: "#f44336",
|
accent: '#f44336',
|
||||||
error: "#FF6D00",
|
error: '#FF6D00',
|
||||||
warning: "#FFB300",
|
warning: '#FFB300',
|
||||||
info: "#5C6BC0",
|
info: '#5C6BC0',
|
||||||
success: "#43A047",
|
success: '#43A047',
|
||||||
};
|
};
|
||||||
|
|
||||||
export {theme, darkTheme};
|
export {theme, darkTheme};
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
export function kebabToCamelCase(string){
|
export function kebabToCamelCase(string){
|
||||||
|
if (!string) return string;
|
||||||
return string.replace(/-([a-z0-9])/g, g => g[1].toUpperCase());
|
return string.replace(/-([a-z0-9])/g, g => g[1].toUpperCase());
|
||||||
};
|
}
|
||||||
|
|
||||||
export function camelToKebabCase(string){
|
export function camelToKebabCase(string){
|
||||||
|
if (!string) return string;
|
||||||
return string.replace(/([a-z][A-Z0-9])/g, g => g[0] + '-' + g[1].toLowerCase());
|
return string.replace(/([a-z][A-Z0-9])/g, g => g[0] + '-' + g[1].toLowerCase());
|
||||||
};
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user