replaced vue3-promise-dialog for internal component

This commit is contained in:
2023-03-24 11:45:12 +10:00
parent ad5b47f2a5
commit 85cfdfd24f
17 changed files with 279 additions and 203 deletions

11
package-lock.json generated
View File

@@ -21,8 +21,7 @@
"vue-loader": "^17.0.1", "vue-loader": "^17.0.1",
"vue-recaptcha-v3": "^2.0.1", "vue-recaptcha-v3": "^2.0.1",
"vue-router": "^4.1.6", "vue-router": "^4.1.6",
"vue3-easy-data-table": "^1.5.24", "vue3-easy-data-table": "^1.5.24"
"vue3-promise-dialog": "^0.3.4"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.48.1", "@typescript-eslint/eslint-plugin": "^5.48.1",
@@ -4220,14 +4219,6 @@
"vue": "^3.2.45" "vue": "^3.2.45"
} }
}, },
"node_modules/vue3-promise-dialog": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/vue3-promise-dialog/-/vue3-promise-dialog-0.3.4.tgz",
"integrity": "sha512-JquYYE+1lxuGgncD8Q+8KTFAWUJsNWnhuYPSsOFRKzz3kz66Rjz5pdlEW6GLfUlV3cKak9T8b3mV49B4ouEgVg==",
"peerDependencies": {
"vue": "^3.x"
}
},
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",

View File

@@ -43,7 +43,6 @@
"vue-loader": "^17.0.1", "vue-loader": "^17.0.1",
"vue-recaptcha-v3": "^2.0.1", "vue-recaptcha-v3": "^2.0.1",
"vue-router": "^4.1.6", "vue-router": "^4.1.6",
"vue3-easy-data-table": "^1.5.24", "vue3-easy-data-table": "^1.5.24"
"vue3-promise-dialog": "^0.3.4"
} }
} }

View File

@@ -95,3 +95,24 @@ code {
} }
} }
} }
/* SM Dialog */
.sm-dialog-outer {
position: fixed;
display: flex;
top: 0;
left: 0;
bottom: 0;
right: 0;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
}
.sm-dialog-outer:last-of-type {
background-color: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
}

View File

@@ -0,0 +1,114 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
AllowedComponentProps,
Component,
defineComponent,
shallowReactive,
VNodeProps,
} from "vue";
export interface DialogInstance {
comp?: any;
dialog: Component;
wrapper: string;
props: unknown;
resolve: (data: unknown) => void;
}
const dialogRefs = shallowReactive<DialogInstance[]>([]);
export default defineComponent({
name: "SMDialogList",
template: `
<div class="sm-dialog-list">
<div v-for="(dialogRef, index) in dialogRefList" :key="index" class="sm-dialog-outer">
<component
:is="dialogRef.dialog"
v-if="dialogRef && dialogRef.wrapper === name"
v-bind="dialogRef.props"
:ref="(ref) => (dialogRef.comp = ref)"></component>
</div>
</div>
`,
data() {
const dialogRefList = dialogRefs;
return {
name: "default",
transitionAttrs: {},
dialogRefList,
};
},
});
/**
* Closes last opened dialog, resolving the promise with the return value of the dialog, or with the given
* data if any.
*
* @param {unknown} data The dialog return value.
*/
export function closeDialog(data?: unknown) {
if (dialogRefs.length <= 1) {
document.getElementsByTagName("html")[0].style.overflow = "";
document.getElementsByTagName("body")[0].style.overflow = "";
}
const lastDialog = dialogRefs.pop();
if (data === undefined) {
data = lastDialog.comp.returnValue();
}
lastDialog.resolve(data);
}
/**
* Extracts the type of props from a component definition.
*/
type PropsType<C extends Component> = C extends new (...args: any) => any
? Omit<
InstanceType<C>["$props"],
keyof VNodeProps | keyof AllowedComponentProps
>
: never;
/**
* Extracts the return type of the dialog from the setup function.
*/
type BindingReturnType<C extends Component> = C extends new (
...args: any
) => any
? InstanceType<C> extends { returnValue: () => infer Y }
? Y
: never
: never;
/**
* Extracts the return type of the dialog either from the setup method or from the methods.
*/
type ReturnType<C extends Component> = BindingReturnType<C>;
/**
* Opens a dialog.
*
* @param {Component} dialog The dialog you want to open.
* @param {PropsType} props The props to be passed to the dialog.
* @param {string} wrapper The dialog wrapper you want the dialog to open into.
* @returns {Promise} A promise that resolves when the dialog is closed
*/
export function openDialog<C extends Component>(
dialog: C,
props?: PropsType<C>,
wrapper: string = "default"
): Promise<ReturnType<C>> {
if (dialogRefs.length === 0) {
document.getElementsByTagName("html")[0].style.overflow = "hidden";
document.getElementsByTagName("body")[0].style.overflow = "hidden";
}
return new Promise((resolve) => {
dialogRefs.push({
dialog,
props,
wrapper,
resolve,
});
});
}

