From bf9639ae595f1248a8bb7ca8e119a97b919b5cc2 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Sun, 17 Jul 2022 22:48:48 +0200 Subject: [PATCH] Library Collections UI built --- app/imports/api/library/Libraries.js | 30 ++- app/imports/api/library/LibraryCollections.js | 13 +- app/imports/server/publications/library.js | 92 ++++++- app/imports/server/publications/users.js | 1 + app/imports/ui/components/SharedIcon.vue | 19 +- .../ui/dialogStack/DialogComponentIndex.js | 4 + .../LibraryCollectionCreationDialog.vue | 107 +++++++++ .../library/LibraryCollectionEditDialog.vue | 147 ++++++++++++ .../ui/library/LibraryCollectionHeader.vue | 86 +++++++ .../ui/library/LibraryCollectionToolbar.vue | 109 +++++++++ .../ui/library/LibraryCreationDialog.vue | 15 +- app/imports/ui/library/LibraryEditDialog.vue | 25 +- app/imports/ui/library/LibraryListTile.vue | 37 +++ app/imports/ui/pages/Library.vue | 226 ++++++++++++++++-- app/imports/ui/pages/LibraryCollection.vue | 72 ++++++ app/imports/ui/pages/SingleLibrary.vue | 18 ++ app/imports/ui/router.js | 23 +- app/imports/ui/styles/cardTitles.css | 3 + app/imports/ui/styles/stylesIndex.js | 1 + 19 files changed, 982 insertions(+), 46 deletions(-) create mode 100644 app/imports/ui/library/LibraryCollectionCreationDialog.vue create mode 100644 app/imports/ui/library/LibraryCollectionEditDialog.vue create mode 100644 app/imports/ui/library/LibraryCollectionHeader.vue create mode 100644 app/imports/ui/library/LibraryCollectionToolbar.vue create mode 100644 app/imports/ui/library/LibraryListTile.vue create mode 100644 app/imports/ui/pages/LibraryCollection.vue create mode 100644 app/imports/ui/pages/SingleLibrary.vue create mode 100644 app/imports/ui/styles/cardTitles.css diff --git a/app/imports/api/library/Libraries.js b/app/imports/api/library/Libraries.js index 358489ea..95444bc3 100644 --- a/app/imports/api/library/Libraries.js +++ b/app/imports/api/library/Libraries.js @@ -24,6 +24,11 @@ let LibrarySchema = new SimpleSchema({ type: String, max: STORAGE_LIMITS.name, }, + description: { + type: String, + optional: true, + max: STORAGE_LIMITS.summary, + }, }); LibrarySchema.extend(SharingSchema); @@ -76,6 +81,29 @@ const updateLibraryName = new ValidatedMethod({ }, }); +const updateLibraryDescription = new ValidatedMethod({ + name: 'libraries.updateDescription', + validate: new SimpleSchema({ + _id: { + type: String, + regEx: SimpleSchema.RegEx.id + }, + description: { + type: String, + }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({_id, description}){ + let library = Libraries.findOne(_id); + assertEditPermission(library, this.userId); + Libraries.update(_id, {$set: {description}}); + }, +}); + const removeLibrary = new ValidatedMethod({ name: 'libraries.remove', validate: new SimpleSchema({ @@ -102,4 +130,4 @@ export function removeLibaryWork(libraryId){ LibraryNodes.remove({'ancestors.id': libraryId}); } -export { LibrarySchema, insertLibrary, updateLibraryName, removeLibrary }; +export { LibrarySchema, insertLibrary, updateLibraryName, updateLibraryDescription, removeLibrary }; diff --git a/app/imports/api/library/LibraryCollections.js b/app/imports/api/library/LibraryCollections.js index 9a31e99e..aec4d351 100644 --- a/app/imports/api/library/LibraryCollections.js +++ b/app/imports/api/library/LibraryCollections.js @@ -10,9 +10,9 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; /** * LibraryCollections are groups of libraries that are subscribed together at once */ -let LibraryCollections = new Mongo.Collection('librarycollections'); +const LibraryCollections = new Mongo.Collection('libraryCollections'); -let LibraryCollectionSchema = new SimpleSchema({ +const LibraryCollectionSchema = new SimpleSchema({ name: { type: String, optional: true, @@ -71,7 +71,14 @@ const updateLibraryCollection = new ValidatedMethod({ regEx: SimpleSchema.RegEx.Id, }, update: { - type: LibraryCollectionSchema.pick('name', 'description', 'libraries') + type: LibraryCollectionSchema + .pick('name', 'description', 'libraries') + .extend({ //make libraries optional + libraries: { + optional: true, + defaultValue: undefined, + }, + }), } }, rateLimit: { diff --git a/app/imports/server/publications/library.js b/app/imports/server/publications/library.js index ebfdc936..808327ee 100644 --- a/app/imports/server/publications/library.js +++ b/app/imports/server/publications/library.js @@ -1,7 +1,9 @@ import SimpleSchema from 'simpl-schema'; import Libraries from '/imports/api/library/Libraries.js'; +import LibraryCollections from '/imports/api/library/LibraryCollections.js'; import LibraryNodes from '/imports/api/library/LibraryNodes.js'; import { assertViewPermission, assertDocViewPermission } from '/imports/api/sharing/sharingPermissions.js'; +import { union } from 'lodash'; const LIBRARY_NODE_TREE_FIELDS = { _id: 1, @@ -40,26 +42,90 @@ const LIBRARY_NODE_TREE_FIELDS = { } export { LIBRARY_NODE_TREE_FIELDS }; + +Meteor.publish('libraryCollection', function (libraryCollectionId) { + this.autorun(function () { + let userId = this.userId; + if (!userId) return []; + this.autorun(function () { + const libraryCollectionCursor = LibraryCollections.find({ + _id: libraryCollectionId, + $or: [ + { owner: userId }, + { writers: userId }, + { readers: userId }, + { public: true }, + ] + }); + const libraryCollection = libraryCollectionCursor.fetch()[0]; + if (!libraryCollection) return [ libraryCollectionCursor ]; + this.autorun(function () { + const libraryCursor = Libraries.find({ + _id: {$in: libraryCollection.libraries}, + $or: [ + { owner: userId }, + { writers: userId }, + { readers: userId }, + { public: true }, + ] + }, { + sort: { name: 1 } + }); + return [ libraryCollectionCursor, libraryCursor ]; + }); + }); + }) +}); -Meteor.publish('libraries', function(){ - this.autorun(function (){ +Meteor.publish('libraries', function () { + this.autorun(function () { let userId = this.userId; if (!userId) { return []; } const user = Meteor.users.findOne(userId, { - fields: {subscribedLibraries: 1} + fields: { subscribedLibraries: 1, subscribedLibraryCollections: 1 } }); - const subs = user && user.subscribedLibraries || []; - return Libraries.find({ - $or: [ - {owner: this.userId}, - {writers: this.userId}, - {readers: this.userId}, - { _id: {$in: subs}, public: true }, - ] - }, { - sort: {name: 1} + + this.autorun(function () { + // Get the collections the user is subscribed to + const subCollections = user && user.subscribedLibraryCollections || []; + const libraryCollectionsCursor = LibraryCollections.find({ + $or: [ + { owner: userId }, + { writers: userId }, + { readers: userId }, + { _id: { $in: subCollections }, public: true }, + ] + }, { + sort: { name: 1 } + }); + + // Collate all the libraryIds in those collections + let collectionLibIds = []; + libraryCollectionsCursor.forEach(libCollection => { + collectionLibIds = union(collectionLibIds, libCollection.libraries); + }); + + // Get the libraries the user is subscribed to directly + const subs = user && user.subscribedLibraries || []; + + // Combine all the library Ids + const libIds = union(collectionLibIds, subs); + + this.autorun(function () { + const librariesCursor = Libraries.find({ + $or: [ + { owner: userId }, + { writers: userId }, + { readers: userId }, + { _id: { $in: libIds }, public: true }, + ] + }, { + sort: { name: 1 } + }); + return [librariesCursor, libraryCollectionsCursor]; + }); }); }); }); diff --git a/app/imports/server/publications/users.js b/app/imports/server/publications/users.js index 289f60dc..f096ccd2 100644 --- a/app/imports/server/publications/users.js +++ b/app/imports/server/publications/users.js @@ -10,6 +10,7 @@ Meteor.publish('user', function(){ apiKey: 1, darkMode: 1, subscribedLibraries: 1, + subscribedLibraryCollections: 1, fileStorageUsed: 1, profile: 1, preferences: 1, diff --git a/app/imports/ui/components/SharedIcon.vue b/app/imports/ui/components/SharedIcon.vue index 568bd91c..936f01d0 100644 --- a/app/imports/ui/components/SharedIcon.vue +++ b/app/imports/ui/components/SharedIcon.vue @@ -1,6 +1,6 @@