progress and autosize

This commit is contained in:
Dan Elbert 2024-10-02 16:20:07 -05:00
parent 67c23015ab
commit 7ead02ad7e
13 changed files with 139 additions and 99 deletions

View File

@ -1,6 +1,6 @@
<template> <template>
<div id="app"> <div id="app">
<vue-progress-bar></vue-progress-bar> <app-progress-bar></app-progress-bar>
<app-navbar></app-navbar> <app-navbar></app-navbar>
<section id="main" class=""> <section id="main" class="">
<div class="container"> <div class="container">
@ -24,6 +24,7 @@
import { useAppConfigStore } from "../stores/appConfig"; import { useAppConfigStore } from "../stores/appConfig";
import { useLoadResource } from "../lib/useLoadResource"; import { useLoadResource } from "../lib/useLoadResource";
import { useCheckAuthentication } from "../lib/useCheckAuthentication"; import { useCheckAuthentication } from "../lib/useCheckAuthentication";
import AppProgressBar from "./AppProgressBar.vue";
const globalTweenGroup = useGlobalTweenGroup(); const globalTweenGroup = useGlobalTweenGroup();
let animationLoop = true; let animationLoop = true;
@ -44,13 +45,6 @@
{ immediate: true } { immediate: true }
); );
watch(
() => appConfig.isLoading,
(val) => {
// Update Progress
}
)
onMounted(() => { onMounted(() => {
// Setup global animation loop // Setup global animation loop
function animate() { function animate() {

View File

@ -0,0 +1,69 @@
<template>
<div class="progress-bar" :style="progressStyle"></div>
</template>
<script setup>
import { computed, ref, watch } from "vue";
import { useAppConfigStore } from "../stores/appConfig";
import TWEEN from '@tweenjs/tween.js';
import { useGlobalTweenGroup } from "../lib/useGlobalTweenGroup";
const appConfig = useAppConfigStore();
const showProgress = ref(false);
const loadingPercent = ref(0);
let animation = null;
const progressStyle = computed(() => {
return {
opacity: showProgress.value ? "1" : "0",
width: `${loadingPercent.value}%`,
height: "4px"
};
});
watch(() => appConfig.isLoading, val => {
if (val) {
start();
} else {
stop();
}
});
function start() {
if (!animation) {
showProgress.value = true;
animation = new TWEEN.Tween({ percent: 0 }, useGlobalTweenGroup())
.to({ percent: 90 })
.easing(TWEEN.Easing.Quartic.Out)
.duration(3000)
.onUpdate(({ percent }) => { loadingPercent.value = percent; })
.onComplete(({ percent }) => {})
.start();
}
}
function stop() {
if (animation) {
showProgress.value = false;
animation.stop();
animation = null;
}
}
</script>
<style lang="scss" scoped>
@use "bulma/sass/utilities" as bulma;
.progress-bar {
position: fixed;
top: 0;
left: 0;
z-index: 999999;
background-color: bulma.$blue;
transition: width 0.1s, opacity 0.3s;
}
</style>

View File

@ -208,8 +208,8 @@ _underline_
<script setup> <script setup>
import { computed, ref, watch } from "vue"; import { computed, ref, useTemplateRef, watch } from "vue";
//import autosize from "autosize"; import { useAutosize } from "../lib/useAutosize";
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
import api from "../lib/Api"; import api from "../lib/Api";
import RecipeEditIngredientEditor from "./RecipeEditIngredientEditor"; import RecipeEditIngredientEditor from "./RecipeEditIngredientEditor";
@ -226,9 +226,12 @@ _underline_
} }
}); });
const stepTextArea = useTemplateRef("step_text_area");
const stepPreviewCache = ref(null); const stepPreviewCache = ref(null);
const isDescriptionHelpOpen = ref(false); const isDescriptionHelpOpen = ref(false);
useAutosize(stepTextArea);
const stepPreview = computed(() => { const stepPreview = computed(() => {
if (stepPreviewCache.value === null) { if (stepPreviewCache.value === null) {
return props.recipe.rendered_steps; return props.recipe.rendered_steps;

View File

@ -64,7 +64,7 @@
const autocompleteElement = useTemplateRef("autocomplete"); const autocompleteElement = useTemplateRef("autocomplete");
watch(props.ingredient.name, (val) => { watch(props.ingredient, (val) => {
if (props.ingredient.ingredient && props.ingredient.ingredient.name !== val) { if (props.ingredient.ingredient && props.ingredient.ingredient.name !== val) {
props.ingredient.ingredient_id = null; props.ingredient.ingredient_id = null;
props.ingredient.ingredient = null; props.ingredient.ingredient = null;

View File

@ -8,7 +8,7 @@
</div> </div>
<div class="buttons"> <div class="buttons">
<router-link v-if="isLoggedIn" class="button" :to="{name: 'edit_log', params: { id: logId }}">Edit</router-link> <router-link v-if="appConfig.isLoggedIn" class="button" :to="{name: 'edit_log', params: { id: logId }}">Edit</router-link>
<router-link class="button" to="/logs">Back</router-link> <router-link class="button" to="/logs">Back</router-link>
</div> </div>
</div> </div>
@ -21,9 +21,11 @@
import LogShow from "./LogShow"; import LogShow from "./LogShow";
import api from "../lib/Api"; import api from "../lib/Api";
import { useLoadResource } from "../lib/useLoadResource"; import { useLoadResource } from "../lib/useLoadResource";
import { useAppConfigStore } from "../stores/appConfig";
const { loadResource } = useLoadResource(); const { loadResource } = useLoadResource();
const route = useRoute(); const route = useRoute();
const appConfig = useAppConfigStore();
const log = ref(null); const log = ref(null);
const logId = computed(() => route.params.id); const logId = computed(() => route.params.id);

View File

@ -17,7 +17,7 @@
<recipe-show :recipe="recipe"></recipe-show> <recipe-show :recipe="recipe"></recipe-show>
</div> </div>
<router-link v-if="isLoggedIn" class="button" :to="{name: 'edit_recipe', params: { id: recipeId }}">Edit</router-link> <router-link v-if="appConfig.isLoggedIn" class="button" :to="{name: 'edit_recipe', params: { id: recipeId }}">Edit</router-link>
<router-link class="button" to="/">Back</router-link> <router-link class="button" to="/">Back</router-link>
</div> </div>
</template> </template>
@ -29,7 +29,9 @@
import RecipeShow from "./RecipeShow"; import RecipeShow from "./RecipeShow";
import api from "../lib/Api"; import api from "../lib/Api";
import { useLoadResource } from "../lib/useLoadResource"; import { useLoadResource } from "../lib/useLoadResource";
import { useAppConfigStore } from "../stores/appConfig";
const appConfig = useAppConfigStore();
const route = useRoute(); const route = useRoute();
const { loadResource } = useLoadResource(); const { loadResource } = useLoadResource();
const recipe = ref(null); const recipe = ref(null);

View File

@ -0,0 +1,35 @@
function clickStrikeClick(evt) {
const isStrikable = el => el && el.tagName === "LI";
const strikeClass = "is-strikethrough";
let t = evt.target;
while (t !== null && t !== this && !isStrikable(t)) {
t = t.parentElement;
}
if (isStrikable(t)) {
const classList = t.className.split(" ");
const strIdx = classList.findIndex(c => c === strikeClass);
if (strIdx >= 0) {
classList.splice(strIdx, 1);
} else {
classList.push(strikeClass);
}
t.className = classList.join(" ");
}
}
export function installClickStrike(app) {
app.directive('click-strike', {
beforeMount(el) {
el.addEventListener("click", clickStrikeClick);
},
unmounted(el) {
el.removeEventListener("click", clickStrikeClick);
}
});
}

View File

@ -1,83 +0,0 @@
import { mapActions, mapState } from "pinia";
import { useAppConfigStore } from "../stores/appConfig";
function clickStrikeClick(evt) {
const isStrikable = el => el && el.tagName === "LI";
const strikeClass = "is-strikethrough";
let t = evt.target;
while (t !== null && t !== this && !isStrikable(t)) {
t = t.parentElement;
}
if (isStrikable(t)) {
const classList = t.className.split(" ");
const strIdx = classList.findIndex(c => c === strikeClass);
if (strIdx >= 0) {
classList.splice(strIdx, 1);
} else {
classList.push(strikeClass);
}
t.className = classList.join(" ");
}
}
export function installMixins(app) {
app.directive('click-strike', {
beforeMount(el) {
el.addEventListener("click", clickStrikeClick);
},
unmounted(el) {
el.removeEventListener("click", clickStrikeClick);
}
});
app.mixin({
data() {
return {
localLoadingCount: 0
};
},
computed: {
...mapState(useAppConfigStore, [
"isLoading",
"isLoggedIn",
"isAdmin",
"user"
]),
localLoading() {
return this.localLoadingCount > 0;
}
},
methods: {
...mapActions(useAppConfigStore, [
'setError',
'setLoading',
'updateCurrentUser'
]),
loadResource(promise) {
this.setLoading(true);
this.localLoadingCount = this.localLoadingCount + 1;
return promise
.catch(err => this.setError(err))
.then(res => {
this.setLoading(false);
this.localLoadingCount = this.localLoadingCount - 1;
return res;
});
},
checkAuthentication() {
return this.loadResource(this.updateCurrentUser());
}
}
});
}

View File

@ -0,0 +1,9 @@
import { onBeforeUnmount } from "vue";
import autosize from 'autosize';
export function useAutosize(elementRef) {
autosize(elementRef.value);
onBeforeUnmount(() => {
autosize.destroy(elementRef.value);
});
}

View File

@ -3,7 +3,7 @@ import { createApp } from 'vue';
import { createPinia } from 'pinia'; import { createPinia } from 'pinia';
import { swInit } from "../lib/ServiceWorker"; import { swInit } from "../lib/ServiceWorker";
import config from '../lib/config'; import config from '../lib/config';
import { installMixins } from "../lib/GlobalMixins"; import { installClickStrike } from "../lib/ClickStrike";
import router from '../router'; import router from '../router';
import AppAutocomplete from "../components/AppAutocomplete"; import AppAutocomplete from "../components/AppAutocomplete";
@ -39,7 +39,7 @@ document.addEventListener('DOMContentLoaded', () => {
app.use(pinia); app.use(pinia);
app.use(router); app.use(router);
swInit(); swInit();
installMixins(app); installClickStrike(app);
app.component("AppAutocomplete", AppAutocomplete); app.component("AppAutocomplete", AppAutocomplete);
app.component("AppConfirm", AppConfirm); app.component("AppConfirm", AppConfirm);

View File

@ -14,7 +14,7 @@ module.exports = {
plugins: [ plugins: [
new VueLoaderPlugin(), new VueLoaderPlugin(),
new DefinePlugin({ new DefinePlugin({
__VUE_OPTIONS_API__: true, __VUE_OPTIONS_API__: false,
__VUE_PROD_DEVTOOLS__: env.isDevelopment, __VUE_PROD_DEVTOOLS__: env.isDevelopment,
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: env.isDevelopment __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: env.isDevelopment
}) })

View File

@ -25,6 +25,7 @@
"@types/babel__core": "7", "@types/babel__core": "7",
"@types/webpack": "5", "@types/webpack": "5",
"@vueuse/core": "^11.1.0", "@vueuse/core": "^11.1.0",
"autosize": "^6.0.1",
"babel-loader": "8", "babel-loader": "8",
"bulma": "^1.0.2", "bulma": "^1.0.2",
"cheerio": "^1.0.0", "cheerio": "^1.0.0",

View File

@ -2455,6 +2455,7 @@ __metadata:
"@types/babel__core": "npm:7" "@types/babel__core": "npm:7"
"@types/webpack": "npm:5" "@types/webpack": "npm:5"
"@vueuse/core": "npm:^11.1.0" "@vueuse/core": "npm:^11.1.0"
autosize: "npm:^6.0.1"
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"
@ -2495,6 +2496,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"autosize@npm:^6.0.1":
version: 6.0.1
resolution: "autosize@npm:6.0.1"
checksum: 10c0/7d1b9823443c2cc3fc962a7c20ec60d62db6a98c889a39f71495b48decbfda2463e6561fb7a748f069d6778e419f2fa1a7ecd09baa02dc33c4a209f777da9ac8
languageName: node
linkType: hard
"babel-loader@npm:8": "babel-loader@npm:8":
version: 8.4.1 version: 8.4.1
resolution: "babel-loader@npm:8.4.1" resolution: "babel-loader@npm:8.4.1"