mirror of
https://github.com/unraid/api.git
synced 2025-12-30 21:19:49 -06:00
refactor: unraid-ui-web-migration (#1106)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced enhanced stepper components for smoother multi-step interactions. - Added new loading indicators and improved the loading experience with customizable variants. - **UI Improvements** - Refreshed the global color palette and updated styling across buttons, badges, and loading indicators for a more modern, consistent experience. - Improved the organization and readability of templates and styles across various components. - **Code & Dependency Updates** - Updated key dependencies and revised the theme and configuration settings to improve performance and maintainability. - Introduced new environment variables for better configuration management. - **Legacy Cleanup** - Removed deprecated components and streamlined registrations to simplify the codebase without affecting end-user functionality. - Eliminated unused utility functions and legacy code to enhance overall code quality. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: mdatelle <mike@datelle.net> Co-authored-by: Eli Bosley <ekbosley@gmail.com>
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
timestamp=1683971161
|
||||
event=Unraid Parity check
|
||||
subject=Notice [UNRAID] - Parity check finished (0 errors)
|
||||
description=Canceled
|
||||
importance=warning
|
||||
1
unraid-ui/.env.development
Normal file
1
unraid-ui/.env.development
Normal file
@@ -0,0 +1 @@
|
||||
VITE_TAILWIND_BASE_FONT_SIZE=16
|
||||
1
unraid-ui/.gitignore
vendored
Normal file
1
unraid-ui/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.env.development
|
||||
@@ -1,9 +1,12 @@
|
||||
import type { Preview } from "@storybook/vue3";
|
||||
import "../src/styles/globals.css";
|
||||
import type { Preview } from '@storybook/vue3';
|
||||
import '../src/styles/globals.css';
|
||||
import { registerAllComponents } from '../src/register';
|
||||
|
||||
registerAllComponents({});
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import baseConfig from "../tailwind.config";
|
||||
import baseConfig from '../tailwind.config';
|
||||
|
||||
export default {
|
||||
...baseConfig,
|
||||
content: [
|
||||
"../src/components/**/*.{js,vue,ts}",
|
||||
"../src/composables/**/*.{js,vue,ts}",
|
||||
"../stories/**/*.stories.{js,ts,jsx,tsx,mdx}"
|
||||
'../src/components/**/*.{js,vue,ts}',
|
||||
'../src/components/**/*.ce.{js,vue,ts}',
|
||||
'../src/composables/**/*.{js,vue,ts}',
|
||||
'../stories/**/*.stories.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -4,16 +4,15 @@
|
||||
"typescript": true,
|
||||
"tsConfigPath": "./tsconfig.json",
|
||||
"tailwind": {
|
||||
"config": "./tailwind.config.js",
|
||||
"config": "./tailwind.config.ts",
|
||||
"css": "./src/styles/globals.css",
|
||||
"baseColor": "slate",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"framework": "vite",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"types": "@/types"
|
||||
"components": "@/components/common",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
|
||||
31
unraid-ui/package-lock.json
generated
31
unraid-ui/package-lock.json
generated
@@ -15,9 +15,9 @@
|
||||
"clsx": "^2.1.1",
|
||||
"kebab-case": "^2.0.1",
|
||||
"lucide-vue-next": "^0.468.0",
|
||||
"radix-vue": "^1.9.11",
|
||||
"radix-vue": "^1.9.13",
|
||||
"shadcn-vue": "^0.11.3",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"vue-sonner": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -31,6 +31,7 @@
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/testing-library__vue": "^5.0.0",
|
||||
"@unraid/tailwind-rem-to-rem": "^1.1.0",
|
||||
"@vitejs/plugin-vue": "^5.0.0",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"@vitest/coverage-v8": "^1.0.0",
|
||||
@@ -45,6 +46,7 @@
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "3.4.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tailwind-rem-to-rem": "github:unraid/tailwind-rem-to-rem",
|
||||
"tailwindcss": "^3.0.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.0.0",
|
||||
@@ -2797,6 +2799,16 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@unraid/tailwind-rem-to-rem": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@unraid/tailwind-rem-to-rem/-/tailwind-rem-to-rem-1.1.0.tgz",
|
||||
"integrity": "sha512-lc5tqdSs5zwBStlC18lK+pg+iX0/i/JtO8qWOqHNT5KHt66Ba6nwDr+mfKekQq7Bsi8noXMBQJDB5b2J/OmDsw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"tailwindcss": "^3.4.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz",
|
||||
@@ -9539,15 +9551,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz",
|
||||
"integrity": "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
|
||||
"integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-rem-to-rem": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "git+ssh://git@github.com/unraid/tailwind-rem-to-rem.git#a4a3958cd0c6af000a9f87a9d2e8543e976d3ace",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"tailwindcss": "^3.4.17"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||
|
||||
@@ -37,9 +37,9 @@
|
||||
"clsx": "^2.1.1",
|
||||
"kebab-case": "^2.0.1",
|
||||
"lucide-vue-next": "^0.468.0",
|
||||
"radix-vue": "^1.9.11",
|
||||
"radix-vue": "^1.9.13",
|
||||
"shadcn-vue": "^0.11.3",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"vue-sonner": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -53,6 +53,7 @@
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/testing-library__vue": "^5.0.0",
|
||||
"@unraid/tailwind-rem-to-rem": "^1.1.0",
|
||||
"@vitejs/plugin-vue": "^5.0.0",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"@vitest/coverage-v8": "^1.0.0",
|
||||
@@ -67,6 +68,7 @@
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "3.4.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tailwind-rem-to-rem": "github:unraid/tailwind-rem-to-rem",
|
||||
"tailwindcss": "^3.0.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.0.0",
|
||||
|
||||
@@ -1,15 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { computed } from 'vue';
|
||||
import { brandLoadingVariants, markAnimations } from './brand-loading.variants';
|
||||
|
||||
export interface Props {
|
||||
gradientStart?: string;
|
||||
gradientStop?: string;
|
||||
title?: string,
|
||||
variant?: 'default' | 'black' | 'white';
|
||||
size?: 'sm' | 'md' | 'lg' | 'full';
|
||||
class?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
gradientStart: '#e32929',
|
||||
gradientStop: '#ff8d30',
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
variant: 'default',
|
||||
size: 'full',
|
||||
title: 'Loading',
|
||||
});
|
||||
|
||||
const GRADIENT_COLORS = {
|
||||
black: { start: '#000000', stop: '#000000' },
|
||||
white: { start: '#FFFFFF', stop: '#FFFFFF' },
|
||||
default: { start: '#e32929', stop: '#ff8d30' },
|
||||
} as const;
|
||||
|
||||
const gradientColors = computed(() => GRADIENT_COLORS[props.variant]);
|
||||
|
||||
const classes = computed(() => {
|
||||
return cn(brandLoadingVariants({ variant: props.variant, size: props.size }), props.class);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -17,7 +34,7 @@ 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_mark`"
|
||||
:class="classes"
|
||||
role="img"
|
||||
>
|
||||
<title>{{ title }}</title>
|
||||
@@ -31,8 +48,8 @@ withDefaults(defineProps<Props>(), {
|
||||
y2="-4.51"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" :stop-color="gradientStart" />
|
||||
<stop offset="1" :stop-color="gradientStop" />
|
||||
<stop offset="0" :stop-color="gradientColors.start" />
|
||||
<stop offset="1" :stop-color="gradientColors.stop" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
@@ -43,17 +60,17 @@ withDefaults(defineProps<Props>(), {
|
||||
<path
|
||||
d="m70,19.24zm47.65,11.9l-6.55,0l0,-23.79l6.55,0l0,23.79z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_8"
|
||||
:class="['unraid_mark_8', markAnimations.mark_6_8]"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm31.77,-4.54l-6.54,0l0,-14.7l6.54,0l0,14.7z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_7"
|
||||
:class="['unraid_mark_7', markAnimations.mark_7]"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm15.9,11.9l-6.54,0l0,-23.79l6.54,0l0,23.79z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_6"
|
||||
:class="['unraid_mark_6', markAnimations.mark_6_8]"
|
||||
/>
|
||||
<path
|
||||
d="m63.49,19.24l6.51,0l0,38.49l-6.51,0l0,-38.49z"
|
||||
@@ -63,17 +80,17 @@ withDefaults(defineProps<Props>(), {
|
||||
<path
|
||||
d="m70,19.24zm-22.38,26.6l6.54,0l0,23.78l-6.54,0l0,-23.78z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_4"
|
||||
:class="['unraid_mark_4', markAnimations.mark_2_4]"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm-38.26,43.03l6.55,0l0,14.73l-6.55,0l0,-14.73z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_3"
|
||||
:class="['unraid_mark_3', markAnimations.mark_3]"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm-54.13,26.6l6.54,0l0,23.78l-6.54,0l0,-23.78z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_2"
|
||||
:class="['unraid_mark_2', markAnimations.mark_2_4]"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm-63.46,38.49l-6.54,0l0,-38.49l6.54,0l0,38.49z"
|
||||
@@ -82,53 +99,3 @@ withDefaults(defineProps<Props>(), {
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped lang="css">
|
||||
.unraid_mark_2,
|
||||
.unraid_mark_4 {
|
||||
animation: mark_2 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_3 {
|
||||
animation: mark_3 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_6,
|
||||
.unraid_mark_8 {
|
||||
animation: mark_6 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_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,7 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import BrandLoading from './BrandLoading.ce.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BrandLoading gradient-start="#ffffff" gradient-stop="#ffffff" />
|
||||
</template>
|
||||
27
unraid-ui/src/components/brand/brand-loading.variants.ts
Normal file
27
unraid-ui/src/components/brand/brand-loading.variants.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
export const brandLoadingVariants = cva('inline-flex items-center justify-center w-full h-full', {
|
||||
variants: {
|
||||
variant: {
|
||||
default: '',
|
||||
black: 'text-black fill-black',
|
||||
white: 'text-white fill-white',
|
||||
},
|
||||
size: {
|
||||
sm: 'h-12 w-12',
|
||||
md: 'h-16 w-16',
|
||||
lg: 'h-20 w-20',
|
||||
full: 'h-full w-full',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
export const markAnimations = {
|
||||
mark_2_4: 'animate-mark-2',
|
||||
mark_3: 'animate-mark-3',
|
||||
mark_6_8: 'animate-mark-6',
|
||||
mark_7: 'animate-mark-7',
|
||||
};
|
||||
@@ -1,6 +1,9 @@
|
||||
export { default as BrandButton } from "./BrandButton.vue";
|
||||
export { brandButtonVariants } from "./brand-button.variants";
|
||||
export { default as BrandLoading } from "./BrandLoading.ce.vue";
|
||||
export { default as BrandLoadingWhite } from "./BrandLoadingWhite.vue";
|
||||
export { default as BrandLogo } from "./BrandLogo.vue";
|
||||
export { default as BrandLogoConnect } from "./BrandLogoConnect.vue";
|
||||
export { default as BrandButton } from './BrandButton.vue';
|
||||
export { brandButtonVariants } from './brand-button.variants';
|
||||
export { default as BrandLoading } from './BrandLoading.ce.vue';
|
||||
export { brandLoadingVariants } from './brand-loading.variants';
|
||||
export { default as BrandLogo } from './BrandLogo.vue';
|
||||
export { default as BrandLogoConnect } from './BrandLogoConnect.vue';
|
||||
|
||||
// Type exports
|
||||
export type { BrandButtonProps } from './BrandButton.vue';
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import type { Component } from "vue";
|
||||
import { badgeVariants } from "./badge.variants";
|
||||
import { computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import { badgeVariants } from './badge.variants';
|
||||
|
||||
export interface BadgeProps {
|
||||
variant?: "red" | "yellow" | "green" | "blue" | "indigo" | "purple" |
|
||||
"pink" | "orange" | "black" | "white" | "transparent" | "current" | "gray" | "custom";
|
||||
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
|
||||
variant?:
|
||||
| 'red'
|
||||
| 'yellow'
|
||||
| 'green'
|
||||
| 'blue'
|
||||
| 'indigo'
|
||||
| 'purple'
|
||||
| 'pink'
|
||||
| 'orange'
|
||||
| 'black'
|
||||
| 'white'
|
||||
| 'transparent'
|
||||
| 'current'
|
||||
| 'gray'
|
||||
| 'custom';
|
||||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
||||
icon?: Component;
|
||||
iconRight?: Component;
|
||||
iconStyles?: string;
|
||||
@@ -14,45 +27,35 @@ export interface BadgeProps {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<BadgeProps>(), {
|
||||
variant: "gray",
|
||||
size: "md",
|
||||
variant: 'gray',
|
||||
size: 'md',
|
||||
icon: undefined,
|
||||
iconRight: undefined,
|
||||
iconStyles: "",
|
||||
class: "",
|
||||
iconStyles: '',
|
||||
class: '',
|
||||
});
|
||||
|
||||
const badgeClasses = computed(() => {
|
||||
const iconSizes = {
|
||||
xs: "w-12px",
|
||||
sm: "w-14px",
|
||||
md: "w-16px",
|
||||
lg: "w-18px",
|
||||
xl: "w-20px",
|
||||
"2xl": "w-24px",
|
||||
xs: 'w-12px',
|
||||
sm: 'w-14px',
|
||||
md: 'w-16px',
|
||||
lg: 'w-18px',
|
||||
xl: 'w-20px',
|
||||
'2xl': 'w-24px',
|
||||
} as const;
|
||||
|
||||
return {
|
||||
badge: badgeVariants({ variant: props.variant, size: props.size }),
|
||||
icon: `${iconSizes[props.size ?? "md"]} ${props.iconStyles}`,
|
||||
icon: `${iconSizes[props.size ?? 'md']} ${props.iconStyles}`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="[badgeClasses.badge, props.class]">
|
||||
<component
|
||||
:is="icon"
|
||||
v-if="icon"
|
||||
class="flex-shrink-0"
|
||||
:class="badgeClasses.icon"
|
||||
/>
|
||||
<component :is="icon" v-if="icon" class="flex-shrink-0" :class="badgeClasses.icon" />
|
||||
<slot />
|
||||
<component
|
||||
:is="iconRight"
|
||||
v-if="iconRight"
|
||||
class="flex-shrink-0"
|
||||
:class="badgeClasses.icon"
|
||||
/>
|
||||
<component :is="iconRight" v-if="iconRight" class="flex-shrink-0" :class="badgeClasses.icon" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
export { default as Badge } from "./Badge.vue";
|
||||
export { badgeVariants } from "./badge.variants";
|
||||
export { default as Badge } from './Badge.vue';
|
||||
export { badgeVariants } from './badge.variants';
|
||||
|
||||
// Type exports
|
||||
export type { BadgeProps } from './Badge.vue';
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
export { default as Button } from "./Button.vue";
|
||||
export { buttonVariants } from "./button.variants";
|
||||
export { default as Button } from './Button.vue';
|
||||
export { buttonVariants } from './button.variants';
|
||||
|
||||
// Type exports
|
||||
export type { ButtonProps } from './Button.vue';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const props = withDefaults(defineProps<{ class?: string }>(), { class: "" });
|
||||
const props = withDefaults(defineProps<{ class?: string }>(), { class: '' });
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
|
||||
23
unraid-ui/src/components/common/stepper/Stepper.vue
Normal file
23
unraid-ui/src/components/common/stepper/Stepper.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { StepperRootEmits, StepperRootProps } from 'radix-vue';
|
||||
import { StepperRoot, useForwardPropsEmits } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<StepperRootProps & { class?: HTMLAttributes['class'] }>();
|
||||
const emits = defineEmits<StepperRootEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StepperRoot v-slot="slotProps" :class="cn('flex gap-2', props.class)" v-bind="forwarded">
|
||||
<slot v-bind="slotProps" />
|
||||
</StepperRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { StepperDescriptionProps } from 'radix-vue';
|
||||
import { StepperDescription, useForwardProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<StepperDescriptionProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StepperDescription
|
||||
v-slot="slotProps"
|
||||
v-bind="forwarded"
|
||||
:class="cn('text-xs text-muted-foreground', props.class)"
|
||||
>
|
||||
<slot v-bind="slotProps" />
|
||||
</StepperDescription>
|
||||
</template>
|
||||
@@ -1,19 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import type { StepperIndicatorProps } from 'radix-vue'
|
||||
import { cn } from '@/components/shadcn/utils'
|
||||
import { StepperIndicator, useForwardProps } from 'radix-vue'
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { StepperIndicatorProps } from 'radix-vue';
|
||||
import { StepperIndicator, useForwardProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<StepperIndicatorProps & { class?: HTMLAttributes['class'] }>()
|
||||
const props = defineProps<StepperIndicatorProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated
|
||||
})
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps)
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
26
unraid-ui/src/components/common/stepper/StepperItem.vue
Normal file
26
unraid-ui/src/components/common/stepper/StepperItem.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { StepperItemProps } from 'radix-vue';
|
||||
import { StepperItem, useForwardProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<StepperItemProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StepperItem
|
||||
v-slot="slotProps"
|
||||
v-bind="forwarded"
|
||||
:class="cn('flex items-center gap-2 group data-[disabled]:pointer-events-none', props.class)"
|
||||
>
|
||||
<slot v-bind="slotProps" />
|
||||
</StepperItem>
|
||||
</template>
|
||||
@@ -1,19 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import type { StepperSeparatorProps } from 'radix-vue'
|
||||
import { cn } from '@/components/shadcn/utils'
|
||||
import { StepperSeparator, useForwardProps } from 'radix-vue'
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { StepperSeparatorProps } from 'radix-vue';
|
||||
import { StepperSeparator, useForwardProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<StepperSeparatorProps & { class?: HTMLAttributes['class'] }>()
|
||||
const props = defineProps<StepperSeparatorProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated
|
||||
})
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps)
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
22
unraid-ui/src/components/common/stepper/StepperTitle.vue
Normal file
22
unraid-ui/src/components/common/stepper/StepperTitle.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { StepperTitleProps } from 'radix-vue';
|
||||
import { StepperTitle, useForwardProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<StepperTitleProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StepperTitle v-bind="forwarded" :class="cn('text-md font-semibold whitespace-nowrap', props.class)">
|
||||
<slot />
|
||||
</StepperTitle>
|
||||
</template>
|
||||
25
unraid-ui/src/components/common/stepper/StepperTrigger.vue
Normal file
25
unraid-ui/src/components/common/stepper/StepperTrigger.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { StepperTriggerProps } from 'radix-vue';
|
||||
import { StepperTrigger, useForwardProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<StepperTriggerProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StepperTrigger
|
||||
v-bind="forwarded"
|
||||
:class="cn('p-2 flex flex-col items-center text-center gap-2 rounded-md', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</StepperTrigger>
|
||||
</template>
|
||||
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
SelectContent,
|
||||
type SelectContentEmits,
|
||||
type SelectContentProps,
|
||||
SelectPortal,
|
||||
SelectViewport,
|
||||
useForwardPropsEmits,
|
||||
} from "radix-vue";
|
||||
import { SelectScrollDownButton, SelectScrollUpButton } from ".";
|
||||
import { cn } from "@/lib/utils";
|
||||
type SelectContentEmits,
|
||||
type SelectContentProps,
|
||||
} from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
import { SelectScrollDownButton, SelectScrollUpButton } from '.';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
@@ -18,16 +18,16 @@ defineOptions({
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
SelectContentProps & {
|
||||
class?: HTMLAttributes["class"];
|
||||
class?: HTMLAttributes['class'];
|
||||
disabled?: boolean;
|
||||
forceMount?: boolean;
|
||||
to?: string | HTMLElement | Element;
|
||||
}
|
||||
>(),
|
||||
{
|
||||
position: "popper",
|
||||
position: 'popper',
|
||||
class: undefined,
|
||||
to: "#modals",
|
||||
to: '#modals',
|
||||
}
|
||||
);
|
||||
const emits = defineEmits<SelectContentEmits>();
|
||||
@@ -42,11 +42,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectPortal
|
||||
:disabled="disabled"
|
||||
:force-mount="forceMount"
|
||||
:to="to as HTMLElement"
|
||||
>
|
||||
<SelectPortal :disabled="disabled" :force-mount="forceMount" :to="to as HTMLElement">
|
||||
<SelectContent
|
||||
v-bind="{ ...forwarded, ...$attrs }"
|
||||
:class="
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
export { Badge } from '@/components/common/badge';
|
||||
export {
|
||||
BrandButton,
|
||||
BrandLoading,
|
||||
BrandLoadingWhite,
|
||||
BrandLogo,
|
||||
BrandLogoConnect,
|
||||
} from '@/components/brand';
|
||||
export { BrandButton, BrandLoading, BrandLogo, BrandLogoConnect } from '@/components/brand';
|
||||
export { Button } from '@/components/common/button';
|
||||
export { CardWrapper, PageContainer } from '@/components/layout';
|
||||
export {
|
||||
|
||||
@@ -1,44 +1,58 @@
|
||||
// Styles
|
||||
import "./styles/index.css";
|
||||
|
||||
// Config
|
||||
import tailwindConfig from "../tailwind.config";
|
||||
|
||||
// Lib
|
||||
import { cn, scaleRemFactor } from "@/lib/utils";
|
||||
|
||||
import './styles/index.css';
|
||||
import {
|
||||
BrandButton,
|
||||
brandButtonVariants,
|
||||
BrandLoading,
|
||||
brandLoadingVariants,
|
||||
BrandLogo,
|
||||
BrandLogoConnect,
|
||||
type BrandButtonProps,
|
||||
} from '@/components/brand';
|
||||
// Components
|
||||
import { Badge } from "@/components/common/badge";
|
||||
import {
|
||||
BrandButton,
|
||||
brandButtonVariants,
|
||||
BrandLoading,
|
||||
BrandLoadingWhite,
|
||||
BrandLogo,
|
||||
BrandLogoConnect
|
||||
} from "@/components/brand";
|
||||
import { Button, buttonVariants } from "@/components/common/button";
|
||||
import { CardWrapper, PageContainer } from "@/components/layout";
|
||||
import { Badge, type BadgeProps } from '@/components/common/badge';
|
||||
import { Button, buttonVariants, type ButtonProps } from '@/components/common/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
} from "@/components/common/dropdown-menu";
|
||||
import { Bar, Error, Spinner } from "@/components/common/loading";
|
||||
import { Input } from "@/components/form/input";
|
||||
import { Label } from "@/components/form/label";
|
||||
import { Lightswitch } from "@/components/form/lightswitch";
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/common/dropdown-menu';
|
||||
import { Bar, Error, Spinner } from '@/components/common/loading';
|
||||
import { ScrollArea, ScrollBar } from '@/components/common/scroll-area';
|
||||
import {
|
||||
Sheet,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@/components/common/sheet';
|
||||
import {
|
||||
Stepper,
|
||||
StepperDescription,
|
||||
StepperItem,
|
||||
StepperSeparator,
|
||||
StepperTitle,
|
||||
StepperTrigger,
|
||||
} from '@/components/common/stepper';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/common/tabs';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/common/tooltip';
|
||||
import { Input } from '@/components/form/input';
|
||||
import { Label } from '@/components/form/label';
|
||||
import { Lightswitch } from '@/components/form/lightswitch';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -46,39 +60,20 @@ import {
|
||||
SelectItem,
|
||||
SelectItemText,
|
||||
SelectLabel,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
SelectScrollUpButton,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/form/select";
|
||||
import { Switch, SwitchHeadlessUI } from "@/components/form/switch";
|
||||
import { ScrollArea, ScrollBar } from "@/components/common/scroll-area";
|
||||
import {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
SheetContent,
|
||||
SheetClose,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
} from "@/components/common/sheet";
|
||||
import {
|
||||
Tabs,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
TabsContent,
|
||||
} from "@/components/common/tabs";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
TooltipProvider,
|
||||
} from "@/components/common/tooltip";
|
||||
|
||||
} from '@/components/form/select';
|
||||
import { Switch, SwitchHeadlessUI } from '@/components/form/switch';
|
||||
import { CardWrapper, PageContainer } from '@/components/layout';
|
||||
// Composables
|
||||
import useTeleport from "@/composables/useTeleport";
|
||||
import useTeleport from '@/composables/useTeleport';
|
||||
// Lib
|
||||
import { cn } from '@/lib/utils';
|
||||
// Config
|
||||
import tailwindConfig from '../tailwind.config';
|
||||
|
||||
// Export
|
||||
export {
|
||||
@@ -87,7 +82,7 @@ export {
|
||||
BrandButton,
|
||||
brandButtonVariants,
|
||||
BrandLoading,
|
||||
BrandLoadingWhite,
|
||||
brandLoadingVariants,
|
||||
BrandLogo,
|
||||
BrandLogoConnect,
|
||||
Button,
|
||||
@@ -112,7 +107,6 @@ export {
|
||||
Input,
|
||||
Label,
|
||||
PageContainer,
|
||||
scaleRemFactor,
|
||||
ScrollBar,
|
||||
ScrollArea,
|
||||
Select,
|
||||
@@ -135,6 +129,12 @@ export {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
Spinner,
|
||||
Stepper,
|
||||
StepperDescription,
|
||||
StepperItem,
|
||||
StepperSeparator,
|
||||
StepperTitle,
|
||||
StepperTrigger,
|
||||
Switch,
|
||||
SwitchHeadlessUI,
|
||||
tailwindConfig,
|
||||
@@ -148,5 +148,10 @@ export {
|
||||
TooltipTrigger,
|
||||
TooltipProvider,
|
||||
useTeleport,
|
||||
|
||||
// Type exports
|
||||
type BrandButtonProps,
|
||||
type BadgeProps,
|
||||
type ButtonProps,
|
||||
};
|
||||
export { Toaster } from '@/components/common/toast';
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
import { scaleRemFactor } from "../utils";
|
||||
import defaultTheme from "tailwindcss/defaultTheme";
|
||||
import plugin from "tailwindcss/plugin";
|
||||
|
||||
export default plugin.withOptions(
|
||||
() => {
|
||||
return function () {
|
||||
// Plugin functionality can be added here if needed in the future
|
||||
};
|
||||
},
|
||||
(options?: {
|
||||
baseFontSize?: number;
|
||||
newFontSize?: number;
|
||||
}): Partial<Config> => {
|
||||
const baseFontSize = options?.baseFontSize ?? 16;
|
||||
const newFontSize = options?.newFontSize ?? 10;
|
||||
|
||||
return {
|
||||
theme: scaleRemFactor(
|
||||
defaultTheme,
|
||||
baseFontSize,
|
||||
newFontSize
|
||||
) as Config["theme"],
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -1,59 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
type RemToPxInput = unknown;
|
||||
|
||||
function isFunction(
|
||||
input: RemToPxInput
|
||||
): input is (...args: unknown[]) => boolean {
|
||||
return typeof input === "function";
|
||||
}
|
||||
|
||||
export function scaleRemFactor(
|
||||
input: RemToPxInput,
|
||||
baseFontSize = 16,
|
||||
newFontSize = 10
|
||||
): unknown {
|
||||
const scaleFactor = baseFontSize / newFontSize; // baseFontSize / newFontSize;
|
||||
if (input == null) return input;
|
||||
|
||||
if (typeof input === "string") {
|
||||
return input.replace(
|
||||
/(\d*\.?\d+)rem$/,
|
||||
(_, val) => `${(parseFloat(val) * scaleFactor).toFixed(4)}rem`
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(input))
|
||||
return input.map((val) => scaleRemFactor(val, baseFontSize, newFontSize));
|
||||
|
||||
if (typeof input === "object") {
|
||||
const ret: Record<string, RemToPxInput> = {};
|
||||
const obj = input as Record<string, RemToPxInput>;
|
||||
for (const key in obj) {
|
||||
ret[key] = scaleRemFactor(obj[key], baseFontSize, newFontSize);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (isFunction(input)) {
|
||||
return function (...args: unknown[]): unknown {
|
||||
const replacedArgs = args.map((arg) => {
|
||||
if (typeof arg === "string") {
|
||||
return arg.replace(
|
||||
/(\d*\.?\d+)rem/g,
|
||||
(_, val) => `${(parseFloat(val) * scaleFactor).toFixed(4)}rem`
|
||||
);
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
return input(...replacedArgs);
|
||||
};
|
||||
}
|
||||
|
||||
return input;
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ type RegisterParams = {
|
||||
};
|
||||
|
||||
export function registerAllComponents(params: RegisterParams = {}) {
|
||||
const { namePrefix = 'unraid', pathToSharedCss = './src/styles/index.css' } = params;
|
||||
const { namePrefix = 'uui', pathToSharedCss = './src/styles/index.css' } = params;
|
||||
Object.entries(Components).forEach(([name, component]) => {
|
||||
// add our shared css to each web component
|
||||
component.styles ??= [];
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* @tailwind directives */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -6,42 +5,66 @@
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--ring: 0 0% 3.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--ring: 0 0% 83.1%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,4 +75,4 @@
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
/* global styles for unraid-ui */
|
||||
@import "./globals.css";
|
||||
@import "./sonner.css";
|
||||
|
||||
@import 'globals.css';
|
||||
@import 'sonner.css';
|
||||
|
||||
@@ -1,248 +1,268 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
import type { PluginAPI } from "tailwindcss/types/config";
|
||||
import typography from "@tailwindcss/typography";
|
||||
import animate from "tailwindcss-animate";
|
||||
import typography from '@tailwindcss/typography';
|
||||
import type { Config } from 'tailwindcss';
|
||||
import animate from 'tailwindcss-animate';
|
||||
import type { PluginAPI } from 'tailwindcss/types/config';
|
||||
|
||||
export const unraidPreset = {
|
||||
darkMode: ['selector', '[data-mode="dark"]'],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
padding: '2rem',
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
'2xl': '1400px',
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
borderColor: {
|
||||
DEFAULT: "hsl(var(--border))",
|
||||
DEFAULT: 'hsl(var(--border))',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: "clear-sans,ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
"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",
|
||||
'yellow-accent': '#E9BF41',
|
||||
'orange-dark': '#f15a2c',
|
||||
orange: '#ff8c2f',
|
||||
'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",
|
||||
'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)",
|
||||
'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))",
|
||||
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))",
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))',
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))',
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))',
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))',
|
||||
},
|
||||
},
|
||||
fontSize: {
|
||||
"10px": "10px",
|
||||
"12px": "12px",
|
||||
"14px": "14px",
|
||||
"16px": "16px",
|
||||
"18px": "18px",
|
||||
"20px": "20px",
|
||||
"24px": "24px",
|
||||
"30px": "30px",
|
||||
'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",
|
||||
'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",
|
||||
'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",
|
||||
'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)" },
|
||||
'2xs': '470px',
|
||||
xs: '530px',
|
||||
tall: { raw: '(min-height: 700px)' },
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
'accordion-down': {
|
||||
from: { height: '0' },
|
||||
to: { height: 'var(--radix-accordion-content-height)' },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
'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-down': {
|
||||
from: { height: '0' },
|
||||
to: { height: 'var(--radix-collapsible-content-height)' },
|
||||
},
|
||||
"collapsible-up": {
|
||||
from: { height: "var(--radix-collapsible-content-height)" },
|
||||
to: { height: "0" },
|
||||
'collapsible-up': {
|
||||
from: { height: 'var(--radix-collapsible-content-height)' },
|
||||
to: { height: '0' },
|
||||
},
|
||||
'mark-2': {
|
||||
'50%': { transform: 'translateY(-40px)' },
|
||||
'100%': { transform: 'translateY(0)' },
|
||||
},
|
||||
'mark-3': {
|
||||
'50%': { transform: 'translateY(-62px)' },
|
||||
'100%': { transform: 'translateY(0)' },
|
||||
},
|
||||
'mark-6': {
|
||||
'50%': { transform: 'translateY(40px)' },
|
||||
'100%': { transform: 'translateY(0)' },
|
||||
},
|
||||
'mark-7': {
|
||||
'50%': { transform: 'translateY(62px)' },
|
||||
'100%': { transform: 'translateY(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",
|
||||
'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',
|
||||
'mark-2': 'mark-2 1.5s ease infinite',
|
||||
'mark-3': 'mark-3 1.5s ease infinite',
|
||||
'mark-6': 'mark-6 1.5s ease infinite',
|
||||
'mark-7': 'mark-7 1.5s ease infinite',
|
||||
},
|
||||
typography: (theme: PluginAPI["theme"]) => ({
|
||||
typography: (theme: PluginAPI['theme']) => ({
|
||||
DEFAULT: {
|
||||
css: {
|
||||
color: theme("colors.foreground"),
|
||||
color: theme('colors.foreground'),
|
||||
a: {
|
||||
color: theme("colors.primary"),
|
||||
textDecoration: "underline",
|
||||
"&:hover": {
|
||||
color: theme("colors.primary-foreground"),
|
||||
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"),
|
||||
'--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'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
plugins: [typography, animate],
|
||||
} satisfies Partial<Config>;
|
||||
} satisfies Partial<Config>;
|
||||
|
||||
11
unraid-ui/src/types/components.d.ts
vendored
Normal file
11
unraid-ui/src/types/components.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue';
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
declare module '*.ce.vue' {
|
||||
import type { DefineComponent } from 'vue';
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
@@ -1,39 +1,49 @@
|
||||
import type { Meta, StoryObj } from "@storybook/vue3";
|
||||
import BrandLoading from "../../../src/components/brand/BrandLoading.ce.vue";
|
||||
import type { Meta, StoryObj } from '@storybook/vue3';
|
||||
import BrandLoadingCe from '../../../src/components/brand/BrandLoading.ce.vue';
|
||||
|
||||
const meta = {
|
||||
title: "Components/Brand",
|
||||
component: BrandLoading,
|
||||
title: 'Components/Brand',
|
||||
component: BrandLoadingCe,
|
||||
argTypes: {
|
||||
gradientStart: { control: 'color' },
|
||||
gradientStop: { control: 'color' },
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['default', 'black', 'white'],
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['sm', 'md', 'lg', 'full'],
|
||||
},
|
||||
title: { control: 'text' },
|
||||
},
|
||||
} satisfies Meta<typeof BrandLoading>;
|
||||
} satisfies Meta<typeof BrandLoadingCe>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Loading: Story = {
|
||||
args: {
|
||||
gradientStart: '#e32929',
|
||||
gradientStop: '#ff8d30',
|
||||
title: 'Loading',
|
||||
},
|
||||
const defaultArgs = {
|
||||
variant: 'default' as const,
|
||||
title: 'Loading',
|
||||
size: 'full' as const,
|
||||
};
|
||||
|
||||
// Web Component version
|
||||
export const LoadingCE: Story = {
|
||||
args: defaultArgs,
|
||||
render: (args) => ({
|
||||
components: { BrandLoading },
|
||||
setup() {
|
||||
return { args };
|
||||
},
|
||||
template: `
|
||||
<div class="w-[200px]">
|
||||
<BrandLoading
|
||||
:gradient-start="args.gradientStart"
|
||||
:gradient-stop="args.gradientStop"
|
||||
:title="args.title"
|
||||
/>
|
||||
<div>
|
||||
<div class="w-[200px]">
|
||||
<uui-brand-loading
|
||||
:variant="args.variant"
|
||||
:size="args.size"
|
||||
:title="args.title"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/vue3";
|
||||
import BrandLoadingWhite from "../../../src/components/brand/BrandLoadingWhite.vue";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Brand",
|
||||
component: BrandLoadingWhite,
|
||||
decorators: [
|
||||
() => ({
|
||||
template: '<div class="bg-black p-8 rounded-md"><story/></div>',
|
||||
}),
|
||||
],
|
||||
parameters: {
|
||||
backgrounds: {
|
||||
default: 'dark',
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof BrandLoadingWhite>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const LoadingWhite: Story = {
|
||||
render: () => ({
|
||||
components: { BrandLoadingWhite },
|
||||
template: `
|
||||
<div class="w-[200px]">
|
||||
<BrandLoadingWhite />
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
@@ -1,11 +1,13 @@
|
||||
import tailwindRemToRem from './src/lib/tailwind-rem-to-rem';
|
||||
import tailwindRemToRem from '@unraid/tailwind-rem-to-rem';
|
||||
import type { Config } from 'tailwindcss';
|
||||
import { unraidPreset } from './src/theme/preset';
|
||||
|
||||
|
||||
export default {
|
||||
presets: [unraidPreset],
|
||||
content: [
|
||||
'./src/components/**/*.{js,vue,ts}',
|
||||
'./src/components/**/*.ce.{js,vue,ts}',
|
||||
'./src/composables/**/*.{js,vue,ts}',
|
||||
'./stories/**/*.stories.{js,ts,jsx,tsx,mdx}',
|
||||
'./index.html',
|
||||
@@ -30,5 +32,10 @@ export default {
|
||||
variants: ['group-hover', 'group-focus'],
|
||||
},
|
||||
],
|
||||
plugins: [tailwindRemToRem({ baseFontSize: 16, newFontSize: process.env.REM_PLUGIN ? 10 : 16 })],
|
||||
} satisfies Partial<Config>;
|
||||
plugins: [
|
||||
tailwindRemToRem({
|
||||
baseFontSize: 16,
|
||||
newFontSize: Number(process.env.VITE_TAILWIND_BASE_FONT_SIZE ?? 10),
|
||||
}),
|
||||
],
|
||||
} satisfies Partial<Config>;
|
||||
@@ -23,6 +23,7 @@
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"src/**/*.ce.vue",
|
||||
"./tailwind.config.ts",
|
||||
"src/theme/**/*.ts"
|
||||
],
|
||||
|
||||
@@ -8,6 +8,48 @@ const config = {
|
||||
printWidth: 105,
|
||||
singleQuote: true,
|
||||
plugins: ['prettier-plugin-tailwindcss', '@ianvs/prettier-plugin-sort-imports'],
|
||||
// decorators-legacy lets the import sorter transform files with decorators
|
||||
importOrderParserPlugins: ['typescript', 'decorators-legacy'],
|
||||
importOrder: [
|
||||
/**----------------------
|
||||
* Style imports
|
||||
*------------------------**/
|
||||
'^tailwindcss',
|
||||
'^~/assets',
|
||||
'',
|
||||
/**----------------------
|
||||
* Vue & Framework
|
||||
*------------------------**/
|
||||
'^vue$',
|
||||
'^vue-i18n$',
|
||||
'^vue-router$',
|
||||
'^pinia$',
|
||||
'^@vue',
|
||||
'^@nuxt',
|
||||
'',
|
||||
/**----------------------
|
||||
* Third party
|
||||
*------------------------**/
|
||||
'^@heroicons',
|
||||
'^@unraid/ui',
|
||||
'<THIRD_PARTY_MODULES>',
|
||||
'',
|
||||
/**----------------------
|
||||
* Types
|
||||
*------------------------**/
|
||||
'<TYPES>^@/types',
|
||||
'<TYPES>^[.]',
|
||||
'<TYPES>',
|
||||
'',
|
||||
/**----------------------
|
||||
* Local imports
|
||||
*------------------------**/
|
||||
'^~/components',
|
||||
'^~/composables',
|
||||
'^~/store',
|
||||
'^~/utils',
|
||||
'^[.]',
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
// import QueryStringAddon from 'wretch/addons/queryString';
|
||||
|
||||
// import { OS_RELEASES } from '~/helpers/urls';
|
||||
import type { Server, ServerState
|
||||
// ServerUpdateOsResponse,
|
||||
import type {
|
||||
Server,
|
||||
ServerState,
|
||||
// ServerUpdateOsResponse,
|
||||
} from '~/types/server';
|
||||
|
||||
// dayjs plugins
|
||||
@@ -201,4 +203,4 @@ export const serverState: Server = {
|
||||
uptime,
|
||||
username: 'zspearmint',
|
||||
wanFQDN: '',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<script lang="ts" setup>
|
||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/vue/24/solid";
|
||||
import { storeToRefs } from "pinia";
|
||||
import type { ComposerTranslation } from "vue-i18n";
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { useActivationCodeStore } from "~/store/activationCode";
|
||||
import { usePurchaseStore } from "~/store/purchase";
|
||||
import type { ButtonProps } from "~/types/ui/button";
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import { BrandButton } from '@unraid/ui';
|
||||
|
||||
import ActivationPartnerLogo from "~/components/Activation/PartnerLogo.vue";
|
||||
import type { BrandButtonProps } from '@unraid/ui';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import ActivationPartnerLogo from '~/components/Activation/PartnerLogo.vue';
|
||||
import { useActivationCodeStore } from '~/store/activationCode';
|
||||
import { usePurchaseStore } from '~/store/purchase';
|
||||
|
||||
export interface Props {
|
||||
t: ComposerTranslation;
|
||||
@@ -25,23 +27,23 @@ const description = computed<string>(() =>
|
||||
`On the following screen, your license will be activated. You’ll then create an Unraid.net Account to manage your license going forward.`
|
||||
)
|
||||
);
|
||||
const docsButtons = computed<ButtonProps[]>(() => {
|
||||
const docsButtons = computed<BrandButtonProps[]>(() => {
|
||||
return [
|
||||
{
|
||||
btnStyle: "underline",
|
||||
btnStyle: 'underline',
|
||||
external: true,
|
||||
href: "https://docs.unraid.net/unraid-os/faq/licensing-faq/",
|
||||
href: 'https://docs.unraid.net/unraid-os/faq/licensing-faq/',
|
||||
iconRight: ArrowTopRightOnSquareIcon,
|
||||
size: "14px",
|
||||
text: props.t("More about Licensing"),
|
||||
size: '14px',
|
||||
text: props.t('More about Licensing'),
|
||||
},
|
||||
{
|
||||
btnStyle: "underline",
|
||||
btnStyle: 'underline',
|
||||
external: true,
|
||||
href: "https://docs.unraid.net/account/",
|
||||
href: 'https://docs.unraid.net/account/',
|
||||
iconRight: ArrowTopRightOnSquareIcon,
|
||||
size: "14px",
|
||||
text: props.t("More about Unraid.net Accounts"),
|
||||
size: '14px',
|
||||
text: props.t('More about Unraid.net Accounts'),
|
||||
},
|
||||
];
|
||||
});
|
||||
@@ -49,17 +51,17 @@ const docsButtons = computed<ButtonProps[]>(() => {
|
||||
/**
|
||||
* Listen for konami code sequence to close the modal
|
||||
*/
|
||||
const keySequence = [
|
||||
"ArrowUp",
|
||||
"ArrowUp",
|
||||
"ArrowDown",
|
||||
"ArrowDown",
|
||||
"ArrowLeft",
|
||||
"ArrowRight",
|
||||
"ArrowLeft",
|
||||
"ArrowRight",
|
||||
"b",
|
||||
"a",
|
||||
const keySequence = [
|
||||
'ArrowUp',
|
||||
'ArrowUp',
|
||||
'ArrowDown',
|
||||
'ArrowDown',
|
||||
'ArrowLeft',
|
||||
'ArrowRight',
|
||||
'ArrowLeft',
|
||||
'ArrowRight',
|
||||
'b',
|
||||
'a',
|
||||
];
|
||||
let sequenceIndex = 0;
|
||||
|
||||
@@ -72,16 +74,16 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
|
||||
if (sequenceIndex === keySequence.length) {
|
||||
activationCodeStore.setActivationModalHidden(true);
|
||||
window.location.href = "/Tools/Registration";
|
||||
window.location.href = '/Tools/Registration';
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("keydown", handleKeydown);
|
||||
window.addEventListener('keydown', handleKeydown);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("keydown", handleKeydown);
|
||||
window.removeEventListener('keydown', handleKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { Button } from "@/components/shadcn/button";
|
||||
|
||||
import { CheckIcon, KeyIcon, ServerStackIcon } from '@heroicons/vue/24/outline';
|
||||
import {
|
||||
KeyIcon as KeyIconSolid,
|
||||
LockClosedIcon,
|
||||
ServerStackIcon as ServerStackIconSolid,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import {
|
||||
Button,
|
||||
Stepper,
|
||||
StepperDescription,
|
||||
StepperItem,
|
||||
StepperSeparator,
|
||||
StepperTitle,
|
||||
StepperTrigger,
|
||||
} from "@/components/shadcn/stepper";
|
||||
import { CheckIcon, KeyIcon, ServerStackIcon } from "@heroicons/vue/24/outline";
|
||||
import {
|
||||
KeyIcon as KeyIconSolid,
|
||||
LockClosedIcon,
|
||||
ServerStackIcon as ServerStackIconSolid,
|
||||
} from "@heroicons/vue/24/solid";
|
||||
} from '@unraid/ui';
|
||||
|
||||
import type { Component } from 'vue'
|
||||
import type { Component } from 'vue';
|
||||
|
||||
type StepState = "inactive" | "active" | "completed";
|
||||
type StepState = 'inactive' | 'active' | 'completed';
|
||||
|
||||
withDefaults(defineProps<{
|
||||
activeStep: number
|
||||
}>(), {
|
||||
activeStep: 1
|
||||
})
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
activeStep: number;
|
||||
}>(),
|
||||
{
|
||||
activeStep: 1,
|
||||
}
|
||||
);
|
||||
|
||||
interface Step {
|
||||
step: number;
|
||||
@@ -35,12 +37,12 @@ interface Step {
|
||||
active: Component;
|
||||
completed: Component;
|
||||
};
|
||||
};
|
||||
}
|
||||
const steps: readonly Step[] = [
|
||||
{
|
||||
step: 1,
|
||||
title: "Create Device Password",
|
||||
description: "Secure your device",
|
||||
title: 'Create Device Password',
|
||||
description: 'Secure your device',
|
||||
icon: {
|
||||
inactive: LockClosedIcon,
|
||||
active: LockClosedIcon,
|
||||
@@ -49,8 +51,8 @@ const steps: readonly Step[] = [
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: "Activate License",
|
||||
description: "Create an Unraid.net account and activate your key",
|
||||
title: 'Activate License',
|
||||
description: 'Create an Unraid.net account and activate your key',
|
||||
icon: {
|
||||
inactive: KeyIcon,
|
||||
active: KeyIconSolid,
|
||||
@@ -59,8 +61,8 @@ const steps: readonly Step[] = [
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: "Unleash Your Hardware",
|
||||
description: "Device is ready to configure",
|
||||
title: 'Unleash Your Hardware',
|
||||
description: 'Device is ready to configure',
|
||||
icon: {
|
||||
inactive: ServerStackIcon,
|
||||
active: ServerStackIconSolid,
|
||||
@@ -71,10 +73,7 @@ const steps: readonly Step[] = [
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Stepper
|
||||
:default-value="activeStep"
|
||||
class="text-foreground flex w-full items-start gap-2 text-16px"
|
||||
>
|
||||
<Stepper :default-value="activeStep" class="text-foreground flex w-full items-start gap-2 text-16px">
|
||||
<StepperItem
|
||||
v-for="step in steps"
|
||||
:key="step.step"
|
||||
@@ -90,13 +89,13 @@ const steps: readonly Step[] = [
|
||||
|
||||
<StepperTrigger as-child>
|
||||
<Button
|
||||
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
|
||||
size="default"
|
||||
class="z-10 rounded-full shrink-0 opacity-100"
|
||||
:class="[
|
||||
state !== 'inactive' &&
|
||||
'ring-2 ring-primary ring-offset-2 ring-offset-background *:cursor-default',
|
||||
]"
|
||||
:variant="state === 'completed' || state === 'active' ? 'primary' : 'outline'"
|
||||
size="md"
|
||||
:class="`z-10 rounded-full shrink-0 opacity-100 ${
|
||||
state !== 'inactive'
|
||||
? 'ring-2 ring-primary ring-offset-2 ring-offset-background *:cursor-default'
|
||||
: ''
|
||||
}`"
|
||||
:disabled="state === 'inactive'"
|
||||
>
|
||||
<component :is="step.icon[state]" class="size-4" />
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import type { ButtonProps } from '~/types/ui/button';
|
||||
|
||||
const props = withDefaults(defineProps<ButtonProps>(), {
|
||||
btnStyle: 'fill',
|
||||
btnType: 'button',
|
||||
class: undefined,
|
||||
click: undefined,
|
||||
href: undefined,
|
||||
icon: undefined,
|
||||
iconRight: undefined,
|
||||
iconRightHoverDisplay: false,
|
||||
// iconRightHoverAnimate: true,
|
||||
noPadding: false,
|
||||
size: '16px',
|
||||
text: '',
|
||||
title: '',
|
||||
});
|
||||
|
||||
defineEmits(['click']);
|
||||
|
||||
const classes = computed(() => {
|
||||
/** @todo consider underline for all buttons to improve accessibility and quick readability */
|
||||
const buttonDefaults = 'group text-center font-semibold leading-none relative z-0 flex flex-row items-center justify-center border-2 border-solid shadow-none cursor-pointer rounded-md hover:shadow-md focus:shadow-md disabled:opacity-25 disabled:hover:opacity-25 disabled:focus:opacity-25 disabled:cursor-not-allowed';
|
||||
let buttonColors = '';
|
||||
let buttonSize = '';
|
||||
let iconSize = '';
|
||||
|
||||
switch (props.btnStyle) {
|
||||
case 'black':
|
||||
buttonColors = 'text-white bg-black border-black transition hover:text-black focus:text-black hover:bg-grey focus:bg-grey hover:border-grey focus:border-grey';
|
||||
break;
|
||||
case 'fill':
|
||||
buttonColors = 'text-white bg-transparent border-transparent'; // border and bg are set in the template
|
||||
break;
|
||||
case 'gray':
|
||||
buttonColors = 'text-black bg-grey transition hover:text-white focus:text-white hover:bg-grey-mid focus:bg-grey-mid hover:border-grey-mid focus:border-grey-mid';
|
||||
break;
|
||||
case 'outline': // border and bg are set in the template
|
||||
buttonColors = 'text-orange bg-transparent border-orange hover:text-white focus:text-white';
|
||||
break;
|
||||
case 'outline-black':
|
||||
buttonColors = 'text-black bg-transparent border-black hover:text-black focus:text-black hover:bg-grey focus:bg-grey hover:border-grey focus:border-grey';
|
||||
break;
|
||||
case 'outline-white':
|
||||
buttonColors = 'text-white bg-transparent border-white hover:text-black focus:text-black hover:bg-white focus:bg-white';
|
||||
break;
|
||||
case 'underline':
|
||||
buttonColors = 'opacity-75 underline border-transparent transition hover:text-primary hover:bg-muted hover:border-muted focus:text-primary focus:bg-muted focus:border-muted hover:opacity-100 focus:opacity-100';
|
||||
break;
|
||||
case 'underline-hover-red':
|
||||
buttonColors = 'opacity-75 underline border-transparent transition hover:text-white hover:bg-unraid-red hover:border-unraid-red focus:text-white focus:bg-unraid-red focus:border-unraid-red hover:opacity-100 focus:opacity-100';
|
||||
break;
|
||||
case 'white':
|
||||
buttonColors = 'text-black bg-white transition hover:bg-grey focus:bg-grey';
|
||||
break;
|
||||
}
|
||||
|
||||
switch (props.size) {
|
||||
case '12px':
|
||||
buttonSize = `text-12px ${props.noPadding ? 'p-0' : 'p-8px'} gap-4px`;
|
||||
iconSize = 'w-12px';
|
||||
break;
|
||||
case '14px':
|
||||
buttonSize = `text-14px ${props.noPadding ? 'p-0' : 'p-8px'} gap-8px`;
|
||||
iconSize = 'w-14px';
|
||||
break;
|
||||
case '16px':
|
||||
buttonSize = `text-16px ${props.noPadding ? 'p-0' : 'p-12px'} gap-8px`;
|
||||
iconSize = 'w-16px';
|
||||
break;
|
||||
case '18px':
|
||||
buttonSize = `text-18px ${props.noPadding ? 'p-0' : 'p-12px'} gap-8px`;
|
||||
iconSize = 'w-18px';
|
||||
break;
|
||||
case '20px':
|
||||
buttonSize = `text-20px ${props.noPadding ? 'p-0' : 'p-16px'} gap-8px`;
|
||||
iconSize = 'w-20px';
|
||||
break;
|
||||
case '24px':
|
||||
buttonSize = `text-24px ${props.noPadding ? 'p-0' : 'p-16px'} gap-8px`;
|
||||
iconSize = 'w-24px';
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
button: props.btnStyle === 'none'
|
||||
? `${buttonSize} ${props.class}`
|
||||
: `${buttonSize} ${buttonColors} ${buttonDefaults} ${props.class}`,
|
||||
icon: `${iconSize} fill-current flex-shrink-0`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="href ? 'a' : 'button'"
|
||||
:disabled="disabled ?? null"
|
||||
:href="href"
|
||||
:rel="external ? 'noopener noreferrer' : ''"
|
||||
:target="external ? '_blank' : ''"
|
||||
:type="!href ? btnType : ''"
|
||||
:class="classes.button"
|
||||
:title="title"
|
||||
@click="click ?? $emit('click')"
|
||||
>
|
||||
<div
|
||||
v-if="btnStyle === 'fill'"
|
||||
class="absolute -top-[2px] -right-[2px] -bottom-[2px] -left-[2px] -z-10 bg-gradient-to-r from-unraid-red to-orange opacity-100 transition-all rounded-md group-hover:opacity-60 group-focus:opacity-60"
|
||||
/>
|
||||
<div
|
||||
v-if="btnStyle === 'outline'"
|
||||
class="absolute -top-[2px] -right-[2px] -bottom-[2px] -left-[2px] -z-10 bg-gradient-to-r from-unraid-red to-orange opacity-0 transition-all rounded-md group-hover:opacity-100 group-focus:opacity-100"
|
||||
/>
|
||||
|
||||
<component
|
||||
:is="icon"
|
||||
v-if="icon"
|
||||
:class="classes.icon"
|
||||
/>
|
||||
|
||||
{{ text }}
|
||||
<slot />
|
||||
|
||||
<component
|
||||
:is="iconRight"
|
||||
v-if="iconRight"
|
||||
:class="[
|
||||
classes.icon,
|
||||
iconRightHoverDisplay && 'opacity-0 group-hover:opacity-100 group-focus:opacity-100 transition-all',
|
||||
]"
|
||||
/>
|
||||
</component>
|
||||
</template>
|
||||
@@ -1,142 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
export interface Props {
|
||||
gradientStart?: string;
|
||||
gradientStop?: string;
|
||||
title?: string,
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
gradientStart: '#e32929',
|
||||
gradientStop: '#ff8d30',
|
||||
title: 'Loading',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 133.52 76.97"
|
||||
:class="`unraid_mark`"
|
||||
role="img"
|
||||
>
|
||||
<title>{{ title }}</title>
|
||||
<desc>Unraid logo animating with a wave like effect</desc>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="unraidLoadingGradient"
|
||||
x1="23.76"
|
||||
y1="81.49"
|
||||
x2="109.76"
|
||||
y2="-4.51"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" :stop-color="gradientStart" />
|
||||
<stop offset="1" :stop-color="gradientStop" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="m70,19.24zm57,0l6.54,0l0,38.49l-6.54,0l0,-38.49z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_9"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm47.65,11.9l-6.55,0l0,-23.79l6.55,0l0,23.79z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_8"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm31.77,-4.54l-6.54,0l0,-14.7l6.54,0l0,14.7z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_7"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm15.9,11.9l-6.54,0l0,-23.79l6.54,0l0,23.79z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_6"
|
||||
/>
|
||||
<path
|
||||
d="m63.49,19.24l6.51,0l0,38.49l-6.51,0l0,-38.49z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_5"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm-22.38,26.6l6.54,0l0,23.78l-6.54,0l0,-23.78z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_4"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm-38.26,43.03l6.55,0l0,14.73l-6.55,0l0,-14.73z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_3"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm-54.13,26.6l6.54,0l0,23.78l-6.54,0l0,-23.78z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_2"
|
||||
/>
|
||||
<path
|
||||
d="m70,19.24zm-63.46,38.49l-6.54,0l0,-38.49l6.54,0l0,38.49z"
|
||||
fill="url(#unraidLoadingGradient)"
|
||||
class="unraid_mark_1"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
.unraid_mark_2,
|
||||
.unraid_mark_4 {
|
||||
animation: mark_2 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_3 {
|
||||
animation: mark_3 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_6,
|
||||
.unraid_mark_8 {
|
||||
animation: mark_6 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_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);
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
@@ -1,12 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* The purpose of this component is to have the ability to pass a
|
||||
* white Loading component as a prop into a different component.
|
||||
* I couldn't figure out how use the base BrandLoading coponent
|
||||
* as a prop iteself and also pass in prop values to change the gradient colors.
|
||||
*/
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BrandLoading gradient-start="#ffffff" gradient-stop="#ffffff" />
|
||||
</template>
|
||||
@@ -1,44 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
export interface Props {
|
||||
gradientStart?: string;
|
||||
gradientStop?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
gradientStart: '#e32929',
|
||||
gradientStop: '#ff8d30',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-name="Layer 1" viewBox="0 0 954.29 142.4">
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="a"
|
||||
x1="-57.82"
|
||||
x2="923.39"
|
||||
y1="71.2"
|
||||
y2="71.2"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" :stop-color="gradientStart" />
|
||||
<stop offset="1" :stop-color="gradientStop" />
|
||||
</linearGradient>
|
||||
<linearGradient id="b" xlink:href="#a" x2="923.39" />
|
||||
<linearGradient id="c" xlink:href="#a" x2="923.39" y1="71.2" y2="71.2" />
|
||||
<linearGradient id="d" xlink:href="#a" x2="923.39" y1="71.2" y2="71.2" />
|
||||
<linearGradient id="e" xlink:href="#a" x2="923.39" y1="71.2" y2="71.2" />
|
||||
<linearGradient id="f" xlink:href="#a" x2="923.39" />
|
||||
<linearGradient id="g" xlink:href="#a" y1="12.16" y2="12.16" />
|
||||
<linearGradient id="h" xlink:href="#a" x2="923.39" y1="86.94" y2="86.94" />
|
||||
</defs>
|
||||
<path fill="url(#a)" d="M54.39 0C20.96 0 0 17.4 0 49.84v42.52c0 32.63 20.96 50.04 53.99 50.04s53.8-16.81 53.8-48.06v-.99H84.25v.99c0 17.8-11.47 27.49-30.26 27.49s-30.46-10.28-30.46-29.47V49.84c0-18.99 11.67-29.47 30.85-29.47s29.86 9.89 29.86 27.69v.79h23.54v-.79C107.79 16.81 87.02 0 54.39 0Z" />
|
||||
<path fill="url(#b)" d="M197.58 0c-33.42 0-54.59 17.4-54.59 49.84v42.52c0 32.63 21.16 50.04 54.19 50.04s54.59-17.4 54.59-50.04V49.84C251.77 17.4 230.61 0 197.58 0Zm30.66 92.36c0 19.18-11.87 29.47-31.05 29.47s-30.66-10.28-30.66-29.47V49.84c0-18.99 11.87-29.47 31.05-29.47s30.66 10.48 30.66 29.47v42.52Z" />
|
||||
<path fill="url(#c)" d="M373.8 97.31 312.49 1.98h-21.95v138.44h23.53V45.09l61.32 95.33h21.95V1.98H373.8v95.33z" />
|
||||
<path fill="url(#d)" d="M521.35 97.31 460.04 1.98h-21.96v138.44h23.54V45.09l61.31 95.33h21.95V1.98h-23.53v95.33z" />
|
||||
<path fill="url(#e)" d="M585.63 140.42h92.95v-20.57h-69.42V81.29h59.54V60.92h-59.54V22.35h69.42V1.98h-92.95v138.44z" />
|
||||
<path fill="url(#f)" d="M766.8 0c-33.43 0-54.39 17.4-54.39 49.84v42.52c0 32.63 20.96 50.04 53.99 50.04s53.8-16.81 53.8-48.06v-.99h-23.54v.99c0 17.8-11.47 27.49-30.26 27.49s-30.46-10.28-30.46-29.47V49.84c0-18.99 11.67-29.47 30.85-29.47s29.86 9.89 29.86 27.69v.79h23.54v-.79c0-31.25-20.77-48.06-53.4-48.06Z" />
|
||||
<path fill="url(#g)" d="M846.11 1.98h108.18v20.37H846.11z" />
|
||||
<path fill="url(#h)" d="M888.43 33.45h23.54v106.97h-23.54z" />
|
||||
</svg>
|
||||
</template>
|
||||
@@ -1,7 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
import { useCallbackStore } from '~/store/callbackActions';
|
||||
|
||||
const callbackStore = useCallbackStore();
|
||||
@@ -16,7 +13,7 @@ onBeforeMount(() => {
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
/* Import unraid-ui globals first */
|
||||
@import '@unraid/ui/styles';
|
||||
@import '../assets/main.css';
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import Input from '~/components/shadcn/input/Input.vue';
|
||||
import Label from '~/components/shadcn/label/Label.vue';
|
||||
import { defaultColors, useThemeStore, type Theme } from '~/store/theme';
|
||||
import { Input, Label, Switch } from '@unraid/ui';
|
||||
|
||||
import type { Theme } from '~/store/theme';
|
||||
|
||||
import { defaultColors, useThemeStore } from '~/store/theme';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const { darkMode } = toRefs(themeStore);
|
||||
@@ -89,7 +91,7 @@ watch([setDarkMode, bgColorToSet, textSecondaryToSet, textPrimaryToSet], (newVal
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
/* Import unraid-ui globals first */
|
||||
@import '@unraid/ui/styles';
|
||||
@import '../assets/main.css';
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
// import { useI18n } from 'vue-i18n';
|
||||
|
||||
// const { t } = useI18n();
|
||||
|
||||
@@ -1,32 +1,42 @@
|
||||
<script lang="ts" setup>
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import { BrandButton, cn } from '@unraid/ui';
|
||||
|
||||
import type { ServerStateDataAction } from '~/types/server';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useServerStore } from '~/store/server';
|
||||
import type { ServerStateDataAction } from '~/types/server';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
actions?: ServerStateDataAction[];
|
||||
filterBy?: string[] | undefined;
|
||||
filterOut?: string[] | undefined;
|
||||
maxWidth?: boolean;
|
||||
t: ComposerTranslation;
|
||||
}>(), {
|
||||
actions: undefined,
|
||||
filterBy: undefined,
|
||||
filterOut: undefined,
|
||||
maxWidth: false,
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
actions?: ServerStateDataAction[];
|
||||
filterBy?: string[] | undefined;
|
||||
filterOut?: string[] | undefined;
|
||||
maxWidth?: boolean;
|
||||
t: ComposerTranslation;
|
||||
}>(),
|
||||
{
|
||||
actions: undefined,
|
||||
filterBy: undefined,
|
||||
filterOut: undefined,
|
||||
maxWidth: false,
|
||||
}
|
||||
);
|
||||
|
||||
const { keyActions } = storeToRefs(useServerStore());
|
||||
|
||||
const computedActions = computed((): ServerStateDataAction[] | undefined => props.actions ? props.actions : keyActions.value);
|
||||
const computedActions = computed((): ServerStateDataAction[] | undefined =>
|
||||
props.actions ? props.actions : keyActions.value
|
||||
);
|
||||
|
||||
const filteredKeyActions = computed((): ServerStateDataAction[] | undefined => {
|
||||
if (!computedActions.value || (!props.filterOut && !props.filterBy)) { return computedActions.value; }
|
||||
if (!computedActions.value || (!props.filterOut && !props.filterBy)) {
|
||||
return computedActions.value;
|
||||
}
|
||||
|
||||
return computedActions.value.filter((action: { name: string; }) => {
|
||||
return computedActions.value.filter((action: { name: string }) => {
|
||||
return props.filterOut
|
||||
? !props.filterOut?.includes(action.name)
|
||||
: props.filterBy?.includes(action.name);
|
||||
@@ -38,10 +48,7 @@ const filteredKeyActions = computed((): ServerStateDataAction[] | undefined => {
|
||||
<ul v-if="filteredKeyActions" class="flex flex-col gap-y-8px">
|
||||
<li v-for="action in filteredKeyActions" :key="action.name">
|
||||
<BrandButton
|
||||
:class="[
|
||||
'w-full',
|
||||
props.maxWidth ? 'sm:max-w-300px' : '',
|
||||
]"
|
||||
:class="cn('w-full', props.maxWidth ? 'sm:max-w-300px' : '')"
|
||||
:disabled="action?.disabled"
|
||||
:external="action?.external"
|
||||
:href="action?.href"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '~/components/shadcn/utils';
|
||||
|
||||
const props = withDefaults(defineProps<{ class?: string }>(), { class: '' });
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
:class="cn('h-5 animate-pulse bg-gray-300 w-full', props.class)"
|
||||
role="progressbar"
|
||||
aria-label="Loading"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,54 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ShieldExclamationIcon } from '@heroicons/vue/24/solid';
|
||||
import { cn } from '~/components/shadcn/utils';
|
||||
|
||||
/**
|
||||
* A default container for displaying loading and error states.
|
||||
*
|
||||
* By default, this component will expand to full height and display contents
|
||||
* in the center of the container.
|
||||
*
|
||||
* Any slot/child will only render when a loading/error state isn't displayed.
|
||||
*
|
||||
* Exposes a 'retry' event (user-triggered during error state).
|
||||
*
|
||||
* @example
|
||||
* <LoadingError @retry="retryFunction" :loading="loading" :error="error" />
|
||||
*
|
||||
* <LoadingError :loading="loading" :error="error">
|
||||
* <p>Only displayed when both loading and error are false.</p>
|
||||
* </LoadingError>
|
||||
*/
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
class?: string;
|
||||
loading: boolean;
|
||||
error: Error | null | undefined;
|
||||
}>(),
|
||||
{ class: '' }
|
||||
);
|
||||
|
||||
defineEmits(['retry']);
|
||||
</script>
|
||||
<template>
|
||||
<div :class="cn('h-full flex flex-col items-center justify-center gap-3', props.class)">
|
||||
<!-- Loading State -->
|
||||
<div v-if="loading" class="contents">
|
||||
<LoadingSpinner />
|
||||
<p>Loading Notifications...</p>
|
||||
</div>
|
||||
<!-- Error State -->
|
||||
<div v-else-if="error" class="space-y-3">
|
||||
<div class="flex justify-center">
|
||||
<ShieldExclamationIcon class="h-10 text-unraid-red" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-bold">{{ `Error` }}</h3>
|
||||
<p>{{ error.message }}</p>
|
||||
</div>
|
||||
<Button type="button" class="w-full" @click="$emit('retry')">Try Again</Button>
|
||||
</div>
|
||||
<!-- Default state -->
|
||||
<slot v-else />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '~/components/shadcn/utils';
|
||||
|
||||
const props = withDefaults(defineProps<{ class?: string }>(), { class: '' });
|
||||
</script>
|
||||
<template>
|
||||
<!-- adapted from https://tw-elements.com/docs/standard/components/spinners/ -->
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'inline-block h-8 w-8 animate-spin rounded-full border-2 border-solid border-current border-e-transparent align-[-0.125em] text-primary motion-reduce:animate-[spin_1.5s_linear_infinite]',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
role="status"
|
||||
>
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { TransitionChild, TransitionRoot } from '@headlessui/vue';
|
||||
import { XMarkIcon } from '@heroicons/vue/24/outline';
|
||||
import { cn } from '@unraid/ui';
|
||||
import { TransitionChild, TransitionRoot } from '@headlessui/vue';
|
||||
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
import { cn } from '~/components/shadcn/utils';
|
||||
|
||||
export interface Props {
|
||||
centerContent?: boolean;
|
||||
@@ -44,7 +45,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
watchEffect(() => {
|
||||
// toggle body scrollability
|
||||
if (props.open) {
|
||||
document.body.style.setProperty('overflow', 'hidden')
|
||||
document.body.style.setProperty('overflow', 'hidden');
|
||||
} else {
|
||||
document.body.style.removeProperty('overflow');
|
||||
}
|
||||
@@ -55,7 +56,9 @@ const closeModal = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const ariaLablledById = computed<string|undefined>(() => props.title ? `ModalTitle-${Math.random()}`.replace('0.', '') : undefined);
|
||||
const ariaLablledById = computed<string | undefined>(() =>
|
||||
props.title ? `ModalTitle-${Math.random()}`.replace('0.', '') : undefined
|
||||
);
|
||||
const computedVerticalCenter = computed<string>(() => {
|
||||
if (props.tallContent) {
|
||||
return 'justify-start sm:justify-center';
|
||||
@@ -138,7 +141,11 @@ const computedVerticalCenter = computed<string>(() => {
|
||||
>
|
||||
<div class="absolute -z-10 inset-0 opacity-10 bg-card" />
|
||||
<template v-if="!$slots['header']">
|
||||
<h1 v-if="title && !titleInMain" :id="ariaLablledById" class="text-center text-20px sm:text-24px font-semibold flex flex-wrap justify-center gap-x-4px">
|
||||
<h1
|
||||
v-if="title && !titleInMain"
|
||||
:id="ariaLablledById"
|
||||
class="text-center text-20px sm:text-24px font-semibold flex flex-wrap justify-center gap-x-4px"
|
||||
>
|
||||
{{ title }}
|
||||
<slot name="headerTitle" />
|
||||
</h1>
|
||||
@@ -149,15 +156,16 @@ const computedVerticalCenter = computed<string>(() => {
|
||||
<div
|
||||
v-if="$slots['main'] || description"
|
||||
class="relative max-h-[65vh] tall:max-h-[75vh] flex flex-col gap-y-16px sm:gap-y-24px p-16px md:p-24px overflow-y-auto"
|
||||
:class="[
|
||||
centerContent && 'text-center',
|
||||
!disableShadow && 'shadow-inner',
|
||||
]"
|
||||
:class="[centerContent && 'text-center', !disableShadow && 'shadow-inner']"
|
||||
>
|
||||
<div class="flex flex-col gap-y-12px">
|
||||
<h1 v-if="title && titleInMain" :id="ariaLablledById" class="text-center text-20px sm:text-24px font-semibold flex flex-wrap justify-center gap-x-4px">
|
||||
{{ title }}
|
||||
<slot name="headerTitle" />
|
||||
<h1
|
||||
v-if="title && titleInMain"
|
||||
:id="ariaLablledById"
|
||||
class="text-center text-20px sm:text-24px font-semibold flex flex-wrap justify-center gap-x-4px"
|
||||
>
|
||||
{{ title }}
|
||||
<slot name="headerTitle" />
|
||||
</h1>
|
||||
<h2 v-if="description" class="text-18px sm:text-20px opacity-75" v-html="description" />
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { useCallbackActionsStore } from '~/store/callbackActions';
|
||||
import { useTrialStore } from '~/store/trial';
|
||||
@@ -32,8 +29,9 @@ const { releaseForUpdate: updateOsChangelogModalVisible } = storeToRefs(useUpdat
|
||||
</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 {
|
||||
@@ -82,6 +80,4 @@ const { releaseForUpdate: updateOsChangelogModalVisible } = storeToRefs(useUpdat
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { BellIcon, ExclamationTriangleIcon, ShieldExclamationIcon } from '@heroicons/vue/24/solid';
|
||||
import { cn } from '~/components/shadcn/utils';
|
||||
import { Importance, type OverviewQuery } from '~/composables/gql/graphql';
|
||||
import { cn } from '@unraid/ui';
|
||||
|
||||
import type { OverviewQuery } from '~/composables/gql/graphql';
|
||||
|
||||
import { Importance } from '~/composables/gql/graphql';
|
||||
|
||||
const props = defineProps<{ overview?: OverviewQuery['notifications']['overview']; seen?: boolean }>();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
ShieldExclamationIcon,
|
||||
TrashIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { Button } from '@unraid/ui';
|
||||
import { useMutation } from '@vue/apollo-composable';
|
||||
import type { NotificationFragmentFragment } from '~/composables/gql/graphql';
|
||||
import { NotificationType } from '~/composables/gql/graphql';
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { CheckIcon } from '@heroicons/vue/24/solid';
|
||||
import { useQuery } from '@vue/apollo-composable';
|
||||
import { vInfiniteScroll } from '@vueuse/components';
|
||||
|
||||
import { CheckIcon } from '@heroicons/vue/24/solid';
|
||||
import { Error as LoadingError, Spinner as LoadingSpinner } from '@unraid/ui';
|
||||
|
||||
import type { Importance, NotificationType } from '~/composables/gql/graphql';
|
||||
|
||||
import { useHaveSeenNotifications } from '~/composables/api/use-notifications';
|
||||
import { useFragment } from '~/composables/gql/fragment-masking';
|
||||
import type { Importance, NotificationType } from '~/composables/gql/graphql';
|
||||
import { useUnraidApiStore } from '~/store/unraidApi';
|
||||
import { getNotifications, NOTIFICATION_FRAGMENT } from './graphql/notification.query';
|
||||
|
||||
|
||||
@@ -1,7 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { Button } from '@/components/shadcn/button';
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/shadcn/sheet';
|
||||
import { useMutation, useQuery, useSubscription } from '@vue/apollo-composable';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from '@unraid/ui';
|
||||
|
||||
import { useTrackLatestSeenNotification } from '~/composables/api/use-notifications';
|
||||
import { useFragment } from '~/composables/gql';
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports -- false positive :(
|
||||
@@ -93,7 +112,7 @@ const readArchivedCount = computed(() => {
|
||||
<NotificationsIndicator :overview="overview" :seen="haveSeenNotifications" />
|
||||
</SheetTrigger>
|
||||
<SheetContent
|
||||
:to="teleportTarget"
|
||||
:to="teleportTarget as HTMLElement"
|
||||
class="w-full max-w-[100vw] sm:max-w-[540px] max-h-screen h-screen min-h-screen px-0 flex flex-col gap-5 pb-0"
|
||||
>
|
||||
<div class="relative flex flex-col h-full w-full">
|
||||
@@ -143,7 +162,7 @@ const readArchivedCount = computed(() => {
|
||||
|
||||
<Select
|
||||
@update:model-value="
|
||||
(val) => {
|
||||
(val: string) => {
|
||||
importance = val === 'all' ? undefined : (val as Importance);
|
||||
}
|
||||
"
|
||||
|
||||
@@ -15,26 +15,23 @@ else
|
||||
echo "Third party plugins found - PLEASE CHECK YOUR UNRAID NOTIFICATIONS AND WAIT FOR THE MESSAGE THAT IT IS SAFE TO REBOOT!"
|
||||
fi
|
||||
*/
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
import {
|
||||
ShieldCheckIcon,
|
||||
ShieldExclamationIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
import { ShieldCheckIcon, ShieldExclamationIcon } from '@heroicons/vue/24/solid';
|
||||
import { BrandButton, CardWrapper, PageContainer } from '@unraid/ui';
|
||||
|
||||
import { useReplaceRenewStore } from '~/store/replaceRenew';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import type { RegistrationItemProps } from '~/types/registration';
|
||||
import type { ServerStateDataAction } from '~/types/server';
|
||||
|
||||
import KeyActions from '~/components/KeyActions.vue';
|
||||
import RegistrationReplaceCheck from '~/components/Registration/ReplaceCheck.vue';
|
||||
import RegistrationKeyLinkedStatus from '~/components/Registration/KeyLinkedStatus.vue';
|
||||
import RegistrationReplaceCheck from '~/components/Registration/ReplaceCheck.vue';
|
||||
import RegistrationUpdateExpirationAction from '~/components/Registration/UpdateExpirationAction.vue';
|
||||
import UserProfileUptimeExpire from '~/components/UserProfile/UptimeExpire.vue';
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
import { useReplaceRenewStore } from '~/store/replaceRenew';
|
||||
import { useServerStore } from '~/store/server';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -72,7 +69,9 @@ const formattedRegTm = ref<string>();
|
||||
* So we need to watch for this value to be able to format it based on the user's date time preferences.
|
||||
*/
|
||||
const setFormattedRegTm = () => {
|
||||
if (!regTm.value) { return; }
|
||||
if (!regTm.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { outputDateTimeFormatted } = useDateTimeHelper(dateTimeFormat.value, t, true, regTm.value);
|
||||
formattedRegTm.value = outputDateTimeFormatted.value;
|
||||
@@ -88,140 +87,189 @@ onBeforeMount(() => {
|
||||
}
|
||||
});
|
||||
|
||||
const headingIcon = computed(() => serverErrors.value.length ? ShieldExclamationIcon : ShieldCheckIcon);
|
||||
const headingIcon = computed(() =>
|
||||
serverErrors.value.length ? ShieldExclamationIcon : ShieldCheckIcon
|
||||
);
|
||||
const heading = computed(() => {
|
||||
if (serverErrors.value.length) { // It's rare to have multiple errors but for the time being only show the first error
|
||||
if (serverErrors.value.length) {
|
||||
// It's rare to have multiple errors but for the time being only show the first error
|
||||
return serverErrors.value[0]?.heading;
|
||||
}
|
||||
return stateData.value.heading;
|
||||
});
|
||||
const subheading = computed(() => {
|
||||
if (serverErrors.value.length) { // It's rare to have multiple errors but for the time being only show the first error
|
||||
if (serverErrors.value.length) {
|
||||
// It's rare to have multiple errors but for the time being only show the first error
|
||||
return serverErrors.value[0]?.message;
|
||||
}
|
||||
return stateData.value.message;
|
||||
});
|
||||
|
||||
const showTrialExpiration = computed((): boolean => state.value === 'TRIAL' || state.value === 'EEXPIRED');
|
||||
const showUpdateEligibility = computed((): boolean => !!(regExp.value));
|
||||
const showTrialExpiration = computed(
|
||||
(): boolean => state.value === 'TRIAL' || state.value === 'EEXPIRED'
|
||||
);
|
||||
const showUpdateEligibility = computed((): boolean => !!regExp.value);
|
||||
const keyInstalled = computed((): boolean => !!(!stateDataError.value && state.value !== 'ENOKEYFILE'));
|
||||
const showLinkedAndTransferStatus = computed((): boolean => !!(keyInstalled.value && guid.value && !showTrialExpiration.value));
|
||||
const showLinkedAndTransferStatus = computed(
|
||||
(): boolean => !!(keyInstalled.value && guid.value && !showTrialExpiration.value)
|
||||
);
|
||||
// filter out renew action and only display other key actions…renew is displayed in RegistrationUpdateExpirationAction
|
||||
const showFilteredKeyActions = computed((): boolean => !!(keyActions.value && keyActions.value?.filter(action => !['renew'].includes(action.name)).length > 0));
|
||||
const showFilteredKeyActions = computed(
|
||||
(): boolean =>
|
||||
!!(
|
||||
keyActions.value &&
|
||||
keyActions.value?.filter((action: ServerStateDataAction) => !['renew'].includes(action.name))
|
||||
.length > 0
|
||||
)
|
||||
);
|
||||
|
||||
const items = computed((): RegistrationItemProps[] => {
|
||||
return [
|
||||
...(computedArray.value
|
||||
? [{
|
||||
label: t('Array status'),
|
||||
text: computedArray.value,
|
||||
warning: arrayWarning.value,
|
||||
}]
|
||||
? [
|
||||
{
|
||||
label: t('Array status'),
|
||||
text: computedArray.value,
|
||||
warning: arrayWarning.value,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(regTy.value
|
||||
? [{
|
||||
label: t('License key type'),
|
||||
text: regTy.value,
|
||||
}]
|
||||
? [
|
||||
{
|
||||
label: t('License key type'),
|
||||
text: regTy.value,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(showTrialExpiration.value
|
||||
? [{
|
||||
error: state.value === 'EEXPIRED',
|
||||
label: t('Trial expiration'),
|
||||
component: UserProfileUptimeExpire,
|
||||
componentProps: {
|
||||
forExpire: true,
|
||||
shortText: true,
|
||||
t,
|
||||
? [
|
||||
{
|
||||
error: state.value === 'EEXPIRED',
|
||||
label: t('Trial expiration'),
|
||||
component: UserProfileUptimeExpire,
|
||||
componentProps: {
|
||||
forExpire: true,
|
||||
shortText: true,
|
||||
t,
|
||||
},
|
||||
componentOpacity: true,
|
||||
},
|
||||
componentOpacity: true,
|
||||
}]
|
||||
]
|
||||
: []),
|
||||
...(regTo.value
|
||||
? [{
|
||||
label: t('Registered to'),
|
||||
text: regTo.value,
|
||||
}]
|
||||
? [
|
||||
{
|
||||
label: t('Registered to'),
|
||||
text: regTo.value,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(regTo.value && regTm.value && formattedRegTm.value
|
||||
? [{
|
||||
label: t('Registered on'),
|
||||
text: formattedRegTm.value,
|
||||
}]
|
||||
? [
|
||||
{
|
||||
label: t('Registered on'),
|
||||
text: formattedRegTm.value,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(showUpdateEligibility.value
|
||||
? [{
|
||||
label: t('OS Update Eligibility'),
|
||||
warning: regUpdatesExpired.value,
|
||||
component: RegistrationUpdateExpirationAction,
|
||||
componentProps: { t },
|
||||
componentOpacity: !regUpdatesExpired.value,
|
||||
}]
|
||||
? [
|
||||
{
|
||||
label: t('OS Update Eligibility'),
|
||||
warning: regUpdatesExpired.value,
|
||||
component: RegistrationUpdateExpirationAction,
|
||||
componentProps: { t },
|
||||
componentOpacity: !regUpdatesExpired.value,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(state.value === 'EGUID'
|
||||
? [{
|
||||
label: t('Registered GUID'),
|
||||
text: regGuid.value,
|
||||
}]
|
||||
? [
|
||||
{
|
||||
label: t('Registered GUID'),
|
||||
text: regGuid.value,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(guid.value
|
||||
? [{
|
||||
label: t('Flash GUID'),
|
||||
text: guid.value,
|
||||
}]
|
||||
? [
|
||||
{
|
||||
label: t('Flash GUID'),
|
||||
text: guid.value,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(flashVendor.value
|
||||
? [{
|
||||
label: t('Flash Vendor'),
|
||||
text: flashVendor.value,
|
||||
}]
|
||||
? [
|
||||
{
|
||||
label: t('Flash Vendor'),
|
||||
text: flashVendor.value,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(flashProduct.value
|
||||
? [{
|
||||
label: t('Flash Product'),
|
||||
text: flashProduct.value,
|
||||
}]
|
||||
? [
|
||||
{
|
||||
label: t('Flash Product'),
|
||||
text: flashProduct.value,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(keyInstalled.value
|
||||
? [{
|
||||
error: tooManyDevices.value,
|
||||
label: t('Attached Storage Devices'),
|
||||
text: tooManyDevices.value
|
||||
? t('{0} out of {1} allowed devices – upgrade your key to support more devices', [deviceCount.value, computedRegDevs.value])
|
||||
: t('{0} out of {1} devices', [deviceCount.value, computedRegDevs.value === -1 ? t('unlimited') : computedRegDevs.value]),
|
||||
}]
|
||||
? [
|
||||
{
|
||||
error: tooManyDevices.value,
|
||||
label: t('Attached Storage Devices'),
|
||||
text: tooManyDevices.value
|
||||
? t('{0} out of {1} allowed devices – upgrade your key to support more devices', [
|
||||
deviceCount.value,
|
||||
computedRegDevs.value,
|
||||
])
|
||||
: t('{0} out of {1} devices', [
|
||||
deviceCount.value,
|
||||
computedRegDevs.value === -1 ? t('unlimited') : computedRegDevs.value,
|
||||
]),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(showLinkedAndTransferStatus.value
|
||||
? [{
|
||||
label: t('Transfer License to New Flash'),
|
||||
component: RegistrationReplaceCheck,
|
||||
componentProps: { t },
|
||||
}]
|
||||
? [
|
||||
{
|
||||
label: t('Transfer License to New Flash'),
|
||||
component: RegistrationReplaceCheck,
|
||||
componentProps: { t },
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(regTo.value && showLinkedAndTransferStatus.value
|
||||
? [{
|
||||
label: t('Linked to Unraid.net account'),
|
||||
component: RegistrationKeyLinkedStatus,
|
||||
componentProps: { t },
|
||||
}]
|
||||
? [
|
||||
{
|
||||
label: t('Linked to Unraid.net account'),
|
||||
component: RegistrationKeyLinkedStatus,
|
||||
componentProps: { t },
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
...(showFilteredKeyActions.value
|
||||
? [{
|
||||
component: KeyActions,
|
||||
componentProps: {
|
||||
filterOut: ['renew'],
|
||||
t,
|
||||
? [
|
||||
{
|
||||
component: KeyActions,
|
||||
componentProps: {
|
||||
filterOut: ['renew'],
|
||||
t,
|
||||
},
|
||||
},
|
||||
}]
|
||||
]
|
||||
: []),
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiPageContainer class="max-w-800px">
|
||||
<UiCardWrapper :increased-padding="true">
|
||||
<PageContainer class="max-w-800px">
|
||||
<CardWrapper :increased-padding="true">
|
||||
<div class="flex flex-col gap-20px sm:gap-24px">
|
||||
<header class="flex flex-col gap-y-16px">
|
||||
<h3
|
||||
@@ -269,12 +317,12 @@ const items = computed((): RegistrationItemProps[] => {
|
||||
</RegistrationItem>
|
||||
</dl>
|
||||
</div>
|
||||
</UiCardWrapper>
|
||||
</UiPageContainer>
|
||||
</CardWrapper>
|
||||
</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>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ArrowTopRightOnSquareIcon,
|
||||
ArrowPathIcon,
|
||||
LinkIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { ArrowPathIcon, ArrowTopRightOnSquareIcon, LinkIcon } from '@heroicons/vue/24/solid';
|
||||
import { Badge, BrandButton } from '@unraid/ui';
|
||||
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useAccountStore } from '~/store/account';
|
||||
@@ -23,35 +22,29 @@ defineProps<{
|
||||
<div class="flex flex-wrap items-center justify-between gap-8px">
|
||||
<BrandButton
|
||||
v-if="keyLinkedStatus !== 'linked' && keyLinkedStatus !== 'checking'"
|
||||
btn-style="none"
|
||||
:no-padding="true"
|
||||
variant="none"
|
||||
:title="t('Refresh')"
|
||||
class="group"
|
||||
@click="replaceRenewStore.check(true)"
|
||||
>
|
||||
<UiBadge
|
||||
<Badge
|
||||
v-if="keyLinkedOutput"
|
||||
:color="keyLinkedOutput.color"
|
||||
:variant="keyLinkedOutput.variant"
|
||||
:icon="keyLinkedOutput.icon"
|
||||
:icon-right="ArrowPathIcon"
|
||||
size="16px"
|
||||
size="md"
|
||||
>
|
||||
{{ t(keyLinkedOutput.text ?? 'Unknown') }}
|
||||
</UiBadge>
|
||||
</Badge>
|
||||
</BrandButton>
|
||||
<UiBadge
|
||||
v-else
|
||||
:color="keyLinkedOutput.color"
|
||||
:icon="keyLinkedOutput.icon"
|
||||
size="16px"
|
||||
>
|
||||
<Badge v-else :variant="keyLinkedOutput.variant" :icon="keyLinkedOutput.icon" size="md">
|
||||
{{ t(keyLinkedOutput.text ?? 'Unknown') }}
|
||||
</UiBadge>
|
||||
</Badge>
|
||||
|
||||
<span class="inline-flex flex-wrap-items-start gap-8px">
|
||||
<BrandButton
|
||||
v-if="keyLinkedStatus === 'notLinked'"
|
||||
btn-style="underline"
|
||||
variant="underline"
|
||||
:external="true"
|
||||
:icon="LinkIcon"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
@@ -62,7 +55,7 @@ defineProps<{
|
||||
/>
|
||||
<BrandButton
|
||||
v-else
|
||||
btn-style="underline"
|
||||
variant="underline"
|
||||
:external="true"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
:text="t('Learn More')"
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ArrowTopRightOnSquareIcon,
|
||||
KeyIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { ArrowTopRightOnSquareIcon, KeyIcon } from '@heroicons/vue/24/solid';
|
||||
import { Badge, BrandButton } from '@unraid/ui';
|
||||
import { DOCS_REGISTRATION_REPLACE_KEY } from '~/helpers/urls';
|
||||
import { useReplaceRenewStore } from '~/store/replaceRenew';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
const replaceRenewStore = useReplaceRenewStore();
|
||||
const { replaceStatusOutput } = storeToRefs(replaceRenewStore);
|
||||
@@ -27,18 +24,13 @@ defineProps<{
|
||||
@click="replaceRenewStore.check"
|
||||
/>
|
||||
|
||||
<UiBadge
|
||||
v-else
|
||||
:color="replaceStatusOutput.color"
|
||||
:icon="replaceStatusOutput.icon"
|
||||
size="16px"
|
||||
>
|
||||
<Badge v-else :variant="replaceStatusOutput.variant" :icon="replaceStatusOutput.icon" size="md">
|
||||
{{ t(replaceStatusOutput.text ?? 'Unknown') }}
|
||||
</UiBadge>
|
||||
</Badge>
|
||||
|
||||
<span class="inline-flex flex-wrap items-center justify-end gap-8px">
|
||||
<BrandButton
|
||||
btn-style="underline"
|
||||
variant="underline"
|
||||
:external="true"
|
||||
:href="DOCS_REGISTRATION_REPLACE_KEY.toString()"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ArrowPathIcon, ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { ArrowPathIcon, ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import { BrandButton } from '@unraid/ui';
|
||||
import { DOCS_REGISTRATION_LICENSING } from '~/helpers/urls';
|
||||
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
import { DOCS_REGISTRATION_LICENSING } from '~/helpers/urls';
|
||||
import { useReplaceRenewStore } from '~/store/replaceRenew';
|
||||
import { useServerStore } from '~/store/server';
|
||||
|
||||
@@ -18,21 +21,14 @@ const replaceRenewStore = useReplaceRenewStore();
|
||||
const serverStore = useServerStore();
|
||||
|
||||
const { renewStatus } = storeToRefs(replaceRenewStore);
|
||||
const {
|
||||
dateTimeFormat,
|
||||
regExp,
|
||||
regUpdatesExpired,
|
||||
renewAction,
|
||||
} = storeToRefs(serverStore);
|
||||
const { dateTimeFormat, regExp, regUpdatesExpired, renewAction } = storeToRefs(serverStore);
|
||||
|
||||
const reload = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
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 output = computed(() => {
|
||||
if (!regExp.value) {
|
||||
@@ -55,7 +51,11 @@ const output = computed(() => {
|
||||
|
||||
<p class="text-14px opacity-90">
|
||||
<template v-if="renewStatus === 'installed'">
|
||||
{{ t('Your license key was automatically renewed and installed. Reload the page to see updated details.') }}
|
||||
{{
|
||||
t(
|
||||
'Your license key was automatically renewed and installed. Reload the page to see updated details.'
|
||||
)
|
||||
}}
|
||||
</template>
|
||||
</p>
|
||||
<div class="flex flex-wrap items-start justify-between gap-8px">
|
||||
@@ -80,7 +80,7 @@ const output = computed(() => {
|
||||
/>
|
||||
|
||||
<BrandButton
|
||||
btn-style="underline"
|
||||
variant="underline"
|
||||
:external="true"
|
||||
:href="DOCS_REGISTRATION_LICENSING.toString()"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import Button from '~/components/Brand/Button.vue';
|
||||
import { BrandButton } from '@unraid/ui';
|
||||
import { ACCOUNT } from '~/helpers/urls';
|
||||
|
||||
export interface Props {
|
||||
@@ -51,7 +51,7 @@ const getStateToken = (): string | null => {
|
||||
const generateStateToken = (): string => {
|
||||
const array = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(array);
|
||||
const state = Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
||||
const state = Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
||||
sessionStorage.setItem('sso_state', state);
|
||||
return state;
|
||||
};
|
||||
@@ -138,19 +138,19 @@ const navigateToExternalSSOUrl = () => {
|
||||
<div class="w-full flex flex-col gap-1 my-1">
|
||||
<p v-if="currentState === 'idle' || currentState === 'error'" class="text-center">or</p>
|
||||
<p v-if="currentState === 'error'" class="text-red-500 text-center">{{ error }}</p>
|
||||
<Button
|
||||
<BrandButton
|
||||
:disabled="currentState === 'loading'"
|
||||
btn-style="outline"
|
||||
variant="outline"
|
||||
class="rounded-none uppercase tracking-widest"
|
||||
@click="navigateToExternalSSOUrl"
|
||||
>{{ buttonText }}</Button
|
||||
>{{ buttonText }}</BrandButton
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
/* Import unraid-ui globals first */
|
||||
@import '@unraid/ui/styles';
|
||||
@import '../assets/main.css';
|
||||
</style>
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { UiBadgeProps } from '~/types/ui/badge';
|
||||
|
||||
const props = withDefaults(defineProps<UiBadgeProps>(), {
|
||||
color: 'gray',
|
||||
icon: undefined,
|
||||
iconRight: undefined,
|
||||
iconStyles: '',
|
||||
size: '16px',
|
||||
});
|
||||
|
||||
const computedStyleClasses = computed(() => {
|
||||
let colorClasses = '';
|
||||
let textSize = '';
|
||||
let iconSize = '';
|
||||
switch (props.color) {
|
||||
case 'red':
|
||||
colorClasses = 'bg-unraid-red text-white group-hover:bg-orange-dark group-focus:bg-orange-dark';
|
||||
break;
|
||||
case 'yellow':
|
||||
colorClasses = 'bg-yellow-100 text-black group-hover:bg-yellow-200 group-focus:bg-yellow-200';
|
||||
break;
|
||||
case 'green':
|
||||
colorClasses = 'bg-green-200 text-green-800 group-hover:bg-green-300 group-focus:bg-green-300';
|
||||
break;
|
||||
case 'blue':
|
||||
colorClasses = 'bg-blue-100 text-blue-800 group-hover:bg-blue-200 group-focus:bg-blue-200';
|
||||
break;
|
||||
case 'indigo':
|
||||
colorClasses = 'bg-indigo-100 text-indigo-800 group-hover:bg-indigo-200 group-focus:bg-indigo-200';
|
||||
break;
|
||||
case 'purple':
|
||||
colorClasses = 'bg-purple-100 text-purple-800 group-hover:bg-purple-200 group-focus:bg-purple-200';
|
||||
break;
|
||||
case 'pink':
|
||||
colorClasses = 'bg-pink-100 text-pink-800 group-hover:bg-pink-200 group-focus:bg-pink-200';
|
||||
break;
|
||||
case 'orange':
|
||||
colorClasses = 'bg-orange text-white group-hover:bg-orange-dark group-focus:bg-orange-dark';
|
||||
break;
|
||||
case 'black':
|
||||
colorClasses = 'bg-black text-white group-hover:bg-gray-800 group-focus:bg-gray-800';
|
||||
break;
|
||||
case 'white':
|
||||
colorClasses = 'bg-white text-black group-hover:bg-gray-100 group-focus:bg-gray-100';
|
||||
break;
|
||||
case 'transparent':
|
||||
colorClasses = 'bg-transparent text-black group-hover:bg-gray-100 group-focus:bg-gray-100';
|
||||
break;
|
||||
case 'current':
|
||||
colorClasses = 'bg-current text-black group-hover:bg-gray-100 group-focus:bg-gray-100';
|
||||
break;
|
||||
case 'gray':
|
||||
colorClasses = 'bg-gray-200 text-gray-800 group-hover:bg-gray-300 group-focus:bg-gray-300';
|
||||
break;
|
||||
case 'custom':
|
||||
colorClasses = '';
|
||||
break;
|
||||
}
|
||||
switch (props.size) {
|
||||
case '12px':
|
||||
textSize = 'text-12px px-8px py-4px gap-4px';
|
||||
iconSize = 'w-12px';
|
||||
break;
|
||||
case '14px':
|
||||
textSize = 'text-14px px-8px py-4px gap-8px';
|
||||
iconSize = 'w-14px';
|
||||
break;
|
||||
case '16px':
|
||||
textSize = 'text-16px px-12px py-8px gap-8px';
|
||||
iconSize = 'w-16px';
|
||||
break;
|
||||
case '18px':
|
||||
textSize = 'text-18px px-12px py-8px gap-8px';
|
||||
iconSize = 'w-18px';
|
||||
break;
|
||||
case '20px':
|
||||
textSize = 'text-20px px-16px py-12px gap-8px';
|
||||
iconSize = 'w-20px';
|
||||
break;
|
||||
case '24px':
|
||||
textSize = 'text-24px px-16px py-12px gap-8px';
|
||||
iconSize = 'w-24px';
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
badge: `${textSize} ${colorClasses}`,
|
||||
icon: `${iconSize} ${props.iconStyles}`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="inline-flex items-center rounded-full font-semibold leading-none transition-all duration-200 ease-in-out"
|
||||
:class="[
|
||||
computedStyleClasses.badge,
|
||||
]"
|
||||
>
|
||||
<component :is="icon" v-if="icon" class="flex-shrink-0" :class="computedStyleClasses.icon" />
|
||||
<slot />
|
||||
<component :is="iconRight" v-if="iconRight" class="flex-shrink-0" :class="computedStyleClasses.icon" />
|
||||
</span>
|
||||
</template>
|
||||
@@ -1,31 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{
|
||||
error?: boolean,
|
||||
hover?: boolean;
|
||||
increasedPadding?: boolean;
|
||||
padding?: boolean;
|
||||
warning?: boolean;
|
||||
}>(), {
|
||||
error: false,
|
||||
hover: true,
|
||||
increasedPadding: false,
|
||||
padding: true,
|
||||
warning: false,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="group/card text-left relative flex flex-col flex-1 border-2 border-solid rounded-md shadow-md "
|
||||
:class="[
|
||||
padding && 'p-4',
|
||||
increasedPadding && 'md:p-6',
|
||||
hover && 'hover:shadow-orange/50 transition-all',
|
||||
error && 'text-white bg-unraid-red border-unraid-red',
|
||||
warning && 'text-black bg-yellow-100 border-yellow-100',
|
||||
!error && !warning && 'text-foreground bg-background border-muted',
|
||||
]"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,61 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* @todo complete this component
|
||||
*/
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
|
||||
withDefaults(defineProps<{
|
||||
description?: string; // @todo setup
|
||||
label: string;
|
||||
}>(), {
|
||||
description: '',
|
||||
});
|
||||
|
||||
const checked = ref(false);
|
||||
</script>
|
||||
<template>
|
||||
<SwitchGroup as="div">
|
||||
<div class="flex flex-shrink-0 items-center gap-16px">
|
||||
<Switch
|
||||
v-model="checked"
|
||||
:class="[
|
||||
checked ? 'bg-green-500' : 'bg-gray-200',
|
||||
'relative inline-flex h-24px w-[44px] flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
checked ? 'translate-x-20px' : 'translate-x-0',
|
||||
'pointer-events-none relative inline-block h-20px w-20px transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
checked ? 'opacity-0 duration-100 ease-out' : 'opacity-100 duration-200 ease-in',
|
||||
'absolute inset-0 flex h-full w-full items-center justify-center transition-opacity',
|
||||
]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg class="h-12px w-12px text-gray-400" fill="none" viewBox="0 0 12 12">
|
||||
<path d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
:class="[
|
||||
checked ? 'opacity-100 duration-200 ease-in' : 'opacity-0 duration-100 ease-out',
|
||||
'absolute inset-0 flex h-full w-full items-center justify-center transition-opacity',
|
||||
]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg class="h-12px w-12px text-green-500" fill="currentColor" viewBox="0 0 12 12">
|
||||
<path d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</Switch>
|
||||
<SwitchLabel class="text-14px">
|
||||
{{ label }}
|
||||
</SwitchLabel>
|
||||
</div>
|
||||
</SwitchGroup>
|
||||
</template>
|
||||
@@ -1,16 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{
|
||||
maxWidth?: string;
|
||||
}>(), {
|
||||
maxWidth: 'max-w-1024px',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="grid gap-y-24px w-full mx-auto px-16px"
|
||||
:class="maxWidth"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,34 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
|
||||
export interface Props {
|
||||
label?: string;
|
||||
// propChecked?: boolean;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
label: '',
|
||||
// propChecked: false,
|
||||
});
|
||||
|
||||
const checked = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SwitchGroup>
|
||||
<div class="flex items-center gap-8px p-8px rounded">
|
||||
<Switch
|
||||
v-model="checked"
|
||||
:class="checked ? 'bg-gradient-to-r from-unraid-red to-orange' : 'bg-transparent'"
|
||||
class="relative inline-flex h-24px w-[48px] items-center rounded-full overflow-hidden"
|
||||
>
|
||||
<span v-show="!checked" class="absolute z-0 inset-0 opacity-10 bg-primary" />
|
||||
<span
|
||||
:class="checked ? 'translate-x-[26px]' : 'translate-x-[2px]'"
|
||||
class="inline-block h-20px w-20px transform rounded-full bg-white transition"
|
||||
/>
|
||||
</Switch>
|
||||
<SwitchLabel>{{ label }}</SwitchLabel>
|
||||
</div>
|
||||
</SwitchGroup>
|
||||
</template>
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ArrowPathIcon, ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import { BrandButton } from '@unraid/ui';
|
||||
|
||||
import type { BrandButtonProps } from '@unraid/ui';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useAccountStore } from '~/store/account';
|
||||
import type { ButtonStyle } from '~/types/ui/button';
|
||||
|
||||
defineProps<{
|
||||
btnStyle?: ButtonStyle;
|
||||
variant?: BrandButtonProps['variant'];
|
||||
t: ComposerTranslation;
|
||||
}>();
|
||||
|
||||
@@ -16,7 +18,7 @@ const accountStore = useAccountStore();
|
||||
<template>
|
||||
<div class="flex flex-col sm:flex-shrink-0 sm:flex-grow-0 items-center">
|
||||
<BrandButton
|
||||
:btn-style="btnStyle"
|
||||
:variant="variant"
|
||||
:icon="ArrowPathIcon"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
:text="t('Check for OS Updates')"
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import {
|
||||
ArrowTopRightOnSquareIcon,
|
||||
ArrowSmallRightIcon,
|
||||
ArrowTopRightOnSquareIcon,
|
||||
EyeIcon,
|
||||
KeyIcon,
|
||||
ServerStackIcon,
|
||||
XMarkIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { BrandButton, BrandLoading } from '@unraid/ui';
|
||||
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { usePurchaseStore } from '~/store/purchase';
|
||||
@@ -31,12 +34,8 @@ const updateOsStore = useUpdateOsStore();
|
||||
const updateOsChangelogStore = useUpdateOsChangelogStore();
|
||||
|
||||
const { availableWithRenewal } = storeToRefs(updateOsStore);
|
||||
const {
|
||||
releaseForUpdate,
|
||||
mutatedParsedChangelog,
|
||||
parseChangelogFailed,
|
||||
parsedChangelogTitle,
|
||||
} = storeToRefs(updateOsChangelogStore);
|
||||
const { releaseForUpdate, mutatedParsedChangelog, parseChangelogFailed, parsedChangelogTitle } =
|
||||
storeToRefs(updateOsChangelogStore);
|
||||
|
||||
const showExtendKeyButton = computed(() => {
|
||||
return availableWithRenewal.value;
|
||||
@@ -62,27 +61,21 @@ const showExtendKeyButton = computed(() => {
|
||||
v-html="mutatedParsedChangelog"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-else-if="parseChangelogFailed"
|
||||
class="text-center flex flex-col gap-4 prose"
|
||||
>
|
||||
<div v-else-if="parseChangelogFailed" class="text-center flex flex-col gap-4 prose">
|
||||
<h2 class="text-lg text-unraid-red italic font-semibold">
|
||||
{{ props.t(`Error Parsing Changelog • {0}`, [parseChangelogFailed]) }}
|
||||
</h2>
|
||||
<p>
|
||||
{{ props.t(`It's highly recommended to review the changelog before continuing your update`) }}
|
||||
</p>
|
||||
<div
|
||||
v-if="releaseForUpdate?.changelogPretty"
|
||||
class="flex self-center"
|
||||
>
|
||||
<div v-if="releaseForUpdate?.changelogPretty" class="flex self-center">
|
||||
<BrandButton
|
||||
:href="releaseForUpdate?.changelogPretty"
|
||||
btn-style="underline"
|
||||
variant="underline"
|
||||
:external="true"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
>
|
||||
{{ props.t("View Changelog on Docs") }}
|
||||
{{ props.t('View Changelog on Docs') }}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,7 +85,7 @@ const showExtendKeyButton = computed(() => {
|
||||
class="text-center flex flex-col justify-center w-full min-h-[250px] min-w-[280px] sm:min-w-[400px]"
|
||||
>
|
||||
<BrandLoading class="w-[150px] mx-auto mt-24px" />
|
||||
<p>{{ props.t("Fetching & parsing changelog…") }}</p>
|
||||
<p>{{ props.t('Fetching & parsing changelog…') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -100,31 +93,31 @@ const showExtendKeyButton = computed(() => {
|
||||
<div class="flex flex-col-reverse xs:flex-row justify-between gap-12px md:gap-16px">
|
||||
<div class="flex flex-col-reverse xs:flex-row xs:justify-start gap-12px md:gap-16px">
|
||||
<BrandButton
|
||||
btn-style="underline-hover-red"
|
||||
variant="underline"
|
||||
:icon="XMarkIcon"
|
||||
@click="updateOsChangelogStore.setReleaseForUpdate(null)"
|
||||
>
|
||||
{{ props.t("Close") }}
|
||||
{{ props.t('Close') }}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
v-if="releaseForUpdate?.changelogPretty"
|
||||
btn-style="underline"
|
||||
variant="underline"
|
||||
:external="true"
|
||||
:href="releaseForUpdate?.changelogPretty"
|
||||
:icon="EyeIcon"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
>
|
||||
{{ props.t("View on Docs") }}
|
||||
{{ props.t('View on Docs') }}
|
||||
</BrandButton>
|
||||
</div>
|
||||
<BrandButton
|
||||
v-if="showExtendKeyButton"
|
||||
btn-style="fill"
|
||||
variant="fill"
|
||||
:icon="KeyIcon"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
@click="purchaseStore.renew()"
|
||||
>
|
||||
{{ props.t("Extend License to Update") }}
|
||||
{{ props.t('Extend License to Update') }}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
v-else-if="releaseForUpdate?.sha256"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import {
|
||||
ArrowTopRightOnSquareIcon,
|
||||
CogIcon,
|
||||
@@ -7,8 +9,10 @@ import {
|
||||
KeyIcon,
|
||||
XMarkIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { BrandButton, BrandLoading } from '@unraid/ui';
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import type { BrandButtonProps } from '@unraid/ui';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
@@ -17,7 +21,6 @@ import { usePurchaseStore } from '~/store/purchase';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore } from '~/store/updateOs';
|
||||
import { useUpdateOsChangelogStore } from '~/store/updateOsChangelog';
|
||||
import type { ButtonProps } from '~/types/ui/button';
|
||||
|
||||
export interface Props {
|
||||
open?: boolean;
|
||||
@@ -55,10 +58,18 @@ const {
|
||||
* So we need to watch for this value to be able to format it based on the user's date time preferences.
|
||||
*/
|
||||
const formattedRegExp = ref<string>();
|
||||
const setFormattedRegExp = () => { // ran in watch on regExp and onBeforeMount
|
||||
if (!regExp.value) { return; }
|
||||
const setFormattedRegExp = () => {
|
||||
// ran in watch on regExp and onBeforeMount
|
||||
if (!regExp.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { outputDateTimeFormatted } = useDateTimeHelper(dateTimeFormat.value, props.t, true, regExp.value);
|
||||
const { outputDateTimeFormatted } = useDateTimeHelper(
|
||||
dateTimeFormat.value,
|
||||
props.t,
|
||||
true,
|
||||
regExp.value
|
||||
);
|
||||
formattedRegExp.value = outputDateTimeFormatted.value;
|
||||
};
|
||||
watch(regExp, (_newV) => {
|
||||
@@ -75,7 +86,9 @@ watch(updateOsIgnoredReleases, (newVal, oldVal) => {
|
||||
|
||||
const notificationsSettings = computed(() => {
|
||||
return !updateOsNotificationsEnabled.value
|
||||
? props.t('Go to Settings > Notifications to enable automatic OS update notifications for future releases.')
|
||||
? props.t(
|
||||
'Go to Settings > Notifications to enable automatic OS update notifications for future releases.'
|
||||
)
|
||||
: undefined;
|
||||
});
|
||||
|
||||
@@ -111,7 +124,9 @@ const modalCopy = computed((): ModalCopy | null => {
|
||||
: undefined;
|
||||
return {
|
||||
title: props.t('Unraid OS {0} Update Available', [available.value]),
|
||||
description: description ? `<p>${formattedReleaseDate}</p><p>${description}</p>` : formattedReleaseDate,
|
||||
description: description
|
||||
? `<p>${formattedReleaseDate}</p><p>${description}</p>`
|
||||
: formattedReleaseDate,
|
||||
};
|
||||
} else if (!available.value && !availableWithRenewal.value) {
|
||||
return {
|
||||
@@ -126,12 +141,12 @@ const showNotificationsSettingsLink = computed(() => {
|
||||
return !updateOsNotificationsEnabled.value && !available.value && !availableWithRenewal.value;
|
||||
});
|
||||
|
||||
const extraLinks = computed((): ButtonProps[] => {
|
||||
const buttons: ButtonProps[] = [];
|
||||
const extraLinks = computed((): BrandButtonProps[] => {
|
||||
const buttons: BrandButtonProps[] = [];
|
||||
|
||||
if (showNotificationsSettingsLink.value) {
|
||||
buttons.push({
|
||||
btnStyle: 'outline',
|
||||
variant: 'outline',
|
||||
href: '/Settings/Notifications',
|
||||
icon: CogIcon,
|
||||
text: props.t('Enable update notifications'),
|
||||
@@ -141,11 +156,13 @@ const extraLinks = computed((): ButtonProps[] => {
|
||||
return buttons;
|
||||
});
|
||||
|
||||
const actionButtons = computed((): ButtonProps[] | null => {
|
||||
const actionButtons = computed((): BrandButtonProps[] | null => {
|
||||
// update not available or no action buttons default closing
|
||||
if (!available.value || ignoreThisRelease.value) { return null; }
|
||||
if (!available.value || ignoreThisRelease.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const buttons: ButtonProps[] = [];
|
||||
const buttons: BrandButtonProps[] = [];
|
||||
|
||||
// update available but not stable branch - should link out to account update callback
|
||||
// if availableWithRenewal.value is true, then we need to renew the license before we can update so don't show the verify button
|
||||
@@ -162,10 +179,9 @@ const actionButtons = computed((): ButtonProps[] | null => {
|
||||
// update available - open changelog to commence update
|
||||
if (available.value && updateOsResponse.value?.changelog) {
|
||||
buttons.push({
|
||||
btnStyle: availableWithRenewal.value
|
||||
? 'outline'
|
||||
: undefined,
|
||||
click: async () => await updateOsChangelogStore.setReleaseForUpdate(updateOsResponse.value ?? null),
|
||||
variant: availableWithRenewal.value ? 'outline' : undefined,
|
||||
click: async () =>
|
||||
await updateOsChangelogStore.setReleaseForUpdate(updateOsResponse.value ?? null),
|
||||
icon: EyeIcon,
|
||||
text: availableWithRenewal.value
|
||||
? props.t('View Changelog')
|
||||
@@ -200,7 +216,13 @@ const close = () => {
|
||||
};
|
||||
|
||||
const renderMainSlot = computed(() => {
|
||||
return !!(checkForUpdatesLoading.value || available.value || availableWithRenewal.value || extraLinks.value?.length > 0 || updateOsIgnoredReleases.value.length > 0);
|
||||
return !!(
|
||||
checkForUpdatesLoading.value ||
|
||||
available.value ||
|
||||
availableWithRenewal.value ||
|
||||
extraLinks.value?.length > 0 ||
|
||||
updateOsIgnoredReleases.value.length > 0
|
||||
);
|
||||
});
|
||||
|
||||
const userFormattedReleaseDate = ref<string>();
|
||||
@@ -209,9 +231,16 @@ const userFormattedReleaseDate = ref<string>();
|
||||
* So we need to watch for this value to be able to format it based on the user's date time preferences.
|
||||
*/
|
||||
const setUserFormattedReleaseDate = () => {
|
||||
if (!availableReleaseDate.value) { return; }
|
||||
if (!availableReleaseDate.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { outputDateTimeFormatted } = useDateTimeHelper(dateTimeFormat.value, props.t, true, availableReleaseDate.value.valueOf());
|
||||
const { outputDateTimeFormatted } = useDateTimeHelper(
|
||||
dateTimeFormat.value,
|
||||
props.t,
|
||||
true,
|
||||
availableReleaseDate.value.valueOf()
|
||||
);
|
||||
userFormattedReleaseDate.value = outputDateTimeFormatted.value;
|
||||
};
|
||||
watch(availableReleaseDate, (_newV) => {
|
||||
@@ -225,7 +254,8 @@ onBeforeMount(() => {
|
||||
});
|
||||
|
||||
const modalWidth = computed(() => {
|
||||
if (availableWithRenewal.value) { // wider since we'll have four buttons
|
||||
if (availableWithRenewal.value) {
|
||||
// wider since we'll have four buttons
|
||||
return 'max-w-800px';
|
||||
}
|
||||
return 'max-w-640px';
|
||||
@@ -249,7 +279,7 @@ const modalWidth = computed(() => {
|
||||
<BrandButton
|
||||
v-for="item in extraLinks"
|
||||
:key="item.text"
|
||||
:btn-style="item.btnStyle ?? undefined"
|
||||
:btn-style="item.variant ?? undefined"
|
||||
:href="item.href ?? undefined"
|
||||
:icon="item.icon"
|
||||
:icon-right="item.iconRight"
|
||||
@@ -265,10 +295,15 @@ const modalWidth = computed(() => {
|
||||
<div class="flex justify-center items-center gap-8px p-8px rounded">
|
||||
<Switch
|
||||
v-model="ignoreThisRelease"
|
||||
:class="ignoreThisRelease ? 'bg-gradient-to-r from-unraid-red to-orange' : 'bg-transparent'"
|
||||
:class="
|
||||
ignoreThisRelease ? 'bg-gradient-to-r from-unraid-red to-orange' : 'bg-transparent'
|
||||
"
|
||||
class="relative inline-flex h-24px w-[48px] items-center rounded-full overflow-hidden"
|
||||
>
|
||||
<span v-show="!ignoreThisRelease" class="absolute z-0 inset-0 opacity-10 bg-foreground" />
|
||||
<span
|
||||
v-show="!ignoreThisRelease"
|
||||
class="absolute z-0 inset-0 opacity-10 bg-foreground"
|
||||
/>
|
||||
<span
|
||||
:class="ignoreThisRelease ? 'translate-x-[26px]' : 'translate-x-[2px]'"
|
||||
class="inline-block h-20px w-20px transform rounded-full bg-white transition"
|
||||
@@ -280,7 +315,10 @@ const modalWidth = computed(() => {
|
||||
</div>
|
||||
</SwitchGroup>
|
||||
</div>
|
||||
<div v-else-if="updateOsIgnoredReleases.length > 0" class="w-full max-w-640px mx-auto flex flex-col gap-8px">
|
||||
<div
|
||||
v-else-if="updateOsIgnoredReleases.length > 0"
|
||||
class="w-full max-w-640px mx-auto flex flex-col gap-8px"
|
||||
>
|
||||
<h3 class="text-left text-16px font-semibold italic">
|
||||
{{ t('Ignored Releases') }}
|
||||
</h3>
|
||||
@@ -304,13 +342,13 @@ const modalWidth = computed(() => {
|
||||
>
|
||||
<div class="flex flex-col-reverse xs:flex-row justify-start gap-8px">
|
||||
<BrandButton
|
||||
btn-style="underline-hover-red"
|
||||
variant="underline-hover-red"
|
||||
:icon="XMarkIcon"
|
||||
:text="t('Close')"
|
||||
@click="close"
|
||||
/>
|
||||
<BrandButton
|
||||
btn-style="underline"
|
||||
variant="underline"
|
||||
:icon="ArrowTopRightOnSquareIcon"
|
||||
:text="t('More options')"
|
||||
@click="accountStore.updateOs()"
|
||||
@@ -320,7 +358,7 @@ const modalWidth = computed(() => {
|
||||
<BrandButton
|
||||
v-for="item in actionButtons"
|
||||
:key="item.text"
|
||||
:btn-style="item.btnStyle ?? undefined"
|
||||
:btn-style="item.variant ?? undefined"
|
||||
:icon="item.icon"
|
||||
:icon-right="item.iconRight"
|
||||
:icon-right-hover-display="item.iconRightHoverDisplay"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { XMarkIcon } from '@heroicons/vue/24/solid';
|
||||
import { BrandButton } from '@unraid/ui';
|
||||
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useServerStore } from '~/store/server';
|
||||
@@ -29,7 +31,7 @@ const evenBgColor = computed(() => {
|
||||
>
|
||||
<span class="font-semibold">{{ label }}</span>
|
||||
<BrandButton
|
||||
:btn-style="'underline'"
|
||||
variant="underline"
|
||||
:icon-right="XMarkIcon"
|
||||
:text="t('Remove')"
|
||||
:title="t('Remove from ignore list')"
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { h } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
ArrowTopRightOnSquareIcon,
|
||||
@@ -8,17 +11,17 @@ import {
|
||||
InformationCircleIcon,
|
||||
XCircleIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { Badge, BrandButton } from '@unraid/ui';
|
||||
import BrandLoadingWhite from '~/components/Brand/LoadingWhite.vue';
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
import { Badge, BrandButton, BrandLoading } from '@unraid/ui';
|
||||
import { WEBGUI_TOOLS_REGISTRATION } from '~/helpers/urls';
|
||||
|
||||
import type { BrandButtonProps } from '@unraid/ui';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
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';
|
||||
|
||||
export interface Props {
|
||||
downgradeNotAvailable?: boolean;
|
||||
@@ -41,6 +44,8 @@ const serverStore = useServerStore();
|
||||
const updateOsStore = useUpdateOsStore();
|
||||
const updateOsActionsStore = useUpdateOsActionsStore();
|
||||
|
||||
const LoadingIcon = () => h(BrandLoading, { variant: 'white' });
|
||||
|
||||
const { dateTimeFormat, osVersion, rebootType, rebootVersion, regExp, regUpdatesExpired } =
|
||||
storeToRefs(serverStore);
|
||||
const { available, availableWithRenewal } = storeToRefs(updateOsStore);
|
||||
@@ -69,10 +74,10 @@ const showRebootButton = computed(
|
||||
() => rebootType.value === 'downgrade' || rebootType.value === 'update'
|
||||
);
|
||||
|
||||
const checkButton = computed((): ButtonProps => {
|
||||
const checkButton = computed((): BrandButtonProps => {
|
||||
if (showRebootButton.value || props.showExternalDowngrade) {
|
||||
return {
|
||||
btnStyle: 'outline',
|
||||
variant: 'outline',
|
||||
click: () => {
|
||||
props.showExternalDowngrade ? accountStore.downgradeOs() : accountStore.updateOs();
|
||||
},
|
||||
@@ -83,7 +88,7 @@ const checkButton = computed((): ButtonProps => {
|
||||
|
||||
if (!updateAvailable.value) {
|
||||
return {
|
||||
btnStyle: 'outline',
|
||||
variant: 'outline',
|
||||
click: () => {
|
||||
updateOsStore.localCheckForUpdate();
|
||||
},
|
||||
@@ -93,7 +98,7 @@ const checkButton = computed((): ButtonProps => {
|
||||
}
|
||||
|
||||
return {
|
||||
btnStyle: 'fill',
|
||||
variant: 'fill',
|
||||
click: () => {
|
||||
updateOsStore.setModalOpen(true);
|
||||
},
|
||||
@@ -151,7 +156,7 @@ const checkButton = computed((): ButtonProps => {
|
||||
{{ t('Key ineligible for {0}', [availableWithRenewal]) }}
|
||||
</Badge>
|
||||
|
||||
<Badge v-if="status === 'checking'" variant="orange" :icon="BrandLoadingWhite">
|
||||
<Badge v-if="status === 'checking'" variant="orange" :icon="LoadingIcon">
|
||||
{{ t('Checking...') }}
|
||||
</Badge>
|
||||
<template v-else>
|
||||
@@ -194,7 +199,7 @@ const checkButton = computed((): ButtonProps => {
|
||||
|
||||
<span>
|
||||
<BrandButton
|
||||
:variant="checkButton.btnStyle"
|
||||
:variant="checkButton.variant"
|
||||
:icon="checkButton.icon"
|
||||
:text="checkButton.text"
|
||||
@click="checkButton.click"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { ExclamationTriangleIcon } from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { ExclamationTriangleIcon } from '@heroicons/vue/24/solid';
|
||||
import { CardWrapper } from '@unraid/ui';
|
||||
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
@@ -13,7 +16,7 @@ const { rebootTypeText } = storeToRefs(useUpdateOsActionsStore());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiCardWrapper :increased-padding="true">
|
||||
<CardWrapper :increased-padding="true">
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-20px sm:gap-24px">
|
||||
<div class="grid gap-y-16px">
|
||||
<h3 class="text-20px font-semibold leading-normal flex flex-row items-center gap-8px">
|
||||
@@ -21,9 +24,15 @@ const { rebootTypeText } = storeToRefs(useUpdateOsActionsStore());
|
||||
{{ t(rebootTypeText) }}
|
||||
</h3>
|
||||
<div class="text-16px leading-relaxed opacity-75 whitespace-normal">
|
||||
<p>{{ t('During the Unraid OS update process third-party drivers were detected and are currently being updated in the background. Please wait for those to finish downloading before rebooting your server to complete the update process. You should receive a system notification when complete. You may also refresh this page to check for an updated status.') }}</p>
|
||||
<p>
|
||||
{{
|
||||
t(
|
||||
'During the Unraid OS update process third-party drivers were detected and are currently being updated in the background. Please wait for those to finish downloading before rebooting your server to complete the update process. You should receive a system notification when complete. You may also refresh this page to check for an updated status.'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UiCardWrapper>
|
||||
</CardWrapper>
|
||||
</template>
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
* @todo require keyfile to update
|
||||
* @todo require valid guid / server state to update
|
||||
*/
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import {
|
||||
ArchiveBoxArrowDownIcon,
|
||||
ArrowPathIcon,
|
||||
@@ -12,19 +14,17 @@ import {
|
||||
BellAlertIcon,
|
||||
EyeIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { BrandButton, CardWrapper } from '@unraid/ui';
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore } from '~/store/updateOs';
|
||||
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
|
||||
const props = defineProps<{
|
||||
t: ComposerTranslation;
|
||||
@@ -39,9 +39,12 @@ const updateOsActionsStore = useUpdateOsActionsStore();
|
||||
const { connectPluginInstalled, flashBackupActivated } = storeToRefs(useServerStore());
|
||||
const { available } = storeToRefs(updateOsStore);
|
||||
|
||||
const {
|
||||
outputDateTimeFormatted: formattedReleaseDate,
|
||||
} = useDateTimeHelper(dateTimeFormat.value, props.t, true, dayjs(updateOsResponse.value?.date ?? '', 'YYYY-MM-DD').valueOf());
|
||||
const { outputDateTimeFormatted: formattedReleaseDate } = useDateTimeHelper(
|
||||
dateTimeFormat.value,
|
||||
props.t,
|
||||
true,
|
||||
dayjs(updateOsResponse.value?.date ?? '', 'YYYY-MM-DD').valueOf()
|
||||
);
|
||||
|
||||
const updateButton = ref<UserProfileLink | undefined>();
|
||||
|
||||
@@ -90,7 +93,12 @@ const startFlashBackup = () => {
|
||||
flashBackup();
|
||||
checkFlashBackupStatus();
|
||||
} else {
|
||||
alert(props.t('Flash Backup is not available. Navigate to {0}/Main/Settings/Flash to try again then come back to this page.', [window.location.origin]));
|
||||
alert(
|
||||
props.t(
|
||||
'Flash Backup is not available. Navigate to {0}/Main/Settings/Flash to try again then come back to this page.',
|
||||
[window.location.origin]
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
/**
|
||||
@@ -114,7 +122,9 @@ const checkFlashBackupStatus = () => {
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const disableCallbackButton = computed(() => !acknowledgeBackup.value || flashBackupBasicStatus.value === 'started');
|
||||
const disableCallbackButton = computed(
|
||||
() => !acknowledgeBackup.value || flashBackupBasicStatus.value === 'started'
|
||||
);
|
||||
|
||||
watchEffect(() => {
|
||||
if (available.value) {
|
||||
@@ -129,28 +139,29 @@ watchEffect(() => {
|
||||
</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">
|
||||
<component :is="headingIcon" class="w-20px shrink-0" />
|
||||
<span class="leading-none inline-flex flex-wrap justify-start items-baseline gap-8px">
|
||||
<span class="text-20px">
|
||||
{{ heading }}
|
||||
</span>
|
||||
<span
|
||||
v-if="updateOsResponse && formattedReleaseDate"
|
||||
class="text-16px opacity-75 shrink"
|
||||
>
|
||||
<span v-if="updateOsResponse && formattedReleaseDate" class="text-16px opacity-75 shrink">
|
||||
{{ formattedReleaseDate }}
|
||||
</span>
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div class="prose opacity-75 text-16px leading-relaxed whitespace-normal">
|
||||
<p>{{ t('Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.') }}</p>
|
||||
<p>
|
||||
{{
|
||||
t(
|
||||
'Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p v-if="available">
|
||||
{{ flashBackupCopy }}
|
||||
</p>
|
||||
@@ -160,7 +171,7 @@ watchEffect(() => {
|
||||
<div class="flex flex-col sm:flex-shrink-0 items-center gap-16px">
|
||||
<template v-if="available && updateButton">
|
||||
<BrandButton
|
||||
btn-style="outline"
|
||||
variant="outline"
|
||||
:disabled="flashBackupBasicStatus === 'started'"
|
||||
:icon="ArchiveBoxArrowDownIcon"
|
||||
:name="'flashBackup'"
|
||||
@@ -191,24 +202,36 @@ watchEffect(() => {
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
acknowledgeBackup ? 'opacity-0 duration-100 ease-out' : 'opacity-100 duration-200 ease-in',
|
||||
acknowledgeBackup
|
||||
? 'opacity-0 duration-100 ease-out'
|
||||
: 'opacity-100 duration-200 ease-in',
|
||||
'absolute inset-0 flex h-full w-full items-center justify-center transition-opacity',
|
||||
]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg class="h-12px w-12px text-gray-400" fill="none" viewBox="0 0 12 12">
|
||||
<path d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path
|
||||
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
:class="[
|
||||
acknowledgeBackup ? 'opacity-100 duration-200 ease-in' : 'opacity-0 duration-100 ease-out',
|
||||
acknowledgeBackup
|
||||
? 'opacity-100 duration-200 ease-in'
|
||||
: 'opacity-0 duration-100 ease-out',
|
||||
'absolute inset-0 flex h-full w-full items-center justify-center transition-opacity',
|
||||
]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg class="h-12px w-12px text-green-500" fill="currentColor" viewBox="0 0 12 12">
|
||||
<path d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" />
|
||||
<path
|
||||
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
@@ -221,24 +244,28 @@ watchEffect(() => {
|
||||
</template>
|
||||
|
||||
<BrandButton
|
||||
btn-style="fill"
|
||||
variant="fill"
|
||||
:disabled="disableCallbackButton"
|
||||
:external="updateButton?.external"
|
||||
:icon="EyeIcon"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
:name="updateButton?.name"
|
||||
:text="t('View Available Updates')"
|
||||
:title="!acknowledgeBackup ? t('Acklowledge that you have made a Flash Backup to enable this action') : ''"
|
||||
:title="
|
||||
!acknowledgeBackup
|
||||
? t('Acklowledge that you have made a Flash Backup to enable this action')
|
||||
: ''
|
||||
"
|
||||
class="flex-none"
|
||||
@click="updateButton?.click"
|
||||
/>
|
||||
</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>
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
ArrowTopRightOnSquareIcon,
|
||||
ExclamationTriangleIcon,
|
||||
EyeIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import dayjs from 'dayjs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
import { ArrowTopRightOnSquareIcon, ExclamationTriangleIcon, EyeIcon } from '@heroicons/vue/24/solid';
|
||||
import { BrandButton, CardWrapper } from '@unraid/ui';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore } from '~/store/updateOs';
|
||||
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
|
||||
const props = defineProps<{
|
||||
t: ComposerTranslation;
|
||||
@@ -30,8 +26,15 @@ const { dateTimeFormat, regTy, renewAction, updateOsResponse } = storeToRefs(ser
|
||||
const { availableWithRenewal } = storeToRefs(updateOsStore);
|
||||
const { ineligibleText } = storeToRefs(updateOsActionsStore);
|
||||
|
||||
const availableWithRenewalRelease = computed(() => availableWithRenewal.value ? updateOsResponse.value : undefined);
|
||||
const { outputDateTimeFormatted: formattedReleaseDate } = useDateTimeHelper(dateTimeFormat.value, props.t, true, dayjs(availableWithRenewalRelease.value?.date, 'YYYY-MM-DD').valueOf());
|
||||
const availableWithRenewalRelease = computed(() =>
|
||||
availableWithRenewal.value ? updateOsResponse.value : undefined
|
||||
);
|
||||
const { outputDateTimeFormatted: formattedReleaseDate } = useDateTimeHelper(
|
||||
dateTimeFormat.value,
|
||||
props.t,
|
||||
true,
|
||||
dayjs(availableWithRenewalRelease.value?.date, 'YYYY-MM-DD').valueOf()
|
||||
);
|
||||
|
||||
const heading = computed((): string => {
|
||||
if (availableWithRenewal.value) {
|
||||
@@ -56,19 +59,19 @@ watchEffect(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiCardWrapper :increased-padding="true" :warning="true">
|
||||
<CardWrapper :increased-padding="true" :warning="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">
|
||||
<ExclamationTriangleIcon class="w-20px shrink-0" />
|
||||
<span class="leading-none inline-flex flex-wrap justify-start items-baseline gap-8px">
|
||||
<span class="text-20px">
|
||||
{{ heading }}
|
||||
</span>
|
||||
<span
|
||||
v-if="availableWithRenewalRelease && availableWithRenewalRelease.date && formattedReleaseDate"
|
||||
v-if="
|
||||
availableWithRenewalRelease && availableWithRenewalRelease.date && formattedReleaseDate
|
||||
"
|
||||
class="text-16px opacity-75 shrink"
|
||||
>
|
||||
{{ formattedReleaseDate }}
|
||||
@@ -80,10 +83,7 @@ watchEffect(() => {
|
||||
<RegistrationUpdateExpiration :t="t" />
|
||||
</h4>
|
||||
|
||||
<div
|
||||
class="prose text-black text-16px leading-relaxed whitespace-normal"
|
||||
v-html="text"
|
||||
/>
|
||||
<div class="prose text-black text-16px leading-relaxed whitespace-normal" v-html="text" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-shrink-0 items-center gap-16px">
|
||||
@@ -98,7 +98,7 @@ watchEffect(() => {
|
||||
@click="renewAction.click?.()"
|
||||
/>
|
||||
<!-- <BrandButton
|
||||
btn-style="black"
|
||||
variant="black"
|
||||
href="/Tools/Registration"
|
||||
:icon="WrenchScrewdriverIcon"
|
||||
:icon-right="ArrowSmallRightIcon"
|
||||
@@ -107,7 +107,7 @@ watchEffect(() => {
|
||||
|
||||
<BrandButton
|
||||
v-if="availableWithRenewal && updateButton"
|
||||
btn-style="outline-black"
|
||||
variant="outline-black"
|
||||
:external="updateButton?.external"
|
||||
:icon="EyeIcon"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
@@ -118,11 +118,11 @@ watchEffect(() => {
|
||||
/>
|
||||
</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>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { OnClickOutside } from '@vueuse/components';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useCallbackStore, useCallbackActionsStore } from '~/store/callbackActions';
|
||||
import { devConfig } from '~/helpers/env';
|
||||
|
||||
import type { Server } from '~/types/server';
|
||||
|
||||
import { useCallbackActionsStore, useCallbackStore } from '~/store/callbackActions';
|
||||
import { useDropdownStore } from '~/store/dropdown';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useThemeStore } from '~/store/theme';
|
||||
import type { Server } from '~/types/server';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
import { devConfig } from '~/helpers/env';
|
||||
|
||||
export interface Props {
|
||||
server?: Server | string;
|
||||
@@ -26,13 +26,7 @@ const serverStore = useServerStore();
|
||||
|
||||
const { callbackData } = storeToRefs(useCallbackActionsStore());
|
||||
const { dropdownVisible } = storeToRefs(dropdownStore);
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
guid,
|
||||
keyfile,
|
||||
lanIp,
|
||||
} = storeToRefs(serverStore);
|
||||
const { name, description, guid, keyfile, lanIp } = storeToRefs(serverStore);
|
||||
const { bannerGradient, theme } = storeToRefs(useThemeStore());
|
||||
|
||||
/**
|
||||
@@ -42,7 +36,9 @@ const { bannerGradient, theme } = storeToRefs(useThemeStore());
|
||||
const clickOutsideTarget = ref();
|
||||
const clickOutsideIgnoreTarget = ref();
|
||||
const outsideDropdown = () => {
|
||||
if (dropdownVisible.value) { return dropdownStore.dropdownToggle(); }
|
||||
if (dropdownVisible.value) {
|
||||
return dropdownStore.dropdownToggle();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -51,7 +47,8 @@ const outsideDropdown = () => {
|
||||
let copyIpInterval: string | number | NodeJS.Timeout | undefined;
|
||||
const { copy, copied, isSupported } = useClipboard({ source: lanIp.value ?? '' });
|
||||
const showCopyNotSupported = ref<boolean>(false);
|
||||
const copyLanIp = () => { // if http then clipboard is not supported
|
||||
const copyLanIp = () => {
|
||||
// if http then clipboard is not supported
|
||||
if (!isSupported || window.location.protocol === 'http:') {
|
||||
showCopyNotSupported.value = true;
|
||||
return;
|
||||
@@ -75,9 +72,11 @@ onBeforeMount(() => {
|
||||
throw new Error('Server data not present');
|
||||
}
|
||||
|
||||
if (typeof props.server === 'object') { // Handles the testing dev Vue component
|
||||
if (typeof props.server === 'object') {
|
||||
// Handles the testing dev Vue component
|
||||
serverStore.setServer(props.server);
|
||||
} else if (typeof props.server === 'string') { // Handle web component
|
||||
} else if (typeof props.server === 'string') {
|
||||
// Handle web component
|
||||
const parsedServerProp = JSON.parse(props.server);
|
||||
serverStore.setServer(parsedServerProp);
|
||||
}
|
||||
@@ -87,10 +86,14 @@ onBeforeMount(() => {
|
||||
|
||||
if (guid.value && keyfile.value) {
|
||||
if (callbackData.value) {
|
||||
return console.debug('Renew callback detected, skipping auto check for key replacement, renewal eligibility, and OS Update.');
|
||||
return console.debug(
|
||||
'Renew callback detected, skipping auto check for key replacement, renewal eligibility, and OS Update.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.warn('A valid keyfile and USB Flash boot device are required to check for key renewals, key replacement eligibiliy, and OS update availability.');
|
||||
console.warn(
|
||||
'A valid keyfile and USB Flash boot device are required to check for key renewals, key replacement eligibiliy, and OS update availability.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -98,7 +101,7 @@ onMounted(() => {
|
||||
if (devConfig.VITE_MOCK_USER_SESSION && devConfig.NODE_ENV === 'development') {
|
||||
document.cookie = 'unraid_session_cookie=mockusersession';
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -106,21 +109,33 @@ onMounted(() => {
|
||||
id="UserProfile"
|
||||
class="text-foreground relative z-20 flex flex-col h-full gap-y-4px pt-4px pr-16px pl-40px"
|
||||
>
|
||||
<div v-if="bannerGradient" class="absolute z-0 w-[125%] top-0 bottom-0 right-0" :style="bannerGradient" />
|
||||
<div
|
||||
v-if="bannerGradient"
|
||||
class="absolute z-0 w-[125%] top-0 bottom-0 right-0"
|
||||
:style="bannerGradient"
|
||||
/>
|
||||
|
||||
<div class="text-xs text-header-text-secondary text-right font-semibold leading-normal relative z-10 flex flex-col items-end justify-end gap-x-4px xs:flex-row xs:items-baseline xs:gap-x-12px">
|
||||
<div
|
||||
class="text-xs text-header-text-secondary text-right font-semibold leading-normal relative z-10 flex flex-col items-end justify-end gap-x-4px xs:flex-row xs:items-baseline xs:gap-x-12px"
|
||||
>
|
||||
<UpcUptimeExpire :t="t" />
|
||||
<span class="hidden xs:block">•</span>
|
||||
<UpcServerState :t="t" />
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 flex flex-row items-center justify-end gap-x-16px h-full">
|
||||
<h1 class="text-14px sm:text-18px relative flex flex-col-reverse items-end md:flex-row border-0 text-header-text-primary">
|
||||
<h1
|
||||
class="text-14px sm:text-18px relative flex flex-col-reverse items-end md:flex-row border-0 text-header-text-primary"
|
||||
>
|
||||
<template v-if="description && theme?.descriptionShow">
|
||||
<span class="text-right text-12px sm:text-18px hidden 2xs:block" v-html="description" />
|
||||
<span class="text-header-text-secondary hidden md:inline-block px-8px">•</span>
|
||||
</template>
|
||||
<button :title="t('Click to Copy LAN IP {0}', [lanIp])" class="text-header-text-primary opacity-100 hover:opacity-75 focus:opacity-75 transition-opacity" @click="copyLanIp()">
|
||||
<button
|
||||
:title="t('Click to Copy LAN IP {0}', [lanIp])"
|
||||
class="text-header-text-primary opacity-100 hover:opacity-75 focus:opacity-75 transition-opacity"
|
||||
@click="copyLanIp()"
|
||||
>
|
||||
{{ name }}
|
||||
</button>
|
||||
<span
|
||||
@@ -137,7 +152,11 @@ onMounted(() => {
|
||||
<!-- Keep the sidebar out of staging/prod builds, but easily accessible for development -->
|
||||
<NotificationsSidebar />
|
||||
|
||||
<OnClickOutside class="flex items-center justify-end h-full" :options="{ ignore: [clickOutsideIgnoreTarget] }" @trigger="outsideDropdown">
|
||||
<OnClickOutside
|
||||
class="flex items-center justify-end h-full"
|
||||
:options="{ ignore: [clickOutsideIgnoreTarget] }"
|
||||
@trigger="outsideDropdown"
|
||||
>
|
||||
<UpcDropdownTrigger ref="clickOutsideIgnoreTarget" :t="t" />
|
||||
<UpcDropdown ref="clickOutsideTarget" :t="t" />
|
||||
</OnClickOutside>
|
||||
@@ -146,9 +165,9 @@ onMounted(() => {
|
||||
</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-popover-foreground);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<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 { storeToRefs } from 'pinia';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronDoubleDownIcon,
|
||||
@@ -9,16 +12,17 @@ import {
|
||||
WrenchScrewdriverIcon,
|
||||
XMarkIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { BrandButton, BrandLoading } from '@unraid/ui';
|
||||
import { WEBGUI_CONNECT_SETTINGS, WEBGUI_TOOLS_REGISTRATION } from '~/helpers/urls';
|
||||
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useAccountStore } from '~/store/account';
|
||||
import { useCallbackActionsStore } from '~/store/callbackActions';
|
||||
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;
|
||||
@@ -366,7 +370,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 variant="underline" :icon="XMarkIcon" :text="closeText" @click="close" />
|
||||
|
||||
<template v-if="connectPluginInstalled && accountActionType === 'signIn'">
|
||||
<BrandButton
|
||||
@@ -394,7 +398,7 @@ const showUpdateEligibility = computed(() => {
|
||||
|
||||
<template v-if="updateOsStatus === 'confirming' && !stateDataError">
|
||||
<BrandButton
|
||||
btn-style="underline"
|
||||
variant="underline"
|
||||
:icon="XMarkIcon"
|
||||
:text="t('Cancel')"
|
||||
@click="cancelUpdateOs"
|
||||
@@ -423,6 +427,7 @@ const showUpdateEligibility = computed(() => {
|
||||
<style lang="postcss">
|
||||
/* Import unraid-ui globals first */
|
||||
@import '@unraid/ui/styles';
|
||||
@import '../../assets/main.css';
|
||||
|
||||
.unraid_mark_2,
|
||||
.unraid_mark_4 {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ExclamationTriangleIcon, CheckCircleIcon, UserCircleIcon } from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { CheckCircleIcon, ExclamationTriangleIcon, UserCircleIcon } from '@heroicons/vue/24/solid';
|
||||
import { BrandLoading } from '@unraid/ui';
|
||||
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import BrandLoading from '~/components/Brand/Loading.vue';
|
||||
import { useUnraidApiStore } from '~/store/unraidApi';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUnraidApiStore } from '~/store/unraidApi';
|
||||
|
||||
const props = defineProps<{ t: ComposerTranslation; }>();
|
||||
const props = defineProps<{ t: ComposerTranslation }>();
|
||||
|
||||
const { username } = storeToRefs(useServerStore());
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
ArrowTopRightOnSquareIcon,
|
||||
@@ -9,22 +10,24 @@ import {
|
||||
KeyIcon,
|
||||
UserIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { BrandLogoConnect } from '@unraid/ui';
|
||||
import {
|
||||
CONNECT_DASHBOARD,
|
||||
WEBGUI_CONNECT_SETTINGS,
|
||||
WEBGUI_TOOLS_REGISTRATION,
|
||||
WEBGUI_TOOLS_DOWNGRADE,
|
||||
WEBGUI_TOOLS_REGISTRATION,
|
||||
WEBGUI_TOOLS_UPDATE,
|
||||
} from '~/helpers/urls';
|
||||
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useAccountStore } from '~/store/account';
|
||||
import { useErrorsStore } from '~/store/errors';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore } from '~/store/updateOs';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
|
||||
const props = defineProps<{ t: ComposerTranslation; }>();
|
||||
const props = defineProps<{ t: ComposerTranslation }>();
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
const errorsStore = useErrorsStore();
|
||||
@@ -40,23 +43,29 @@ const {
|
||||
stateData,
|
||||
stateDataError,
|
||||
} = storeToRefs(useServerStore());
|
||||
const {
|
||||
available: osUpdateAvailable,
|
||||
availableWithRenewal: osUpdateAvailableWithRenewal,
|
||||
} = storeToRefs(updateOsStore);
|
||||
const { available: osUpdateAvailable, availableWithRenewal: osUpdateAvailableWithRenewal } =
|
||||
storeToRefs(updateOsStore);
|
||||
|
||||
const signInAction = computed(() => stateData.value.actions?.filter((act: { name: string; }) => act.name === 'signIn') ?? []);
|
||||
const signOutAction = computed(() => stateData.value.actions?.filter((act: { name: string; }) => act.name === 'signOut') ?? []);
|
||||
const signInAction = computed(
|
||||
() => stateData.value.actions?.filter((act: { name: string }) => act.name === 'signIn') ?? []
|
||||
);
|
||||
const signOutAction = computed(
|
||||
() => stateData.value.actions?.filter((act: { name: string }) => act.name === 'signOut') ?? []
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter out the renew action from the key actions so we can display it separately and link to the Tools > Registration page
|
||||
*/
|
||||
const filteredKeyActions = computed(() => keyActions.value?.filter(action => !['renew'].includes(action.name)));
|
||||
const filteredKeyActions = computed(() =>
|
||||
keyActions.value?.filter((action) => !['renew'].includes(action.name))
|
||||
);
|
||||
|
||||
const manageUnraidNetAccount = computed((): UserProfileLink => {
|
||||
return {
|
||||
external: true,
|
||||
click: () => { accountStore.manage(); },
|
||||
click: () => {
|
||||
accountStore.manage();
|
||||
},
|
||||
icon: UserIcon,
|
||||
text: props.t('Manage Unraid.net Account'),
|
||||
title: props.t('Manage Unraid.net Account in new tab'),
|
||||
@@ -86,13 +95,15 @@ const updateOsResponseModalOpenButton = computed((): UserProfileLink => {
|
||||
});
|
||||
const rebootDetectedButton = computed((): UserProfileLink => {
|
||||
return {
|
||||
href: rebootType.value === 'downgrade'
|
||||
? WEBGUI_TOOLS_DOWNGRADE.toString()
|
||||
: WEBGUI_TOOLS_UPDATE.toString(),
|
||||
href:
|
||||
rebootType.value === 'downgrade'
|
||||
? WEBGUI_TOOLS_DOWNGRADE.toString()
|
||||
: WEBGUI_TOOLS_UPDATE.toString(),
|
||||
icon: ExclamationTriangleIcon,
|
||||
text: rebootType.value === 'downgrade'
|
||||
? props.t('Reboot Required for Downgrade')
|
||||
: props.t('Reboot Required for Update'),
|
||||
text:
|
||||
rebootType.value === 'downgrade'
|
||||
? props.t('Reboot Required for Downgrade')
|
||||
: props.t('Reboot Required for Update'),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -111,15 +122,17 @@ const updateOsButton = computed((): UserProfileLink[] => {
|
||||
return btns;
|
||||
});
|
||||
|
||||
const links = computed(():UserProfileLink[] => {
|
||||
const links = computed((): UserProfileLink[] => {
|
||||
return [
|
||||
...(regUpdatesExpired.value
|
||||
? [{
|
||||
href: WEBGUI_TOOLS_REGISTRATION.toString(),
|
||||
icon: KeyIcon,
|
||||
text: props.t('OS Update Eligibility Expired'),
|
||||
title: props.t('Go to Tools > Registration to Learn More'),
|
||||
}]
|
||||
? [
|
||||
{
|
||||
href: WEBGUI_TOOLS_REGISTRATION.toString(),
|
||||
icon: KeyIcon,
|
||||
text: props.t('OS Update Eligibility Expired'),
|
||||
title: props.t('Go to Tools > Registration to Learn More'),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
// ensure we only show the update button when we don't have an error
|
||||
@@ -136,31 +149,36 @@ const links = computed(():UserProfileLink[] => {
|
||||
text: props.t('Go to Connect'),
|
||||
title: props.t('Opens Connect in new tab'),
|
||||
},
|
||||
...([manageUnraidNetAccount.value]),
|
||||
...[manageUnraidNetAccount.value],
|
||||
{
|
||||
href: WEBGUI_CONNECT_SETTINGS.toString(),
|
||||
icon: CogIcon,
|
||||
text: props.t('Settings'),
|
||||
title: props.t('Go to Connect plugin settings'),
|
||||
},
|
||||
...(signOutAction.value),
|
||||
...signOutAction.value,
|
||||
]
|
||||
: [
|
||||
...([manageUnraidNetAccount.value]),
|
||||
]
|
||||
),
|
||||
: [...[manageUnraidNetAccount.value]]),
|
||||
];
|
||||
});
|
||||
|
||||
const showErrors = computed(() => errors.value.length);
|
||||
const showConnectStatus = computed(() => !showErrors.value && !stateData.value.error && registered.value && connectPluginInstalled.value);
|
||||
const showKeyline = computed(() =>
|
||||
(showConnectStatus.value && (keyActions.value?.length || links.value.length)) ||
|
||||
unraidConnectWelcome.value
|
||||
const showConnectStatus = computed(
|
||||
() => !showErrors.value && !stateData.value.error && registered.value && connectPluginInstalled.value
|
||||
);
|
||||
const showKeyline = computed(
|
||||
() =>
|
||||
(showConnectStatus.value && (keyActions.value?.length || links.value.length)) ||
|
||||
unraidConnectWelcome.value
|
||||
);
|
||||
|
||||
const unraidConnectWelcome = computed(() => {
|
||||
if (connectPluginInstalled.value && !registered.value && !errors.value.length && !stateDataError.value) {
|
||||
if (
|
||||
connectPluginInstalled.value &&
|
||||
!registered.value &&
|
||||
!errors.value.length &&
|
||||
!stateDataError.value
|
||||
) {
|
||||
return {
|
||||
heading: props.t('Thank you for installing Connect!'),
|
||||
message: props.t('Sign In to your Unraid.net account to get started'),
|
||||
@@ -172,9 +190,16 @@ const unraidConnectWelcome = computed(() => {
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-y-8px min-w-300px max-w-350px">
|
||||
<header v-if="connectPluginInstalled" class="flex flex-col items-start justify-between mt-8px mx-8px">
|
||||
<header
|
||||
v-if="connectPluginInstalled"
|
||||
class="flex flex-col items-start justify-between mt-8px mx-8px"
|
||||
>
|
||||
<h2 class="text-18px leading-none flex flex-row gap-x-4px items-center justify-between">
|
||||
<BrandLogoConnect gradient-start="currentcolor" gradient-stop="currentcolor" class="text-foreground w-[120px]" />
|
||||
<BrandLogoConnect
|
||||
gradient-start="currentcolor"
|
||||
gradient-stop="currentcolor"
|
||||
class="text-foreground w-[120px]"
|
||||
/>
|
||||
<UpcBeta />
|
||||
</h2>
|
||||
<template v-if="unraidConnectWelcome">
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { BrandButton, BrandLoading } from '@unraid/ui';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUnraidApiStore } from '~/store/unraidApi';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { h } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { BrandButton, BrandLoading } from '@unraid/ui';
|
||||
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUnraidApiStore } from '~/store/unraidApi';
|
||||
|
||||
defineProps<{ t: ComposerTranslation }>();
|
||||
|
||||
const BrandLoadingIcon = () => h(BrandLoading, { variant: 'white' });
|
||||
|
||||
const { expireTime, connectPluginInstalled, state, stateData } = storeToRefs(useServerStore());
|
||||
const { unraidApiStatus, unraidApiRestartAction } = storeToRefs(useUnraidApiStore());
|
||||
|
||||
@@ -35,11 +40,7 @@ const showExpireTime = computed(
|
||||
<BrandButton
|
||||
class="w-full"
|
||||
:disabled="unraidApiStatus === 'connecting' || unraidApiStatus === 'restarting'"
|
||||
:icon="
|
||||
unraidApiStatus === 'restarting'
|
||||
? h(BrandLoading, { variant: 'white' })
|
||||
: unraidApiRestartAction?.icon
|
||||
"
|
||||
:icon="unraidApiStatus === 'restarting' ? BrandLoadingIcon : unraidApiRestartAction?.icon"
|
||||
:text="
|
||||
unraidApiStatus === 'restarting' ? t('Restarting unraid-api…') : t('Restart unraid-api')
|
||||
"
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
/**
|
||||
* @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 { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
import { CONNECT_DOCS } from '~/helpers/urls';
|
||||
|
||||
import type { UserProfilePromoFeature } from '~/types/userProfile';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import useInstallPlugin from '~/composables/installPlugin';
|
||||
import { CONNECT_DOCS } from '~/helpers/urls';
|
||||
import { usePromoStore } from '~/store/promo';
|
||||
import type { UserProfilePromoFeature } from '~/types/userProfile';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
export interface Props {
|
||||
open?: boolean;
|
||||
@@ -46,11 +45,11 @@ const features = ref<UserProfilePromoFeature[]>([
|
||||
},
|
||||
{
|
||||
title: 'Real-time Monitoring',
|
||||
copy: 'Get an overview of your server\'s state, storage space, apps and VMs status, and more.',
|
||||
copy: "Get an overview of your server's state, storage space, apps and VMs status, and more.",
|
||||
},
|
||||
{
|
||||
title: 'Customizable Dashboard Tiles',
|
||||
copy: 'Set custom server tiles how you like and automatically display your server\'s banner image on your Connect Dashboard.',
|
||||
copy: "Set custom server tiles how you like and automatically display your server's banner image on your Connect Dashboard.",
|
||||
},
|
||||
{
|
||||
title: 'License Management',
|
||||
@@ -65,7 +64,9 @@ const features = ref<UserProfilePromoFeature[]>([
|
||||
const staging = ref(false);
|
||||
|
||||
const connectPluginUrl = computed((): string => {
|
||||
const url = new URL(`https://sfo2.digitaloceanspaces.com/unraid-dl/unraid-api/dynamix.unraid.net${staging.value ? '.staging.plg' : '.plg'}`);
|
||||
const url = new URL(
|
||||
`https://sfo2.digitaloceanspaces.com/unraid-dl/unraid-api/dynamix.unraid.net${staging.value ? '.staging.plg' : '.plg'}`
|
||||
);
|
||||
return url.toString();
|
||||
});
|
||||
|
||||
@@ -102,8 +103,20 @@ const { install } = useInstallPlugin();
|
||||
<template #footer>
|
||||
<div class="w-full max-w-xs flex flex-col items-center gap-y-16px mx-auto">
|
||||
<SwitchGroup v-if="import.meta.env.DEV" as="div" class="flex items-center justify-center">
|
||||
<Switch v-model="staging" :class="[staging ? 'bg-indigo-600' : 'bg-gray-200', 'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2']">
|
||||
<span aria-hidden="true" :class="[staging ? 't-x-5' : 't-x-0', 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out']" />
|
||||
<Switch
|
||||
v-model="staging"
|
||||
:class="[
|
||||
staging ? 'bg-indigo-600' : 'bg-gray-200',
|
||||
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
:class="[
|
||||
staging ? 't-x-5' : 't-x-0',
|
||||
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||
]"
|
||||
/>
|
||||
</Switch>
|
||||
<SwitchLabel as="span" class="ml-3 text-12px">
|
||||
<span class="font-semibold">Install Staging</span>
|
||||
@@ -140,7 +153,7 @@ const { install } = useInstallPlugin();
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
/* Import unraid-ui globals first */
|
||||
@import '@unraid/ui/styles';
|
||||
@import '../../assets/main.css';
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { BrandLoading } from '@unraid/ui';
|
||||
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useTrialStore } from '~/store/trial';
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { request } from '~/composables/services/request';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
export interface Props {
|
||||
phpWanIp?: string;
|
||||
@@ -22,8 +20,12 @@ const fetchError = ref<string>('');
|
||||
const loading = ref(false);
|
||||
|
||||
const computedError = computed((): string => {
|
||||
if (!props.phpWanIp) { return t('DNS issue, unable to resolve wanip4.unraid.net'); }
|
||||
if (fetchError.value) { return fetchError.value; }
|
||||
if (!props.phpWanIp) {
|
||||
return t('DNS issue, unable to resolve wanip4.unraid.net');
|
||||
}
|
||||
if (fetchError.value) {
|
||||
return fetchError.value;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
@@ -36,9 +38,7 @@ watchEffect(async () => {
|
||||
if (!wanIp.value && props.phpWanIp) {
|
||||
loading.value = true;
|
||||
|
||||
const response = await request.url('https://wanip4.unraid.net/')
|
||||
.get()
|
||||
.text();
|
||||
const response = await request.url('https://wanip4.unraid.net/').get().text();
|
||||
|
||||
if (response) {
|
||||
loading.value = false;
|
||||
@@ -58,10 +58,19 @@ watchEffect(async () => {
|
||||
<template v-else>
|
||||
<span v-if="computedError" class="text-unraid-red font-semibold">{{ computedError }}</span>
|
||||
<template v-else>
|
||||
<span v-if="isRemoteAccess || phpWanIp === wanIp && !isRemoteAccess">{{ t('Remark: your WAN IPv4 is {0}', [wanIp]) }}</span>
|
||||
<span v-if="isRemoteAccess || (phpWanIp === wanIp && !isRemoteAccess)">{{
|
||||
t('Remark: your WAN IPv4 is {0}', [wanIp])
|
||||
}}</span>
|
||||
<span v-else class="inline-block w-1/2 whitespace-normal">
|
||||
{{ t("Remark: Unraid's WAN IPv4 {0} does not match your client's WAN IPv4 {1}.", [phpWanIp, wanIp]) }}
|
||||
{{ t('This may indicate a complex network that will not work with this Remote Access solution.') }}
|
||||
{{
|
||||
t("Remark: Unraid's WAN IPv4 {0} does not match your client's WAN IPv4 {1}.", [
|
||||
phpWanIp,
|
||||
wanIp,
|
||||
])
|
||||
}}
|
||||
{{
|
||||
t('This may indicate a complex network that will not work with this Remote Access solution.')
|
||||
}}
|
||||
{{ t('Ignore this message if you are currently connected via Remote Access or VPN.') }}
|
||||
</span>
|
||||
</template>
|
||||
@@ -69,7 +78,7 @@ watchEffect(async () => {
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
/* Import unraid-ui globals first */
|
||||
@import '@unraid/ui/styles';
|
||||
@import '../assets/main.css';
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
import { BrandButton } from '@unraid/ui';
|
||||
|
||||
import type { Server } from '~/types/server';
|
||||
|
||||
import ActivationSteps from '~/components/Activation/Steps.vue';
|
||||
import { useActivationCodeStore } from '~/store/activationCode';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import type { Server } from '~/types/server';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -29,14 +29,18 @@ const title = computed<string>(() =>
|
||||
);
|
||||
|
||||
const description = computed<string>(() =>
|
||||
t(`First, you’ll create your device’s login credentials, then you’ll activate your Unraid license—your device’s operating system (OS).`)
|
||||
t(
|
||||
`First, you’ll create your device’s login credentials, then you’ll activate your Unraid license—your device’s operating system (OS).`
|
||||
)
|
||||
);
|
||||
|
||||
const showModal = ref<boolean>(true);
|
||||
const dropdownHide = () => { showModal.value = false; };
|
||||
const dropdownHide = () => {
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
/**
|
||||
/**
|
||||
* A necessary workaround for how the webgui handles font-size.
|
||||
* There's not a shared CSS file between /login and any of the authenticated webgui pages.
|
||||
* Which has lead to font-size differences.
|
||||
@@ -60,9 +64,11 @@ onBeforeMount(() => {
|
||||
throw new Error('Server data not present');
|
||||
}
|
||||
|
||||
if (typeof props.server === 'object') { // Handles the testing dev Vue component
|
||||
if (typeof props.server === 'object') {
|
||||
// Handles the testing dev Vue component
|
||||
serverStore.setServer(props.server);
|
||||
} else if (typeof props.server === 'string') { // Handle web component
|
||||
} else if (typeof props.server === 'string') {
|
||||
// Handle web component
|
||||
const parsedServerProp = JSON.parse(props.server);
|
||||
serverStore.setServer(parsedServerProp);
|
||||
}
|
||||
@@ -93,10 +99,7 @@ onBeforeMount(() => {
|
||||
|
||||
<template #footer>
|
||||
<div class="w-full flex gap-8px justify-center mx-auto">
|
||||
<BrandButton
|
||||
:text="t('Create a password')"
|
||||
@click="dropdownHide"
|
||||
/>
|
||||
<BrandButton :text="t('Create a password')" @click="dropdownHide" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -108,8 +111,9 @@ onBeforeMount(() => {
|
||||
</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 {
|
||||
@@ -158,6 +162,4 @@ onBeforeMount(() => {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { Primitive, type PrimitiveProps } from 'radix-vue'
|
||||
import { type ButtonVariants, buttonVariants } from '.'
|
||||
import { cn } from '~/components/shadcn/utils'
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
variant?: ButtonVariants['variant']
|
||||
size?: ButtonVariants['size']
|
||||
class?: HTMLAttributes['class']
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: 'button',
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
class: undefined,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||
>
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
@@ -1,32 +0,0 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
export { default as Button } from './Button.vue';
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'px-4 py-2',
|
||||
xs: 'h-7 px-2',
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-10 w-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
||||
@@ -1,14 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownMenuRoot, type DropdownMenuRootEmits, type DropdownMenuRootProps, useForwardPropsEmits } from 'radix-vue'
|
||||
|
||||
const props = defineProps<DropdownMenuRootProps>()
|
||||
const emits = defineEmits<DropdownMenuRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuRoot v-bind="forwarded">
|
||||
<slot />
|
||||
</DropdownMenuRoot>
|
||||
</template>
|
||||
@@ -1,40 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
DropdownMenuCheckboxItem,
|
||||
type DropdownMenuCheckboxItemEmits,
|
||||
type DropdownMenuCheckboxItemProps,
|
||||
DropdownMenuItemIndicator,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { Check } from 'lucide-vue-next'
|
||||
import { cn } from '~/components/shadcn/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuCheckboxItem
|
||||
v-bind="forwarded"
|
||||
:class=" cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuItemIndicator>
|
||||
<Check class="w-4 h-4" />
|
||||
</DropdownMenuItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</DropdownMenuCheckboxItem>
|
||||
</template>
|
||||
@@ -1,39 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
DropdownMenuContent,
|
||||
type DropdownMenuContentEmits,
|
||||
type DropdownMenuContentProps,
|
||||
DropdownMenuPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '~/components/shadcn/utils'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes['class'] }>(),
|
||||
{
|
||||
sideOffset: 4,
|
||||
class: undefined,
|
||||
},
|
||||
)
|
||||
const emits = defineEmits<DropdownMenuContentEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent
|
||||
v-bind="forwarded"
|
||||
:class="cn('z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</template>
|
||||
@@ -1,11 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownMenuGroup, type DropdownMenuGroupProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<DropdownMenuGroupProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuGroup v-bind="props">
|
||||
<slot />
|
||||
</DropdownMenuGroup>
|
||||
</template>
|
||||
@@ -1,28 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { DropdownMenuItem, type DropdownMenuItemProps, useForwardProps } from 'radix-vue'
|
||||
import { cn } from '~/components/shadcn/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuItemProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuItem
|
||||
v-bind="forwardedProps"
|
||||
:class="cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
inset && 'pl-8',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuItem>
|
||||
</template>
|
||||
@@ -1,24 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { DropdownMenuLabel, type DropdownMenuLabelProps, useForwardProps } from 'radix-vue'
|
||||
import { cn } from '~/components/shadcn/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuLabel
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuLabel>
|
||||
</template>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
DropdownMenuRadioGroup,
|
||||
type DropdownMenuRadioGroupEmits,
|
||||
type DropdownMenuRadioGroupProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
|
||||
const props = defineProps<DropdownMenuRadioGroupProps>()
|
||||
const emits = defineEmits<DropdownMenuRadioGroupEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuRadioGroup v-bind="forwarded">
|
||||
<slot />
|
||||
</DropdownMenuRadioGroup>
|
||||
</template>
|
||||
@@ -1,41 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuRadioItem,
|
||||
type DropdownMenuRadioItemEmits,
|
||||
type DropdownMenuRadioItemProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { Circle } from 'lucide-vue-next'
|
||||
import { cn } from '~/components/shadcn/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const emits = defineEmits<DropdownMenuRadioItemEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuRadioItem
|
||||
v-bind="forwarded"
|
||||
:class="cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuItemIndicator>
|
||||
<Circle class="h-2 w-2 fill-current" />
|
||||
</DropdownMenuItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</DropdownMenuRadioItem>
|
||||
</template>
|
||||
@@ -1,22 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
DropdownMenuSeparator,
|
||||
type DropdownMenuSeparatorProps,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '~/components/shadcn/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuSeparatorProps & {
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSeparator v-bind="delegatedProps" :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" />
|
||||
</template>
|
||||
@@ -1,14 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/components/shadcn/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="cn('ml-auto text-xs tracking-widest opacity-60', props.class)">
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
DropdownMenuSub,
|
||||
type DropdownMenuSubEmits,
|
||||
type DropdownMenuSubProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
|
||||
const props = defineProps<DropdownMenuSubProps>()
|
||||
const emits = defineEmits<DropdownMenuSubEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSub v-bind="forwarded">
|
||||
<slot />
|
||||
</DropdownMenuSub>
|
||||
</template>
|
||||
@@ -1,30 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
DropdownMenuSubContent,
|
||||
type DropdownMenuSubContentEmits,
|
||||
type DropdownMenuSubContentProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '~/components/shadcn/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DropdownMenuSubContentEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSubContent
|
||||
v-bind="forwarded"
|
||||
:class="cn('z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuSubContent>
|
||||
</template>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user