More of the sheet conforms to library allowances

This commit is contained in:
Stefan Zermatten
2022-07-20 00:09:58 +02:00
parent 0c06f60b7e
commit 6f7e742eb9
10 changed files with 377 additions and 173 deletions

View File

@@ -33,7 +33,7 @@
@change="(value, ack) => $emit('change', {path: ['avatarPicture'], value, ack})"
/>
<form-sections>
<form-section name="settings">
<form-section name="Settings">
<v-switch
label="Hide redundant stats"
:input-value="model.settings.hideUnusedStats"
@@ -88,17 +88,48 @@
/>
-->
</form-section>
<form-section name="Libraries">
<smart-switch
label="All user libraries"
@change="allUserLibrariesChange"
/>
<library-list
selection
:disabled="!model.allowedLibraries && !model.allowedLibraryCollections"
:libraries-selected="model.allowedLibraries"
:library-collections-selected="model.allowedLibraryCollections"
:libraries-selected-by-collections="librariesSelectedByCollections"
@select-library="selectLibrary"
@select-library-collection="selectLibraryCollection"
/>
<v-progress-linear
v-if="libraryWriteLoading"
style="margin: 12px -24px -16px -24px; width: calc(100% + 48px);"
indeterminate
/>
<p
v-if="libraryWriteError"
class="text--error"
>
{{ libraryWriteError }}
</p>
</form-section>
</form-sections>
</div>
</template>
<script lang="js">
import { union, without, debounce } from 'lodash';
import FormSection, {FormSections} from '/imports/ui/properties/forms/shared/FormSection.vue';
import LibraryList from '/imports/ui/library/LibraryList.vue';
import LibraryCollections from '/imports/api/library/LibraryCollections.js';
import {changeAllowedLibraries, toggleAllUserLibraries} from '/imports/api/creature/creatures/methods/changeAllowedLibraries.js';
export default {
components: {
FormSection,
FormSections,
FormSections,
LibraryList,
},
props: {
stored: {
@@ -116,7 +147,64 @@ export default {
type: Boolean,
},
disabled: Boolean,
},
},
data() { return {
libraryCollections: this.model.allowedLibraryCollections,
libraries: this.model.allowedLibraries,
libraryWriteLoading: false,
libraryWriteError: undefined,
dirty: false, // If there are pending changes
}
},
watch: {
'model.allowedLibraryCollections': function (newVal) {
if (!this.dirty) this.libraryCollections = newVal;
},
'model.allowedLibraries': function (newVal) {
if (!this.dirty) this.libraries = newVal;
},
},
mounted() {
this.updateAllowedLibraryCollections = debounce(() => {
this.libraryWriteLoading = true;
this.dirty = false;
changeAllowedLibraries.call({
_id: this.model._id,
allowedLibraryCollections: this.libraryCollections,
}, error => {
this.libraryWriteLoading = false;
this.libraryWriteError = error;
});
}, 500);
this.updateAllowedLibraries = debounce(() => {
this.libraryWriteLoading = true;
this.dirty = false;
changeAllowedLibraries.call({
_id: this.model._id,
allowedLibraries: this.libraries,
}, error => {
this.libraryWriteLoading = false;
this.libraryWriteError = error;
});
}, 500);
},
meteor: {
$subscribe: {
'libraries': [],
},
librariesSelectedByCollections() {
let ids = [];
LibraryCollections.find({
_id: { $in: this.model.allowedLibraryCollections }
}).forEach(collection => {
ids = union(ids, collection.libraries);
});
return ids;
},
},
methods: {
changeShowTreeTab(value){
this.$emit('change', {
@@ -144,6 +232,30 @@ export default {
);
}
},
allUserLibrariesChange(val, ack) {
toggleAllUserLibraries.call({
_id: this.model._id,
val
}, error => ack(error));
},
selectLibrary(id, val) {
if (val) {
this.libraries = union(this.libraries, [id]);
} else {
this.libraries = without(this.libraries, id);
}
this.dirty = true;
this.updateAllowedLibraries();
},
selectLibraryCollection(id, val) {
if (val) {
this.libraryCollections = union(this.libraryCollections, [id]);
} else {
this.libraryCollections = without(this.libraryCollections, id);
}
this.dirty = true;
this.updateAllowedLibraryCollections();
},
},
};
</script>

View File

@@ -195,7 +195,7 @@ export default {
} else {
this.$store.commit(
'setTabForCharacterSheet',
{id: creatureId, tab: 4}
{id: creatureId, tab: 5}
);
this.$emit('pop', creatureId);
defer(() => {

View File

@@ -170,6 +170,7 @@
data: {
parentDoc: forcedType ? undefined : parent,
forcedType,
creatureId: this.creatureId,
},
callback(result){
if (!result){

View File

@@ -171,160 +171,166 @@
</template>
<script lang="js">
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
import LibraryNodeExpansionContent from '/imports/ui/library/LibraryNodeExpansionContent.vue';
import schemaFormMixin from '/imports/ui/properties/forms/shared/schemaFormMixin.js';
import propertyFormIndex from '/imports/ui/properties/forms/shared/propertyFormIndex.js';
import propertySchemasIndex from '/imports/api/properties/propertySchemasIndex.js';
import Libraries from '/imports/api/library/Libraries.js';
import getThemeColor from '/imports/ui/utility/getThemeColor.js';
import PropertySelector from '/imports/ui/properties/shared/PropertySelector.vue';
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
import LibraryNodeExpansionContent from '/imports/ui/library/LibraryNodeExpansionContent.vue';
import schemaFormMixin from '/imports/ui/properties/forms/shared/schemaFormMixin.js';
import propertyFormIndex from '/imports/ui/properties/forms/shared/propertyFormIndex.js';
import propertySchemasIndex from '/imports/api/properties/propertySchemasIndex.js';
import Libraries from '/imports/api/library/Libraries.js';
import getThemeColor from '/imports/ui/utility/getThemeColor.js';
import PropertySelector from '/imports/ui/properties/shared/PropertySelector.vue';
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
export default {
components: {
PropertySelector,
DialogBase,
TreeNodeView,
LibraryNodeExpansionContent,
...propertyFormIndex,
export default {
components: {
PropertySelector,
DialogBase,
TreeNodeView,
LibraryNodeExpansionContent,
...propertyFormIndex,
},
mixins: [schemaFormMixin],
props: {
creatureId: {
type: String,
default: undefined,
},
mixins: [schemaFormMixin],
props: {
forcedType: {
type: String,
default: undefined,
},
suggestedTypes: {
type: Array,
default: undefined,
},
suggestedType: {
type: String,
default: undefined,
},
parentDoc: {
type: Object,
default: undefined,
},
forcedType: {
type: String,
default: undefined,
},
reactiveProvide: {
name: 'context',
include: ['debounceTime'],
suggestedTypes: {
type: Array,
default: undefined,
},
data(){return {
selectedNodeIds: [],
type: this.forcedType || this.suggestedType,
model: {
type: this.type,
},
searchValue: undefined,
debounceTime: 0,
tab: 0,
};},
computed: {
typeName(){
return getPropertyName(this.type) || 'Property';
},
toolbarColor(){
return getThemeColor('secondary');
}
suggestedType: {
type: String,
default: undefined,
},
watch: {
type(newType){
this.changeType(newType);
},
parentDoc: {
type: Object,
default: undefined,
},
mounted(){
this.changeType(this.type);
},
reactiveProvide: {
name: 'context',
include: ['debounceTime'],
},
data(){return {
selectedNodeIds: [],
type: this.forcedType || this.suggestedType,
model: {
type: this.type,
},
methods: {
propertyHelpChanged(value){
Meteor.users.setPreference.call({
preference: 'hidePropertySelectDialogHelp',
value: !value
}, error => {
if (!error) return;
console.error(error);
snackbar({
text: error.reason,
});
});
},
searchChanged(val, ack){
this._subs.searchLibraryNodes.setData('searchTerm', val);
this._subs.searchLibraryNodes.setData('limit', undefined);
this.selectedNode = undefined;
this.searchValue = val;
setTimeout(ack, 200);
},
loadMore(){
if (this.currentLimit >= this.countAll) return;
this._subs.searchLibraryNodes.setData('limit', this.currentLimit + 32);
},
insert(){
if (!this.selectedNodeIds.length) return;
this.$store.dispatch('popDialogStack', this.selectedNodeIds);
},
changeType(type){
this._subs.searchLibraryNodes.setData('type', type);
if (!type) return;
this.tab = 1;
this.schema = propertySchemasIndex[type];
this.validationContext = this.schema.newContext();
let model = this.schema.clean({});
model.type = type;
this.model = model;
},
openPropertyDetails(id){
this.$store.commit('pushDialogStack', {
component: 'library-node-dialog',
elementId: id,
data: {
_id: id,
},
});
},
searchValue: undefined,
debounceTime: 0,
tab: 0,
};},
computed: {
typeName(){
return getPropertyName(this.type) || 'Property';
},
meteor: {
'$subscribe':{
'searchLibraryNodes': [],
'selectedLibraryNodes'(){
return [this.selectedNodeIds];
},
},
showPropertyHelp(){
let user = Meteor.user();
return !(user?.preferences?.hidePropertySelectDialogHelp)
},
currentLimit(){
return this._subs.searchLibraryNodes.data('limit') || 32;
},
countAll(){
return this._subs.searchLibraryNodes.data('countAll');
},
libraryNodes(){
return LibraryNodes.find({
_searchResult: true
},{
sort: {
'ancestors.0.id': 1,
name: 1,
order: 1,
},
});
},
libraryNames(){
let names = {};
Libraries.find().forEach(lib => names[lib._id] = lib.name)
return names;
}
toolbarColor(){
return getThemeColor('secondary');
}
};
},
watch: {
type(newType){
this.changeType(newType);
},
},
mounted(){
this.changeType(this.type);
},
methods: {
propertyHelpChanged(value){
Meteor.users.setPreference.call({
preference: 'hidePropertySelectDialogHelp',
value: !value
}, error => {
if (!error) return;
console.error(error);
snackbar({
text: error.reason,
});
});
},
searchChanged(val, ack){
this._subs.searchLibraryNodes.setData('searchTerm', val);
this._subs.searchLibraryNodes.setData('limit', undefined);
this.selectedNode = undefined;
this.searchValue = val;
setTimeout(ack, 200);
},
loadMore(){
if (this.currentLimit >= this.countAll) return;
this._subs.searchLibraryNodes.setData('limit', this.currentLimit + 32);
},
insert(){
if (!this.selectedNodeIds.length) return;
this.$store.dispatch('popDialogStack', this.selectedNodeIds);
},
changeType(type){
this._subs.searchLibraryNodes.setData('type', type);
if (!type) return;
this.tab = 1;
this.schema = propertySchemasIndex[type];
this.validationContext = this.schema.newContext();
let model = this.schema.clean({});
model.type = type;
this.model = model;
},
openPropertyDetails(id){
this.$store.commit('pushDialogStack', {
component: 'library-node-dialog',
elementId: id,
data: {
_id: id,
},
});
},
},
meteor: {
'$subscribe':{
'searchLibraryNodes'() {
return [this.creatureId]
},
'selectedLibraryNodes'(){
return [this.selectedNodeIds];
},
},
showPropertyHelp(){
let user = Meteor.user();
return !(user?.preferences?.hidePropertySelectDialogHelp)
},
currentLimit(){
return this._subs.searchLibraryNodes.data('limit') || 32;
},
countAll(){
return this._subs.searchLibraryNodes.data('countAll');
},
libraryNodes(){
return LibraryNodes.find({
_searchResult: true
},{
sort: {
'ancestors.0.id': 1,
name: 1,
order: 1,
},
});
},
libraryNames(){
let names = {};
Libraries.find().forEach(lib => names[lib._id] = lib.name)
return names;
}
}
};
</script>
<style lang="css" scoped>

View File

@@ -276,6 +276,7 @@ export default {
elementId: 'insert-creature-property-btn',
data: {
parentDoc: this.model,
creatureId: this.creatureId,
},
callback(result){
if (!result) return;