Merge branch 'feature-library-ui'

This commit is contained in:
Stefan Zermatten
2019-05-06 14:55:15 +02:00
30 changed files with 523 additions and 260 deletions

View File

@@ -25,13 +25,13 @@ chuangbo:marked
reywood:iron-router-ga
meteor-base@1.4.0
mobile-experience@1.0.5
mongo@1.6.0
mongo@1.6.2
blaze-html-templates
session@1.2.0
jquery@1.11.10
tracker@1.2.0
logging@1.1.20
reload@1.2.0
reload@1.3.0
ejson@1.1.0
spacebars
check@1.3.1
@@ -43,11 +43,11 @@ templates:array
ecmascript@0.12.4
es5-shim@4.8.0
differential:vulcanize
reactive-dict@1.2.1
reactive-dict@1.3.0
ongoworks:speakingurl
service-configuration@1.0.11
google-config-ui@1.0.1
dynamic-import@0.5.0
dynamic-import@0.5.1
ddp-rate-limiter@1.0.7
rate-limit@1.0.9
iron:router

View File

@@ -1 +1 @@
METEOR@1.8.0.2
METEOR@1.8.1

View File

@@ -1,4 +1,4 @@
accounts-base@1.4.3
accounts-base@1.4.4
accounts-google@1.3.2
accounts-oauth@1.1.16
accounts-password@1.5.1
@@ -10,8 +10,8 @@ aldeed:schema-deny@1.1.0
aldeed:schema-index@1.1.1
aldeed:simple-schema@1.5.4
allow-deny@1.1.0
autoupdate@1.5.1
babel-compiler@7.2.4
autoupdate@1.6.0
babel-compiler@7.3.4
babel-runtime@1.3.0
base64@1.0.11
binary-heap@1.0.11
@@ -31,12 +31,12 @@ ddp@1.4.0
ddp-client@2.3.3
ddp-common@1.4.0
ddp-rate-limiter@1.0.7
ddp-server@2.2.0
ddp-server@2.3.0
deps@1.0.12
diff-sequence@1.1.1
differential:vulcanize@3.0.0
dynamic-import@0.5.1
ecmascript@0.12.6
ecmascript@0.12.7
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.8.0
ecmascript-runtime-server@0.7.1
@@ -103,9 +103,9 @@ promise@0.11.2
raix:eventemitter@0.1.3
random@1.1.0
rate-limit@1.0.9
reactive-dict@1.2.1
reactive-dict@1.3.0
reactive-var@1.0.11
reload@1.2.0
reload@1.3.0
retry@1.1.0
reywood:iron-router-ga@0.7.1
routepolicy@1.1.0
@@ -133,7 +133,7 @@ url@1.2.0
useraccounts:core@1.14.2
useraccounts:iron-routing@1.14.2
useraccounts:polymer@1.14.2
webapp@1.7.3
webapp@1.7.4
webapp-hashing@1.0.9
zimme:collection-behaviours@1.1.3
zimme:collection-softremovable@1.0.5

View File

