Added library list UI
This commit is contained in:
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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}},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{{userName}}
|
||||
<v-spacer></v-spacer>
|
||||
<v-tooltip bottom>
|
||||
<v-btn flat icon slot="activator"><v-icon>settings</v-icon></v-btn>
|
||||
<v-btn flat icon slot="activator" to="/account"><v-icon>settings</v-icon></v-btn>
|
||||
<span>Account Settings</span>
|
||||
</v-tooltip>
|
||||
</v-layout>
|
||||
@@ -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"},
|
||||
|
||||
@@ -18,7 +18,7 @@ export default {
|
||||
dirty: false,
|
||||
safeValue: this.value,
|
||||
inputValue: this.value,
|
||||
}},
|
||||
};},
|
||||
props: {
|
||||
value: [String, Number],
|
||||
debounceTime: {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
53
app/imports/ui/library/LibraryCreationDialog.vue
Normal file
53
app/imports/ui/library/LibraryCreationDialog.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template lang="html">
|
||||
<dialog-base>
|
||||
<template slot="toolbar">
|
||||
<div>
|
||||
New Library
|
||||
</div>
|
||||
</template>
|
||||
<template>
|
||||
<text-field label="name" :value="library.name" @change="nameChanged" :debounce-time="0"/>
|
||||
</template>
|
||||
<template slot="actions">
|
||||
<v-spacer/>
|
||||
<v-btn
|
||||
flat
|
||||
:disabled="!valid"
|
||||
@click="$store.dispatch('popDialogStack', library)"
|
||||
>
|
||||
Insert Library
|
||||
</v-btn>
|
||||
</template>
|
||||
</dialog-base>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
|
||||
export default {
|
||||
data(){ return {
|
||||
library: {
|
||||
name: 'New Library',
|
||||
},
|
||||
valid: true,
|
||||
}},
|
||||
components: {
|
||||
DialogBase,
|
||||
},
|
||||
methods: {
|
||||
nameChanged(val, ack){
|
||||
if (val){
|
||||
this.library.name = val;
|
||||
this.valid = true,
|
||||
ack();
|
||||
} else {
|
||||
this.valid = false;
|
||||
ack('Name is required')
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
71
app/imports/ui/pages/Libraries.vue
Normal file
71
app/imports/ui/pages/Libraries.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template lang="html">
|
||||
<toolbar-layout>
|
||||
<span slot="toolbar">Libraries</span>
|
||||
<v-card class="ma-4">
|
||||
<v-list v-if="libraries.length">
|
||||
<v-list-tile
|
||||
v-for="library in libraries"
|
||||
:key="library._id"
|
||||
:to="library.url"
|
||||
:data-id="library._id"
|
||||
>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
{{library.name}}
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
<v-card-text v-else-if="$subReady.libraries">
|
||||
You aren't subscribed to any libraries :O
|
||||
</v-card-text>
|
||||
<v-card-text v-if="!$subReady.libraries" class="layout row justify-center">
|
||||
<v-progress-circular indeterminate/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-btn fixed fab bottom right
|
||||
color="primary"
|
||||
@click="insertLibrary"
|
||||
data-id="insert-library-fab"
|
||||
>
|
||||
<v-icon>add</v-icon>
|
||||
</v-btn>
|
||||
</toolbar-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ToolbarLayout from '/imports/ui/layouts/ToolbarLayout.vue';
|
||||
import Libraries, {insertLibrary} from '/imports/api/library/Libraries.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ToolbarLayout,
|
||||
},
|
||||
methods: {
|
||||
insertLibrary(){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'library-creation-dialog',
|
||||
elementId: 'insert-library-fab',
|
||||
callback(library){
|
||||
if (!library) return;
|
||||
let libraryId = insertLibrary.call(library);
|
||||
return libraryId;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'libraries': [],
|
||||
},
|
||||
libraries(){
|
||||
return Libraries.find({}, {
|
||||
sort: {name: 1}
|
||||
}).fetch();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -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,
|
||||
},
|
||||
]);
|
||||
|
||||
14
app/imports/ui/styles/inheritBackgrounds.css
Normal file
14
app/imports/ui/styles/inheritBackgrounds.css
Normal file
@@ -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;
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
import "./speedDial.css";
|
||||
import './speedDial.css';
|
||||
import './inheritBackgrounds.css';
|
||||
|
||||
Reference in New Issue
Block a user