Begin converting to composition api

This commit is contained in:
Dan Elbert 2024-09-29 13:35:49 -05:00
parent b957d44aed
commit a071e6b21e
17 changed files with 574 additions and 612 deletions

2
.gitignore vendored
View File

@ -9,7 +9,7 @@
# Ignore the default SQLite database. # Ignore the default SQLite database.
/db/*.sqlite3 /db/*.sqlite3
/db/*.sqlite3-journal /db/*.sqlite3*
# Ignore all logfiles and tempfiles. # Ignore all logfiles and tempfiles.
/log/* /log/*

View File

@ -9,7 +9,7 @@
<component v-if="!hasError" :is="Component" /> <component v-if="!hasError" :is="Component" />
<div v-else> <div v-else>
<h1>Error!</h1> <h1>Error!</h1>
<p>{{ error }}</p> <p>{{ appConfig.error }}</p>
</div> </div>
</transition> </transition>
</router-view> </router-view>
@ -18,70 +18,56 @@
</div> </div>
</template> </template>
<script> <script setup>
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import { mapState } from "pinia";
import { useGlobalTweenGroup } from "../lib/useGlobalTweenGroup"; import { useGlobalTweenGroup } from "../lib/useGlobalTweenGroup";
import { useAppConfigStore } from "../stores/appConfig"; import { useAppConfigStore } from "../stores/appConfig";
import { useLoadResource } from "../lib/useLoadResource";
import { useCheckAuthentication } from "../lib/useCheckAuthentication";
const globalTweenGroup = useGlobalTweenGroup(); const globalTweenGroup = useGlobalTweenGroup();
let animationLoop = true;
export default { const appConfig = useAppConfigStore();
data() { const hasError = computed(() => appConfig.error !== null);
return {
};
},
computed: { const { loadResource } = useLoadResource();
...mapState(useAppConfigStore, { const { checkAuthentication } = useCheckAuthentication(loadResource);
hasError: store => store.error !== null,
error: store => store.error,
authChecked: store => store.authChecked,
initialLoad: store => store.initialLoad
})
},
watch: { watch(
isLoading(val) { () => appConfig.initialLoad,
(val) => {
if (val) { if (val) {
// this.$Progress.start(); nextTick(() => document.body.classList.remove("loading"));
} else {
// this.$Progress.finish();
} }
}, },
{ immediate: true }
);
initialLoad(val) { watch(
if (val) { () => appConfig.isLoading,
this.$nextTick(() => { (val) => {
document.body.classList.remove("loading"); // Update Progress
});
} }
} )
},
created() { onMounted(() => {
// Setup global animation loop // Setup global animation loop
function animate(time) { function animate() {
globalTweenGroup.update(time); if (animationLoop) {
globalTweenGroup.update();
requestAnimationFrame(animate); requestAnimationFrame(animate);
} }
}
animate(); animate();
if (this.user === null && this.authChecked === false) { if (appConfig.user === null && appConfig.authChecked === false) {
this.checkAuthentication(); checkAuthentication();
} }
},
mounted() {
if (this.initialLoad) {
this.$nextTick(() => {
document.body.classList.remove("loading");
}); });
}
},
components: { onUnmounted(() => {
} animationLoop = false;
} });
</script> </script>

View File

@ -27,12 +27,14 @@
</div> </div>
</template> </template>
<script> <script setup>
import { computed, ref, watch } from "vue";
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
export default { const emit = defineEmits(["update:modelValue", "inputClick", "optionSelected"]);
props: {
const props = defineProps({
modelValue: String, modelValue: String,
id: String, id: String,
placeholder: String, placeholder: String,
@ -58,163 +60,145 @@
onGetOptions: Function, onGetOptions: Function,
searchOptions: Array searchOptions: Array
}, });
data() { const options = ref([]);
return { const rawValue = ref("");
options: [], const isListOpen = ref(false);
rawValue: "", const activeListIndex = ref(0);
isListOpen: false,
activeListIndex: 0
}
},
created() { const finalInputClass = computed(() => {
this.rawValue = this.modelValue;
},
watch: {
modelValue(newValue) {
this.rawValue = newValue;
}
},
computed: {
finalInputClass() {
let cls = ['input']; let cls = ['input'];
if (this.inputClass === null) { if (props.inputClass === null) {
return cls; return cls;
} else if (Array.isArray(this.inputClass)) { } else if (Array.isArray(props.inputClass)) {
return cls.concat(this.inputClass); return cls.concat(props.inputClass);
} else { } else {
cls.push(this.inputClass); cls.push(props.inputClass);
return cls; return cls;
} }
}, });
debouncedUpdateOptions() { watch(
return debounce(this.updateOptions, this.debounce); () => props.modelValue,
(newValue) => { rawValue.value = newValue; },
{ immediate: true }
);
function optionClass(idx) {
return activeListIndex.value === idx ? 'option active' : 'option';
} }
},
methods: { function optionClick(opt) {
optionClass(idx) { selectOption(opt);
return this.activeListIndex === idx ? 'option active' : 'option'; }
},
optionClick(opt) { function optionKey(opt) {
this.selectOption(opt); if (props.keyAttribute) {
}, return opt[props.keyAttribute]
} else if (props.valueAttribute) {
optionKey(opt) { return opt[props.valueAttribute];
if (this.keyAttribute) {
return opt[this.keyAttribute]
} else if (this.valueAttribute) {
return opt[this.valueAttribute];
} else { } else {
return opt.toString(); return opt.toString();
} }
}, }
optionValue(opt) { function optionValue(opt) {
if (this.valueAttribute) { if (props.valueAttribute) {
return opt[this.valueAttribute]; return opt[props.valueAttribute];
} else { } else {
return opt.toString(); return opt.toString();
} }
}, }
optionLabel(opt) { function optionLabel(opt) {
if (this.labelAttribute) { if (props.labelAttribute) {
return opt[this.labelAttribute]; return opt[props.labelAttribute];
} else { } else {
return null; return null;
} }
}, }
optionMousemove(idx) { function optionMousemove(idx) {
this.activeListIndex = idx; activeListIndex.value = idx;
}, }
clickHandler(evt) { function clickHandler(evt) {
this.$emit("inputClick", evt); emit('inputClick', evt);
}, }
blurHandler(evt) { function blurHandler(evt) {
// blur fires before click. If the blur was fired because the user clicked a list item, immediately hiding the list here // blur fires before click. If the blur was fired because the user clicked a list item, immediately hiding the list here
// would prevent the click event from firing // would prevent the click event from firing
setTimeout(() => { setTimeout(() => {
this.isListOpen = false; isListOpen.value = false;
},250); },250);
}, }
inputHandler(evt) { function inputHandler(evt) {
const newValue = evt.target.value; const newValue = evt.target.value;
if (this.rawValue !== newValue) { if (rawValue.value !== newValue) {
this.rawValue = newValue; rawValue.value = newValue;
this.$emit("update:modelValue", newValue); emit("update:modelValue", newValue);
if (newValue.length >= Math.max(1, this.minLength)) { if (newValue.length >= Math.max(1, props.minLength)) {
this.debouncedUpdateOptions(newValue); this.updateOptions(newValue);
} else { } else {
this.isListOpen = false; isListOpen.value = false;
}
} }
} }
},
keydownHandler(evt) { function keydownHandler(evt) {
if (this.isListOpen === false) if (isListOpen.value === false)
return; return;
switch (evt.key) { switch (evt.key) {
case "ArrowUp": case "ArrowUp":
evt.preventDefault(); evt.preventDefault();
this.activeListIndex = Math.max(0, this.activeListIndex - 1); activeListIndex.value = Math.max(0, activeListIndex.value - 1);
break; break;
case "ArrowDown": case "ArrowDown":
evt.preventDefault(); evt.preventDefault();
this.activeListIndex = Math.min(this.options.length - 1, this.activeListIndex + 1); activeListIndex.value = Math.min(options.value.length - 1, activeListIndex.value + 1);
break; break;
case "Enter": case "Enter":
evt.preventDefault(); evt.preventDefault();
this.selectOption(this.options[this.activeListIndex]); selectOption(options.value[activeListIndex.value]);
break; break;
case "Escape": case "Escape":
evt.preventDefault(); evt.preventDefault();
this.isListOpen = false; isListOpen.value = false;
break; break;
} }
}, }
selectOption(opt) { function selectOption(opt) {
this.rawValue = this.optionValue(opt); rawValue.value = optionValue(opt);
this.$emit("update:modelValue", this.rawValue); emit("update:modelValue", rawValue.value);
this.$emit("optionSelected", opt); emit("optionSelected", opt);
this.isListOpen = false; isListOpen.value = false;
}, }
updateOptions(value) { const updateOptions = debounce(function(value) {
let p = null; let p = null;
if (this.searchOptions) { if (props.searchOptions) {
const reg = new RegExp("^" + value, "i"); const reg = new RegExp("^" + value, "i");
const matcher = o => reg.test(this.optionValue(o)); const matcher = o => reg.test(optionValue(o));
p = Promise.resolve(this.searchOptions.filter(matcher)); p = Promise.resolve(props.searchOptions.filter(matcher));
} else { } else {
p = this.onGetOptions(value) p = props.onGetOptions(value)
} }
p.then(opts => { p.then(opts => {
this.options = opts; options.value = opts;
this.isListOpen = opts.length > 0; isListOpen.value = opts.length > 0;
this.activeListIndex = 0; activeListIndex.value = 0;
}) })
} }, props.debounce);
}
}
</script> </script>

View File

@ -7,10 +7,9 @@
</app-modal> </app-modal>
</template> </template>
<script> <script setup>
export default { const props = defineProps({
props: {
cancel: { cancel: {
type: Function, type: Function,
required: true required: true
@ -31,17 +30,14 @@
type: Boolean, type: Boolean,
required: true required: true
} }
}, });
methods: { function runConfirm() {
runConfirm() { props.confirm();
this.confirm(); }
},
runCancel() { function runCancel() {
this.cancel(); props.cancel();
}
}
} }
</script> </script>

View File

@ -2,12 +2,13 @@
<app-text-field :value="stringValue" @input="input" :label="label" type="date"></app-text-field> <app-text-field :value="stringValue" @input="input" :label="label" type="date"></app-text-field>
</template> </template>
<script> <script setup>
import { computed } from "vue";
import DateTimeUtils from "../lib/DateTimeUtils"; import DateTimeUtils from "../lib/DateTimeUtils";
export default { const emit = defineEmits(["update:modelValue"]);
props: { const props = defineProps({
modelValue: { modelValue: {
required: false, required: false,
type: [Date, String] type: [Date, String]
@ -18,21 +19,16 @@
type: String, type: String,
default: null default: null
} }
}, });
computed: { const stringValue = computed(() => {
stringValue() { const d = DateTimeUtils.toDate(props.modelValue);
const d = DateTimeUtils.toDate(this.modelValue);
return DateTimeUtils.formatDateForEdit(d); return DateTimeUtils.formatDateForEdit(d);
} });
},
methods: { function input(val) {
input(val) {
let d = DateTimeUtils.toDate(val + " 00:00"); let d = DateTimeUtils.toDate(val + " 00:00");
this.$emit("update:modelValue", d); emit("update:modelValue", d);
}
}
} }
</script> </script>

View File

@ -5,12 +5,12 @@
</span> </span>
</template> </template>
<script> <script setup>
import { computed } from "vue";
import DateTimeUtils from "../lib/DateTimeUtils"; import DateTimeUtils from "../lib/DateTimeUtils";
export default { const props = defineProps({
props: {
dateTime: { dateTime: {
required: true, required: true,
type: [Date, String] type: [Date, String]
@ -33,29 +33,21 @@
type: Boolean, type: Boolean,
default: false default: false
} }
}, });
computed: { const dateObj = computed(() => DateTimeUtils.toDate(props.dateTime));
dateObj() { const fullString = computed(() => DateTimeUtils.formatTimestamp(dateObj.value));
return DateTimeUtils.toDate(this.dateTime);
},
friendlyString() { const friendlyString = computed(() => {
const parts = []; const parts = [];
if (this.showDate) { if (props.showDate) {
parts.push(DateTimeUtils.formatDate(this.dateObj)); parts.push(DateTimeUtils.formatDate(dateObj.value));
} }
if (this.showTime) { if (props.showTime) {
parts.push(DateTimeUtils.formatTime(this.dateObj, true)); parts.push(DateTimeUtils.formatTime(dateObj.value, true));
} }
return parts.join(" "); return parts.join(" ");
}, });
fullString() {
return DateTimeUtils.formatTimestamp(this.dateObj);
}
}
}
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="dropdown" :class="{'is-active': open, 'is-hoverable': hover}"> <div ref="dropdown" class="dropdown" :class="{'is-active': open, 'is-hoverable': hover}">
<div class="dropdown-trigger"> <div class="dropdown-trigger">
<slot name="button"> <slot name="button">
<button type="button" class="button" :class="buttonClass" @click="toggle"> <button type="button" class="button" :class="buttonClass" @click="toggle">
@ -19,10 +19,13 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { import { useTemplateRef } from "vue";
props: { import { onClickOutside } from '@vueuse/core'
const emit = defineEmits(["close", "open"]);
const props = defineProps({
open: { open: {
required: false, required: false,
type: Boolean, type: Boolean,
@ -45,40 +48,33 @@
required: false, required: false,
default: "" default: ""
} }
}, });
methods: { const dropdownElement = useTemplateRef("dropdown");
toggle() {
if (this.open) { onClickOutside(dropdownElement, event => handleOutsideClick(event))
this.triggerClose();
function toggle() {
if (props.open) {
triggerClose();
} else { } else {
this.triggerOpen(); triggerOpen();
}
},
triggerOpen() {
this.$emit("open");
},
triggerClose() {
this.$emit("close");
},
handleOutsideClick(evt) {
if (this.open) {
if (!this.$el.contains(evt.target)) {
this.triggerClose();
} }
} }
function triggerOpen() {
emit("open");
} }
},
mounted() { function triggerClose() {
document.addEventListener("click", this.handleOutsideClick); emit("close");
}, }
beforeDestroy() { function handleOutsideClick(evt) {
document.removeEventListener("click", this.handleOutsideClick); if (props.open) {
if (!dropdownElement.value.contains(evt.target)) {
triggerClose();
}
} }
} }

View File

@ -9,34 +9,28 @@
</transition> </transition>
</template> </template>
<script> <script setup>
import TWEEN from '@tweenjs/tween.js'; import TWEEN from '@tweenjs/tween.js';
import { useGlobalTweenGroup } from "../lib/useGlobalTweenGroup"; import { useGlobalTweenGroup } from "../lib/useGlobalTweenGroup";
export default { const props = defineProps({
props: {
expandTime: { expandTime: {
type: Number, type: Number,
default: 250 default: 250
} }
}, });
data() { let animation = null;
return {
animation: null function cancel () {
if (animation) {
animation.stop();
animation = null;
} }
},
methods: {
cancel () {
if (this.animation) {
this.animation.stop();
this.animation = null;
} }
},
enter(element, done) { function enter(element, done) {
const width = parseInt(getComputedStyle(element).width); const width = parseInt(getComputedStyle(element).width);
const paddingTop = parseInt(getComputedStyle(element).paddingTop); const paddingTop = parseInt(getComputedStyle(element).paddingTop);
const paddingBottom = parseInt(getComputedStyle(element).paddingBottom); const paddingBottom = parseInt(getComputedStyle(element).paddingBottom);
@ -54,15 +48,15 @@
element.style.overflow = 'hidden'; element.style.overflow = 'hidden';
element.style.height = 0; element.style.height = 0;
this.animation = new TWEEN.Tween({height: 0, paddingTop: 0, paddingBottom: 0}) animation = new TWEEN.Tween({height: 0, paddingTop: 0, paddingBottom: 0})
.to({height: height, paddingTop: paddingTop, paddingBottom: paddingBottom}, this.expandTime) .to({height: height, paddingTop: paddingTop, paddingBottom: paddingBottom}, props.expandTime)
.onUpdate(obj => { .onUpdate(obj => {
element.style.height = obj.height + "px"; element.style.height = obj.height + "px";
element.style.paddingBottom = obj.paddingBottom + "px"; element.style.paddingBottom = obj.paddingBottom + "px";
element.style.paddingTop = obj.paddingTop + "px"; element.style.paddingTop = obj.paddingTop + "px";
}) })
.onComplete(() => { .onComplete(() => {
this.animation = null; animation = null;
element.removeAttribute('style'); element.removeAttribute('style');
element.style.opacity = 0.99; element.style.opacity = 0.99;
setTimeout(() => { setTimeout(() => {
@ -73,32 +67,30 @@
}) })
.group(useGlobalTweenGroup()) .group(useGlobalTweenGroup())
.start(); .start();
}, }
leave(element, done) { function leave(element, done) {
const height = parseInt(getComputedStyle(element).height); const height = parseInt(getComputedStyle(element).height);
const paddingTop = parseInt(getComputedStyle(element).paddingTop); const paddingTop = parseInt(getComputedStyle(element).paddingTop);
const paddingBottom = parseInt(getComputedStyle(element).paddingBottom); const paddingBottom = parseInt(getComputedStyle(element).paddingBottom);
element.style.overflow = 'hidden'; element.style.overflow = 'hidden';
this.animation = new TWEEN.Tween({height: height, paddingTop: paddingTop, paddingBottom: paddingBottom}) animation = new TWEEN.Tween({height: height, paddingTop: paddingTop, paddingBottom: paddingBottom})
.to({height: 0, paddingTop: 0, paddingBottom: 0}, this.expandTime) .to({height: 0, paddingTop: 0, paddingBottom: 0}, props.expandTime)
.onUpdate(obj => { .onUpdate(obj => {
element.style.height = obj.height + "px"; element.style.height = obj.height + "px";
element.style.paddingBottom = obj.paddingBottom + "px"; element.style.paddingBottom = obj.paddingBottom + "px";
element.style.paddingTop = obj.paddingTop + "px"; element.style.paddingTop = obj.paddingTop + "px";
}) })
.onComplete(() => { .onComplete(() => {
this.animation = null; animation = null;
element.removeAttribute('style'); element.removeAttribute('style');
done(); done();
}) })
.group(useGlobalTweenGroup()) .group(useGlobalTweenGroup())
.start(); .start();
} }
}
}
</script> </script>

View File

@ -6,6 +6,8 @@
<script> <script>
import { computed } from "vue";
class IconData { class IconData {
constructor(iconicIcon, dataAttributes) { constructor(iconicIcon, dataAttributes) {
this.iconicIcon = iconicIcon; this.iconicIcon = iconicIcon;
@ -49,6 +51,7 @@
}; };
export default { export default {
emits: ["click"],
props: { props: {
icon: { icon: {
validator: (i) => iconMap[i] !== undefined validator: (i) => iconMap[i] !== undefined
@ -66,38 +69,21 @@
} }
}, },
data() { setup(props) {
const iconData = computed(() => iconMap[props.icon]);
const sizeData = computed(() => sizeMap[props.size]);
const iconClasses = computed(() => [sizeData.value.bulmaIconClass, sizeData.value.customIconClass]);
const iconicSize = computed(() => sizeData.value.iconicSize);
const iconicIcon = computed(() => iconData.value.iconicIcon);
const iconicAttributes = computed(() => iconData.value.dataAttributes);
return { return {
injectedSvg: null iconClasses,
} iconData,
}, sizeData,
iconicAttributes,
computed: { iconicIcon,
iconData() { iconicSize
return iconMap[this.icon];
},
sizeData() {
return sizeMap[this.size];
},
iconClasses() {
return [
this.sizeData.bulmaIconClass,
this.sizeData.customIconClass
]
},
iconicSize() {
return this.sizeData.iconicSize;
},
iconicIcon() {
return this.iconData.iconicIcon;
},
iconicAttributes() {
return this.iconData.dataAttributes;
} }
} }
} }

View File

@ -1,9 +1,11 @@
<template> <template>
<svg v-bind="svgAttributes" v-html="svgContent" :class="calculatedClasses"></svg> <svg ref="svg" v-bind="svgAttributes" v-html="svgContent" :class="calculatedClasses"></svg>
</template> </template>
<script> <script>
import { computed, nextTick, useTemplateRef, watch } from "vue";
import Caret from "../iconic/svg/smart/caret"; import Caret from "../iconic/svg/smart/caret";
import Check from "../iconic/svg/smart/check"; import Check from "../iconic/svg/smart/check";
import CircleCheck from "../iconic/svg/smart/circle-check"; import CircleCheck from "../iconic/svg/smart/circle-check";
@ -68,29 +70,15 @@
} }
}, },
data() { setup(props) {
return { const svgElement = useTemplateRef("svg");
calculatedClasses: [] const svgData = computed(() => iconMap[props.icon]);
} const svgAttributes = computed(() => svgData.value.attributes);
}, const svgName = computed(() => svgAttributes.value['data-icon']);
const svgContent = computed(() => {
let content = String(svgData.value.content);
computed: { for (let idRep of svgData.value.idReplacements) {
svgData() {
return iconMap[this.icon];
},
svgAttributes() {
return this.svgData.attributes;
},
svgName() {
return this.svgAttributes['data-icon'];
},
svgContent() {
let content = String(this.svgData.content);
for (let idRep of this.svgData.idReplacements) {
let newId = `__new_id_${globalIdCounter}`; let newId = `__new_id_${globalIdCounter}`;
globalIdCounter += 1; globalIdCounter += 1;
@ -98,63 +86,66 @@
} }
return content; return content;
} });
},
methods: { const calculatedClasses = computed(() => {
const classes = (svgAttributes.value.class || "").split(" ");
rebind() { classes.push(`iconic-${props.size}`);
const apis = APIS;
if (apis && this.svgName && apis[this.svgName]) { if (props.iconSizeOverride) {
const iconApi = apis[this.svgName](this.$el); classes.push(`iconic-icon-${props.iconSizeOverride}`);
for (let func in iconApi) this.$el[func] = iconApi[func]
} else {
this.$el.update = function() {}
} }
this.calculatedClasses = (this.svgAttributes.class || "").split(" "); if (props.displaySizeOverride) {
classes.push(`iconic-size-${props.displaySizeOverride}`);
this.calculatedClasses.push(`iconic-${this.size}`);
if (this.iconSizeOverride) {
this.calculatedClasses.push(`iconic-icon-${this.iconSizeOverride}`);
} }
if (this.displaySizeOverride) { return classes;
this.calculatedClasses.push(`iconic-size-${this.displaySizeOverride}`); });
}
this.$el.update(); function ensureSvgApi(name, scripts) {
} if (!name) { return; }
}, if (LOADED_APIS[name] !== true) {
for (let sb of scripts) {
created() {
if (LOADED_APIS[this.svgName] !== true) {
for (let sb of this.svgData.scriptBlocks) {
try { try {
new Function(sb)(window); new Function(sb)(window);
} catch (e) { } catch (e) {
console.log(sb); console.log(sb);
console.log(e); console.log(e);
} }
}
LOADED_APIS[name] = true;
}
}
function setupSvgApi(name) {
const apis = APIS;
if (apis && apis[name]) {
const iconApi = apis[name](svgElement.value);
for (let func in iconApi) svgElement.value[func] = iconApi[func]
} else {
svgElement.value.update = function() {}
} }
LOADED_APIS[this.svgName] = true; svgElement.value.update();
} }
watch(
() => props.icon,
() => {
ensureSvgApi(svgName.value, svgData.value.scriptBlocks);
nextTick(() => setupSvgApi(svgName.value));
}, },
{ immediate: true }
mounted() {
this.$watch(
function() { return [this.$attrs, this.icon, this.fluid] },
function() {
this.rebind()
},
{
immediate: true
}
); );
return {
svgData,
svgAttributes,
svgName,
svgContent,
calculatedClasses
};
} }
} }

View File

@ -4,10 +4,7 @@
</div> </div>
</template> </template>
<script> <script setup>
export default {
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -18,15 +18,14 @@
</Teleport> </Teleport>
</template> </template>
<script> <script setup>
import { mapState } from "pinia"; import { computed } from "vue";
import { useAppConfigStore } from "../stores/appConfig"; import { useAppConfigStore } from "../stores/appConfig";
export default { const emit = defineEmits(["dismiss"]);
emits: ["dismiss"],
props: { const props = defineProps({
open: { open: {
type: Boolean, type: Boolean,
default: false default: false
@ -36,22 +35,13 @@
type: Boolean, type: Boolean,
default: false default: false
} }
}, });
computed: { const appConfig = useAppConfigStore();
...mapState(useAppConfigStore, [ const error = computed(() => appConfig.error);
'error'
])
},
methods: { function close() {
close() { emit("dismiss");
this.$emit("dismiss");
}
},
components: {
}
} }
</script> </script>

View File

@ -51,47 +51,27 @@
</nav> </nav>
</template> </template>
<script> <script setup>
import { ref, watch } from "vue";
import UserLogin from "./UserLogin"; import UserLogin from "./UserLogin";
import { mapState } from "pinia"; import { storeToRefs } from "pinia";
import { useAppConfigStore } from "../stores/appConfig"; import { useAppConfigStore } from "../stores/appConfig";
import { swUpdate } from "../lib/ServiceWorker"; import { swUpdate } from "../lib/ServiceWorker";
import { useRoute } from "vue-router";
export default { const appConfig = useAppConfigStore();
data() { const menuActive = ref(false);
return { const route = useRoute();
menuActive: false const { isAdmin, isLoggedIn, updateAvailable, user } = storeToRefs(appConfig);
};
},
computed: { function updateApp() {
...mapState(useAppConfigStore, [
'route',
'user',
'updateAvailable'
])
},
methods: {
updateApp() {
swUpdate(); swUpdate();
} }
},
watch: { watch(
route() { () => [route, appConfig.user],
this.menuActive = false; () => menuActive.value = false
}, );
user() {
this.menuActive = false;
}
},
components: {
UserLogin
}
}
</script> </script>

View File

@ -0,0 +1,12 @@
import { useAppConfigStore } from "../stores/appConfig";
export function useCheckAuthentication(loadResource) {
const appConfig = useAppConfigStore();
const checkAuthentication = function() {
return loadResource(appConfig.updateCurrentUser());
}
return {
checkAuthentication
}
}

View File

@ -0,0 +1,27 @@
import { computed, ref } from "vue";
import { useAppConfigStore } from "../stores/appConfig";
export function useLoadResource() {
const appConfig = useAppConfigStore();
const localLoadingCount = ref(0);
const localLoading = computed(() => localLoadingCount.value > 0);
const loadResource = async (promise) => {
appConfig.setLoading(true);
localLoadingCount.value = localLoadingCount.value + 1;
try {
return await promise;
} catch (error) {
appConfig.setError(error);
} finally {
appConfig.setLoading(false);
localLoadingCount.value = localLoadingCount.value - 1;
}
};
return {
loadResource,
localLoading,
localLoadingCount
};
}

View File

@ -24,6 +24,7 @@
"@tweenjs/tween.js": "^25.0.0", "@tweenjs/tween.js": "^25.0.0",
"@types/babel__core": "7", "@types/babel__core": "7",
"@types/webpack": "5", "@types/webpack": "5",
"@vueuse/core": "^11.1.0",
"babel-loader": "8", "babel-loader": "8",
"bulma": "^1.0.2", "bulma": "^1.0.2",
"cheerio": "^1.0.0", "cheerio": "^1.0.0",

View File

@ -1895,6 +1895,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/web-bluetooth@npm:^0.0.20":
version: 0.0.20
resolution: "@types/web-bluetooth@npm:0.0.20"
checksum: 10c0/3a49bd9396506af8f1b047db087aeeea9fe4301b7fad4fe06ae0f6e00d331138caae878fd09e6410658b70b4aaf10e4b191c41c1a5ff72211fe58da290c7d003
languageName: node
linkType: hard
"@types/webpack@npm:5": "@types/webpack@npm:5":
version: 5.28.5 version: 5.28.5
resolution: "@types/webpack@npm:5.28.5" resolution: "@types/webpack@npm:5.28.5"
@ -2038,6 +2045,34 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@vueuse/core@npm:^11.1.0":
version: 11.1.0
resolution: "@vueuse/core@npm:11.1.0"
dependencies:
"@types/web-bluetooth": "npm:^0.0.20"
"@vueuse/metadata": "npm:11.1.0"
"@vueuse/shared": "npm:11.1.0"
vue-demi: "npm:>=0.14.10"
checksum: 10c0/ecbeb277de81608c78aa4ebc7e4cae8a6d5f0650e2ee5ed71bf387676df4725bd2e8bcfeadcc0798ab6850e6317ff79822ef32adef4646184086764fe7f684ac
languageName: node
linkType: hard
"@vueuse/metadata@npm:11.1.0":
version: 11.1.0
resolution: "@vueuse/metadata@npm:11.1.0"
checksum: 10c0/5063d8b81c31e3c7ea24ff7fad0d0ec43a951540066329f36daf96fc0e8780ed902282d36c6df856288f07d0c5edb525f551cf9e8b84837782e2ac0a1988c498
languageName: node
linkType: hard
"@vueuse/shared@npm:11.1.0":
version: 11.1.0
resolution: "@vueuse/shared@npm:11.1.0"
dependencies:
vue-demi: "npm:>=0.14.10"
checksum: 10c0/ed9a4625537825fe783c66823592560696604f5301e8dcef12dda3a816b85334e7c70ccbaaf1c0c62ea2b8efcce58893b780210162374d2e9bc1e4c5889e066b
languageName: node
linkType: hard
"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": "@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1":
version: 1.12.1 version: 1.12.1
resolution: "@webassemblyjs/ast@npm:1.12.1" resolution: "@webassemblyjs/ast@npm:1.12.1"
@ -2419,6 +2454,7 @@ __metadata:
"@tweenjs/tween.js": "npm:^25.0.0" "@tweenjs/tween.js": "npm:^25.0.0"
"@types/babel__core": "npm:7" "@types/babel__core": "npm:7"
"@types/webpack": "npm:5" "@types/webpack": "npm:5"
"@vueuse/core": "npm:^11.1.0"
babel-loader: "npm:8" babel-loader: "npm:8"
bulma: "npm:^1.0.2" bulma: "npm:^1.0.2"
cheerio: "npm:^1.0.0" cheerio: "npm:^1.0.0"
@ -6606,7 +6642,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"vue-demi@npm:^0.14.10": "vue-demi@npm:>=0.14.10, vue-demi@npm:^0.14.10":
version: 0.14.10 version: 0.14.10
resolution: "vue-demi@npm:0.14.10" resolution: "vue-demi@npm:0.14.10"
peerDependencies: peerDependencies: