Can now move creatures between folders using drag and drop
This commit is contained in:
@@ -5,7 +5,6 @@ let CreatureFolders = new Mongo.Collection('creatureFolders');
|
|||||||
let creatureFolderSchema = new SimpleSchema({
|
let creatureFolderSchema = new SimpleSchema({
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
defaultValue: 'Folder',
|
|
||||||
trim: false,
|
trim: false,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
import '/imports/api/creature/creatureFolders/methods.js/insertCreatureFolder.js';
|
import '/imports/api/creature/creatureFolders/methods.js/insertCreatureFolder.js';
|
||||||
import '/imports/api/creature/creatureFolders/methods.js/updateCreatureFolderName.js';
|
import '/imports/api/creature/creatureFolders/methods.js/updateCreatureFolderName.js';
|
||||||
import '/imports/api/creature/creatureFolders/methods.js/removeCreatureFolder.js';
|
import '/imports/api/creature/creatureFolders/methods.js/removeCreatureFolder.js';
|
||||||
|
import '/imports/api/creature/creatureFolders/methods.js/moveCreatureToFolder.js';
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const insertCreatureFolder = new ValidatedMethod({
|
|||||||
}
|
}
|
||||||
// Insert
|
// Insert
|
||||||
return CreatureFolders.insert({
|
return CreatureFolders.insert({
|
||||||
|
name: 'Folder',
|
||||||
owner: userId,
|
owner: userId,
|
||||||
order,
|
order,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
|
||||||
|
const moveCreatureToFolder = new ValidatedMethod({
|
||||||
|
name: 'creatureFolders.methods.moveCreatureToFolder',
|
||||||
|
validate: null,
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({creatureId, folderId}) {
|
||||||
|
// Ensure logged in
|
||||||
|
let userId = this.userId;
|
||||||
|
if (!userId) {
|
||||||
|
throw new Meteor.Error('creatureFolders.methods.updateName.denied',
|
||||||
|
'You need to be logged in to remove a folder');
|
||||||
|
}
|
||||||
|
// Check that this folder is owned by the user
|
||||||
|
if (folderId){
|
||||||
|
let existingFolder = CreatureFolders.findOne(folderId);
|
||||||
|
if (existingFolder.owner !== userId){
|
||||||
|
throw new Meteor.Error('creatureFolders.methods.updateName.denied',
|
||||||
|
'This folder does not belong to you');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove from other folders
|
||||||
|
CreatureFolders.update({
|
||||||
|
owner: userId
|
||||||
|
}, {
|
||||||
|
$pull: {creatures: creatureId},
|
||||||
|
}, {
|
||||||
|
multi: true,
|
||||||
|
});
|
||||||
|
if (folderId){
|
||||||
|
// Add to this folder
|
||||||
|
CreatureFolders.update(folderId, {
|
||||||
|
$addToSet: {creatures: creatureId},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default moveCreatureToFolder;
|
||||||
91
app/imports/ui/creature/creatureList/CreatureList.vue
Normal file
91
app/imports/ui/creature/creatureList/CreatureList.vue
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<v-list>
|
||||||
|
<draggable
|
||||||
|
v-model="dataCreatures"
|
||||||
|
style="min-height: 24px;"
|
||||||
|
:sort="false"
|
||||||
|
:group="`creature-list`"
|
||||||
|
ghost-class="ghost"
|
||||||
|
draggable=".creature"
|
||||||
|
handle=".handle"
|
||||||
|
:animation="200"
|
||||||
|
@change="change"
|
||||||
|
>
|
||||||
|
<creature-list-tile
|
||||||
|
v-for="creature in dataCreatures"
|
||||||
|
:key="creature._id"
|
||||||
|
class="creature"
|
||||||
|
:to="creature.url"
|
||||||
|
:model="creature"
|
||||||
|
/>
|
||||||
|
</draggable>
|
||||||
|
</v-list>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import CreatureListTile from '/imports/ui/creature/creatureList/CreatureListTile.vue';
|
||||||
|
import draggable from 'vuedraggable';
|
||||||
|
import moveCreatureToFolder from '/imports/api/creature/creatureFolders/methods.js/moveCreatureToFolder.js';
|
||||||
|
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
CreatureListTile,
|
||||||
|
draggable,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
creatures: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
folderId: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data(){return {
|
||||||
|
dataCreatures: [],
|
||||||
|
}},
|
||||||
|
watch:{
|
||||||
|
creatures(newValue){
|
||||||
|
this.dataCreatures = newValue;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted(){
|
||||||
|
this.dataCreatures = this.creatures;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
change({added, moved}){
|
||||||
|
let event = added || moved;
|
||||||
|
if (event){
|
||||||
|
// If this item is now adjacent to another, set the order accordingly
|
||||||
|
let order;
|
||||||
|
let before = this.dataCreatures[event.newIndex - 1];
|
||||||
|
let after = this.dataCreatures[event.newIndex + 1];
|
||||||
|
if (before && before._id){
|
||||||
|
order = before.order + 0.5;
|
||||||
|
} else if (after && after._id) {
|
||||||
|
order = after.order - 0.5;
|
||||||
|
} else {
|
||||||
|
order = -0.5;
|
||||||
|
}
|
||||||
|
let doc = event.element;
|
||||||
|
console.log({doc, order, newIndex: event.newIndex, before, after});
|
||||||
|
moveCreatureToFolder.call({
|
||||||
|
creatureId: doc._id,
|
||||||
|
folderId: this.folderId
|
||||||
|
}, error => {
|
||||||
|
if (!error) return;
|
||||||
|
console.error(error);
|
||||||
|
snackbar({
|
||||||
|
text: error.reason,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -21,11 +21,18 @@
|
|||||||
{{ model.alignment }} {{ model.gender }} {{ model.race }}
|
{{ model.alignment }} {{ model.gender }} {{ model.race }}
|
||||||
</v-list-item-subtitle>
|
</v-list-item-subtitle>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
<v-list-item-action v-if="selection">
|
<v-list-item-action>
|
||||||
<v-checkbox
|
<v-checkbox
|
||||||
|
v-if="selection"
|
||||||
:input-value="selected && selected.has(model._id)"
|
:input-value="selected && selected.has(model._id)"
|
||||||
@change="$emit('select')"
|
@change="$emit('select')"
|
||||||
/>
|
/>
|
||||||
|
<v-icon
|
||||||
|
style="height: 100%; width: 40px; cursor: move;"
|
||||||
|
class="handle"
|
||||||
|
>
|
||||||
|
mdi-drag
|
||||||
|
</v-icon>
|
||||||
</v-list-item-action>
|
</v-list-item-action>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -4,16 +4,12 @@
|
|||||||
style="height: 100%"
|
style="height: 100%"
|
||||||
>
|
>
|
||||||
<v-card :class="{'mb-4': folders && folders.length}">
|
<v-card :class="{'mb-4': folders && folders.length}">
|
||||||
<v-list v-if="CreaturesWithNoParty.length">
|
<creature-list :creatures="CreaturesWithNoParty" />
|
||||||
<creature-list-tile
|
|
||||||
v-for="creature in CreaturesWithNoParty"
|
|
||||||
:key="creature._id"
|
|
||||||
:to="creature.url"
|
|
||||||
:model="creature"
|
|
||||||
/>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
<v-expansion-panels v-if="folders && folders.length">
|
<v-expansion-panels
|
||||||
|
v-if="folders && folders.length"
|
||||||
|
multiple
|
||||||
|
>
|
||||||
<v-expansion-panel
|
<v-expansion-panel
|
||||||
v-for="folder in folders"
|
v-for="folder in folders"
|
||||||
:key="folder._id"
|
:key="folder._id"
|
||||||
@@ -57,24 +53,22 @@
|
|||||||
</template>
|
</template>
|
||||||
</v-expansion-panel-header>
|
</v-expansion-panel-header>
|
||||||
<v-expansion-panel-content>
|
<v-expansion-panel-content>
|
||||||
<v-list v-if="folder.creatures">
|
<creature-list
|
||||||
<creature-list-tile
|
:creatures="folder.creatures"
|
||||||
v-for="creature in folder.creatures"
|
:folder-id="folder._id"
|
||||||
:key="creature._id"
|
/>
|
||||||
:to="creature.url"
|
|
||||||
:model="creature"
|
|
||||||
/>
|
|
||||||
</v-list>
|
|
||||||
</v-expansion-panel-content>
|
</v-expansion-panel-content>
|
||||||
</v-expansion-panel>
|
</v-expansion-panel>
|
||||||
</v-expansion-panels>
|
</v-expansion-panels>
|
||||||
<v-btn
|
<div class="layout justify-end mt-2">
|
||||||
text
|
<v-btn
|
||||||
:loading="loadingInsertFolder"
|
text
|
||||||
@click="insertFolder"
|
:loading="loadingInsertFolder"
|
||||||
>
|
@click="insertFolder"
|
||||||
add folder
|
>
|
||||||
</v-btn>
|
add folder
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
<v-btn
|
<v-btn
|
||||||
color="accent"
|
color="accent"
|
||||||
fab
|
fab
|
||||||
@@ -93,13 +87,13 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Creatures, {insertCreature} from '/imports/api/creature/Creatures.js';
|
import Creatures, {insertCreature} from '/imports/api/creature/Creatures.js';
|
||||||
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
||||||
|
import CreatureList from '/imports/ui/creature/creatureList/CreatureList.vue';
|
||||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||||
import CreatureListTile from '/imports/ui/creature/CreatureListTile.vue';
|
|
||||||
import insertCreatureFolder from '/imports/api/creature/creatureFolders/methods.js/insertCreatureFolder.js';
|
import insertCreatureFolder from '/imports/api/creature/creatureFolders/methods.js/insertCreatureFolder.js';
|
||||||
import updateCreatureFolderName from '/imports/api/creature/creatureFolders/methods.js/updateCreatureFolderName.js';
|
import updateCreatureFolderName from '/imports/api/creature/creatureFolders/methods.js/updateCreatureFolderName.js';
|
||||||
import removeCreatureFolder from '/imports/api/creature/creatureFolders/methods.js/removeCreatureFolder.js';
|
import removeCreatureFolder from '/imports/api/creature/creatureFolders/methods.js/removeCreatureFolder.js';
|
||||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||||
|
|
||||||
const characterTransform = function(char){
|
const characterTransform = function(char){
|
||||||
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
||||||
char.initial = char.name && char.name[0] || '?';
|
char.initial = char.name && char.name[0] || '?';
|
||||||
@@ -107,7 +101,7 @@
|
|||||||
};
|
};
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CreatureListTile,
|
CreatureList,
|
||||||
},
|
},
|
||||||
data(){ return{
|
data(){ return{
|
||||||
fab: false,
|
fab: false,
|
||||||
@@ -183,9 +177,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
insertFolder(){
|
insertFolder(){
|
||||||
loadingInsertFolder = true;
|
this.loadingInsertFolder = true;
|
||||||
insertCreatureFolder.call(error => {
|
insertCreatureFolder.call(error => {
|
||||||
loadingInsertFolder = false;
|
this.loadingInsertFolder = false;
|
||||||
if (!error) return;
|
if (!error) return;
|
||||||
console.error(error);
|
console.error(error);
|
||||||
snackbar({
|
snackbar({
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<script lang="js">
|
<script lang="js">
|
||||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
import Creatures from '/imports/api/creature/Creatures.js';
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
import CreatureListTile from '/imports/ui/creature/CreatureListTile.vue';
|
import CreatureListTile from '/imports/ui/creature/creatureList/CreatureListTile.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
import Creatures from '/imports/api/creature/Creatures.js';
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
import Libraries from '/imports/api/library/Libraries.js';
|
import Libraries from '/imports/api/library/Libraries.js';
|
||||||
import CreatureListTile from '/imports/ui/creature/CreatureListTile.vue';
|
import CreatureListTile from '/imports/ui/creature/creatureList/CreatureListTile.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
Reference in New Issue
Block a user