View File

@@ -88,7 +88,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, inject, ref, useSlots, watch } from "vue"; import { computed, inject, ref, useSlots, watch } from "vue";
import { openDialog } from "vue3-promise-dialog"; import { openDialog } from "./SMDialog";
import { api } from "../helpers/api"; import { api } from "../helpers/api";
import { MediaResponse } from "../helpers/api.types"; import { MediaResponse } from "../helpers/api.types";
import { imageMedium } from "../helpers/image"; import { imageMedium } from "../helpers/image";

View File

@@ -37,7 +37,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, Ref, watch } from "vue"; import { ref, Ref, watch } from "vue";
import { openDialog } from "vue3-promise-dialog"; import { openDialog } from "../components/SMDialog";
import { api } from "../helpers/api"; import { api } from "../helpers/api";
import { Media, MediaResponse } from "../helpers/api.types"; import { Media, MediaResponse } from "../helpers/api.types";
import { bytesReadable } from "../helpers/types"; import { bytesReadable } from "../helpers/types";

View File

@@ -1,36 +0,0 @@
<template>
<div class="sm-modal">
<slot></slot>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted } from "vue";
onMounted(() => {
document.getElementsByTagName("body")[0].style.overflow = "hidden";
});
onUnmounted(() => {
document.getElementsByTagName("body")[0].style.overflow = "auto";
});
</script>
<style lang="scss">
.sm-modal {
position: fixed;
display: flex;
top: 0;
left: 0;
bottom: 0;
right: 0;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
z-index: 1000;
padding: 1rem;
}
</style>

View File

