Merge branch 'develop' into feature-tabletop

This commit is contained in:
Stefan Zermatten
2023-08-02 11:36:01 +02:00
17 changed files with 215 additions and 123 deletions

View File

@@ -18,23 +18,31 @@ export default function getSlotFillFilter({ slot, libraryIds }) {
}]
});
} else if (slot.type === 'class') {
filter.$and.push({
$or: [{
type: 'classLevel',
}, {
slotFillerType: 'classLevel',
}]
});
const classLevelFilter = {
type: 'classLevel',
};
const slotFillerFilter = {
slotFillerType: 'classLevel',
};
// Match variable name or tags
if (slot.variableName) {
filter.variableName = slot.variableName;
classLevelFilter.variableName = slot.variableName;
slotFillerFilter.libraryTags = slot.variableName;
}
// Only search for levels the class needs
if (slot.missingLevels && slot.missingLevels.length) {
filter.level = { $in: slot.missingLevels };
classLevelFilter.level = { $in: slot.missingLevels };
slotFillerFilter['cache.node.level'] = { $in: slot.missingLevels };
} else {
filter.level = { $gt: slot.level || 0 };
classLevelFilter.level = { $gt: slot.level || 0 };
slotFillerFilter['cache.node.level'] = { $gt: slot.level || 0 };
}
filter.$and.push({
$or: [classLevelFilter, slotFillerFilter]
});
}
let tagsOr = [];
let tagsNin = [];

View File

