progress and autosize
This commit is contained in:
parent
67c23015ab
commit
7ead02ad7e
@ -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() {
|
||||||
|
69
app/javascript/components/AppProgressBar.vue
Normal file
69
app/javascript/components/AppProgressBar.vue
Normal 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>
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
35
app/javascript/lib/ClickStrike.js
Normal file
35
app/javascript/lib/ClickStrike.js
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
9
app/javascript/lib/useAutosize.js
Normal file
9
app/javascript/lib/useAutosize.js
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
@ -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);
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
|
@ -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",
|
||||||
|
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user