@@ -1,32 +1,27 @@
<template> <template>
<SMModal> <SMFormCard :loading="dialogLoading">
<SMFormCard :loading="dialogLoading"> <h1>Change Password</h1>
<h1>Change Password</h1> <p class="text-center">Enter your new password below</p>
<p class="text-center">Enter your new password below</p> <SMForm :model-value="form" @submit="handleSubmit">
<SMForm :model-value="form" @submit="handleSubmit"> <SMInput control="password" type="password" label="New Password" />
<SMInput <SMFormFooter>
control="password" <template #left>
type="password" <SMButton
label="New Password" /> type="secondary"
<SMFormFooter> label="Cancel"
<template #left> @click="handleClickCancel" />
<SMButton </template>
type="secondary" <template #right>
label="Cancel" <SMButton type="submit" label="Update" />
@click="handleClickCancel" /> </template>
</template> </SMFormFooter>
<template #right> </SMForm>
<SMButton type="submit" label="Update" /> </SMFormCard>
</template>
</SMFormFooter>
</SMForm>
</SMFormCard>
</SMModal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref } from "vue"; import { onMounted, onUnmounted, reactive, ref } from "vue";
import { closeDialog } from "vue3-promise-dialog"; import { closeDialog } from "../SMDialog";
import { api } from "../../helpers/api"; import { api } from "../../helpers/api";
import { Form, FormControl, FormObject } from "../../helpers/form"; import { Form, FormControl, FormObject } from "../../helpers/form";
import { And, Password, Required } from "../../helpers/validate"; import { And, Password, Required } from "../../helpers/validate";
@@ -38,7 +33,6 @@ import SMFormCard from "../SMFormCard.vue";
import SMForm from "../SMForm.vue"; import SMForm from "../SMForm.vue";
import SMFormFooter from "../SMFormFooter.vue"; import SMFormFooter from "../SMFormFooter.vue";
import SMInput from "../SMInput.vue"; import SMInput from "../SMInput.vue";
import SMModal from "../SMModal.vue";
const form: FormObject = reactive( const form: FormObject = reactive(
Form({ Form({

View File

@@ -1,35 +1,32 @@
<template> <template>
<SMModal> <SMFormCard>
<SMFormCard> <h1>{{ props.title }}</h1>
<h1>{{ props.title }}</h1> <p v-html="computedSanitizedText"></p>
<p v-html="computedSanitizedText"></p> <SMFormFooter>
<SMFormFooter> <template #left>
<template #left> <SMButton
<SMButton :type="props.cancel.type"
:type="props.cancel.type" :label="props.cancel.label"
:label="props.cancel.label" @click="handleClickCancel()" />
@click="handleClickCancel()" /> </template>
</template> <template #right>
<template #right> <SMButton
<SMButton :type="props.confirm.type"
:type="props.confirm.type" :label="props.confirm.label"
:label="props.confirm.label" @click="handleClickConfirm()" />
@click="handleClickConfirm()" /> </template>
</template> </SMFormFooter>
</SMFormFooter> </SMFormCard>
</SMFormCard>
</SMModal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import DOMPurify from "dompurify"; import DOMPurify from "dompurify";
import { computed, onMounted, onUnmounted } from "vue"; import { computed, onMounted, onUnmounted } from "vue";
import { closeDialog } from "vue3-promise-dialog"; import { closeDialog } from "../SMDialog";
import { useApplicationStore } from "../../store/ApplicationStore"; import { useApplicationStore } from "../../store/ApplicationStore";
import SMButton from "../SMButton.vue"; import SMButton from "../SMButton.vue";
import SMFormCard from "../SMFormCard.vue"; import SMFormCard from "../SMFormCard.vue";
import SMFormFooter from "../SMFormFooter.vue"; import SMFormFooter from "../SMFormFooter.vue";
import SMModal from "../SMModal.vue";
const props = defineProps({ const props = defineProps({
title: { title: {

View File

@@ -1,108 +1,106 @@
<template> <template>
<SMModal> <SMFormCard
<SMFormCard :loading="dialogLoading"
:loading="dialogLoading" full
full :loading-message="dialogLoadingMessage"
:loading-message="dialogLoadingMessage" class="sm-dialog-media">
class="sm-dialog-media"> <h1>Insert Media</h1>
<h1>Insert Media</h1> <SMMessage
<SMMessage v-if="formMessage"
v-if="formMessage" icon="alert-circle-outline"
icon="alert-circle-outline" type="error"
type="error" :message="formMessage"
:message="formMessage" class="d-flex" />
class="d-flex" /> <div class="media-browser" :class="mediaBrowserClasses">
<div class="media-browser" :class="mediaBrowserClasses"> <div class="media-browser-content">
<div class="media-browser-content"> <SMLoadingIcon v-if="mediaLoading" />
<SMLoadingIcon v-if="mediaLoading" /> <div
<div v-if="!mediaLoading && mediaItems.length == 0"
v-if="!mediaLoading && mediaItems.length == 0" class="media-none">
class="media-none"> <ion-icon name="sad-outline"></ion-icon>
<ion-icon name="sad-outline"></ion-icon> <p>No media found</p>
<p>No media found</p>
</div>
<ul v-if="!mediaLoading && mediaItems.length > 0">
<li
v-for="item in mediaItems"
:key="item.id"
:class="[{ selected: item.id == selected }]"
@click="handleClickItem(item.id)"
@dblclick="handleDblClickItem(item.id)">
<div
:style="{
backgroundImage: `url('${getFilePreview(
item.url
)}')`,
}"
class="media-image"></div>
<span class="media-title">{{ item.title }}</span>
<span class="media-size">{{
bytesReadable(item.size)
}}</span>
</li>
</ul>
</div> </div>
<div class="media-browser-toolbar"> <ul v-if="!mediaLoading && mediaItems.length > 0">
<div class="layout-buttons"> <li
<ion-icon v-for="item in mediaItems"
name="grid-outline" :key="item.id"
class="layout-button-grid" :class="[{ selected: item.id == selected }]"
@click="handleClickGridLayout"></ion-icon> @click="handleClickItem(item.id)"
<ion-icon @dblclick="handleDblClickItem(item.id)">
name="list-outline" <div
class="layout-button-list" :style="{
@click="handleClickListLayout"></ion-icon> backgroundImage: `url('${getFilePreview(
</div> item.url
<div class="pagination-buttons"> )}')`,
<ion-icon }"
name="chevron-back-outline" class="media-image"></div>
:class="[{ disabled: computedDisablePrevButton }]" <span class="media-title">{{ item.title }}</span>
@click="handleClickPrev" /> <span class="media-size">{{
<span class="pagination-info">{{ bytesReadable(item.size)
computedPaginationInfo
}}</span> }}</span>
<ion-icon </li>
name="chevron-forward-outline" </ul>
:class="[{ disabled: computedDisableNextButton }]" </div>
@click="handleClickNext" /> <div class="media-browser-toolbar">
</div> <div class="layout-buttons">
<ion-icon
name="grid-outline"
class="layout-button-grid"
@click="handleClickGridLayout"></ion-icon>
<ion-icon
name="list-outline"
class="layout-button-list"
@click="handleClickListLayout"></ion-icon>
</div>
<div class="pagination-buttons">
<ion-icon
name="chevron-back-outline"
:class="[{ disabled: computedDisablePrevButton }]"
@click="handleClickPrev" />
<span class="pagination-info">{{
computedPaginationInfo
}}</span>
<ion-icon
name="chevron-forward-outline"
:class="[{ disabled: computedDisableNextButton }]"
@click="handleClickNext" />
</div> </div>
</div> </div>
<SMFormFooter> </div>
<template #left> <SMFormFooter>
<SMButton <template #left>
type="button" <SMButton
label="Cancel" type="button"
@click="handleClickCancel" /> label="Cancel"
</template> @click="handleClickCancel" />
<template #right> </template>
<SMButton <template #right>
v-if="props.allowUpload" <SMButton
type="button" v-if="props.allowUpload"
label="Upload" type="button"
@click="handleClickUpload" /> label="Upload"
<SMButton @click="handleClickUpload" />
type="primary" <SMButton
label="Insert" type="primary"
:disabled="selected.length == 0" label="Insert"
@click="handleClickInsert" /> :disabled="selected.length == 0"
</template> @click="handleClickInsert" />
</SMFormFooter> </template>
<input </SMFormFooter>
v-if="props.allowUpload" <input
id="file" v-if="props.allowUpload"
ref="refUploadInput" id="file"
type="file" ref="refUploadInput"
style="display: none" type="file"
:accept="computedAccepts" style="display: none"
@change="handleChangeUpload" /> :accept="computedAccepts"
</SMFormCard> @change="handleChangeUpload" />
</SMModal> </SMFormCard>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, Ref, watch } from "vue"; import { computed, onMounted, onUnmounted, ref, Ref, watch } from "vue";
import { closeDialog } from "vue3-promise-dialog"; import { closeDialog } from "../SMDialog";
import { api } from "../../helpers/api"; import { api } from "../../helpers/api";
import { Media, MediaCollection, MediaResponse } from "../../helpers/api.types"; import { Media, MediaCollection, MediaResponse } from "../../helpers/api.types";
import { bytesReadable } from "../../helpers/types"; import { bytesReadable } from "../../helpers/types";
@@ -113,7 +111,6 @@ import SMFormCard from "../SMFormCard.vue";
import SMFormFooter from "../SMFormFooter.vue"; import SMFormFooter from "../SMFormFooter.vue";
import SMLoadingIcon from "../SMLoadingIcon.vue"; import SMLoadingIcon from "../SMLoadingIcon.vue";
import SMMessage from "../SMMessage.vue"; import SMMessage from "../SMMessage.vue";
import SMModal from "../SMModal.vue";
const props = defineProps({ const props = defineProps({
mime: { mime: {
@@ -356,7 +353,7 @@ const handleClickNext = ($event: MouseEvent): void => {
/** /**
* When the user clicks the upload button * When the user clicks the upload button
*/ */
const handleClickUpload = () => { const handleClickUpload = async () => {
if (refUploadInput.value != null) { if (refUploadInput.value != null) {
refUploadInput.value.click(); refUploadInput.value.click();
} }

View File

@@ -4,7 +4,6 @@ import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
import { createApp } from "vue"; import { createApp } from "vue";
import { VueReCaptcha } from "vue-recaptcha-v3"; import { VueReCaptcha } from "vue-recaptcha-v3";
import { PromiseDialog } from "vue3-promise-dialog";
import "../css/app.scss"; import "../css/app.scss";
import "./bootstrap"; import "./bootstrap";
import SMColumn from "./components/SMColumn.vue"; import SMColumn from "./components/SMColumn.vue";
@@ -20,7 +19,6 @@ pinia.use(piniaPluginPersistedstate);
createApp(App) createApp(App)
.use(pinia) .use(pinia)
.use(Router) .use(Router)
.use(PromiseDialog)
.use(VueReCaptcha, { .use(VueReCaptcha, {
siteKey: import.meta.env.GOOGLE_RECAPTCHA_SITE_KEY, siteKey: import.meta.env.GOOGLE_RECAPTCHA_SITE_KEY,
loaderOptions: { loaderOptions: {

View File

@@ -11,6 +11,7 @@
<SMProgress /> <SMProgress />
<SMToastList /> <SMToastList />
<DialogWrapper :transition-attrs="{ name: 'fade' }" /> <DialogWrapper :transition-attrs="{ name: 'fade' }" />
<SMDialogList />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -18,7 +19,7 @@ import SMNavbar from "../components/SMNavbar.vue";
import SMFooter from "../components/SMFooter.vue"; import SMFooter from "../components/SMFooter.vue";
import SMProgress from "../components/SMProgress.vue"; import SMProgress from "../components/SMProgress.vue";
import SMToastList from "../components/SMToastList.vue"; import SMToastList from "../components/SMToastList.vue";
import { DialogWrapper } from "vue3-promise-dialog"; import SMDialogList from "../components/SMDialog";
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -63,7 +63,7 @@
import { reactive, ref, watch } from "vue"; import { reactive, ref, watch } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import EasyDataTable from "vue3-easy-data-table"; import EasyDataTable from "vue3-easy-data-table";
import { openDialog } from "vue3-promise-dialog"; import { openDialog } from "../../components/SMDialog";
import SMDialogConfirm from "../../components/dialogs/SMDialogConfirm.vue"; import SMDialogConfirm from "../../components/dialogs/SMDialogConfirm.vue";
import SMButton from "../../components/SMButton.vue"; import SMButton from "../../components/SMButton.vue";
import SMHeading from "../../components/SMHeading.vue"; import SMHeading from "../../components/SMHeading.vue";

View File

@@ -59,7 +59,7 @@
import { reactive, ref, watch } from "vue"; import { reactive, ref, watch } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import EasyDataTable from "vue3-easy-data-table"; import EasyDataTable from "vue3-easy-data-table";
import { openDialog } from "vue3-promise-dialog"; import { openDialog } from "../../components/SMDialog";
import SMDialogConfirm from "../../components/dialogs/SMDialogConfirm.vue"; import SMDialogConfirm from "../../components/dialogs/SMDialogConfirm.vue";
import SMButton from "../../components/SMButton.vue"; import SMButton from "../../components/SMButton.vue";
import SMFileLink from "../../components/SMFileLink.vue"; import SMFileLink from "../../components/SMFileLink.vue";

View File

@@ -59,7 +59,7 @@
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import EasyDataTable from "vue3-easy-data-table"; import EasyDataTable from "vue3-easy-data-table";
import { openDialog } from "vue3-promise-dialog"; import { openDialog } from "../../components/SMDialog";
import SMDialogConfirm from "../../components/dialogs/SMDialogConfirm.vue"; import SMDialogConfirm from "../../components/dialogs/SMDialogConfirm.vue";
import SMButton from "../../components/SMButton.vue"; import SMButton from "../../components/SMButton.vue";
import SMHeading from "../../components/SMHeading.vue"; import SMHeading from "../../components/SMHeading.vue";

View File

@@ -32,7 +32,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive } from "vue"; import { computed, reactive } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { openDialog } from "vue3-promise-dialog"; import { openDialog } from "../../components/SMDialog";
import SMDialogChangePassword from "../../components/dialogs/SMDialogChangePassword.vue"; import SMDialogChangePassword from "../../components/dialogs/SMDialogChangePassword.vue";
import SMButton from "../../components/SMButton.vue"; import SMButton from "../../components/SMButton.vue";
import SMForm from "../../components/SMForm.vue"; import SMForm from "../../components/SMForm.vue";

View File

@@ -29,7 +29,7 @@
import { reactive, ref, watch } from "vue"; import { reactive, ref, watch } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import EasyDataTable from "vue3-easy-data-table"; import EasyDataTable from "vue3-easy-data-table";
import { openDialog } from "vue3-promise-dialog"; import { openDialog } from "../../components/SMDialog";
import DialogConfirm from "../../components/dialogs/SMDialogConfirm.vue"; import DialogConfirm from "../../components/dialogs/SMDialogConfirm.vue";
import SMHeading from "../../components/SMHeading.vue"; import SMHeading from "../../components/SMHeading.vue";
import SMLoadingIcon from "../../components/SMLoadingIcon.vue"; import SMLoadingIcon from "../../components/SMLoadingIcon.vue";