mirror of
https://github.com/unraid/api.git
synced 2026-02-19 14:38:29 -06:00
fix: web component modals
This commit is contained in:
@@ -46,8 +46,8 @@ const serverState = {
|
||||
expireTime,
|
||||
lanIp: '192.168.0.1',
|
||||
locale: 'en',
|
||||
pluginInstalled: true,
|
||||
registered: true,
|
||||
pluginInstalled: false,
|
||||
registered: false,
|
||||
site: 'http://localhost:4321',
|
||||
state,
|
||||
uptime,
|
||||
|
||||
@@ -10,11 +10,12 @@ $myservers = file_exists($myservers_flash_cfg_path) ? @parse_ini_file($myservers
|
||||
// extract web component JS file from manifest
|
||||
$jsonManifest = file_get_contents('/usr/local/emhttp/plugins/dynamix.my.servers/webComponents/manifest.json');
|
||||
$jsonManifestData = json_decode($jsonManifest, true);
|
||||
$webComponentFile = $jsonManifestData["connect-components.client.mjs"]["file"];
|
||||
$webComponentJsFile = $jsonManifestData["connect-components.client.mjs"]["file"];
|
||||
// web component
|
||||
$localSource = '/plugins/dynamix.my.servers/webComponents/' . $webComponentFile;
|
||||
$localSourceBasePath = '/plugins/dynamix.my.servers/webComponents/';
|
||||
$localSourceJs = $localSourceBasePath . $webComponentJsFile;
|
||||
// add the web component source to the DOM
|
||||
echo '<script id="unraid-webcomponents" defer src="' . $localSource . '"></script>';
|
||||
echo '<script id="unraid-webcomponents" defer src="' . $localSourceJs . '"></script>';
|
||||
|
||||
/**
|
||||
* Build vars for user profile prop
|
||||
|
||||
57
app.vue
57
app.vue
@@ -1,6 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
import serverState from './_data/serverState';
|
||||
|
||||
const nuxtApp = useNuxtApp();
|
||||
onBeforeMount(() => {
|
||||
nuxtApp.$customElements.registerEntry('ConnectComponents');
|
||||
@@ -8,56 +6,7 @@ onBeforeMount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-5xl mx-auto bg-gray-200">
|
||||
<client-only>
|
||||
<div class="flex flex-col gap-6 p-6">
|
||||
<h2>Vue Components</h2>
|
||||
<h3>UserProfileCe</h3>
|
||||
<UserProfileCe :server="serverState" />
|
||||
<hr />
|
||||
<h3>DownloadApiLogsCe</h3>
|
||||
<DownloadApiLogsCe />
|
||||
<hr />
|
||||
<h3>AuthCe</h3>
|
||||
<AuthCe />
|
||||
<hr />
|
||||
<h3>KeyActionsCe</h3>
|
||||
<KeyActionsCe />
|
||||
<hr />
|
||||
<h3>WanIpCheckCe</h3>
|
||||
<WanIpCheckCe />
|
||||
</div>
|
||||
<div class="flex flex-col gap-6 p-6">
|
||||
<h2>Web Components</h2>
|
||||
<h3>UserProfileCe</h3>
|
||||
<connect-user-profile :server="JSON.stringify(serverState)"></connect-user-profile>
|
||||
<hr />
|
||||
<h3>DownloadApiLogsCe</h3>
|
||||
<connect-download-api-logs></connect-download-api-logs>
|
||||
<hr />
|
||||
<h3>AuthCe</h3>
|
||||
<connect-auth></connect-auth>
|
||||
<hr />
|
||||
<h3>KeyActionsCe</h3>
|
||||
<connect-key-actions></connect-key-actions>
|
||||
<hr />
|
||||
<h3>WanIpCheckCe</h3>
|
||||
<connect-wan-ip-check></connect-wan-ip-check>
|
||||
</div>
|
||||
</client-only>
|
||||
</div>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
h2 {
|
||||
@apply text-xl font-semibold font-mono;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-lg font-semibold font-mono;
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply border-black;
|
||||
}
|
||||
</style>
|
||||
@@ -40,4 +40,52 @@ body {
|
||||
border-bottom: 11px solid var(--color-alpha);
|
||||
border-left: 11px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.BrandLoading_2,
|
||||
.BrandLoading_4 {
|
||||
animation: mark_2 1.5s ease infinite;
|
||||
}
|
||||
.BrandLoading_3 {
|
||||
animation: mark_3 1.5s ease infinite;
|
||||
}
|
||||
.BrandLoading_6,
|
||||
.BrandLoading_8 {
|
||||
animation: mark_6 1.5s ease infinite;
|
||||
}
|
||||
.BrandLoading_7 {
|
||||
animation: mark_7 1.5s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes mark_2 {
|
||||
50% {
|
||||
transform: translateY(-40px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_3 {
|
||||
50% {
|
||||
transform: translateY(-62px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_6 {
|
||||
50% {
|
||||
transform: translateY(40px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_7 {
|
||||
50% {
|
||||
transform: translateY(62px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
export interface Props {
|
||||
gradientStart?: string;
|
||||
gradientStop?: string;
|
||||
@@ -12,7 +15,6 @@ withDefaults(defineProps<Props>(), {
|
||||
height: 64,
|
||||
title: 'Loading',
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -20,7 +22,6 @@ withDefaults(defineProps<Props>(), {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 133.52 76.97"
|
||||
class="unraid_sc_loader"
|
||||
:class="`h-[${height}px]`"
|
||||
role="img"
|
||||
>
|
||||
@@ -42,97 +43,47 @@ withDefaults(defineProps<Props>(), {
|
||||
<path
|
||||
d="m70,19.24zm57,0l6.54,0l0,38.49l-6.54,0l0,-38.49z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_sc_loader_9"
|
||||
class="BrandLoading_9"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm47.65,11.9l-6.55,0l0,-23.79l6.55,0l0,23.79z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_sc_loader_8"
|
||||
class="BrandLoading_8"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm31.77,-4.54l-6.54,0l0,-14.7l6.54,0l0,14.7z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_sc_loader_7"
|
||||
class="BrandLoading_7"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm15.9,11.9l-6.54,0l0,-23.79l6.54,0l0,23.79z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_sc_loader_6"
|
||||
class="BrandLoading_6"
|
||||
/>
|
||||
<path
|
||||
d="m63.49,19.24l6.51,0l0,38.49l-6.51,0l0,-38.49z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_sc_loader_5"
|
||||
class="BrandLoading_5"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm-22.38,26.6l6.54,0l0,23.78l-6.54,0l0,-23.78z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_sc_loader_4"
|
||||
class="BrandLoading_4"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm-38.26,43.03l6.55,0l0,14.73l-6.55,0l0,-14.73z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_sc_loader_3"
|
||||
class="BrandLoading_3"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm-54.13,26.6l6.54,0l0,23.78l-6.54,0l0,-23.78z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_sc_loader_2"
|
||||
class="BrandLoading_2"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm-63.46,38.49l-6.54,0l0,-38.49l6.54,0l0,38.49z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_sc_loader_1"
|
||||
class="BrandLoading_1"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.unraid_sc_loader_2,
|
||||
.unraid_sc_loader_4 {
|
||||
animation: mark_2 1.5s ease infinite;
|
||||
}
|
||||
.unraid_sc_loader_3 {
|
||||
animation: mark_3 1.5s ease infinite;
|
||||
}
|
||||
.unraid_sc_loader_6,
|
||||
.unraid_sc_loader_8 {
|
||||
animation: mark_6 1.5s ease infinite;
|
||||
}
|
||||
.unraid_sc_loader_7 {
|
||||
animation: mark_7 1.5s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes mark_2 {
|
||||
50% {
|
||||
transform: translateY(-40px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_3 {
|
||||
50% {
|
||||
transform: translateY(-62px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_6 {
|
||||
50% {
|
||||
transform: translateY(40px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_7 {
|
||||
50% {
|
||||
transform: translateY(62px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* @fix Modal closes when clicking inside the modal
|
||||
*/
|
||||
import { Dialog, DialogPanel, DialogTitle, DialogDescription, TransitionChild, TransitionRoot } from '@headlessui/vue';
|
||||
import { CheckIcon, XMarkIcon } from '@heroicons/vue/24/outline';
|
||||
import { TransitionChild, TransitionRoot } from '@headlessui/vue';
|
||||
import { XMarkIcon } from '@heroicons/vue/24/outline';
|
||||
import useFocusTrap from '~/composables/useFocusTrap';
|
||||
|
||||
export interface Props {
|
||||
description?: string;
|
||||
@@ -13,39 +11,49 @@ export interface Props {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
maxWidth: 'sm:max-w-lg',
|
||||
open: false,
|
||||
showCloseX: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const closeModal = () => {
|
||||
console.debug('[closeModal]');
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const { trapRef } = useFocusTrap();
|
||||
|
||||
const ariaLablledById = computed((): string|undefined => props.title ? `ModalTitle-${Math.random()}`.replace('0.', '') : undefined);
|
||||
|
||||
onMounted(() => {
|
||||
console.debug('[onMounted]');
|
||||
document.body.style.setProperty('overflow', 'hidden');
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('keyup', () => {});
|
||||
document.body.style.removeProperty('overflow');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TransitionRoot as="template" :show="open">
|
||||
<Dialog as="div" class="relative z-[99999]">
|
||||
<TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0" enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0">
|
||||
<div class="fixed inset-0 z-0 bg-black bg-opacity-50 transition-opacity" />
|
||||
</TransitionChild>
|
||||
|
||||
<div class="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200" leave-from="opacity-100 translate-y-0 sm:scale-100" leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
|
||||
<DialogPanel :class="maxWidth" class="text-beta bg-alpha relative transform overflow-hidden rounded-lg px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:p-6">
|
||||
<DialogTitle v-if="title">{{ title }}</DialogTitle>
|
||||
<DialogDescription v-if="description">{{ description }}</DialogDescription>
|
||||
|
||||
<div v-if="showCloseX" class="absolute z-20 right-0 top-0 hidden pt-2 pr-2 sm:block">
|
||||
<button type="button" class="rounded-md bg-alpha text-gray-400 p-2 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" @click="$emit('close')">
|
||||
<span class="sr-only">Close</span>
|
||||
<XMarkIcon class="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
<div v-if="open" @keyup.esc="closeModal" ref="trapRef" class="fixed inset-0 z-10 overflow-y-auto" role="dialog" aria-dialog="true" :aria-labelledby="ariaLablledById" tabindex="-1">
|
||||
<div @click="closeModal" class="fixed inset-0 z-0 bg-black bg-opacity-50 transition-opacity" />
|
||||
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<div :class="maxWidth" class="text-beta bg-alpha relative transform overflow-hidden rounded-lg px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:p-6">
|
||||
<div v-if="showCloseX" class="absolute z-20 right-0 top-0 hidden pt-2 pr-2 sm:block">
|
||||
<button @click="closeModal" type="button" class="rounded-md bg-alpha text-gray-400 p-2 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
||||
<span class="sr-only">Close</span>
|
||||
<XMarkIcon class="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 v-if="title" :id="ariaLablledById">{{ title }}</h1>
|
||||
<h2 v-if="description">{{ description }}</h2>
|
||||
<slot />
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
26
components/Modals.ce.vue
Normal file
26
components/Modals.ce.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
import { useCallbackStore } from '~/store/callback';
|
||||
import { usePromoStore } from '~/store/promo';
|
||||
|
||||
const callbackStore = useCallbackStore();
|
||||
const promoStore = usePromoStore();
|
||||
const { callbackFeedbackVisible } = storeToRefs(callbackStore);
|
||||
const { promoVisible } = storeToRefs(promoStore);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative z-[99999]">
|
||||
<UpcCallbackFeedback v-if="callbackFeedbackVisible" :open="callbackFeedbackVisible" />
|
||||
<UpcPromo v-if="promoVisible" :open="promoVisible" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
@@ -81,9 +81,6 @@ onBeforeMount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UpcCallbackFeedback />
|
||||
<UpcPromo />
|
||||
|
||||
<div id="UserProfile" class="text-alpha relative z-20 flex flex-col h-full gap-y-4px pl-40px rounded">
|
||||
<div class="text-gamma text-12px text-right font-semibold leading-normal flex flex-row items-baseline justify-end gap-x-12px">
|
||||
<UpcUptimeExpire />
|
||||
|
||||
@@ -2,19 +2,23 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
import { useCallbackStore } from '~/store/callback';
|
||||
|
||||
const callbackStore = useCallbackStore();
|
||||
const { callbackFeedbackVisible, decryptedData } = storeToRefs(callbackStore);
|
||||
onBeforeMount(() => {
|
||||
callbackStore.watcher();
|
||||
export interface Props {
|
||||
open?: boolean;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
open: false,
|
||||
});
|
||||
|
||||
const callbackStore = useCallbackStore();
|
||||
const { decryptedData } = storeToRefs(callbackStore);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:open="callbackFeedbackVisible"
|
||||
:open="open"
|
||||
@close="callbackStore.hide()"
|
||||
max-width="max-w-800px"
|
||||
>
|
||||
|
||||
@@ -3,15 +3,20 @@
|
||||
* @todo future idea – turn this into a carousel. each feature could have a short video if we ever them
|
||||
*/
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { usePromoStore } from '~/store/promo';
|
||||
|
||||
import type { UserProfilePromoFeature } from '~/types/userProfile';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
export interface Props {
|
||||
open?: boolean;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
open: false,
|
||||
});
|
||||
|
||||
const promoStore = usePromoStore();
|
||||
const { promoVisible } = storeToRefs(promoStore);
|
||||
|
||||
const features = ref<UserProfilePromoFeature[]>([
|
||||
{
|
||||
@@ -58,8 +63,9 @@ const installButtonClasses = 'text-white text-14px text-center w-full flex flex-
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:open="promoVisible"
|
||||
:open="open"
|
||||
@close="promoStore.promoHide()"
|
||||
:show-close-x="true"
|
||||
max-width="max-w-800px"
|
||||
>
|
||||
<div class="text-center relative w-full p-24px">
|
||||
|
||||
42
composables/useFocusTrap.js
Normal file
42
composables/useFocusTrap.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @see https://www.telerik.com/blogs/how-to-trap-focus-modal-vue-3
|
||||
*/
|
||||
import { customRef } from "vue";
|
||||
import { createFocusTrap } from "focus-trap";
|
||||
|
||||
const useFocusTrap = focusTrapArgs => {
|
||||
const trapRef = customRef((track, trigger) => {
|
||||
let $trapEl = null;
|
||||
return {
|
||||
get() {
|
||||
track();
|
||||
return $trapEl;
|
||||
},
|
||||
set(value) {
|
||||
$trapEl = value;
|
||||
value ? initFocusTrap(focusTrapArgs) : clearFocusTrap();
|
||||
trigger();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
let trap = null;
|
||||
const initFocusTrap = focusTrapArgs => {
|
||||
if (!trapRef.value) return;
|
||||
trap = createFocusTrap(trapRef.value, focusTrapArgs);
|
||||
trap.activate();
|
||||
};
|
||||
|
||||
const clearFocusTrap = () => {
|
||||
trap?.deactivate();
|
||||
trap = null;
|
||||
};
|
||||
|
||||
return {
|
||||
trapRef,
|
||||
initFocusTrap,
|
||||
clearFocusTrap,
|
||||
};
|
||||
};
|
||||
|
||||
export default useFocusTrap;
|
||||
9
layouts/default.vue
Normal file
9
layouts/default.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<client-only>
|
||||
<div class="flex flex-row items-center justify-center gap-6 p-6">
|
||||
<NuxtLink to="/" class="underline hover:no-underline focus:no-underline">Test Vue Components</NuxtLink>
|
||||
<NuxtLink to="/webComponents" class="underline hover:no-underline focus:no-underline">Test Web Components</NuxtLink>
|
||||
</div>
|
||||
<slot />
|
||||
</client-only>
|
||||
</template>
|
||||
@@ -37,6 +37,10 @@ export default defineNuxtConfig({
|
||||
name: 'ConnectKeyActions',
|
||||
path: '@/components/KeyActions.ce',
|
||||
},
|
||||
{
|
||||
name: 'ConnectModals',
|
||||
path: '@/components/Modals.ce',
|
||||
},
|
||||
{
|
||||
name: 'ConnectUserProfile',
|
||||
path: '@/components/UserProfile.ce',
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@@ -2940,6 +2940,14 @@
|
||||
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
|
||||
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="
|
||||
},
|
||||
"focus-trap": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.4.3.tgz",
|
||||
"integrity": "sha512-BgSSbK4GPnS2VbtZ50VtOv1Sti6DIkj3+LkVjiWMNjLeAp1SH1UlLx3ULu/DCu4vq5R4/uvTm+zrvsMsuYmGLg==",
|
||||
"requires": {
|
||||
"tabbable": "^6.1.2"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
@@ -6341,6 +6349,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tabbable": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.2.tgz",
|
||||
"integrity": "sha512-qCN98uP7i9z0fIS4amQ5zbGBOq+OSigYeGvPy7NDk8Y9yncqDZ9pRPgfsc2PJIVM9RrJj7GIfuRgmjoUU9zTHQ=="
|
||||
},
|
||||
"tailwind-config-viewer": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-config-viewer/-/tailwind-config-viewer-1.7.2.tgz",
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
"@pinia/nuxt": "^0.4.11",
|
||||
"@vueuse/components": "^10.1.2",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dayjs": "^1.11.7"
|
||||
"dayjs": "^1.11.7",
|
||||
"focus-trap": "^7.4.3"
|
||||
},
|
||||
"overrides": {
|
||||
"vue": "latest"
|
||||
|
||||
49
pages/index.vue
Normal file
49
pages/index.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<script lang="ts" setup>
|
||||
import serverState from '../_data/serverState';
|
||||
|
||||
const nuxtApp = useNuxtApp();
|
||||
onBeforeMount(() => {
|
||||
nuxtApp.$customElements.registerEntry('ConnectComponents');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-5xl mx-auto bg-gray-200">
|
||||
<client-only>
|
||||
<div class="flex flex-col gap-6 p-6">
|
||||
<h2>Vue Components</h2>
|
||||
<h3>UserProfileCe</h3>
|
||||
<UserProfileCe :server="serverState" />
|
||||
<hr />
|
||||
<h3>DownloadApiLogsCe</h3>
|
||||
<DownloadApiLogsCe />
|
||||
<hr />
|
||||
<h3>AuthCe</h3>
|
||||
<AuthCe />
|
||||
<hr />
|
||||
<h3>KeyActionsCe</h3>
|
||||
<KeyActionsCe />
|
||||
<hr />
|
||||
<h3>WanIpCheckCe</h3>
|
||||
<WanIpCheckCe />
|
||||
<hr />
|
||||
<h3>ModalsCe</h3>
|
||||
<ModalsCe />
|
||||
</div>
|
||||
</client-only>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
h2 {
|
||||
@apply text-xl font-semibold font-mono;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-lg font-semibold font-mono;
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply border-black;
|
||||
}
|
||||
</style>
|
||||
49
pages/webComponents.vue
Normal file
49
pages/webComponents.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<script lang="ts" setup>
|
||||
import serverState from '../_data/serverState';
|
||||
|
||||
const nuxtApp = useNuxtApp();
|
||||
onBeforeMount(() => {
|
||||
nuxtApp.$customElements.registerEntry('ConnectComponents');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<client-only>
|
||||
<div class="max-w-5xl mx-auto bg-gray-200">
|
||||
<div class="flex flex-col gap-6 p-6">
|
||||
<h2>Web Components</h2>
|
||||
<h3>UserProfileCe</h3>
|
||||
<connect-user-profile :server="JSON.stringify(serverState)"></connect-user-profile>
|
||||
<hr />
|
||||
<h3>DownloadApiLogsCe</h3>
|
||||
<connect-download-api-logs></connect-download-api-logs>
|
||||
<hr />
|
||||
<h3>AuthCe</h3>
|
||||
<connect-auth></connect-auth>
|
||||
<hr />
|
||||
<h3>KeyActionsCe</h3>
|
||||
<connect-key-actions></connect-key-actions>
|
||||
<hr />
|
||||
<h3>WanIpCheckCe</h3>
|
||||
<connect-wan-ip-check></connect-wan-ip-check>
|
||||
<hr />
|
||||
<h3>ModalsCe</h3>
|
||||
<connect-modals></connect-modals>
|
||||
</div>
|
||||
</div>
|
||||
</client-only>
|
||||
</template>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
h2 {
|
||||
@apply text-xl font-semibold font-mono;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-lg font-semibold font-mono;
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply border-black;
|
||||
}
|
||||
</style>
|
||||
@@ -18,6 +18,7 @@ export const useCallbackStore = defineStore('callback', () => {
|
||||
// const encryptKey = config.public.callbackKey;
|
||||
const encryptKey = 'Uyv2o8e*FiQe8VeLekTqyX6Z*8XonB';
|
||||
// state
|
||||
const currentUrl = ref();
|
||||
const callbackFeedbackVisible = ref<boolean>(false);
|
||||
const decryptedData = ref();
|
||||
const encryptedMessage = ref('');
|
||||
@@ -40,8 +41,8 @@ export const useCallbackStore = defineStore('callback', () => {
|
||||
const watcher = () => {
|
||||
console.debug('[watcher]');
|
||||
const currentUrl = new URL(window.location);
|
||||
console.debug('[watcher]', currentUrl);
|
||||
const callbackValue = currentUrl.searchParams.get('data');
|
||||
console.debug('[watcher]', { callbackValue });
|
||||
if (!callbackValue) {
|
||||
return console.debug('[watcher] no callback to handle');
|
||||
}
|
||||
@@ -66,15 +67,23 @@ export const useCallbackStore = defineStore('callback', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const hide = () => callbackFeedbackVisible.value = false;
|
||||
const show = () => callbackFeedbackVisible.value = true;
|
||||
const hide = () => {
|
||||
console.debug('[hide]');
|
||||
callbackFeedbackVisible.value = false;
|
||||
};
|
||||
const show = () => {
|
||||
console.debug('[show]');
|
||||
callbackFeedbackVisible.value = true;
|
||||
}
|
||||
const toggle = useToggle(callbackFeedbackVisible);
|
||||
|
||||
/**
|
||||
* @todo consider removing query string once actions are done
|
||||
*/
|
||||
watch(callbackFeedbackVisible, (newVal, _oldVal) => {
|
||||
console.debug('[callbackFeedbackVisible]', newVal, _oldVal);
|
||||
console.debug('[callbackFeedbackVisible]', newVal);
|
||||
// removing query string once actions are done so users can't refresh the page and go through the same actions
|
||||
if (newVal === false) {
|
||||
console.debug('[callbackFeedbackVisible] push history w/o query');
|
||||
window.history.pushState(null, '', window.location.pathname);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user