@@ -10,19 +10,21 @@ Schemas.Library = new SimpleSchema({
Libraries.attachSchema(Schemas.Library);
Libraries.after.remove(function(userId, library) {
LibraryItems.remove({library: library._id});
LibrarySpells.remove({library: library._id});
});
if (Meteor.isServer){
Libraries.after.remove(function(userId, library) {
LibraryItems.remove({library: library._id});
LibrarySpells.remove({library: library._id});
});
}
Meteor.methods({
removeLibrary: function(libraryId) {
unshareLibraryWithMe: function(libraryId) {
let library = Libraries.findOne(libraryId);
let userId = Meteor.userId();
let userId = Meteor.userId();
if (!library) return;
if (library.owner === userId){
Libraries.remove(libraryId);
throw new Meteor.error("Can't unshare, you own this")
} else {
if (_.contains(library.readers, userId)){
Libraries.update(libraryId, {$pull: {"readers": userId}});

View File

@@ -8,6 +8,8 @@ Router.plugin("ensureSignedIn", {
only: [
"profile",
"characterList",
"library",
"libraries",
]
});
@@ -118,11 +120,28 @@ Router.map(function() {
},
});
this.route("library", {
this.route("libraries", {
path: "/library",
waitOn: function(){
return subsManager.subscribe("customLibraries");
},
onAfterAction: function() {
document.title = appName + " - Libraries";
},
fastRender: true,
});
this.route("library", {
path: "/library/:_id",
waitOn: function(){
return [
subsManager.subscribe("libraryItems", this.params._id),
subsManager.subscribe("singleLibrary", this.params._id),
];
},
data: function() {
return Libraries.findOne(this.params._id);
},
onAfterAction: function() {
document.title = appName + " - Library";
},

View File

@@ -0,0 +1,7 @@
Template.registerHelper("isTier5", function(){
let user = Meteor.user();
if (!user) return false;
patreon = user.patreon;
if (!patreon) return false;
return patreon.entitledCents >= 500 || patreon.entitledCentsOverride >= 500;
});

View File

@@ -0,0 +1,19 @@
const CLIENT_ID = Meteor.settings &&
Meteor.settings.public.patreon &&
Meteor.settings.public.patreon.clientId;
Template.registerHelper("patreonLoginUrl", function() {
if (!CLIENT_ID) return;
return formatUrl({
protocol: 'https',
host: 'patreon.com',
pathname: '/oauth2/authorize',
query: {
response_type: 'code',
client_id: CLIENT_ID,
redirect_uri: Meteor.absoluteUrl() + 'patreon-redirect',
state: Meteor.userId(),
scope: 'identity',
},
});
});

View File

@@ -157,7 +157,7 @@ Template.inventory.events({
}
// Make the library item into a regular item
let item = _.omit(result, "libraryName", "library", "attacks", "effects");
delete item.settings.category;
if (item.settings && item.settings.category) delete item.settings.category;
// Update the item to match library item
Items.update(itemId, {$set: item});
// Copy over attacks and effects

View File

@@ -4,19 +4,30 @@
<p>
To get started, add a feature
</p>
<div class="layout vertical end">
<paper-button class="skip-button" style="color: #d13b2e">Skip</paper-button>
</div>
</paper-step>
<paper-step id="step1" label="Add an effect">
<p>
Add a racial effect to set your speed
</p>
<div class="layout vertical end">
<paper-button class="skip-button" style="color: #d13b2e">Skip</paper-button>
</div>
</paper-step>
<paper-step id="step2" label="See the effect in action">
<p>
View your speed stat
</p>
<div class="layout vertical end">
<paper-button class="skip-button" style="color: #d13b2e">Skip</paper-button>
</div>
</paper-step>
<paper-step id="step3" label="Finish">
Done! If you get stuck, be sure to check out the <a href="/guide">guide</a>, or ask for help using the feedback form
<p>
Done! If you get stuck, be sure to check out the <a href="/guide">guide</a>, or ask for help using the feedback form
</p>
<div class="layout vertical end">
<paper-button class="done-button" style="color: #d13b2e">Finish</paper-button>
</div>

View File

@@ -46,6 +46,10 @@ Template.newUserStepper.events({
const stepper = instance.find("paper-stepper");
stepper.continue();
},
"click .skip-button": function(event, instance){
const stepper = instance.find("paper-stepper");
stepper.continue();
},
});
Template.stats.events({

View File

@@ -44,14 +44,12 @@
Characters
</paper-icon-item>
</a>
{{#if isTier5}}
<a href="/library" tabindex="-1">
<paper-icon-item id="libary">
<iron-icon icon="book" item-icon></iron-icon>
Library (beta)
</paper-icon-item>
</a>
{{/if}}
<a href="/library" tabindex="-1">
<paper-icon-item id="libary">
<iron-icon icon="book" item-icon></iron-icon>
Libraries (beta)
</paper-icon-item>
</a>
<a href="/guide" tabindex="-1">
<paper-icon-item id="guide">
<iron-icon icon="social:school" item-icon></iron-icon>

View File

@@ -17,13 +17,6 @@ Template.appDrawer.helpers({
let post = PatreonPosts.findOne({}, {sort: {date: -1}});
return (post && post.link) || 'https://www.patreon.com/dicecloud';
},
isTier5: function(){
let user = Meteor.user();
if (!user) return false;
patreon = user.patreon;
if (!patreon) return false;
return patreon.entitledCents >= 500 || patreon.entitledCentsOverride >= 500;
},
patreonTier: function(){
let user = Meteor.user();
if (!user) return;

View File

@@ -1,29 +0,0 @@
<template name="itemLibrary">
{{#each libraries}}
<div class="paper-font-subhead library-header layout horizontal center" data-id={{_id}} style="height: 40px;">
<iron-icon icon="chevron-right" class="{{#if isOpen _id}}open{{/if}}">
</iron-icon>
<div class="flex">{{name}}</div>
{{#if isOpen _id}}
<div class="relative">
<paper-icon-button icon="create" class="editLibrary"></paper-icon-button>
{{#simpleTooltip}}Edit Library{{/simpleTooltip}}
</div>
<div class="relative">
<paper-icon-button icon="add" class="addItem"></paper-icon-button>
{{#simpleTooltip}}Add Item{{/simpleTooltip}}
</div>
{{/if}}
</div>
<iron-collapse opened={{isOpen _id}}>
{{#each libraryItems}}
<paper-item class="short item-name" data-id={{_id}}>
{{name}}
</paper-item>
{{/each}}
{{#unless ready _id}}
<paper-spinner active></paper-spinner>
{{/unless}}
</iron-collapse>
{{/each}}
</template>

View File

@@ -1,94 +0,0 @@
const librarySubs = new SubsManager();
Template.itemLibrary.onCreated(function(){
this.selectedTab = new ReactiveVar("0");
this.librariesOpen = new ReactiveVar([]);
this.readyDict = new ReactiveDict();
this.autorun(() => {
// Subscribe to all open libraries
_.each(this.librariesOpen.get(), (libraryId) => {
var handle = librarySubs.subscribe("libraryItems", libraryId);
this.autorun(() => {
this.readyDict.set(libraryId, handle.ready());
});
});
});
});
Template.itemLibrary.helpers({
selectedTab(){
return Template.instance().selectedTab.get();
},
libraries(){
let userId = Meteor.userId();
return Libraries.find({
$or: [
{readers: userId},
{writers: userId},
{owner: userId},
],
});
},
libraryItems(){
return LibraryItems.find({
library: this._id
},{
sort: {name: 1}
});
},
ready(libraryId){
return Template.instance().readyDict.get(libraryId);
},
isOpen(libraryId){
const librariesOpen = Template.instance().librariesOpen.get();
return _.contains(librariesOpen, libraryId);
},
});
Template.itemLibrary.events({
"click .library-header": function(event, template){
let libs = template.librariesOpen.get();
const libraryId = this._id;
// Toggle whether this key is in the array or not
if (_.contains(libs, libraryId)){
libs = _.without(libs, libraryId);
} else {
libs.push(libraryId);
}
template.librariesOpen.set(libs);
},
"click .editLibrary": function(event, instance){
event.stopPropagation();
var libraryId = this._id;
pushDialogStack({
template: "libraryDialog",
data: {libraryId},
element: event.currentTarget.parentElement.parentElement,
returnElement: () => instance.find(`.library-header[data-id='${libraryId}']`),
});
},
"click .addItem": function(event, instance){
event.stopPropagation();
var libraryId = this._id;
var itemId = LibraryItems.insert({
name: "New Library Item",
library: libraryId,
});
pushDialogStack({
template: "libraryItemDialog",
data: {itemId},
element: event.currentTarget,
returnElement: () => instance.find(`.item-name[data-id='${itemId}']`),
});
},
"click .item-name": function(event, instance){
event.stopPropagation();
var itemId = this._id;
pushDialogStack({
template: "libraryItemDialog",
data: {itemId},
element: event.currentTarget,
returnElement: () => instance.find(`.item-name[data-id='${itemId}']`),
});
},
})

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,35 @@
<template name="libraries">
<div class="fit layout vertical library">
<app-header fixed effects="waterfall">
<app-toolbar class="medium-tall app-grey white-text">
<div top-item class="layout horizontal center" style="min-height: 56px;">
<paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
<div class="flex layout horizontal center" style="height: 40px; margin-left: 8px;">
Libraries
</div>
</div>
</app-toolbar>
</app-header>
{{#if isTier5}}
<div class="flex layout vertical center" style="position: relative; padding: 0 16px;">
<paper-material class="card" style="padding: 32px; max-width: 800px; width: 100%;">
{{#each library in libraries}}
<a href="/library/{{library._id}}" tabindex="-1">
<paper-item class="library" data-id="{{library._id}}">
<paper-item-body>
<div>{{library.name}}</div>
</paper-item-body>
</paper-item>
</a>
{{/each}}
</paper-material>
</div>
<div class="floatyButton">
<paper-fab id="addLibrary" icon="add"></paper-fab>
{{#simpleTooltip}}Add Library{{/simpleTooltip}}
</div>
{{else}}
{{> patronsOnly }}
{{/if}}
</div>
</template>

View File

@@ -0,0 +1,38 @@
const librarySubs = new SubsManager();
Template.libraries.helpers({
libraries(){
let userId = Meteor.userId();
let subs = Meteor.user() && Meteor.user().profile.librarySubscriptions;
return Libraries.find({
$or: [
{readers: userId},
{writers: userId},
{owner: userId},
{_id: {$in: subs}}
],
}, {
sort: {name: 1},
});
},
});
Template.libraries.events({
"click #addLibrary": function(event, instance){
var libraryId = Libraries.insert({
name: "New Library",
owner: Meteor.userId(),
});
pushDialogStack({
template: "libraryDialog",
data: {libraryId},
element: event.currentTarget,
returnElement: () => instance.find(`.library[data-id='${libraryId}']`),
callback(data){
if (data && data.delete){
Libraries.remove(libraryId);
}
}
});
},
})

View File

@@ -1,19 +0,0 @@
.library .item-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
}
.library .library-header {
font-weight: 500;
cursor: pointer;
}
.library .library-header iron-icon {
transition: transform 0.3s ease;
}
.library .library-header iron-icon.open {
transform: rotate(90deg);
}

View File

@@ -2,31 +2,55 @@
<div class="fit layout vertical library">
<app-header fixed effects="waterfall">
<app-toolbar class="medium-tall app-grey white-text">
<div top-item class="layout horizontal center">
<div top-item class="layout horizontal center" style="min-height: 56px;">
<paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
<a href="/library"><paper-icon-button icon="arrow-back"></paper-icon-button></a>
<div class="flex layout horizontal center" style="height: 40px; margin-left: 8px;">
Item Library
{{name}}
</div>
{{#if isTier5}}{{#if canUserEdit}}
<paper-icon-button icon="settings" id="edit"></paper-icon-button>
{{/if}}{{/if}}
</div>
<!--
<div bottom-item>
<paper-tabs id="libraryTabs" selected={{selectedTab}} class="app-grey white-text">
<paper-tab name="items">Items</paper-tab>
<paper-tab name="spells">Spells</paper-tab>
</paper-tabs>
</div>
-->
{{#if isTier5}}
<div bottom-item class="layout horizontal center">
<paper-input label="Search" class="search-input">
<iron-icon icon="search" prefix></iron-icon>
</paper-input>
<div class="flex"></div>
{{#if canUserSubscribe}}
<paper-button style="color: rgba(255,255,255,0.87);" id="subscribe">
<iron-icon icon="add-circle"></iron-icon>
Subscribe
</paper-button>
{{else if canUserUnsubscribe}}
<paper-button style="color: rgba(255,255,255,0.87);" id="unsubscribe">
<iron-icon icon="remove-circle"></iron-icon>
Unsubscribe
</paper-button>
{{/if}}
</div>
{{/if}}
</app-toolbar>
</app-header>
<div class="flex" style="position: relative;">
<!-- <iron-pages id="tabPages" class="fit" selected={{selectedTab}}> -->
<div name="items" class="tab-page fit">{{> itemLibrary}}</div>
<!-- <div name="spells" class="tab-page fit">{{! {{> spellLibrary}} }}</div>
</iron-pages> -->
</div>
<div class="floatyButton">
<paper-fab id="addLibrary" icon="add"></paper-fab>
{{#simpleTooltip}}Add Library{{/simpleTooltip}}
</div>
{{#if isTier5}}
<div class="flex layout vertical center" style="position: relative; padding: 0 16px;">
<paper-material class="card" style="padding: 32px; max-width: 800px; width: 100%;">
{{#each items}}
<paper-item data-id={{_id}} class="item">
<paper-item-body>
<div>{{displayName}}</div>
</paper-item-body>
</paper-item>
{{/each}}
</paper-material>
</div>
<div class="floatyButton">
<paper-fab id="addLibraryItem" icon="add"></paper-fab>
{{#simpleTooltip}}Add Library Item{{/simpleTooltip}}
</div>
{{else}}
{{> patronsOnly }}
{{/if}}
</div>
</template>

View File

@@ -1,29 +1,120 @@
const librarySubs = new SubsManager();
Template.library.onCreated(function(){
this.selectedTab = new ReactiveVar("0");
this.searchTerm = new ReactiveVar("");
});
Template.library.helpers({
selectedTab(){
return Template.instance().selectedTab.get();
items(){
let search = Template.instance().searchTerm.get();
if (search){
return LibraryItems.find(
{
library: this._id,
$or: [
{
name: {$regex: new RegExp(".*" + search + ".*", "gi")}
},
{
libraryname: {$regex: new RegExp(".*" + search + ".*", "gi")}
},
],
},
{sort: {name: 1}},
);
} else {
return LibraryItems.find(
{library: this._id},
{sort: {name: 1}},
);
}
},
displayName(){
return this.libraryName || this.name;
},
canUserSubscribe(){
let user = Meteor.user();
let userId = user._id;
return !(
_.contains(this.readers, userId) ||
_.contains(this.writers, userId) ||
this.owner === userId ||
_.contains(user.profile.librarySubscriptions, this._id)
);
},
canUserUnsubscribe(){
let user = Meteor.user();
let userId = user._id;
return (
_.contains(user.profile.librarySubscriptions, this._id) ||
_.contains(this.readers, userId)
);
},
canUserEdit(){
let userId = Meteor.userId();
return (
_.contains(this.writers, userId) ||
this.owner === userId
);
},
});
Template.library.events({
"iron-select #libraryTabs": function(event, instance){
instance.selectedTab.set(event.target.selected);
"input .search-input, change .search-input": function(event, template){
const value = event.currentTarget.value;
template.searchTerm.set(value);
},
"click #addLibrary": function(event, instance){
var libraryId = Libraries.insert({
name: "New Library",
owner: Meteor.userId(),
});
"click #edit": function(event, instance){
event.stopPropagation();
var libraryId = this._id;
pushDialogStack({
template: "libraryDialog",
data: {libraryId},
element: event.currentTarget,
returnElement: () => instance.find(`.library-header[data-id='${libraryId}']`),
element: event.currentTarget.parentElement.parentElement,
callback(data){
if (data && data.delete){
Router.go('/library');
Tracker.afterFlush(function(){
Libraries.remove(libraryId);
});
}
},
});
},
})
"click #addLibraryItem": function(event, instance){
event.stopPropagation();
var libraryId = this._id;
var itemId = LibraryItems.insert({
name: "New Library Item",
library: libraryId,
});
pushDialogStack({
template: "libraryItemDialog",
data: {itemId},
element: event.currentTarget,
returnElement: () => instance.find(`.item[data-id='${itemId}']`),
});
},
"click .item": function(event, instance){
event.stopPropagation();
var itemId = this._id;
pushDialogStack({
template: "libraryItemDialog",
data: {itemId},
element: event.currentTarget,
returnElement: () => instance.find(`.item[data-id='${itemId}']`),
});
},
"click #subscribe": function(event, instance){
Meteor.users.update(Meteor.userId(), {
$addToSet: {"profile.librarySubscriptions": this._id},
});
},
"click #unsubscribe": function(event, instance){
let userId = Meteor.userId();
Meteor.users.update(userId, {
$pull: {"profile.librarySubscriptions": this._id},
});
Meteor.call("unshareLibraryWithMe", this._id);
},
});

View File

@@ -0,0 +1,18 @@
<template name="libraryDeleteConfirmation">
<div class="fit layout vertical">
<app-header-layout has-scrolling-region class="feedback flex">
<app-header fixed effects="waterfall">
<app-toolbar>
<div main-title>Delete Library</div>
</app-toolbar>
</app-header>
<div class="form flex">
Deleting a library cannot be undone<br>
<paper-button id="deleteButton" raised>Delete Library and All Contents</paper-button>
</div>
</app-header-layout>
<div class="buttons layout horizontal end-justified">
<paper-button class="cancelButton"> Cancel </paper-button>
</div>
</div>
</template>

View File

@@ -0,0 +1,8 @@
Template.libraryDeleteConfirmation.events({
"click #deleteButton": function(event, instance) {
popDialogStack(true);
},
"click .cancelButton": function(event, instance){
popDialogStack();
},
});

View File

@@ -1,15 +1,39 @@
<template name="libraryDialog">
<div class="fit base-dialog layout vertical">
<app-toolbar>
<div main-title>{{library.name}}</div>
<paper-icon-button id="deleteButton"
role="button"
tabindex="0"
icon="delete">
<paper-icon-button id="backButton"
icon="arrow-back">
</paper-icon-button>
<div main-title>{{library.name}}</div>
<paper-menu-button class="character-menu" horizontal-align="right">
<paper-icon-button icon="delete" class="dropdown-trigger" disabled="{{notOwner}}">
</paper-icon-button>
<paper-menu class="dropdown-content black87">
<paper-button id="deleteButton" disabled="{{notOwner}}" raised>
Delete library and all its contents
</paper-button>
</paper-menu>
</paper-menu-button>
</app-toolbar>
<div class="form flex scroll-y" style="position: relative;">
<paper-input id="libraryNameInput" class="fullwidth" label="Name" value={{library.name}}></paper-input>
<hr style="margin: 24px 0; opacity: 0.4;">
<paper-dropdown-menu label="Who can view and subscribe to this library">
<dicecloud-selector class="visibilityDropdown dropdown-content" selected={{viewPermission}}>
<paper-item name="whitelist">Only people I share with</paper-item>
<paper-item name="public">Anyone with link</paper-item>
</dicecloud-selector>
</paper-dropdown-menu>
{{#if library.public}}
<div style="margin-top: 16px;">
Share this link for others to subscribe to this library:
</div>
<a href="{{pathFor route='library' data=library}}" style="color: #d13b2e; font-size: 18px">
{{urlFor route='library' data=library}}
</a>
{{/if}}
<hr style="margin: 24px 0; opacity: 0.4;">
<div class="paper-font-title" style="margin-top: 32px;">Share Directly</div>
<div class="layout horizontal center wrap">
<paper-input class="flex" id="userNameOrEmailInput" label="Share with username or email" floatinglabel></paper-input>
<paper-button id="shareButton"

View File

@@ -13,6 +13,10 @@ Template.libraryDialog.helpers({
library(){
return Libraries.findOne(this.libraryId);
},
viewPermission(){
var library = Libraries.findOne(this.libraryId, {fields: {public: 1}});
return library && library.public ? "public" : "whitelist";
},
readers: function(){
var library = Libraries.findOne(this.libraryId, {fields: {readers: 1}});
return library && library.readers;
@@ -33,9 +37,17 @@ Template.libraryDialog.helpers({
return "User not found";
}
},
notOwner: function(){
var library = Libraries.findOne(this.libraryId, {fields: {owner: 1}});
if (!library) return;
return Meteor.userId() !== library.owner;
},
});
Template.libraryDialog.events({
"click #backButton": function(){
popDialogStack();
},
"input #libraryNameInput": _.debounce(function(event){
const input = event.currentTarget;
var name = input.value;
@@ -53,8 +65,10 @@ Template.libraryDialog.events({
}
}, 300),
"click #deleteButton": function(){
Meteor.call("removeLibrary", this.libraryId);
popDialogStack();
var library = Libraries.findOne(this.libraryId, {fields: {owner: 1}});
if (Meteor.userId() === library.owner){
popDialogStack({delete: true});
}
},
"input #userNameOrEmailInput":
function(event, instance){
@@ -64,11 +78,25 @@ Template.libraryDialog.events({
if (err){
console.error(err);
} else {
console.log(result);
instance.userId.set(result);
}
});
},
"iron-select .visibilityDropdown": function(event){
var detail = event.originalEvent.detail;
var value = detail.item.getAttribute("name");
let public;
if (value === "whitelist"){
public = false;
} else if (value === "public") {
public = true;
} else {
return;
}
var library = Libraries.findOne(this.libraryId, {fields: {public: 1}});
if (library.public === public) return;
Libraries.update(this.libraryId, {$set: {public}});
},
"click #shareButton": function(event, instance){
var self = this;
var permission = instance.find("#accessLevelMenu").selected;

View File

@@ -8,32 +8,34 @@
<paper-icon-button id="deleteButton"
role="button"
tabindex="0"
icon="delete">
icon="delete"
disabled="{{cantEdit}}">
</paper-icon-button>
</app-toolbar>
<div class="form flex scroll-y" style="position: relative;">
<paper-input id="libraryItemLibraryNameInput" class="fullwidth" label="Library name (optional)" value={{item.libraryName}}></paper-input>
<paper-input id="libraryItemNameInput" class="fullwidth" label="Item name" value={{item.name}}></paper-input>
{{#if ready}}
<paper-input id="libraryItemLibraryNameInput" class="fullwidth" label="Library name (optional)" value={{item.libraryName}} disabled="{{cantEdit}}"></paper-input>
<paper-input id="libraryItemNameInput" class="fullwidth" label="Item name" value={{item.name}} disabled="{{cantEdit}}"></paper-input>
<div class="layout horizontal center wrap">
<paper-input id="libraryItemPluralInput" class="flex" label="Plural name" value={{item.plural}}></paper-input>
<paper-input id="libraryItemQuantityInput" class="flex" label="Quantity" type="number" value={{item.quantity}}></paper-input>
<paper-checkbox id="incrementCheckbox" class="flex" checked={{item.settings.showIncrement}}>
<paper-input id="libraryItemPluralInput" class="flex" label="Plural name" value={{item.plural}} disabled="{{cantEdit}}"></paper-input>
<paper-input id="libraryItemQuantityInput" class="flex" label="Quantity" type="number" value={{item.quantity}} disabled="{{cantEdit}}"></paper-input>
<paper-checkbox id="incrementCheckbox" class="flex" checked={{item.settings.showIncrement}} disabled="{{cantEdit}}">
Show Increment
</paper-checkbox>
</div>
<div class="layout horizontal center wrap">
<paper-input id="libraryItemValueInput" class="flex" label="Value" type="number" value={{item.value}}></paper-input>
<paper-input id="libraryItemWeightInput" class="flex" label="Weight" type="number" value={{item.weight}}></paper-input>
<paper-checkbox id="attunementCheckbox" class="flex" checked={{item.requiresAttunement}}>
<paper-input id="libraryItemValueInput" class="flex" label="Value" type="number" value={{item.value}} disabled="{{cantEdit}}"></paper-input>
<paper-input id="libraryItemWeightInput" class="flex" label="Weight" type="number" value={{item.weight}} disabled="{{cantEdit}}"></paper-input>
<paper-checkbox id="attunementCheckbox" class="flex" checked={{item.requiresAttunement}} disabled="{{cantEdit}}">
Requires Attunement
</paper-checkbox>
</div>
<paper-textarea id="libraryItemDescriptionInput" label="Description" value={{item.description}}></paper-textarea>
<paper-textarea id="libraryItemDescriptionInput" label="Description" value={{item.description}} disabled="{{cantEdit}}"></paper-textarea>
<div style="margin-top: 8px;">
<div class="paper-font-subhead">Effects</div>
{{#each indexedEffects}}
<div class="effect layout horizontal center wrap">
<paper-dropdown-menu label="Operation" class="operationMenu">
<paper-dropdown-menu label="Operation" class="operationMenu" disabled="{{cantEdit}}">
<paper-listbox class="dropdown-content" selected={{operationIndex operation}}>
<paper-item label="Base Value" name="base"> Base Value </paper-item>
<paper-item label="Add" name="add"> Add </paper-item>
@@ -47,21 +49,21 @@
<paper-item label="Conditional" name="conditional"> Conditional </paper-item>
</paper-listbox>
</paper-dropdown-menu>
<paper-input class="LibraryItemEffectStat flex" label="Attribute" value={{stat}}></paper-input>
<paper-input class="LibraryItemEffectValue flex" label="Value" value={{calculationOrValue}}></paper-input>
<paper-icon-button icon="delete" class="deleteEffect"></paper-icon-button>
<paper-input class="LibraryItemEffectStat flex" label="Attribute" value={{stat}} disabled="{{cantEdit}}"></paper-input>
<paper-input class="LibraryItemEffectValue flex" label="Value" value={{calculationOrValue}} disabled="{{cantEdit}}"></paper-input>
<paper-icon-button icon="delete" class="deleteEffect" disabled="{{cantEdit}}"></paper-icon-button>
</div>
{{/each}}
<paper-button id="addEffect" class="red-button">Add Effect</paper-button>
<paper-button id="addEffect" class="red-button" disabled="{{cantEdit}}">Add Effect</paper-button>
</div>
<div style="margin-top: 8px;">
<div class="paper-font-subhead">Attacks</div>
{{#each indexedAttacks}}
<div class="effect layout horizontal center wrap">
<paper-input class="LibraryItemAttackBonusInput flex" label="Attack Bonus" value={{attackBonus}}></paper-input>
<paper-input class="LibraryItemAttackDamageInput flex" label="Damage" value={{damage}}></paper-input>
<paper-input class="LibraryItemAttackDetailsInput flex" label="Details" value={{details}}></paper-input>
<paper-dropdown-menu label="Damage Type" class="damageTypeMenu">
<paper-input class="LibraryItemAttackBonusInput flex" label="Attack Bonus" value={{attackBonus}} disabled="{{cantEdit}}"></paper-input>
<paper-input class="LibraryItemAttackDamageInput flex" label="Damage" value={{damage}} disabled="{{cantEdit}}"></paper-input>
<paper-input class="LibraryItemAttackDetailsInput flex" label="Details" value={{details}} disabled="{{cantEdit}}"></paper-input>
<paper-dropdown-menu label="Damage Type" class="damageTypeMenu" disabled="{{cantEdit}}">
<paper-listbox class="dropdown-content" selected={{damageTypeIndex damageType}}>
<paper-item label="Bludgeoning" name="bludgeoning"> Bludgeoning </paper-item>
<paper-item label="Piercing" name="piercing"> Piercing </paper-item>
@@ -78,11 +80,14 @@
<paper-item label="Thunder" name="thunder"> Thunder </paper-item>
</paper-listbox>
</paper-dropdown-menu>
<paper-icon-button icon="delete" class="deleteAttack"></paper-icon-button>
<paper-icon-button icon="delete" class="deleteAttack" disabled="{{cantEdit}}"></paper-icon-button>
</div>
{{/each}}
<paper-button id="addAttack" class="red-button">Add Attack</paper-button>
<paper-button id="addAttack" class="red-button" disabled="{{cantEdit}}">Add Attack</paper-button>
</div>
{{else}}
<paper-spinner active></paper-spinner>
{{/if}}
</div>
</div>
</template>

View File

@@ -1,3 +1,9 @@
Template.libraryItemDialog.onCreated(function(){
this.autorun(() => {
this.subscribe('libraryItem', Template.currentData().itemId);
});
});
Template.libraryItemDialog.helpers({
item(){
return LibraryItems.findOne(this.itemId);
@@ -55,6 +61,20 @@ Template.libraryItemDialog.helpers({
thunder: 12,
};
return ref[damageType];
},
ready(){
return Template.instance().subscriptionsReady();
},
cantEdit(){
let item = LibraryItems.findOne(this.itemId);
if (!item) return;
let library = Libraries.findOne(item.library);
if (!library) return;
let userId = Meteor.userId();
return !(
library.owner === userId ||
_.contains(library.writers, userId)
);
}
});

View File

@@ -0,0 +1,27 @@
<template name="patronsOnly">
<div class="flex layout vertical center" style="position: relative; padding: 0 16px;">
<paper-material class="card" style="padding: 32px; max-width: 800px; width: 100%;">
<h3>
This beta feature is available to Patreon Insiders who pledge $5 or more
</h3>
<div class="layout vertical center">
<a href="https://www.patreon.com/join/dicecloud/checkout?rid=3002853">
<paper-button raised> Become a Patron </paper-button>
</a>
<a href="{{patreonLoginUrl}}">
<paper-button class="connectPatreon" style="color: #d13b2e; margin-top: 12px;">
Connect Patreon account
</paper-button>
</a>
</div>
<p style="margin-top: 32px;">
With the Item Libraries beta you can create collections of items to use
across your characters, and share them with other players.
</p>
<p>
You can also subscribe to existing community libraries of items, saving
time and effort manually entering item details.
</p>
</paper-material>
</div>
</template>

View File

@@ -1,7 +1,5 @@
import { format as formatUrl } from 'url';
const CLIENT_ID = Meteor.settings.public.patreon.clientId;
Template.profile.onCreated(function(){
this.showApiKey = new ReactiveVar(false);
this.loadingPatreon = new ReactiveVar(false);
@@ -17,20 +15,6 @@ Template.profile.helpers({
showApiKey: function(){
return Template.instance().showApiKey.get();
},
patreonLoginUrl: function(){
return formatUrl({
protocol: 'https',
host: 'patreon.com',
pathname: '/oauth2/authorize',
query: {
response_type: 'code',
client_id: CLIENT_ID,
redirect_uri: Meteor.absoluteUrl() + 'patreon-redirect',
state: Meteor.userId(),
scope: 'identity',
},
});
},
patreon: function(){
let user = Meteor.user();
return user && user.patreon || {};

View File

@@ -70,4 +70,8 @@
--paper-diff-slider-knob-color: #00BCD4;
--paper-diff-slider-pin-color: #00BCD4;
}
.white-text paper-input {
/* Input foreground color */
--paper-input-container-input-color: rgba(255,255,255,0.87);
}
</style>

View File

@@ -25,16 +25,61 @@ Meteor.publish("standardLibrarySpells", function(level){
});
Meteor.publish("customLibraries", function(){
userId = this.userId;
const userId = this.userId;
let user = Meteor.user()
let subs = user && user.profile && user.profile.librarySubscriptions;
return Libraries.find({
$or: [
{readers: userId},
{writers: userId},
{owner: userId},
{public: true, _id: {$in: subs || []}},
],
});
});
Meteor.publish("singleLibrary", function(id){
const userId = this.userId;
return Libraries.find({
_id: id,
$or: [
{readers: userId},
{writers: userId},
{owner: userId},
{public: true},
],
});
});
Meteor.publish("libraryItems", function(libraryId){
return LibraryItems.find({library: libraryId});
return LibraryItems.find({
library: libraryId
}, {
fields: {
name: 1,
libraryName: 1,
library: 1,
},
});
});
Meteor.publish("libraryItem", function(itemId){
let cursor = LibraryItems.find(itemId);
let item = cursor.fetch()[0];
let userId = Meteor.userId();
if (!item) return [];
let library = Libraries.findOne(item.library);
if (!library) {
throw new Meteor.Error("Library item " + item._id + " is an orphan");
}
if (
library.public ||
library.owner === userId ||
_.contains(library.readers, userId) ||
_.contains(library.writers, userId)
) {
return cursor;
} else {
return [];
}
});