Added basic community library browser
This commit is contained in:
@@ -29,6 +29,16 @@ let LibrarySchema = new SimpleSchema({
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.summary,
|
||||
},
|
||||
showInMarket: {
|
||||
index: 1,
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
subscriberCount: {
|
||||
index: 1,
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
LibrarySchema.extend(SharingSchema);
|
||||
@@ -104,6 +114,29 @@ const updateLibraryDescription = new ValidatedMethod({
|
||||
},
|
||||
});
|
||||
|
||||
const updateLibraryShowInMarket = new ValidatedMethod({
|
||||
name: 'libraries.updateShowInMarket',
|
||||
validate: new SimpleSchema({
|
||||
_id: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.id
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ _id, value }) {
|
||||
let library = Libraries.findOne(_id);
|
||||
assertEditPermission(library, this.userId);
|
||||
Libraries.update(_id, { $set: { showInMarket: value } });
|
||||
},
|
||||
});
|
||||
|
||||
const removeLibrary = new ValidatedMethod({
|
||||
name: 'libraries.remove',
|
||||
validate: new SimpleSchema({
|
||||
@@ -130,4 +163,4 @@ export function removeLibaryWork(libraryId) {
|
||||
LibraryNodes.remove({ 'ancestors.id': libraryId });
|
||||
}
|
||||
|
||||
export { LibrarySchema, insertLibrary, updateLibraryName, updateLibraryDescription, removeLibrary };
|
||||
export { LibrarySchema, insertLibrary, updateLibraryName, updateLibraryDescription, updateLibraryShowInMarket, removeLibrary };
|
||||
|
||||
@@ -32,6 +32,16 @@ const LibraryCollectionSchema = new SimpleSchema({
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
showInMarket: {
|
||||
index: 1,
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
subscriberCount: {
|
||||
index: 1,
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
LibraryCollectionSchema.extend(SharingSchema);
|
||||
@@ -48,12 +58,12 @@ const insertLibraryCollection = new ValidatedMethod({
|
||||
run(libraryCollection) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('LibraryCollections.methods.insert.denied',
|
||||
'You need to be logged in to insert a library');
|
||||
'You need to be logged in to insert a library');
|
||||
}
|
||||
let tier = getUserTier(this.userId);
|
||||
if (!tier.paidBenefits){
|
||||
if (!tier.paidBenefits) {
|
||||
throw new Meteor.Error('LibraryCollections.methods.insert.denied',
|
||||
`The ${tier.name} tier does not allow you to insert a library collection`);
|
||||
`The ${tier.name} tier does not allow you to insert a library collection`);
|
||||
}
|
||||
libraryCollection.owner = this.userId;
|
||||
return LibraryCollections.insert(libraryCollection);
|
||||
@@ -72,7 +82,7 @@ const updateLibraryCollection = new ValidatedMethod({
|
||||
},
|
||||
update: {
|
||||
type: LibraryCollectionSchema
|
||||
.pick('name', 'description', 'libraries')
|
||||
.pick('name', 'description', 'libraries', 'showInMarket')
|
||||
.extend({ //make libraries optional
|
||||
libraries: {
|
||||
optional: true,
|
||||
@@ -85,7 +95,7 @@ const updateLibraryCollection = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, update}){
|
||||
run({ _id, update }) {
|
||||
const libraryCollection = LibraryCollections.findOne(_id, {
|
||||
fields: {
|
||||
owner: 1,
|
||||
@@ -93,7 +103,7 @@ const updateLibraryCollection = new ValidatedMethod({
|
||||
}
|
||||
});
|
||||
assertEditPermission(libraryCollection, this.userId);
|
||||
return LibraryCollections.update(_id, {$set: update});
|
||||
return LibraryCollections.update(_id, { $set: update });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -110,7 +120,7 @@ const removeLibraryCollection = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id}){
|
||||
run({ _id }) {
|
||||
const libraryCollection = LibraryCollections.findOne(_id, {
|
||||
fields: {
|
||||
owner: 1,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import LibraryCollections from '/imports/api/library/LibraryCollections.js';
|
||||
import '/imports/api/users/methods/deleteMyAccount.js';
|
||||
import '/imports/api/users/methods/addEmail.js';
|
||||
import '/imports/api/users/methods/removeEmail.js';
|
||||
import '/imports/api/users/methods/updateFileStorageUsed.js';
|
||||
|
||||
import { some } from 'lodash';
|
||||
const defaultLibraries = process.env.DEFAULT_LIBRARIES && process.env.DEFAULT_LIBRARIES.split(',') || [];
|
||||
const defaultLibraryCollections = process.env.DEFAULT_LIBRARY_COLLECTIONS && process.env.DEFAULT_LIBRARY_COLLECTIONS.split(',') || [];
|
||||
@@ -250,6 +251,29 @@ Meteor.users.setPreference = new ValidatedMethod({
|
||||
},
|
||||
});
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Accounts.onCreateUser(() => {
|
||||
if (defaultLibraries?.length) {
|
||||
Libraries.update({
|
||||
_id: { $in: defaultLibraries }
|
||||
}, {
|
||||
$inc: { subscriberCount: 1 }
|
||||
}, {
|
||||
multi: true,
|
||||
}, () => {/**/ });
|
||||
}
|
||||
if (defaultLibraryCollections?.length) {
|
||||
LibraryCollections.update({
|
||||
_id: { $in: defaultLibraryCollections }
|
||||
}, {
|
||||
$inc: { subscriberCount: 1 }
|
||||
}, {
|
||||
multi: true,
|
||||
}, () => {/**/ });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Meteor.users.subscribeToLibrary = new ValidatedMethod({
|
||||
name: 'users.subscribeToLibrary',
|
||||
validate: new SimpleSchema({
|
||||
@@ -264,15 +288,17 @@ Meteor.users.subscribeToLibrary = new ValidatedMethod({
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
timeInterval: 2000,
|
||||
},
|
||||
run({ libraryId, subscribe }) {
|
||||
if (!this.userId) throw 'Can only subscribe if logged in';
|
||||
if (subscribe) {
|
||||
Libraries.update({ _id: libraryId }, { $inc: { subscriberCount: 1 } }, () => {/**/ });
|
||||
return Meteor.users.update(this.userId, {
|
||||
$addToSet: { subscribedLibraries: libraryId },
|
||||
});
|
||||
} else {
|
||||
Libraries.update({ _id: libraryId }, { $inc: { subscriberCount: -1 } }, () => {/**/ });
|
||||
return Meteor.users.update(this.userId, {
|
||||
$pullAll: { subscribedLibraries: libraryId },
|
||||
});
|
||||
@@ -299,10 +325,12 @@ Meteor.users.subscribeToLibraryCollection = new ValidatedMethod({
|
||||
run({ libraryCollectionId, subscribe }) {
|
||||
if (!this.userId) throw 'Can only subscribe if logged in';
|
||||
if (subscribe) {
|
||||
LibraryCollections.update({ _id: libraryCollectionId }, { $inc: { subscriberCount: 1 } }, () => {/**/ });
|
||||
return Meteor.users.update(this.userId, {
|
||||
$addToSet: { subscribedLibraryCollections: libraryCollectionId },
|
||||
});
|
||||
} else {
|
||||
LibraryCollections.update({ _id: libraryCollectionId }, { $inc: { subscriberCount: -1 } }, () => {/**/ });
|
||||
return Meteor.users.update(this.userId, {
|
||||
$pullAll: { subscribedLibraryCollections: libraryCollectionId },
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-bind="$attrs"
|
||||
:disabled="isDisabled"
|
||||
:loading="loading"
|
||||
@click.stop="click"
|
||||
@click.stop.prevent="click"
|
||||
>
|
||||
<slot />
|
||||
</v-btn>
|
||||
|
||||
@@ -147,11 +147,27 @@
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
<v-layout
|
||||
column
|
||||
align-center
|
||||
justify-center
|
||||
class="ma-4"
|
||||
class="text-caption text--disabled mt-8 mb-2"
|
||||
>
|
||||
Can't find what you're looking for?
|
||||
</v-layout>
|
||||
<v-layout
|
||||
align-center
|
||||
justify-center
|
||||
wrap
|
||||
class="mx-4 mb-4"
|
||||
>
|
||||
<v-btn
|
||||
v-if="!dummySlot"
|
||||
text
|
||||
data-id="library-browser-button"
|
||||
:disabled="!model"
|
||||
@click="openLibraryBrowser"
|
||||
>
|
||||
Browse community libraries
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="!dummySlot"
|
||||
text
|
||||
@@ -160,7 +176,7 @@
|
||||
data-id="custom-button"
|
||||
@click="insertCustomFiller"
|
||||
>
|
||||
Create custom
|
||||
Create custom filler
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
<template v-if="!showDisabled && disabledNodeCount">
|
||||
@@ -304,6 +320,12 @@ export default {
|
||||
},
|
||||
});
|
||||
},
|
||||
openLibraryBrowser() {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'library-browser-dialog',
|
||||
elementId: 'library-browser-button',
|
||||
});
|
||||
},
|
||||
isDisabled(node) {
|
||||
return node._disabledBySlotFillerCondition ||
|
||||
node._disabledByAlreadyAdded ||
|
||||
|
||||
@@ -15,6 +15,7 @@ import SelectLibraryNodeDialog from '/imports/client/ui/library/SelectLibraryNod
|
||||
import SlotFillDialog from '/imports/client/ui/creature/slots/SlotFillDialog.vue';
|
||||
import TierTooLowDialog from '/imports/client/ui/user/TierTooLowDialog.vue';
|
||||
import TransferOwnershipDialog from '/imports/client/ui/sharing/TransferOwnershipDialog.vue';
|
||||
import LibraryBrowserDialog from '/imports/client/ui/library/LibraryBrowserDialog.vue';
|
||||
|
||||
// Lazily load less common dialogs
|
||||
const ArchiveDialog = () => import('/imports/client/ui/creature/archive/ArchiveDialog.vue');
|
||||
@@ -48,6 +49,7 @@ export default {
|
||||
HelpDialog,
|
||||
InviteDialog,
|
||||
LevelUpDialog,
|
||||
LibraryBrowserDialog,
|
||||
LibraryCollectionCreationDialog,
|
||||
LibraryCollectionEditDialog,
|
||||
LibraryCreationDialog,
|
||||
|
||||
35
app/imports/client/ui/library/LibraryBrowserDialog.vue
Normal file
35
app/imports/client/ui/library/LibraryBrowserDialog.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template lang="html">
|
||||
<dialog-base>
|
||||
<template slot="toolbar">
|
||||
<v-toolbar-title>
|
||||
Community Libraries
|
||||
</v-toolbar-title>
|
||||
</template>
|
||||
<library-browser slot="unwrapped-content" />
|
||||
<template slot="actions">
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
text
|
||||
@click="$store.dispatch('popDialogStack')"
|
||||
>
|
||||
Done
|
||||
</v-btn>
|
||||
</template>
|
||||
</dialog-base>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import DialogBase from '/imports/client/ui/dialogStack/DialogBase.vue';
|
||||
import LibraryBrowser from '/imports/client/ui/pages/LibraryBrowser.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DialogBase,
|
||||
LibraryBrowser,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
@@ -31,6 +31,11 @@
|
||||
:value="model.description"
|
||||
@change="(description, ack) => updateLibraryCollection({description}, ack)"
|
||||
/>
|
||||
<smart-switch
|
||||
:value="model.showInMarket"
|
||||
label="Show in community library browser"
|
||||
@change="(showInMarket, ack) => updateLibraryCollection({showInMarket}, ack)"
|
||||
/>
|
||||
<smart-select
|
||||
label="Libraries"
|
||||
:items="libraryOptions"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<v-app-bar-nav-icon @click="toggleDrawer" />
|
||||
<v-btn
|
||||
icon
|
||||
@click="$router.push('/library')"
|
||||
@click="back"
|
||||
>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
@@ -34,6 +34,14 @@
|
||||
>
|
||||
<v-icon>mdi-cog</v-icon>
|
||||
</v-btn>
|
||||
<v-spacer slot="extension" />
|
||||
<div
|
||||
v-if="libraryCollection && libraryCollection.subscriberCount"
|
||||
slot="extension"
|
||||
class="mx-4 text--disabled"
|
||||
>
|
||||
{{ formatNumber(libraryCollection.subscriberCount) }} subscribers
|
||||
</div>
|
||||
</v-app-bar>
|
||||
</template>
|
||||
|
||||
@@ -41,6 +49,7 @@
|
||||
import LibraryCollections from '/imports/api/library/LibraryCollections.js';
|
||||
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { mapMutations } from 'vuex';
|
||||
import formatter from '/imports/client/ui/utility/numberFormatter.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -87,6 +96,9 @@ export default {
|
||||
...mapMutations([
|
||||
'toggleDrawer',
|
||||
]),
|
||||
formatNumber(num) {
|
||||
return formatter.format(num);
|
||||
},
|
||||
subscribe(value) {
|
||||
this.loading = true;
|
||||
Meteor.users.subscribeToLibraryCollection.call({
|
||||
@@ -103,6 +115,9 @@ export default {
|
||||
data: { _id: this.$route.params.id },
|
||||
});
|
||||
},
|
||||
back() {
|
||||
return window.history.length > 2 ? this.$router.back() : this.$router.push('/library');
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -31,6 +31,11 @@
|
||||
:value="model.description"
|
||||
@change="updateDescription"
|
||||
/>
|
||||
<smart-switch
|
||||
:value="model.showInMarket"
|
||||
label="Show in community library browser"
|
||||
@change="updateShowInMarket"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="removedDocs.length">
|
||||
<h3>Recently Deleted Properties</h3>
|
||||
@@ -76,7 +81,7 @@
|
||||
|
||||
<script lang="js">
|
||||
import DialogBase from '/imports/client/ui/dialogStack/DialogBase.vue';
|
||||
import Libraries, { updateLibraryName, updateLibraryDescription, removeLibrary } from '/imports/api/library/Libraries.js';
|
||||
import Libraries, { updateLibraryName, updateLibraryDescription, updateLibraryShowInMarket, removeLibrary } from '/imports/api/library/Libraries.js';
|
||||
import LibraryNodes, { restoreLibraryNode } from '/imports/api/library/LibraryNodes.js';
|
||||
import TreeNodeView from '/imports/client/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js';
|
||||
@@ -100,8 +105,15 @@ export default {
|
||||
ack(error && error.reason || error);
|
||||
});
|
||||
},
|
||||
updateShowInMarket(value, ack) {
|
||||
updateLibraryShowInMarket.call({ _id: this._id, value }, (error) => {
|
||||
ack(error && error.reason || error);
|
||||
});
|
||||
},
|
||||
remove() {
|
||||
let that = this;
|
||||
const _id = this._id;
|
||||
const $router = this.$router;
|
||||
const $store = this.$store;
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'delete-confirmation-dialog',
|
||||
elementId: 'delete-library-button',
|
||||
@@ -111,15 +123,15 @@ export default {
|
||||
},
|
||||
callback(confirmation) {
|
||||
if (!confirmation) return;
|
||||
removeLibrary.call({ _id: that._id }, (error) => {
|
||||
removeLibrary.call({ _id }, (error) => {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
snackbar({
|
||||
text: error.reason,
|
||||
});
|
||||
} else {
|
||||
that.$router.push({ name: 'library', replace: true });
|
||||
that.$store.dispatch('popDialogStack');
|
||||
$router.push({ name: 'library', replace: true });
|
||||
$store.dispatch('popDialogStack');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<v-app-bar-nav-icon @click="toggleDrawer" />
|
||||
<v-btn
|
||||
icon
|
||||
@click="$router.push('/library')"
|
||||
@click="back"
|
||||
>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
@@ -34,11 +34,20 @@
|
||||
>
|
||||
<v-icon>mdi-cog</v-icon>
|
||||
</v-btn>
|
||||
<v-spacer slot="extension" />
|
||||
<div
|
||||
v-if="library && library.subscriberCount"
|
||||
slot="extension"
|
||||
class="mx-4 text--disabled"
|
||||
>
|
||||
{{ formatNumber(library.subscriberCount) }} subscribers
|
||||
</div>
|
||||
</v-app-bar>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import formatter from '/imports/client/ui/utility/numberFormatter.js';
|
||||
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { mapMutations } from 'vuex';
|
||||
|
||||
@@ -87,6 +96,9 @@ export default {
|
||||
...mapMutations([
|
||||
'toggleDrawer',
|
||||
]),
|
||||
formatNumber(num) {
|
||||
return formatter.format(num);
|
||||
},
|
||||
subscribe(value) {
|
||||
this.loading = true;
|
||||
Meteor.users.subscribeToLibrary.call({
|
||||
@@ -103,6 +115,9 @@ export default {
|
||||
data: { _id: this.$route.params.id },
|
||||
});
|
||||
},
|
||||
back() {
|
||||
return window.history.length > 2 ? this.$router.back() : this.$router.push('/library');
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -29,11 +29,18 @@
|
||||
<library-list v-else />
|
||||
</v-fade-transition>
|
||||
</v-card>
|
||||
<div class="layout justify-end mt-2">
|
||||
<div class="layout wrap justify-end mt-2">
|
||||
<v-btn
|
||||
text
|
||||
to="/community-libraries"
|
||||
>
|
||||
Browse community libraries
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="paidBenefits"
|
||||
text
|
||||
data-id="insert-library-collection-button"
|
||||
color="accent"
|
||||
:loading="loadingInsertLibraryCollection"
|
||||
@click="insertLibraryCollection"
|
||||
>
|
||||
|
||||
153
app/imports/client/ui/pages/LibraryBrowser.vue
Normal file
153
app/imports/client/ui/pages/LibraryBrowser.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div
|
||||
class="card-background"
|
||||
style="height: 100%"
|
||||
>
|
||||
<v-container>
|
||||
<v-fade-transition mode="out-in">
|
||||
<v-row
|
||||
v-if="$subReady.browseLibraries"
|
||||
key="loaded-cards"
|
||||
dense
|
||||
>
|
||||
<v-col
|
||||
v-for="card in libraryCards"
|
||||
:key="card._id"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
lg="3"
|
||||
>
|
||||
<v-sheet
|
||||
class="fill-height"
|
||||
rounded
|
||||
outlined
|
||||
:color="card.subscribed ? 'accent': ''"
|
||||
>
|
||||
<v-card
|
||||
class="fill-height d-flex flex-column"
|
||||
elevation="0"
|
||||
:to="`/library${card._type === 'libraryCollection' ? '-collection' : ''}/${card._id}`"
|
||||
>
|
||||
<v-card-title>
|
||||
{{ card.name }}
|
||||
</v-card-title>
|
||||
<v-card-subtitle v-if="card.subscriberCount">
|
||||
{{ formatNumber(card.subscriberCount) }} subscribers
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<markdown-text :markdown="card.description" />
|
||||
</v-card-text>
|
||||
<v-spacer />
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<smart-btn
|
||||
text
|
||||
single-click
|
||||
:color="card.subscribed ? '': 'accent'"
|
||||
@click="ack => changeSubscribe(card, ack)"
|
||||
>
|
||||
{{ card.subscribed ? 'Unsubscribe' : 'Subscribe' }}
|
||||
</smart-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row
|
||||
v-else
|
||||
key="loading-spinner"
|
||||
>
|
||||
<v-col
|
||||
cols="12"
|
||||
class="d-flex align-center justify-center"
|
||||
>
|
||||
<v-progress-circular
|
||||
indeterminate
|
||||
color="primary"
|
||||
size="64"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-fade-transition>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { sortBy } from 'lodash';
|
||||
import LibraryCollections from '/imports/api/library/LibraryCollections.js';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import MarkdownText from '/imports/client/ui/components/MarkdownText.vue';
|
||||
import formatter from '/imports/client/ui/utility/numberFormatter.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MarkdownText
|
||||
},
|
||||
data(){ return{
|
||||
loadingInsertLibraryCollection: false,
|
||||
openCollections: [],
|
||||
}},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'browseLibraries': [],
|
||||
},
|
||||
collections(){
|
||||
const user = Meteor.user() || {};
|
||||
const subCollections = user.subscribedLibraryCollections || [];
|
||||
return LibraryCollections.find({
|
||||
showInMarket: true,
|
||||
public: true,
|
||||
}, {
|
||||
sort: { subscriberCount: 1, name: 1 }
|
||||
}).map(col => {
|
||||
col.subscribed = subCollections.includes(col._id);
|
||||
col._type = 'libraryCollection';
|
||||
return col;
|
||||
});
|
||||
},
|
||||
libraries(){
|
||||
const user = Meteor.user() || {};
|
||||
const subLibraries = user.subscribedLibraries || [];
|
||||
return Libraries.find({
|
||||
showInMarket: true,
|
||||
public: true,
|
||||
}, {
|
||||
sort: { subscriberCount: 1, name: 1 }
|
||||
}).map(lib => {
|
||||
lib.subscribed = subLibraries.includes(lib._id);
|
||||
lib._type = 'library';
|
||||
return lib;
|
||||
});
|
||||
},
|
||||
libraryCards() {
|
||||
return sortBy([...this.libraries, ...this.collections], 'name');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatNumber(num) {
|
||||
return formatter.format(num);
|
||||
},
|
||||
changeSubscribe(card, ack) {
|
||||
const id = card._id;
|
||||
const subscribe = !card.subscribed;
|
||||
|
||||
if (card._type === 'library') {
|
||||
Meteor.users.subscribeToLibrary.call({
|
||||
libraryId: id,
|
||||
subscribe,
|
||||
}, ack);
|
||||
} else if (card._type === 'libraryCollection') {
|
||||
Meteor.users.subscribeToLibraryCollection.call({
|
||||
libraryCollectionId: id,
|
||||
subscribe,
|
||||
}, ack);
|
||||
} else {
|
||||
console.log('sub fail')
|
||||
ack('Library or Library Collection not found')
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -9,6 +9,7 @@ const CharacterListToolbarItems = () => import('/imports/client/ui/creature/crea
|
||||
const Library = () => import('/imports/client/ui/pages/Library.vue');
|
||||
const LibraryCollection = () => import('/imports/client/ui/pages/LibraryCollection.vue');
|
||||
const LibraryCollectionToolbar = () => import('/imports/client/ui/library/LibraryCollectionToolbar.vue');
|
||||
const LibraryBrowser = () => import('/imports/client/ui/pages/LibraryBrowser.vue');
|
||||
const CharacterSheetPage = () => import('/imports/client/ui/pages/CharacterSheetPage.vue');
|
||||
const CharacterSheetToolbar = () => import('/imports/client/ui/creature/character/CharacterSheetToolbar.vue');
|
||||
const CharacterSheetRightDrawer = () => import('/imports/client/ui/creature/character/CharacterSheetRightDrawer.vue');
|
||||
@@ -168,6 +169,15 @@ RouterFactory.configure(router => {
|
||||
meta: {
|
||||
title: 'Library Collection',
|
||||
},
|
||||
}, {
|
||||
name: 'libraryBrowser',
|
||||
path: '/community-libraries',
|
||||
components: {
|
||||
default: LibraryBrowser,
|
||||
},
|
||||
meta: {
|
||||
title: 'Community Libraries',
|
||||
},
|
||||
}, {
|
||||
name: 'characterSheet',
|
||||
path: '/character/:id',
|
||||
|
||||
3
app/imports/client/ui/utility/numberFormatter.js
Normal file
3
app/imports/client/ui/utility/numberFormatter.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const formatter = Intl.NumberFormat('en', { notation: 'compact' });
|
||||
|
||||
export default formatter;
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Migrations } from 'meteor/percolate:migrations';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import { union } from 'lodash';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import LibraryCollections from '/imports/api/library/LibraryCollections.js';
|
||||
|
||||
// Git version 2.0-beta.33
|
||||
// Database version 1
|
||||
// Git version 2.0.52
|
||||
// Database version 2
|
||||
Migrations.add({
|
||||
version: 2,
|
||||
name: 'Separates creature property tags from library tags',
|
||||
@@ -13,10 +15,11 @@ Migrations.add({
|
||||
const bulk = LibraryNodes.rawCollection().initializeUnorderedBulkOp();
|
||||
LibraryNodes.find({}).forEach(prop => migratePropUp(bulk, prop));
|
||||
bulk.execute();
|
||||
countSubscribers();
|
||||
},
|
||||
|
||||
down() {
|
||||
console.log('migrating down library nodes 2 -> 1');
|
||||
console.log('Migrating down library nodes 2 -> 1');
|
||||
const bulk = LibraryNodes.rawCollection().initializeUnorderedBulkOp();
|
||||
LibraryNodes.find({}).forEach(prop => migratePropDown(bulk, prop));
|
||||
bulk.execute();
|
||||
@@ -61,3 +64,30 @@ export function migratePropDown(bulk, prop) {
|
||||
}
|
||||
bulk.find({ _id: prop._id }).updateOne(update);
|
||||
}
|
||||
|
||||
function countSubscribers() {
|
||||
console.log('Migrating up libraries and collections to count subscribers');
|
||||
const bulkLib = Libraries.rawCollection().initializeUnorderedBulkOp();
|
||||
Libraries.find({}, {
|
||||
fields: { _id: 1 }
|
||||
}).forEach(lib => {
|
||||
bulkLib.find({ _id: lib._id }).updateOne({
|
||||
$set: {
|
||||
subscriberCount: Meteor.users.find({ subscribedLibraries: lib._id }).count(),
|
||||
}
|
||||
});
|
||||
});
|
||||
bulkLib.execute();
|
||||
|
||||
const bulkLibCols = Libraries.rawCollection().initializeUnorderedBulkOp();
|
||||
LibraryCollections.find({}, {
|
||||
fields: { _id: 1 }
|
||||
}).forEach(col => {
|
||||
bulkLibCols.find({ _id: col._id }).updateOne({
|
||||
$set: {
|
||||
subscriberCount: Meteor.users.find({ subscribedLibraryCollections: col._id }).count(),
|
||||
}
|
||||
});
|
||||
});
|
||||
bulkLibCols.execute();
|
||||
}
|
||||
|
||||
@@ -141,6 +141,24 @@ Meteor.publish('libraries', function () {
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.publish('browseLibraries', function () {
|
||||
if (!this.userId) return [];
|
||||
return [
|
||||
Libraries.find({
|
||||
showInMarket: true,
|
||||
public: true,
|
||||
}, {
|
||||
sort: { name: 1 }
|
||||
}),
|
||||
LibraryCollections.find({
|
||||
showInMarket: true,
|
||||
public: true,
|
||||
}, {
|
||||
sort: { name: 1 }
|
||||
}),
|
||||
];
|
||||
});
|
||||
|
||||
Meteor.publish('library', function (libraryId) {
|
||||
if (!libraryId) return [];
|
||||
libraryIdSchema.validate({ libraryId });
|
||||
|
||||
Reference in New Issue
Block a user