From 9a194a20cb2bf024010a0b4d5f33ef56b1d426ad Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Fri, 10 May 2019 13:05:21 +0200 Subject: [PATCH] Added library list UI --- app/imports/api/library/Libraries.js | 16 ++++- app/imports/api/users/Users.js | 32 +++++---- app/imports/server/publications/library.js | 26 +++++-- app/imports/ui/components/Sidebar.vue | 3 +- .../ui/components/global/SmartInput.js | 2 +- .../ui/dialogStack/DialogComponentIndex.js | 2 + .../ui/library/LibraryCreationDialog.vue | 53 ++++++++++++++ app/imports/ui/pages/Libraries.vue | 71 +++++++++++++++++++ app/imports/ui/router.js | 30 ++++---- app/imports/ui/styles/inheritBackgrounds.css | 14 ++++ app/imports/ui/styles/stylesIndex.js | 3 +- 11 files changed, 217 insertions(+), 35 deletions(-) create mode 100644 app/imports/ui/library/LibraryCreationDialog.vue create mode 100644 app/imports/ui/pages/Libraries.vue create mode 100644 app/imports/ui/styles/inheritBackgrounds.css diff --git a/app/imports/api/library/Libraries.js b/app/imports/api/library/Libraries.js index ad485196..f83c48ae 100644 --- a/app/imports/api/library/Libraries.js +++ b/app/imports/api/library/Libraries.js @@ -1,5 +1,6 @@ import schema from '/imports/api/schema.js'; import SharingSchema from '/imports/api/sharing/SharingSchema.js'; +import simpleSchemaMixin from '/imports/api/creature/mixins/simpleSchemaMixin.js'; /** * Libraries are trees of library nodes where each node represents a character @@ -23,4 +24,17 @@ LibrarySchema.extend(SharingSchema); Libraries.attachSchema(LibrarySchema); export default Libraries; -export { LibrarySchema }; + +const insertLibrary = new ValidatedMethod({ + name: 'Libraries.methods.insert', + mixins: [ + simpleSchemaMixin, + ], + schema: LibrarySchema.omit('owner'), + run(library) { + library.owner = this.userId; + return Libraries.insert(library); + }, +}); + +export { LibrarySchema, insertLibrary }; diff --git a/app/imports/api/users/Users.js b/app/imports/api/users/Users.js index dd4cd049..fe7a9069 100644 --- a/app/imports/api/users/Users.js +++ b/app/imports/api/users/Users.js @@ -10,21 +10,21 @@ const userSchema = schema({ type: Array, optional: true, }, - "emails.$": { + 'emails.$': { type: Object, }, - "emails.$.address": { + 'emails.$.address': { type: String, regEx: SimpleSchema.RegEx.Email, }, - "emails.$.verified": { + 'emails.$.verified': { type: Boolean, }, registered_emails: { type: Array, optional: true, }, - "registered_emails.$": { + 'registered_emails.$': { type: Object, blackbox: true, }, @@ -40,7 +40,7 @@ const userSchema = schema({ type: Array, optional: true, }, - "roles.$": { + 'roles.$': { type: String }, // In order to avoid an 'Exception in setInterval callback' from Meteor @@ -57,12 +57,20 @@ const userSchema = schema({ type: Boolean, optional: true, }, + subscribedLibraries: { + type: Array, + defaultValue: [], + max: 100, + }, + 'subscribedLibraries.$': { + type: String, + }, }); Meteor.users.attachSchema(userSchema); Meteor.users.generateApiKey = new ValidatedMethod({ - name: "Users.methods.generateApiKey", + name: 'Users.methods.generateApiKey', validate: null, run(){ if(Meteor.isClient) return; @@ -75,7 +83,7 @@ Meteor.users.generateApiKey = new ValidatedMethod({ }); Meteor.users.setDarkMode = new ValidatedMethod({ - name: "Users.methods.setDarkMode", + name: 'Users.methods.setDarkMode', validate: new SimpleSchema({ darkMode: { type: Boolean }, }).validator(), @@ -86,7 +94,7 @@ Meteor.users.setDarkMode = new ValidatedMethod({ }); Meteor.users.sendVerificationEmail = new ValidatedMethod({ - name: "Users.methods.sendVerificationEmail", + name: 'Users.methods.sendVerificationEmail', validate: schema({ userId:{ type: String, @@ -100,12 +108,12 @@ Meteor.users.sendVerificationEmail = new ValidatedMethod({ userId = this.userId || userId; let user = Meteor.users.findOne(); if (!user) { - throw new Meteor.Error("User not found", - "Can't send a validation email to a user that does not exist"); + throw new Meteor.Error('User not found', + 'Can\'t send a validation email to a user that does not exist'); } if (!_.some(user.emails, email => email.address === address)) { - throw new Meteor.Error("Email address not found", - "The specified email address wasn't found on this user account"); + throw new Meteor.Error('Email address not found', + 'The specified email address wasn\'t found on this user account'); } Accounts.sendVerificationEmail(this.userId, address); } diff --git a/app/imports/server/publications/library.js b/app/imports/server/publications/library.js index e8089567..a2981ecc 100644 --- a/app/imports/server/publications/library.js +++ b/app/imports/server/publications/library.js @@ -1,23 +1,23 @@ -import Libraries from "/imports/api/library/Libraries.js" +import Libraries from '/imports/api/library/Libraries.js'; const standardLibraryIds = [ - "SRDLibraryGA3XWsd", + 'SRDLibraryGA3XWsd', ]; -Meteor.publish("standardLibraries", function(){ +Meteor.publish('standardLibraries', function(){ return Libraries.find({_id: {$in: standardLibraryIds}}); }); -Meteor.publish("standardLibraryItems", function(categoryKey){ +Meteor.publish('standardLibraryItems', function(categoryKey){ return LibraryItems.find({ library: {$in: standardLibraryIds}, - "settings.category": categoryKey, + 'settings.category': categoryKey, }, { sort: {name: 1}, }); }); -Meteor.publish("standardLibrarySpells", function(level){ +Meteor.publish('standardLibrarySpells', function(level){ return LibrarySpells.find({ library: {$in: standardLibraryIds}, level, @@ -25,3 +25,17 @@ Meteor.publish("standardLibrarySpells", function(level){ sort: {name: 1}, }); }); + +Meteor.publish('libraries', function(){ + const user = Meteor.user(); + const userId = user && user._id; + const subs = user && user.subscribedLibraries || []; + return Libraries.find({ + $or: [ + {owner: userId}, + {writers: userId}, + {readers: userId}, + {_id: {$in: subs}}, + ] + }); +}); diff --git a/app/imports/ui/components/Sidebar.vue b/app/imports/ui/components/Sidebar.vue index 9fdc7b4e..f948d63d 100644 --- a/app/imports/ui/components/Sidebar.vue +++ b/app/imports/ui/components/Sidebar.vue @@ -5,7 +5,7 @@ {{userName}} - settings + settings Account Settings @@ -82,6 +82,7 @@ return [ {title: "Home", icon: "home", to: "/"}, {title: "Creatures", icon: "group", to: "/characterList", vif: Meteor.userId()}, + {title: "Libraries", icon: "book", to: "/library", vif: Meteor.userId()}, {title: "Send Feedback", icon: "bug_report", to: "/feedback"}, {title: "Patreon", icon: "", href: "https://www.patreon.com/dicecloud"}, {title: "Github", icon: "", href: "https://github.com/ThaumRystra/DiceCloud1"}, diff --git a/app/imports/ui/components/global/SmartInput.js b/app/imports/ui/components/global/SmartInput.js index e24e402c..d0574f0d 100644 --- a/app/imports/ui/components/global/SmartInput.js +++ b/app/imports/ui/components/global/SmartInput.js @@ -18,7 +18,7 @@ export default { dirty: false, safeValue: this.value, inputValue: this.value, - }}, + };}, props: { value: [String, Number], debounceTime: { diff --git a/app/imports/ui/dialogStack/DialogComponentIndex.js b/app/imports/ui/dialogStack/DialogComponentIndex.js index be78d4e2..4f022a5d 100644 --- a/app/imports/ui/dialogStack/DialogComponentIndex.js +++ b/app/imports/ui/dialogStack/DialogComponentIndex.js @@ -3,6 +3,7 @@ import AttributeDialogContainer from '/imports/ui/creature/properties/attributes import AttributeCreationDialog from '/imports/ui/creature/properties/attributes/AttributeCreationDialog.vue'; import FeatureCreationDialog from '/imports/ui/creature/properties/features/FeatureCreationDialog.vue'; import FeatureDialogContainer from '/imports/ui/creature/properties/features/FeatureDialogContainer.vue'; +import LibraryCreationDialog from '/imports/ui/library/LibraryCreationDialog.vue'; import SkillDialogContainer from '/imports/ui/creature/properties/skills/SkillDialogContainer.vue'; export default { @@ -11,5 +12,6 @@ export default { AttributeCreationDialog, FeatureCreationDialog, FeatureDialogContainer, + LibraryCreationDialog, SkillDialogContainer, }; diff --git a/app/imports/ui/library/LibraryCreationDialog.vue b/app/imports/ui/library/LibraryCreationDialog.vue new file mode 100644 index 00000000..d5b2d98f --- /dev/null +++ b/app/imports/ui/library/LibraryCreationDialog.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/app/imports/ui/pages/Libraries.vue b/app/imports/ui/pages/Libraries.vue new file mode 100644 index 00000000..db067767 --- /dev/null +++ b/app/imports/ui/pages/Libraries.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/app/imports/ui/router.js b/app/imports/ui/router.js index 682978ce..d5de3fa5 100644 --- a/app/imports/ui/router.js +++ b/app/imports/ui/router.js @@ -1,13 +1,14 @@ import { RouterFactory, nativeScrollBehavior } from 'meteor/akryum:vue-router2'; -import Vue from "vue"; +import Vue from 'vue'; // Components import Home from '/imports/ui/pages/Home.vue'; -import CharacterList from "/imports/ui/pages/CharacterList.vue"; -import CharacterSheetPage from "/imports/ui/pages/CharacterSheetPage.vue"; -import SignIn from "/imports/ui/pages/SignIn.vue" ; -import Register from "/imports/ui/pages/Register.vue" ; -import Account from "/imports/ui/pages/Account.vue" ; +import CharacterList from '/imports/ui/pages/CharacterList.vue'; +import Libraries from '/imports/ui/pages/Libraries.vue'; +import CharacterSheetPage from '/imports/ui/pages/CharacterSheetPage.vue'; +import SignIn from '/imports/ui/pages/SignIn.vue' ; +import Register from '/imports/ui/pages/Register.vue' ; +import Account from '/imports/ui/pages/Account.vue' ; // Not found import NotFound from '/imports/ui/pages/NotFound.vue'; @@ -27,22 +28,25 @@ RouterFactory.configure(factory => { name: 'home', component: Home, },{ - path: "/characterList", + path: '/characterList', component: CharacterList, },{ - path: "/character/:id/:urlName", + path: '/library', + component: Libraries, + },{ + path: '/character/:id/:urlName', component: CharacterSheetPage, },{ - path: "/character/:id", + path: '/character/:id', component: CharacterSheetPage, },{ - path: "/sign-in", + path: '/sign-in', component: SignIn, },{ - path: "/register", + path: '/register', component: Register, },{ - path: "/account", + path: '/account', component: Account, }, ]); @@ -61,7 +65,7 @@ RouterFactory.configure(factory => { component: StoryBook, }, { path: '/icon-admin', - name: "iconAdmin", + name: 'iconAdmin', component: IconAdmin, }, ]); diff --git a/app/imports/ui/styles/inheritBackgrounds.css b/app/imports/ui/styles/inheritBackgrounds.css new file mode 100644 index 00000000..f8c64dfb --- /dev/null +++ b/app/imports/ui/styles/inheritBackgrounds.css @@ -0,0 +1,14 @@ +/** + * Some componets are used as targets for dialog animations often, and thus + * require a background color. Inheriting the color from the nearest ancestor + * that is known to have a background ensures that the background is + * consistent for both light and dark themes +**/ + +.v-list > div { + background-color: inherit; +} + +.v-list > div > .v-list__tile { + background-color: inherit; +} diff --git a/app/imports/ui/styles/stylesIndex.js b/app/imports/ui/styles/stylesIndex.js index 39a0ca67..bf8bcfe9 100644 --- a/app/imports/ui/styles/stylesIndex.js +++ b/app/imports/ui/styles/stylesIndex.js @@ -1 +1,2 @@ -import "./speedDial.css"; +import './speedDial.css'; +import './inheritBackgrounds.css';