Archive UI
This commit is contained in:
@@ -12,9 +12,9 @@ import ArchivedCreatures from '/imports/api/creature/archive/ArchivedCreatures.j
|
|||||||
function archiveCreature(creatureId){
|
function archiveCreature(creatureId){
|
||||||
// Build the archive document
|
// Build the archive document
|
||||||
const creature = Creatures.findOne(creatureId);
|
const creature = Creatures.findOne(creatureId);
|
||||||
const properties = CreatureProperties.find({'ancestors.id': creatureId});
|
const properties = CreatureProperties.find({'ancestors.id': creatureId}).fetch();
|
||||||
const experiences = Experiences.find({creatureId});
|
const experiences = Experiences.find({creatureId}).fetch();
|
||||||
const logs = CreatureLogs.find({creatureId});
|
const logs = CreatureLogs.find({creatureId}).fetch();
|
||||||
let archiveCreature = {
|
let archiveCreature = {
|
||||||
owner: creature.owner,
|
owner: creature.owner,
|
||||||
archiveDate: new Date(),
|
archiveDate: new Date(),
|
||||||
@@ -51,11 +51,11 @@ const archiveCreatures = new ValidatedMethod({
|
|||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({creatureIds}) {
|
run({creatureIds}) {
|
||||||
for (let id in creatureIds){
|
for (let id of creatureIds){
|
||||||
assertOwnership(id, this.userId)
|
assertOwnership(id, this.userId)
|
||||||
}
|
}
|
||||||
let archivedIds = [];
|
let archivedIds = [];
|
||||||
for (let id in creatureIds){
|
for (let id of creatureIds){
|
||||||
let archivedId = archiveCreature(id);
|
let archivedId = archiveCreature(id);
|
||||||
archivedIds.push(archivedId);
|
archivedIds.push(archivedId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
|||||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
import Experiences from '/imports/api/creature/experience/Experiences.js';
|
import Experiences from '/imports/api/creature/experience/Experiences.js';
|
||||||
import ArchivedCreatures from '/imports/api/creature/archive/ArchivedCreatures.js';
|
import ArchivedCreatures from '/imports/api/creature/archive/ArchivedCreatures.js';
|
||||||
|
import { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||||
|
|
||||||
function restoreCreature(archiveId){
|
function restoreCreature(archiveId){
|
||||||
// Get the archive
|
// Get the archive
|
||||||
@@ -15,15 +16,28 @@ function restoreCreature(archiveId){
|
|||||||
// Insert the creature sub documents
|
// Insert the creature sub documents
|
||||||
// They still have their original _id's
|
// They still have their original _id's
|
||||||
Creatures.insert(archivedCreature.creature);
|
Creatures.insert(archivedCreature.creature);
|
||||||
CreatureProperties.batchInsert(archivedCreature.properties);
|
try {
|
||||||
Experiences.batchInsert(archivedCreature.experiences);
|
// Add all the properties
|
||||||
CreatureLogs.batchInsert(archivedCreature.logs);
|
if (archivedCreature.properties && archivedCreature.properties.length){
|
||||||
|
CreatureProperties.batchInsert(archivedCreature.properties);
|
||||||
|
}
|
||||||
|
if (archivedCreature.experiences && archivedCreature.experiences.length){
|
||||||
|
Experiences.batchInsert(archivedCreature.experiences);
|
||||||
|
}
|
||||||
|
if (archivedCreature.logs && archivedCreature.logs.length){
|
||||||
|
CreatureLogs.batchInsert(archivedCreature.logs);
|
||||||
|
}
|
||||||
|
// Remove the archived creature
|
||||||
|
ArchivedCreatures.remove(archiveId);
|
||||||
|
} catch (e) {
|
||||||
|
// If the above fails, delete the inserted creature
|
||||||
|
removeCreatureWork(archivedCreature.creature._id);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
// Do not recompute. The creature was in a computed and ordered state when
|
// Do not recompute. The creature was in a computed and ordered state when
|
||||||
// we archived it, just restore everything as-is
|
// we archived it, just restore everything as-is
|
||||||
|
|
||||||
// Remove the archived creature
|
|
||||||
ArchivedCreatures.remove(archiveId);
|
|
||||||
|
|
||||||
return archivedCreature.creature._id;
|
return archivedCreature.creature._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,14 +59,14 @@ const restoreCreatures = new ValidatedMethod({
|
|||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({archiveIds}) {
|
run({archiveIds}) {
|
||||||
for (let id in archiveIds){
|
for (let id of archiveIds){
|
||||||
let archivedCreature = ArchivedCreatures.findOne(id, {
|
let archivedCreature = ArchivedCreatures.findOne(id, {
|
||||||
fields: {owner: 1}
|
fields: {owner: 1}
|
||||||
});
|
});
|
||||||
assertOwnership(archivedCreature, this.userId)
|
assertOwnership(archivedCreature, this.userId)
|
||||||
}
|
}
|
||||||
let creatureIds = [];
|
let creatureIds = [];
|
||||||
for (let id in archiveIds){
|
for (let id of archiveIds){
|
||||||
let creatureId = restoreCreature(id);
|
let creatureId = restoreCreature(id);
|
||||||
creatureIds.push(creatureId);
|
creatureIds.push(creatureId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Meteor.publish('archivedCreatures', function(){
|
|||||||
}, {
|
}, {
|
||||||
fields: {
|
fields: {
|
||||||
creature: 1,
|
creature: 1,
|
||||||
|
owner: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,26 +1,213 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<dialog-base>
|
<dialog-base>
|
||||||
<div>
|
<template #toolbar>
|
||||||
TODO
|
<v-toolbar-title>
|
||||||
</div>
|
{{ mode === 'archive' ? 'Archive' : 'Restore' }}
|
||||||
|
</v-toolbar-title>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn-toggle
|
||||||
|
v-model="mode"
|
||||||
|
mandatory
|
||||||
|
>
|
||||||
|
<v-btn value="archive">
|
||||||
|
<span>Archive</span>
|
||||||
|
<v-icon right>
|
||||||
|
mdi-archive-arrow-down
|
||||||
|
</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn value="restore">
|
||||||
|
<span>Restore</span>
|
||||||
|
<v-icon right>
|
||||||
|
mdi-archive-arrow-up-outline
|
||||||
|
</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</template>
|
||||||
|
<creature-folder-list
|
||||||
|
selection
|
||||||
|
:creatures="mode === 'archive' ? CreaturesWithNoParty : archiveCreaturesWithNoParty"
|
||||||
|
:folders="mode === 'archive' ? folders : archivefolders"
|
||||||
|
:selected-creature="selectedCreature"
|
||||||
|
@creature-selected="id => selectedCreature = id"
|
||||||
|
/>
|
||||||
<v-spacer slot="actions" />
|
<v-spacer slot="actions" />
|
||||||
|
<v-btn
|
||||||
|
slot="actions"
|
||||||
|
text
|
||||||
|
:loading="archiveActionLoading"
|
||||||
|
:disabled="!numSelected"
|
||||||
|
color="primary"
|
||||||
|
@click="archiveAction"
|
||||||
|
>
|
||||||
|
{{ mode === 'archive' ? 'Archive' : 'Restore' }}
|
||||||
|
<template v-if="numSelected > 1">
|
||||||
|
{{ numSelected }} characters
|
||||||
|
</template>
|
||||||
|
<template v-else-if="numSelected === 1">
|
||||||
|
character
|
||||||
|
</template>
|
||||||
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
slot="actions"
|
slot="actions"
|
||||||
text
|
text
|
||||||
@click="$store.dispatch('popDialogStack')"
|
@click="$store.dispatch('popDialogStack')"
|
||||||
>
|
>
|
||||||
Done
|
Close
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</dialog-base>
|
</dialog-base>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<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/Creatures.js';
|
||||||
|
import ArchivedCreatures from '/imports/api/creature/archive/ArchivedCreatures.js';
|
||||||
|
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
||||||
|
import CreatureFolderList from '/imports/ui/creature/creatureList/CreatureFolderList.vue';
|
||||||
|
import archiveCreatures from '/imports/api/creature/archive/methods/archiveCreatures.js';
|
||||||
|
import restoreCreatures from '/imports/api/creature/archive/methods/restoreCreatures.js';
|
||||||
|
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||||
|
|
||||||
|
const characterTransform = function(char){
|
||||||
|
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
||||||
|
char.initial = char.name && char.name[0] || '?';
|
||||||
|
return char;
|
||||||
|
};
|
||||||
|
|
||||||
|
const creatureFields = {
|
||||||
|
'color': 1,
|
||||||
|
'avatarPicture': 1,
|
||||||
|
'name': 1,
|
||||||
|
'initial': 1,
|
||||||
|
'alignment': 1,
|
||||||
|
'gender': 1,
|
||||||
|
'race': 1,
|
||||||
|
'readers': 1,
|
||||||
|
'writers': 1,
|
||||||
|
'owner': 1,
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
DialogBase,
|
DialogBase,
|
||||||
|
CreatureFolderList,
|
||||||
},
|
},
|
||||||
|
data(){return {
|
||||||
|
selectedCreature: null,
|
||||||
|
mode: 'archive',
|
||||||
|
archiveActionLoading: false,
|
||||||
|
}},
|
||||||
|
computed: {
|
||||||
|
numSelected(){
|
||||||
|
return this.selectedCreature ? 1 : 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
mode(){
|
||||||
|
this.selectedCreature = null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
archiveAction(){
|
||||||
|
if (!this.selectedCreature) return;
|
||||||
|
this.archiveActionLoading = true;
|
||||||
|
if (this.mode === 'archive'){
|
||||||
|
archiveCreatures.call({
|
||||||
|
creatureIds: [this.selectedCreature],
|
||||||
|
}, error => {
|
||||||
|
this.archiveActionLoading = false;
|
||||||
|
if (!error) return;
|
||||||
|
console.error(error);
|
||||||
|
snackbar({text: error.reason});
|
||||||
|
});
|
||||||
|
} else if (this.mode === 'restore'){
|
||||||
|
let archiveId = ArchivedCreatures.findOne({
|
||||||
|
'creature._id': this.selectedCreature
|
||||||
|
})._id;
|
||||||
|
restoreCreatures.call({
|
||||||
|
archiveIds: [archiveId],
|
||||||
|
}, error => {
|
||||||
|
this.archiveActionLoading = false;
|
||||||
|
if (!error) return;
|
||||||
|
console.error(error);
|
||||||
|
snackbar({text: error.reason});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.selectedCreature = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
$subscribe: {
|
||||||
|
'archivedCreatures': [],
|
||||||
|
},
|
||||||
|
folders(){
|
||||||
|
const userId = Meteor.userId();
|
||||||
|
let folders = CreatureFolders.find(
|
||||||
|
{owner: userId, archived: {$ne: true}},
|
||||||
|
{sort: {order: 1}},
|
||||||
|
).map(folder => {
|
||||||
|
folder.creatures = Creatures.find(
|
||||||
|
{
|
||||||
|
_id: {$in: folder.creatures || []},
|
||||||
|
owner: userId,
|
||||||
|
}, {
|
||||||
|
sort: {name: 1},
|
||||||
|
fields: creatureFields,
|
||||||
|
}
|
||||||
|
).map(characterTransform);
|
||||||
|
return folder;
|
||||||
|
});
|
||||||
|
folders = folders.filter(folder => !!folder.creatures.length);
|
||||||
|
return folders;
|
||||||
|
},
|
||||||
|
CreaturesWithNoParty() {
|
||||||
|
var userId = Meteor.userId();
|
||||||
|
var charArrays = CreatureFolders.find({owner: userId}).map(p => p.creatures);
|
||||||
|
var folderChars = _.uniq(_.flatten(charArrays));
|
||||||
|
return Creatures.find(
|
||||||
|
{
|
||||||
|
_id: {$nin: folderChars},
|
||||||
|
owner: userId,
|
||||||
|
}, {
|
||||||
|
sort: {name: 1},
|
||||||
|
fields: creatureFields,
|
||||||
|
}
|
||||||
|
).map(characterTransform);
|
||||||
|
},
|
||||||
|
archivefolders(){
|
||||||
|
const userId = Meteor.userId();
|
||||||
|
let folders = CreatureFolders.find(
|
||||||
|
{owner: userId},
|
||||||
|
{sort: {order: 1}},
|
||||||
|
).map(folder => {
|
||||||
|
folder.creatures = ArchivedCreatures.find(
|
||||||
|
{
|
||||||
|
'creature._id': {$in: folder.creatures || []},
|
||||||
|
owner: userId,
|
||||||
|
}, {
|
||||||
|
sort: {'creature.name': 1},
|
||||||
|
fields: {creature: 1},
|
||||||
|
}
|
||||||
|
).map(arc => characterTransform(arc.creature));
|
||||||
|
return folder;
|
||||||
|
});
|
||||||
|
folders = folders.filter(folder => !!folder.creatures.length);
|
||||||
|
return folders;
|
||||||
|
},
|
||||||
|
archiveCreaturesWithNoParty() {
|
||||||
|
var userId = Meteor.userId();
|
||||||
|
var charArrays = CreatureFolders.find({owner: userId}).map(p => p.creatures);
|
||||||
|
var folderChars = _.uniq(_.flatten(charArrays));
|
||||||
|
return ArchivedCreatures.find(
|
||||||
|
{
|
||||||
|
'creature._id': {$nin: folderChars},
|
||||||
|
owner: userId,
|
||||||
|
}, {
|
||||||
|
sort: {'creature.name': 1},
|
||||||
|
fields: {creature: 1},
|
||||||
|
}
|
||||||
|
).map(arc => characterTransform(arc.creature));
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
38
app/imports/ui/creature/creatureList/ArchiveButton.vue
Normal file
38
app/imports/ui/creature/creatureList/ArchiveButton.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<v-btn
|
||||||
|
:icon="!text"
|
||||||
|
:text="text"
|
||||||
|
:data-id="randomId"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@click="openArchive"
|
||||||
|
>
|
||||||
|
<template v-if="text">
|
||||||
|
Archive Characters
|
||||||
|
</template>
|
||||||
|
<v-icon :right="text">
|
||||||
|
mdi-archive
|
||||||
|
</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
text: Boolean,
|
||||||
|
},
|
||||||
|
data(){return {
|
||||||
|
randomId: Random.id(),
|
||||||
|
}},
|
||||||
|
methods: {
|
||||||
|
openArchive(){
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'archive-dialog',
|
||||||
|
elementId: this.randomId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -7,23 +7,19 @@
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
{{ characterSlots }}
|
{{ characterSlots }}
|
||||||
</template>
|
</template>
|
||||||
<v-btn
|
<archive-button />
|
||||||
icon
|
|
||||||
data-id="open-archive-btn"
|
|
||||||
@click="openArchive"
|
|
||||||
>
|
|
||||||
<v-icon>
|
|
||||||
mdi-archive
|
|
||||||
</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||||
|
import ArchiveButton from '/imports/ui/creature/creatureList/ArchiveButton.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
ArchiveButton,
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
creatureCount(){
|
creatureCount(){
|
||||||
return Creatures.find({owner: Meteor.userId()}).count();
|
return Creatures.find({owner: Meteor.userId()}).count();
|
||||||
@@ -32,14 +28,6 @@ export default {
|
|||||||
return getUserTier(Meteor.userId()).characterSlots;
|
return getUserTier(Meteor.userId()).characterSlots;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
openArchive(){
|
|
||||||
this.$store.commit('pushDialogStack', {
|
|
||||||
component: 'archive-dialog',
|
|
||||||
elementId: 'open-archive-btn',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
102
app/imports/ui/creature/creatureList/CreatureFolderHeader.vue
Normal file
102
app/imports/ui/creature/creatureList/CreatureFolderHeader.vue
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<v-list-item style="min-height: 60px;">
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>
|
||||||
|
<template v-if="!renaming">
|
||||||
|
{{ model.name }}
|
||||||
|
</template>
|
||||||
|
<text-field
|
||||||
|
v-if="renaming"
|
||||||
|
ref="name-input"
|
||||||
|
regular
|
||||||
|
hide-details
|
||||||
|
dense
|
||||||
|
:value="model.name"
|
||||||
|
@change="renameFolder"
|
||||||
|
@click.native.stop="()=>{}"
|
||||||
|
/>
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item-content>
|
||||||
|
<v-list-item-action v-if="!selection && (renaming || open)">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
style="flex-grow: 0"
|
||||||
|
@click.stop="renaming = !renaming"
|
||||||
|
>
|
||||||
|
<v-icon v-if="renaming">
|
||||||
|
mdi-check
|
||||||
|
</v-icon>
|
||||||
|
<v-icon v-else>
|
||||||
|
mdi-pencil
|
||||||
|
</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-list-item-action>
|
||||||
|
<v-list-item-action v-if="!selection && open">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
style="flex-grow: 0"
|
||||||
|
@click.stop="removeFolder"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-list-item-action>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import updateCreatureFolderName from '/imports/api/creature/creatureFolders/methods.js/updateCreatureFolderName.js';
|
||||||
|
import removeCreatureFolder from '/imports/api/creature/creatureFolders/methods.js/removeCreatureFolder.js';
|
||||||
|
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
open: Boolean,
|
||||||
|
selection: Boolean,
|
||||||
|
},
|
||||||
|
data(){return {
|
||||||
|
renaming: false,
|
||||||
|
}},
|
||||||
|
watch: {
|
||||||
|
renaming(value){
|
||||||
|
if (!value) return;
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
this.$refs['name-input'].focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
renameFolder(name, ack){
|
||||||
|
updateCreatureFolderName.call({
|
||||||
|
_id: this.model._id,
|
||||||
|
name
|
||||||
|
}, error => {
|
||||||
|
ack(error);
|
||||||
|
if (!error) return;
|
||||||
|
console.error(error);
|
||||||
|
snackbar({
|
||||||
|
text: error.reason,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeFolder(){
|
||||||
|
removeCreatureFolder.call({
|
||||||
|
_id: this.model._id
|
||||||
|
}, error => {
|
||||||
|
if (!error) return;
|
||||||
|
console.error(error);
|
||||||
|
snackbar({
|
||||||
|
text: error.reason,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
64
app/imports/ui/creature/creatureList/CreatureFolderList.vue
Normal file
64
app/imports/ui/creature/creatureList/CreatureFolderList.vue
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<v-list expand>
|
||||||
|
<creature-list
|
||||||
|
:creatures="creatures"
|
||||||
|
:selection="selection"
|
||||||
|
:selected-creature="selectedCreature"
|
||||||
|
@creature-selected="id => $emit('creature-selected', id)"
|
||||||
|
/>
|
||||||
|
<v-list-group
|
||||||
|
v-for="folder in folders"
|
||||||
|
:key="folder._id"
|
||||||
|
v-model="openFolders[folder._id]"
|
||||||
|
group="folder"
|
||||||
|
>
|
||||||
|
<template #activator>
|
||||||
|
<creature-folder-header
|
||||||
|
:open="openFolders[folder._id]"
|
||||||
|
:model="folder"
|
||||||
|
:selection="selection"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<creature-list
|
||||||
|
:creatures="folder.creatures"
|
||||||
|
:folder-id="folder._id"
|
||||||
|
:selection="selection"
|
||||||
|
:selected-creature="selectedCreature"
|
||||||
|
@creature-selected="id => $emit('creature-selected', id)"
|
||||||
|
/>
|
||||||
|
</v-list-group>
|
||||||
|
</v-list>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import CreatureFolderHeader from '/imports/ui/creature/creatureList/CreatureFolderHeader.vue';
|
||||||
|
import CreatureList from '/imports/ui/creature/creatureList/CreatureList.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
CreatureFolderHeader,
|
||||||
|
CreatureList,
|
||||||
|
},
|
||||||
|
props:{
|
||||||
|
creatures: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
folders: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
selection: Boolean,
|
||||||
|
selectedCreature: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data(){return{
|
||||||
|
openFolders: {},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -1,25 +1,26 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-list>
|
<draggable
|
||||||
<draggable
|
v-model="dataCreatures"
|
||||||
v-model="dataCreatures"
|
style="min-height: 24px;"
|
||||||
style="min-height: 24px;"
|
:sort="false"
|
||||||
:sort="false"
|
:group="`creature-list`"
|
||||||
:group="`creature-list`"
|
ghost-class="ghost"
|
||||||
ghost-class="ghost"
|
draggable=".creature"
|
||||||
draggable=".creature"
|
handle=".handle"
|
||||||
handle=".handle"
|
:animation="200"
|
||||||
:animation="200"
|
@change="draggableChange"
|
||||||
@change="change"
|
>
|
||||||
>
|
<creature-list-tile
|
||||||
<creature-list-tile
|
v-for="creature in dataCreatures"
|
||||||
v-for="creature in dataCreatures"
|
:key="creature._id"
|
||||||
:key="creature._id"
|
class="creature"
|
||||||
class="creature"
|
:model="creature"
|
||||||
:to="creature.url"
|
:selection="selection"
|
||||||
:model="creature"
|
:is-selected="selectedCreature === creature._id"
|
||||||
/>
|
v-bind="selection ? {} : {to: creature.url}"
|
||||||
</draggable>
|
@click="$emit('creature-selected', creature._id)"
|
||||||
</v-list>
|
/>
|
||||||
|
</draggable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
@@ -42,6 +43,11 @@
|
|||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
selection: Boolean,
|
||||||
|
selectedCreature: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data(){return {
|
||||||
dataCreatures: [],
|
dataCreatures: [],
|
||||||
@@ -55,9 +61,10 @@
|
|||||||
this.dataCreatures = this.creatures;
|
this.dataCreatures = this.creatures;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
change({added, moved}){
|
draggableChange({added, moved}){
|
||||||
let event = added || moved;
|
let event = added || moved;
|
||||||
if (event){
|
if (event){
|
||||||
|
/*
|
||||||
// If this item is now adjacent to another, set the order accordingly
|
// If this item is now adjacent to another, set the order accordingly
|
||||||
let order;
|
let order;
|
||||||
let before = this.dataCreatures[event.newIndex - 1];
|
let before = this.dataCreatures[event.newIndex - 1];
|
||||||
@@ -69,6 +76,7 @@
|
|||||||
} else {
|
} else {
|
||||||
order = -0.5;
|
order = -0.5;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
let doc = event.element;
|
let doc = event.element;
|
||||||
moveCreatureToFolder.call({
|
moveCreatureToFolder.call({
|
||||||
creatureId: doc._id,
|
creatureId: doc._id,
|
||||||
@@ -82,6 +90,9 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
selectionChange(index){
|
||||||
|
this.$emit('creatureSelected', this.dataCreatures[index]._id)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,16 +2,31 @@
|
|||||||
lang="html"
|
lang="html"
|
||||||
functional
|
functional
|
||||||
>
|
>
|
||||||
<v-list-item v-bind="$attrs">
|
<v-list-item
|
||||||
<v-list-item-avatar :color="model.color || 'grey'">
|
v-bind="$attrs"
|
||||||
<img
|
:class="isSelected && 'primary--text v-list-item--active'"
|
||||||
v-if="model.avatarPicture"
|
v-on="selection ? { click() {$emit('click')} } : {}"
|
||||||
:src="model.avatarPicture"
|
>
|
||||||
:alt="model.name"
|
<v-list-item-avatar
|
||||||
>
|
:color="isSelected ? 'red darken-1' : model.color || 'grey'"
|
||||||
<template v-else>
|
class="white--text"
|
||||||
{{ model.initial }}
|
style="transition: background 0.3s;"
|
||||||
</template>
|
>
|
||||||
|
<v-fade-transition leave-absolute>
|
||||||
|
<v-icon v-if="isSelected">
|
||||||
|
mdi-check
|
||||||
|
</v-icon>
|
||||||
|
<img
|
||||||
|
v-else-if="model.avatarPicture"
|
||||||
|
:src="model.avatarPicture"
|
||||||
|
:alt="model.name"
|
||||||
|
>
|
||||||
|
<template v-else>
|
||||||
|
<span>
|
||||||
|
{{ model.initial }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</v-fade-transition>
|
||||||
</v-list-item-avatar>
|
</v-list-item-avatar>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-list-item-title>
|
<v-list-item-title>
|
||||||
@@ -25,12 +40,8 @@
|
|||||||
<shared-icon :model="model" />
|
<shared-icon :model="model" />
|
||||||
</v-list-item-action>
|
</v-list-item-action>
|
||||||
<v-list-item-action>
|
<v-list-item-action>
|
||||||
<v-checkbox
|
|
||||||
v-if="selection"
|
|
||||||
:input-value="selected && selected.has(model._id)"
|
|
||||||
@change="$emit('select')"
|
|
||||||
/>
|
|
||||||
<v-icon
|
<v-icon
|
||||||
|
v-if="!selection"
|
||||||
style="height: 100%; width: 40px; cursor: move;"
|
style="height: 100%; width: 40px; cursor: move;"
|
||||||
class="handle"
|
class="handle"
|
||||||
>
|
>
|
||||||
@@ -53,10 +64,7 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
selection: Boolean,
|
selection: Boolean,
|
||||||
selected: {
|
isSelected: Boolean,
|
||||||
type: Set,
|
|
||||||
default: () => new Set(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,107 +1,74 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="card-background pa-4"
|
class="card-background"
|
||||||
style="height: 100%"
|
style="height: 100%"
|
||||||
>
|
>
|
||||||
<v-alert
|
<v-container>
|
||||||
v-if="exceededCharacterSpace"
|
<v-row justify="center">
|
||||||
type="error"
|
<v-col
|
||||||
>
|
cols="12"
|
||||||
You have exceeded your maximum number of character slots, archive or delete
|
xl="8"
|
||||||
some characters.
|
>
|
||||||
</v-alert>
|
<v-alert
|
||||||
<v-card :class="{'mb-4': folders && folders.length}">
|
v-if="characterSpaceLeft < 0"
|
||||||
<creature-list :creatures="CreaturesWithNoParty" />
|
type="error"
|
||||||
</v-card>
|
>
|
||||||
<v-expansion-panels
|
You have exceeded your maximum number of character slots, archive or delete
|
||||||
v-if="folders && folders.length"
|
some characters.
|
||||||
multiple
|
</v-alert>
|
||||||
>
|
<v-alert
|
||||||
<v-expansion-panel
|
v-else-if="characterSpaceLeft === 0"
|
||||||
v-for="folder in folders"
|
type="info"
|
||||||
:key="folder._id"
|
>
|
||||||
>
|
You have hit your maximum number of characters.
|
||||||
<v-expansion-panel-header>
|
<archive-button
|
||||||
<template #default="{ open }">
|
small
|
||||||
<div v-if="renamingFolder !== folder._id">
|
text
|
||||||
{{ folder.name }}
|
class="mx-2"
|
||||||
</div>
|
|
||||||
<text-field
|
|
||||||
v-else
|
|
||||||
:ref="`name-input-${folder._id}`"
|
|
||||||
regular
|
|
||||||
hide-details
|
|
||||||
dense
|
|
||||||
:value="folder.name"
|
|
||||||
@change="(value, ack) => renameFolder(folder._id, value, ack)"
|
|
||||||
@click.native.stop="()=>{}"
|
|
||||||
/>
|
/>
|
||||||
|
</v-alert>
|
||||||
|
<v-card :class="{'mb-4': folders && folders.length}">
|
||||||
|
<creature-folder-list
|
||||||
|
:creatures="CreaturesWithNoParty"
|
||||||
|
:folders="folders"
|
||||||
|
/>
|
||||||
|
</v-card>
|
||||||
|
<div class="layout justify-end mt-2">
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="renamingFolder === folder._id || open"
|
text
|
||||||
icon
|
:loading="loadingInsertFolder"
|
||||||
style="flex-grow: 0"
|
@click="insertFolder"
|
||||||
@click.stop="renamingFolder !== folder._id ? renamingFolder = folder._id : renamingFolder = undefined"
|
|
||||||
>
|
>
|
||||||
<v-icon v-if="renamingFolder !== folder._id">
|
add folder
|
||||||
mdi-pencil
|
|
||||||
</v-icon>
|
|
||||||
<v-icon v-else>
|
|
||||||
mdi-check
|
|
||||||
</v-icon>
|
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
</div>
|
||||||
v-if="open"
|
<v-btn
|
||||||
icon
|
color="accent"
|
||||||
style="flex-grow: 0"
|
fab
|
||||||
@click.stop="removeFolder(folder._id)"
|
fixed
|
||||||
>
|
bottom
|
||||||
<v-icon>mdi-delete</v-icon>
|
right
|
||||||
</v-btn>
|
data-id="new-character-button"
|
||||||
</template>
|
:disabled="characterSpaceLeft <= 0"
|
||||||
</v-expansion-panel-header>
|
@click="insertCharacter"
|
||||||
<v-expansion-panel-content>
|
>
|
||||||
<creature-list
|
<v-icon>mdi-plus</v-icon>
|
||||||
:creatures="folder.creatures"
|
</v-btn>
|
||||||
:folder-id="folder._id"
|
</v-col>
|
||||||
/>
|
</v-row>
|
||||||
</v-expansion-panel-content>
|
</v-container>
|
||||||
</v-expansion-panel>
|
|
||||||
</v-expansion-panels>
|
|
||||||
<div class="layout justify-end mt-2">
|
|
||||||
<v-btn
|
|
||||||
text
|
|
||||||
:loading="loadingInsertFolder"
|
|
||||||
@click="insertFolder"
|
|
||||||
>
|
|
||||||
add folder
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
<v-btn
|
|
||||||
color="accent"
|
|
||||||
fab
|
|
||||||
fixed
|
|
||||||
bottom
|
|
||||||
right
|
|
||||||
data-id="new-character-button"
|
|
||||||
:disabled="!hasCharacterSpace"
|
|
||||||
@click="insertCharacter"
|
|
||||||
>
|
|
||||||
<v-icon>mdi-plus</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import Vue from 'vue';
|
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
import insertCreature from '/imports/api/creature/creatures/methods/insertCreature.js';
|
import insertCreature from '/imports/api/creature/creatures/methods/insertCreature.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 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 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';
|
||||||
|
import CreatureFolderList from '/imports/ui/creature/creatureList/CreatureFolderList.vue';
|
||||||
|
import ArchiveButton from '/imports/ui/creature/creatureList/ArchiveButton.vue';
|
||||||
|
|
||||||
const characterTransform = function(char){
|
const characterTransform = function(char){
|
||||||
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
||||||
@@ -110,7 +77,8 @@
|
|||||||
};
|
};
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CreatureList,
|
CreatureFolderList,
|
||||||
|
ArchiveButton,
|
||||||
},
|
},
|
||||||
data(){ return{
|
data(){ return{
|
||||||
fab: false,
|
fab: false,
|
||||||
@@ -163,26 +131,17 @@
|
|||||||
let userId = Meteor.userId();
|
let userId = Meteor.userId();
|
||||||
return getUserTier(userId);
|
return getUserTier(userId);
|
||||||
},
|
},
|
||||||
hasCharacterSpace(){
|
characterSpaceLeft(){
|
||||||
let tier = this.tier;
|
let tier = this.tier;
|
||||||
let currentCharacterCount = this.creatureCount;
|
let currentCharacterCount = this.creatureCount;
|
||||||
return tier.characterSlots === -1 || currentCharacterCount < tier.characterSlots
|
if (tier.characterSlots === -1) return Number.POSITIVE_INFINITY;
|
||||||
|
return tier.characterSlots - currentCharacterCount
|
||||||
},
|
},
|
||||||
exceededCharacterSpace(){
|
exceededCharacterSpace(){
|
||||||
let tier = this.tier;
|
let tier = this.tier;
|
||||||
let currentCharacterCount = this.creatureCount;
|
let currentCharacterCount = this.creatureCount;
|
||||||
return tier.characterSlots !== -1 && currentCharacterCount > tier.characterSlots
|
return tier.characterSlots !== -1 && currentCharacterCount > tier.characterSlots
|
||||||
}
|
},
|
||||||
},
|
|
||||||
watch:{
|
|
||||||
renamingFolder(newId){
|
|
||||||
if(newId){
|
|
||||||
Vue.nextTick(() => {
|
|
||||||
let input = this.$refs[`name-input-${newId}`];
|
|
||||||
input[0].focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
insertCharacter(){
|
insertCharacter(){
|
||||||
@@ -213,18 +172,6 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
renameFolder(_id, name, ack){
|
|
||||||
updateCreatureFolderName.call({_id, name}, ack);
|
|
||||||
},
|
|
||||||
removeFolder(_id){
|
|
||||||
removeCreatureFolder.call({_id}, error => {
|
|
||||||
if (!error) return;
|
|
||||||
console.error(error);
|
|
||||||
snackbar({
|
|
||||||
text: error.reason,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user