mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-27 23:46:37 +01:00
feat: begin upgrading deps, still very buggy
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -4,7 +4,7 @@
|
||||
},
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"package.json": "package-lock.json, yarn.lock, .eslintrc.js, tsconfig.json, .prettierrc, .editorconfig, pnpm-lock.yaml, postcss.config.js, tailwind.config.js",
|
||||
"package.json": "package-lock.json, yarn.lock, eslint.config.mjs, tsconfig.json, .prettierrc, .editorconfig, pnpm-lock.yaml, postcss.config.js, tailwind.config.js",
|
||||
"docker-compose.yml": "Dockerfile, .dockerignore, docker-compose.dev.yml, docker-compose.yml",
|
||||
"README.md": "LICENSE, SECURITY.md"
|
||||
},
|
||||
@@ -22,6 +22,7 @@
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
"eslint.format.enable": true,
|
||||
"eslint.useFlatConfig": true,
|
||||
"css.validate": false,
|
||||
"tailwindCSS.includeLanguages": {
|
||||
"vue": "html",
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/essential",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"@nuxtjs/eslint-config-typescript",
|
||||
"plugin:vue/vue3-recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:tailwindcss/recommended",
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
parser: "@typescript-eslint/parser",
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["vue", "@typescript-eslint"],
|
||||
rules: {
|
||||
"no-console": 0,
|
||||
"no-unused-vars": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/no-setup-props-destructure": 0,
|
||||
"vue/no-multiple-template-root": 0,
|
||||
"vue/no-v-model-argument": 0,
|
||||
"vue/no-v-html": 0,
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"@typescript-eslint/ban-ts-comment": 0,
|
||||
"tailwindcss/no-custom-classname": "warn",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
ignoreRestSiblings: true,
|
||||
destructuredArrayIgnorePattern: "_",
|
||||
caughtErrors: "none",
|
||||
},
|
||||
],
|
||||
"prettier/prettier": [
|
||||
"warn",
|
||||
{
|
||||
arrowParens: "avoid",
|
||||
semi: true,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
vueIndentScriptAndStyle: true,
|
||||
singleQuote: false,
|
||||
trailingComma: "es5",
|
||||
printWidth: 120,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<NuxtLayout>
|
||||
<Html :lang="locale" :data-theme="theme || 'homebox'" />
|
||||
<Link rel="icon" type="image/svg" href="/favicon.svg"></Link>
|
||||
<Link rel="icon" type="image/svg" href="/favicon.svg" />
|
||||
<Link rel="apple-touch-icon" href="/apple-touch-icon.png" size="180x180" />
|
||||
<Link rel="mask-icon" href="/mask-icon.svg" color="#5b7f67" />
|
||||
<Meta name="theme-color" content="#5b7f67" />
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
import { useMediaQuery } from "@vueuse/core";
|
||||
import type { DialogID } from "@/components/ui/dialog-provider/utils";
|
||||
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from "@/components/ui/drawer";
|
||||
import { Dialog, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Dialog, DialogScrollContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)");
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<!-- eslint-disable-next-line tailwindcss/no-custom-classname -->
|
||||
<video ref="video" class="aspect-video w-full rounded-lg bg-muted shadow" poster="data:image/gif,AAAA"></video>
|
||||
<video ref="video" class="aspect-video w-full rounded-lg bg-muted shadow" poster="data:image/gif,AAAA" />
|
||||
<div class="mt-4">
|
||||
<Select v-model="selectedSource">
|
||||
<SelectTrigger class="w-full">
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
>
|
||||
<div :data-theme="theme.value" class="w-full cursor-pointer bg-background-accent text-foreground">
|
||||
<div class="grid grid-cols-5 grid-rows-3">
|
||||
<div class="col-start-1 row-start-1 bg-background"></div>
|
||||
<div class="col-start-1 row-start-2 bg-sidebar"></div>
|
||||
<div class="col-start-1 row-start-3 bg-background-accent"></div>
|
||||
<div class="col-start-1 row-start-1 bg-background" />
|
||||
<div class="col-start-1 row-start-2 bg-sidebar" />
|
||||
<div class="col-start-1 row-start-3 bg-background-accent" />
|
||||
<div class="col-span-4 col-start-2 row-span-3 row-start-1 flex flex-col gap-1 bg-background p-2">
|
||||
<div class="font-bold">{{ theme.label }}</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<CardHeader v-if="$slots.title" class="px-4 py-5 sm:px-6">
|
||||
<component :is="collapsable ? 'button' : 'div'" v-on="collapsable ? { click: toggle } : {}">
|
||||
<h3 class="flex items-center text-lg font-medium leading-6">
|
||||
<slot name="title"></slot>
|
||||
<slot name="title" />
|
||||
<template v-if="collapsable">
|
||||
<span class="ml-2 transition-transform" :class="{ 'rotate-180': collapsed }">
|
||||
<MdiChevronDown class="size-6" />
|
||||
@@ -13,10 +13,10 @@
|
||||
</component>
|
||||
<div>
|
||||
<p v-if="$slots.subtitle" class="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
<slot name="subtitle"></slot>
|
||||
<slot name="subtitle" />
|
||||
</p>
|
||||
<template v-if="$slots['title-actions']">
|
||||
<slot name="title-actions"></slot>
|
||||
<slot name="title-actions" />
|
||||
</template>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
@@ -2,24 +2,24 @@
|
||||
<div class="flex flex-col gap-10 py-6 md:flex-row">
|
||||
<div class="flex-1">
|
||||
<h4 class="mb-1 text-lg font-semibold">
|
||||
<slot name="title"></slot>
|
||||
<slot name="title" />
|
||||
</h4>
|
||||
<p class="text-sm">
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<template v-if="to">
|
||||
<NuxtLink :to="to" :class="buttonVariants({ size: 'lg' })" class="min-w-52 grow">
|
||||
<slot name="button">
|
||||
<slot name="title"></slot>
|
||||
<slot name="title" />
|
||||
</slot>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button class="min-w-52 grow" size="lg" @click="$emit('action')">
|
||||
<slot name="button">
|
||||
<slot name="title"></slot>
|
||||
<slot name="title" />
|
||||
</slot>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
<MdiClose class="size-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<input :id="id" ref="colorInput" v-model="value" type="color" class="sr-only" tabindex="-1" />
|
||||
<input :id="id" ref="colorInput" v-model="value" type="color" class="sr-only" tabindex="-1" >
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
|
||||
@@ -149,7 +149,7 @@
|
||||
<MdiClose class="size-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<input :id="id" ref="colorInput" v-model="value" type="color" class="sr-only" tabindex="-1" />
|
||||
<input :id="id" ref="colorInput" v-model="value" type="color" class="sr-only" tabindex="-1" >
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<FormTextField v-model="value" :placeholder="localizedPlaceholder" :label="localizedLabel" :type="inputType">
|
||||
</FormTextField>
|
||||
<FormTextField v-model="value" :placeholder="localizedPlaceholder" :label="localizedLabel" :type="inputType" />
|
||||
<TooltipProvider :delay-duration="0">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div v-if="!inline" class="flex w-full flex-col gap-1.5">
|
||||
<Label :for="id" class="flex w-full px-1">
|
||||
<span>{{ label }}</span>
|
||||
<span class="grow"></span>
|
||||
<span class="grow" />
|
||||
<span :class="{ 'text-destructive': isLengthInvalid }">
|
||||
{{ lengthIndicator }}
|
||||
</span>
|
||||
@@ -18,7 +18,7 @@
|
||||
<div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
|
||||
<Label :for="id" class="flex w-full px-1 py-2">
|
||||
<span>{{ label }}</span>
|
||||
<span class="grow"></span>
|
||||
<span class="grow" />
|
||||
<span :class="{ 'text-destructive': isLengthInvalid }">
|
||||
{{ lengthIndicator }}
|
||||
</span>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div v-if="!inline" class="flex w-full flex-col gap-1.5">
|
||||
<Label :for="id" class="flex w-full px-1">
|
||||
<span> {{ label }} </span>
|
||||
<span class="grow"></span>
|
||||
<span class="grow" />
|
||||
<span
|
||||
:class="{
|
||||
'text-destructive':
|
||||
@@ -26,7 +26,7 @@
|
||||
<div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
|
||||
<Label class="flex w-full px-1 py-2" :for="id">
|
||||
<span> {{ label }} </span>
|
||||
<span class="grow"></span>
|
||||
<span class="grow" />
|
||||
<span
|
||||
:class="{
|
||||
'text-destructive':
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
>
|
||||
<template v-if="h.type === 'name'">
|
||||
<div class="flex items-center space-x-4">
|
||||
<img :src="p.imageBase64" class="w-16 rounded object-fill shadow-sm" alt="Product's photo" />
|
||||
<img :src="p.imageBase64" class="w-16 rounded object-fill shadow-sm" alt="Product's photo" >
|
||||
<span class="text-sm font-medium">
|
||||
{{ p.item.name }}
|
||||
</span>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Card class="overflow-hidden">
|
||||
<NuxtLink :to="`/item/${item.id}`">
|
||||
<div class="relative h-[200px]">
|
||||
<img v-if="imageUrl" class="h-[200px] w-full object-cover shadow-md" loading="lazy" :src="imageUrl" alt="" />
|
||||
<img v-if="imageUrl" class="h-[200px] w-full object-cover shadow-md" loading="lazy" :src="imageUrl" alt="" >
|
||||
<div class="absolute inset-x-1 bottom-1">
|
||||
<Badge class="text-wrap bg-secondary text-secondary-foreground hover:bg-secondary/70 hover:underline">
|
||||
<NuxtLink v-if="item.location" :to="`/location/${item.location.id}`">
|
||||
@@ -32,7 +32,7 @@
|
||||
{{ $t("global.archived") }}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<div class="grow"></div>
|
||||
<div class="grow" />
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Badge>
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
:src="photo.fileBase64"
|
||||
class="w-full rounded object-fill shadow-sm"
|
||||
:alt="$t('components.item.create_modal.uploaded')"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<TooltipProvider class="flex gap-2" :delay-duration="0">
|
||||
@@ -190,6 +190,10 @@
|
||||
import { useDialog, useDialogHotkey } from "~/components/ui/dialog-provider";
|
||||
import LabelSelector from "~/components/Label/Selector.vue";
|
||||
import ItemSelector from "~/components/Item/Selector.vue";
|
||||
import { TooltipProvider, Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
|
||||
import LocationSelector from "~/components/Location/Selector.vue";
|
||||
import FormTextField from "~/components/Form/TextField.vue";
|
||||
import FormTextArea from "~/components/Form/TextArea.vue";
|
||||
|
||||
interface PhotoPreview {
|
||||
photoName: string;
|
||||
|
||||
@@ -75,8 +75,8 @@
|
||||
<Dialog :dialog-id="DialogID.ItemImage">
|
||||
<DialogContent class="w-auto border-transparent bg-transparent p-0" disable-close>
|
||||
<picture>
|
||||
<source :srcset="image.originalSrc" :type="image.originalType" />
|
||||
<img :src="image.thumbnailSrc" alt="attachment image" />
|
||||
<source :srcset="image.originalSrc" :type="image.originalType" >
|
||||
<img :src="image.thumbnailSrc" alt="attachment image" >
|
||||
</picture>
|
||||
<Button variant="destructive" size="icon" class="absolute right-[84px] top-1" @click="deleteAttachment">
|
||||
<MdiDelete />
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
import { Label } from "~/components/ui/label";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { useId } from "#imports";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
'hover:bg-accent hover:text-accent-foreground': hasChildren,
|
||||
}"
|
||||
>
|
||||
<div v-if="!hasChildren" class="size-6"></div>
|
||||
<div v-if="!hasChildren" class="size-6" />
|
||||
<div v-else class="group/node relative size-6" :data-swap="openRef">
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-center transition-transform duration-300 group-data-[swap=true]/node:rotate-90"
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
</section>
|
||||
<section>
|
||||
<!-- begin -->
|
||||
<MaintenanceEditModal ref="maintenanceEditModal" @changed="refreshList"></MaintenanceEditModal>
|
||||
<MaintenanceEditModal ref="maintenanceEditModal" @changed="refreshList" />
|
||||
<div class="container space-y-6">
|
||||
<BaseCard v-for="e in maintenanceDataList" :key="e.id">
|
||||
<BaseSectionHeader class="border-b p-6">
|
||||
|
||||
@@ -19,6 +19,16 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDialog } from "./ui/dialog-provider";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
|
||||
const { text, isRevealed, confirm, cancel } = useConfirm();
|
||||
const { addAlert, removeAlert } = useDialog();
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
<Checkbox :model-value="true" @update:model-value="_ => (selected = selected.filter(s => s.id !== v.id))" />
|
||||
</Label>
|
||||
<hr v-if="selected.length > 0" />
|
||||
<hr v-if="selected.length > 0" >
|
||||
<Label
|
||||
v-for="v in unselected"
|
||||
:key="v.id"
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
{{ $t("components.global.label_maker.confirm_description") }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<img :src="getLabelUrl(false)" />
|
||||
<img :src="getLabelUrl(false)" >
|
||||
<DialogFooter>
|
||||
<ButtonGroup>
|
||||
<Button v-if="status?.labelPrinting || false" type="submit" :disabled="serverPrinting" @click="serverPrint">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<template>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="markdown text-wrap break-words" v-html="raw"></div>
|
||||
<div class="markdown text-wrap break-words" v-html="raw" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
{{ $t("components.global.page_qr_code.page_url") }}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<img :src="getQRCodeUrl()" />
|
||||
<img :src="getQRCodeUrl()" >
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<template>
|
||||
<div class="max-w-full"></div>
|
||||
<div class="max-w-full" />
|
||||
</template>
|
||||
|
||||
@@ -34,7 +34,6 @@ export interface IAuthContext {
|
||||
}
|
||||
|
||||
class AuthContext implements IAuthContext {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
private static _instance?: AuthContext;
|
||||
|
||||
private static readonly cookieTokenKey = "hb.auth.session";
|
||||
|
||||
@@ -44,7 +44,6 @@ export function useBreakpoints(): Breakpoints {
|
||||
}
|
||||
|
||||
class ThemeObserver {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
private static instance?: ThemeObserver;
|
||||
private readonly observer: MutationObserver;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ComputedRef } from "vue";
|
||||
import { type DaisyTheme } from "~~/lib/data/themes";
|
||||
import type { DaisyTheme } from "~~/lib/data/themes";
|
||||
|
||||
export interface UseTheme {
|
||||
theme: ComputedRef<DaisyTheme>;
|
||||
|
||||
58
frontend/eslint.config.mjs
Normal file
58
frontend/eslint.config.mjs
Normal file
@@ -0,0 +1,58 @@
|
||||
import withNuxt from './.nuxt/eslint.config.mjs';
|
||||
|
||||
import tailwind from 'eslint-plugin-tailwindcss';
|
||||
import prettier from 'eslint-plugin-prettier';
|
||||
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const gitignorePath = fileURLToPath(new URL('../.gitignore', import.meta.url));
|
||||
|
||||
export default withNuxt([
|
||||
includeIgnoreFile(gitignorePath, 'Imported ../.gitignore patterns'),
|
||||
...tailwind.configs['flat/recommended'],
|
||||
{
|
||||
plugins: {
|
||||
prettier,
|
||||
},
|
||||
rules: {
|
||||
'vue/no-undef-components': [
|
||||
'error',
|
||||
{
|
||||
ignorePatterns: [],
|
||||
},
|
||||
],
|
||||
'no-console': 0,
|
||||
'no-unused-vars': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-setup-props-destructure': 0,
|
||||
'vue/no-multiple-template-root': 0,
|
||||
'vue/no-v-model-argument': 0,
|
||||
'vue/no-v-html': 0,
|
||||
'tailwind/no-custom-classname': 'warn',
|
||||
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
ignoreRestSiblings: true,
|
||||
destructuredArrayIgnorePattern: '_',
|
||||
caughtErrors: 'none',
|
||||
},
|
||||
],
|
||||
|
||||
'prettier/prettier': [
|
||||
'warn',
|
||||
{
|
||||
arrowParens: 'avoid',
|
||||
semi: true,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
vueIndentScriptAndStyle: true,
|
||||
singleQuote: false,
|
||||
trailingComma: 'es5',
|
||||
printWidth: 120,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
2
frontend/global.d.ts
vendored
2
frontend/global.d.ts
vendored
@@ -1 +1 @@
|
||||
/// <reference types="unplugin-icons/types/vue" />
|
||||
/// <reference types="unplugin-icons/types/vue" />
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
up the tree
|
||||
-->
|
||||
<ModalConfirm />
|
||||
<AppOutdatedModal v-if="status" :status="status" />
|
||||
<OutdatedModal v-if="status" :status="status" />
|
||||
<ItemCreateModal />
|
||||
<LabelCreateModal />
|
||||
<LocationCreateModal />
|
||||
@@ -124,7 +124,7 @@
|
||||
<AppHeaderText class="h-6" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="sm:grow"></div>
|
||||
<div class="sm:grow" />
|
||||
<div class="flex h-1/2 grow items-center justify-end gap-2 sm:h-auto">
|
||||
<Input
|
||||
v-model:model-value="search"
|
||||
@@ -146,8 +146,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
<div class="grow"></div>
|
||||
<slot />
|
||||
<div class="grow" />
|
||||
|
||||
<footer v-if="status" class="bottom-0 w-full pb-4 text-center">
|
||||
<p class="text-center text-sm">
|
||||
@@ -157,9 +157,9 @@
|
||||
$t('global.footer.version_link', { version: status.build.version, build: status.build.commit })
|
||||
)
|
||||
"
|
||||
></span>
|
||||
/>
|
||||
~
|
||||
<span v-html="DOMPurify.sanitize($t('global.footer.api_link'))"></span>
|
||||
<span v-html="DOMPurify.sanitize($t('global.footer.api_link'))" />
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -186,6 +186,7 @@
|
||||
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarProvider,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
@@ -211,6 +212,9 @@
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { toast } from "@/components/ui/sonner";
|
||||
import { DialogID, type NoParamDialogIDs, type OptionalDialogIDs } from "~/components/ui/dialog-provider/utils";
|
||||
import ModalConfirm from "~/components/ModalConfirm.vue";
|
||||
import OutdatedModal from "~/components/App/OutdatedModal.vue";
|
||||
import ItemCreateModal from "~/components/Item/CreateModal.vue";
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const username = computed(() => authCtx.user?.name || "User");
|
||||
|
||||
@@ -82,7 +82,7 @@ export class Requests {
|
||||
const token = this.token();
|
||||
if (token !== "" && payload.headers !== undefined) {
|
||||
// @ts-expect-error - we know that the header is there
|
||||
payload.headers["Authorization"] = token; // eslint-disable-line dot-notation
|
||||
payload.headers["Authorization"] = token;
|
||||
}
|
||||
|
||||
if (this.methodSupportsBody(method)) {
|
||||
|
||||
@@ -4,6 +4,10 @@ import { defineNuxtConfig } from "nuxt/config";
|
||||
export default defineNuxtConfig({
|
||||
ssr: false,
|
||||
|
||||
components: {
|
||||
dirs: [],
|
||||
},
|
||||
|
||||
build: {
|
||||
transpile: ["vue-i18n"],
|
||||
},
|
||||
@@ -15,8 +19,13 @@ export default defineNuxtConfig({
|
||||
"@vite-pwa/nuxt",
|
||||
"unplugin-icons/nuxt",
|
||||
"shadcn-nuxt",
|
||||
"@nuxt/eslint",
|
||||
],
|
||||
|
||||
eslint: {
|
||||
config: {},
|
||||
},
|
||||
|
||||
nitro: {
|
||||
devProxy: {
|
||||
"/api": {
|
||||
|
||||
@@ -5,78 +5,77 @@
|
||||
"dev": "nuxt dev",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare",
|
||||
"lint": "eslint --ext \".ts,.js,.vue\" --ignore-path ../.gitignore --ignore-pattern \"components/ui\" .",
|
||||
"lint:fix": "eslint --ext \".ts,.js,.vue\" --ignore-path ../.gitignore --ignore-pattern \"components/ui\" . --fix",
|
||||
"lint:ci": "eslint --ext \".ts,.js,.vue\" --ignore-path ../.gitignore --ignore-pattern \"components/ui\" . --max-warnings 1",
|
||||
"typecheck": "pnpm vue-tsc --noEmit",
|
||||
"lint": "eslint --ext \".ts,.js,.vue\" --ignore-pattern \"components/ui\" .",
|
||||
"lint:fix": "eslint --ext \".ts,.js,.vue\" --ignore-pattern \"components/ui\" . --fix",
|
||||
"lint:ci": "eslint --ext \".ts,.js,.vue\" --ignore-pattern \"components/ui\" . --max-warnings 1",
|
||||
"typecheck": "pnpm nuxi typecheck --noEmit",
|
||||
"test:ci": "TEST_SHUTDOWN_API_SERVER=true vitest --run --config ./test/vitest.config.ts",
|
||||
"test:local": "TEST_SHUTDOWN_API_SERVER=false && vitest --run --config ./test/vitest.config.ts",
|
||||
"test:watch": " TEST_SHUTDOWN_API_SERVER=false vitest --config ./test/vitest.config.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@eslint/compat": "^1.3.2",
|
||||
"@faker-js/faker": "^10.0.0",
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@types/markdown-it": "^14.1.0",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
||||
"@nuxt/eslint": "^1.9.0",
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vite-pwa/nuxt": "^0.5.0",
|
||||
"@vue/runtime-core": "^3.5.13",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-tailwindcss": "^3.18.0",
|
||||
"eslint-plugin-vue": "^9.33.0",
|
||||
"@vite-pwa/nuxt": "^1.0.4",
|
||||
"@vue/runtime-core": "^3.5.20",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-tailwindcss": "^3.18.2",
|
||||
"globals": "^16.3.0",
|
||||
"h3": "^1.7.1",
|
||||
"intl-messageformat": "^10.7.16",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"nuxt": "3.12.4",
|
||||
"prettier": "^3.5.3",
|
||||
"shadcn-nuxt": "0.11.3",
|
||||
"typescript": "5.6.2",
|
||||
"unplugin-icons": "^0.18.5",
|
||||
"nuxt": "4.0.3",
|
||||
"prettier": "^3.6.2",
|
||||
"shadcn-nuxt": "2.2.0",
|
||||
"typescript": "5.9.2",
|
||||
"unplugin-icons": "^22.2.0",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vitest": "^1.6.1",
|
||||
"vue-i18n": "^9.14.4",
|
||||
"vue-tsc": "2.1.6"
|
||||
"vitest": "^3.2.4",
|
||||
"vue-i18n": "^11.1.11",
|
||||
"vue-tsc": "3.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdit/plugin-img-size": "^0.22.2",
|
||||
"@nuxtjs/color-mode": "^3.5.2",
|
||||
"@nuxtjs/tailwindcss": "^6.13.2",
|
||||
"@pinia/nuxt": "^0.5.5",
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
"@pinia/nuxt": "^0.11.2",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@vuepic/vue-datepicker": "^8.8.1",
|
||||
"@vueuse/core": "^12.8.2",
|
||||
"@vueuse/nuxt": "^10.11.1",
|
||||
"@vueuse/router": "^10.11.1",
|
||||
"@vuepic/vue-datepicker": "^11.0.2",
|
||||
"@vueuse/core": "^13.8.0",
|
||||
"@vueuse/nuxt": "^13.8.0",
|
||||
"@vueuse/router": "^13.8.0",
|
||||
"@zxing/library": "^0.21.3",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"dompurify": "^3.2.5",
|
||||
"date-fns": "^4.1.0",
|
||||
"dompurify": "^3.2.6",
|
||||
"fuzzysort": "^3.1.0",
|
||||
"h3": "^1.15.1",
|
||||
"h3": "^1.15.4",
|
||||
"http-proxy": "^1.18.1",
|
||||
"lucide-vue-next": "^0.474.0",
|
||||
"lucide-vue-next": "^0.542.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"pinia": "^2.3.1",
|
||||
"postcss": "^8.5.3",
|
||||
"reka-ui": "^2.2.0",
|
||||
"semver": "^7.7.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"pinia": "^3.0.3",
|
||||
"postcss": "^8.5.6",
|
||||
"reka-ui": "^2.5.0",
|
||||
"semver": "^7.7.2",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vaul-vue": "^0.4.1",
|
||||
"vite": "^6.3.5",
|
||||
"vue": "3.4.8",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-sonner": "^1.3.2"
|
||||
"vite": "^7.1.3",
|
||||
"vue": "3.5.20",
|
||||
"vue-router": "^4.5.1",
|
||||
"vue-sonner": "^2.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,16 @@ type StatCard = {
|
||||
export function statCardData(api: UserClient) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const { data: statistics } = useAsyncData(async () => {
|
||||
const { data } = await api.stats.group();
|
||||
return data;
|
||||
});
|
||||
const { data: statistics } = useAsyncData(
|
||||
"statistics",
|
||||
async () => {
|
||||
const { data } = await api.stats.group();
|
||||
return data;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
return computed(() => {
|
||||
return [
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import type { UserClient } from "~~/lib/api/user";
|
||||
|
||||
export function itemsTable(api: UserClient) {
|
||||
const { data: items, refresh } = useAsyncData(async () => {
|
||||
const { data } = await api.items.getAll({
|
||||
page: 1,
|
||||
pageSize: 5,
|
||||
orderBy: "createdAt",
|
||||
});
|
||||
return data.items;
|
||||
});
|
||||
const { data: items, refresh } = useAsyncData(
|
||||
"items",
|
||||
async () => {
|
||||
const { data } = await api.items.getAll({
|
||||
page: 1,
|
||||
pageSize: 5,
|
||||
orderBy: "createdAt",
|
||||
});
|
||||
return data.items;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
onServerEvent(ServerEvent.ItemMutation, () => {
|
||||
console.log("item mutation");
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
<path
|
||||
fill-opacity="1"
|
||||
d="M0,32L80,69.3C160,107,320,181,480,181.3C640,181,800,107,960,117.3C1120,128,1280,224,1360,272L1440,320L1440,0L1360,0C1280,0,1120,0,960,0C800,0,640,0,480,0C320,0,160,0,80,0L0,0Z"
|
||||
></path>
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -645,7 +645,7 @@
|
||||
</header>
|
||||
<Separator v-if="item.description" />
|
||||
<div v-if="item.description" class="prose max-w-full p-1">
|
||||
<Markdown class="text-base" :source="item.description"> </Markdown>
|
||||
<Markdown class="text-base" :source="item.description" />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -711,8 +711,8 @@
|
||||
<div class="scroll-bg container mx-auto flex max-h-[500px] flex-wrap gap-2 overflow-y-scroll border-t p-4">
|
||||
<button v-for="(img, i) in photos" :key="i" @click="openImageDialog(img, item.id)">
|
||||
<picture>
|
||||
<source :srcset="img.originalSrc" :type="img.originalType" />
|
||||
<img class="max-h-[200px] rounded" :src="img.thumbnailSrc" alt="attachment image" />
|
||||
<source :srcset="img.originalSrc" :type="img.originalType" >
|
||||
<img class="max-h-[200px] rounded" :src="img.thumbnailSrc" alt="attachment image" >
|
||||
</picture>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (locations && data.location?.id) {
|
||||
if (locations.value && data.location?.id) {
|
||||
// @ts-expect-error - we know the locations is valid
|
||||
const location = locations.value.find(l => l.id === data.location.id);
|
||||
if (location) {
|
||||
@@ -679,7 +679,7 @@
|
||||
class="grid h-24 w-full place-content-center border-2 border-dashed border-primary"
|
||||
@click="clickUpload"
|
||||
>
|
||||
<input ref="refAttachmentInput" hidden type="file" @change="uploadImage" />
|
||||
<input ref="refAttachmentInput" hidden type="file" @change="uploadImage" >
|
||||
<p>{{ $t("items.drag_and_drop") }}</p>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
|
||||
<template>
|
||||
<BaseContainer class="flex flex-col gap-8">
|
||||
<MaintenanceListView :current-item-id="props.item.id"></MaintenanceListView>
|
||||
<MaintenanceListView :current-item-id="props.item.id" />
|
||||
</BaseContainer>
|
||||
</template>
|
||||
|
||||
@@ -398,27 +398,27 @@
|
||||
<PopoverContent class="z-40 flex flex-col gap-2">
|
||||
<Label class="flex cursor-pointer items-center">
|
||||
<Switch v-model="includeArchived" class="ml-auto" />
|
||||
<div class="grow"></div>
|
||||
<div class="grow" />
|
||||
{{ $t("items.include_archive") }}
|
||||
</Label>
|
||||
<Label class="flex cursor-pointer items-center">
|
||||
<Switch v-model="fieldSelector" class="ml-auto" />
|
||||
<div class="grow"></div>
|
||||
<div class="grow" />
|
||||
{{ $t("items.field_selector") }}
|
||||
</Label>
|
||||
<Label class="flex cursor-pointer items-center">
|
||||
<Switch v-model="negateLabels" class="ml-auto" />
|
||||
<div class="grow"></div>
|
||||
<div class="grow" />
|
||||
{{ $t("items.negate_labels") }}
|
||||
</Label>
|
||||
<Label class="flex cursor-pointer items-center">
|
||||
<Switch v-model="onlyWithoutPhoto" class="ml-auto" />
|
||||
<div class="grow"></div>
|
||||
<div class="grow" />
|
||||
{{ $t("items.only_without_photo") }}
|
||||
</Label>
|
||||
<Label class="flex cursor-pointer items-center">
|
||||
<Switch v-model="onlyWithPhoto" class="ml-auto" />
|
||||
<div class="grow"></div>
|
||||
<div class="grow" />
|
||||
{{ $t("items.only_with_photo") }}
|
||||
</Label>
|
||||
<Label class="flex cursor-pointer flex-col gap-2">
|
||||
@@ -440,7 +440,7 @@
|
||||
<Button @click="reset"> {{ $t("items.reset_search") }} </Button>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div class="grow"></div>
|
||||
<div class="grow" />
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button size="sm" variant="outline"> {{ $t("items.tips") }}</Button>
|
||||
|
||||
@@ -213,8 +213,7 @@
|
||||
</div>
|
||||
</header>
|
||||
<Separator v-if="location && location.description" />
|
||||
<Markdown v-if="location && location.description" class="mt-3 text-base" :source="location.description">
|
||||
</Markdown>
|
||||
<Markdown v-if="location && location.description" class="mt-3 text-base" :source="location.description" />
|
||||
</Card>
|
||||
<section v-if="location && items">
|
||||
<ItemViewSelectable :items="items" />
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<div>
|
||||
<BaseContainer class="flex flex-col gap-4">
|
||||
<BaseSectionHeader> {{ $t("menu.maintenance") }} </BaseSectionHeader>
|
||||
<MaintenanceListView></MaintenanceListView>
|
||||
<MaintenanceListView />
|
||||
</BaseContainer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -360,7 +360,7 @@
|
||||
<Button variant="secondary" :disabled="!(notifier && notifier.url)" type="button" @click="testNotifier">
|
||||
{{ $t("profile.test") }}
|
||||
</Button>
|
||||
<div class="grow"></div>
|
||||
<div class="grow" />
|
||||
<Button type="submit"> {{ $t("global.submit") }} </Button>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
|
||||
@@ -344,12 +344,12 @@
|
||||
<p>
|
||||
{{ $t("reports.label_generator.instruction_2") }}
|
||||
</p>
|
||||
<p v-html="DOMPurify.sanitize($t('reports.label_generator.instruction_3'))"></p>
|
||||
<p v-html="DOMPurify.sanitize($t('reports.label_generator.instruction_3'))" />
|
||||
<h2>{{ $t("reports.label_generator.tips") }}</h2>
|
||||
<ul>
|
||||
<li v-html="DOMPurify.sanitize($t('reports.label_generator.tip_1'))"></li>
|
||||
<li v-html="DOMPurify.sanitize($t('reports.label_generator.tip_2'))"></li>
|
||||
<li v-html="DOMPurify.sanitize($t('reports.label_generator.tip_3'))"></li>
|
||||
<li v-html="DOMPurify.sanitize($t('reports.label_generator.tip_1'))" />
|
||||
<li v-html="DOMPurify.sanitize($t('reports.label_generator.tip_2'))" />
|
||||
<li v-html="DOMPurify.sanitize($t('reports.label_generator.tip_3'))" />
|
||||
</ul>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<NuxtLink href="/tools">{{ $t("menu.tools") }}</NuxtLink>
|
||||
@@ -435,7 +435,7 @@
|
||||
width: `${out.card.height * 0.9}${out.measure}`,
|
||||
height: `${out.card.height * 0.9}${out.measure}`,
|
||||
}"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div class="ml-2 flex flex-col justify-center">
|
||||
<div class="font-bold">{{ item.assetID }}</div>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<DetailAction @action="openDialog(DialogID.Import)">
|
||||
<template #title> {{ $t("tools.import_export_set.import") }} </template>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-html="DOMPurify.sanitize($t('tools.import_export_set.import_sub'))"></div>
|
||||
<div v-html="DOMPurify.sanitize($t('tools.import_export_set.import_sub'))" />
|
||||
<template #button> {{ $t("tools.import_export_set.import_button") }} </template>
|
||||
</DetailAction>
|
||||
<DetailAction @action="getExportCSV()">
|
||||
@@ -57,7 +57,7 @@
|
||||
<span> {{ $t("tools.actions") }} </span>
|
||||
<template #description>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-html="DOMPurify.sanitize($t('tools.actions_sub'))"></div>
|
||||
<div v-html="DOMPurify.sanitize($t('tools.actions_sub'))" />
|
||||
</template>
|
||||
</BaseSectionHeader>
|
||||
</template>
|
||||
@@ -75,19 +75,19 @@
|
||||
<DetailAction @action="resetItemDateTimes">
|
||||
<template #title> {{ $t("tools.actions_set.zero_datetimes") }} </template>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-html="DOMPurify.sanitize($t('tools.actions_set.zero_datetimes_sub'))"></div>
|
||||
<div v-html="DOMPurify.sanitize($t('tools.actions_set.zero_datetimes_sub'))" />
|
||||
<template #button> {{ $t("tools.actions_set.zero_datetimes_button") }} </template>
|
||||
</DetailAction>
|
||||
<DetailAction @action="setPrimaryPhotos">
|
||||
<template #title> {{ $t("tools.actions_set.set_primary_photo") }} </template>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-html="DOMPurify.sanitize($t('tools.actions_set.set_primary_photo_sub'))"></div>
|
||||
<div v-html="DOMPurify.sanitize($t('tools.actions_set.set_primary_photo_sub'))" />
|
||||
<template #button> {{ $t("tools.actions_set.set_primary_photo_button") }} </template>
|
||||
</DetailAction>
|
||||
<DetailAction @action="createMissingThumbnails">
|
||||
<template #title> {{ $t("tools.actions_set.create_missing_thumbnails") }} </template>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-html="DOMPurify.sanitize($t('tools.actions_set.create_missing_thumbnails_sub'))"></div>
|
||||
<div v-html="DOMPurify.sanitize($t('tools.actions_set.create_missing_thumbnails_sub'))" />
|
||||
<template #button> {{ $t("tools.actions_set.create_missing_thumbnails_button") }} </template>
|
||||
</DetailAction>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@ export const messages = () => {
|
||||
};
|
||||
|
||||
export const messageCompiler: (
|
||||
message: String | any,
|
||||
message: string | any,
|
||||
{
|
||||
locale,
|
||||
key,
|
||||
|
||||
10387
frontend/pnpm-lock.yaml
generated
10387
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,10 @@
|
||||
try {
|
||||
console.log('Setting theme');
|
||||
const theme = JSON.parse(
|
||||
localStorage.getItem('homebox/preferences/location')
|
||||
).theme;
|
||||
console.log("Setting theme");
|
||||
const theme = JSON.parse(localStorage.getItem("homebox/preferences/location")).theme;
|
||||
if (theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
document.documentElement.classList.add('theme-' + theme);
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
document.documentElement.classList.add("theme-" + theme);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to set theme', e);
|
||||
console.error("Failed to set theme", e);
|
||||
}
|
||||
|
||||
@@ -15,5 +15,8 @@
|
||||
"devDependencies": {
|
||||
"vitepress": "^1.6.3"
|
||||
},
|
||||
"packageManager": "pnpm@9.1.4+sha512.9df9cf27c91715646c7d675d1c9c8e41f6fce88246f1318c1aa6a1ed1aeb3c4f032fcdf4ba63cc69c4fe6d634279176b5358727d8f2cc1e65b65f43ce2f8bfb0"
|
||||
"packageManager": "pnpm@9.1.4+sha512.9df9cf27c91715646c7d675d1c9c8e41f6fce88246f1318c1aa6a1ed1aeb3c4f032fcdf4ba63cc69c4fe6d634279176b5358727d8f2cc1e65b65f43ce2f8bfb0",
|
||||
"dependencies": {
|
||||
"nuxt": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
7827
pnpm-lock.yaml
generated
7827
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user