update components with changes from other plugins

This commit is contained in:
joshuaboud 2022-06-13 16:11:40 -03:00
parent 8d498d925b
commit 6b201edc55
No known key found for this signature in database
GPG Key ID: 17EFB59E2A8BF50E
6 changed files with 168 additions and 300 deletions

View File

@ -1,9 +1,26 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<SwitchGroup as="div" :class="[labelRight ? 'flex-row-reverse' : 'flex-row', 'inline-flex items-center']">
<span :class="[labelRight ? 'grow' : '', 'flex flex-col']">
<SwitchLabel as="span" class="text-label" passive><slot /></SwitchLabel>
<SwitchLabel as="div" class="text-label"><slot /></SwitchLabel>
<SwitchDescription
as="span"
as="div"
class="text-sm text-muted"
><slot name="description" /></SwitchDescription>
</span>
@ -11,11 +28,11 @@
<Switch
:modelValue="modelValue"
@update:modelValue="newValue => { $emit('update:modelValue', newValue); $emit('change', newValue); $emit('input', newValue); }"
:class="[modelValue ? 'bg-45d' : 'bg-well', 'relative inline-flex flex-shrink-0 h-6 w-11 p-[2px] rounded-full cursor-pointer focus:outline-none focus:ring-0 shadow-inner']"
:class="[modelValue ? 'bg-45d' : 'bg-well', 'inline-flex shrink-0 h-6 w-11 p-[2px] rounded-full cursor-pointer shadow-inner']"
>
<span
aria-hidden="true"
:class="[modelValue ? 'translate-x-5' : 'translate-x-0', 'pointer-events-none inline-block h-5 w-5 rounded-full bg-default shadow-md transform ring-0 transition-transform ease-in-out duration-200 top-px']"
:class="[modelValue ? 'translate-x-5' : 'translate-x-0', 'pointer-events-none inline-block h-5 w-5 rounded-full bg-default shadow-md transform transition-transform ease-in-out duration-200']"
/>
</Switch>
</SwitchGroup>
@ -29,18 +46,6 @@ export default {
modelValue: Boolean,
labelRight: Boolean,
},
// setup(props, { emit }) {
// const internalModel = ref(props.modelValue);
// const updateModelValue = () => emit('update:modelValue', internalModel.value);
// watch(() => props.modelValue, (newValue) => internalModel.value = newValue);
// return {
// internalModel,
// updateModelValue,
// };
// },
components: {
Switch,
SwitchDescription,

View File

@ -1,37 +1,20 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit File Sharing.
This file is part of Cockpit Navigator.
Cockpit File Sharing is free software: you can redistribute it and/or modify it under the terms
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit File Sharing is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit File Sharing.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="aspect-square loading-spinner border-neutral-300 border-t-neutral-500 dark:border-neutral-500 dark:border-t-neutral-200 rounded-full"></div>
<div class="aspect-square animate-spin border-neutral-300 border-t-neutral-500 dark:border-neutral-500 dark:border-t-neutral-200 rounded-full"></div>
</template>
<style>
.loading-spinner {
border-width: 0.2rem;
animation: spin 2s linear infinite;
display: inline-block;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -1,72 +1,63 @@
<template>
<TransitionRoot as="template" :show="showModal">
<Dialog as="div" class="fixed z-10 inset-0 overflow-y-auto">
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0 text-default"
>
<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"
>
<DialogOverlay class="fixed inset-0 bg-neutral-500/75 dark:bg-black/50 transition-opacity" />
</TransitionChild>
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
<!-- This element is to trick the browser into centering the modal contents. -->
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<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"
>
<div
:class="[autoWidth ? 'sm:max-w-full' : 'sm:max-w-lg', 'relative inline-flex flex-col items-stretch align-bottom overflow-hidden transform transition-all sm:my-8 sm:align-middle text-left card']"
>
<div class="block w-[512px]"></div>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<TransitionRoot as="div" class="fixed inset-0 z-10 overflow-visible" :show="showModal">
<TransitionChild as="template" enter="ease-out duration-500" enter-from="opacity-0" enter-to="opacity-100"
leave="ease-in duration-500" leave-from="opacity-100" leave-to="opacity-0">
<div class="fixed z-10 inset-0 bg-neutral-500/75 dark:bg-black/50 transition-opacity" />
</TransitionChild>
<div class="fixed z-10 inset-0 overflow-hidden flex items-end sm:items-center justify-center px-4 pb-20 sm:p-0">
<TransitionChild as="template" enter="ease-out duration-300"
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-90"
enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-100"
leave-from="opacity-100 translate-y-0 sm:scale-100"
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-75">
<div
:class="[autoWidth ? 'sm:max-w-full' : 'sm:max-w-lg', 'inline-flex flex-col items-stretch overflow-hidden transform transition-all text-left z-10']">
<div class="block w-[512px]" /> <!-- set min width of div -->
<div class="card flex flex-col items-stretch overflow-hidden">
<div class="card-header">
<slot name="header">
<h3 class="text-header">{{ headerText }}</h3>
</slot>
</div>
<div class="card-body flex flex-row items-center">
<div class="shrink-0">
<slot name="icon" />
</div>
<div class="grow">
<slot name="icon" />
<div class="grow ml-2 only:ml-0 overflow-x-auto">
<slot />
</div>
</div>
<div class="card-footer button-group-row w-full justify-end">
<button
v-if="!noCancel"
type="button"
class="btn btn-secondary"
@click="$emit('cancel')"
>{{ cancelText }}</button>
<button
type="button"
:class="['btn', applyDangerous ? 'btn-danger' : 'btn-primary']"
@click="$emit('apply')"
:disabled="disableContinue"
>{{ applyText }}</button>
<div class="card-footer button-group-row justify-end">
<button v-if="!noCancel" type="button" class="btn btn-secondary" @click="$emit('cancel')">{{
cancelText
}}</button>
<button type="button" :class="['btn', applyDangerous ? 'btn-danger' : 'btn-primary']"
@click="$emit('apply')" :disabled="disableContinue">{{ applyText }}</button>
</div>
</div>
</TransitionChild>
</div>
</Dialog>
</div>
</TransitionChild>
</div>
</TransitionRoot>
</template>
<script>
import { Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue';
import { TransitionChild, TransitionRoot } from '@headlessui/vue';
export default {
props: {
@ -92,9 +83,6 @@ export default {
disableContinue: Boolean,
},
components: {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
},

View File

@ -1,85 +1,70 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<!-- Global notification live region, render this permanently at the end of the document -->
<div
aria-live="assertive"
class="fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start z-20 h-screen overflow-y-auto"
>
<div class="w-full flex flex-col items-center sm:items-end space-y-content">
<!-- Notification panel, dynamically insert this into the live region when it needs to be displayed -->
<transition
v-for="notification in notificationList"
:key="notification.id"
enter-active-class="transform ease-out duration-300 transition"
enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
class="transition-transform"
>
<div
v-if="notification.show"
class="max-w-sm w-full shadow-lg pointer-events-auto overflow-hidden bg-default text-default"
@mouseenter="notification.clearTimeouts?.()"
@mouseleave="notification.setTimeouts?.()"
>
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0" aria-hidden="true">
<ExclamationCircleIcon
v-if="notification.level === 'error'"
class="icon-error size-icon-lg"
aria-hidden="true"
/>
<ExclamationCircleIcon
v-else-if="notification.level === 'warning'"
class="icon-warning size-icon-lg"
aria-hidden="true"
/>
<CheckCircleIcon
v-else-if="notification.level === 'success'"
class="icon-success size-icon-lg"
aria-hidden="true"
/>
<MinusCircleIcon
v-else-if="notification.level === 'denied'"
class="icon-error size-icon-lg"
aria-hidden="true"
/>
<InformationCircleIcon v-else class="icon-info size-icon-lg" />
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium">{{ notification.title }}</p>
<p class="mt-1 text-sm text-muted whitespace-pre-wrap" v-html="notification.body"></p>
<div v-if="notification.actions?.length" class="mt-3 flex space-x-7">
<button
v-for="action in notification.actions"
@click="action.callback"
class="rounded-md text-sm font-medium text-primary focus:outline-none focus:ring-0 focus:ring-offset-0"
>
{{ action.text }}
</button>
<button
@click="notification.show = false"
type="button"
class="rounded-md text-sm font-medium text-secondary focus:outline-none focus:ring-0 focus:ring-offset-0"
>Dismiss</button>
</div>
</div>
<div class="ml-4 flex-shrink-0 flex">
<button
@click="notification.show = false"
class="icon-default focus:outline-none focus:ring-0 focus:ring-offset-0"
>
<span class="sr-only">Close</span>
<XIcon class="size-icon" aria-hidden="true" />
<div aria-live="assertive"
class="fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start z-20 h-screen overflow-y-auto">
<transition-group tag="div" class="w-full flex flex-col-reverse items-center sm:items-end sm:flex-col space-y-content"
enter-active-class="transition-all transform ease-out duration-300"
enter-from-class="translate-y-8 opacity-0 scale-95 sm:translate-y-0 sm:translate-x-8"
enter-to-class="translate-y-0 opacity-100 scale-100 sm:translate-x-0"
leave-active-class="transition-all transform ease-in duration-100"
leave-from-class="opacity-100 scale-100 sm:translate-x-0"
leave-to-class="opacity-0 scale-95 sm:translate-x-8">
<div v-for="notification in notificationList" :key="notification.id"
class="max-w-sm w-full shadow-lg pointer-events-auto overflow-hidden bg-default text-default"
@mouseenter="notification.clearTimeouts?.()" @mouseleave="notification.setTimeouts?.()">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0" aria-hidden="true">
<ExclamationCircleIcon v-if="notification.level === 'error'" class="icon-error size-icon-lg"
aria-hidden="true" />
<ExclamationCircleIcon v-else-if="notification.level === 'warning'"
class="icon-warning size-icon-lg" aria-hidden="true" />
<CheckCircleIcon v-else-if="notification.level === 'success'"
class="icon-success size-icon-lg" aria-hidden="true" />
<MinusCircleIcon v-else-if="notification.level === 'denied'" class="icon-error size-icon-lg"
aria-hidden="true" />
<InformationCircleIcon v-else class="icon-info size-icon-lg" />
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium">{{ notification.title }}</p>
<p class="mt-1 text-sm text-muted whitespace-pre-wrap" v-html="notification.body">
</p>
<div v-if="notification.actions?.length" class="mt-3 flex space-x-7">
<button v-for="action in notification.actions" @click="action.callback"
class="rounded-md text-sm font-medium text-primary">
{{ action.text }}
</button>
<button @click="notification.show = false" type="button"
class="rounded-md text-sm font-medium text-secondary">Dismiss</button>
</div>
</div>
<div class="ml-4 flex-shrink-0 flex">
<button @click="notification.show = false"
class="icon-default">
<span class="sr-only">Close</span>
<XIcon class="size-icon" aria-hidden="true" />
</button>
</div>
</div>
</div>
</transition>
</div>
</div>
</transition-group>
</div>
</template>
@ -87,7 +72,7 @@
import { ref, watch, reactive, onUnmounted } from 'vue';
import { InformationCircleIcon, ExclamationCircleIcon, MinusCircleIcon, CheckCircleIcon } from '@heroicons/vue/outline';
import { XIcon } from '@heroicons/vue/solid';
import { FIFO, UniqueIDGenerator } from '@45drives/cockpit-helpers';
import { FIFO } from '@45drives/cockpit-helpers';
/** Notification passed to showNotification
*
@ -106,13 +91,11 @@ export default {
setup(props) {
const notificationList = ref([]);
const uniqueIDGenerator = new UniqueIDGenerator();
/** Construct new notification and show it
*
* @param {string} title - Header text
* @param {string} body - Notification content
* @param {string} [level='info'] - 'info'|'warning'|'error'|'success'
* @param {string} [level='info'] - 'info'|'warning'|'error'|'success'|'denied'
* @param {number} [timeout=10000] - time to display notification in milliseconds, or zero to display forever
*
* @returns {Notification} - Object to chain add actions to
@ -120,7 +103,6 @@ export default {
const constructNotification = (title, body, level = 'info', timeout = 10000) => {
const actions = [];
const obj = reactive({
show: true,
title,
body,
level,
@ -137,8 +119,8 @@ export default {
obj.actions.push({
text,
callback: () => {
obj.show = false;
callback();
obj.removeSelf();
}
});
return obj;
@ -153,27 +135,27 @@ export default {
*/
const showNotificationObj = (notificationObj) => {
notificationObj.show = true;
notificationObj.id = uniqueIDGenerator.get();
// backwards compat for setting show to false to remove notification
notificationObj.stopShowWatch = watch(() => notificationObj.show, (value) => {
if (!value) notificationObj.removeSelf();
})
notificationObj.id = Math.floor(Math.random() * Date.now());
notificationObj.removeSelf = () => {
const idToRemove = notificationObj.id
notificationList.value = notificationList.value.filter(({ id }) => id !== idToRemove);
notificationObj.stopShowWatch();
}
notificationObj.setTimeouts = () => {
if (notificationObj.timeout > 0) {
notificationObj.timeout1 = setTimeout(
() => notificationObj.show = false,
notificationObj.removerTimeout = setTimeout(
notificationObj.removeSelf,
notificationObj.timeout
);
notificationObj.timeout2 = setTimeout(
() => {
notificationList.value = notificationList.value.filter(obj => obj !== notificationObj);
uniqueIDGenerator.release(notificationObj.id);
},
notificationObj.timeout + 2000
);
}
}
notificationObj.clearTimeouts = () => {
if (notificationObj.timeout1 !== undefined)
clearTimeout(notificationObj.timeout1);
if (notificationObj.timeout2 !== undefined)
clearTimeout(notificationObj.timeout2);
if (notificationObj.removerTimeout !== undefined)
clearTimeout(notificationObj.removerTimeout);
}
notificationList.value = [notificationObj, ...notificationList.value];
if (notificationObj.level === 'error') {
@ -196,12 +178,6 @@ export default {
}
});
let cleanupHandle = setInterval(() => {
notificationList.value = notificationList.value.filter(n => n.show);
}, 1000);
onUnmounted(() => clearInterval(cleanupHandle));
return {
notificationList,
constructNotification,

View File

@ -1,8 +1,25 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="shadow border border-default h-full">
<div class="shadow border border-default h-full overflow-hidden">
<div
v-if="!noHeader"
class="bg-accent py-3 px-4 lg:pl-8 lg:pr-6 text-sm font-semibold flex flex-row"
class="bg-accent py-3 px-4 lg:px-6 text-sm font-semibold flex flex-row"
>
<div class="grow">
<slot name="header">{{ headerText }}</slot>
@ -80,7 +97,7 @@ table.houston-table thead.use-sticky tr th {
table.houston-table th,
table.houston-table td {
@apply py-2 px-4 lg:pl-8 lg:pr-6 whitespace-nowrap text-sm;
@apply py-2 px-4 lg:px-6 whitespace-nowrap text-sm;
}
table.houston-table th:not(.text-right):not(.text-center),

View File

@ -1,101 +0,0 @@
<template>
<div class="flex flex-col h-full">
<div class="inline-block w-full align-middle h-full">
<div class="shadow md:rounded-[9px] border border-default h-full">
<div
v-if="!noHeader"
class="md:rounded-t-[8px] bg-accent py-3 px-4 lg:pl-8 lg:pr-6 text-sm font-semibold flex flex-row"
>
<div class="grow">
<slot name="header">
{{ headerText }}
</slot>
</div>
<div :class="[noScroll ? '' : 'overflow-y-auto']" :style="{'scrollbar-gutter': noScroll ? 'auto' : 'stable'}"></div>
</div>
<div
:class="[noShrink ? noShrinkHeight : shrinkHeight, noScroll ? '' : 'overflow-y-scroll', noHeader ? 'md:rounded-t-[8px]' : '', 'flex flex-col md:rounded-b-[8px] overflow-x-auto']"
:style="{'scrollbar-gutter': noScroll ? 'auto' : 'stable'}"
>
<table class="min-w-full divide houston-table">
<thead :class="[stickyHeaders ? 'use-sticky' : '']">
<slot name="thead" />
</thead>
<tbody class="bg-default w-full">
<slot name="tbody">
<tr>
<td colspan="100%" class="text-center align-middle text-muted text-sm">{{ emptyText }}</td>
</tr>
</slot>
</tbody>
</table>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
headerText: {
type: String,
required: false,
default: "Table",
},
emptyText: {
type: String,
required: false,
default: "Nothing to show.",
},
noShrink: {
type: Boolean,
required: false,
default: false,
},
stickyHeaders: {
type: Boolean,
required: false,
default: false,
},
noShrinkHeight: {
type: String,
required: false,
default: 'h-80'
},
shrinkHeight: {
type: String,
required: false,
default: 'max-h-80'
},
noScroll: Boolean,
noHeader: Boolean,
}
}
</script>
<style>
@import '@45drives/cockpit-css/src/index.css';
table.houston-table thead.use-sticky tr th {
@apply sticky z-10 top-0;
}
table.houston-table th,
table.houston-table td {
@apply py-2 px-4 lg:pl-8 lg:pr-6 whitespace-nowrap text-sm;
}
table.houston-table th:not(.text-right):not(.text-center),
table.houston-table td:not(.text-right):not(.text-center) {
@apply text-left;
}
table.houston-table th {
@apply bg-accent font-semibold;
}
table.houston-table tr {
@apply even:bg-accent;
}
</style>