@@ -105,6 +105,8 @@ function insertPropertyFromNode(nodeId, ancestors, order) {
// Convert all references into actual nodes
nodes = reifyNodeReferences(nodes);
// Refetch the root node, it might have been reified
node = nodes[0] || node;
// set libraryNodeIds
storeLibraryNodeReferences(nodes);

View File

@@ -179,11 +179,6 @@ let CreatureSchema = new SimpleSchema({
blackbox: true,
defaultValue: {}
},
variables: {
type: Object,
blackbox: true,
defaultValue: {}
},
computeErrors: {
type: Array,
optional: true,

View File

@@ -2,9 +2,8 @@ import operator from '/imports/parser/parseTree/operator.js';
import { parse } from '/imports/parser/parser.js';
import logErrors from './logErrors.js';
export default function applyEffectsToCalculationParseNode(calcObj, actionContext){
if (!calcObj.effects) return;
calcObj.effects.forEach(effect => {
export default function applyEffectsToCalculationParseNode(calcObj, actionContext) {
calcObj.effects?.forEach(effect => {
if (effect.operation !== 'add') return;
if (!effect.amount) return;
if (effect.amount.value === null) return;
@@ -17,8 +16,31 @@ export default function applyEffectsToCalculationParseNode(calcObj, actionContex
operator: '+',
fn: 'add'
});
} catch (e){
} catch (e) {
logErrors([e], actionContext)
}
});
// Add the highest proficiency as well
let highestProficiency;
calcObj.proficiencies?.forEach(proficiency => {
if (
proficiency.value > highestProficiency
|| (highestProficiency === undefined && Number.isFinite(proficiency.value))
) {
highestProficiency = proficiency.value;
}
});
if (highestProficiency) {
try {
let profParseNode = parse(highestProficiency.toString());
calcObj.parseNode = operator.create({
left: calcObj.parseNode,
right: profParseNode,
operator: '+',
fn: 'add'
});
} catch (e) {
logErrors([e], actionContext)
}
}
}

View File

@@ -2,7 +2,7 @@ import evaluateCalculation from '/imports/api/engine/computation/utility/evaluat
import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js';
import logErrors from './logErrors.js';
export default function recalculateCalculation(calc, actionContext, context){
export default function recalculateCalculation(calc, actionContext, context) {
if (!calc?.parseNode) return;
calc._parseLevel = 'reduce';
applyEffectsToCalculationParseNode(calc, actionContext);

View File

@@ -1,14 +1,14 @@
import resolve, { toString } from '/imports/parser/resolve.js';
export default function evaluateCalculation(calculation, scope, givenContext){
export default function evaluateCalculation(calculation, scope, givenContext) {
const parseNode = calculation.parseNode;
const fn = calculation._parseLevel;
const calculationScope = {...calculation._localScope, ...scope};
const {result: resultNode, context} = resolve(fn, parseNode, calculationScope, givenContext);
const calculationScope = { ...calculation._localScope, ...scope };
const { result: resultNode, context } = resolve(fn, parseNode, calculationScope, givenContext);
calculation.errors = context.errors;
if (resultNode?.parseType === 'constant'){
if (resultNode?.parseType === 'constant') {
calculation.value = resultNode.value;
} else if (resultNode?.parseType === 'error'){
} else if (resultNode?.parseType === 'error') {
calculation.value = null;
} else {
calculation.value = toString(resultNode);

View File

@@ -28,8 +28,8 @@ const duplicateLibraryNode = new ValidatedMethod({
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 1,
timeInterval: 5000,
numRequests: 4,
timeInterval: 6000,
},
run({ _id }) {
let libraryNode = LibraryNodes.findOne(_id);

View File

@@ -16,42 +16,27 @@ export default {
wideColumns: Boolean,
},
};
/*
Removed to improve chrome layout performance, put it back if there are rendering errors
.column-layout>span>div {
display: table;
table-layout: fixed;
}
*/
</script>
<style lang="css">
.column-layout {
column-count: 12;
column-fill: balance;
column-gap: 0;
column-gap: 8px;
column-width: 240px;
transform: translateZ(0);
padding: 4px;
padding: 8px;
}
.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>*,
.column-layout>span>div {
width: 100%;
backface-visibility: hidden;
transform: translateX(0);
page-break-inside: avoid;
display: inline-block;
break-inside: avoid;
padding: 4px;
page-break-inside: avoid;
margin-bottom: 8px;
width: 100%;
}
</style>

View File

@@ -52,7 +52,7 @@ export default {
props: {
timeout: {
type: Number,
default: 6000000,
default: 15000,
},
pause: {
type: Number,

View File

@@ -596,7 +596,6 @@ export default {
margin-top: 4px;
margin-left: -30px;
padding-left: 34px;
z-index: -1;
}
.number-label .number {

View File

@@ -49,7 +49,7 @@
:key="libraryNode._id"
:model="libraryNode"
:data-id="libraryNode._id"
:class="{disabled: isDisabled(libraryNode)}"
:class="{disabled: isDisabled(libraryNode) || libraryNode._disabledBySlotFillerCondition}"
>
<v-expansion-panel-header>
<template #default="{ open }">
@@ -69,6 +69,7 @@
v-model="selectedNodeIds"
class="my-0 py-0"
hide-details
:color="libraryNode._disabledBySlotFillerCondition ? 'error' : ''"
:disabled="isDisabled(libraryNode)"
:value="libraryNode._id"
@click.stop
@@ -81,7 +82,7 @@
v-if="libraryNode._disabledBySlotFillerCondition"
class="error--text text-no-wrap text-truncate"
>
{{ libraryNode.slotFillerCondition }}
{{ libraryNode._conditionError }}
</div>
</v-layout>
<div class="text-caption text-no-wrap text-truncate">
@@ -110,7 +111,7 @@
</template>
</v-expansion-panel-header>
<v-expansion-panel-content>
<library-node-expansion-content :model="libraryNode" />
<library-node-expansion-content :id="libraryNode._id" />
</v-expansion-panel-content>
</v-expansion-panel>
</template>
@@ -120,11 +121,12 @@
column
align-center
justify-center
class="ma-3"
class="ma-3 mt-8"
>
<v-btn
:loading="!$subReady.classFillers"
color="accent"
outlined
@click="loadMore"
>
Load More
@@ -192,7 +194,7 @@ import getSlotFillFilter from '/imports/api/creature/creatureProperties/methods/
import Libraries from '/imports/api/library/Libraries.js';
import LibraryNodeExpansionContent from '/imports/client/ui/library/LibraryNodeExpansionContent.vue';
import PropertyTags from '/imports/client/ui/properties/viewers/shared/PropertyTags.vue';
import { clone, difference } from 'lodash';
import { clone, difference, isEqual } from 'lodash';
export default {
components: {
@@ -247,29 +249,69 @@ export default {
});
return { or, not };
},
filledLevels() {
return LibraryNodes.find({
_id: { $in: this.selectedNodeIds }
}).map(
node => node.level || node.cache?.node?.level || 0
).sort((a, b) => a - b);
}
},
watch: {
selectedNodeIds(selectedIds, oldSelectedIds) {
// Skip if we didn't increase the length by adding a new Id
if (oldSelectedIds.length >= selectedIds.length) return;
// Find out which library node was added
const addedId = difference(selectedIds, oldSelectedIds)[0];
if (!addedId) return;
const addedNode = LibraryNodes.findOne(addedId);
if (!addedNode) return;
// Tick any unchecked nodes of a lower level, but only one per level
const backFilledLevels = new Set();
this.libraryNodes.forEach(node => {
if (
!selectedIds.includes(node._id)
&& node.level < addedNode.level
&& !backFilledLevels.has(node.level)
) {
selectedIds.push(node._id);
}
});
this.selectedNodeIds = selectedIds;
}
// Skip if we increased the length by adding a new Id, see if we need to backfill levels
if (oldSelectedIds.length < selectedIds.length) {
// Find out which library node was added
const addedId = difference(selectedIds, oldSelectedIds)[0];
if (!addedId) return;
const addedNode = LibraryNodes.findOne(addedId);
if (!addedNode) return;
// Check which levels are already backfilled
const backFilledLevels = new Set();
const sortedIds = LibraryNodes.find({
_id: { $in: selectedIds }
}).map(node => backFilledLevels.add(node.level || node.cache?.node?.level || 0));
// Tick any unchecked nodes of a lower level, but only one per level
this.libraryNodes.forEach(node => {
if (
!selectedIds.includes(node._id)
&& (node.level < addedNode.level)
&& !backFilledLevels.has(node.level)
&& !this.isDisabled(node)
&& !node._disabledBySlotFillerCondition
) {
selectedIds.push(node._id);
backFilledLevels.add(node.level)
}
});
this.selectedNodeIds = sortedIds;
}
// Refetch the library nodes to sort them correctly
const sortedIds = LibraryNodes.find({
_id: { $in: selectedIds }
}, {
sort: { level: 1, name: 1, order: 1 }
})
.fetch()
.sort((a, b) => (a.level || a.cache?.node?.level || 0) - (b.level || b.cache?.node?.level || 0))
.map(node => node._id);
// Only update if the order changed
if (!isEqual(this.selectedNodeIds, sortedIds)) {
this.selectedNodeIds = sortedIds;
}
},
activeCount(val) {
// Still loading fillers
if (!this._subs['classFillers'].ready()) return;
// Can load more, and not showing enough active choices, so load more
if (
this.currentLimit < this.countAll
&& val < 20
) {
this.loadMore();
}
},
},
methods: {
loadMore() {
@@ -286,12 +328,10 @@ export default {
});
},
isDisabled(node) {
return node._disabledBySlotFillerCondition ||
node._disabledByAlreadyAdded ||
(
node._disabledByQuantityFilled &&
!this.selectedNodeIds.includes(node._id)
)
const selected = this.selectedNodeIds.includes(node._id);
return node._disabledByAlreadyAdded
|| ( node._disabledByQuantityFilled && !selected )
|| ( this.filledLevels.includes(node.level || node.cache?.node?.level || 0) && !selected )
},
},
meteor: {
@@ -324,6 +364,10 @@ export default {
countAll() {
return this._subs['classFillers'].data('countAll');
},
activeCount() {
if (!this.libraryNodes) return;
return this.libraryNodes.length - (this.disabledNodeCount || 0);
},
alreadyAdded() {
let added = new Set();
if (!this.model.unique) return added;
@@ -377,12 +421,15 @@ export default {
if (!this.libraryNodeFilter) return [];
if (!this.$subReady.classFillers) return [];
let nodes = LibraryNodes.find(this.libraryNodeFilter, {
sort: { name: 1, order: 1 }
sort: { level: 1, name: 1, order: 1 }
}).fetch();
let disabledNodeCount = 0;
// Mark classFillers whose condition isn't met or are too big to fit
// the quantity to fill
nodes.forEach(node => {
if (node.cache?.node) {
node.level = node.cache.node.level;
}
if (node.slotFillerCondition) {
try {
let parseNode = parse(node.slotFillerCondition);
@@ -390,18 +437,19 @@ export default {
if (resultNode?.parseType === 'constant') {
if (!resultNode.value) {
node._disabledBySlotFillerCondition = true;
node._conditionError = node.slotFillerConditionNote || node.slotFillerCondition;
disabledNodeCount += 1;
}
} else {
node._disabledBySlotFillerCondition = true;
node._conditionError = toString(resultNode);
node._conditionError = node.slotFillerConditionNote || toString(resultNode);
disabledNodeCount += 1;
}
} catch (e) {
console.warn(e);
let error = prettifyParseError(e);
node._disabledBySlotFillerCondition = true;
node._conditionError = error;
node._conditionError = 'Condition error: ' + error;
disabledNodeCount += 1;
}
}
@@ -415,6 +463,7 @@ export default {
node._disabledByAlreadyAdded = true;
}
});
nodes.sort((a, b) => a.level - b.level);
this.disabledNodeCount = disabledNodeCount;
return nodes;
},

View File

@@ -64,7 +64,7 @@
:key="libraryNode._id"
:model="libraryNode"
:data-id="libraryNode._id"
:class="{disabled: isDisabled(libraryNode)}"
:class="{disabled: isDisabled(libraryNode) || libraryNode._disabledBySlotFillerCondition}"
>
<v-expansion-panel-header>
<template #default="{ open }">
@@ -84,6 +84,7 @@
v-model="selectedNodeIds"
class="my-0 py-0"
hide-details
:color="libraryNode._disabledBySlotFillerCondition ? 'error' : ''"
:disabled="isDisabled(libraryNode)"
:value="libraryNode._id"
@click.stop
@@ -125,7 +126,7 @@
</template>
</v-expansion-panel-header>
<v-expansion-panel-content>
<library-node-expansion-content :model="libraryNode" />
<library-node-expansion-content :id="libraryNode._id" />
</v-expansion-panel-content>
</v-expansion-panel>
</template>
@@ -136,16 +137,38 @@
column
align-center
justify-center
class="ma-3"
class="ma-3 mt-8"
>
<v-btn
:loading="!$subReady.slotFillers"
color="accent"
outlined
@click="loadMore"
>
Load More
</v-btn>
</v-layout>
<template v-if="!showDisabled && disabledNodeCount">
<v-layout
column
align-center
justify-center
class="ma-3 mt-8"
>
<div>
Requirements of {{ disabledNodeCount }} properties were not met
</div>
<v-btn
class="mt-2"
elevation="0"
color="accent"
outlined
@click="showDisabled = true"
>
Show All
</v-btn>
</v-layout>
</template>
<v-layout
align-center
justify-center
@@ -162,6 +185,7 @@
<v-btn
v-if="!dummySlot"
text
small
data-id="library-browser-button"
:disabled="!model"
@click="openLibraryBrowser"
@@ -171,7 +195,7 @@
<v-btn
v-if="!dummySlot"
text
color="accent"
small
:disabled="!model"
data-id="custom-button"
@click="insertCustomFiller"
@@ -179,26 +203,7 @@
Create custom filler
</v-btn>
</v-layout>
<template v-if="!showDisabled && disabledNodeCount">
<v-layout
column
align-center
justify-center
class="ma-3"
>
<div>
Requirements of {{ disabledNodeCount }} properties were not met
</div>
<v-btn
class="mt-2"
elevation="0"
color="accent"
@click="showDisabled = true"
>
Show All
</v-btn>
</v-layout>
</template>
<template slot="actions">
<v-btn
text
@@ -306,6 +311,19 @@ export default {
return propName;
},
},
watch: {
activeCount(val) {
// Still loading fillers
if (!this._subs['slotFillers'].ready()) return;
// Can load more, and not showing enough active choices, so load more
if (
this.currentLimit < this.countAll
&& val < 25
) {
this.loadMore();
}
},
},
methods: {
loadMore() {
if (this.currentLimit >= this.countAll) return;
@@ -327,8 +345,7 @@ export default {
});
},
isDisabled(node) {
return node._disabledBySlotFillerCondition ||
node._disabledByAlreadyAdded ||
return node._disabledByAlreadyAdded ||
(
node._disabledByQuantityFilled &&
!this.selectedNodeIds.includes(node._id)
@@ -408,6 +425,10 @@ export default {
countAll() {
return this._subs['slotFillers'].data('countAll');
},
activeCount() {
if (!this.libraryNodes) return;
return this.libraryNodes.length - (this.disabledNodeCount || 0);
},
libraryNodeFilter() {
const filterString = this._subs['slotFillers'].data('libraryNodeFilter');
if (!filterString) return;

View File

@@ -5,7 +5,7 @@
<v-list-item
v-bind="$attrs"
:class="(isSelected || selectedByCollection) && !disabled && 'primary--text v-list-item--active'"
:to="singleSelect ? undefined : to"
:to="selection ? undefined : to"
@click="singleSelect && $emit('select')"
>
<v-list-item-action

View File

@@ -1,5 +1,5 @@
<template lang="html">
<div>
<div :key="id">
<v-progress-linear
v-if="!subsReady"
indeterminate
@@ -37,8 +37,8 @@ export default {
...propertyViewerIndex,
},
props: {
model: {
type: Object,
id: {
type: String,
required: true,
},
},
@@ -61,16 +61,19 @@ export default {
meteor: {
$subscribe: {
libraryNode(){
return [this.model._id];
return [this.id];
},
descendantLibraryNodes(){
return [this.model._id];
return [this.id];
},
},
model() {
return LibraryNodes.findOne(this.id);
},
propertyChildren(){
return nodesToTree({
collection: LibraryNodes,
ancestorId: this.model._id
ancestorId: this.id
});
},
}

View File

@@ -30,7 +30,7 @@
v-else-if="model.attributeType === 'resource'"
:model="model"
@click="$emit('click')"
@change="({ type, value }) => damageProperty({type, value: -value})"
@change="({ type, value, ack }) => damageProperty({type, value: -value, ack})"
@mouseover="hover = true"
@mouseleave="hover = false"
/>
@@ -96,6 +96,9 @@ export default {
_id: this.model._id,
operation: change.type,
value: change.value
}, e => {
console.log(change);
change.ack?.(e);
});
},
log({_id}) {

View File

@@ -41,16 +41,21 @@ Meteor.publish('slotFillers', function (slotId, searchTerm, isDummySlot) {
sort: { name: 1 }
});
// Build a filter for nodes in those libraries that match the slot
let filter = getSlotFillFilter({ slot, libraryIds });
this.autorun(function () {
// Build a filter for nodes in those libraries that match the slot
let filter = getSlotFillFilter({ slot, libraryIds });
// Get the limit of the documents the user can fetch
var limit = self.data('limit') || 50;
check(limit, Number);
let options = undefined;
if (searchTerm) {
filter.name = { $regex: escapeRegex(searchTerm), '$options': 'i' };
filter.$and.push({
$or: [
{ name: { $regex: escapeRegex(searchTerm), '$options': 'i' } },
{ libraryTags: searchTerm }
]
});
//filter.$text = { $search: searchTerm };
options = {
// relevant documents have a higher score.

View File

@@ -1,6 +1,6 @@
{
"name": "dicecloud",
"version": "2.0.53",
"version": "2.0.55",
"description": "Unofficial Online Realtime D&D 5e App",
"license": "GPL-3.0",
"repository": {