refactor: unraid ui cleanup and migration (#998)

Co-authored-by: Eli Bosley <ekbosley@gmail.com>
Co-authored-by: Pujit Mehrotra <pujit@lime-technology.com>
Co-authored-by: mdatelle <mike@datelle.net>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Zack Spear <zackspear@users.noreply.github.com>
This commit is contained in:
Michael Datelle
2025-01-15 11:15:52 -05:00
committed by GitHub
parent 8d386043ae
commit 6669a963af
72 changed files with 11981 additions and 6912 deletions

View File

@@ -1,12 +1,9 @@
<script lang="ts" setup>
// eslint-disable vue/no-v-html
import { BrandButton } from '@unraid/ui';
import { useServerStore } from '~/store/server';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useServerStore } from '~/store/server';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
const { t } = useI18n();
const serverStore = useServerStore();
@@ -33,7 +30,7 @@ const { authAction, stateData } = storeToRefs(serverStore);
</template>
<style lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Import unraid-ui globals first */
@import '@unraid/ui/styles';
@import '../assets/main.css';
</style>

View File

@@ -15,15 +15,12 @@ else
echo "Third party plugins found - PLEASE CHECK YOUR UNRAID NOTIFICATIONS AND WAIT FOR THE MESSAGE THAT IT IS SAFE TO REBOOT!"
fi
*/
import { PageContainer } from '@unraid/ui';
import { useServerStore } from '~/store/server';
import { storeToRefs } from 'pinia';
import { onBeforeMount } from 'vue';
import { useI18n } from 'vue-i18n';
import { useServerStore } from '~/store/server';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
const { t } = useI18n();
export interface Props {
@@ -56,7 +53,7 @@ onBeforeMount(() => {
</script>
<template>
<UiPageContainer>
<PageContainer>
<UpdateOsStatus
:title="t('Downgrade Unraid OS')"
:subtitle="subtitle"
@@ -70,15 +67,12 @@ onBeforeMount(() => {
:version="restoreVersion"
:t="t"
/>
<UpdateOsThirdPartyDrivers
v-if="rebootType === 'thirdPartyDriversDownloading'"
:t="t"
/>
</UiPageContainer>
<UpdateOsThirdPartyDrivers v-if="rebootType === 'thirdPartyDriversDownloading'" :t="t" />
</PageContainer>
</template>
<style lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Import unraid-ui globals first */
@import '@unraid/ui/styles';
@import '../assets/main.css';
</style>

View File

@@ -1,12 +1,10 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { ArrowDownTrayIcon, ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
import { useI18n } from 'vue-i18n';
import { BrandButton } from '@unraid/ui';
import { CONNECT_FORUMS, CONTACT, DISCORD, WEBGUI_GRAPHQL } from '~/helpers/urls';
import { useServerStore } from '~/store/server';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
@@ -19,7 +17,11 @@ const downloadUrl = computed(() => new URL(`/graphql/api/logs?apiKey=${apiKey.va
<div class="whitespace-normal flex flex-col gap-y-16px max-w-3xl">
<span>
{{ t('The primary method of support for Unraid Connect is through our forums and Discord.') }}
{{ t('If you are asked to supply logs, please open a support request on our Contact Page and reply to the email message you receive with your logs attached.') }}
{{
t(
'If you are asked to supply logs, please open a support request on our Contact Page and reply to the email message you receive with your logs attached.'
)
}}
{{ t('The logs may contain sensitive information so do not post them publicly.') }}
</span>
<span class="flex flex-col gap-y-16px">
@@ -36,15 +38,30 @@ const downloadUrl = computed(() => new URL(`/graphql/api/logs?apiKey=${apiKey.va
</div>
<div class="flex flex-row items-baseline gap-8px">
<a :href="CONNECT_FORUMS.toString()" target="_blank" rel="noopener noreferrer" class="text-[#486dba] hover:text-[#3b5ea9] focus:text-[#3b5ea9] hover:underline focus:underline inline-flex flex-row items-center justify-start gap-8px">
<a
:href="CONNECT_FORUMS.toString()"
target="_blank"
rel="noopener noreferrer"
class="text-[#486dba] hover:text-[#3b5ea9] focus:text-[#3b5ea9] hover:underline focus:underline inline-flex flex-row items-center justify-start gap-8px"
>
{{ t('Unraid Connect Forums') }}
<ArrowTopRightOnSquareIcon class="w-16px" />
</a>
<a :href="DISCORD.toString()" target="_blank" rel="noopener noreferrer" class="text-[#486dba] hover:text-[#3b5ea9] focus:text-[#3b5ea9] hover:underline focus:underline inline-flex flex-row items-center justify-start gap-8px">
<a
:href="DISCORD.toString()"
target="_blank"
rel="noopener noreferrer"
class="text-[#486dba] hover:text-[#3b5ea9] focus:text-[#3b5ea9] hover:underline focus:underline inline-flex flex-row items-center justify-start gap-8px"
>
{{ t('Unraid Discord') }}
<ArrowTopRightOnSquareIcon class="w-16px" />
</a>
<a :href="CONTACT.toString()" target="_blank" rel="noopener noreferrer" class="text-[#486dba] hover:text-[#3b5ea9] focus:text-[#3b5ea9] hover:underline focus:underline inline-flex flex-row items-center justify-start gap-8px">
<a
:href="CONTACT.toString()"
target="_blank"
rel="noopener noreferrer"
class="text-[#486dba] hover:text-[#3b5ea9] focus:text-[#3b5ea9] hover:underline focus:underline inline-flex flex-row items-center justify-start gap-8px"
>
{{ t('Unraid Contact Page') }}
<ArrowTopRightOnSquareIcon class="w-16px" />
</a>
@@ -54,7 +71,7 @@ const downloadUrl = computed(() => new URL(`/graphql/api/logs?apiKey=${apiKey.va
</template>
<style lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Import unraid-ui globals first */
@import '@unraid/ui/styles';
@import '../assets/main.css';
</style>

View File

@@ -1,21 +1,12 @@
<script lang="ts" setup>
import {
BellAlertIcon,
ExclamationTriangleIcon,
InformationCircleIcon,
} from '@heroicons/vue/24/solid';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
import { BellAlertIcon, ExclamationTriangleIcon, InformationCircleIcon } from '@heroicons/vue/24/solid';
import { Badge } from '@unraid/ui';
import { getReleaseNotesUrl, WEBGUI_TOOLS_DOWNGRADE, WEBGUI_TOOLS_UPDATE } from '~/helpers/urls';
import { useServerStore } from '~/store/server';
import { useUpdateOsStore } from '~/store/updateOs';
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
import type { UserProfileLink } from '~/types/userProfile';
import type { UiBadgeProps, UiBadgePropsColor } from '~/types/ui/badge';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
@@ -27,23 +18,22 @@ const { osVersion, rebootType, stateDataError } = storeToRefs(serverStore);
const { available, availableWithRenewal } = storeToRefs(updateOsStore);
const { rebootTypeText } = storeToRefs(updateOsActionsStore);
export interface UpdateOsStatus extends UserProfileLink {
badge: UiBadgeProps;
}
const updateOsStatus = computed(() => {
if (stateDataError.value) { // only allowed to update when server is does not have a state error
if (stateDataError.value) {
// only allowed to update when server is does not have a state error
return null;
}
if (rebootTypeText.value) {
return {
badge: {
color: 'yellow' as UiBadgePropsColor,
color: 'yellow',
icon: ExclamationTriangleIcon,
},
href: rebootType.value === 'downgrade'
? WEBGUI_TOOLS_DOWNGRADE.toString()
: WEBGUI_TOOLS_UPDATE.toString(),
href:
rebootType.value === 'downgrade'
? WEBGUI_TOOLS_DOWNGRADE.toString()
: WEBGUI_TOOLS_UPDATE.toString(),
text: t(rebootTypeText.value),
};
}
@@ -51,13 +41,13 @@ const updateOsStatus = computed(() => {
if (availableWithRenewal.value || available.value) {
return {
badge: {
color: 'orange' as UiBadgePropsColor,
color: 'orange',
icon: BellAlertIcon,
},
click: () => { updateOsStore.setModalOpen(true); },
text: availableWithRenewal.value
? t('Update Released')
: t('Update Available'),
click: () => {
updateOsStore.setModalOpen(true);
},
text: availableWithRenewal.value ? t('Update Released') : t('Update Available'),
title: availableWithRenewal.value
? t('Unraid OS {0} Released', [availableWithRenewal.value])
: t('Unraid OS {0} Update Available', [available.value]),
@@ -77,15 +67,15 @@ const updateOsStatus = computed(() => {
target="_blank"
rel="noopener"
>
<UiBadge
color="custom"
<Badge
variant="custom"
:icon="InformationCircleIcon"
icon-styles="text-header-text-secondary"
size="14px"
size="sm"
class="text-header-text-secondary group-hover:text-orange-dark group-focus:text-orange-dark group-hover:underline group-focus:underline"
>
{{ osVersion }}
</UiBadge>
</Badge>
</a>
<component
:is="updateOsStatus.href ? 'a' : 'button'"
@@ -95,14 +85,14 @@ const updateOsStatus = computed(() => {
class="group"
@click="updateOsStatus.click?.()"
>
<UiBadge
<Badge
v-if="updateOsStatus.badge"
:color="updateOsStatus.badge.color"
:icon="updateOsStatus.badge.icon"
size="12px"
size="xs"
>
{{ updateOsStatus.text }}
</UiBadge>
</Badge>
<template v-else>
{{ updateOsStatus.text }}
</template>
@@ -111,7 +101,6 @@ const updateOsStatus = computed(() => {
</template>
<style lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Import unraid-ui globals first */
@import '@unraid/ui/styles';
</style>

View File

@@ -15,15 +15,12 @@ else
echo "Third party plugins found - PLEASE CHECK YOUR UNRAID NOTIFICATIONS AND WAIT FOR THE MESSAGE THAT IT IS SAFE TO REBOOT!"
fi
*/
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { BrandLoading, PageContainer } from '@unraid/ui';
import { WEBGUI_TOOLS_UPDATE } from '~/helpers/urls';
import { useAccountStore } from '~/store/account';
import { useServerStore } from '~/store/server';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
@@ -46,7 +43,9 @@ const subtitle = computed(() => {
});
/** when we're not prompting for reboot /Tools/Update will automatically send the user to account.unraid.net/server/update-os */
const showLoader = computed(() => window.location.pathname === WEBGUI_TOOLS_UPDATE.pathname && rebootType.value === '');
const showLoader = computed(
() => window.location.pathname === WEBGUI_TOOLS_UPDATE.pathname && rebootType.value === ''
);
onBeforeMount(() => {
if (showLoader.value) {
@@ -57,7 +56,7 @@ onBeforeMount(() => {
</script>
<template>
<UiPageContainer>
<PageContainer>
<BrandLoading v-if="showLoader" class="mx-auto my-12 max-w-160px" />
<UpdateOsStatus
v-else
@@ -66,16 +65,14 @@ onBeforeMount(() => {
:subtitle="subtitle"
:t="t"
/>
<UpdateOsThirdPartyDrivers
v-if="rebootType === 'thirdPartyDriversDownloading'"
:t="t"
/>
</UiPageContainer>
<UpdateOsThirdPartyDrivers v-if="rebootType === 'thirdPartyDriversDownloading'" :t="t" />
</PageContainer>
</template>
<style lang="postcss">
@tailwind base;
@tailwind components;
/* Import unraid-ui globals first */
@import '@unraid/ui/styles';
@import '../assets/main.css';
.unraid_mark_2,
.unraid_mark_4 {
@@ -124,6 +121,4 @@ onBeforeMount(() => {
transform: translateY(0);
}
}
@tailwind utilities;
</style>

View File

@@ -6,19 +6,16 @@ import {
InformationCircleIcon,
LifebuoyIcon,
} from '@heroicons/vue/24/solid';
import dayjs from 'dayjs';
import { storeToRefs } from 'pinia';
import { ref } from 'vue';
import type { ComposerTranslation } from 'vue-i18n';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
import { BrandButton, CardWrapper } from '@unraid/ui';
import useDateTimeHelper from '~/composables/dateTime';
import { FORUMS_BUG_REPORT } from '~/helpers/urls';
import { useServerStore } from '~/store/server';
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
import type { UserProfileLink } from '~/types/userProfile';
import dayjs from 'dayjs';
import { storeToRefs } from 'pinia';
import { ref } from 'vue';
import type { ComposerTranslation } from 'vue-i18n';
const props = defineProps<{
t: ComposerTranslation;
@@ -30,9 +27,12 @@ const serverStore = useServerStore();
const updateOsActionsStore = useUpdateOsActionsStore();
const { dateTimeFormat } = storeToRefs(serverStore);
const {
outputDateTimeFormatted: formattedReleaseDate,
} = useDateTimeHelper(dateTimeFormat.value, props.t, true, dayjs(props.releaseDate, 'YYYY-MM-DD').valueOf());
const { outputDateTimeFormatted: formattedReleaseDate } = useDateTimeHelper(
dateTimeFormat.value,
props.t,
true,
dayjs(props.releaseDate, 'YYYY-MM-DD').valueOf()
);
const diagnosticsButton = ref<UserProfileLink | undefined>({
click: () => {
@@ -55,12 +55,10 @@ const downgradeButton = ref<UserProfileLink>({
</script>
<template>
<UiCardWrapper :increased-padding="true">
<CardWrapper :increased-padding="true">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-20px sm:gap-24px">
<div class="grid gap-y-16px">
<h3
class="font-semibold leading-normal flex flex-row items-start justify-start gap-8px"
>
<h3 class="font-semibold leading-normal flex flex-row items-start justify-start gap-8px">
<ArrowUturnDownIcon class="w-20px shrink-0" />
<span class="leading-none inline-flex flex-wrap justify-start items-baseline gap-8px">
<span class="text-20px">
@@ -76,28 +74,45 @@ const downgradeButton = ref<UserProfileLink>({
</h3>
<div class="prose text-16px leading-relaxed opacity-75 whitespace-normal">
<p>{{ t(`Downgrades are only recommended if you're unable to solve a critical issue.`) }}</p>
<p>{{ t('In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue.') }}</p>
<p>{{ t('Download the Diagnostics zip then please open a bug report on our forums with a description of the issue along with your diagnostics.') }} </p>
<p>
{{
t(
'In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue.'
)
}}
</p>
<p>
{{
t(
'Download the Diagnostics zip then please open a bug report on our forums with a description of the issue along with your diagnostics.'
)
}}
</p>
</div>
</div>
<div v-if="downgradeButton" class="flex flex-col flex-shrink-0 gap-16px flex-grow items-stretch">
<BrandButton
:btn-style="'underline'"
:variant="'underline'"
:icon="InformationCircleIcon"
:text="t('{0} Release Notes', [version])"
@click="updateOsActionsStore.viewReleaseNotes(t('{0} Release Notes', [version]), '/boot/previous/changes.txt')"
@click="
updateOsActionsStore.viewReleaseNotes(
t('{0} Release Notes', [version]),
'/boot/previous/changes.txt'
)
"
/>
<BrandButton
v-if="diagnosticsButton"
:btn-style="'gray'"
:variant="'gray'"
:icon="diagnosticsButton.icon"
:name="diagnosticsButton.name"
:text="diagnosticsButton.text"
@click="diagnosticsButton.click"
/>
<BrandButton
:btn-style="'gray'"
:variant="'gray'"
:external="true"
:href="FORUMS_BUG_REPORT.toString()"
:icon="LifebuoyIcon"
@@ -113,11 +128,11 @@ const downgradeButton = ref<UserProfileLink>({
/>
</div>
</div>
</UiCardWrapper>
</CardWrapper>
</template>
<style lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Import unraid-ui globals first */
@import '@unraid/ui/styles';
@import '../../assets/main.css';
</style>

View File

@@ -8,19 +8,18 @@ import {
InformationCircleIcon,
XCircleIcon,
} from '@heroicons/vue/24/solid';
import { storeToRefs } from 'pinia';
import { WEBGUI_TOOLS_REGISTRATION } from '~/helpers/urls';
import { Badge, BrandButton } from '@unraid/ui';
import BrandLoadingWhite from '~/components/Brand/LoadingWhite.vue';
import useDateTimeHelper from '~/composables/dateTime';
import { WEBGUI_TOOLS_REGISTRATION } from '~/helpers/urls';
import { useAccountStore } from '~/store/account';
import { useServerStore } from '~/store/server';
import { useUpdateOsStore } from '~/store/updateOs';
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
import type { ButtonProps } from '~/types/ui/button';
import { storeToRefs } from 'pinia';
import type { ComposerTranslation } from 'vue-i18n';
import BrandLoadingWhite from '~/components/Brand/LoadingWhite.vue';
export interface Props {
downgradeNotAvailable?: boolean;
restoreVersion?: string | undefined;
@@ -42,16 +41,15 @@ const serverStore = useServerStore();
const updateOsStore = useUpdateOsStore();
const updateOsActionsStore = useUpdateOsActionsStore();
const { dateTimeFormat, osVersion, rebootType, rebootVersion, regExp, regUpdatesExpired } = storeToRefs(serverStore);
const { dateTimeFormat, osVersion, rebootType, rebootVersion, regExp, regUpdatesExpired } =
storeToRefs(serverStore);
const { available, availableWithRenewal } = storeToRefs(updateOsStore);
const { ineligibleText, rebootTypeText, status } = storeToRefs(updateOsActionsStore);
const updateAvailable = computed(() => available.value || availableWithRenewal.value);
const {
outputDateTimeReadableDiff: readableDiffRegExp,
outputDateTimeFormatted: formattedRegExp,
} = useDateTimeHelper(dateTimeFormat.value, props.t, true, regExp.value);
const { outputDateTimeReadableDiff: readableDiffRegExp, outputDateTimeFormatted: formattedRegExp } =
useDateTimeHelper(dateTimeFormat.value, props.t, true, regExp.value);
const regExpOutput = computed(() => {
if (!regExp.value) {
@@ -67,16 +65,16 @@ const regExpOutput = computed(() => {
};
});
const showRebootButton = computed(() => rebootType.value === 'downgrade' || rebootType.value === 'update');
const showRebootButton = computed(
() => rebootType.value === 'downgrade' || rebootType.value === 'update'
);
const checkButton = computed((): ButtonProps => {
if (showRebootButton.value || props.showExternalDowngrade) {
return {
btnStyle: 'outline',
click: () => {
props.showExternalDowngrade
? accountStore.downgradeOs()
: accountStore.updateOs();
props.showExternalDowngrade ? accountStore.downgradeOs() : accountStore.updateOs();
},
icon: ArrowTopRightOnSquareIcon,
text: props.t('More options'),
@@ -124,9 +122,9 @@ const checkButton = computed((): ButtonProps => {
:title="t('View release notes')"
@click="updateOsActionsStore.viewReleaseNotes(t('{0} Release Notes', [osVersion]))"
>
<UiBadge :icon="InformationCircleIcon" class="underline">
<Badge :icon="InformationCircleIcon" variant="gray" size="md">
{{ t('Current Version {0}', [osVersion]) }}
</UiBadge>
</Badge>
</button>
<a
@@ -135,75 +133,68 @@ const checkButton = computed((): ButtonProps => {
class="group"
:title="t('Learn more and fix')"
>
<UiBadge
:color="'yellow'"
<Badge
variant="yellow"
:icon="ExclamationTriangleIcon"
:title="regExpOutput?.text"
class="underline"
>
{{ t('Key ineligible for future releases') }}
</UiBadge>
</Badge>
</a>
<UiBadge
<Badge
v-else-if="ineligibleText && availableWithRenewal"
:color="'yellow'"
variant="yellow"
:icon="ExclamationTriangleIcon"
:title="regExpOutput?.text"
>
{{ t('Key ineligible for {0}', [availableWithRenewal]) }}
</UiBadge>
</Badge>
<UiBadge
v-if="status === 'checking'"
:color="'orange'"
:icon="BrandLoadingWhite"
>
<Badge v-if="status === 'checking'" variant="orange" :icon="BrandLoadingWhite">
{{ t('Checking...') }}
</UiBadge>
</Badge>
<template v-else>
<UiBadge
<Badge
v-if="rebootType === ''"
:color="updateAvailable ? 'orange' : 'green'"
:variant="updateAvailable ? 'orange' : 'green'"
:icon="updateAvailable ? BellAlertIcon : CheckCircleIcon"
>
{{ (available
? t('Unraid {0} Available', [available])
: (availableWithRenewal
? t('Up-to-date with eligible releases')
: t('Up-to-date')))
{{
available
? t('Unraid {0} Available', [available])
: availableWithRenewal
? t('Up-to-date with eligible releases')
: t('Up-to-date')
}}
</UiBadge>
<UiBadge
v-else
:color="'yellow'"
:icon="ExclamationTriangleIcon"
>
</Badge>
<Badge v-else variant="yellow" :icon="ExclamationTriangleIcon">
{{ t(rebootTypeText) }}
</UiBadge>
</Badge>
</template>
<UiBadge
v-if="downgradeNotAvailable"
:color="'gray'"
:icon="XCircleIcon"
>
<Badge v-if="downgradeNotAvailable" variant="gray" :icon="XCircleIcon">
{{ t('No downgrade available') }}
</UiBadge>
</Badge>
</div>
<div class="inline-flex flex-col flex-shrink-0 gap-16px flex-grow items-center md:items-end">
<span v-if="showRebootButton">
<BrandButton
btn-style="fill"
variant="fill"
:icon="ArrowPathIcon"
:text="rebootType === 'downgrade' ? t('Reboot Now to Downgrade to {0}', [rebootVersion]) : t('Reboot Now to Update to {0}', [rebootVersion])"
:text="
rebootType === 'downgrade'
? t('Reboot Now to Downgrade to {0}', [rebootVersion])
: t('Reboot Now to Update to {0}', [rebootVersion])
"
@click="updateOsActionsStore.rebootServer()"
/>
</span>
<span>
<BrandButton
:btn-style="checkButton.btnStyle"
:variant="checkButton.btnStyle"
:icon="checkButton.icon"
:text="checkButton.text"
@click="checkButton.click"
@@ -212,7 +203,7 @@ const checkButton = computed((): ButtonProps => {
<span v-if="rebootType !== ''">
<BrandButton
btn-style="outline"
variant="outline"
:icon="XCircleIcon"
:text="t('Cancel {0}', [rebootType === 'downgrade' ? t('Downgrade') : t('Update')])"
@click="updateOsStore.cancelUpdate()"

View File

@@ -1,7 +1,6 @@
<script lang="ts" setup>
// @todo ensure key installs and updateOs can be handled at the same time
// @todo with multiple actions of key install and update after successful key install, rather than showing default success message, show a message to have them confirm the update
import { useClipboard } from '@vueuse/core';
import {
CheckIcon,
ChevronDoubleDownIcon,
@@ -10,10 +9,7 @@ import {
WrenchScrewdriverIcon,
XMarkIcon,
} from '@heroicons/vue/24/solid';
import { storeToRefs } from 'pinia';
import type { ComposerTranslation } from 'vue-i18n';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
import { useClipboard } from '@vueuse/core';
import { WEBGUI_CONNECT_SETTINGS, WEBGUI_TOOLS_REGISTRATION } from '~/helpers/urls';
import { useAccountStore } from '~/store/account';
import { useCallbackActionsStore } from '~/store/callbackActions';
@@ -21,6 +17,8 @@ import { useInstallKeyStore } from '~/store/installKey';
// import { usePromoStore } from '~/store/promo';
import { useServerStore } from '~/store/server';
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
import { storeToRefs } from 'pinia';
import type { ComposerTranslation } from 'vue-i18n';
export interface Props {
open?: boolean;
@@ -38,21 +36,10 @@ const installKeyStore = useInstallKeyStore();
const serverStore = useServerStore();
const updateOsActionStore = useUpdateOsActionsStore();
const {
accountAction,
accountActionHide,
accountActionStatus,
accountActionType,
} = storeToRefs(accountStore);
const {
callbackStatus,
} = storeToRefs(callbackActionsStore);
const {
keyActionType,
keyUrl,
keyInstallStatus,
keyType,
} = storeToRefs(installKeyStore);
const { accountAction, accountActionHide, accountActionStatus, accountActionType } =
storeToRefs(accountStore);
const { callbackStatus } = storeToRefs(callbackActionsStore);
const { keyActionType, keyUrl, keyInstallStatus, keyType } = storeToRefs(installKeyStore);
const {
connectPluginInstalled,
refreshServerStateStatus,
@@ -80,7 +67,9 @@ const isSettingsPage = ref<boolean>(document.location.pathname === '/Settings/Ma
const heading = computed(() => {
if (updateOsStatus.value === 'confirming') {
return callbackTypeDowngrade.value ? props.t('Downgrade Unraid OS confirmation required') : props.t('Update Unraid OS confirmation required');
return callbackTypeDowngrade.value
? props.t('Downgrade Unraid OS confirmation required')
: props.t('Update Unraid OS confirmation required');
}
switch (callbackStatus.value) {
case 'error':
@@ -94,19 +83,37 @@ const heading = computed(() => {
});
const subheading = computed(() => {
if (updateOsStatus.value === 'confirming') {
return callbackTypeDowngrade.value ? props.t('Please confirm the downgrade details below') : props.t('Please confirm the update details below');
return callbackTypeDowngrade.value
? props.t('Please confirm the downgrade details below')
: props.t('Please confirm the update details below');
}
if (callbackStatus.value === 'error') {
return props.t('Something went wrong'); /** @todo show actual error messages */
}
if (callbackStatus.value === 'loading') { return props.t('Please keep this window open while we perform some actions'); }
if (callbackStatus.value === 'loading') {
return props.t('Please keep this window open while we perform some actions');
}
if (callbackStatus.value === 'success') {
if (accountActionType.value === 'signIn') { return props.t('You\'re one step closer to enhancing your Unraid experience'); }
if (keyActionType.value === 'purchase') { return props.t('Thank you for purchasing an Unraid {0} Key!', [keyType.value]); }
if (keyActionType.value === 'replace') { return props.t('Your {0} Key has been replaced!', [keyType.value]); }
if (keyActionType.value === 'trialExtend') { return props.t('Your Trial key has been extended!'); }
if (keyActionType.value === 'trialStart') { return props.t('Your free Trial key provides all the functionality of an Unleashed Registration key'); }
if (keyActionType.value === 'upgrade') { return props.t('Thank you for upgrading to an Unraid {0} Key!', [keyType.value]); }
if (accountActionType.value === 'signIn') {
return props.t("You're one step closer to enhancing your Unraid experience");
}
if (keyActionType.value === 'purchase') {
return props.t('Thank you for purchasing an Unraid {0} Key!', [keyType.value]);
}
if (keyActionType.value === 'replace') {
return props.t('Your {0} Key has been replaced!', [keyType.value]);
}
if (keyActionType.value === 'trialExtend') {
return props.t('Your Trial key has been extended!');
}
if (keyActionType.value === 'trialStart') {
return props.t(
'Your free Trial key provides all the functionality of an Unleashed Registration key'
);
}
if (keyActionType.value === 'upgrade') {
return props.t('Thank you for upgrading to an Unraid {0} Key!', [keyType.value]);
}
return '';
}
return '';
@@ -137,30 +144,50 @@ const cancelUpdateOs = () => {
// close();
// };
const keyInstallStatusCopy = computed((): { text: string; } => {
const keyInstallStatusCopy = computed((): { text: string } => {
let txt1 = props.t('Installing');
let txt2 = props.t('Installed');
let txt3 = props.t('Install');
switch (keyInstallStatus.value) {
case 'installing':
if (keyActionType.value === 'trialExtend') { txt1 = props.t('Installing Extended Trial'); }
if (keyActionType.value === 'recover') { txt1 = props.t('Installing Recovered'); }
if (keyActionType.value === 'renew') { txt1 = props.t('Installing Extended'); }
if (keyActionType.value === 'replace') { txt1 = props.t('Installing Replaced'); }
if (keyActionType.value === 'trialExtend') {
txt1 = props.t('Installing Extended Trial');
}
if (keyActionType.value === 'recover') {
txt1 = props.t('Installing Recovered');
}
if (keyActionType.value === 'renew') {
txt1 = props.t('Installing Extended');
}
if (keyActionType.value === 'replace') {
txt1 = props.t('Installing Replaced');
}
return {
text: props.t('{0} {1} Key…', [txt1, keyType.value]),
};
case 'success':
if (keyActionType.value === 'renew' || keyActionType.value === 'trialExtend') { txt2 = props.t('Extension Installed'); }
if (keyActionType.value === 'recover') { txt2 = props.t('Recovered'); }
if (keyActionType.value === 'replace') { txt2 = props.t('Replaced'); }
if (keyActionType.value === 'renew' || keyActionType.value === 'trialExtend') {
txt2 = props.t('Extension Installed');
}
if (keyActionType.value === 'recover') {
txt2 = props.t('Recovered');
}
if (keyActionType.value === 'replace') {
txt2 = props.t('Replaced');
}
return {
text: props.t('{1} Key {0} Successfully', [txt2, keyType.value]),
};
case 'failed':
if (keyActionType.value === 'trialExtend') { txt3 = props.t('Install Extended'); }
if (keyActionType.value === 'recover') { txt3 = props.t('Install Recovered'); }
if (keyActionType.value === 'replace') { txt3 = props.t('Install Replaced'); }
if (keyActionType.value === 'trialExtend') {
txt3 = props.t('Install Extended');
}
if (keyActionType.value === 'recover') {
txt3 = props.t('Install Recovered');
}
if (keyActionType.value === 'replace') {
txt3 = props.t('Install Replaced');
}
return {
text: props.t('Failed to {0} {1} Key', [txt3, keyType.value]),
};
@@ -172,31 +199,32 @@ const keyInstallStatusCopy = computed((): { text: string; } => {
}
});
const accountActionStatusCopy = computed((): { text: string; } => {
const accountActionStatusCopy = computed((): { text: string } => {
switch (accountActionStatus.value) {
case 'waiting':
return {
text: accountAction.value?.type === 'signIn'
? props.t('Signing In')
: props.t('Signing Out'),
text: accountAction.value?.type === 'signIn' ? props.t('Signing In') : props.t('Signing Out'),
};
case 'updating':
return {
text: accountAction.value?.type === 'signIn'
? props.t('Signing in {0}…', [accountAction.value.user?.preferred_username])
: props.t('Signing out {0}…', [username.value]),
text:
accountAction.value?.type === 'signIn'
? props.t('Signing in {0}…', [accountAction.value.user?.preferred_username])
: props.t('Signing out {0}…', [username.value]),
};
case 'success':
return {
text: accountAction.value?.type === 'signIn'
? props.t('{0} Signed In Successfully', [accountAction.value.user?.preferred_username])
: props.t('{0} Signed Out Successfully', [username.value]),
text:
accountAction.value?.type === 'signIn'
? props.t('{0} Signed In Successfully', [accountAction.value.user?.preferred_username])
: props.t('{0} Signed Out Successfully', [username.value]),
};
case 'failed':
return {
text: accountAction.value?.type === 'signIn'
? props.t('Sign In Failed')
: props.t('Sign Out Failed'),
text:
accountAction.value?.type === 'signIn'
? props.t('Sign In Failed')
: props.t('Sign Out Failed'),
};
case 'ready':
default:
@@ -214,7 +242,9 @@ const { copy, copied, isSupported } = useClipboard({ source: keyUrl.value });
*/
const showUpdateEligibility = computed(() => {
// rather than specifically targeting 'Starter' and 'Unleashed' we'll target all keys that are not 'Basic', 'Plus', 'Pro', 'Lifetime', or 'Trial'
if (!keyType.value) { return false; }
if (!keyType.value) {
return false;
}
return !['Basic', 'Plus', 'Pro', 'Lifetime', 'Trial'].includes(keyType.value);
});
</script>
@@ -245,20 +275,13 @@ const showUpdateEligibility = computed(() => {
:text="keyInstallStatusCopy.text"
>
<div v-if="keyType === 'Trial'" class="opacity-75 italic mt-4px">
<UpcUptimeExpire
v-if="refreshServerStateStatus === 'done'"
:for-expire="true"
:t="t"
/>
<UpcUptimeExpire v-if="refreshServerStateStatus === 'done'" :for-expire="true" :t="t" />
<p v-else>
{{ t('Calculating trial expiration') }}
</p>
</div>
<div v-if="showUpdateEligibility" class="opacity-75 italic mt-4px">
<RegistrationUpdateExpiration
v-if="refreshServerStateStatus === 'done'"
:t="t"
/>
<RegistrationUpdateExpiration v-if="refreshServerStateStatus === 'done'" :t="t" />
<p v-else>
{{ t('Calculating OS Update Eligibility') }}
</p>
@@ -276,7 +299,10 @@ const showUpdateEligibility = computed(() => {
{{ t('Copy your Key URL: {0}', [keyUrl]) }}
</p>
<p>
<a href="/Tools/Registration" class="opacity-75 hover:opacity-100 focus:opacity-100 underline transition">
<a
href="/Tools/Registration"
class="opacity-75 hover:opacity-100 focus:opacity-100 underline transition"
>
{{ t('Then go to Tools > Registration to manually install it') }}
</a>
</p>
@@ -284,7 +310,11 @@ const showUpdateEligibility = computed(() => {
</UpcCallbackFeedbackStatus>
<UpcCallbackFeedbackStatus
v-if="stateDataError && callbackStatus !== 'loading' && (keyInstallStatus === 'success' || keyInstallStatus === 'failed')"
v-if="
stateDataError &&
callbackStatus !== 'loading' &&
(keyInstallStatus === 'success' || keyInstallStatus === 'failed')
"
:error="true"
:text="t('Post Install License Key Error')"
>
@@ -322,7 +352,11 @@ const showUpdateEligibility = computed(() => {
</p>
<p class="text-14px italic opacity-75">
{{ callbackTypeDowngrade ? t('This downgrade will require a reboot') : t('This update will require a reboot') }}
{{
callbackTypeDowngrade
? t('This downgrade will require a reboot')
: t('This update will require a reboot')
}}
</p>
</div>
</div>
@@ -332,12 +366,7 @@ const showUpdateEligibility = computed(() => {
<template v-if="callbackStatus === 'success' || updateOsStatus === 'confirming'" #footer>
<div class="flex flex-row justify-center gap-16px">
<template v-if="callbackStatus === 'success'">
<BrandButton
btn-style="underline"
:icon="XMarkIcon"
:text="closeText"
@click="close"
/>
<BrandButton btn-style="underline" :icon="XMarkIcon" :text="closeText" @click="close" />
<template v-if="connectPluginInstalled && accountActionType === 'signIn'">
<BrandButton
@@ -372,7 +401,9 @@ const showUpdateEligibility = computed(() => {
/>
<BrandButton
:icon="CheckIcon"
:text="callbackTypeDowngrade ? t('Confirm and start downgrade') : t('Confirm and start update')"
:text="
callbackTypeDowngrade ? t('Confirm and start downgrade') : t('Confirm and start update')
"
@click="confirmUpdateOs"
/>
</template>
@@ -390,8 +421,8 @@ const showUpdateEligibility = computed(() => {
</template>
<style lang="postcss">
@tailwind base;
@tailwind components;
/* Import unraid-ui globals first */
@import '@unraid/ui/styles';
.unraid_mark_2,
.unraid_mark_4 {
@@ -440,6 +471,4 @@ const showUpdateEligibility = computed(() => {
transform: translateY(0);
}
}
@tailwind utilities;
</style>

View File

@@ -1,21 +1,18 @@
<script lang="ts" setup>
import { BrandButton, BrandLoadingWhite } from '@unraid/ui';
import { useServerStore } from '~/store/server';
import { useUnraidApiStore } from '~/store/unraidApi';
import { storeToRefs } from 'pinia';
import type { ComposerTranslation } from 'vue-i18n';
import { useServerStore } from '~/store/server';
import { useUnraidApiStore } from '~/store/unraidApi';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
import BrandLoadingWhite from '~/components/Brand/LoadingWhite.vue';
defineProps<{ t: ComposerTranslation; }>();
defineProps<{ t: ComposerTranslation }>();
const { expireTime, connectPluginInstalled, state, stateData } = storeToRefs(useServerStore());
const { unraidApiStatus, unraidApiRestartAction } = storeToRefs(useUnraidApiStore());
const showExpireTime = computed(() => (state.value === 'TRIAL' || state.value === 'EEXPIRED') && expireTime.value > 0);
const showExpireTime = computed(
() => (state.value === 'TRIAL' || state.value === 'EEXPIRED') && expireTime.value > 0
);
</script>
<template>
@@ -26,21 +23,24 @@ const showExpireTime = computed(() => (state.value === 'TRIAL' || state.value ==
class="text-center prose text-16px leading-relaxed whitespace-normal opacity-75 gap-y-8px"
v-html="t(stateData.message)"
/>
<UpcUptimeExpire
v-if="showExpireTime"
class="text-center opacity-75 mt-12px"
:t="t"
/>
<UpcUptimeExpire v-if="showExpireTime" class="text-center opacity-75 mt-12px" :t="t" />
</header>
<template v-if="stateData.actions">
<ul v-if="connectPluginInstalled && unraidApiStatus !== 'online'" class="list-reset flex flex-col gap-y-8px px-16px">
<ul
v-if="connectPluginInstalled && unraidApiStatus !== 'online'"
class="list-reset flex flex-col gap-y-8px px-16px"
>
<li>
<BrandButton
class="w-full"
:disabled="unraidApiStatus === 'connecting' || unraidApiStatus === 'restarting'"
:icon="unraidApiStatus === 'restarting' ? BrandLoadingWhite : unraidApiRestartAction?.icon"
:text="unraidApiStatus === 'restarting' ? t('Restarting unraid-api…') : t('Restart unraid-api')"
:title="unraidApiStatus === 'restarting' ? t('Restarting unraid-api…') : t('Restart unraid-api')"
:text="
unraidApiStatus === 'restarting' ? t('Restarting unraid-api…') : t('Restart unraid-api')
"
:title="
unraidApiStatus === 'restarting' ? t('Restarting unraid-api…') : t('Restart unraid-api')
"
@click="unraidApiRestartAction?.click?.()"
/>
</li>
@@ -51,9 +51,9 @@ const showExpireTime = computed(() => (state.value === 'TRIAL' || state.value ==
</template>
<style lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Import unraid-ui globals first */
@import '@unraid/ui/styles';
@import '../../assets/main.css';
.DropdownWrapper_blip {
box-shadow: var(--ring-offset-shadow), var(--ring-shadow), var(--shadow-foreground);

12489
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -65,6 +65,7 @@
"@heroicons/vue": "^2.2.0",
"@nuxtjs/color-mode": "^3.5.2",
"@pinia/nuxt": "^0.9.0",
"@unraid/ui": "file:../unraid-ui",
"@vue/apollo-composable": "^4.2.1",
"@vueuse/components": "^12.0.0",
"@vueuse/integrations": "^12.0.0",
@@ -87,6 +88,9 @@
"vue-i18n": "^10.0.5",
"wretch": "^2.11.0"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "^4.30.1"
},
"overrides": {
"vue": "latest",
"radix-vue": {

View File

@@ -1,24 +1,18 @@
<script lang="ts" setup>
import {
ExclamationTriangleIcon,
} from '@heroicons/vue/24/solid';
import { ExclamationTriangleIcon } from '@heroicons/vue/24/solid';
import { BrandButton, BrandLogo } from '@unraid/ui';
import { serverState } from '~/_data/serverState';
import type { SendPayloads } from '~/store/callback';
import type { UiBadgePropsColor } from '~/types/ui/badge';
import type { ButtonStyle } from '~/types/ui/button';
import AES from 'crypto-js/aes';
import BrandButton from '~/components/Brand/Button.vue';
const { registerEntry } = useCustomElements();
onBeforeMount(() => {
registerEntry('UnraidComponents');
});
useHead({
meta: [
{ name: 'viewport',
content: 'width=1300', }
]
})
meta: [{ name: 'viewport', content: 'width=1300' }],
});
const valueToMakeCallback = ref<SendPayloads | undefined>();
const callbackDestination = ref<string>('');
@@ -42,6 +36,19 @@ const createCallbackUrl = (payload: SendPayloads, sendType: string) => {
callbackDestination.value = destinationUrl.toString(); // differs from callbackActions.send
};
const variants = [
'fill',
'black',
'gray',
'outline',
'outline-black',
'outline-white',
'underline',
'underline-hover-red',
'white',
'none',
] as const;
onMounted(() => {
createCallbackUrl(
[
@@ -59,35 +66,6 @@ onMounted(() => {
'forUpc'
);
});
const badgeColors = [
'black',
'white',
'red',
'yellow',
'green',
'blue',
'indigo',
'purple',
'pink',
'orange',
'transparent',
'current',
'gray',
'custom',
] as UiBadgePropsColor[];
const buttonColors = [
'black',
'fill',
'gray',
'outline',
'outline-black',
'outline-white',
'underline',
'underline-hover-red',
'white',
] as ButtonStyle[];
</script>
<template>
@@ -162,17 +140,16 @@ const buttonColors = [
</code>
</div>
<div class="bg-background">
<hr class="border-black dark:border-white" />
<h2 class="text-xl font-semibold font-mono">Legacy Badge Components</h2>
<template v-for="color in badgeColors" :key="color">
<UiBadge size="14px" :icon="ExclamationTriangleIcon" :color="color">{{ color }}</UiBadge>
</template>
</div>
<div class="bg-background">
<hr class="border-black dark:border-white" />
<h2 class="text-xl font-semibold font-mono">Legacy Button Components</h2>
<template v-for="color in buttonColors" :key="color">
<BrandButton type="button" size="14px" :icon="ExclamationTriangleIcon" :btn-style="color as ButtonStyle">{{ color }}</BrandButton>
<hr class="border-black dark:border-white" />
<h2 class="text-xl font-semibold font-mono">Brand Button Component</h2>
<template v-for="variant in variants" :key="variant">
<BrandButton
:variant="variant"
type="button"
size="14px"
:icon="ExclamationTriangleIcon"
>{{ variant }}</BrandButton
>
</template>
</div>
</div>
@@ -182,6 +159,10 @@ const buttonColors = [
</template>
<style lang="postcss">
/* Import unraid-ui globals first */
@import '@unraid/ui/styles';
@import '../assets/main.css';
code {
@apply rounded-lg bg-gray-200 p-1 text-black shadow;
}

View File

@@ -1,283 +1,34 @@
import 'dotenv/config';
import tailwindConfig from '@unraid/ui/tailwind.config';
import type { Config } from 'tailwindcss';
import type { PluginAPI } from 'tailwindcss/types/config';
import remToRem from './utils/tailwind-rem-to-rem';
// @ts-expect-error - just trying to get this to build @fixme
export default <Partial<Config>>{
export default {
presets: [tailwindConfig],
content: [
// Web components
'./components/**/*.ce.{js,vue,ts}',
// Regular Vue components
'./components/**/*.{js,vue,ts}',
'./layouts/**/*.vue',
'./pages/**/*.vue',
'../unraid-ui/src/**/*.{vue,ts}',
],
darkMode: ['selector'],
safelist: [
'dark',
'DropdownWrapper_blip',
'unraid_mark_1',
'unraid_mark_2',
'unraid_mark_3',
'unraid_mark_4',
'unraid_mark_6',
'unraid_mark_7',
'unraid_mark_8',
'unraid_mark_9',
],
theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px',
},
},
extend: {
fontFamily: {
sans: 'clear-sans,ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji',
},
colors: {
inherit: 'inherit',
transparent: 'transparent',
black: '#1c1b1b',
'grey-darkest': '#222',
'grey-darker': '#606f7b',
'grey-dark': '#383735',
'grey-mid': '#999999',
grey: '#e0e0e0',
'grey-light': '#dae1e7',
'grey-lighter': '#f1f5f8',
'grey-lightest': '#f2f2f2',
white: '#ffffff',
// unraid colors
'yellow-accent': '#E9BF41',
'orange-dark': '#f15a2c',
orange: '#ff8c2f',
// palettes generated from https://uicolors.app/create
'unraid-red': {
DEFAULT: '#E22828',
'50': '#fef2f2',
'100': '#ffe1e1',
'200': '#ffc9c9',
'300': '#fea3a3',
'400': '#fc6d6d',
'500': '#f43f3f',
'600': '#e22828',
'700': '#bd1818',
'800': '#9c1818',
'900': '#821a1a',
'950': '#470808',
},
'unraid-green': {
DEFAULT: '#63A659',
'50': '#f5f9f4',
'100': '#e7f3e5',
'200': '#d0e6cc',
'300': '#aad1a4',
'400': '#7db474',
'500': '#63a659',
'600': '#457b3e',
'700': '#396134',
'800': '#314e2d',
'900': '#284126',
'950': '#122211',
},
'header-text-primary': 'var(--header-text-primary)',
'header-text-secondary': 'var(--header-text-secondary)',
'header-background-color': 'var(--header-background-color)',
// ShadCN
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
// Unfortunately due to webGUI CSS setting base HTML font-size to .65% or something we must use pixel values for web components
fontSize: {
'10px': '10px',
'12px': '12px',
'14px': '14px',
'16px': '16px',
'18px': '18px',
'20px': '20px',
'24px': '24px',
'30px': '30px',
},
spacing: {
'4.5': '1.125rem',
'-8px': '-8px',
'2px': '2px',
'4px': '4px',
'6px': '6px',
'8px': '8px',
'10px': '10px',
'12px': '12px',
'14px': '14px',
'16px': '16px',
'20px': '20px',
'24px': '24px',
'28px': '28px',
'32px': '32px',
'36px': '36px',
'40px': '40px',
'64px': '64px',
'80px': '80px',
'90px': '90px',
'150px': '150px',
'160px': '160px',
'200px': '200px',
'260px': '260px',
'300px': '300px',
'310px': '310px',
'350px': '350px',
'448px': '448px',
'512px': '512px',
'640px': '640px',
'800px': '800px',
},
minWidth: {
'86px': '86px',
'160px': '160px',
'260px': '260px',
'300px': '300px',
'310px': '310px',
'350px': '350px',
'800px': '800px',
},
maxWidth: {
'86px': '86px',
'160px': '160px',
'260px': '260px',
'300px': '300px',
'310px': '310px',
'350px': '350px',
'640px': '640px',
'800px': '800px',
'1024px': '1024px',
},
screens: {
'2xs': '470px',
xs: '530px',
tall: { raw: '(min-height: 700px)' },
},
keyframes: {
'accordion-down': {
from: { height: 0 },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: 0 },
},
'collapsible-down': {
from: { height: 0 },
to: { height: 'var(--radix-collapsible-content-height)' },
},
'collapsible-up': {
from: { height: 'var(--radix-collapsible-content-height)' },
to: { height: 0 },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'collapsible-down': 'collapsible-down 0.2s ease-in-out',
'collapsible-up': 'collapsible-up 0.2s ease-in-out',
},
/**
* @todo modify prose classes to use pixels for webgui…sadge https://tailwindcss.com/docs/typography-plugin#customizing-the-default-theme
*/
typography: (theme: PluginAPI['theme']) => ({
DEFAULT: {
css: {
color: theme('colors.foreground'),
a: {
color: theme('colors.primary'),
textDecoration: 'underline',
'&:hover': {
color: theme('colors.primary-foreground'),
},
},
'--tw-prose-body': theme('colors.foreground'),
'--tw-prose-headings': theme('colors.foreground'),
'--tw-prose-lead': theme('colors.foreground'),
'--tw-prose-links': theme('colors.primary'),
'--tw-prose-bold': theme('colors.foreground'),
'--tw-prose-counters': theme('colors.foreground'),
'--tw-prose-bullets': theme('colors.foreground'),
'--tw-prose-hr': theme('colors.foreground'),
'--tw-prose-quotes': theme('colors.foreground'),
'--tw-prose-quote-borders': theme('colors.foreground'),
'--tw-prose-captions': theme('colors.foreground'),
'--tw-prose-code': theme('colors.foreground'),
'--tw-prose-pre-code': theme('colors.foreground'),
'--tw-prose-pre-bg': theme('colors.background'),
'--tw-prose-th-borders': theme('colors.foreground'),
'--tw-prose-td-borders': theme('colors.foreground'),
'--tw-prose-invert-body': theme('colors.background'),
'--tw-prose-invert-headings': theme('colors.background'),
'--tw-prose-invert-lead': theme('colors.background'),
'--tw-prose-invert-links': theme('colors.primary'),
'--tw-prose-invert-bold': theme('colors.background'),
'--tw-prose-invert-counters': theme('colors.background'),
'--tw-prose-invert-bullets': theme('colors.background'),
'--tw-prose-invert-hr': theme('colors.background'),
'--tw-prose-invert-quotes': theme('colors.background'),
'--tw-prose-invert-quote-borders': theme('colors.background'),
'--tw-prose-invert-captions': theme('colors.background'),
'--tw-prose-invert-code': theme('colors.background'),
'--tw-prose-invert-pre-code': theme('colors.background'),
'--tw-prose-invert-pre-bg': theme('colors.foreground'),
'--tw-prose-invert-th-borders': theme('colors.background'),
'--tw-prose-invert-td-borders': theme('colors.background'),
},
},
}),
},
},
mode: 'jit',
safelist: [],
plugins: [
require('@tailwindcss/typography'),
require('tailwindcss-animate'),
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('./utils/tailwind-rem-to-rem').default({
remToRem({
baseFontSize: 16,
/**
* The font size where the web components will be rendered in production.
* Required due to the webgui using the 62.5% font-size "trick".
* Set an env to 16 for local development and 10 for everything else.
*/
newFontSize: process.env.VITE_TAILWIND_BASE_FONT_SIZE ?? 10,
newFontSize: Number(process.env.VITE_TAILWIND_BASE_FONT_SIZE ?? 10),
}),
],
};
theme: {
extend: {
// web-specific extensions only
},
},
} satisfies Partial<Config>;