Merge branch 'version-2-dev' into version-2-tabletop
This commit is contained in:
@@ -9,6 +9,7 @@ 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 { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature.js';
|
import { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||||
import ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
|
import ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
|
||||||
|
import { incrementFileStorageUsed } from '/imports/api/users/methods/updateFileStorageUsed.js';
|
||||||
|
|
||||||
export function getArchiveObj(creatureId){
|
export function getArchiveObj(creatureId){
|
||||||
// Build the archive document
|
// Build the archive document
|
||||||
@@ -31,7 +32,7 @@ export function getArchiveObj(creatureId){
|
|||||||
return archiveCreature;
|
return archiveCreature;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function archiveCreature(creatureId){
|
export function archiveCreature(creatureId, userId){
|
||||||
const archive = getArchiveObj(creatureId);
|
const archive = getArchiveObj(creatureId);
|
||||||
const buffer = Buffer.from(JSON.stringify(archive, null, 2));
|
const buffer = Buffer.from(JSON.stringify(archive, null, 2));
|
||||||
ArchiveCreatureFiles.write(buffer, {
|
ArchiveCreatureFiles.write(buffer, {
|
||||||
@@ -43,11 +44,12 @@ export function archiveCreature(creatureId){
|
|||||||
creatureId: archive.creature._id,
|
creatureId: archive.creature._id,
|
||||||
creatureName: archive.creature.name,
|
creatureName: archive.creature.name,
|
||||||
},
|
},
|
||||||
}, (error) => {
|
}, (error, file) => {
|
||||||
if (error){
|
if (error){
|
||||||
throw error;
|
throw error;
|
||||||
} else {
|
} else {
|
||||||
removeCreatureWork(creatureId);
|
removeCreatureWork(creatureId);
|
||||||
|
incrementFileStorageUsed(userId, file.size);
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
@@ -68,7 +70,7 @@ const archiveCreatureToFile = new ValidatedMethod({
|
|||||||
async run({creatureId}) {
|
async run({creatureId}) {
|
||||||
assertOwnership(creatureId, this.userId);
|
assertOwnership(creatureId, this.userId);
|
||||||
if (Meteor.isServer){
|
if (Meteor.isServer){
|
||||||
archiveCreature(creatureId);
|
archiveCreature(creatureId, this.userId);
|
||||||
} else {
|
} else {
|
||||||
removeCreatureWork(creatureId);
|
removeCreatureWork(creatureId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,3 +2,4 @@
|
|||||||
import '/imports/api/creature/archive/methods/archiveCreatureToFile.js';
|
import '/imports/api/creature/archive/methods/archiveCreatureToFile.js';
|
||||||
import '/imports/api/creature/archive/methods/restoreCreatures.js';
|
import '/imports/api/creature/archive/methods/restoreCreatures.js';
|
||||||
import '/imports/api/creature/archive/methods/restoreCreatureFromFile.js';
|
import '/imports/api/creature/archive/methods/restoreCreatureFromFile.js';
|
||||||
|
import '/imports/api/creature/archive/methods/removeArchiveCreature.js';
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import SCHEMA_VERSION from '/imports/constants/SCHEMA_VERSION.js';
|
||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
|
import Experiences from '/imports/api/creature/experience/Experiences.js';
|
||||||
|
import { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||||
|
import ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
|
||||||
|
import assertHasCharactersSlots from '/imports/api/creature/creatures/methods/assertHasCharacterSlots.js';
|
||||||
|
import { incrementFileStorageUsed } from '/imports/api/users/methods/updateFileStorageUsed.js';
|
||||||
|
|
||||||
|
const removeArchiveCreature = new ValidatedMethod({
|
||||||
|
name: 'ArchiveCreatureFiles.methods.removeArchiveCreature',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
'fileId': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
async run({ fileId }) {
|
||||||
|
// fetch the file
|
||||||
|
const file = ArchiveCreatureFiles.findOne({ _id: fileId }).get();
|
||||||
|
if (!file) {
|
||||||
|
throw new Meteor.Error('File not found',
|
||||||
|
'The requested creature archive does not exist');
|
||||||
|
}
|
||||||
|
// Assert ownership
|
||||||
|
const userId = file?.userId;
|
||||||
|
if (!userId || userId !== this.userId) {
|
||||||
|
throw new Meteor.Error('Permission denied',
|
||||||
|
'You can only restore creatures you own');
|
||||||
|
}
|
||||||
|
//Remove the archive once the restore succeeded
|
||||||
|
ArchiveCreatureFiles.remove({ _id: fileId });
|
||||||
|
// Update the user's file storage limits
|
||||||
|
incrementFileStorageUsed(userId, -file.size);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default removeArchiveCreature;
|
||||||
@@ -8,6 +8,9 @@ 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 { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature.js';
|
import { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||||
import ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
|
import ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
|
||||||
|
import assertHasCharactersSlots from '/imports/api/creature/creatures/methods/assertHasCharacterSlots.js';
|
||||||
|
import { incrementFileStorageUsed } from '/imports/api/users/methods/updateFileStorageUsed.js';
|
||||||
|
|
||||||
let migrateArchive;
|
let migrateArchive;
|
||||||
if (Meteor.isServer){
|
if (Meteor.isServer){
|
||||||
migrateArchive = require('/imports/migrations/server/migrateArchive.js').default;
|
migrateArchive = require('/imports/migrations/server/migrateArchive.js').default;
|
||||||
@@ -69,13 +72,18 @@ const restoreCreaturefromFile = new ValidatedMethod({
|
|||||||
throw new Meteor.Error('Permission denied',
|
throw new Meteor.Error('Permission denied',
|
||||||
'You can only restore creatures you own');
|
'You can only restore creatures you own');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertHasCharactersSlots(this.userId);
|
||||||
|
|
||||||
if (Meteor.isServer){
|
if (Meteor.isServer){
|
||||||
// Read the file data
|
// Read the file data
|
||||||
const archive = await ArchiveCreatureFiles.readJSONFile(file);
|
const archive = await ArchiveCreatureFiles.readJSONFile(file);
|
||||||
restoreCreature(archive);
|
restoreCreature(archive);
|
||||||
}
|
}
|
||||||
//Remove the archive once the restore succeeded
|
//Remove the archive once the restore succeeded
|
||||||
ArchiveCreatureFiles.remove({_id: fileId});
|
ArchiveCreatureFiles.remove({ _id: fileId });
|
||||||
|
// Update the user's file storage limits
|
||||||
|
incrementFileStorageUsed(userId, -file.size);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||||
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
|
|
||||||
|
export default function assertHasCharactersSlots(userId) {
|
||||||
|
if (characterSlotsRemaining(userId) <= 0) {
|
||||||
|
throw new Meteor.Error('characterSlotLimit',
|
||||||
|
'No character slots left')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function characterSlotsRemaining(userId) {
|
||||||
|
let tier = getUserTier(userId);
|
||||||
|
const currentCharacterCount = Creatures.find({
|
||||||
|
owner: userId,
|
||||||
|
}, {
|
||||||
|
fields: { _id: 1 },
|
||||||
|
}).count();
|
||||||
|
if (tier.characterSlots === -1) {
|
||||||
|
return Number.POSITIVE_INFINITY;
|
||||||
|
}
|
||||||
|
return tier.characterSlots - currentCharacterCount;
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
|||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
|
||||||
import defaultCharacterProperties from '/imports/api/creature/creatures/defaultCharacterProperties.js';
|
import defaultCharacterProperties from '/imports/api/creature/creatures/defaultCharacterProperties.js';
|
||||||
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
|
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
|
||||||
|
import assertHasCharactersSlots from '/imports/api/creature/creatures/methods/assertHasCharacterSlots.js';
|
||||||
|
|
||||||
const insertCreature = new ValidatedMethod({
|
const insertCreature = new ValidatedMethod({
|
||||||
|
|
||||||
@@ -23,21 +23,8 @@ const insertCreature = new ValidatedMethod({
|
|||||||
throw new Meteor.Error('Creatures.methods.insert.denied',
|
throw new Meteor.Error('Creatures.methods.insert.denied',
|
||||||
'You need to be logged in to insert a creature');
|
'You need to be logged in to insert a creature');
|
||||||
}
|
}
|
||||||
let tier = getUserTier(this.userId);
|
|
||||||
|
|
||||||
let currentCharacterCount = Creatures.find({
|
assertHasCharactersSlots(this.userId);
|
||||||
owner: this.userId,
|
|
||||||
}, {
|
|
||||||
fields: {_id: 1},
|
|
||||||
}).count();
|
|
||||||
|
|
||||||
if (
|
|
||||||
tier.characterSlots !== -1 &&
|
|
||||||
currentCharacterCount >= tier.characterSlots
|
|
||||||
){
|
|
||||||
throw new Meteor.Error('Creatures.methods.insert.denied',
|
|
||||||
`You are already at your limit of ${tier.characterSlots} characters`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the creature document
|
// Create the creature document
|
||||||
let creatureId = Creatures.insert({
|
let creatureId = Creatures.insert({
|
||||||
|
|||||||
18
app/imports/api/files/UserImages.js
Normal file
18
app/imports/api/files/UserImages.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { createS3FilesCollection } from '/imports/api/files/s3FileStorage.js';
|
||||||
|
|
||||||
|
const UserImages = createS3FilesCollection({
|
||||||
|
collectionName: 'userImages',
|
||||||
|
storagePath: Meteor.isDevelopment ? '/DiceCloud/userImages/' : 'assets/app/userImages',
|
||||||
|
onBeforeUpload(file) {
|
||||||
|
// Allow upload files under 10MB
|
||||||
|
if (file.size > 10485760) {
|
||||||
|
return 'Please upload with size equal or less than 10MB';
|
||||||
|
}
|
||||||
|
// Allow common image extensions
|
||||||
|
if (/gif|png|jpe?g|webp/i.test(file.extension || '')) {
|
||||||
|
return 'Please upload an image file only';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default UserImages;
|
||||||
@@ -213,9 +213,6 @@ if (Meteor.isServer && Meteor.settings.useS3) {
|
|||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Meteor.isServer){
|
|
||||||
// console.log('No S3 details specified, files will be stored in the local filesystem');
|
|
||||||
}
|
|
||||||
createS3FilesCollection = function({
|
createS3FilesCollection = function({
|
||||||
collectionName,
|
collectionName,
|
||||||
storagePath,
|
storagePath,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
|||||||
import '/imports/api/users/methods/deleteMyAccount.js';
|
import '/imports/api/users/methods/deleteMyAccount.js';
|
||||||
import '/imports/api/users/methods/addEmail.js';
|
import '/imports/api/users/methods/addEmail.js';
|
||||||
import '/imports/api/users/methods/removeEmail.js';
|
import '/imports/api/users/methods/removeEmail.js';
|
||||||
|
import '/imports/api/users/methods/updateFileStorageUsed.js';
|
||||||
|
|
||||||
import { some } from 'lodash';
|
import { some } from 'lodash';
|
||||||
const defaultLibraries = process.env.DEFAULT_LIBRARIES && process.env.DEFAULT_LIBRARIES.split(',') || [];
|
const defaultLibraries = process.env.DEFAULT_LIBRARIES && process.env.DEFAULT_LIBRARIES.split(',') || [];
|
||||||
@@ -84,6 +85,10 @@ const userSchema = new SimpleSchema({
|
|||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
},
|
},
|
||||||
|
fileStorageUsed: {
|
||||||
|
type: Number,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
profile: {
|
profile: {
|
||||||
type: Object,
|
type: Object,
|
||||||
blackbox: true,
|
blackbox: true,
|
||||||
|
|||||||
71
app/imports/api/users/methods/updateFileStorageUsed.js
Normal file
71
app/imports/api/users/methods/updateFileStorageUsed.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
import ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
|
||||||
|
import UserImages from '/imports/api/files/UserImages.js';
|
||||||
|
const fileCollections = [ArchiveCreatureFiles, UserImages];
|
||||||
|
|
||||||
|
const updateFileStorageUsed = new ValidatedMethod({
|
||||||
|
name: 'users.recalculateFileStorageUsed',
|
||||||
|
validate: null,
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run() {
|
||||||
|
const userId = Meteor.userId();
|
||||||
|
if (!userId) throw new Meteor.Error('No user',
|
||||||
|
'You must be logged in to recalculate your file use');
|
||||||
|
const user = Meteor.users.findOne(userId);
|
||||||
|
if (!user) {
|
||||||
|
throw new Meteor.Error('noUser', 'User not found');
|
||||||
|
}
|
||||||
|
updateFileStorageUsedWork(userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default updateFileStorageUsed;
|
||||||
|
|
||||||
|
export function updateFileStorageUsedWork(userId) {
|
||||||
|
if (!userId) {
|
||||||
|
throw new Meteor.Error('idRequired',
|
||||||
|
'No user ID was provided to update file storage used')
|
||||||
|
}
|
||||||
|
|
||||||
|
let sum = 0;
|
||||||
|
fileCollections.forEach(collection => {
|
||||||
|
collection.find({ userId }, { fields: { size: 1 } }).forEach(file => {
|
||||||
|
sum += file.size;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.users.update(userId, {
|
||||||
|
$set: {
|
||||||
|
fileStorageUsed: sum,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function incrementFileStorageUsed(userId, amount) {
|
||||||
|
if (!userId) {
|
||||||
|
throw new Meteor.Error('idRequired',
|
||||||
|
'No user ID was provided to update file storage used')
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = Meteor.users.findOne(userId);
|
||||||
|
if (!user) {
|
||||||
|
throw new Meteor.Error('noUser', 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.fileStorageUsed === undefined) {
|
||||||
|
// The user doesn't have a current value for storage used, calculate it
|
||||||
|
// from scratch
|
||||||
|
updateFileStorageUsedWork(userId);
|
||||||
|
} else {
|
||||||
|
Meteor.users.update(userId, {
|
||||||
|
$inc: {
|
||||||
|
fileStorageUsed: amount,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,18 +9,21 @@ const TIERS = Object.freeze([
|
|||||||
minimumEntitledCents: 0,
|
minimumEntitledCents: 0,
|
||||||
invites: 0,
|
invites: 0,
|
||||||
characterSlots: 5,
|
characterSlots: 5,
|
||||||
|
fileStorage: 50,
|
||||||
paidBenefits: false,
|
paidBenefits: false,
|
||||||
}, {
|
}, {
|
||||||
name: 'Dreamer',
|
name: 'Dreamer',
|
||||||
minimumEntitledCents: 100,
|
minimumEntitledCents: 100,
|
||||||
invites: 0,
|
invites: 0,
|
||||||
characterSlots: 5,
|
characterSlots: 5,
|
||||||
|
fileStorage: 50,
|
||||||
paidBenefits: false,
|
paidBenefits: false,
|
||||||
}, {
|
}, {
|
||||||
name: 'Wanderer',
|
name: 'Wanderer',
|
||||||
minimumEntitledCents: 300,
|
minimumEntitledCents: 300,
|
||||||
invites: 0,
|
invites: 0,
|
||||||
characterSlots: 5,
|
characterSlots: 5,
|
||||||
|
fileStorage: 50,
|
||||||
paidBenefits: false,
|
paidBenefits: false,
|
||||||
}, {
|
}, {
|
||||||
//cost per user $5
|
//cost per user $5
|
||||||
@@ -28,6 +31,7 @@ const TIERS = Object.freeze([
|
|||||||
minimumEntitledCents: 500,
|
minimumEntitledCents: 500,
|
||||||
invites: 0,
|
invites: 0,
|
||||||
characterSlots: 20,
|
characterSlots: 20,
|
||||||
|
fileStorage: 200,
|
||||||
paidBenefits: true,
|
paidBenefits: true,
|
||||||
}, {
|
}, {
|
||||||
//cost per user $3.33
|
//cost per user $3.33
|
||||||
@@ -35,6 +39,7 @@ const TIERS = Object.freeze([
|
|||||||
minimumEntitledCents: 1000,
|
minimumEntitledCents: 1000,
|
||||||
invites: 2,
|
invites: 2,
|
||||||
characterSlots: 50,
|
characterSlots: 50,
|
||||||
|
fileStorage: 500,
|
||||||
paidBenefits: true,
|
paidBenefits: true,
|
||||||
}, {
|
}, {
|
||||||
//cost per user $3.333
|
//cost per user $3.333
|
||||||
@@ -42,6 +47,7 @@ const TIERS = Object.freeze([
|
|||||||
minimumEntitledCents: 2000,
|
minimumEntitledCents: 2000,
|
||||||
invites: 5,
|
invites: 5,
|
||||||
characterSlots: 120,
|
characterSlots: 120,
|
||||||
|
fileStorage: 1000,
|
||||||
paidBenefits: true,
|
paidBenefits: true,
|
||||||
}, {
|
}, {
|
||||||
//cost per user $3.125
|
//cost per user $3.125
|
||||||
@@ -49,6 +55,7 @@ const TIERS = Object.freeze([
|
|||||||
minimumEntitledCents: 5000,
|
minimumEntitledCents: 5000,
|
||||||
invites: 15,
|
invites: 15,
|
||||||
characterSlots: -1, // Unlimited characters
|
characterSlots: -1, // Unlimited characters
|
||||||
|
fileStorage: 2000,
|
||||||
paidBenefits: true,
|
paidBenefits: true,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -59,6 +66,7 @@ const GUEST_TIER = Object.freeze({
|
|||||||
guest: true,
|
guest: true,
|
||||||
invites: 0,
|
invites: 0,
|
||||||
characterSlots: 20,
|
characterSlots: 20,
|
||||||
|
fileStorage: 200,
|
||||||
paidBenefits: true,
|
paidBenefits: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,6 +76,7 @@ const PATREON_DISABLED_TIER = Object.freeze({
|
|||||||
name: 'Outlander',
|
name: 'Outlander',
|
||||||
invites: 0,
|
invites: 0,
|
||||||
characterSlots: -1, // Can have infinitely many characters
|
characterSlots: -1, // Can have infinitely many characters
|
||||||
|
fileStorage: 1000000, // 1TB file storage
|
||||||
paidBenefits: true,
|
paidBenefits: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Generated automatically by nearley, version 2.20.1
|
// Generated automatically by nearley, version 2.16.0
|
||||||
// http://github.com/Hardmath123/nearley
|
// http://github.com/Hardmath123/nearley
|
||||||
function id(x) { return x[0]; }
|
function id(x) { return x[0]; }
|
||||||
|
|
||||||
@@ -85,26 +85,24 @@ let ParserRules = [
|
|||||||
d => node.call.create({functionName: d[0].name, args: d[2]})
|
d => node.call.create({functionName: d[0].name, args: d[2]})
|
||||||
},
|
},
|
||||||
{"name": "callExpression", "symbols": ["indexExpression"], "postprocess": id},
|
{"name": "callExpression", "symbols": ["indexExpression"], "postprocess": id},
|
||||||
{"name": "arguments$ebnf$1$subexpression$1", "symbols": ["expression"], "postprocess": d => d[0]},
|
{"name": "arguments$subexpression$1", "symbols": ["expression"], "postprocess": d => d[0]},
|
||||||
{"name": "arguments$ebnf$1", "symbols": ["arguments$ebnf$1$subexpression$1"], "postprocess": id},
|
{"name": "arguments$ebnf$1", "symbols": []},
|
||||||
{"name": "arguments$ebnf$1", "symbols": [], "postprocess": function(d) {return null;}},
|
{"name": "arguments$ebnf$1$subexpression$1", "symbols": ["_", (lexer.has("separator") ? {type: "separator"} : separator), "_", "expression"], "postprocess": d => d[3]},
|
||||||
{"name": "arguments$ebnf$2", "symbols": []},
|
{"name": "arguments$ebnf$1", "symbols": ["arguments$ebnf$1", "arguments$ebnf$1$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
|
||||||
{"name": "arguments$ebnf$2$subexpression$1", "symbols": ["_", (lexer.has("separator") ? {type: "separator"} : separator), "_", "expression"], "postprocess": d => d[3]},
|
{"name": "arguments", "symbols": [{"literal":"("}, "_", "arguments$subexpression$1", "arguments$ebnf$1", "_", {"literal":")"}], "postprocess":
|
||||||
{"name": "arguments$ebnf$2", "symbols": ["arguments$ebnf$2", "arguments$ebnf$2$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
|
|
||||||
{"name": "arguments", "symbols": [{"literal":"("}, "_", "arguments$ebnf$1", "arguments$ebnf$2", "_", {"literal":")"}], "postprocess":
|
|
||||||
d => [d[2], ...d[3]]
|
d => [d[2], ...d[3]]
|
||||||
},
|
},
|
||||||
|
{"name": "arguments", "symbols": [{"literal":"("}, "_", {"literal":")"}], "postprocess": d => []},
|
||||||
{"name": "indexExpression", "symbols": ["arrayExpression", {"literal":"["}, "_", "expression", "_", {"literal":"]"}], "postprocess": d => node.index.create({array: d[0], index: d[3]})},
|
{"name": "indexExpression", "symbols": ["arrayExpression", {"literal":"["}, "_", "expression", "_", {"literal":"]"}], "postprocess": d => node.index.create({array: d[0], index: d[3]})},
|
||||||
{"name": "indexExpression", "symbols": ["arrayExpression"], "postprocess": id},
|
{"name": "indexExpression", "symbols": ["arrayExpression"], "postprocess": id},
|
||||||
{"name": "arrayExpression$ebnf$1$subexpression$1", "symbols": ["expression"], "postprocess": d => d[0]},
|
{"name": "arrayExpression$subexpression$1", "symbols": ["expression"], "postprocess": d => d[0]},
|
||||||
{"name": "arrayExpression$ebnf$1", "symbols": ["arrayExpression$ebnf$1$subexpression$1"], "postprocess": id},
|
{"name": "arrayExpression$ebnf$1", "symbols": []},
|
||||||
{"name": "arrayExpression$ebnf$1", "symbols": [], "postprocess": function(d) {return null;}},
|
{"name": "arrayExpression$ebnf$1$subexpression$1", "symbols": ["_", (lexer.has("separator") ? {type: "separator"} : separator), "_", "expression"], "postprocess": d => d[3]},
|
||||||
{"name": "arrayExpression$ebnf$2", "symbols": []},
|
{"name": "arrayExpression$ebnf$1", "symbols": ["arrayExpression$ebnf$1", "arrayExpression$ebnf$1$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
|
||||||
{"name": "arrayExpression$ebnf$2$subexpression$1", "symbols": ["_", (lexer.has("separator") ? {type: "separator"} : separator), "_", "expression"], "postprocess": d => d[3]},
|
{"name": "arrayExpression", "symbols": [{"literal":"["}, "_", "arrayExpression$subexpression$1", "arrayExpression$ebnf$1", "_", {"literal":"]"}], "postprocess":
|
||||||
{"name": "arrayExpression$ebnf$2", "symbols": ["arrayExpression$ebnf$2", "arrayExpression$ebnf$2$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
|
d => node.array.create({ values: [d[2], ...d[3]] })
|
||||||
{"name": "arrayExpression", "symbols": [{"literal":"["}, "_", "arrayExpression$ebnf$1", "arrayExpression$ebnf$2", "_", {"literal":"]"}], "postprocess":
|
|
||||||
d => node.array.create({values: d[2] ? [d[2], ...d[3]] : []})
|
|
||||||
},
|
},
|
||||||
|
{"name": "arrayExpression", "symbols": [{"literal":"["}, "_", {"literal":"]"}], "postprocess": d => node.array.create({ values: [] })},
|
||||||
{"name": "arrayExpression", "symbols": ["parenthesizedExpression"], "postprocess": id},
|
{"name": "arrayExpression", "symbols": ["parenthesizedExpression"], "postprocess": id},
|
||||||
{"name": "parenthesizedExpression", "symbols": [{"literal":"("}, "_", "expression", "_", {"literal":")"}], "postprocess": d => node.parenthesis.create({content: d[2]})},
|
{"name": "parenthesizedExpression", "symbols": [{"literal":"("}, "_", "expression", "_", {"literal":")"}], "postprocess": d => node.parenthesis.create({content: d[2]})},
|
||||||
{"name": "parenthesizedExpression", "symbols": ["accessorExpression"], "postprocess": id},
|
{"name": "parenthesizedExpression", "symbols": ["accessorExpression"], "postprocess": id},
|
||||||
|
|||||||
@@ -119,18 +119,20 @@ callExpression ->
|
|||||||
| indexExpression {% id %}
|
| indexExpression {% id %}
|
||||||
|
|
||||||
arguments ->
|
arguments ->
|
||||||
"(" _ (expression {% d => d[0] %}):? ( _ %separator _ expression {% d => d[3] %} ):* _ ")" {%
|
"(" _ (expression {% d => d[0] %}) ( _ %separator _ expression {% d => d[3] %} ):* _ ")" {%
|
||||||
d => [d[2], ...d[3]]
|
d => [d[2], ...d[3]]
|
||||||
%}
|
%}
|
||||||
|
| "(" _ ")" {% d => [] %}
|
||||||
|
|
||||||
indexExpression ->
|
indexExpression ->
|
||||||
arrayExpression "[" _ expression _ "]" {% d => node.index.create({array: d[0], index: d[3]}) %}
|
arrayExpression "[" _ expression _ "]" {% d => node.index.create({array: d[0], index: d[3]}) %}
|
||||||
| arrayExpression {% id %}
|
| arrayExpression {% id %}
|
||||||
|
|
||||||
arrayExpression ->
|
arrayExpression ->
|
||||||
"[" _ (expression {% d => d[0] %}):? ( _ %separator _ expression {% d => d[3] %} ):* _ "]" {%
|
"[" _ (expression {% d => d[0] %}) ( _ %separator _ expression {% d => d[3] %} ):* _ "]" {%
|
||||||
d => node.array.create({values: d[2] ? [d[2], ...d[3]] : []})
|
d => node.array.create({ values: [d[2], ...d[3]] })
|
||||||
%}
|
%}
|
||||||
|
| "[" _ "]" {% d => node.array.create({ values: [] }) %}
|
||||||
| parenthesizedExpression {% id %}
|
| parenthesizedExpression {% id %}
|
||||||
|
|
||||||
parenthesizedExpression ->
|
parenthesizedExpression ->
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const call = {
|
|||||||
|
|
||||||
// Check that the arguments match what is expected
|
// Check that the arguments match what is expected
|
||||||
let checkFailed = call.checkArugments({
|
let checkFailed = call.checkArugments({
|
||||||
|
node,
|
||||||
fn,
|
fn,
|
||||||
resolvedArgs,
|
resolvedArgs,
|
||||||
argumentsExpected: func.arguments,
|
argumentsExpected: func.arguments,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ Meteor.publish('user', function(){
|
|||||||
apiKey: 1,
|
apiKey: 1,
|
||||||
darkMode: 1,
|
darkMode: 1,
|
||||||
subscribedLibraries: 1,
|
subscribedLibraries: 1,
|
||||||
|
fileStorageUsed: 1,
|
||||||
profile: 1,
|
profile: 1,
|
||||||
preferences: 1,
|
preferences: 1,
|
||||||
'services.patreon.id': 1,
|
'services.patreon.id': 1,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<dialog-base :color="model.color">
|
<dialog-base v-if="model" :color="model.color">
|
||||||
<template slot="toolbar">
|
<template slot="toolbar">
|
||||||
<v-toolbar-title>
|
<v-toolbar-title>
|
||||||
Character Details
|
Character Details
|
||||||
@@ -43,7 +43,10 @@ export default {
|
|||||||
ColorPicker,
|
ColorPicker,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
_id: String,
|
_id: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
startInEditTab: Boolean,
|
startInEditTab: Boolean,
|
||||||
},
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
|
|||||||
@@ -35,16 +35,15 @@
|
|||||||
slot="actions"
|
slot="actions"
|
||||||
text
|
text
|
||||||
:loading="archiveActionLoading"
|
:loading="archiveActionLoading"
|
||||||
:disabled="!numSelected"
|
:disabled="!numSelected || (mode === 'restore' && characterSlots <= 0)"
|
||||||
color="primary"
|
color="primary"
|
||||||
@click="archiveAction"
|
@click="archiveAction"
|
||||||
>
|
>
|
||||||
{{ mode === 'archive' ? 'Archive' : 'Restore' }}
|
<template v-if="mode === 'restore' && characterSlots <= 0">
|
||||||
<template v-if="numSelected > 1">
|
No Character Slots Left
|
||||||
{{ numSelected }} characters
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="numSelected === 1">
|
<template v-else>
|
||||||
character
|
{{ mode === 'archive' ? 'Archive' : 'Restore' }}
|
||||||
</template>
|
</template>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
@@ -65,8 +64,9 @@ import CreatureFolderList from '/imports/ui/creature/creatureList/CreatureFolder
|
|||||||
import ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
|
import ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
|
||||||
import archiveCreatureToFile from '/imports/api/creature/archive/methods/archiveCreatureToFile.js';
|
import archiveCreatureToFile from '/imports/api/creature/archive/methods/archiveCreatureToFile.js';
|
||||||
import restoreCreatureFromFile from '/imports/api/creature/archive/methods/restoreCreatureFromFile.js';
|
import restoreCreatureFromFile from '/imports/api/creature/archive/methods/restoreCreatureFromFile.js';
|
||||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||||
import { uniq, flatten } from 'lodash';
|
import { uniq, flatten } from 'lodash';
|
||||||
|
import { characterSlotsRemaining } from '/imports/api/creature/creatures/methods/assertHasCharacterSlots.js';
|
||||||
|
|
||||||
const characterTransform = function(char){
|
const characterTransform = function(char){
|
||||||
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
||||||
@@ -146,6 +146,10 @@ export default {
|
|||||||
$subscribe: {
|
$subscribe: {
|
||||||
'archivedCreatures': [],
|
'archivedCreatures': [],
|
||||||
'archiveCreatureFiles': [],
|
'archiveCreatureFiles': [],
|
||||||
|
'characterList': [],
|
||||||
|
},
|
||||||
|
characterSlots(){
|
||||||
|
return characterSlotsRemaining(Meteor.userId());
|
||||||
},
|
},
|
||||||
folders(){
|
folders(){
|
||||||
const userId = Meteor.userId();
|
const userId = Meteor.userId();
|
||||||
|
|||||||
@@ -63,6 +63,9 @@
|
|||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
<character-tab :creature-id="creatureId" />
|
<character-tab :creature-id="creatureId" />
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
|
<v-tab-item>
|
||||||
|
<build-tab :creature-id="creatureId" />
|
||||||
|
</v-tab-item>
|
||||||
<v-tab-item
|
<v-tab-item
|
||||||
v-if="creature.settings.showTreeTab"
|
v-if="creature.settings.showTreeTab"
|
||||||
>
|
>
|
||||||
@@ -82,7 +85,8 @@
|
|||||||
import FeaturesTab from '/imports/ui/creature/character/characterSheetTabs/FeaturesTab.vue';
|
import FeaturesTab from '/imports/ui/creature/character/characterSheetTabs/FeaturesTab.vue';
|
||||||
import InventoryTab from '/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue';
|
import InventoryTab from '/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue';
|
||||||
import SpellsTab from '/imports/ui/creature/character/characterSheetTabs/SpellsTab.vue';
|
import SpellsTab from '/imports/ui/creature/character/characterSheetTabs/SpellsTab.vue';
|
||||||
import CharacterTab from '/imports/ui/creature/character/characterSheetTabs/CharacterTab.vue';
|
import CharacterTab from '/imports/ui/creature/character/characterSheetTabs/JournalTab.vue';
|
||||||
|
import BuildTab from '/imports/ui/creature/character/characterSheetTabs/BuildTab.vue';
|
||||||
import TreeTab from '/imports/ui/creature/character/characterSheetTabs/TreeTab.vue';
|
import TreeTab from '/imports/ui/creature/character/characterSheetTabs/TreeTab.vue';
|
||||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
@@ -95,6 +99,7 @@
|
|||||||
InventoryTab,
|
InventoryTab,
|
||||||
SpellsTab,
|
SpellsTab,
|
||||||
CharacterTab,
|
CharacterTab,
|
||||||
|
BuildTab,
|
||||||
TreeTab,
|
TreeTab,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -110,7 +110,10 @@
|
|||||||
Spells
|
Spells
|
||||||
</v-tab>
|
</v-tab>
|
||||||
<v-tab>
|
<v-tab>
|
||||||
Character
|
Journal
|
||||||
|
</v-tab>
|
||||||
|
<v-tab>
|
||||||
|
Build
|
||||||
</v-tab>
|
</v-tab>
|
||||||
<v-tab v-if="creature.settings.showTreeTab">
|
<v-tab v-if="creature.settings.showTreeTab">
|
||||||
Tree
|
Tree
|
||||||
|
|||||||
56
app/imports/ui/creature/character/CreatureSummary.vue
Normal file
56
app/imports/ui/creature/character/CreatureSummary.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<v-card
|
||||||
|
hover
|
||||||
|
data-id="creature-summary"
|
||||||
|
@mouseover="hover = true"
|
||||||
|
@mouseleave="hover = false"
|
||||||
|
@click="showCharacterForm"
|
||||||
|
>
|
||||||
|
<v-img
|
||||||
|
v-if="creature.picture"
|
||||||
|
:src="creature.picture"
|
||||||
|
/>
|
||||||
|
<v-card-title class="text-h6">
|
||||||
|
{{ creature.name }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
{{ creature.alignment }}<br>
|
||||||
|
{{ creature.gender }}
|
||||||
|
</v-card-text>
|
||||||
|
<card-highlight :active="hover" />
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
CardHighlight,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
creature: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data(){ return {
|
||||||
|
hover: false,
|
||||||
|
}},
|
||||||
|
methods: {
|
||||||
|
showCharacterForm(){
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'creature-form-dialog',
|
||||||
|
elementId: 'creature-summary',
|
||||||
|
data: {
|
||||||
|
_id: this.creature._id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,53 +1,6 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div class="inventory">
|
<div class="build">
|
||||||
<column-layout wide-columns>
|
<column-layout wide-columns>
|
||||||
<div>
|
|
||||||
<v-card
|
|
||||||
hover
|
|
||||||
data-id="creature-summary"
|
|
||||||
@mouseover="summaryHover = true"
|
|
||||||
@mouseleave="summaryHover = false"
|
|
||||||
@click="showCharacterForm"
|
|
||||||
>
|
|
||||||
<v-img
|
|
||||||
v-if="creature.picture"
|
|
||||||
:src="creature.picture"
|
|
||||||
/>
|
|
||||||
<v-card-title class="text-h6">
|
|
||||||
{{ creature.name }}
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
{{ creature.alignment }}<br>
|
|
||||||
{{ creature.gender }}
|
|
||||||
</v-card-text>
|
|
||||||
<card-highlight :active="summaryHover" />
|
|
||||||
</v-card>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<toolbar-card
|
|
||||||
data-id="slot-card"
|
|
||||||
@toolbarclick="showSlotDialog"
|
|
||||||
>
|
|
||||||
<template slot="toolbar">
|
|
||||||
<v-toolbar-title>
|
|
||||||
Build
|
|
||||||
</v-toolbar-title>
|
|
||||||
<v-spacer />
|
|
||||||
<v-toolbar-title>
|
|
||||||
<v-icon
|
|
||||||
small
|
|
||||||
style="width: 16px;"
|
|
||||||
class="mr-1"
|
|
||||||
>
|
|
||||||
mdi-pencil
|
|
||||||
</v-icon>
|
|
||||||
</v-toolbar-title>
|
|
||||||
</template>
|
|
||||||
<v-card-text style="background-color: inherit;">
|
|
||||||
<slots :creature-id="creatureId" />
|
|
||||||
</v-card-text>
|
|
||||||
</toolbar-card>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<v-card class="class-details">
|
<v-card class="class-details">
|
||||||
<v-card-title
|
<v-card-title
|
||||||
@@ -117,13 +70,30 @@
|
|||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div>
|
||||||
v-for="note in notes"
|
<toolbar-card
|
||||||
:key="note._id"
|
data-id="slot-card"
|
||||||
>
|
@toolbarclick="showSlotDialog"
|
||||||
<note-card
|
>
|
||||||
:model="note"
|
<template slot="toolbar">
|
||||||
/>
|
<v-toolbar-title>
|
||||||
|
Build
|
||||||
|
</v-toolbar-title>
|
||||||
|
<v-spacer />
|
||||||
|
<v-toolbar-title>
|
||||||
|
<v-icon
|
||||||
|
small
|
||||||
|
style="width: 16px;"
|
||||||
|
class="mr-1"
|
||||||
|
>
|
||||||
|
mdi-pencil
|
||||||
|
</v-icon>
|
||||||
|
</v-toolbar-title>
|
||||||
|
</template>
|
||||||
|
<v-card-text style="background-color: inherit;">
|
||||||
|
<slots :creature-id="creatureId" />
|
||||||
|
</v-card-text>
|
||||||
|
</toolbar-card>
|
||||||
</div>
|
</div>
|
||||||
</column-layout>
|
</column-layout>
|
||||||
</div>
|
</div>
|
||||||
@@ -133,18 +103,16 @@
|
|||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||||
import NoteCard from '/imports/ui/properties/components/persona/NoteCard.vue';
|
|
||||||
import Slots from '/imports/ui/creature/slots/Slots.vue';
|
import Slots from '/imports/ui/creature/slots/Slots.vue';
|
||||||
import ToolbarCard from '/imports/ui/components/ToolbarCard.vue';
|
import ToolbarCard from '/imports/ui/components/ToolbarCard.vue';
|
||||||
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
import CreatureSummary from '/imports/ui/creature/character/CreatureSummary.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ColumnLayout,
|
ColumnLayout,
|
||||||
NoteCard,
|
|
||||||
Slots,
|
Slots,
|
||||||
ToolbarCard,
|
ToolbarCard,
|
||||||
CardHighlight,
|
CreatureSummary,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
creatureId: {
|
creatureId: {
|
||||||
@@ -152,9 +120,6 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
|
||||||
summaryHover: false,
|
|
||||||
}},
|
|
||||||
computed: {
|
computed: {
|
||||||
highestClassLevels(){
|
highestClassLevels(){
|
||||||
let highestLevels = {};
|
let highestLevels = {};
|
||||||
@@ -182,16 +147,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
notes(){
|
|
||||||
return CreatureProperties.find({
|
|
||||||
'ancestors.id': this.creatureId,
|
|
||||||
type: 'note',
|
|
||||||
removed: {$ne: true},
|
|
||||||
inactive: {$ne: true},
|
|
||||||
}, {
|
|
||||||
sort: {order: 1},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
creature(){
|
creature(){
|
||||||
return Creatures.findOne(this.creatureId);
|
return Creatures.findOne(this.creatureId);
|
||||||
},
|
},
|
||||||
@@ -207,15 +162,6 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showCharacterForm(){
|
|
||||||
this.$store.commit('pushDialogStack', {
|
|
||||||
component: 'creature-form-dialog',
|
|
||||||
elementId: 'creature-summary',
|
|
||||||
data: {
|
|
||||||
_id: this.creatureId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
addExperience(){
|
addExperience(){
|
||||||
this.$store.commit('pushDialogStack', {
|
this.$store.commit('pushDialogStack', {
|
||||||
component: 'experience-insert-dialog',
|
component: 'experience-insert-dialog',
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<div class="build-tab">
|
||||||
|
<column-layout wide-columns>
|
||||||
|
<div>
|
||||||
|
<creature-summary :creature="creature" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="note in notes"
|
||||||
|
:key="note._id"
|
||||||
|
>
|
||||||
|
<note-card
|
||||||
|
:model="note"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</column-layout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||||
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
|
import Slots from '/imports/ui/creature/slots/Slots.vue';
|
||||||
|
import ToolbarCard from '/imports/ui/components/ToolbarCard.vue';
|
||||||
|
import NoteCard from '/imports/ui/properties/components/persona/NoteCard.vue';
|
||||||
|
import CreatureSummary from '/imports/ui/creature/character/CreatureSummary.vue';
|
||||||
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ColumnLayout,
|
||||||
|
CreatureSummary,
|
||||||
|
NoteCard,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
creatureId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
notes(){
|
||||||
|
return CreatureProperties.find({
|
||||||
|
'ancestors.id': this.creatureId,
|
||||||
|
type: 'note',
|
||||||
|
removed: {$ne: true},
|
||||||
|
inactive: {$ne: true},
|
||||||
|
}, {
|
||||||
|
sort: {order: 1},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
creature(){
|
||||||
|
return Creatures.findOne(this.creatureId);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,33 +1,19 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div>
|
<div>
|
||||||
{{ creatureCount }} /
|
<creature-storage-stats />
|
||||||
<v-icon v-if="characterSlots === -1">
|
|
||||||
mdi-infinity
|
|
||||||
</v-icon>
|
|
||||||
<template v-else>
|
|
||||||
{{ characterSlots }}
|
|
||||||
</template>
|
|
||||||
<archive-button />
|
<archive-button />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
import CreatureStorageStats from '/imports/ui/creature/creatureList/CreatureStorageStats.vue';
|
||||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
|
||||||
import ArchiveButton from '/imports/ui/creature/creatureList/ArchiveButton.vue';
|
import ArchiveButton from '/imports/ui/creature/creatureList/ArchiveButton.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
CreatureStorageStats,
|
||||||
ArchiveButton,
|
ArchiveButton,
|
||||||
},
|
},
|
||||||
meteor: {
|
|
||||||
creatureCount(){
|
|
||||||
return Creatures.find({owner: Meteor.userId()}).count();
|
|
||||||
},
|
|
||||||
characterSlots(){
|
|
||||||
return getUserTier(Meteor.userId()).characterSlots;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="creature-storage-stats"
|
||||||
|
style="display: inline-block;"
|
||||||
|
>
|
||||||
|
{{ creatureCount }} /
|
||||||
|
<v-icon v-if="characterSlots === -1">
|
||||||
|
mdi-infinity
|
||||||
|
</v-icon>
|
||||||
|
<template v-else>
|
||||||
|
{{ characterSlots }}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
|
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
meteor: {
|
||||||
|
creatureCount(){
|
||||||
|
return Creatures.find({owner: Meteor.userId()}).count();
|
||||||
|
},
|
||||||
|
characterSlots(){
|
||||||
|
return getUserTier(Meteor.userId()).characterSlots;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -4,8 +4,11 @@
|
|||||||
Delete {{ typeName }}
|
Delete {{ typeName }}
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
<div>
|
<div>
|
||||||
|
<v-alert type="warning" outlined>
|
||||||
|
This can't be undone
|
||||||
|
</v-alert>
|
||||||
<p v-if="name">
|
<p v-if="name">
|
||||||
Type "{{ name }}" to permanenetly delete
|
Type "{{ name }}" to permanenetly delete.
|
||||||
</p>
|
</p>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-if="name"
|
v-if="name"
|
||||||
|
|||||||
87
app/imports/ui/files/ArchiveFileCard.vue
Normal file
87
app/imports/ui/files/ArchiveFileCard.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<v-card :data-id="`${model._id}-archive-card`">
|
||||||
|
<v-card-title>
|
||||||
|
{{ model.meta.creatureName }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-subtitle>
|
||||||
|
{{ model.size }}
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn
|
||||||
|
v-if="characterSlots > 0"
|
||||||
|
text
|
||||||
|
@click="restore(model._id)"
|
||||||
|
>
|
||||||
|
Restore
|
||||||
|
</v-btn>
|
||||||
|
<v-flex />
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
@click="removeArchiveCharacter"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
:href="`${model.link}?download=true`"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-download</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import restoreCreatureFromFile from '/imports/api/creature/archive/methods/restoreCreatureFromFile.js';
|
||||||
|
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||||
|
import { characterSlotsRemaining } from '/imports/api/creature/creatures/methods/assertHasCharacterSlots.js';
|
||||||
|
import removeArchiveCreature from '/imports/api/creature/archive/methods/removeArchiveCreature.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data(){return {
|
||||||
|
restoreLoading: false,
|
||||||
|
removeLoading: false,
|
||||||
|
}},
|
||||||
|
meteor: {
|
||||||
|
characterSlots(){
|
||||||
|
return characterSlotsRemaining(Meteor.userId());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
restore(){
|
||||||
|
this.restoreLoading = true;
|
||||||
|
restoreCreatureFromFile.call({
|
||||||
|
fileId: this.model._id,
|
||||||
|
}, error => {
|
||||||
|
this.restoreLoading = false;
|
||||||
|
if (!error) return;
|
||||||
|
console.error(error);
|
||||||
|
snackbar({text: error.reason});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeArchiveCharacter(){
|
||||||
|
let that = this;
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'delete-confirmation-dialog',
|
||||||
|
elementId: `${that.model._id}-archive-card`,
|
||||||
|
data: {
|
||||||
|
name: this.model.meta.creatureName,
|
||||||
|
typeName: 'Character Archive'
|
||||||
|
},
|
||||||
|
callback(confirmation){
|
||||||
|
if(!confirmation) return;
|
||||||
|
removeArchiveCreature.call({fileId: that.model._id}, (error) => {
|
||||||
|
if (error) console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
66
app/imports/ui/files/FileStorageStats.vue
Normal file
66
app/imports/ui/files/FileStorageStats.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="4"
|
||||||
|
lg="3"
|
||||||
|
class="layout column justify-center align-center"
|
||||||
|
>
|
||||||
|
<v-progress-circular
|
||||||
|
:rotate="-90"
|
||||||
|
:size="100"
|
||||||
|
:width="15"
|
||||||
|
:value="percentFileStorageUsed"
|
||||||
|
:buffer-value="50"
|
||||||
|
color="accent"
|
||||||
|
>
|
||||||
|
{{ percentFileStorageUsed }}%
|
||||||
|
</v-progress-circular>
|
||||||
|
<div class="ma-2 mt-4">
|
||||||
|
{{ prettyBytes(storageUsed) }} / {{ prettyBytes(storageAllowed) }}
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
@click="updateStorageUsed"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-refresh</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||||
|
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||||
|
import updateFileStorageUsed from '/imports/api/users/methods/updateFileStorageUsed.js';
|
||||||
|
import prettyBytes from 'pretty-bytes';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
meteor: {
|
||||||
|
storageUsed(){
|
||||||
|
return Meteor.user().fileStorageUsed || 0;
|
||||||
|
},
|
||||||
|
storageAllowed(){
|
||||||
|
return getUserTier(Meteor.userId()).fileStorage * 1000000;
|
||||||
|
},
|
||||||
|
percentFileStorageUsed(){
|
||||||
|
return Math.round((this.storageUsed / this.storageAllowed) * 100);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
prettyBytes(input){
|
||||||
|
return prettyBytes(input)
|
||||||
|
},
|
||||||
|
updateStorageUsed(){
|
||||||
|
this.updateStorageUsedLoading = true;
|
||||||
|
updateFileStorageUsed.call(error => {
|
||||||
|
this.updateStorageUsedLoading = false;
|
||||||
|
if (!error) return;
|
||||||
|
snackbar({text: error.reason});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -96,6 +96,7 @@
|
|||||||
{title: 'Library', icon: 'mdi-library-shelves', to: '/library', requireLogin: true},
|
{title: 'Library', icon: 'mdi-library-shelves', to: '/library', requireLogin: true},
|
||||||
//{title: 'Tabletops', icon: 'api', to: '/tabletops', requireLogin: true},
|
//{title: 'Tabletops', icon: 'api', to: '/tabletops', requireLogin: true},
|
||||||
//{title: 'Friends', icon: 'people', to: '/friends', requireLogin: true},
|
//{title: 'Friends', icon: 'people', to: '/friends', requireLogin: true},
|
||||||
|
{title: 'Files', icon: 'mdi-file-multiple', to: '/my-files'},
|
||||||
{title: 'Feedback', icon: 'mdi-bug', to: '/feedback'},
|
{title: 'Feedback', icon: 'mdi-bug', to: '/feedback'},
|
||||||
{title: 'About', icon: 'mdi-sign-text', to: '/about'},
|
{title: 'About', icon: 'mdi-sign-text', to: '/about'},
|
||||||
{title: 'Patreon', icon: 'mdi-patreon', href: 'https://www.patreon.com/dicecloud'},
|
{title: 'Patreon', icon: 'mdi-patreon', href: 'https://www.patreon.com/dicecloud'},
|
||||||
|
|||||||
@@ -8,6 +8,18 @@
|
|||||||
style="flex-basis: 900px"
|
style="flex-basis: 900px"
|
||||||
>
|
>
|
||||||
<v-list>
|
<v-list>
|
||||||
|
<v-subheader>
|
||||||
|
File storage used
|
||||||
|
</v-subheader>
|
||||||
|
<file-storage-stats />
|
||||||
|
<v-subheader>
|
||||||
|
Character storage used
|
||||||
|
</v-subheader>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title>
|
||||||
|
<creature-storage-stats />
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
<v-subheader>
|
<v-subheader>
|
||||||
Preferences
|
Preferences
|
||||||
</v-subheader>
|
</v-subheader>
|
||||||
@@ -221,8 +233,14 @@
|
|||||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||||
import addEmail from '/imports/api/users/methods/addEmail.js';
|
import addEmail from '/imports/api/users/methods/addEmail.js';
|
||||||
import removeEmail from '/imports/api/users/methods/removeEmail.js';
|
import removeEmail from '/imports/api/users/methods/removeEmail.js';
|
||||||
|
import CreatureStorageStats from '/imports/ui/creature/creatureList/CreatureStorageStats.vue';
|
||||||
|
import FileStorageStats from '/imports/ui/files/FileStorageStats.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
CreatureStorageStats,
|
||||||
|
FileStorageStats,
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
$subscribe: {
|
$subscribe: {
|
||||||
'userPublicProfiles'(){
|
'userPublicProfiles'(){
|
||||||
|
|||||||
114
app/imports/ui/pages/Files.vue
Normal file
114
app/imports/ui/pages/Files.vue
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-row
|
||||||
|
justify="center"
|
||||||
|
class="mt-2"
|
||||||
|
>
|
||||||
|
<file-storage-stats />
|
||||||
|
</v-row>
|
||||||
|
<v-row dense>
|
||||||
|
<template v-if="archiveFiles && archiveFiles.length">
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-subheader> Archived Characters </v-subheader>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
v-for="file in archiveFiles"
|
||||||
|
:key="file._id"
|
||||||
|
cols="12"
|
||||||
|
md="4"
|
||||||
|
lg="3"
|
||||||
|
>
|
||||||
|
<archive-file-card :model="file" />
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
key="upload"
|
||||||
|
cols="12"
|
||||||
|
md="4"
|
||||||
|
lg="3"
|
||||||
|
class="layout column justify-center"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref="archiveFileInput"
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
style="display: none;"
|
||||||
|
@input="inputArchiveFile"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
outlined
|
||||||
|
style="height: 100%; width: 100%;"
|
||||||
|
:color="archiveFileError ? 'error' : undefined"
|
||||||
|
@click="$refs.archiveFileInput.click()"
|
||||||
|
>
|
||||||
|
<v-icon left>
|
||||||
|
mdi-file-upload-outline
|
||||||
|
</v-icon>
|
||||||
|
<template v-if="archiveFileError">
|
||||||
|
{{ archiveFileError }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
Upload archive
|
||||||
|
</template>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</template>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
|
||||||
|
import prettyBytes from 'pretty-bytes';
|
||||||
|
import ArchiveFileCard from '/imports/ui/files/ArchiveFileCard.vue';
|
||||||
|
import FileStorageStats from '/imports/ui/files/FileStorageStats.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ArchiveFileCard,
|
||||||
|
FileStorageStats,
|
||||||
|
},
|
||||||
|
data(){ return {
|
||||||
|
updateStorageUsedLoading: false,
|
||||||
|
archiveFileError: undefined,
|
||||||
|
archiveFile: undefined,
|
||||||
|
}},
|
||||||
|
meteor: {
|
||||||
|
$subscribe: {
|
||||||
|
'archiveCreatureFiles': [],
|
||||||
|
'characterList': [],
|
||||||
|
},
|
||||||
|
archiveFiles() {
|
||||||
|
var userId = Meteor.userId();
|
||||||
|
return ArchiveCreatureFiles.find(
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
}, {
|
||||||
|
sort: {'size': -1},
|
||||||
|
}
|
||||||
|
).map(f => {
|
||||||
|
f.size = prettyBytes(f.size);
|
||||||
|
f.link = ArchiveCreatureFiles.link(f);
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
inputArchiveFile(){
|
||||||
|
this.archiveFile = undefined;
|
||||||
|
this.archiveFileError = undefined;
|
||||||
|
const file = this.$refs.archiveFileInput.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
if (file.type !== 'application/json'){
|
||||||
|
this.archiveFileError = 'File must be .json';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (file.size > 10000000){
|
||||||
|
this.archiveFileError = 'File too large';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.archiveFile = file;
|
||||||
|
console.log(this.archiveFile);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -30,6 +30,7 @@ const TabletopToolbar = () => import('/imports/ui/tabletop/TabletopToolbar.vue')
|
|||||||
const TabletopRightDrawer = () => import('/imports/ui/tabletop/TabletopRightDrawer.vue');
|
const TabletopRightDrawer = () => import('/imports/ui/tabletop/TabletopRightDrawer.vue');
|
||||||
const Admin = () => import('/imports/ui/pages/Admin.vue');
|
const Admin = () => import('/imports/ui/pages/Admin.vue');
|
||||||
const Maintenance = () => import('/imports/ui/pages/Maintenance.vue');
|
const Maintenance = () => import('/imports/ui/pages/Maintenance.vue');
|
||||||
|
const Files = () => import('/imports/ui/pages/Files.vue');
|
||||||
|
|
||||||
// Not found
|
// Not found
|
||||||
const NotFound = () => import('/imports/ui/pages/NotFound.vue');
|
const NotFound = () => import('/imports/ui/pages/NotFound.vue');
|
||||||
@@ -199,8 +200,8 @@ RouterFactory.configure(router => {
|
|||||||
meta: {
|
meta: {
|
||||||
title: 'Register',
|
title: 'Register',
|
||||||
},
|
},
|
||||||
},{
|
}, {
|
||||||
path: '/account',
|
path: '/account',
|
||||||
components: {
|
components: {
|
||||||
default: Account,
|
default: Account,
|
||||||
},
|
},
|
||||||
@@ -208,7 +209,16 @@ RouterFactory.configure(router => {
|
|||||||
title: 'Account',
|
title: 'Account',
|
||||||
},
|
},
|
||||||
beforeEnter: ensureLoggedIn,
|
beforeEnter: ensureLoggedIn,
|
||||||
},{
|
}, {
|
||||||
|
path: '/my-files',
|
||||||
|
components: {
|
||||||
|
default: Files,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
title: 'Files',
|
||||||
|
},
|
||||||
|
beforeEnter: ensureLoggedIn,
|
||||||
|
}, {
|
||||||
path: '/feedback',
|
path: '/feedback',
|
||||||
components: {
|
components: {
|
||||||
default: Feedback,
|
default: Feedback,
|
||||||
|
|||||||
7
app/package-lock.json
generated
7
app/package-lock.json
generated
@@ -2564,6 +2564,11 @@
|
|||||||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"pretty-bytes": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg=="
|
||||||
|
},
|
||||||
"prism-media": {
|
"prism-media": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.1.tgz",
|
||||||
@@ -2788,7 +2793,7 @@
|
|||||||
},
|
},
|
||||||
"signal-exit": {
|
"signal-exit": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "",
|
"resolved": false,
|
||||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
||||||
},
|
},
|
||||||
"simpl-schema": {
|
"simpl-schema": {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"author": "Stefan Zermatten",
|
"author": "Stefan Zermatten",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"run": "meteor --once",
|
"run": "meteor",
|
||||||
"test": "meteor test --driver-package meteortesting:mocha --port 3001"
|
"test": "meteor test --driver-package meteortesting:mocha --port 3001"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -40,6 +40,7 @@
|
|||||||
"nearley": "^2.19.1",
|
"nearley": "^2.19.1",
|
||||||
"ngraph.graph": "^19.1.0",
|
"ngraph.graph": "^19.1.0",
|
||||||
"ngraph.path": "^1.4.0",
|
"ngraph.path": "^1.4.0",
|
||||||
|
"pretty-bytes": "^6.0.0",
|
||||||
"qrcode": "^1.5.0",
|
"qrcode": "^1.5.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"simpl-schema": "^1.12.0",
|
"simpl-schema": "^1.12.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user