Compare commits
153 Commits
2.0-beta.3
...
2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b088a2d433 | ||
|
|
8aa5ee81d5 | ||
|
|
ef26153bb2 | ||
|
|
77597e8056 | ||
|
|
ee1b876259 | ||
|
|
12fbca5c78 | ||
|
|
da6fb55ca0 | ||
|
|
8551e318c2 | ||
|
|
f175cffab8 | ||
|
|
2bca582af6 | ||
|
|
5815c7ca34 | ||
|
|
c237162475 | ||
|
|
e87772c2a3 | ||
|
|
704314a7eb | ||
|
|
7ffd0bf61d | ||
|
|
69b3ba781d | ||
|
|
bf8eb52a96 | ||
|
|
684d672028 | ||
|
|
fb98544ae1 | ||
|
|
ec8b9c209c | ||
|
|
bee90a7a80 | ||
|
|
5ad0de9eb7 | ||
|
|
0b377fcb71 | ||
|
|
1f26fbf00e | ||
|
|
bb1e9624ad | ||
|
|
bda446858e | ||
|
|
e19e91f7e0 | ||
|
|
bac9fc98dd | ||
|
|
420663c149 | ||
|
|
23d44e54e3 | ||
|
|
881496e9c1 | ||
|
|
002c767d1a | ||
|
|
9aaf31d5cf | ||
|
|
d05cd2fa19 | ||
|
|
f13774df11 | ||
|
|
cc78ba948e | ||
|
|
c6bfb84bb0 | ||
|
|
7e49100d14 | ||
|
|
c3ac49a8c4 | ||
|
|
fd9d525ba9 | ||
|
|
d947b62ba4 | ||
|
|
046509224e | ||
|
|
63bcf023ee | ||
|
|
9741d1d56c | ||
|
|
0f12c98408 | ||
|
|
254390e1c1 | ||
|
|
ad2f43712d | ||
|
|
55f8dac0db | ||
|
|
9f8c3f0f3d | ||
|
|
56bd41f435 | ||
|
|
063d4288ef | ||
|
|
a3355dd988 | ||
|
|
d2649fd66e | ||
|
|
e619734ee1 | ||
|
|
5108b32624 | ||
|
|
a9b389023e | ||
|
|
e06d2befc4 | ||
|
|
cc7dc257fb | ||
|
|
f3deadb3f1 | ||
|
|
dcfb380e57 | ||
|
|
a568cdfb1e | ||
|
|
cea63e6a8e | ||
|
|
b6b0cfbb9b | ||
|
|
428aeef635 | ||
|
|
e3644eb9e8 | ||
|
|
060b5f93ca | ||
|
|
0f3a96da17 | ||
|
|
c55d572134 | ||
|
|
0a2b60990e | ||
|
|
a437ff5aef | ||
|
|
3d31d62860 | ||
|
|
8377231254 | ||
|
|
1ec29365cb | ||
|
|
60b21c1901 | ||
|
|
03f87b0afa | ||
|
|
48291d2c8f | ||
|
|
1cedf55fbf | ||
|
|
bed4d4b162 | ||
|
|
a1d992ec8d | ||
|
|
008ef62517 | ||
|
|
c436309ba8 | ||
|
|
0bfdb73b47 | ||
|
|
a462cc5ca2 | ||
|
|
5d57a74667 | ||
|
|
21b0029df7 | ||
|
|
c0ccafa787 | ||
|
|
d63ad9ea8f | ||
|
|
8f56a60fb1 | ||
|
|
358ae46627 | ||
|
|
0b1db3c40c | ||
|
|
0ad7e659d2 | ||
|
|
58c3875dc7 | ||
|
|
84f506f1fe | ||
|
|
d0a3ccc76a | ||
|
|
93ac9215c2 | ||
|
|
a6b501a62c | ||
|
|
e956bacf07 | ||
|
|
60b6b283b1 | ||
|
|
1c9b390551 | ||
|
|
21a487635d | ||
|
|
c92a26d5e6 | ||
|
|
49b514b8f3 | ||
|
|
5cb835c536 | ||
|
|
aa8f2d230d | ||
|
|
2fa913b09a | ||
|
|
de598c70a7 | ||
|
|
baecdeff24 | ||
|
|
d4b7d22b5f | ||
|
|
87f79737e8 | ||
|
|
9f0ffe13f8 | ||
|
|
adaa31d76c | ||
|
|
b051d764f8 | ||
|
|
ffb5b4a4f3 | ||
|
|
fd87b7fb75 | ||
|
|
f035902842 | ||
|
|
dbc5f7253f | ||
|
|
f0e7253374 | ||
|
|
ffe37bf907 | ||
|
|
a63e2099d3 | ||
|
|
0308e4e7a7 | ||
|
|
43f8df09f0 | ||
|
|
b6ed9ffb74 | ||
|
|
a84da7d8a5 | ||
|
|
249aebea0f | ||
|
|
11a527481e | ||
|
|
8d729216b5 | ||
|
|
1677e8c424 | ||
|
|
987aacbb67 | ||
|
|
2714d0b9d5 | ||
|
|
1d98c41168 | ||
|
|
e42ec4b862 | ||
|
|
59fc5ab851 | ||
|
|
5d14c392e8 | ||
|
|
c6ca8c1fa4 | ||
|
|
28307e26c3 | ||
|
|
6d42eb62f0 | ||
|
|
877c9ca099 | ||
|
|
9b652fc133 | ||
|
|
7d66c06107 | ||
|
|
21629138f0 | ||
|
|
59a488256b | ||
|
|
766519b4a3 | ||
|
|
e7f73d0e54 | ||
|
|
193d5eec50 | ||
|
|
9284c9ad76 | ||
|
|
f86152675f | ||
|
|
cbac5264cd | ||
|
|
34e3325464 | ||
|
|
79c9e67ce2 | ||
|
|
4c2aabf90d | ||
|
|
48331d3806 | ||
|
|
45f05d0d34 | ||
|
|
58629c92f4 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
build
|
||||||
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@@ -3,6 +3,7 @@
|
|||||||
.demeteorized
|
.demeteorized
|
||||||
.cache
|
.cache
|
||||||
.vscode
|
.vscode
|
||||||
|
fileStorage
|
||||||
settings.json
|
settings.json
|
||||||
public/components
|
public/components
|
||||||
public/_imports.html
|
public/_imports.html
|
||||||
|
|||||||
@@ -4,27 +4,27 @@
|
|||||||
# but you can also edit it by hand.
|
# but you can also edit it by hand.
|
||||||
|
|
||||||
accounts-password@2.3.1
|
accounts-password@2.3.1
|
||||||
random@1.2.0
|
random@1.2.1
|
||||||
underscore@1.0.10
|
underscore@1.0.11
|
||||||
dburles:mongo-collection-instances
|
dburles:mongo-collection-instances
|
||||||
accounts-google@1.4.0
|
accounts-google@1.4.0
|
||||||
email@2.2.1
|
email@2.2.2
|
||||||
meteor-base@1.5.1
|
meteor-base@1.5.1
|
||||||
mobile-experience@1.1.0
|
mobile-experience@1.1.0
|
||||||
mongo@1.15.0
|
mongo@1.16.1
|
||||||
session@1.2.0
|
session@1.2.1
|
||||||
tracker@1.2.0
|
tracker@1.2.1
|
||||||
logging@1.3.1
|
logging@1.3.1
|
||||||
reload@1.3.1
|
reload@1.3.1
|
||||||
ejson@1.1.2
|
ejson@1.1.3
|
||||||
check@1.3.1
|
check@1.3.2
|
||||||
standard-minifier-js@2.8.0
|
standard-minifier-js@2.8.1
|
||||||
shell-server@0.5.0
|
shell-server@0.5.0
|
||||||
ecmascript@0.16.2
|
ecmascript@0.16.3
|
||||||
es5-shim@4.8.0
|
es5-shim@4.8.0
|
||||||
service-configuration@1.3.0
|
service-configuration@1.3.1
|
||||||
dynamic-import@0.7.2
|
dynamic-import@0.7.2
|
||||||
ddp-rate-limiter@1.1.0
|
ddp-rate-limiter@1.1.1
|
||||||
rate-limit@1.0.9
|
rate-limit@1.0.9
|
||||||
mdg:validated-method
|
mdg:validated-method
|
||||||
static-html@1.3.2
|
static-html@1.3.2
|
||||||
@@ -37,7 +37,6 @@ simple:rest
|
|||||||
simple:rest-method-mixin
|
simple:rest-method-mixin
|
||||||
mikowals:batch-insert
|
mikowals:batch-insert
|
||||||
peerlibrary:subscription-data
|
peerlibrary:subscription-data
|
||||||
seba:minifiers-autoprefixer
|
|
||||||
zer0th:meteor-vuetify-loader
|
zer0th:meteor-vuetify-loader
|
||||||
akryum:vue-component
|
akryum:vue-component
|
||||||
akryum:vue-router2
|
akryum:vue-router2
|
||||||
@@ -48,3 +47,5 @@ simple:rest-bearer-token-parser
|
|||||||
simple:rest-json-error-handler
|
simple:rest-json-error-handler
|
||||||
littledata:synced-cron
|
littledata:synced-cron
|
||||||
mdg:meteor-apm-agent
|
mdg:meteor-apm-agent
|
||||||
|
typescript@4.5.4
|
||||||
|
seba:minifiers-autoprefixer
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
METEOR@2.7.3
|
METEOR@2.8.1
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
accounts-base@2.2.3
|
accounts-base@2.2.5
|
||||||
accounts-google@1.4.0
|
accounts-google@1.4.0
|
||||||
accounts-oauth@1.4.1
|
accounts-oauth@1.4.1
|
||||||
accounts-password@2.3.1
|
accounts-password@2.3.1
|
||||||
@@ -12,7 +12,7 @@ aldeed:collection2@3.5.0
|
|||||||
aldeed:schema-index@3.0.0
|
aldeed:schema-index@3.0.0
|
||||||
allow-deny@1.1.1
|
allow-deny@1.1.1
|
||||||
autoupdate@1.8.0
|
autoupdate@1.8.0
|
||||||
babel-compiler@7.9.0
|
babel-compiler@7.9.2
|
||||||
babel-runtime@1.5.1
|
babel-runtime@1.5.1
|
||||||
base64@1.0.12
|
base64@1.0.12
|
||||||
binary-heap@1.0.11
|
binary-heap@1.0.11
|
||||||
@@ -22,26 +22,26 @@ bozhao:link-accounts@2.6.1
|
|||||||
caching-compiler@1.2.2
|
caching-compiler@1.2.2
|
||||||
caching-html-compiler@1.2.1
|
caching-html-compiler@1.2.1
|
||||||
callback-hook@1.4.0
|
callback-hook@1.4.0
|
||||||
check@1.3.1
|
check@1.3.2
|
||||||
coffeescript@2.4.1
|
coffeescript@2.4.1
|
||||||
coffeescript-compiler@2.4.1
|
coffeescript-compiler@2.4.1
|
||||||
dburles:mongo-collection-instances@0.3.6
|
dburles:mongo-collection-instances@0.3.6
|
||||||
ddp@1.4.0
|
ddp@1.4.1
|
||||||
ddp-client@2.5.0
|
ddp-client@2.6.1
|
||||||
ddp-common@1.4.0
|
ddp-common@1.4.0
|
||||||
ddp-rate-limiter@1.1.0
|
ddp-rate-limiter@1.1.1
|
||||||
ddp-server@2.5.0
|
ddp-server@2.6.0
|
||||||
diff-sequence@1.1.1
|
diff-sequence@1.1.2
|
||||||
dynamic-import@0.7.2
|
dynamic-import@0.7.2
|
||||||
ecmascript@0.16.2
|
ecmascript@0.16.3
|
||||||
ecmascript-runtime@0.8.0
|
ecmascript-runtime@0.8.0
|
||||||
ecmascript-runtime-client@0.12.1
|
ecmascript-runtime-client@0.12.1
|
||||||
ecmascript-runtime-server@0.11.0
|
ecmascript-runtime-server@0.11.0
|
||||||
ejson@1.1.2
|
ejson@1.1.3
|
||||||
email@2.2.1
|
email@2.2.2
|
||||||
es5-shim@4.8.0
|
es5-shim@4.8.0
|
||||||
fetch@0.1.1
|
fetch@0.1.2
|
||||||
geojson-utils@1.0.10
|
geojson-utils@1.0.11
|
||||||
google-oauth@1.4.2
|
google-oauth@1.4.2
|
||||||
hot-code-push@1.0.4
|
hot-code-push@1.0.4
|
||||||
html-tools@1.1.3
|
html-tools@1.1.3
|
||||||
@@ -55,33 +55,33 @@ littledata:synced-cron@1.5.1
|
|||||||
livedata@1.0.18
|
livedata@1.0.18
|
||||||
localstorage@1.2.0
|
localstorage@1.2.0
|
||||||
logging@1.3.1
|
logging@1.3.1
|
||||||
mdg:meteor-apm-agent@3.5.0
|
mdg:meteor-apm-agent@3.5.1
|
||||||
mdg:validated-method@1.2.0
|
mdg:validated-method@1.2.0
|
||||||
meteor@1.10.0
|
meteor@1.10.2
|
||||||
meteor-base@1.5.1
|
meteor-base@1.5.1
|
||||||
meteortesting:browser-tests@1.3.5
|
meteortesting:browser-tests@1.3.5
|
||||||
meteortesting:mocha@2.0.3
|
meteortesting:mocha@2.0.3
|
||||||
meteortesting:mocha-core@8.1.2
|
meteortesting:mocha-core@8.1.2
|
||||||
mikowals:batch-insert@1.3.0
|
mikowals:batch-insert@1.3.0
|
||||||
minifier-css@1.6.0
|
minifier-css@1.6.1
|
||||||
minifier-js@2.7.4
|
minifier-js@2.7.5
|
||||||
minimongo@1.8.0
|
minimongo@1.9.0
|
||||||
mobile-experience@1.1.0
|
mobile-experience@1.1.0
|
||||||
mobile-status-bar@1.1.0
|
mobile-status-bar@1.1.0
|
||||||
modern-browsers@0.1.8
|
modern-browsers@0.1.9
|
||||||
modules@0.18.0
|
modules@0.19.0
|
||||||
modules-runtime@0.13.0
|
modules-runtime@0.13.1
|
||||||
mongo@1.15.0
|
mongo@1.16.1
|
||||||
mongo-decimal@0.1.3
|
mongo-decimal@0.1.3
|
||||||
mongo-dev-server@1.1.0
|
mongo-dev-server@1.1.0
|
||||||
mongo-id@1.0.8
|
mongo-id@1.0.8
|
||||||
mongo-livedata@1.0.12
|
mongo-livedata@1.0.12
|
||||||
npm-mongo@4.3.1
|
npm-mongo@4.11.0
|
||||||
oauth@2.1.2
|
oauth@2.1.2
|
||||||
oauth2@1.3.1
|
oauth2@1.3.1
|
||||||
ordered-dict@1.1.0
|
ordered-dict@1.1.0
|
||||||
ostrio:cookies@2.7.2
|
ostrio:cookies@2.7.2
|
||||||
ostrio:files@2.0.1
|
ostrio:files@2.3.2
|
||||||
patreon-oauth@0.1.0
|
patreon-oauth@0.1.0
|
||||||
peerlibrary:assert@0.3.0
|
peerlibrary:assert@0.3.0
|
||||||
peerlibrary:check-extension@0.7.0
|
peerlibrary:check-extension@0.7.0
|
||||||
@@ -93,20 +93,20 @@ peerlibrary:reactive-mongo@0.4.1
|
|||||||
peerlibrary:reactive-publish@0.10.0
|
peerlibrary:reactive-publish@0.10.0
|
||||||
peerlibrary:server-autorun@0.8.0
|
peerlibrary:server-autorun@0.8.0
|
||||||
peerlibrary:subscription-data@0.8.0
|
peerlibrary:subscription-data@0.8.0
|
||||||
percolate:migrations@1.0.3
|
percolate:migrations@1.1.0
|
||||||
promise@0.12.0
|
promise@0.12.1
|
||||||
raix:eventemitter@1.0.0
|
raix:eventemitter@1.0.0
|
||||||
random@1.2.0
|
random@1.2.1
|
||||||
rate-limit@1.0.9
|
rate-limit@1.0.9
|
||||||
react-fast-refresh@0.2.3
|
react-fast-refresh@0.2.3
|
||||||
reactive-dict@1.3.0
|
reactive-dict@1.3.1
|
||||||
reactive-var@1.0.11
|
reactive-var@1.0.12
|
||||||
reload@1.3.1
|
reload@1.3.1
|
||||||
retry@1.1.0
|
retry@1.1.0
|
||||||
routepolicy@1.1.1
|
routepolicy@1.1.1
|
||||||
seba:minifiers-autoprefixer@2.0.1
|
seba:minifiers-autoprefixer@2.0.1
|
||||||
service-configuration@1.3.0
|
service-configuration@1.3.1
|
||||||
session@1.2.0
|
session@1.2.1
|
||||||
sha@1.0.9
|
sha@1.0.9
|
||||||
shell-server@0.5.0
|
shell-server@0.5.0
|
||||||
simple:json-routes@2.3.1
|
simple:json-routes@2.3.1
|
||||||
@@ -116,14 +116,14 @@ simple:rest-json-error-handler@1.1.1
|
|||||||
simple:rest-method-mixin@1.1.0
|
simple:rest-method-mixin@1.1.0
|
||||||
socket-stream-client@0.5.0
|
socket-stream-client@0.5.0
|
||||||
spacebars-compiler@1.3.1
|
spacebars-compiler@1.3.1
|
||||||
standard-minifier-js@2.8.0
|
standard-minifier-js@2.8.1
|
||||||
static-html@1.3.2
|
static-html@1.3.2
|
||||||
templating-tools@1.2.2
|
templating-tools@1.2.2
|
||||||
tmeasday:check-npm-versions@1.0.2
|
tmeasday:check-npm-versions@1.0.2
|
||||||
tracker@1.2.0
|
tracker@1.2.1
|
||||||
typescript@4.5.4
|
typescript@4.5.4
|
||||||
underscore@1.0.10
|
underscore@1.0.11
|
||||||
url@1.3.2
|
url@1.3.2
|
||||||
webapp@1.13.1
|
webapp@1.13.2
|
||||||
webapp-hashing@1.1.0
|
webapp-hashing@1.1.1
|
||||||
zer0th:meteor-vuetify-loader@0.1.41
|
zer0th:meteor-vuetify-loader@0.1.41
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import '/imports/api/simpleSchemaConfig.js';
|
import '/imports/api/simpleSchemaConfig.js';
|
||||||
import '/imports/ui/vueSetup.js';
|
import '/imports/client/ui/vueSetup.js';
|
||||||
import '/imports/ui/styles/stylesIndex.js';
|
import '/imports/client/ui/styles/stylesIndex.js';
|
||||||
import '/imports/client/config.js';
|
import '/imports/client/config.js';
|
||||||
import '/imports/client/serviceWorker.js';
|
import '/imports/client/serviceWorker.js';
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import { createS3FilesCollection } from '/imports/api/files/s3FileStorage.js';
|
|
||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import { incrementFileStorageUsed } from '/imports/api/users/methods/updateFileStorageUsed.js';
|
import { incrementFileStorageUsed } from '/imports/api/users/methods/updateFileStorageUsed.js';
|
||||||
import { CreaturePropertySchema } from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import { CreaturePropertySchema } from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import { CreatureSchema } from '/imports/api/creature/creatures/Creatures.js';
|
import { CreatureSchema } from '/imports/api/creature/creatures/Creatures.js';
|
||||||
|
let createS3FilesCollection;
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
createS3FilesCollection = require('/imports/api/files/server/s3FileStorage.js').createS3FilesCollection
|
||||||
|
} else {
|
||||||
|
createS3FilesCollection = require('/imports/api/files/client/s3FileStorage.js').createS3FilesCollection
|
||||||
|
}
|
||||||
|
|
||||||
const ArchiveCreatureFiles = createS3FilesCollection({
|
const ArchiveCreatureFiles = createS3FilesCollection({
|
||||||
collectionName: 'archiveCreatureFiles',
|
collectionName: 'archiveCreatureFiles',
|
||||||
storagePath: Meteor.isDevelopment ? '/DiceCloud/archiveCreatures/' : 'assets/app/archiveCreatures',
|
storagePath: Meteor.isDevelopment ? '../../../../../fileStorage/archiveCreatures' : 'assets/app/archiveCreatures',
|
||||||
onBeforeUpload(file) {
|
onBeforeUpload(file) {
|
||||||
// Allow upload files under 10MB, and only in json format
|
// Allow upload files under 10MB, and only in json format
|
||||||
if (file.size > 10485760) {
|
if (file.size > 10485760) {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const damageProperty = new ValidatedMethod({
|
|||||||
run({ _id, operation, value }) {
|
run({ _id, operation, value }) {
|
||||||
|
|
||||||
// Get action context
|
// Get action context
|
||||||
const prop = CreatureProperties.findOne(_id);
|
let prop = CreatureProperties.findOne(_id);
|
||||||
if (!prop) throw new Meteor.Error(
|
if (!prop) throw new Meteor.Error(
|
||||||
'Damage property failed', 'Property doesn\'t exist'
|
'Damage property failed', 'Property doesn\'t exist'
|
||||||
);
|
);
|
||||||
@@ -43,6 +43,14 @@ const damageProperty = new ValidatedMethod({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace the prop by its actionContext counterpart if possible
|
||||||
|
if (prop.variableName) {
|
||||||
|
const actionContextProp = actionContext.scope[prop.variableName];
|
||||||
|
if (actionContextProp?._id === prop._id) {
|
||||||
|
prop = actionContextProp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const result = damagePropertyWork({ prop, operation, value, actionContext });
|
const result = damagePropertyWork({ prop, operation, value, actionContext });
|
||||||
|
|
||||||
// Insert the log
|
// Insert the log
|
||||||
@@ -51,7 +59,7 @@ const damageProperty = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function damagePropertyWork({ prop, operation, value, actionContext }) {
|
export function damagePropertyWork({ prop, operation, value, actionContext, logFunction }) {
|
||||||
|
|
||||||
// Save the value to the scope before applying the before triggers
|
// Save the value to the scope before applying the before triggers
|
||||||
if (operation === 'increment') {
|
if (operation === 'increment') {
|
||||||
@@ -94,6 +102,10 @@ export function damagePropertyWork({ prop, operation, value, actionContext }) {
|
|||||||
}, {
|
}, {
|
||||||
selector: prop
|
selector: prop
|
||||||
});
|
});
|
||||||
|
// Also write it straight to the prop so that it is updated in the actionContext
|
||||||
|
prop.damage = damage;
|
||||||
|
prop.value = newValue;
|
||||||
|
logFunction?.(newValue);
|
||||||
} else if (operation === 'increment') {
|
} else if (operation === 'increment') {
|
||||||
let currentValue = prop.value || 0;
|
let currentValue = prop.value || 0;
|
||||||
let currentDamage = prop.damage || 0;
|
let currentDamage = prop.damage || 0;
|
||||||
@@ -111,6 +123,10 @@ export function damagePropertyWork({ prop, operation, value, actionContext }) {
|
|||||||
}, {
|
}, {
|
||||||
selector: prop
|
selector: prop
|
||||||
});
|
});
|
||||||
|
// Also write it straight to the prop so that it is updated in the actionContext
|
||||||
|
prop.damage += increment;
|
||||||
|
prop.value -= increment;
|
||||||
|
logFunction?.(increment);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyTriggers(actionContext.triggers?.damageProperty?.after, prop, actionContext);
|
applyTriggers(actionContext.triggers?.damageProperty?.after, prop, actionContext);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { reorderDocs } from '/imports/api/parenting/order.js';
|
|||||||
var snackbar;
|
var snackbar;
|
||||||
if (Meteor.isClient) {
|
if (Meteor.isClient) {
|
||||||
snackbar = require(
|
snackbar = require(
|
||||||
'/imports/ui/components/snackbars/SnackbarQueue.js'
|
'/imports/client/ui/components/snackbars/SnackbarQueue.js'
|
||||||
).snackbar
|
).snackbar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,11 +32,13 @@ const flipToggle = new ValidatedMethod({
|
|||||||
|
|
||||||
// Invert the current value, disabled is the canonical store of value
|
// Invert the current value, disabled is the canonical store of value
|
||||||
const currentValue = !property.disabled;
|
const currentValue = !property.disabled;
|
||||||
CreatureProperties.update(_id, {$set: {
|
CreatureProperties.update(_id, {
|
||||||
|
$set: {
|
||||||
enabled: !currentValue,
|
enabled: !currentValue,
|
||||||
disabled: currentValue,
|
disabled: currentValue,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
}}, {
|
}
|
||||||
|
}, {
|
||||||
selector: { type: 'toggle' },
|
selector: { type: 'toggle' },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ let CreatureSettingsSchema = new SimpleSchema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
//hide rest buttons
|
||||||
|
hideRestButtons: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
// Swap around the modifier and stat
|
// Swap around the modifier and stat
|
||||||
swapStatAndModifier: {
|
swapStatAndModifier: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { assertEditPermission } from '/imports/api/creature/creatures/creaturePe
|
|||||||
import { union } from 'lodash';
|
import { union } from 'lodash';
|
||||||
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
||||||
import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||||
|
|
||||||
const restCreature = new ValidatedMethod({
|
const restCreature = new ValidatedMethod({
|
||||||
name: 'creature.methods.rest',
|
name: 'creature.methods.rest',
|
||||||
@@ -62,31 +63,59 @@ function doRestWork(restType, actionContext) {
|
|||||||
} else {
|
} else {
|
||||||
resetFilter = { $in: ['shortRest', 'longRest'] }
|
resetFilter = { $in: ['shortRest', 'longRest'] }
|
||||||
}
|
}
|
||||||
|
resetProperties(creatureId, resetFilter, actionContext);
|
||||||
|
|
||||||
|
// Reset half hit dice on a long rest, starting with the highest dice
|
||||||
|
if (restType === 'longRest') {
|
||||||
|
resetHitDice(creatureId, actionContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetProperties(creatureId, resetFilter, actionContext) {
|
||||||
// Only apply to active properties
|
// Only apply to active properties
|
||||||
let filter = {
|
const filter = {
|
||||||
'ancestors.id': creatureId,
|
'ancestors.id': creatureId,
|
||||||
reset: resetFilter,
|
reset: resetFilter,
|
||||||
removed: { $ne: true },
|
removed: { $ne: true },
|
||||||
inactive: { $ne: true },
|
inactive: { $ne: true },
|
||||||
};
|
};
|
||||||
// update all attribute's damage
|
// update all attribute's damage
|
||||||
filter.type = 'attribute';
|
const attributeFilter = {
|
||||||
CreatureProperties.update(filter, {
|
...filter,
|
||||||
$set: {
|
type: 'attribute',
|
||||||
damage: 0,
|
damage: { $nin: [0, undefined] },
|
||||||
dirty: true,
|
|
||||||
}
|
}
|
||||||
}, {
|
CreatureProperties.find(attributeFilter).forEach(prop => {
|
||||||
selector: {type: 'attribute'},
|
damagePropertyWork({
|
||||||
multi: true,
|
prop,
|
||||||
|
operation: 'increment',
|
||||||
|
value: -prop.damage ?? 0,
|
||||||
|
actionContext,
|
||||||
|
logFunction(increment) {
|
||||||
|
actionContext.addLog({
|
||||||
|
name: prop.name,
|
||||||
|
value: increment < 0 ? `Restored ${-increment}` : `Removed ${-increment}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
// Update all action-like properties' usesUsed
|
// Update all action-like properties' usesUsed
|
||||||
filter.type = {$in: [
|
const actionFilter = {
|
||||||
'action',
|
...filter,
|
||||||
'attack',
|
type: {
|
||||||
'spell'
|
$in: ['action', 'spell']
|
||||||
]};
|
},
|
||||||
CreatureProperties.update(filter, {
|
usesUsed: { $nin: [0, undefined] },
|
||||||
|
};
|
||||||
|
CreatureProperties.find(actionFilter, {
|
||||||
|
fields: { name: 1, usesUsed: 1 }
|
||||||
|
}).forEach(prop => {
|
||||||
|
actionContext.addLog({
|
||||||
|
name: prop.name,
|
||||||
|
value: prop.usesUsed >= 0 ? `Restored ${prop.usesUsed} uses` : `Removed ${-prop.usesUsed} uses`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
CreatureProperties.update(actionFilter, {
|
||||||
$set: {
|
$set: {
|
||||||
usesUsed: 0,
|
usesUsed: 0,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
@@ -95,20 +124,15 @@ function doRestWork(restType, actionContext) {
|
|||||||
selector: { type: 'action' },
|
selector: { type: 'action' },
|
||||||
multi: true,
|
multi: true,
|
||||||
});
|
});
|
||||||
// Reset half hit dice on a long rest, starting with the highest dice
|
}
|
||||||
if (restType === 'longRest'){
|
|
||||||
|
function resetHitDice(creatureId, actionContext) {
|
||||||
let hitDice = CreatureProperties.find({
|
let hitDice = CreatureProperties.find({
|
||||||
'ancestors.id': creatureId,
|
'ancestors.id': creatureId,
|
||||||
type: 'attribute',
|
type: 'attribute',
|
||||||
attributeType: 'hitDice',
|
attributeType: 'hitDice',
|
||||||
removed: { $ne: true },
|
removed: { $ne: true },
|
||||||
inactive: { $ne: true },
|
inactive: { $ne: true },
|
||||||
}, {
|
|
||||||
fields: {
|
|
||||||
hitDiceSize: 1,
|
|
||||||
damage: 1,
|
|
||||||
total: 1,
|
|
||||||
}
|
|
||||||
}).fetch();
|
}).fetch();
|
||||||
// Use a collator to do sorting in natural order
|
// Use a collator to do sorting in natural order
|
||||||
let collator = new Intl.Collator('en', {
|
let collator = new Intl.Collator('en', {
|
||||||
@@ -122,23 +146,25 @@ function doRestWork(restType, actionContext) {
|
|||||||
let resetMultiplier = actionContext.creature.settings.hitDiceResetMultiplier || 0.5;
|
let resetMultiplier = actionContext.creature.settings.hitDiceResetMultiplier || 0.5;
|
||||||
let recoverableHd = Math.max(Math.floor(totalHd * resetMultiplier), 1);
|
let recoverableHd = Math.max(Math.floor(totalHd * resetMultiplier), 1);
|
||||||
// recover each hit dice in turn until the recoverable amount is used up
|
// recover each hit dice in turn until the recoverable amount is used up
|
||||||
let amountToRecover, resultingDamage;
|
let amountToRecover;
|
||||||
hitDice.forEach(hd => {
|
hitDice.forEach(hd => {
|
||||||
if (!recoverableHd) return;
|
if (!recoverableHd) return;
|
||||||
amountToRecover = Math.min(recoverableHd, hd.damage || 0);
|
amountToRecover = Math.min(recoverableHd, hd.damage ?? 0);
|
||||||
if (!amountToRecover) return;
|
if (!amountToRecover) return;
|
||||||
recoverableHd -= amountToRecover;
|
recoverableHd -= amountToRecover;
|
||||||
resultingDamage = hd.damage - amountToRecover;
|
damagePropertyWork({
|
||||||
CreatureProperties.update(hd._id, {
|
prop: hd,
|
||||||
$set: {
|
operation: 'increment',
|
||||||
damage: resultingDamage,
|
value: -amountToRecover,
|
||||||
dirty: true,
|
actionContext,
|
||||||
}
|
logFunction(increment) {
|
||||||
}, {
|
actionContext.addLog({
|
||||||
selector: {type: 'attribute'},
|
name: hd.name,
|
||||||
});
|
value: increment < 0 ? `Restored ${-increment} hit dice` : `Removed ${increment} hit dice`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default restCreature;
|
export default restCreature;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ let ExperienceSchema = new SimpleSchema({
|
|||||||
|
|
||||||
Experiences.attachSchema(ExperienceSchema);
|
Experiences.attachSchema(ExperienceSchema);
|
||||||
|
|
||||||
const insertExperienceForCreature = function({experience, creatureId, userId}){
|
const insertExperienceForCreature = function ({ experience, creatureId }) {
|
||||||
if (experience.xp) {
|
if (experience.xp) {
|
||||||
Creatures.update(creatureId, {
|
Creatures.update(creatureId, {
|
||||||
$inc: { 'denormalizedStats.xp': experience.xp },
|
$inc: { 'denormalizedStats.xp': experience.xp },
|
||||||
@@ -172,11 +172,13 @@ const recomputeExperiences = new ValidatedMethod({
|
|||||||
xp += experience.xp || 0;
|
xp += experience.xp || 0;
|
||||||
milestoneLevels += experience.levels || 0;
|
milestoneLevels += experience.levels || 0;
|
||||||
});
|
});
|
||||||
Creatures.update(creatureId, {$set: {
|
Creatures.update(creatureId, {
|
||||||
|
$set: {
|
||||||
'denormalizedStats.xp': xp,
|
'denormalizedStats.xp': xp,
|
||||||
'denormalizedStats.milestoneLevels': milestoneLevels,
|
'denormalizedStats.milestoneLevels': milestoneLevels,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -99,14 +99,16 @@ const insertCreatureLog = new ValidatedMethod({
|
|||||||
}).validator(),
|
}).validator(),
|
||||||
run({ log }) {
|
run({ log }) {
|
||||||
const creatureId = log.creatureId;
|
const creatureId = log.creatureId;
|
||||||
const creature = Creatures.findOne(creatureId, {fields: {
|
const creature = Creatures.findOne(creatureId, {
|
||||||
|
fields: {
|
||||||
readers: 1,
|
readers: 1,
|
||||||
writers: 1,
|
writers: 1,
|
||||||
owner: 1,
|
owner: 1,
|
||||||
'settings.discordWebhook': 1,
|
'settings.discordWebhook': 1,
|
||||||
name: 1,
|
name: 1,
|
||||||
avatarPicture: 1,
|
avatarPicture: 1,
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
assertEditPermission(creature, this.userId);
|
assertEditPermission(creature, this.userId);
|
||||||
// Build the new log
|
// Build the new log
|
||||||
let id = insertCreatureLogWork({ log, creature, method: this })
|
let id = insertCreatureLogWork({ log, creature, method: this })
|
||||||
@@ -154,14 +156,16 @@ const logRoll = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
}).validator(),
|
}).validator(),
|
||||||
run({ roll, creatureId }) {
|
run({ roll, creatureId }) {
|
||||||
const creature = Creatures.findOne(creatureId, {fields: {
|
const creature = Creatures.findOne(creatureId, {
|
||||||
|
fields: {
|
||||||
readers: 1,
|
readers: 1,
|
||||||
writers: 1,
|
writers: 1,
|
||||||
owner: 1,
|
owner: 1,
|
||||||
'settings.discordWebhook': 1,
|
'settings.discordWebhook': 1,
|
||||||
name: 1,
|
name: 1,
|
||||||
avatarPicture: 1,
|
avatarPicture: 1,
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
assertEditPermission(creature, this.userId);
|
assertEditPermission(creature, this.userId);
|
||||||
const variables = CreatureVariables.findOne({ _creatureId: creatureId });
|
const variables = CreatureVariables.findOne({ _creatureId: creatureId });
|
||||||
let logContent = []
|
let logContent = []
|
||||||
|
|||||||
324
app/imports/api/docs/Docs.js
Normal file
324
app/imports/api/docs/Docs.js
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { Mongo } from 'meteor/mongo';
|
||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { softRemove } from '/imports/api/parenting/softRemove.js';
|
||||||
|
import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema.js';
|
||||||
|
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
|
||||||
|
import '/imports/api/library/methods/index.js';
|
||||||
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||||
|
import { restore } from '/imports/api/parenting/softRemove.js';
|
||||||
|
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||||
|
import { getAncestry } from '/imports/api/parenting/parenting.js';
|
||||||
|
|
||||||
|
const Docs = new Mongo.Collection('docs');
|
||||||
|
|
||||||
|
const RefSchema = new SimpleSchema({
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
index: 1
|
||||||
|
},
|
||||||
|
collection: {
|
||||||
|
type: String,
|
||||||
|
max: STORAGE_LIMITS.collectionName,
|
||||||
|
},
|
||||||
|
urlName: {
|
||||||
|
type: String,
|
||||||
|
regEx: /[a-z]+(?:[a-z]|-)+/,
|
||||||
|
min: 2,
|
||||||
|
max: STORAGE_LIMITS.variableName,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
max: STORAGE_LIMITS.description,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let ChildSchema = new SimpleSchema({
|
||||||
|
order: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
type: RefSchema,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
ancestors: {
|
||||||
|
type: Array,
|
||||||
|
defaultValue: [],
|
||||||
|
maxCount: STORAGE_LIMITS.ancestorCount,
|
||||||
|
},
|
||||||
|
'ancestors.$': {
|
||||||
|
type: RefSchema,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let DocSchema = new SimpleSchema({
|
||||||
|
_id: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
max: STORAGE_LIMITS.description,
|
||||||
|
},
|
||||||
|
urlName: {
|
||||||
|
type: String,
|
||||||
|
regEx: /[a-z]+(?:[a-z]|-)+/,
|
||||||
|
min: 2,
|
||||||
|
max: STORAGE_LIMITS.variableName,
|
||||||
|
},
|
||||||
|
href: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
published: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: storedIconsSchema,
|
||||||
|
optional: true,
|
||||||
|
max: STORAGE_LIMITS.icon,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let schema = new SimpleSchema({});
|
||||||
|
schema.extend(DocSchema);
|
||||||
|
schema.extend(ChildSchema);
|
||||||
|
schema.extend(SoftRemovableSchema);
|
||||||
|
Docs.attachSchema(schema);
|
||||||
|
|
||||||
|
function assertDocsEditPermission(userId) {
|
||||||
|
if (!userId || typeof userId !== 'string') throw new Meteor.Error('No user id provided');
|
||||||
|
const user = Meteor.users.findOne(userId);
|
||||||
|
if (!user) throw new Meteor.Error('User does not exist');
|
||||||
|
if (!user?.roles?.includes?.('docsWriter')) throw ('Permission denied')
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDocLink(doc, urlName) {
|
||||||
|
if (!urlName) urlName = doc.urlName;
|
||||||
|
const address = ['/docs'];
|
||||||
|
doc.ancestors?.forEach(a => {
|
||||||
|
address.push(a.urlName);
|
||||||
|
});
|
||||||
|
address.push(urlName);
|
||||||
|
return address.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuildDocAncestors(docId) {
|
||||||
|
const newDoc = Docs.findOne(docId);
|
||||||
|
Docs.find({ 'ancestors.id': docId }).forEach(doc => {
|
||||||
|
doc.ancestors.forEach((a, i) => {
|
||||||
|
if (a.id === docId) {
|
||||||
|
Docs.update(doc._id, {
|
||||||
|
$set: {
|
||||||
|
[`ancestors.${i}`]: {
|
||||||
|
id: newDoc._id,
|
||||||
|
collection: 'docs',
|
||||||
|
urlName: newDoc.urlName,
|
||||||
|
name: newDoc.name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
doc = Docs.findOne(doc._id);
|
||||||
|
const newLink = getDocLink(doc);
|
||||||
|
if (doc.href !== newLink) {
|
||||||
|
Docs.update(doc._id, { $set: { href: newLink } })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a means of seeding new servers with documentation
|
||||||
|
if (Meteor.isClient) {
|
||||||
|
Docs.getJsonDocs = function () {
|
||||||
|
return JSON.stringify(Docs.find({}).fetch(), null, 2);
|
||||||
|
}
|
||||||
|
} else if (Meteor.isServer) {
|
||||||
|
Meteor.startup(() => {
|
||||||
|
if (!Docs.findOne()) {
|
||||||
|
Assets.getText('docs/defaultDocs.json', (error, string) => {
|
||||||
|
const docs = JSON.parse(string)
|
||||||
|
docs.forEach(doc => Docs.insert(doc));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertDoc = new ValidatedMethod({
|
||||||
|
name: 'docs.insert',
|
||||||
|
validate: null,
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({ doc, parentRef }) {
|
||||||
|
delete doc._id;
|
||||||
|
assertDocsEditPermission(this.userId);
|
||||||
|
// get the new ancestry for the properties
|
||||||
|
if (parentRef) {
|
||||||
|
var { ancestors } = getAncestry({
|
||||||
|
parentRef,
|
||||||
|
inheritedFields: { name: 1, urlName: 1 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
doc.parent = parentRef;
|
||||||
|
doc.ancestors = ancestors;
|
||||||
|
|
||||||
|
const lastOrder = Docs.find({}, { sort: { order: -1 } }).fetch()[0]?.order || 0;
|
||||||
|
doc.order = lastOrder + 1;
|
||||||
|
doc.urlName = 'new-doc-' + (lastOrder + 1);
|
||||||
|
|
||||||
|
doc.href = getDocLink(doc);
|
||||||
|
if (Docs.findOne({ href: doc.href })) {
|
||||||
|
throw new Meteor.Error('Link collision', 'A document with the same URL already exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
const docId = Docs.insert(doc);
|
||||||
|
reorderDocs({
|
||||||
|
collection: Docs,
|
||||||
|
ancestorId: 'root',
|
||||||
|
});
|
||||||
|
return docId;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateDoc = new ValidatedMethod({
|
||||||
|
name: 'docs.update',
|
||||||
|
validate({ _id, path }) {
|
||||||
|
if (!_id) return false;
|
||||||
|
// We cannot change these fields with a simple update
|
||||||
|
switch (path[0]) {
|
||||||
|
case '_is':
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({ _id, path, value }) {
|
||||||
|
assertDocsEditPermission(this.userId);
|
||||||
|
let pathString = path.join('.');
|
||||||
|
let modifier;
|
||||||
|
// unset empty values
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
modifier = { $unset: { [pathString]: 1 } };
|
||||||
|
} else {
|
||||||
|
modifier = { $set: { [pathString]: value } };
|
||||||
|
}
|
||||||
|
if (pathString === 'urlName') {
|
||||||
|
const doc = Docs.findOne(_id);
|
||||||
|
const newLink = getDocLink(doc, value);
|
||||||
|
if (Docs.findOne({ href: newLink })) {
|
||||||
|
throw new Meteor.Error('Link collision', 'A document with the same URL already exists');
|
||||||
|
}
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.href = newLink;
|
||||||
|
rebuildDocAncestors(_id);
|
||||||
|
}
|
||||||
|
const updates = Docs.update(_id, modifier);
|
||||||
|
if (pathString === 'name' || pathString === 'urlName') {
|
||||||
|
rebuildDocAncestors(_id);
|
||||||
|
}
|
||||||
|
reorderDocs({
|
||||||
|
collection: Docs,
|
||||||
|
ancestorId: 'root',
|
||||||
|
});
|
||||||
|
return updates;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const pushToDoc = new ValidatedMethod({
|
||||||
|
name: 'docs.push',
|
||||||
|
validate: null,
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({ _id, path, value }) {
|
||||||
|
assertDocsEditPermission(this.userId);
|
||||||
|
return Docs.update(_id, {
|
||||||
|
$push: { [path.join('.')]: value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const pullFromDoc = new ValidatedMethod({
|
||||||
|
name: 'docs.pull',
|
||||||
|
validate: null,
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({ _id, path, itemId }) {
|
||||||
|
assertDocsEditPermission(this.userId);
|
||||||
|
return Docs.update(_id, {
|
||||||
|
$pull: { [path.join('.')]: { _id: itemId } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const softRemoveDoc = new ValidatedMethod({
|
||||||
|
name: 'docs.softRemove',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
_id: SimpleSchema.RegEx.Id
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({ _id }) {
|
||||||
|
assertDocsEditPermission(this.userId);
|
||||||
|
softRemove({ _id, collection: Docs });
|
||||||
|
reorderDocs({
|
||||||
|
collection: Docs,
|
||||||
|
ancestorId: 'root',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const restoreDoc = new ValidatedMethod({
|
||||||
|
name: 'docs.restore',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
_id: SimpleSchema.RegEx.Id
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({ _id }) {
|
||||||
|
assertDocsEditPermission(this.userId);
|
||||||
|
restore({ _id, collection: Docs });
|
||||||
|
reorderDocs({
|
||||||
|
collection: Docs,
|
||||||
|
ancestorId: 'root',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
DocSchema,
|
||||||
|
insertDoc,
|
||||||
|
updateDoc,
|
||||||
|
pushToDoc,
|
||||||
|
pullFromDoc,
|
||||||
|
softRemoveDoc,
|
||||||
|
restoreDoc,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Docs;
|
||||||
@@ -2,7 +2,9 @@ import action from './applyPropertyByType/applyAction.js';
|
|||||||
import adjustment from './applyPropertyByType/applyAdjustment.js';
|
import adjustment from './applyPropertyByType/applyAdjustment.js';
|
||||||
import branch from './applyPropertyByType/applyBranch.js';
|
import branch from './applyPropertyByType/applyBranch.js';
|
||||||
import buff from './applyPropertyByType/applyBuff.js';
|
import buff from './applyPropertyByType/applyBuff.js';
|
||||||
|
import buffRemover from './applyPropertyByType/applyBuffRemover.js';
|
||||||
import damage from './applyPropertyByType/applyDamage.js';
|
import damage from './applyPropertyByType/applyDamage.js';
|
||||||
|
import folder from './applyPropertyByType/applyFolder.js';
|
||||||
import note from './applyPropertyByType/applyNote.js';
|
import note from './applyPropertyByType/applyNote.js';
|
||||||
import roll from './applyPropertyByType/applyRoll.js';
|
import roll from './applyPropertyByType/applyRoll.js';
|
||||||
import savingThrow from './applyPropertyByType/applySavingThrow.js';
|
import savingThrow from './applyPropertyByType/applySavingThrow.js';
|
||||||
@@ -13,7 +15,9 @@ const applyPropertyByType = {
|
|||||||
adjustment,
|
adjustment,
|
||||||
branch,
|
branch,
|
||||||
buff,
|
buff,
|
||||||
|
buffRemover,
|
||||||
damage,
|
damage,
|
||||||
|
folder,
|
||||||
note,
|
note,
|
||||||
roll,
|
roll,
|
||||||
savingThrow,
|
savingThrow,
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ import applyProperty from '../applyProperty.js';
|
|||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
||||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
import numberToSignedString from '/imports/api/utility/numberToSignedString.js';
|
||||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
import { resetProperties } from '/imports/api/creature/creatures/methods/restCreature.js';
|
||||||
|
|
||||||
export default function applyAction(node, actionContext) {
|
export default function applyAction(node, actionContext) {
|
||||||
applyNodeTriggers(node, 'before', actionContext);
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
const prop = node.node;
|
const prop = node.node;
|
||||||
let targets = actionContext.targets;
|
if (prop.target === 'self') actionContext.targets = [actionContext.creature];
|
||||||
if (prop.target === 'self') targets = [actionContext.creature];
|
const targets = actionContext.targets;
|
||||||
|
|
||||||
// Log the name and summary
|
// Log the name and summary
|
||||||
let content = { name: prop.name };
|
let content = { name: prop.name };
|
||||||
@@ -20,7 +21,7 @@ export default function applyAction(node, actionContext) {
|
|||||||
recalculateInlineCalculations(prop.summary, actionContext);
|
recalculateInlineCalculations(prop.summary, actionContext);
|
||||||
content.value = prop.summary.value;
|
content.value = prop.summary.value;
|
||||||
}
|
}
|
||||||
actionContext.addLog(content);
|
if (!prop.silent) actionContext.addLog(content);
|
||||||
|
|
||||||
// Spend the resources
|
// Spend the resources
|
||||||
const failed = spendResources(prop, actionContext);
|
const failed = spendResources(prop, actionContext);
|
||||||
@@ -44,6 +45,9 @@ export default function applyAction(node, actionContext) {
|
|||||||
} else {
|
} else {
|
||||||
applyChildren(node, actionContext);
|
applyChildren(node, actionContext);
|
||||||
}
|
}
|
||||||
|
if (prop.actionType === 'event' && prop.variableName) {
|
||||||
|
resetProperties(actionContext.creature._id, prop.variableName, actionContext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyAttackWithoutTarget({ attack, actionContext }) {
|
function applyAttackWithoutTarget({ attack, actionContext }) {
|
||||||
@@ -159,8 +163,9 @@ function rollAttack(attack, scope){
|
|||||||
value = rollDice(1, 20)[0];
|
value = rollDice(1, 20)[0];
|
||||||
resultPrefix = `1d20 [${value}] ${rollModifierText}`
|
resultPrefix = `1d20 [${value}] ${rollModifierText}`
|
||||||
}
|
}
|
||||||
scope['$attackRoll'] = {value};
|
scope['$attackDiceRoll'] = { value };
|
||||||
const result = value + attack.value;
|
const result = value + attack.value;
|
||||||
|
scope['$attackRoll'] = { value: result };
|
||||||
const { criticalHit, criticalMiss } = applyCrits(value, scope);
|
const { criticalHit, criticalMiss } = applyCrits(value, scope);
|
||||||
return { resultPrefix, result, value, criticalHit, criticalMiss };
|
return { resultPrefix, result, value, criticalHit, criticalMiss };
|
||||||
}
|
}
|
||||||
@@ -188,7 +193,7 @@ function applyChildren(node, actionContext) {
|
|||||||
function spendResources(prop, actionContext) {
|
function spendResources(prop, actionContext) {
|
||||||
// Check Uses
|
// Check Uses
|
||||||
if (prop.usesLeft <= 0) {
|
if (prop.usesLeft <= 0) {
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value: `${prop.name || 'action'} does not have enough uses left`,
|
value: `${prop.name || 'action'} does not have enough uses left`,
|
||||||
});
|
});
|
||||||
@@ -196,7 +201,7 @@ function spendResources(prop, actionContext){
|
|||||||
}
|
}
|
||||||
// Resources
|
// Resources
|
||||||
if (prop.insufficientResources) {
|
if (prop.insufficientResources) {
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value: 'This creature doesn\'t have sufficient resources to perform this action',
|
value: 'This creature doesn\'t have sufficient resources to perform this action',
|
||||||
});
|
});
|
||||||
@@ -257,7 +262,7 @@ function spendResources(prop, actionContext){
|
|||||||
}, {
|
}, {
|
||||||
selector: prop
|
selector: prop
|
||||||
});
|
});
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Uses left',
|
name: 'Uses left',
|
||||||
value: prop.usesLeft - 1,
|
value: prop.usesLeft - 1,
|
||||||
inline: true,
|
inline: true,
|
||||||
@@ -288,12 +293,12 @@ function spendResources(prop, actionContext){
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Log all the spending
|
// Log all the spending
|
||||||
if (gainLog.length) actionContext.addLog({
|
if (gainLog.length && !prop.silent) actionContext.addLog({
|
||||||
name: 'Gained',
|
name: 'Gained',
|
||||||
value: gainLog.join('\n'),
|
value: gainLog.join('\n'),
|
||||||
inline: true,
|
inline: true,
|
||||||
});
|
});
|
||||||
if (spendLog.length) actionContext.addLog({
|
if (spendLog.length && !prop.silent) actionContext.addLog({
|
||||||
name: 'Spent',
|
name: 'Spent',
|
||||||
value: spendLog.join('\n'),
|
value: spendLog.join('\n'),
|
||||||
inline: true,
|
inline: true,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default function applyAdjustment(node, actionContext){
|
|||||||
damageTargets.forEach(target => {
|
damageTargets.forEach(target => {
|
||||||
let stat = target.variables[prop.stat];
|
let stat = target.variables[prop.stat];
|
||||||
if (!stat?.type) {
|
if (!stat?.type) {
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set`
|
value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set`
|
||||||
});
|
});
|
||||||
@@ -36,7 +36,7 @@ export default function applyAdjustment(node, actionContext){
|
|||||||
value,
|
value,
|
||||||
actionContext,
|
actionContext,
|
||||||
});
|
});
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage',
|
||||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
||||||
` ${value}`,
|
` ${value}`,
|
||||||
@@ -44,7 +44,7 @@ export default function applyAdjustment(node, actionContext){
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage',
|
||||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
||||||
` ${value}`,
|
` ${value}`,
|
||||||
|
|||||||
@@ -36,25 +36,25 @@ export default function applyBranch(node, actionContext){
|
|||||||
break;
|
break;
|
||||||
case 'hit':
|
case 'hit':
|
||||||
if (scope['$attackHit']?.value){
|
if (scope['$attackHit']?.value){
|
||||||
if (!targets.length) actionContext.addLog({value: '**On hit**'});
|
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On hit**'});
|
||||||
applyChildren();
|
applyChildren();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'miss':
|
case 'miss':
|
||||||
if (scope['$attackMiss']?.value){
|
if (scope['$attackMiss']?.value){
|
||||||
if (!targets.length) actionContext.addLog({value: '**On miss**'});
|
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On miss**'});
|
||||||
applyChildren();
|
applyChildren();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'failedSave':
|
case 'failedSave':
|
||||||
if (scope['$saveFailed']?.value){
|
if (scope['$saveFailed']?.value){
|
||||||
if (!targets.length) actionContext.addLog({value: '**On failed save**'});
|
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On failed save**'});
|
||||||
applyChildren();
|
applyChildren();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'successfulSave':
|
case 'successfulSave':
|
||||||
if (scope['$saveSucceeded']?.value){
|
if (scope['$saveSucceeded']?.value){
|
||||||
if (!targets.length) actionContext.addLog({value: '**On save**',});
|
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On save**',});
|
||||||
applyChildren();
|
applyChildren();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import logErrors from './shared/logErrors.js';
|
|||||||
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js';
|
import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js';
|
||||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
|
||||||
|
import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js';
|
||||||
|
|
||||||
export default function applyBuff(node, actionContext) {
|
export default function applyBuff(node, actionContext) {
|
||||||
applyNodeTriggers(node, 'before', actionContext);
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
@@ -21,14 +23,20 @@ export default function applyBuff(node, actionContext){
|
|||||||
|
|
||||||
// Then copy the decendants of the buff to the targets
|
// Then copy the decendants of the buff to the targets
|
||||||
let propList = [prop];
|
let propList = [prop];
|
||||||
function addChildrenToPropList(children){
|
function addChildrenToPropList(children, { skipCrystalize } = {}) {
|
||||||
children.forEach(child => {
|
children.forEach(child => {
|
||||||
|
if (skipCrystalize) child.node._skipCrystalize = true;
|
||||||
propList.push(child.node);
|
propList.push(child.node);
|
||||||
addChildrenToPropList(child.children);
|
// recursively add the child's children, but don't crystalize nested buffs
|
||||||
|
addChildrenToPropList(child.children, {
|
||||||
|
skipCrystalize: skipCrystalize || child.node.type === 'buff'
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
addChildrenToPropList(node.children);
|
addChildrenToPropList(node.children);
|
||||||
|
if (!prop.skipCrystalization) {
|
||||||
crystalizeVariables({ propList, actionContext });
|
crystalizeVariables({ propList, actionContext });
|
||||||
|
}
|
||||||
|
|
||||||
let oldParent = {
|
let oldParent = {
|
||||||
id: prop.parent.id,
|
id: prop.parent.id,
|
||||||
@@ -39,12 +47,17 @@ export default function applyBuff(node, actionContext){
|
|||||||
copyNodeListToTarget(propList, target, oldParent);
|
copyNodeListToTarget(propList, target, oldParent);
|
||||||
|
|
||||||
//Log the buff
|
//Log the buff
|
||||||
if (prop.name || prop.description?.value){
|
let logValue = prop.description?.value
|
||||||
|
if (prop.description?.text) {
|
||||||
|
recalculateInlineCalculations(prop.description, actionContext);
|
||||||
|
logValue = prop.description?.value;
|
||||||
|
}
|
||||||
|
if ((prop.name || prop.description?.value) && !prop.silent) {
|
||||||
if (target._id === actionContext.creature._id) {
|
if (target._id === actionContext.creature._id) {
|
||||||
// Targeting self
|
// Targeting self
|
||||||
actionContext.addLog({
|
actionContext.addLog({
|
||||||
name: prop.name,
|
name: prop.name,
|
||||||
value: prop.description?.value,
|
value: logValue,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Targeting other
|
// Targeting other
|
||||||
@@ -53,7 +66,7 @@ export default function applyBuff(node, actionContext){
|
|||||||
creatureId: target._id,
|
creatureId: target._id,
|
||||||
content: [{
|
content: [{
|
||||||
name: prop.name,
|
name: prop.name,
|
||||||
value: prop.description?.value,
|
value: logValue,
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -88,6 +101,11 @@ function copyNodeListToTarget(propList, target, oldParent){
|
|||||||
*/
|
*/
|
||||||
function crystalizeVariables({ propList, actionContext }) {
|
function crystalizeVariables({ propList, actionContext }) {
|
||||||
propList.forEach(prop => {
|
propList.forEach(prop => {
|
||||||
|
if (prop._skipCrystalize) {
|
||||||
|
delete prop._skipCrystalize;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Iterate through all the calculations and crystalize them
|
||||||
computedSchemas[prop.type].computedFields().forEach(calcKey => {
|
computedSchemas[prop.type].computedFields().forEach(calcKey => {
|
||||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||||
const calcObj = get(prop, key);
|
const calcObj = get(prop, key);
|
||||||
@@ -124,5 +142,36 @@ function crystalizeVariables({propList, actionContext}){
|
|||||||
calcObj.hash = cyrb53(calcObj.calculation);
|
calcObj.hash = cyrb53(calcObj.calculation);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// For each key in the schema
|
||||||
|
computedSchemas[prop.type].inlineCalculationFields().forEach(calcKey => {
|
||||||
|
// That ends in .inlineCalculations
|
||||||
|
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||||
|
const inlineCalcObj = get(prop, key);
|
||||||
|
if (!inlineCalcObj) return;
|
||||||
|
|
||||||
|
// If there is no text, skip
|
||||||
|
if (!inlineCalcObj.text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace all the existing calculations
|
||||||
|
let index = -1;
|
||||||
|
inlineCalcObj.text = inlineCalcObj.text.replace(INLINE_CALCULATION_REGEX, () => {
|
||||||
|
index += 1;
|
||||||
|
return `{${inlineCalcObj.inlineCalculations[index].calculation}}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the value to the uncomputed string
|
||||||
|
inlineCalcObj.value = inlineCalcObj.text;
|
||||||
|
|
||||||
|
// Write a new hash
|
||||||
|
const inlineCalcHash = cyrb53(inlineCalcObj.text);
|
||||||
|
if (inlineCalcHash === inlineCalcObj.hash) {
|
||||||
|
// Skip if nothing changed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inlineCalcObj.hash = inlineCalcHash;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { findLast, difference, intersection, filter } from 'lodash';
|
||||||
|
import applyProperty from '../applyProperty.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
import { getProperyAncestors, getPropertiesOfType } from '/imports/api/engine/loadCreatures.js';
|
||||||
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
import { softRemove } from '/imports/api/parenting/softRemove.js';
|
||||||
|
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js';
|
||||||
|
|
||||||
|
export default function applyBuffRemover(node, actionContext) {
|
||||||
|
// Apply triggers
|
||||||
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
|
|
||||||
|
const prop = node.node;
|
||||||
|
|
||||||
|
// Log Name
|
||||||
|
if (prop.name && !prop.silent){
|
||||||
|
actionContext.addLog({ name: prop.name });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove buffs
|
||||||
|
if (prop.targetParentBuff) {
|
||||||
|
// Remove nearest ancestor buff
|
||||||
|
const ancestors = getProperyAncestors(actionContext.creature._id, prop._id);
|
||||||
|
const nearestBuff = findLast(ancestors, ancestor => ancestor.type === 'buff');
|
||||||
|
if (!nearestBuff) {
|
||||||
|
actionContext.addLog({
|
||||||
|
name: 'Error',
|
||||||
|
value: 'Buff remover does not have a parent buff to remove',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeBuff(nearestBuff, actionContext, prop);
|
||||||
|
} else {
|
||||||
|
// Get all the buffs targeted by tags
|
||||||
|
const allBuffs = getPropertiesOfType(actionContext.creature._id, 'buff');
|
||||||
|
const targetedBuffs = filter(allBuffs, buff => {
|
||||||
|
if (buff.inactive) return false;
|
||||||
|
if (buffRemoverMatchTags(prop, buff)) return true;
|
||||||
|
});
|
||||||
|
// Remove the buffs
|
||||||
|
if (prop.removeAll) {
|
||||||
|
// Remove all matching buffs
|
||||||
|
targetedBuffs.forEach(buff => {
|
||||||
|
removeBuff(buff, actionContext, prop);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Sort in reverse order
|
||||||
|
targetedBuffs.sort((a, b) => b.order - a.order);
|
||||||
|
// Remove the one with the highest order
|
||||||
|
const buff = targetedBuffs[0];
|
||||||
|
if (buff) {
|
||||||
|
removeBuff(buff, actionContext, prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply triggers
|
||||||
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
|
// Apply children
|
||||||
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBuff(buff, actionContext, prop) {
|
||||||
|
if (!prop.silent) actionContext.addLog({
|
||||||
|
name: 'Removed',
|
||||||
|
value: `${buff.name || 'Buff'}`
|
||||||
|
});
|
||||||
|
softRemove({ _id: buff._id, collection: CreatureProperties });
|
||||||
|
}
|
||||||
|
|
||||||
|
function buffRemoverMatchTags(buffRemover, prop) {
|
||||||
|
let matched = false;
|
||||||
|
const propTags = getEffectivePropTags(prop);
|
||||||
|
// Check the target tags
|
||||||
|
if (
|
||||||
|
!buffRemover.targetTags?.length ||
|
||||||
|
difference(buffRemover.targetTags, propTags).length === 0
|
||||||
|
) {
|
||||||
|
matched = true;
|
||||||
|
}
|
||||||
|
// Check the extra tags
|
||||||
|
buffRemover.extraTags?.forEach(extra => {
|
||||||
|
if (extra.operation === 'OR') {
|
||||||
|
if (matched) return;
|
||||||
|
if (
|
||||||
|
!extra.tags.length ||
|
||||||
|
difference(extra.tags, propTags).length === 0
|
||||||
|
) {
|
||||||
|
matched = true;
|
||||||
|
}
|
||||||
|
} else if (extra.operation === 'NOT') {
|
||||||
|
if (
|
||||||
|
extra.tags.length &&
|
||||||
|
intersection(extra.tags, propTags)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { some, intersection, difference, remove } from 'lodash';
|
import { some, intersection, difference, remove, includes } from 'lodash';
|
||||||
import applyProperty from '../applyProperty.js';
|
import applyProperty from '../applyProperty.js';
|
||||||
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
import resolve, { Context, toString } from '/imports/parser/resolve.js';
|
import resolve, { Context, toString } from '/imports/parser/resolve.js';
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
getPropertiesOfType
|
getPropertiesOfType
|
||||||
} from '/imports/api/engine/loadCreatures.js';
|
} from '/imports/api/engine/loadCreatures.js';
|
||||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js';
|
||||||
|
|
||||||
export default function applyDamage(node, actionContext) {
|
export default function applyDamage(node, actionContext) {
|
||||||
applyNodeTriggers(node, 'before', actionContext);
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
@@ -128,7 +129,7 @@ export default function applyDamage(node, actionContext){
|
|||||||
// There are no targets, just log the result
|
// There are no targets, just log the result
|
||||||
logValue.push(`**${damage}** ${suffix}`);
|
logValue.push(`**${damage}** ${suffix}`);
|
||||||
}
|
}
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: logName,
|
name: logName,
|
||||||
value: logValue.join('\n'),
|
value: logValue.join('\n'),
|
||||||
inline: true,
|
inline: true,
|
||||||
@@ -147,21 +148,21 @@ function applyDamageMultipliers({target, damage, damageProp, logValue}){
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
multiplier.immunity &&
|
multiplier.immunity &&
|
||||||
some(multiplier.immunities, multiplierAppliesTo(damageProp))
|
some(multiplier.immunities, multiplierAppliesTo(damageProp, 'immunity'))
|
||||||
) {
|
) {
|
||||||
logValue.push(`Immune to ${damageTypeText}`);
|
logValue.push(`Immune to ${damageTypeText}`);
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
multiplier.resistance &&
|
multiplier.resistance &&
|
||||||
some(multiplier.resistances, multiplierAppliesTo(damageProp))
|
some(multiplier.resistances, multiplierAppliesTo(damageProp, 'resistance'))
|
||||||
) {
|
) {
|
||||||
logValue.push(`Resistant to ${damageTypeText}`);
|
logValue.push(`Resistant to ${damageTypeText}`);
|
||||||
damage = Math.floor(damage / 2);
|
damage = Math.floor(damage / 2);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
multiplier.vulnerability &&
|
multiplier.vulnerability &&
|
||||||
some(multiplier.vulnerabilities, multiplierAppliesTo(damageProp))
|
some(multiplier.vulnerabilities, multiplierAppliesTo(damageProp, 'vulnerability'))
|
||||||
) {
|
) {
|
||||||
logValue.push(`Vulnerable to ${damageTypeText}`);
|
logValue.push(`Vulnerable to ${damageTypeText}`);
|
||||||
damage = Math.floor(damage * 2);
|
damage = Math.floor(damage * 2);
|
||||||
@@ -170,14 +171,18 @@ function applyDamageMultipliers({target, damage, damageProp, logValue}){
|
|||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
function multiplierAppliesTo(damageProp){
|
function multiplierAppliesTo(damageProp, multiplierType) {
|
||||||
return multiplier => {
|
return multiplier => {
|
||||||
|
// Apply the default 'ignore x' tags
|
||||||
|
const effectiveTags = getEffectivePropTags(damageProp);
|
||||||
|
if (includes(effectiveTags, `ignore ${multiplierType}`)) return false;
|
||||||
|
|
||||||
const hasRequiredTags = difference(
|
const hasRequiredTags = difference(
|
||||||
multiplier.includeTags, damageProp.tags
|
multiplier.includeTags, effectiveTags
|
||||||
).length === 0;
|
).length === 0;
|
||||||
|
|
||||||
const hasNoExcludedTags = intersection(
|
const hasNoExcludedTags = intersection(
|
||||||
multiplier.excludeTags, damageProp.tags
|
multiplier.excludeTags, effectiveTags
|
||||||
).length === 0;
|
).length === 0;
|
||||||
|
|
||||||
return hasRequiredTags && hasNoExcludedTags;
|
return hasRequiredTags && hasNoExcludedTags;
|
||||||
@@ -219,6 +224,16 @@ function dealDamage({target, damageType, amount, actionContext}){
|
|||||||
if (damageType === 'healing') damageLeft = -totalDamage;
|
if (damageType === 'healing') damageLeft = -totalDamage;
|
||||||
healthBars.forEach(healthBar => {
|
healthBars.forEach(healthBar => {
|
||||||
if (damageLeft === 0) return;
|
if (damageLeft === 0) return;
|
||||||
|
// Replace the healthbar by the one in the action context if we can
|
||||||
|
// The damagePropertyWork function bashes the prop with the damage
|
||||||
|
// So we can use the new value in later action properties
|
||||||
|
if (healthBar.variableName) {
|
||||||
|
const targetHealthBar = target.variables[healthBar.variableName];
|
||||||
|
if (targetHealthBar?._id === healthBar._id) {
|
||||||
|
healthBar = targetHealthBar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do the damage
|
||||||
let damageAdded = damagePropertyWork({
|
let damageAdded = damagePropertyWork({
|
||||||
prop: healthBar,
|
prop: healthBar,
|
||||||
operation: 'increment',
|
operation: 'increment',
|
||||||
@@ -226,6 +241,14 @@ function dealDamage({target, damageType, amount, actionContext}){
|
|||||||
actionContext
|
actionContext
|
||||||
});
|
});
|
||||||
damageLeft -= damageAdded;
|
damageLeft -= damageAdded;
|
||||||
|
// Prevent overflow
|
||||||
|
if (
|
||||||
|
damageType === 'healing' ?
|
||||||
|
healthBar.healthBarNoHealingOverflow :
|
||||||
|
healthBar.healthBarNoDamageOverflow
|
||||||
|
) {
|
||||||
|
damageLeft = 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return totalDamage;
|
return totalDamage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js';
|
||||||
|
import applyProperty from '../applyProperty.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
|
export default function applyFolder(node, actionContext) {
|
||||||
|
// Apply triggers
|
||||||
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
|
// Apply children
|
||||||
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import rollDice from '/imports/parser/rollDice.js';
|
import rollDice from '/imports/parser/rollDice.js';
|
||||||
import recalculateCalculation from './shared/recalculateCalculation.js';
|
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||||
import applyProperty from '../applyProperty.js';
|
import applyProperty from '../applyProperty.js';
|
||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
import numberToSignedString from '/imports/api/utility/numberToSignedString.js';
|
||||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
import { applyUnresolvedEffects } from '/imports/api/engine/actions/doCheck.js';
|
||||||
|
|
||||||
export default function applySavingThrow(node, actionContext) {
|
export default function applySavingThrow(node, actionContext) {
|
||||||
applyNodeTriggers(node, 'before', actionContext);
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
@@ -20,7 +21,7 @@ export default function applySavingThrow(node, actionContext){
|
|||||||
});
|
});
|
||||||
return node.children.forEach(child => applyProperty(child, actionContext));
|
return node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
}
|
}
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: prop.name,
|
name: prop.name,
|
||||||
value: `DC **${dc}**`,
|
value: `DC **${dc}**`,
|
||||||
inline: true,
|
inline: true,
|
||||||
@@ -59,7 +60,11 @@ export default function applySavingThrow(node, actionContext){
|
|||||||
return applyChildren();
|
return applyChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
const rollModifierText = numberToSignedString(save.value, true);
|
let rollModifierText = numberToSignedString(save.value, true);
|
||||||
|
let rollModifier = save.value
|
||||||
|
const { effectBonus, effectString } = applyUnresolvedEffects(save, scope)
|
||||||
|
rollModifierText += effectString;
|
||||||
|
rollModifier += effectBonus;
|
||||||
|
|
||||||
let value, values, resultPrefix;
|
let value, values, resultPrefix;
|
||||||
if (save.advantage === 1) {
|
if (save.advantage === 1) {
|
||||||
@@ -86,7 +91,7 @@ export default function applySavingThrow(node, actionContext){
|
|||||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText}`
|
resultPrefix = `1d20 [ ${value} ] ${rollModifierText}`
|
||||||
}
|
}
|
||||||
scope['$saveDiceRoll'] = { value };
|
scope['$saveDiceRoll'] = { value };
|
||||||
const result = value + save.value || 0;
|
const result = value + rollModifier || 0;
|
||||||
scope['$saveRoll'] = { value: result };
|
scope['$saveRoll'] = { value: result };
|
||||||
const saveSuccess = result >= dc;
|
const saveSuccess = result >= dc;
|
||||||
if (saveSuccess) {
|
if (saveSuccess) {
|
||||||
@@ -94,7 +99,7 @@ export default function applySavingThrow(node, actionContext){
|
|||||||
} else {
|
} else {
|
||||||
scope['$saveFailed'] = { value: true };
|
scope['$saveFailed'] = { value: true };
|
||||||
}
|
}
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: saveSuccess ? 'Successful save' : 'Failed save',
|
name: saveSuccess ? 'Successful save' : 'Failed save',
|
||||||
value: resultPrefix + '\n**' + result + '**',
|
value: resultPrefix + '\n**' + result + '**',
|
||||||
inline: true,
|
inline: true,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import logErrors from './logErrors.js';
|
|||||||
export default function recalculateCalculation(calc, actionContext, context){
|
export default function recalculateCalculation(calc, actionContext, context){
|
||||||
if (!calc?.parseNode) return;
|
if (!calc?.parseNode) return;
|
||||||
calc._parseLevel = 'reduce';
|
calc._parseLevel = 'reduce';
|
||||||
applyEffectsToCalculationParseNode(calc, actionContext.log);
|
applyEffectsToCalculationParseNode(calc, actionContext);
|
||||||
evaluateCalculation(calc, actionContext.scope, context);
|
evaluateCalculation(calc, actionContext.scope, context);
|
||||||
logErrors(calc.errors, actionContext.log);
|
logErrors(calc.errors, actionContext);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function applyTrigger(trigger, prop, actionContext) {
|
|||||||
recalculateInlineCalculations(trigger.description, actionContext);
|
recalculateInlineCalculations(trigger.description, actionContext);
|
||||||
content.value = trigger.description.value;
|
content.value = trigger.description.value;
|
||||||
}
|
}
|
||||||
actionContext.addLog(content);
|
if(!trigger.silent) actionContext.addLog(content);
|
||||||
|
|
||||||
// Get all the trigger's properties and apply them
|
// Get all the trigger's properties and apply them
|
||||||
const properties = getPropertyDecendants(actionContext.creature._id, trigger._id);
|
const properties = getPropertyDecendants(actionContext.creature._id, trigger._id);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
|||||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||||
import { doActionWork } from '/imports/api/engine/actions/doAction.js';
|
import { doActionWork } from '/imports/api/engine/actions/doAction.js';
|
||||||
import { CreatureLogSchema } from '/imports/api/creature/log/CreatureLogs.js';
|
|
||||||
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
||||||
|
|
||||||
const doAction = new ValidatedMethod({
|
const doAction = new ValidatedMethod({
|
||||||
@@ -21,6 +20,10 @@ const doAction = new ValidatedMethod({
|
|||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
ritual: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
targetIds: {
|
targetIds: {
|
||||||
type: Array,
|
type: Array,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
@@ -42,7 +45,7 @@ const doAction = new ValidatedMethod({
|
|||||||
numRequests: 10,
|
numRequests: 10,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({ spellId, slotId, targetIds = [], scope = {} }) {
|
run({ spellId, slotId, ritual, targetIds = [], scope = {} }) {
|
||||||
// Get action context
|
// Get action context
|
||||||
let spell = CreatureProperties.findOne(spellId);
|
let spell = CreatureProperties.findOne(spellId);
|
||||||
const creatureId = spell.ancestors[0].id;
|
const creatureId = spell.ancestors[0].id;
|
||||||
@@ -65,9 +68,8 @@ const doAction = new ValidatedMethod({
|
|||||||
let slotLevel = spell.level || 0;
|
let slotLevel = spell.level || 0;
|
||||||
let slot;
|
let slot;
|
||||||
|
|
||||||
actionContext.scope['slotLevel'] = slotLevel;
|
// If a spell requires a slot, make sure a slot is spent
|
||||||
|
if (spell.level && !spell.castWithoutSpellSlots && !(ritual && spell.ritual)) {
|
||||||
if (slotId && !spell.castWithoutSpellSlots){
|
|
||||||
slot = CreatureProperties.findOne(slotId);
|
slot = CreatureProperties.findOne(slotId);
|
||||||
if (!slot) {
|
if (!slot) {
|
||||||
throw new Meteor.Error('No slot',
|
throw new Meteor.Error('No slot',
|
||||||
@@ -104,10 +106,18 @@ const doAction = new ValidatedMethod({
|
|||||||
name: `Casting using a level ${slotLevel} spell slot`
|
name: `Casting using a level ${slotLevel} spell slot`
|
||||||
});
|
});
|
||||||
} else if (slotLevel) {
|
} else if (slotLevel) {
|
||||||
|
if (ritual) {
|
||||||
|
actionContext.addLog({
|
||||||
|
name: `Ritual casting at level ${slotLevel}`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
actionContext.addLog({
|
actionContext.addLog({
|
||||||
name: `Casting at level ${slotLevel}`
|
name: `Casting at level ${slotLevel}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actionContext.scope['slotLevel'] = slotLevel;
|
||||||
|
|
||||||
// Do the action
|
// Do the action
|
||||||
doActionWork({
|
doActionWork({
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
|||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||||
import rollDice from '/imports/parser/rollDice.js';
|
import rollDice from '/imports/parser/rollDice.js';
|
||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
import numberToSignedString from '/imports/api/utility/numberToSignedString.js';
|
||||||
import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
||||||
|
import evaluateCalculation from '/imports/api/engine/computation/utility/evaluateCalculation.js';
|
||||||
|
|
||||||
const doCheck = new ValidatedMethod({
|
const doCheck = new ValidatedMethod({
|
||||||
name: 'creatureProperties.doCheck',
|
name: 'creatureProperties.doCheck',
|
||||||
@@ -27,6 +28,7 @@ const doCheck = new ValidatedMethod({
|
|||||||
const creatureId = prop.ancestors[0].id;
|
const creatureId = prop.ancestors[0].id;
|
||||||
const actionContext = new ActionContext(creatureId, [creatureId], this);
|
const actionContext = new ActionContext(creatureId, [creatureId], this);
|
||||||
Object.assign(actionContext.scope, scope);
|
Object.assign(actionContext.scope, scope);
|
||||||
|
actionContext.scope[`#${prop.type}`] = prop;
|
||||||
|
|
||||||
// Check permissions
|
// Check permissions
|
||||||
assertEditPermission(actionContext.creature, this.userId);
|
assertEditPermission(actionContext.creature, this.userId);
|
||||||
@@ -72,7 +74,11 @@ function rollCheck(prop, actionContext) {
|
|||||||
throw (`${prop.type} not supported for checks`);
|
throw (`${prop.type} not supported for checks`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rollModifierText = numberToSignedString(rollModifier, true);
|
let rollModifierText = numberToSignedString(rollModifier, true);
|
||||||
|
|
||||||
|
const { effectBonus, effectString } = applyUnresolvedEffects(prop, scope)
|
||||||
|
rollModifierText += effectString;
|
||||||
|
rollModifier += effectBonus;
|
||||||
|
|
||||||
let value, values, resultPrefix;
|
let value, values, resultPrefix;
|
||||||
if (scope['$checkAdvantage'] === 1) {
|
if (scope['$checkAdvantage'] === 1) {
|
||||||
@@ -101,8 +107,29 @@ function rollCheck(prop, actionContext) {
|
|||||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
||||||
}
|
}
|
||||||
const result = (value + rollModifier) || 0;
|
const result = (value + rollModifier) || 0;
|
||||||
|
scope['$checkDiceRoll'] = value;
|
||||||
|
scope['$checkRoll'] = result;
|
||||||
|
scope['$checkModifier'] = rollModifier;
|
||||||
actionContext.addLog({
|
actionContext.addLog({
|
||||||
name: logName,
|
name: logName,
|
||||||
value: `${resultPrefix} **${result}**`,
|
value: `${resultPrefix} **${result}**`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyUnresolvedEffects(prop, scope) {
|
||||||
|
let effectBonus = 0;
|
||||||
|
let effectString = '';
|
||||||
|
if (!prop.effects) {
|
||||||
|
return { effectBonus, effectString };
|
||||||
|
}
|
||||||
|
prop.effects.forEach(effect => {
|
||||||
|
if (!effect.amount?.parseNode) return;
|
||||||
|
if (effect.operation !== 'add') return;
|
||||||
|
effect.amount._parseLevel = 'reduce';
|
||||||
|
evaluateCalculation(effect.amount, scope);
|
||||||
|
if (typeof effect.amount?.value !== 'number') return;
|
||||||
|
effectBonus += effect.amount.value;
|
||||||
|
effectString += ` ${effect.amount.value < 0 ? '-' : '+'} [${effect.amount.calculation}] ${Math.abs(effect.amount.value)}`
|
||||||
|
});
|
||||||
|
return { effectBonus, effectString };
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
import { EJSON } from 'meteor/ejson';
|
import { EJSON } from 'meteor/ejson';
|
||||||
import createGraph from 'ngraph.graph';
|
import createGraph, { Graph } from 'ngraph.graph';
|
||||||
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js';
|
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js';
|
||||||
|
|
||||||
|
interface CreatureProperty {
|
||||||
|
_id: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default class CreatureComputation {
|
export default class CreatureComputation {
|
||||||
constructor(properties, creature, variables){
|
originalPropsById: object;
|
||||||
|
propsById: object;
|
||||||
|
propsWithTag: object;
|
||||||
|
scope: object;
|
||||||
|
props: Array<CreatureProperty>;
|
||||||
|
dependencyGraph: Graph;
|
||||||
|
errors: Array<object>;
|
||||||
|
creature: object;
|
||||||
|
variables: object;
|
||||||
|
|
||||||
|
constructor(properties: Array<CreatureProperty>, creature: object, variables: object) {
|
||||||
// Set up fields
|
// Set up fields
|
||||||
this.originalPropsById = {};
|
this.originalPropsById = {};
|
||||||
this.propsById = {};
|
this.propsById = {};
|
||||||
@@ -29,8 +29,8 @@ function childrenActive(prop){
|
|||||||
// Children of disabled properties are always inactive
|
// Children of disabled properties are always inactive
|
||||||
if (prop.disabled) return false;
|
if (prop.disabled) return false;
|
||||||
switch (prop.type){
|
switch (prop.type){
|
||||||
// Only equipped items have active children
|
// Only equipped items with non-zero quantity have active children
|
||||||
case 'item': return !!prop.equipped;
|
case 'item': return !!prop.equipped && prop.quantity !== 0;
|
||||||
// The children of actions, spells, and triggers are always inactive
|
// The children of actions, spells, and triggers are always inactive
|
||||||
case 'action': return false;
|
case 'action': return false;
|
||||||
case 'spell': return false;
|
case 'spell': return false;
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ export default function computeToggleDependencies(node, dependencyGraph){
|
|||||||
prop.enabled
|
prop.enabled
|
||||||
) return;
|
) return;
|
||||||
walkDown(node.children, child => {
|
walkDown(node.children, child => {
|
||||||
child.node._computationDetails.toggleAncestors.push(prop);
|
|
||||||
// The child nodes depend on the toggle condition compuation
|
// The child nodes depend on the toggle condition compuation
|
||||||
|
child.node._computationDetails.toggleAncestors.push(prop);
|
||||||
dependencyGraph.addLink(child.node._id, prop._id, 'toggle');
|
dependencyGraph.addLink(child.node._id, prop._id, 'toggle');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export default function linkCalculationDependencies(dependencyGraph, prop, {prop
|
|||||||
);
|
);
|
||||||
if (!ancestorProp) return;
|
if (!ancestorProp) return;
|
||||||
// Link the ancestor prop as a direct dependency
|
// Link the ancestor prop as a direct dependency
|
||||||
|
// TODO: we might be referencing a calculation sub-field, depend on that instead
|
||||||
dependencyGraph.addLink(
|
dependencyGraph.addLink(
|
||||||
calcNodeId, ancestorProp._id, 'ancestorReference'
|
calcNodeId, ancestorProp._id, 'ancestorReference'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const linkDependenciesByType = {
|
|||||||
effect: linkEffects,
|
effect: linkEffects,
|
||||||
proficiency: linkProficiencies,
|
proficiency: linkProficiencies,
|
||||||
roll: linkRoll,
|
roll: linkRoll,
|
||||||
|
pointBuy: linkPointBuy,
|
||||||
propertySlot: linkSlot,
|
propertySlot: linkSlot,
|
||||||
skill: linkSkill,
|
skill: linkSkill,
|
||||||
spell: linkAction,
|
spell: linkAction,
|
||||||
@@ -36,6 +37,9 @@ function dependOnCalc({dependencyGraph, prop, key}){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function linkAction(dependencyGraph, prop, { propsById }) {
|
function linkAction(dependencyGraph, prop, { propsById }) {
|
||||||
|
if (prop.variableName) {
|
||||||
|
dependencyGraph.addLink(prop.variableName, prop._id, 'eventDefinition');
|
||||||
|
}
|
||||||
// The action depends on its attack roll and uses calculations
|
// The action depends on its attack roll and uses calculations
|
||||||
dependOnCalc({ dependencyGraph, prop, key: 'attackRoll' });
|
dependOnCalc({ dependencyGraph, prop, key: 'attackRoll' });
|
||||||
dependOnCalc({ dependencyGraph, prop, key: 'uses' });
|
dependOnCalc({ dependencyGraph, prop, key: 'uses' });
|
||||||
@@ -106,6 +110,7 @@ function linkBuff(dependencyGraph, prop){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function linkClassLevel(dependencyGraph, prop) {
|
function linkClassLevel(dependencyGraph, prop) {
|
||||||
|
if (prop.inactive) return;
|
||||||
// The variableName of the prop depends on the prop
|
// The variableName of the prop depends on the prop
|
||||||
if (prop.variableName && prop.level) {
|
if (prop.variableName && prop.level) {
|
||||||
dependencyGraph.addLink(prop.variableName, prop._id, 'classLevel');
|
dependencyGraph.addLink(prop.variableName, prop._id, 'classLevel');
|
||||||
@@ -124,8 +129,13 @@ function linkDamage(dependencyGraph, prop){
|
|||||||
function linkEffects(dependencyGraph, prop, computation) {
|
function linkEffects(dependencyGraph, prop, computation) {
|
||||||
// The effect depends on its amount calculation
|
// The effect depends on its amount calculation
|
||||||
dependOnCalc({ dependencyGraph, prop, key: 'amount' });
|
dependOnCalc({ dependencyGraph, prop, key: 'amount' });
|
||||||
|
// Inactive effects aren't going to impact their targeted stats
|
||||||
|
if (prop.inactive) return;
|
||||||
// The stats depend on the effect
|
// The stats depend on the effect
|
||||||
if (prop.targetByTags){
|
if (prop.inactive) {
|
||||||
|
// Inactive effects apply to no stats
|
||||||
|
return;
|
||||||
|
} else if (prop.targetByTags) {
|
||||||
getEffectTagTargets(prop, computation).forEach(targetId => {
|
getEffectTagTargets(prop, computation).forEach(targetId => {
|
||||||
const targetProp = computation.propsById[targetId];
|
const targetProp = computation.propsById[targetId];
|
||||||
if (
|
if (
|
||||||
@@ -221,13 +231,14 @@ function linkRoll(dependencyGraph, prop){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function linkVariableName(dependencyGraph, prop) {
|
function linkVariableName(dependencyGraph, prop) {
|
||||||
// The variableName of the prop depends on the prop
|
// The variableName of the prop depends on the prop if the prop is active
|
||||||
if (prop.variableName){
|
if (prop.variableName && !prop.inactive) {
|
||||||
dependencyGraph.addLink(prop.variableName, prop._id, 'definition');
|
dependencyGraph.addLink(prop.variableName, prop._id, 'definition');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkDamageMultiplier(dependencyGraph, prop) {
|
function linkDamageMultiplier(dependencyGraph, prop) {
|
||||||
|
if (prop.inactive) return;
|
||||||
prop.damageTypes.forEach(damageType => {
|
prop.damageTypes.forEach(damageType => {
|
||||||
// Remove all non-letter characters from the damage name
|
// Remove all non-letter characters from the damage name
|
||||||
const damageName = damageType.replace(/[^a-z]/gi, '')
|
const damageName = damageType.replace(/[^a-z]/gi, '')
|
||||||
@@ -235,8 +246,31 @@ function linkDamageMultiplier(dependencyGraph, prop){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function linkPointBuy(dependencyGraph, prop) {
|
||||||
|
dependOnCalc({ dependencyGraph, prop, key: 'min' });
|
||||||
|
dependOnCalc({ dependencyGraph, prop, key: 'max' });
|
||||||
|
dependOnCalc({ dependencyGraph, prop, key: 'cost' });
|
||||||
|
dependOnCalc({ dependencyGraph, prop, key: 'total' });
|
||||||
|
prop.values?.forEach(row => {
|
||||||
|
// Wrap the document in a new object so we don't bash it unintentionally
|
||||||
|
const pointBuyRow = {
|
||||||
|
...row,
|
||||||
|
type: 'pointBuyRow',
|
||||||
|
tableName: prop.name,
|
||||||
|
tableId: prop._id,
|
||||||
|
}
|
||||||
|
dependencyGraph.addNode(row._id, pointBuyRow);
|
||||||
|
linkVariableName(dependencyGraph, pointBuyRow);
|
||||||
|
dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.min' });
|
||||||
|
dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.max' });
|
||||||
|
dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.cost' });
|
||||||
|
});
|
||||||
|
if (prop.inactive) return;
|
||||||
|
}
|
||||||
|
|
||||||
function linkProficiencies(dependencyGraph, prop) {
|
function linkProficiencies(dependencyGraph, prop) {
|
||||||
// The stats depend on the proficiency
|
// The stats depend on the proficiency
|
||||||
|
if (prop.inactive) return;
|
||||||
prop.stats.forEach(statName => {
|
prop.stats.forEach(statName => {
|
||||||
if (!statName) return;
|
if (!statName) return;
|
||||||
dependencyGraph.addLink(statName, prop._id, prop.type);
|
dependencyGraph.addLink(statName, prop._id, prop.type);
|
||||||
@@ -248,6 +282,10 @@ function linkSavingThrow(dependencyGraph, prop){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function linkSkill(dependencyGraph, prop) {
|
function linkSkill(dependencyGraph, prop) {
|
||||||
|
// Depends on base value
|
||||||
|
dependOnCalc({ dependencyGraph, prop, key: 'baseValue' });
|
||||||
|
// Link dependents
|
||||||
|
if (prop.inactive) return;
|
||||||
linkVariableName(dependencyGraph, prop);
|
linkVariableName(dependencyGraph, prop);
|
||||||
// The prop depends on the variable references as the ability
|
// The prop depends on the variable references as the ability
|
||||||
if (prop.ability) {
|
if (prop.ability) {
|
||||||
@@ -255,9 +293,6 @@ function linkSkill(dependencyGraph, prop){
|
|||||||
}
|
}
|
||||||
// Skills depend on the creature's proficiencyBonus
|
// Skills depend on the creature's proficiencyBonus
|
||||||
dependencyGraph.addLink(prop._id, 'proficiencyBonus', 'skillProficiencyBonus');
|
dependencyGraph.addLink(prop._id, 'proficiencyBonus', 'skillProficiencyBonus');
|
||||||
|
|
||||||
// Depends on base value
|
|
||||||
dependOnCalc({dependencyGraph, prop, key: 'baseValue'});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkSlot(dependencyGraph, prop) {
|
function linkSlot(dependencyGraph, prop) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import computeToggleDependencies from './buildComputation/computeToggleDependenc
|
|||||||
import linkCalculationDependencies from './buildComputation/linkCalculationDependencies.js';
|
import linkCalculationDependencies from './buildComputation/linkCalculationDependencies.js';
|
||||||
import linkTypeDependencies from './buildComputation/linkTypeDependencies.js';
|
import linkTypeDependencies from './buildComputation/linkTypeDependencies.js';
|
||||||
import computeSlotQuantityFilled from './buildComputation/computeSlotQuantityFilled.js';
|
import computeSlotQuantityFilled from './buildComputation/computeSlotQuantityFilled.js';
|
||||||
import CreatureComputation from './CreatureComputation.js';
|
import CreatureComputation from './CreatureComputation.ts';
|
||||||
import removeSchemaFields from './buildComputation/removeSchemaFields.js';
|
import removeSchemaFields from './buildComputation/removeSchemaFields.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,6 +89,10 @@ export function buildComputationFromProps(properties, creature, variables){
|
|||||||
// Walk the property trees computing things that need to be inherited
|
// Walk the property trees computing things that need to be inherited
|
||||||
walkDown(forest, node => {
|
walkDown(forest, node => {
|
||||||
computeInactiveStatus(node);
|
computeInactiveStatus(node);
|
||||||
|
});
|
||||||
|
// Inactive status must be complete for the whole tree before toggle deps
|
||||||
|
// are calculated
|
||||||
|
walkDown(forest, node => {
|
||||||
computeToggleDependencies(node, dependencyGraph);
|
computeToggleDependencies(node, dependencyGraph);
|
||||||
computeSlotQuantityFilled(node, dependencyGraph);
|
computeSlotQuantityFilled(node, dependencyGraph);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import _variable from './computeByType/computeVariable.js';
|
|||||||
import action from './computeByType/computeAction.js';
|
import action from './computeByType/computeAction.js';
|
||||||
import attribute from './computeByType/computeAttribute.js';
|
import attribute from './computeByType/computeAttribute.js';
|
||||||
import skill from './computeByType/computeSkill.js';
|
import skill from './computeByType/computeSkill.js';
|
||||||
|
import pointBuy from './computeByType/computePointBuy.js';
|
||||||
import propertySlot from './computeByType/computeSlot.js';
|
import propertySlot from './computeByType/computeSlot.js';
|
||||||
import container from './computeByType/computeContainer.js';
|
import container from './computeByType/computeContainer.js';
|
||||||
|
import spellList from './computeByType/computeSpellList.js';
|
||||||
import _calculation from './computeByType/computeCalculation.js';
|
import _calculation from './computeByType/computeCalculation.js';
|
||||||
|
|
||||||
export default Object.freeze({
|
export default Object.freeze({
|
||||||
@@ -13,6 +15,8 @@ export default Object.freeze({
|
|||||||
attribute,
|
attribute,
|
||||||
container,
|
container,
|
||||||
skill,
|
skill,
|
||||||
|
pointBuy,
|
||||||
propertySlot,
|
propertySlot,
|
||||||
spell: action,
|
spell: action,
|
||||||
|
spellList,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { has } from 'lodash';
|
||||||
|
import evaluateCalculation from '../../utility/evaluateCalculation.js';
|
||||||
|
|
||||||
|
export default function computePointBuy(computation, node) {
|
||||||
|
const prop = node.data;
|
||||||
|
const tableMin = prop.min?.value || null;
|
||||||
|
const tableMax = prop.max?.value || null;
|
||||||
|
prop.spent = 0;
|
||||||
|
prop.values?.forEach(row => {
|
||||||
|
// Clean up added properties
|
||||||
|
// delete row.tableId;
|
||||||
|
// delete row.tableName;
|
||||||
|
// delete row.type;
|
||||||
|
|
||||||
|
row.spent = 0;
|
||||||
|
if (row.value === undefined) return;
|
||||||
|
const min = has(row, 'min.value') ? row.min.value : tableMin;
|
||||||
|
const max = has(row, 'max.value') ? row.max.value : tableMax;
|
||||||
|
const costFunction = EJSON.clone(row.cost || prop.cost);
|
||||||
|
if (costFunction) costFunction.parseLevel = 'reduce';
|
||||||
|
|
||||||
|
// Check min and max
|
||||||
|
if (min !== null && row.value < min) {
|
||||||
|
row.value = min;
|
||||||
|
}
|
||||||
|
if (max !== null && row.value > max) {
|
||||||
|
row.value = max;
|
||||||
|
}
|
||||||
|
// Evaluate the cost function
|
||||||
|
if (!costFunction) return;
|
||||||
|
evaluateCalculation(costFunction, { ...computation.scope, value: row.value });
|
||||||
|
// Write calculation errors
|
||||||
|
costFunction.errors?.forEach(error => {
|
||||||
|
if (error?.message) {
|
||||||
|
row.errors = row.errors || [];
|
||||||
|
error.message = 'Cost calculation error.\n' + error.message;
|
||||||
|
row.errors.push(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Number.isFinite(costFunction.value)) {
|
||||||
|
row.spent = costFunction.value;
|
||||||
|
prop.spent += costFunction.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
prop.pointsLeft = (prop.total?.value || 0) - (prop.spent || 0);
|
||||||
|
if (prop.spent > prop.total?.value) {
|
||||||
|
prop.errors = prop.errors || [];
|
||||||
|
prop.errors.push({
|
||||||
|
type: 'pointBuyError',
|
||||||
|
message: 'Spent more than total points available',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
// by computeVariableAsSkill
|
// by computeVariableAsSkill
|
||||||
export default function computeSkill(computation, node){
|
export default function computeSkill(computation, node){
|
||||||
const prop = node.data;
|
const prop = node.data;
|
||||||
prop.proficiency = prop.baseProficiency;
|
prop.proficiency = prop.baseProficiency || 0;
|
||||||
let profBonus = computation.scope['proficiencyBonus']?.value || 0;
|
let profBonus = computation.scope['proficiencyBonus']?.value || 0;
|
||||||
// Multiply the proficiency bonus by the actual proficiency
|
// Multiply the proficiency bonus by the actual proficiency
|
||||||
if(prop.proficiency === 0.49){
|
if(prop.proficiency === 0.49){
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export default function computeSpelllist(computation, node) {
|
||||||
|
const prop = node.data;
|
||||||
|
|
||||||
|
const ability = computation.scope[prop.ability];
|
||||||
|
if (Number.isFinite(ability?.modifier)) {
|
||||||
|
prop.abilityMod = ability.modifier;
|
||||||
|
} else if (Number.isFinite(ability?.value)) {
|
||||||
|
prop.abilityMod = ability.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ function aggregateLinks(computation, node){
|
|||||||
aggregate.damageMultiplier(arg);
|
aggregate.damageMultiplier(arg);
|
||||||
aggregate.definition(arg);
|
aggregate.definition(arg);
|
||||||
aggregate.effect(arg);
|
aggregate.effect(arg);
|
||||||
|
aggregate.eventDefinition(arg);
|
||||||
aggregate.inventory(arg);
|
aggregate.inventory(arg);
|
||||||
aggregate.proficiency(arg);
|
aggregate.proficiency(arg);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,13 @@ export default function aggregateDefinition({node, linkedNode, link}){
|
|||||||
// get current defining prop
|
// get current defining prop
|
||||||
const definingProp = node.data.definingProp;
|
const definingProp = node.data.definingProp;
|
||||||
// Find the last defining prop
|
// Find the last defining prop
|
||||||
if (!definingProp || prop.order > definingProp.order){
|
if (
|
||||||
|
!definingProp ||
|
||||||
|
prop.type !== 'pointBuyRow' && (
|
||||||
|
definingProp.type === 'pointBuyRow' ||
|
||||||
|
prop.order > definingProp.order
|
||||||
|
)
|
||||||
|
) {
|
||||||
// override the current defining prop
|
// override the current defining prop
|
||||||
overrideProp(definingProp, node);
|
overrideProp(definingProp, node);
|
||||||
// set this prop as the new defining prop
|
// set this prop as the new defining prop
|
||||||
@@ -18,9 +24,32 @@ export default function aggregateDefinition({node, linkedNode, link}){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Aggregate the base value due to the defining properties
|
// Aggregate the base value due to the defining properties
|
||||||
const propBaseValue = prop.baseValue?.value;
|
let propBaseValue = prop.baseValue?.value;
|
||||||
|
// Point buy rows use prop.value instead of prop.baseValue
|
||||||
|
if (prop.type === 'pointBuyRow') {
|
||||||
|
propBaseValue = prop.value;
|
||||||
|
}
|
||||||
|
|
||||||
if (propBaseValue === undefined) return;
|
if (propBaseValue === undefined) return;
|
||||||
|
// Store a summary of the definition as a base value effect
|
||||||
|
node.data.effects = node.data.effects || [];
|
||||||
|
if (prop.type === 'pointBuyRow') {
|
||||||
|
node.data.effects.push({
|
||||||
|
_id: prop.tableId,
|
||||||
|
name: prop.tableName,
|
||||||
|
operation: 'base',
|
||||||
|
amount: { value: propBaseValue },
|
||||||
|
type: 'pointBuy',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
node.data.effects.push({
|
||||||
|
_id: prop._id,
|
||||||
|
name: prop.name,
|
||||||
|
operation: 'base',
|
||||||
|
amount: { value: propBaseValue },
|
||||||
|
type: prop.type,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (node.data.baseValue === undefined || propBaseValue > node.data.baseValue){
|
if (node.data.baseValue === undefined || propBaseValue > node.data.baseValue){
|
||||||
node.data.baseValue = propBaseValue;
|
node.data.baseValue = propBaseValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
export default function aggregateEffect({ node, linkedNode, link }) {
|
export default function aggregateEffect({ node, linkedNode, link }) {
|
||||||
if (link.data !== 'effect') return;
|
if (link.data !== 'effect') return;
|
||||||
// store the effect aggregator, its presence indicates that the variable is
|
// store the effect aggregator, its presence indicates that the variable is
|
||||||
@@ -19,20 +21,33 @@ export default function aggregateEffect({node, linkedNode, link}){
|
|||||||
|
|
||||||
// Store a summary of the effect itself
|
// Store a summary of the effect itself
|
||||||
node.data.effects = node.data.effects || [];
|
node.data.effects = node.data.effects || [];
|
||||||
|
// Store either just
|
||||||
|
let effectAmount;
|
||||||
|
if (!linkedNode.data.amount) {
|
||||||
|
effectAmount = undefined;
|
||||||
|
} else if (typeof linkedNode.data.amount.value === 'string') {
|
||||||
|
effectAmount = pick(linkedNode.data.amount, [
|
||||||
|
'calculation', 'parseNode', 'parseError', 'value'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
effectAmount = pick(linkedNode.data.amount, ['value']);
|
||||||
|
}
|
||||||
node.data.effects.push({
|
node.data.effects.push({
|
||||||
_id: linkedNode.data._id,
|
_id: linkedNode.data._id,
|
||||||
name: linkedNode.data.name,
|
name: linkedNode.data.name,
|
||||||
operation: linkedNode.data.operation,
|
operation: linkedNode.data.operation,
|
||||||
amount: linkedNode.data.amount && {value: linkedNode.data.amount.value},
|
amount: effectAmount,
|
||||||
|
type: linkedNode.data.type,
|
||||||
|
text: linkedNode.data.text,
|
||||||
// ancestors: linkedNode.data.ancestors,
|
// ancestors: linkedNode.data.ancestors,
|
||||||
});
|
});
|
||||||
|
|
||||||
// get a shorter reference to the aggregator document
|
// get a shorter reference to the aggregator document
|
||||||
const aggregator = node.data.effectAggregator;
|
const aggregator = node.data.effectAggregator;
|
||||||
// Get the result of the effect
|
// Get the result of the effect
|
||||||
const result = linkedNode.data.amount?.value;
|
let result = linkedNode.data.amount?.value;
|
||||||
// Skip aggregating if the result is not resolved completely
|
if (typeof result !== 'number') result = undefined;
|
||||||
if (typeof result === 'string') return;
|
|
||||||
// Aggregate the effect based on its operation
|
// Aggregate the effect based on its operation
|
||||||
switch (linkedNode.data.operation) {
|
switch (linkedNode.data.operation) {
|
||||||
case 'base':
|
case 'base':
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
export default function aggregateEventDefinition({ node, linkedNode, link }) {
|
||||||
|
// Look at all event definition links
|
||||||
|
if (link.data !== 'eventDefinition') return;
|
||||||
|
|
||||||
|
// Store which property is THE defining event and which are overridden
|
||||||
|
const prop = linkedNode.data;
|
||||||
|
// get current defining event
|
||||||
|
const definingEvent = node.data.definingEvent;
|
||||||
|
// Find the last defining event
|
||||||
|
if (
|
||||||
|
!definingEvent ||
|
||||||
|
prop.order > definingEvent.order
|
||||||
|
) {
|
||||||
|
// override the current defining prop
|
||||||
|
if (definingEvent) definingEvent.overridden = true;
|
||||||
|
// set this prop as the new defining prop
|
||||||
|
node.data.definingEvent = prop;
|
||||||
|
} else {
|
||||||
|
prop.overridden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import definition from './aggregateDefinition.js';
|
import definition from './aggregateDefinition.js';
|
||||||
import damageMultiplier from './aggregateDamageMultiplier.js';
|
import damageMultiplier from './aggregateDamageMultiplier.js';
|
||||||
import effect from './aggregateEffect.js';
|
import effect from './aggregateEffect.js';
|
||||||
|
import eventDefinition from './aggregateEventDefinition.js';
|
||||||
import proficiency from './aggregateProficiency.js';
|
import proficiency from './aggregateProficiency.js';
|
||||||
import classLevel from './aggregateClassLevel.js';
|
import classLevel from './aggregateClassLevel.js';
|
||||||
import inventory from './aggregateInventory.js';
|
import inventory from './aggregateInventory.js';
|
||||||
@@ -10,6 +11,7 @@ export default Object.freeze({
|
|||||||
damageMultiplier,
|
damageMultiplier,
|
||||||
definition,
|
definition,
|
||||||
effect,
|
effect,
|
||||||
|
eventDefinition,
|
||||||
inventory,
|
inventory,
|
||||||
proficiency,
|
proficiency,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ export default function computeVariableAsSkill(computation, node, prop){
|
|||||||
const aggregator = node.data.effectAggregator;
|
const aggregator = node.data.effectAggregator;
|
||||||
const aggregatorBase = aggregator?.base || 0;
|
const aggregatorBase = aggregator?.base || 0;
|
||||||
|
|
||||||
|
// Store effects
|
||||||
|
prop.effects = node.data.effects;
|
||||||
|
|
||||||
// If there is no aggregator, determine if the prop can hide, then exit
|
// If there is no aggregator, determine if the prop can hide, then exit
|
||||||
if (!aggregator){
|
if (!aggregator){
|
||||||
prop.hide = statBase === undefined &&
|
prop.hide = statBase === undefined &&
|
||||||
@@ -71,8 +74,6 @@ export default function computeVariableAsSkill(computation, node, prop){
|
|||||||
prop.fail = aggregator.fail;
|
prop.fail = aggregator.fail;
|
||||||
// Rollbonus
|
// Rollbonus
|
||||||
prop.rollBonuses = aggregator.rollBonus;
|
prop.rollBonuses = aggregator.rollBonus;
|
||||||
// Store effects
|
|
||||||
prop.effects = node.data.effects;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function aggregateAbilityEffects({computation, skillNode, abilityNode}){
|
function aggregateAbilityEffects({computation, skillNode, abilityNode}){
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function getAggregatorResult(node){
|
|||||||
if (aggregator.set !== undefined) {
|
if (aggregator.set !== undefined) {
|
||||||
result = aggregator.set;
|
result = aggregator.set;
|
||||||
}
|
}
|
||||||
if (!node.definingProp?.decimal && Number.isFinite(result)){
|
if (!node.data.definingProp?.decimal && Number.isFinite(result)){
|
||||||
result = Math.floor(result);
|
result = Math.floor(result);
|
||||||
} else if (Number.isFinite(result)){
|
} else if (Number.isFinite(result)){
|
||||||
result = stripFloatingPointOddities(result);
|
result = stripFloatingPointOddities(result);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default function evaluateToggles(computation, node){
|
|||||||
let toggles = prop._computationDetails?.toggleAncestors;
|
let toggles = prop._computationDetails?.toggleAncestors;
|
||||||
if (!toggles) return;
|
if (!toggles) return;
|
||||||
toggles.forEach(toggle => {
|
toggles.forEach(toggle => {
|
||||||
if (prop.inactive || !toggle.condition) return;
|
if (!toggle.condition) return;
|
||||||
if (!toggle.condition.value){
|
if (!toggle.condition.value){
|
||||||
prop.inactive = true;
|
prop.inactive = true;
|
||||||
prop.deactivatedByToggle = true;
|
prop.deactivatedByToggle = true;
|
||||||
|
|||||||
@@ -52,10 +52,21 @@ function compute(computation, node){
|
|||||||
function pushDependenciesToStack(nodeId, graph, stack, computation){
|
function pushDependenciesToStack(nodeId, graph, stack, computation){
|
||||||
graph.forEachLinkedNode(nodeId, linkedNode => {
|
graph.forEachLinkedNode(nodeId, linkedNode => {
|
||||||
if (linkedNode._visitedChildren && !linkedNode._visited) {
|
if (linkedNode._visitedChildren && !linkedNode._visited) {
|
||||||
const pather = path.nba(graph, {
|
// This is a dependency loop, find a path from the node to itself
|
||||||
oriented: true
|
// and store that path as a dependency loop error
|
||||||
});
|
const pather = path.nba(graph, { oriented: true });
|
||||||
const loop = pather.find(nodeId, nodeId);
|
let loop = [];
|
||||||
|
// Pather doesn't like going from a node to iteself, so find all the
|
||||||
|
// paths going from the next node back to the original node
|
||||||
|
// and return the shortest one
|
||||||
|
graph.forEachLinkedNode(nodeId, nextNode => {
|
||||||
|
const newLoop = pather.find(nextNode.id, nodeId);
|
||||||
|
if (!newLoop.length) return;
|
||||||
|
if (!loop.length || newLoop.length < loop.length - 1) {
|
||||||
|
loop = [linkedNode, ...newLoop];
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
if (loop.length) {
|
if (loop.length) {
|
||||||
computation.errors.push({
|
computation.errors.push({
|
||||||
type: 'dependencyLoop',
|
type: 'dependencyLoop',
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ export default function getEffectivePropTags(prop) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tags for some string properties
|
// Tags for some string properties
|
||||||
|
if (prop.variableName) tags.push(prop.variableName);
|
||||||
if (prop.damageType) tags.push(prop.damageType);
|
if (prop.damageType) tags.push(prop.damageType);
|
||||||
if (prop.skillType) tags.push(prop.skillType);
|
if (prop.skillType) tags.push(prop.skillType);
|
||||||
|
if (prop.actionType) tags.push(prop.actionType);
|
||||||
if (prop.attributeType) tags.push(prop.attributeType);
|
if (prop.attributeType) tags.push(prop.attributeType);
|
||||||
if (prop.reset) tags.push(prop.reset);
|
if (prop.reset) tags.push(prop.reset);
|
||||||
return tags;
|
return tags;
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ export function getSingleProperty(creatureId, propertyId) {
|
|||||||
'removed': {$ne: true},
|
'removed': {$ne: true},
|
||||||
}, {
|
}, {
|
||||||
sort: { order: 1 },
|
sort: { order: 1 },
|
||||||
fields: { icon: 0 },
|
|
||||||
});
|
});
|
||||||
// console.timeEnd(`Cache miss on creature properties: ${creatureId}`);
|
// console.timeEnd(`Cache miss on creature properties: ${creatureId}`);
|
||||||
return prop;
|
return prop;
|
||||||
@@ -65,7 +64,6 @@ export function getProperties(creatureId) {
|
|||||||
'removed': {$ne: true},
|
'removed': {$ne: true},
|
||||||
}, {
|
}, {
|
||||||
sort: { order: 1 },
|
sort: { order: 1 },
|
||||||
fields: { icon: 0 },
|
|
||||||
}).fetch();
|
}).fetch();
|
||||||
// console.timeEnd(`Cache miss on creature properties: ${creatureId}`);
|
// console.timeEnd(`Cache miss on creature properties: ${creatureId}`);
|
||||||
return props;
|
return props;
|
||||||
@@ -90,7 +88,6 @@ export function getPropertiesOfType(creatureId, propType) {
|
|||||||
'type': propType,
|
'type': propType,
|
||||||
}, {
|
}, {
|
||||||
sort: { order: 1 },
|
sort: { order: 1 },
|
||||||
fields: { icon: 0 },
|
|
||||||
}).fetch();
|
}).fetch();
|
||||||
// console.timeEnd(`Cache miss on creature properties: ${creatureId}`);
|
// console.timeEnd(`Cache miss on creature properties: ${creatureId}`);
|
||||||
return props;
|
return props;
|
||||||
@@ -100,14 +97,13 @@ export function getCreature(creatureId) {
|
|||||||
if (loadedCreatures.has(creatureId)) {
|
if (loadedCreatures.has(creatureId)) {
|
||||||
const loadedCreature = loadedCreatures.get(creatureId);
|
const loadedCreature = loadedCreatures.get(creatureId);
|
||||||
const creature = loadedCreature.creature;
|
const creature = loadedCreature.creature;
|
||||||
if (creature) return creature;
|
if (creature) {
|
||||||
|
const cloneCreature = EJSON.clone(creature);
|
||||||
|
return cloneCreature;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// console.time(`Cache miss on Creature: ${creatureId}`);
|
// console.time(`Cache miss on Creature: ${creatureId}`);
|
||||||
const creature = Creatures.findOne(creatureId, {
|
const creature = Creatures.findOne(creatureId);
|
||||||
denormalizedStats: 1,
|
|
||||||
variables: 1,
|
|
||||||
dirty: 1,
|
|
||||||
});
|
|
||||||
// console.timeEnd(`Cache miss on Creature: ${creatureId}`);
|
// console.timeEnd(`Cache miss on Creature: ${creatureId}`);
|
||||||
return creature;
|
return creature;
|
||||||
}
|
}
|
||||||
@@ -116,7 +112,10 @@ export function getVariables(creatureId) {
|
|||||||
if (loadedCreatures.has(creatureId)) {
|
if (loadedCreatures.has(creatureId)) {
|
||||||
const loadedCreature = loadedCreatures.get(creatureId);
|
const loadedCreature = loadedCreatures.get(creatureId);
|
||||||
const variables = loadedCreature.variables;
|
const variables = loadedCreature.variables;
|
||||||
if (variables) return variables;
|
if (variables) {
|
||||||
|
const cloneVarables = EJSON.clone(variables);
|
||||||
|
return cloneVarables;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// console.time(`Cache miss on variables: ${creatureId}`);
|
// console.time(`Cache miss on variables: ${creatureId}`);
|
||||||
const variables = CreatureVariables.findOne({_creatureId: creatureId});
|
const variables = CreatureVariables.findOne({_creatureId: creatureId});
|
||||||
@@ -149,6 +148,7 @@ export function getProperyAncestors(creatureId, propertyId) {
|
|||||||
// Fetch from database
|
// Fetch from database
|
||||||
return CreatureProperties.find({
|
return CreatureProperties.find({
|
||||||
_id: { $in: ancestorIds },
|
_id: { $in: ancestorIds },
|
||||||
|
removed: {$ne: true},
|
||||||
}, {
|
}, {
|
||||||
sort: { order: 1 },
|
sort: { order: 1 },
|
||||||
}).fetch();
|
}).fetch();
|
||||||
@@ -175,6 +175,8 @@ export function getPropertyDecendants(creatureId, propertyId) {
|
|||||||
return CreatureProperties.find({
|
return CreatureProperties.find({
|
||||||
'ancestors.id': propertyId,
|
'ancestors.id': propertyId,
|
||||||
removed: { $ne: true },
|
removed: { $ne: true },
|
||||||
|
}, {
|
||||||
|
sort: { order: 1 },
|
||||||
}).fetch();
|
}).fetch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,7 +201,6 @@ class LoadedCreature {
|
|||||||
removed: { $ne: true },
|
removed: { $ne: true },
|
||||||
}, {
|
}, {
|
||||||
sort: { order: 1 },
|
sort: { order: 1 },
|
||||||
fields: { icon: 0 },
|
|
||||||
}).observeChanges({
|
}).observeChanges({
|
||||||
added(id, fields) {
|
added(id, fields) {
|
||||||
fields._id = id;
|
fields._id = id;
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { createS3FilesCollection } from '/imports/api/files/s3FileStorage.js';
|
let createS3FilesCollection;
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
createS3FilesCollection = require('/imports/api/files/server/s3FileStorage.js').createS3FilesCollection
|
||||||
|
} else {
|
||||||
|
createS3FilesCollection = require('/imports/api/files/client/s3FileStorage.js').createS3FilesCollection
|
||||||
|
}
|
||||||
|
|
||||||
const UserImages = createS3FilesCollection({
|
const UserImages = createS3FilesCollection({
|
||||||
collectionName: 'userImages',
|
collectionName: 'userImages',
|
||||||
storagePath: Meteor.isDevelopment ? '/DiceCloud/userImages/' : 'assets/app/userImages',
|
storagePath: Meteor.isDevelopment ? '../../../../../fileStorage/userImages' : 'assets/app/userImages',
|
||||||
onBeforeUpload(file) {
|
onBeforeUpload(file) {
|
||||||
// Allow upload files under 10MB
|
// Allow upload files under 10MB
|
||||||
if (file.size > 10485760) {
|
if (file.size > 10485760) {
|
||||||
|
|||||||
24
app/imports/api/files/client/s3FileStorage.js
Normal file
24
app/imports/api/files/client/s3FileStorage.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// https://github.com/VeliovGroup/Meteor-Files/blob/master/docs/aws-s3-integration.md
|
||||||
|
import { FilesCollection } from 'meteor/ostrio:files';
|
||||||
|
|
||||||
|
const createS3FilesCollection = function ({
|
||||||
|
collectionName,
|
||||||
|
storagePath,
|
||||||
|
onBeforeUpload,
|
||||||
|
onAfterUpload,
|
||||||
|
debug,// = !Meteor.isProduction,
|
||||||
|
allowClientCode = false,
|
||||||
|
}) {
|
||||||
|
const collection = new FilesCollection({
|
||||||
|
collectionName,
|
||||||
|
storagePath,
|
||||||
|
onBeforeUpload,
|
||||||
|
onAfterUpload,
|
||||||
|
debug,
|
||||||
|
allowClientCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createS3FilesCollection };
|
||||||
@@ -29,7 +29,7 @@ let createS3FilesCollection;
|
|||||||
|
|
||||||
/* Check settings existence in `Meteor.settings` */
|
/* Check settings existence in `Meteor.settings` */
|
||||||
/* This is the best practice for app security */
|
/* This is the best practice for app security */
|
||||||
if (Meteor.isServer && Meteor.settings.useS3) {
|
if (Meteor.settings.useS3) {
|
||||||
// Create a new S3 object
|
// Create a new S3 object
|
||||||
const s3 = new S3({
|
const s3 = new S3({
|
||||||
accessKeyId: s3Conf.key,
|
accessKeyId: s3Conf.key,
|
||||||
@@ -48,7 +48,7 @@ if (Meteor.isServer && Meteor.settings.useS3) {
|
|||||||
storagePath,
|
storagePath,
|
||||||
onBeforeUpload,
|
onBeforeUpload,
|
||||||
onAfterUpload,
|
onAfterUpload,
|
||||||
debug = !Meteor.isProduction,
|
debug,// = !Meteor.isProduction,
|
||||||
allowClientCode = false,
|
allowClientCode = false,
|
||||||
}) {
|
}) {
|
||||||
const collection = new FilesCollection({
|
const collection = new FilesCollection({
|
||||||
@@ -222,7 +222,7 @@ if (Meteor.isServer && Meteor.settings.useS3) {
|
|||||||
storagePath,
|
storagePath,
|
||||||
onBeforeUpload,
|
onBeforeUpload,
|
||||||
onAfterUpload,
|
onAfterUpload,
|
||||||
debug = !Meteor.isProduction,
|
debug,// = !Meteor.isProduction,
|
||||||
allowClientCode = false,
|
allowClientCode = false,
|
||||||
}) {
|
}) {
|
||||||
const collection = new FilesCollection({
|
const collection = new FilesCollection({
|
||||||
@@ -234,13 +234,11 @@ if (Meteor.isServer && Meteor.settings.useS3) {
|
|||||||
allowClientCode,
|
allowClientCode,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
|
||||||
// Use the normal file system to read files
|
// Use the normal file system to read files
|
||||||
collection.readJSONFile = async function (file) {
|
collection.readJSONFile = async function (file) {
|
||||||
const fileString = await fsp.readFile(file.path, 'utf8');
|
const fileString = await fsp.readFile(file.path, 'utf8');
|
||||||
return JSON.parse(fileString);
|
return JSON.parse(fileString);
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
97
app/imports/api/library/methods/copyLibraryNodeTo.js
Normal file
97
app/imports/api/library/methods/copyLibraryNodeTo.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
import { RefSchema } from '/imports/api/parenting/ChildSchema.js';
|
||||||
|
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||||
|
import {
|
||||||
|
assertDocCopyPermission,
|
||||||
|
assertDocEditPermission
|
||||||
|
} from '/imports/api/sharing/sharingPermissions.js';
|
||||||
|
import {
|
||||||
|
setLineageOfDocs,
|
||||||
|
renewDocIds
|
||||||
|
} from '/imports/api/parenting/parenting.js';
|
||||||
|
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||||
|
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||||
|
|
||||||
|
var snackbar;
|
||||||
|
if (Meteor.isClient) {
|
||||||
|
snackbar = require(
|
||||||
|
'/imports/client/ui/components/snackbars/SnackbarQueue.js'
|
||||||
|
).snackbar
|
||||||
|
}
|
||||||
|
|
||||||
|
const DUPLICATE_CHILDREN_LIMIT = 500;
|
||||||
|
|
||||||
|
const copyLibraryNodeTo = new ValidatedMethod({
|
||||||
|
name: 'libraryNodes.copyTo',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
_id: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
type: RefSchema,
|
||||||
|
},
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 1,
|
||||||
|
timeInterval: 10000,
|
||||||
|
},
|
||||||
|
run({ _id, parent }) {
|
||||||
|
if (parent.collection !== 'libraryNodes' && parent.collection !== 'libraries') {
|
||||||
|
throw new Meteor.Error('Invalid destination',
|
||||||
|
'Library documents can only be copied to destinations inside other libraries'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const libraryNode = LibraryNodes.findOne(_id);
|
||||||
|
const parentDoc = fetchDocByRef(parent);
|
||||||
|
assertDocCopyPermission(libraryNode, this.userId);
|
||||||
|
assertDocEditPermission(parentDoc, this.userId);
|
||||||
|
|
||||||
|
let decendants = LibraryNodes.find({
|
||||||
|
'ancestors.id': _id,
|
||||||
|
removed: { $ne: true },
|
||||||
|
}, {
|
||||||
|
limit: DUPLICATE_CHILDREN_LIMIT + 1,
|
||||||
|
sort: { order: 1 },
|
||||||
|
}).fetch();
|
||||||
|
|
||||||
|
if (decendants.length > DUPLICATE_CHILDREN_LIMIT) {
|
||||||
|
decendants.pop();
|
||||||
|
if (Meteor.isClient) {
|
||||||
|
snackbar({
|
||||||
|
text: `Only the first ${DUPLICATE_CHILDREN_LIMIT} children were duplicated`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodes = [libraryNode, ...decendants];
|
||||||
|
|
||||||
|
const newAncestry = parentDoc.ancestors || [];
|
||||||
|
newAncestry.push(parent);
|
||||||
|
// re-map all the ancestors
|
||||||
|
setLineageOfDocs({
|
||||||
|
docArray: nodes,
|
||||||
|
newAncestry,
|
||||||
|
oldParent: libraryNode.parent,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Give the docs new IDs without breaking internal references
|
||||||
|
renewDocIds({ docArray: nodes });
|
||||||
|
|
||||||
|
// Order the root node
|
||||||
|
libraryNode.order = (parentDoc.order || 0) + 0.5;
|
||||||
|
|
||||||
|
LibraryNodes.batchInsert(nodes);
|
||||||
|
|
||||||
|
// Tree structure changed by inserts, reorder the tree
|
||||||
|
reorderDocs({
|
||||||
|
collection: LibraryNodes,
|
||||||
|
ancestorId: parent.collection === 'libraries' ? parent.id : parentDoc.ancestors[0].id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default copyLibraryNodeTo;
|
||||||
@@ -12,11 +12,11 @@ import { reorderDocs } from '/imports/api/parenting/order.js';
|
|||||||
var snackbar;
|
var snackbar;
|
||||||
if (Meteor.isClient) {
|
if (Meteor.isClient) {
|
||||||
snackbar = require(
|
snackbar = require(
|
||||||
'/imports/ui/components/snackbars/SnackbarQueue.js'
|
'/imports/client/ui/components/snackbars/SnackbarQueue.js'
|
||||||
).snackbar
|
).snackbar
|
||||||
}
|
}
|
||||||
|
|
||||||
const DUPLICATE_CHILDREN_LIMIT = 50;
|
const DUPLICATE_CHILDREN_LIMIT = 500;
|
||||||
|
|
||||||
const duplicateLibraryNode = new ValidatedMethod({
|
const duplicateLibraryNode = new ValidatedMethod({
|
||||||
name: 'libraryNodes.duplicate',
|
name: 'libraryNodes.duplicate',
|
||||||
@@ -28,7 +28,7 @@ const duplicateLibraryNode = new ValidatedMethod({
|
|||||||
}).validator(),
|
}).validator(),
|
||||||
mixins: [RateLimiterMixin],
|
mixins: [RateLimiterMixin],
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
numRequests: 5,
|
numRequests: 1,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({ _id }) {
|
run({ _id }) {
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
import '/imports/api/library/methods/copyLibraryNodeTo.js';
|
||||||
import '/imports/api/library/methods/duplicateLibraryNode.js';
|
import '/imports/api/library/methods/duplicateLibraryNode.js';
|
||||||
import '/imports/api/library/methods/updateReferenceNode.js';
|
import '/imports/api/library/methods/updateReferenceNode.js';
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
|
||||||
let SoftRemovableSchema = new SimpleSchema({
|
let SoftRemovableSchema = new SimpleSchema({
|
||||||
"removed": {
|
'removed': {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
"removedAt": {
|
'removedAt': {
|
||||||
type: Date,
|
type: Date,
|
||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
"removedWith": {
|
'removedWith': {
|
||||||
optional: true,
|
optional: true,
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema';
|
|||||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||||
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
|
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
|
||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||||
|
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Actions are things a character can do
|
* Actions are things a character can do
|
||||||
@@ -24,9 +25,17 @@ let ActionSchema = createPropertySchema({
|
|||||||
// long actions take longer than 1 round to cast
|
// long actions take longer than 1 round to cast
|
||||||
actionType: {
|
actionType: {
|
||||||
type: String,
|
type: String,
|
||||||
allowedValues: ['action', 'bonus', 'attack', 'reaction', 'free', 'long'],
|
allowedValues: ['action', 'bonus', 'attack', 'reaction', 'free', 'long', 'event'],
|
||||||
defaultValue: 'action',
|
defaultValue: 'action',
|
||||||
},
|
},
|
||||||
|
// If the action type is an event, what is the variable name of that event?
|
||||||
|
variableName: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
regEx: VARIABLE_NAME_REGEX,
|
||||||
|
min: 2,
|
||||||
|
max: STORAGE_LIMITS.variableName,
|
||||||
|
},
|
||||||
// Who is the action directed at
|
// Who is the action directed at
|
||||||
target: {
|
target: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -56,8 +65,10 @@ let ActionSchema = createPropertySchema({
|
|||||||
// How this action's uses are reset automatically
|
// How this action's uses are reset automatically
|
||||||
reset: {
|
reset: {
|
||||||
type: String,
|
type: String,
|
||||||
allowedValues: ['longRest', 'shortRest'],
|
|
||||||
optional: true,
|
optional: true,
|
||||||
|
regEx: VARIABLE_NAME_REGEX,
|
||||||
|
min: 2,
|
||||||
|
max: STORAGE_LIMITS.variableName,
|
||||||
},
|
},
|
||||||
// Resources
|
// Resources
|
||||||
resources: {
|
resources: {
|
||||||
@@ -114,6 +125,11 @@ let ActionSchema = createPropertySchema({
|
|||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedOnlyActionSchema = createPropertySchema({
|
const ComputedOnlyActionSchema = createPropertySchema({
|
||||||
@@ -146,6 +162,12 @@ const ComputedOnlyActionSchema = createPropertySchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
removeBeforeCompute: true,
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
|
// Denormalised tag if event is overridden by one with the same variable name
|
||||||
|
overridden: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
// Resources
|
// Resources
|
||||||
resources: {
|
resources: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ const AdjustmentSchema = createPropertySchema({
|
|||||||
allowedValues: ['set', 'increment'],
|
allowedValues: ['set', 'increment'],
|
||||||
defaultValue: 'increment',
|
defaultValue: 'increment',
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedOnlyAdjustmentSchema = createPropertySchema({
|
const ComputedOnlyAdjustmentSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ let AttributeSchema = createPropertySchema({
|
|||||||
'stat', // Speed, Armor Class
|
'stat', // Speed, Armor Class
|
||||||
'modifier', // Proficiency Bonus, displayed as +x
|
'modifier', // Proficiency Bonus, displayed as +x
|
||||||
'hitDice', // d12 hit dice
|
'hitDice', // d12 hit dice
|
||||||
'healthBar', // Hitpoints, Temporary Hitpoints, can take damage
|
'healthBar', // Hitpoints, Temporary Hitpoints
|
||||||
'bar', // Displayed as a health bar, can't take damage
|
|
||||||
'resource', // Rages, sorcery points
|
'resource', // Rages, sorcery points
|
||||||
'spellSlot', // Level 1, 2, 3... spell slots
|
'spellSlot', // Level 1, 2, 3... spell slots
|
||||||
'utility', // Aren't displayed, Jump height, Carry capacity
|
'utility', // Aren't displayed, Jump height, Carry capacity
|
||||||
@@ -69,6 +68,16 @@ let AttributeSchema = createPropertySchema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Control how the health bar handles overflow
|
||||||
|
healthBarNoDamageOverflow: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
healthBarNoHealingOverflow: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
// Control when the health bar takes damage or healing
|
||||||
healthBarDamageOrder: {
|
healthBarDamageOrder: {
|
||||||
type: SimpleSchema.Integer,
|
type: SimpleSchema.Integer,
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -107,11 +116,21 @@ let AttributeSchema = createPropertySchema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
hideWhenTotalZero: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
hideWhenValueZero: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
// Automatically zero the adjustment on these conditions
|
// Automatically zero the adjustment on these conditions
|
||||||
reset: {
|
reset: {
|
||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
allowedValues: ['shortRest', 'longRest'],
|
regEx: VARIABLE_NAME_REGEX,
|
||||||
|
min: 2,
|
||||||
|
max: STORAGE_LIMITS.variableName,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -176,6 +195,7 @@ let ComputedOnlyAttributeSchema = createPropertySchema({
|
|||||||
effects: {
|
effects: {
|
||||||
type: Array,
|
type: Array,
|
||||||
optional: true,
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
'effects.$': {
|
'effects.$': {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ let BranchSchema = createPropertySchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
parseLevel: 'compile',
|
parseLevel: 'compile',
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let ComputedOnlyBranchSchema = createPropertySchema({
|
let ComputedOnlyBranchSchema = createPropertySchema({
|
||||||
|
|||||||
84
app/imports/api/properties/BuffRemovers.js
Normal file
84
app/imports/api/properties/BuffRemovers.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||||
|
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||||
|
|
||||||
|
let BuffRemoverSchema = createPropertySchema({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
max: STORAGE_LIMITS.name,
|
||||||
|
},
|
||||||
|
// This will remove just the nearest ancestor buff
|
||||||
|
targetParentBuff: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
// The following only applies when not targeting the parent buff
|
||||||
|
// Which character to remove buffs from
|
||||||
|
target: {
|
||||||
|
type: String,
|
||||||
|
allowedValues: [
|
||||||
|
'self',
|
||||||
|
'target',
|
||||||
|
],
|
||||||
|
defaultValue: 'target',
|
||||||
|
},
|
||||||
|
// remove 1 or remove all
|
||||||
|
removeAll: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
defaultValue: true,
|
||||||
|
},
|
||||||
|
// Buffs to remove based on tags:
|
||||||
|
targetTags: {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
maxCount: STORAGE_LIMITS.tagCount,
|
||||||
|
},
|
||||||
|
'targetTags.$': {
|
||||||
|
type: String,
|
||||||
|
max: STORAGE_LIMITS.tagLength,
|
||||||
|
},
|
||||||
|
extraTags: {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
maxCount: STORAGE_LIMITS.extraTagsCount,
|
||||||
|
},
|
||||||
|
'extraTags.$': {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
'extraTags.$._id': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
autoValue() {
|
||||||
|
if (!this.isSet) return Random.id();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'extraTags.$.operation': {
|
||||||
|
type: String,
|
||||||
|
allowedValues: ['OR', 'NOT'],
|
||||||
|
defaultValue: 'OR',
|
||||||
|
},
|
||||||
|
'extraTags.$.tags': {
|
||||||
|
type: Array,
|
||||||
|
defaultValue: [],
|
||||||
|
maxCount: STORAGE_LIMITS.tagCount,
|
||||||
|
},
|
||||||
|
'extraTags.$.tags.$': {
|
||||||
|
type: String,
|
||||||
|
max: STORAGE_LIMITS.tagLength,
|
||||||
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let ComputedOnlyBuffRemoverSchema = createPropertySchema({});
|
||||||
|
|
||||||
|
const ComputedBuffRemoverSchema = new SimpleSchema()
|
||||||
|
.extend(BuffRemoverSchema)
|
||||||
|
.extend(ComputedOnlyBuffRemoverSchema);
|
||||||
|
|
||||||
|
export { BuffRemoverSchema, ComputedOnlyBuffRemoverSchema, ComputedBuffRemoverSchema };
|
||||||
@@ -12,6 +12,10 @@ let BuffSchema = createPropertySchema({
|
|||||||
type: 'inlineCalculationFieldToCompute',
|
type: 'inlineCalculationFieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
hideRemoveButton: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
// How many rounds this buff lasts
|
// How many rounds this buff lasts
|
||||||
duration: {
|
duration: {
|
||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
@@ -25,6 +29,16 @@ let BuffSchema = createPropertySchema({
|
|||||||
],
|
],
|
||||||
defaultValue: 'target',
|
defaultValue: 'target',
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
// Prevent the children from being crystalized
|
||||||
|
skipCrystalization: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let ComputedOnlyBuffSchema = createPropertySchema({
|
let ComputedOnlyBuffSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ const DamageSchema = createPropertySchema({
|
|||||||
defaultValue: 'slashing',
|
defaultValue: 'slashing',
|
||||||
regEx: VARIABLE_NAME_REGEX,
|
regEx: VARIABLE_NAME_REGEX,
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedOnlyDamageSchema = createPropertySchema({
|
const ComputedOnlyDamageSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -1,15 +1,38 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
|
||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||||
|
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||||
|
|
||||||
// Folders organize a character sheet into a tree, particularly to group things
|
// Folders organize a character sheet into a tree, particularly to group things
|
||||||
// like 'race' and 'background'
|
// like 'race' and 'background'
|
||||||
let FolderSchema = new SimpleSchema({
|
let FolderSchema = new createPropertySchema({
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
max: STORAGE_LIMITS.name,
|
max: STORAGE_LIMITS.name,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
groupStats: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
hideStatsGroup: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
tab: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
allowedValues: [
|
||||||
|
'stats', 'features', 'actions', 'spells', 'inventory', 'journal', 'build'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
location: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
allowedValues: [
|
||||||
|
'start', 'events', 'stats', 'skills', 'proficiencies', 'end'
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedOnlyFolderSchema = new SimpleSchema({});
|
const ComputedOnlyFolderSchema = new createPropertySchema({});
|
||||||
|
|
||||||
export { FolderSchema, ComputedOnlyFolderSchema };
|
export { FolderSchema, ComputedOnlyFolderSchema };
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema';
|
|||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||||
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||||
|
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PointBuys are reason-value attached to skills and abilities
|
* PointBuys are reason-value attached to skills and abilities
|
||||||
@@ -13,13 +14,6 @@ let PointBuySchema = createPropertySchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
max: STORAGE_LIMITS.name,
|
max: STORAGE_LIMITS.name,
|
||||||
},
|
},
|
||||||
variableName: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
regEx: VARIABLE_NAME_REGEX,
|
|
||||||
min: 2,
|
|
||||||
max: STORAGE_LIMITS.variableName,
|
|
||||||
},
|
|
||||||
ignored: {
|
ignored: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -27,10 +21,18 @@ let PointBuySchema = createPropertySchema({
|
|||||||
'values': {
|
'values': {
|
||||||
type: Array,
|
type: Array,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
|
maxCount: STORAGE_LIMITS.pointBuyRowsCount,
|
||||||
},
|
},
|
||||||
'values.$': {
|
'values.$': {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
|
'values.$._id': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
autoValue(){
|
||||||
|
if (!this.isSet) return Random.id();
|
||||||
|
}
|
||||||
|
},
|
||||||
'values.$.name': {
|
'values.$.name': {
|
||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -47,6 +49,18 @@ let PointBuySchema = createPropertySchema({
|
|||||||
type: Number,
|
type: Number,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
'values.$.min': {
|
||||||
|
type: 'fieldToCompute',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'values.$.max': {
|
||||||
|
type: 'fieldToCompute',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'values.$.cost': {
|
||||||
|
type: 'fieldToCompute',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
min: {
|
min: {
|
||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -62,6 +76,7 @@ let PointBuySchema = createPropertySchema({
|
|||||||
cost: {
|
cost: {
|
||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
|
parseLevel: 'compile',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,11 +89,46 @@ const ComputedOnlyPointBuySchema = createPropertySchema({
|
|||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
total: {
|
cost: {
|
||||||
|
type: 'computedOnlyField',
|
||||||
|
optional: true,
|
||||||
|
parseLevel: 'compile',
|
||||||
|
},
|
||||||
|
'values': {
|
||||||
|
type: Array,
|
||||||
|
defaultValue: [],
|
||||||
|
maxCount: STORAGE_LIMITS.pointBuyRowsCount,
|
||||||
|
},
|
||||||
|
'values.$': {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
'values.$.min': {
|
||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
cost: {
|
'values.$.max': {
|
||||||
|
type: 'computedOnlyField',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'values.$.cost': {
|
||||||
|
type: 'computedOnlyField',
|
||||||
|
optional: true,
|
||||||
|
parseLevel: 'compile',
|
||||||
|
},
|
||||||
|
'values.$.spent': {
|
||||||
|
type: Number,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
'values.$.errors': {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
'values.$.errors.$': {
|
||||||
|
type: ErrorSchema,
|
||||||
|
},
|
||||||
|
total: {
|
||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
@@ -87,6 +137,19 @@ const ComputedOnlyPointBuySchema = createPropertySchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
removeBeforeCompute: true,
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
|
pointsLeft: {
|
||||||
|
type: Number,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
'errors.$': {
|
||||||
|
type: ErrorSchema,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedPointBuySchema = new SimpleSchema()
|
const ComputedPointBuySchema = new SimpleSchema()
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ let SavingThrowSchema = createPropertySchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
max: STORAGE_LIMITS.variableName,
|
max: STORAGE_LIMITS.variableName,
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedOnlySavingThrowSchema = createPropertySchema({
|
const ComputedOnlySavingThrowSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ let ComputedOnlySkillSchema = createPropertySchema({
|
|||||||
effects: {
|
effects: {
|
||||||
type: Array,
|
type: Array,
|
||||||
optional: true,
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
'effects.$': {
|
'effects.$': {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ let SpellListSchema = createPropertySchema({
|
|||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// The variable name of the ability this spell relies on
|
||||||
|
ability: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
max: STORAGE_LIMITS.variableName,
|
||||||
|
},
|
||||||
// Calculation of The attack roll bonus used by spell attacks in this list
|
// Calculation of The attack roll bonus used by spell attacks in this list
|
||||||
attackRollBonus: {
|
attackRollBonus: {
|
||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
@@ -38,6 +44,12 @@ const ComputedOnlySpellListSchema = createPropertySchema({
|
|||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Computed value determined by the ability
|
||||||
|
abilityMod: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
attackRollBonus: {
|
attackRollBonus: {
|
||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
optional: true,
|
optional: true,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const actionPropertyTypeOptions = {
|
|||||||
adjustment: 'Attribute damage',
|
adjustment: 'Attribute damage',
|
||||||
branch: 'Branch',
|
branch: 'Branch',
|
||||||
buff: 'Buff',
|
buff: 'Buff',
|
||||||
|
buffRemover: 'Buff Removed',
|
||||||
damage: 'Damage',
|
damage: 'Damage',
|
||||||
note: 'Note',
|
note: 'Note',
|
||||||
roll: 'Roll',
|
roll: 'Roll',
|
||||||
@@ -108,6 +109,11 @@ let TriggerSchema = createPropertySchema({
|
|||||||
type: String,
|
type: String,
|
||||||
max: STORAGE_LIMITS.tagLength,
|
max: STORAGE_LIMITS.tagLength,
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedOnlyTriggerSchema = createPropertySchema({
|
const ComputedOnlyTriggerSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ComputedOnlyActionSchema } from '/imports/api/properties/Actions.js';
|
|||||||
import { ComputedOnlyAdjustmentSchema } from '/imports/api/properties/Adjustments.js';
|
import { ComputedOnlyAdjustmentSchema } from '/imports/api/properties/Adjustments.js';
|
||||||
import { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.js';
|
import { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.js';
|
||||||
import { ComputedOnlyBuffSchema } from '/imports/api/properties/Buffs.js';
|
import { ComputedOnlyBuffSchema } from '/imports/api/properties/Buffs.js';
|
||||||
|
import { ComputedOnlyBuffRemoverSchema } from '/imports/api/properties/BuffRemovers.js';
|
||||||
import { ComputedOnlyBranchSchema } from '/imports/api/properties/Branches.js';
|
import { ComputedOnlyBranchSchema } from '/imports/api/properties/Branches.js';
|
||||||
import { ComputedOnlyClassSchema } from '/imports/api/properties/Classes.js';
|
import { ComputedOnlyClassSchema } from '/imports/api/properties/Classes.js';
|
||||||
import { ComputedOnlyClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
import { ComputedOnlyClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
||||||
@@ -15,6 +16,7 @@ import { ComputedOnlyFeatureSchema } from '/imports/api/properties/Features.js';
|
|||||||
import { ComputedOnlyFolderSchema } from '/imports/api/properties/Folders.js';
|
import { ComputedOnlyFolderSchema } from '/imports/api/properties/Folders.js';
|
||||||
import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js';
|
import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js';
|
||||||
import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js';
|
import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js';
|
||||||
|
import { ComputedOnlyPointBuySchema } from '/imports/api/properties/PointBuys.js';
|
||||||
import { ComputedOnlyProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
import { ComputedOnlyProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||||
import { ComputedOnlyReferenceSchema } from '/imports/api/properties/References.js';
|
import { ComputedOnlyReferenceSchema } from '/imports/api/properties/References.js';
|
||||||
import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js';
|
import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js';
|
||||||
@@ -32,6 +34,7 @@ const propertySchemasIndex = {
|
|||||||
adjustment: ComputedOnlyAdjustmentSchema,
|
adjustment: ComputedOnlyAdjustmentSchema,
|
||||||
attribute: ComputedOnlyAttributeSchema,
|
attribute: ComputedOnlyAttributeSchema,
|
||||||
buff: ComputedOnlyBuffSchema,
|
buff: ComputedOnlyBuffSchema,
|
||||||
|
buffRemover: ComputedOnlyBuffRemoverSchema,
|
||||||
branch: ComputedOnlyBranchSchema,
|
branch: ComputedOnlyBranchSchema,
|
||||||
class: ComputedOnlyClassSchema,
|
class: ComputedOnlyClassSchema,
|
||||||
classLevel: ComputedOnlyClassLevelSchema,
|
classLevel: ComputedOnlyClassLevelSchema,
|
||||||
@@ -44,6 +47,7 @@ const propertySchemasIndex = {
|
|||||||
folder: ComputedOnlyFolderSchema,
|
folder: ComputedOnlyFolderSchema,
|
||||||
item: ComputedOnlyItemSchema,
|
item: ComputedOnlyItemSchema,
|
||||||
note: ComputedOnlyNoteSchema,
|
note: ComputedOnlyNoteSchema,
|
||||||
|
pointBuy: ComputedOnlyPointBuySchema,
|
||||||
proficiency: ComputedOnlyProficiencySchema,
|
proficiency: ComputedOnlyProficiencySchema,
|
||||||
propertySlot: ComputedOnlySlotSchema,
|
propertySlot: ComputedOnlySlotSchema,
|
||||||
reference: ComputedOnlyReferenceSchema,
|
reference: ComputedOnlyReferenceSchema,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ComputedActionSchema } from '/imports/api/properties/Actions.js';
|
|||||||
import { ComputedAdjustmentSchema } from '/imports/api/properties/Adjustments.js';
|
import { ComputedAdjustmentSchema } from '/imports/api/properties/Adjustments.js';
|
||||||
import { ComputedAttributeSchema } from '/imports/api/properties/Attributes.js';
|
import { ComputedAttributeSchema } from '/imports/api/properties/Attributes.js';
|
||||||
import { ComputedBuffSchema } from '/imports/api/properties/Buffs.js';
|
import { ComputedBuffSchema } from '/imports/api/properties/Buffs.js';
|
||||||
|
import { ComputedBuffRemoverSchema } from '/imports/api/properties/BuffRemovers.js';
|
||||||
import { ComputedBranchSchema } from '/imports/api/properties/Branches.js';
|
import { ComputedBranchSchema } from '/imports/api/properties/Branches.js';
|
||||||
import { ComputedClassSchema } from '/imports/api/properties/Classes.js';
|
import { ComputedClassSchema } from '/imports/api/properties/Classes.js';
|
||||||
import { ComputedClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
import { ComputedClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
||||||
@@ -15,6 +16,7 @@ import { ComputedFeatureSchema } from '/imports/api/properties/Features.js';
|
|||||||
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||||
import { ComputedItemSchema } from '/imports/api/properties/Items.js';
|
import { ComputedItemSchema } from '/imports/api/properties/Items.js';
|
||||||
import { ComputedNoteSchema } from '/imports/api/properties/Notes.js';
|
import { ComputedNoteSchema } from '/imports/api/properties/Notes.js';
|
||||||
|
import { ComputedPointBuySchema } from '/imports/api/properties/PointBuys.js';
|
||||||
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||||
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
||||||
import { ComputedRollSchema } from '/imports/api/properties/Rolls.js';
|
import { ComputedRollSchema } from '/imports/api/properties/Rolls.js';
|
||||||
@@ -32,6 +34,7 @@ const propertySchemasIndex = {
|
|||||||
adjustment: ComputedAdjustmentSchema,
|
adjustment: ComputedAdjustmentSchema,
|
||||||
attribute: ComputedAttributeSchema,
|
attribute: ComputedAttributeSchema,
|
||||||
buff: ComputedBuffSchema,
|
buff: ComputedBuffSchema,
|
||||||
|
buffRemover: ComputedBuffRemoverSchema,
|
||||||
branch: ComputedBranchSchema,
|
branch: ComputedBranchSchema,
|
||||||
class: ComputedClassSchema,
|
class: ComputedClassSchema,
|
||||||
classLevel: ComputedClassLevelSchema,
|
classLevel: ComputedClassLevelSchema,
|
||||||
@@ -42,6 +45,7 @@ const propertySchemasIndex = {
|
|||||||
feature: ComputedFeatureSchema,
|
feature: ComputedFeatureSchema,
|
||||||
folder: FolderSchema,
|
folder: FolderSchema,
|
||||||
note: ComputedNoteSchema,
|
note: ComputedNoteSchema,
|
||||||
|
pointBuy: ComputedPointBuySchema,
|
||||||
proficiency: ProficiencySchema,
|
proficiency: ProficiencySchema,
|
||||||
propertySlot: ComputedSlotSchema,
|
propertySlot: ComputedSlotSchema,
|
||||||
reference: ReferenceSchema,
|
reference: ReferenceSchema,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ActionSchema } from '/imports/api/properties/Actions.js';
|
|||||||
import { AdjustmentSchema } from '/imports/api/properties/Adjustments.js';
|
import { AdjustmentSchema } from '/imports/api/properties/Adjustments.js';
|
||||||
import { AttributeSchema } from '/imports/api/properties/Attributes.js';
|
import { AttributeSchema } from '/imports/api/properties/Attributes.js';
|
||||||
import { BuffSchema } from '/imports/api/properties/Buffs.js';
|
import { BuffSchema } from '/imports/api/properties/Buffs.js';
|
||||||
|
import { BuffRemoverSchema } from '/imports/api/properties/BuffRemovers.js';
|
||||||
import { BranchSchema } from '/imports/api/properties/Branches.js';
|
import { BranchSchema } from '/imports/api/properties/Branches.js';
|
||||||
import { ClassSchema } from '/imports/api/properties/Classes.js';
|
import { ClassSchema } from '/imports/api/properties/Classes.js';
|
||||||
import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
||||||
@@ -13,6 +14,7 @@ import { EffectSchema } from '/imports/api/properties/Effects.js';
|
|||||||
import { FeatureSchema } from '/imports/api/properties/Features.js';
|
import { FeatureSchema } from '/imports/api/properties/Features.js';
|
||||||
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||||
import { NoteSchema } from '/imports/api/properties/Notes.js';
|
import { NoteSchema } from '/imports/api/properties/Notes.js';
|
||||||
|
import { PointBuySchema } from '/imports/api/properties/PointBuys.js';
|
||||||
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||||
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
||||||
import { RollSchema } from '/imports/api/properties/Rolls.js';
|
import { RollSchema } from '/imports/api/properties/Rolls.js';
|
||||||
@@ -32,6 +34,7 @@ const propertySchemasIndex = {
|
|||||||
adjustment: AdjustmentSchema,
|
adjustment: AdjustmentSchema,
|
||||||
attribute: AttributeSchema,
|
attribute: AttributeSchema,
|
||||||
buff: BuffSchema,
|
buff: BuffSchema,
|
||||||
|
buffRemover: BuffRemoverSchema,
|
||||||
branch: BranchSchema,
|
branch: BranchSchema,
|
||||||
class: ClassSchema,
|
class: ClassSchema,
|
||||||
classLevel: ClassLevelSchema,
|
classLevel: ClassLevelSchema,
|
||||||
@@ -42,6 +45,7 @@ const propertySchemasIndex = {
|
|||||||
feature: FeatureSchema,
|
feature: FeatureSchema,
|
||||||
folder: FolderSchema,
|
folder: FolderSchema,
|
||||||
note: NoteSchema,
|
note: NoteSchema,
|
||||||
|
pointBuy: PointBuySchema,
|
||||||
proficiency: ProficiencySchema,
|
proficiency: ProficiencySchema,
|
||||||
propertySlot: SlotSchema,
|
propertySlot: SlotSchema,
|
||||||
reference: ReferenceSchema,
|
reference: ReferenceSchema,
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ let SharingSchema = new SimpleSchema({
|
|||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
|
readersCanCopy: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SharingSchema;
|
export default SharingSchema;
|
||||||
|
|||||||
@@ -27,6 +27,26 @@ const setPublic = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const setReadersCanCopy = new ValidatedMethod({
|
||||||
|
name: 'sharing.setReadersCanCopy',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
docRef: RefSchema,
|
||||||
|
readersCanCopy: { type: Boolean },
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({ docRef, readersCanCopy }) {
|
||||||
|
let doc = fetchDocByRef(docRef);
|
||||||
|
assertOwnership(doc, this.userId);
|
||||||
|
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||||
|
$set: { readersCanCopy },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const updateUserSharePermissions = new ValidatedMethod({
|
const updateUserSharePermissions = new ValidatedMethod({
|
||||||
name: 'sharing.updateUserSharePermissions',
|
name: 'sharing.updateUserSharePermissions',
|
||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
@@ -129,4 +149,4 @@ const transferOwnership = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export { setPublic, updateUserSharePermissions, transferOwnership };
|
export { setPublic, setReadersCanCopy, updateUserSharePermissions, transferOwnership };
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { _ } from 'meteor/underscore';
|
import { includes } from 'lodash';
|
||||||
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||||
|
|
||||||
function assertIdValid(userId) {
|
function assertIdValid(userId) {
|
||||||
@@ -18,6 +18,7 @@ function assertdocExists(doc){
|
|||||||
export function assertOwnership(doc, userId) {
|
export function assertOwnership(doc, userId) {
|
||||||
assertIdValid(userId);
|
assertIdValid(userId);
|
||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
|
|
||||||
if (doc.owner === userId) {
|
if (doc.owner === userId) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -37,7 +38,6 @@ export function assertEditPermission(doc, userId) {
|
|||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
const user = Meteor.users.findOne(userId, {
|
const user = Meteor.users.findOne(userId, {
|
||||||
fields: {
|
fields: {
|
||||||
'services.patreon': 1,
|
|
||||||
'roles': 1,
|
'roles': 1,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -50,7 +50,7 @@ export function assertEditPermission(doc, userId) {
|
|||||||
// Ensure the user is authorized for this specific document
|
// Ensure the user is authorized for this specific document
|
||||||
if (
|
if (
|
||||||
doc.owner === userId ||
|
doc.owner === userId ||
|
||||||
_.contains(doc.writers, userId)
|
includes(doc.writers, userId)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -59,6 +59,43 @@ export function assertEditPermission(doc, userId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the user can edit the root document which manages its own sharing
|
||||||
|
* permissions.
|
||||||
|
*
|
||||||
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
|
*/
|
||||||
|
export function assertCopyPermission(doc, userId) {
|
||||||
|
assertIdValid(userId);
|
||||||
|
assertdocExists(doc);
|
||||||
|
const user = Meteor.users.findOne(userId, {
|
||||||
|
fields: {
|
||||||
|
'roles': 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Admin override
|
||||||
|
if (user.roles && user.roles.includes('admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the user is authorized for this specific document
|
||||||
|
if (
|
||||||
|
doc.owner === userId ||
|
||||||
|
includes(doc.writers, userId)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else if (
|
||||||
|
(includes(doc.readers, userId) || doc.public) &&
|
||||||
|
doc.readersCanCopy
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Meteor.Error('Copy permission denied',
|
||||||
|
'You do not have permission to copy this document');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getRoot(doc) {
|
function getRoot(doc) {
|
||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
if (doc.ancestors && doc.ancestors.length && doc.ancestors[0]) {
|
if (doc.ancestors && doc.ancestors.length && doc.ancestors[0]) {
|
||||||
@@ -79,6 +116,17 @@ export function assertDocEditPermission(doc, userId){
|
|||||||
assertEditPermission(root, userId);
|
assertEditPermission(root, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the user can copy a descendant document whose root ancestor
|
||||||
|
* implements sharing permissions.
|
||||||
|
*
|
||||||
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
|
*/
|
||||||
|
export function assertDocCopyPermission(doc, userId) {
|
||||||
|
let root = getRoot(doc);
|
||||||
|
assertCopyPermission(root, userId);
|
||||||
|
}
|
||||||
|
|
||||||
export function assertViewPermission(doc, userId) {
|
export function assertViewPermission(doc, userId) {
|
||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
if (doc.public) return true;
|
if (doc.public) return true;
|
||||||
@@ -86,8 +134,8 @@ export function assertViewPermission(doc, userId) {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
doc.owner === userId ||
|
doc.owner === userId ||
|
||||||
_.contains(doc.readers, userId) ||
|
includes(doc.readers, userId) ||
|
||||||
_.contains(doc.writers, userId)
|
includes(doc.writers, userId)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import valueToCoins from '/imports/ui/utility/valueToCoins.js';
|
import valueToCoins from '/imports/client/ui/utility/valueToCoins.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props:{
|
props:{
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
:outlined="!!label"
|
:outlined="!!label"
|
||||||
:icon="!label"
|
:icon="!label"
|
||||||
:min-width="label && 108"
|
:min-width="label && 108"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
@@ -98,9 +99,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
import isDarkColor from '/imports/client/ui/utility/isDarkColor.js';
|
||||||
import vuetifyColors from 'vuetify/es5/util/colors';
|
import vuetifyColors from 'vuetify/es5/util/colors';
|
||||||
import { kebabToCamelCase, camelToKebabCase } from '/imports/ui/utility/swapCase.js';
|
import { kebabToCamelCase, camelToKebabCase } from '/imports/client/ui/utility/swapCase.js';
|
||||||
|
|
||||||
function colorToHex(color, shade = 'base'){
|
function colorToHex(color, shade = 'base'){
|
||||||
if (!color) return;
|
if (!color) return;
|
||||||
@@ -124,6 +125,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
//hex string
|
//hex string
|
||||||
value: {
|
value: {
|
||||||
46
app/imports/client/ui/components/ColumnLayout.vue
Normal file
46
app/imports/client/ui/components/ColumnLayout.vue
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<template
|
||||||
|
lang="html"
|
||||||
|
functional
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="column-layout"
|
||||||
|
:class="wideColumns ? 'wide-columns' : ''"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
wideColumns: Boolean,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css">
|
||||||
|
.column-layout {
|
||||||
|
column-count: 12;
|
||||||
|
column-fill: balance;
|
||||||
|
column-gap: 0;
|
||||||
|
column-width: 240px;
|
||||||
|
transform: translateZ(0);
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-layout.wide-columns {
|
||||||
|
column-count: 12;
|
||||||
|
column-fill: balance;
|
||||||
|
column-gap: 0;
|
||||||
|
column-width: 320px;
|
||||||
|
transform: translateZ(0);
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-layout>div,
|
||||||
|
.column-layout>span>div {
|
||||||
|
page-break-inside: avoid;
|
||||||
|
break-inside: avoid;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import IncrementMenu from '/imports/ui/components/IncrementMenu.vue';
|
import IncrementMenu from '/imports/client/ui/components/IncrementMenu.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
167
app/imports/client/ui/components/IncrementMenu.vue
Normal file
167
app/imports/client/ui/components/IncrementMenu.vue
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<template>
|
||||||
|
<v-layout
|
||||||
|
align-center
|
||||||
|
justify-center
|
||||||
|
class="increment-menu"
|
||||||
|
>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn-toggle
|
||||||
|
:value="operation === 'add' ? 0: operation === 'subtract' ? 1 : null"
|
||||||
|
class="mx-2"
|
||||||
|
@click="$refs.editInput.focus()"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
|
class="filled"
|
||||||
|
@click="toggleAdd(); $nextTick(() => $refs.editInput.focus())"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-plus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
|
class="filled"
|
||||||
|
@click="toggleSubtract(); $nextTick(() => $refs.editInput.focus())"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-minus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
<v-text-field
|
||||||
|
ref="editInput"
|
||||||
|
:solo="!flat"
|
||||||
|
:class="flat && 'ma-0 pa-0'"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
style="max-width: 120px;"
|
||||||
|
min="0"
|
||||||
|
:value="editValue"
|
||||||
|
:prepend-inner-icon="operationIcon(operation)"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
|
@focus="$event.target.select()"
|
||||||
|
@keypress="keypress"
|
||||||
|
@input="input"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:small="!flat"
|
||||||
|
:fab="!flat"
|
||||||
|
:text="flat"
|
||||||
|
:icon="flat"
|
||||||
|
class="mx-2 filled"
|
||||||
|
@click="commitEdit"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-check</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
:small="!flat"
|
||||||
|
:fab="!flat"
|
||||||
|
:text="flat"
|
||||||
|
:icon="flat"
|
||||||
|
class="filled"
|
||||||
|
@click="cancelEdit"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-spacer />
|
||||||
|
</v-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
export default {
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
open: Boolean,
|
||||||
|
flat: Boolean,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editValue: this.value,
|
||||||
|
operation: 'set',
|
||||||
|
hover: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
open: {
|
||||||
|
immediate: true,
|
||||||
|
handler(isOpen) {
|
||||||
|
if (isOpen) this.resetData();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resetData() {
|
||||||
|
this.editValue = this.value;
|
||||||
|
this.operation = 'set';
|
||||||
|
// this.$nextTick didn't work, using timeout instead did
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.$refs.editInput) {
|
||||||
|
this.$refs.editInput.focus();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
},
|
||||||
|
cancelEdit() {
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
commitEdit() {
|
||||||
|
this.editing = false;
|
||||||
|
let value = +this.$refs.editInput.lazyValue;
|
||||||
|
if (this.operation === 'add') {
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
let type = this.operation === 'set' ? 'set' : 'increment';
|
||||||
|
this.$emit('change', { type, value });
|
||||||
|
},
|
||||||
|
operationIcon(operation) {
|
||||||
|
switch (operation) {
|
||||||
|
case 'set':
|
||||||
|
return 'mdi-forward';
|
||||||
|
case 'add':
|
||||||
|
return 'mdi-plus';
|
||||||
|
case 'subtract':
|
||||||
|
return 'mdi-minus';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleAdd() {
|
||||||
|
this.operation = (this.operation === 'add') ? 'set' : 'add';
|
||||||
|
},
|
||||||
|
toggleSubtract() {
|
||||||
|
this.operation = (this.operation === 'subtract') ? 'set' : 'subtract';
|
||||||
|
},
|
||||||
|
keypress(event) {
|
||||||
|
let digitsOnly = /[0-9]/;
|
||||||
|
let key = event.key;
|
||||||
|
if (key === '+') {
|
||||||
|
this.toggleAdd();
|
||||||
|
event.preventDefault();
|
||||||
|
} else if (key === '-') {
|
||||||
|
this.toggleSubtract();
|
||||||
|
event.preventDefault();
|
||||||
|
} else if (key === 'Enter') {
|
||||||
|
this.commitEdit();
|
||||||
|
} else if (!digitsOnly.test(key)) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
input(value) {
|
||||||
|
if (+value < 0) {
|
||||||
|
this.editValue = -value;
|
||||||
|
this.operation = 'subtract';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.filled.theme--light {
|
||||||
|
background: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filled.theme--dark {
|
||||||
|
background: #424242 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
27
app/imports/client/ui/components/MarkdownText.vue
Normal file
27
app/imports/client/ui/components/MarkdownText.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
|
<div
|
||||||
|
class="markdown"
|
||||||
|
@click="e => $emit('click', e)"
|
||||||
|
v-html="compiledMarkdown"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import { marked } from 'marked';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
markdown: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
compiledMarkdown() {
|
||||||
|
if (!this.markdown) return;
|
||||||
|
return marked(this.markdown);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
46
app/imports/client/ui/components/ResetSelector.vue
Normal file
46
app/imports/client/ui/components/ResetSelector.vue
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<smart-select
|
||||||
|
label="Reset"
|
||||||
|
clearable
|
||||||
|
style="flex-basis: 300px;"
|
||||||
|
:hint="hint"
|
||||||
|
:items="resetOptions"
|
||||||
|
:value="value"
|
||||||
|
:error-messages="errorMessages"
|
||||||
|
:menu-props="{auto: true, lazy: true}"
|
||||||
|
@change="(value, ack) => $emit('change', value, ack)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import createListOfProperties from '/imports/client/ui/properties/forms/shared/lists/createListOfProperties.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: [String, Number, Date, Array, Object, Boolean],
|
||||||
|
errorMessages: [String, Array],
|
||||||
|
hint: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
resetOptions() {
|
||||||
|
const eventActions = createListOfProperties({
|
||||||
|
type: 'action',
|
||||||
|
actionType: 'event',
|
||||||
|
}, true);
|
||||||
|
const defaultEvents = [
|
||||||
|
{
|
||||||
|
text: 'Short rest',
|
||||||
|
value: 'shortRest',
|
||||||
|
}, {
|
||||||
|
text: 'Long rest',
|
||||||
|
value: 'longRest',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return [...defaultEvents, ...eventActions];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import VerticalHex from '/imports/ui/components/VerticalHex.vue';
|
import VerticalHex from '/imports/client/ui/components/VerticalHex.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -27,9 +27,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
import isDarkColor from '/imports/client/ui/utility/isDarkColor.js';
|
||||||
import getThemeColor from '/imports/ui/utility/getThemeColor.js';
|
import getThemeColor from '/imports/client/ui/utility/getThemeColor.js';
|
||||||
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
import CardHighlight from '/imports/client/ui/components/CardHighlight.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -44,9 +44,11 @@
|
|||||||
},
|
},
|
||||||
transparentToolbar: Boolean,
|
transparentToolbar: Boolean,
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
hovering: false,
|
hovering: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isDark() {
|
isDark() {
|
||||||
return isDarkColor(this.color);
|
return isDarkColor(this.color);
|
||||||
@@ -72,9 +74,11 @@
|
|||||||
.toolbar-card .v-toolbar__title {
|
.toolbar-card .v-toolbar__title {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-card {
|
.toolbar-card {
|
||||||
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-card.transparent-toolbar .theme--dark.v-toolbar.v-sheet {
|
.toolbar-card.transparent-toolbar .theme--dark.v-toolbar.v-sheet {
|
||||||
background-color: #303030;
|
background-color: #303030;
|
||||||
}
|
}
|
||||||
@@ -30,14 +30,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
import SmartInput from '/imports/client/ui/components/global/SmartInputMixin.js';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [SmartInput],
|
mixins: [SmartInput],
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
menu: false,
|
menu: false,
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
formattedSafeValue() {
|
formattedSafeValue() {
|
||||||
return format(this.safeValue, 'YYYY-MM-DD')
|
return format(this.safeValue, 'YYYY-MM-DD')
|
||||||
@@ -53,4 +55,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
45
app/imports/client/ui/components/global/DragHandle.vue
Normal file
45
app/imports/client/ui/components/global/DragHandle.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<v-icon
|
||||||
|
class="handle"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@click.native="e => { }"
|
||||||
|
@touchstart.native.stop="e => { }"
|
||||||
|
@touchend.native="portalEvent"
|
||||||
|
>
|
||||||
|
mdi-drag
|
||||||
|
</v-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import { defer } from 'lodash'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
portalEvent(e) {
|
||||||
|
// Stop everything in the document listening for this touch event
|
||||||
|
e.stopPropagation();
|
||||||
|
// But also send it to straight to the root for draggable.js
|
||||||
|
defer(() => {
|
||||||
|
e.target.ownerDocument.dispatchEvent(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.handle {
|
||||||
|
cursor: move !important;
|
||||||
|
cursor: -webkit-grab !important;
|
||||||
|
}
|
||||||
|
.handle::after {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.sortable-drag.handle {
|
||||||
|
cursor: move !important;
|
||||||
|
cursor: -webkit-grabbing !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -78,8 +78,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import SvgIcon from '/imports/ui/components/global/SvgIcon.vue';
|
import SvgIcon from '/imports/client/ui/components/global/SvgIcon.vue';
|
||||||
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
import SmartInput from '/imports/client/ui/components/global/SmartInputMixin.js';
|
||||||
import { findIcons } from '/imports/api/icons/Icons.js';
|
import { findIcons } from '/imports/api/icons/Icons.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -97,11 +97,13 @@ export default {
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
menu: false,
|
menu: false,
|
||||||
searchString: '',
|
searchString: '',
|
||||||
icons: [],
|
icons: [],
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
menu(value) {
|
menu(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
@@ -131,4 +133,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
78
app/imports/client/ui/components/global/SmartBtn.vue
Normal file
78
app/imports/client/ui/components/global/SmartBtn.vue
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<v-btn
|
||||||
|
v-bind="$attrs"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
:loading="loading"
|
||||||
|
@click="click"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
disabled: Boolean,
|
||||||
|
debounce: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
singleClick: Boolean,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
timesClicked: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isDisabled(){
|
||||||
|
return this.context.editPermission === false || this.disabled;
|
||||||
|
},
|
||||||
|
debounceTime() {
|
||||||
|
if (Number.isFinite(this.debounce)){
|
||||||
|
return this.debounce;
|
||||||
|
} else if (Number.isFinite(this.context.debounceTime)){
|
||||||
|
return this.context.debounceTime;
|
||||||
|
} else {
|
||||||
|
return 750;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created(){
|
||||||
|
this.debounceClicks = debounce(this.clicks, this.debounceTime);
|
||||||
|
},
|
||||||
|
beforeDestroy(){
|
||||||
|
this.debounceClicks.flush();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
click() {
|
||||||
|
if (this.singleClick) {
|
||||||
|
this.loading = true;
|
||||||
|
} else {
|
||||||
|
this.timesClicked += 1;
|
||||||
|
this.debounceClicks();
|
||||||
|
}
|
||||||
|
this.$emit('click', this.acknowledgeChange);
|
||||||
|
},
|
||||||
|
clicks() {
|
||||||
|
this.loading = true;
|
||||||
|
this.$emit('clicks', this.timesClicked, this.acknowledgeChange);
|
||||||
|
this.timesClicked = 0;
|
||||||
|
},
|
||||||
|
acknowledgeChange(error){
|
||||||
|
this.loading = false;
|
||||||
|
if (error) {
|
||||||
|
console.error(error)
|
||||||
|
snackbar({ text: error.reason || error.message || error.toString() });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
import SmartInput from '/imports/client/ui/components/global/SmartInputMixin.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [SmartInput],
|
mixins: [SmartInput],
|
||||||
56
app/imports/client/ui/components/global/SmartCombobox.vue
Normal file
56
app/imports/client/ui/components/global/SmartCombobox.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<v-combobox
|
||||||
|
v-bind="$attrs"
|
||||||
|
:loading="loading"
|
||||||
|
:error-messages="errors"
|
||||||
|
:value="safeValue"
|
||||||
|
:menu-props="{auto: true, lazy: true}"
|
||||||
|
:search-input.sync="searchInput"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
:multiple="multiple"
|
||||||
|
outlined
|
||||||
|
@change="customChange"
|
||||||
|
@focus="focused = true"
|
||||||
|
@blur="focused = false"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
slot="prepend"
|
||||||
|
name="prepend"
|
||||||
|
/>
|
||||||
|
</v-combobox>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import SmartInput from '/imports/client/ui/components/global/SmartInputMixin.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [SmartInput],
|
||||||
|
props: {
|
||||||
|
multiple: Boolean,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
searchInput: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// Multiple combobox gets a long default debounce time while single
|
||||||
|
// value gets a shorter one
|
||||||
|
debounceTime() {
|
||||||
|
if (Number.isFinite(this.debounce)) {
|
||||||
|
return this.debounce;
|
||||||
|
} else if (Number.isFinite(this.context.debounceTime)) {
|
||||||
|
return this.context.debounceTime;
|
||||||
|
} else {
|
||||||
|
return this.multiple ? 1000 : 100;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
customChange(val) {
|
||||||
|
this.input(val);
|
||||||
|
this.searchInput = '';
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -13,7 +13,8 @@ export default {
|
|||||||
context: { default: {} }
|
context: { default: {} }
|
||||||
},
|
},
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
error: false,
|
error: false,
|
||||||
ackErrors: null,
|
ackErrors: null,
|
||||||
rulesErrors: null,
|
rulesErrors: null,
|
||||||
@@ -22,7 +23,8 @@ export default {
|
|||||||
dirty: false,
|
dirty: false,
|
||||||
safeValue: this.value,
|
safeValue: this.value,
|
||||||
inputValue: this.value,
|
inputValue: this.value,
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
value: [String, Number, Date, Array, Object, Boolean],
|
value: [String, Number, Date, Array, Object, Boolean],
|
||||||
errorMessages: [String, Array],
|
errorMessages: [String, Array],
|
||||||
@@ -115,7 +117,7 @@ export default {
|
|||||||
},
|
},
|
||||||
change(val) {
|
change(val) {
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
if (this.hasChangeListener) this.loading = true;
|
if (this.hasChangeListener()) this.loading = true;
|
||||||
this.$emit('change', val, this.acknowledgeChange);
|
this.$emit('change', val, this.acknowledgeChange);
|
||||||
},
|
},
|
||||||
hasChangeListener() {
|
hasChangeListener() {
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
import SmartInput from '/imports/client/ui/components/global/SmartInputMixin.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [SmartInput],
|
mixins: [SmartInput],
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user