From 224a4d1b58d00d5022b0432c9eef8ba271ae055e Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Fri, 23 Feb 2024 00:54:42 +0100 Subject: [PATCH 001/197] Re-added UI for projects and create project pages --- index.html | 34 +- package.json | 6 + postcss.config.js | 6 + src/renderer/react/app.tsx | 2 +- src/renderer/react/index.css | 3 + src/renderer/react/routeTree.gen.ts | 18 +- src/renderer/react/routes/__root.tsx | 107 ++++- src/renderer/react/routes/_hasUserGuard.tsx | 20 - src/renderer/react/routes/index.tsx | 13 +- src/renderer/react/routes/projects/create.tsx | 109 +++++ src/renderer/react/routes/projects/index.tsx | 98 +++- tailwind.config.js | 18 + yarn.lock | 442 +++++++++++++++++- 13 files changed, 758 insertions(+), 118 deletions(-) create mode 100644 postcss.config.js create mode 100644 src/renderer/react/index.css delete mode 100644 src/renderer/react/routes/_hasUserGuard.tsx create mode 100644 src/renderer/react/routes/projects/create.tsx create mode 100644 tailwind.config.js diff --git a/index.html b/index.html index c881974f..92713344 100644 --- a/index.html +++ b/index.html @@ -1,38 +1,12 @@ - + Hello World! - -
-

💖 Hello World!

-

Welcome to your Electron application.

-

- Internal navigation to any - origin is currently not allowed. -

-

- Navigation to an URL with the intent to - open a new window should - open given URL in the OS's default browser (external) instead - - if the origin is on the whitelist. -

- -
+ +
+
diff --git a/package.json b/package.json index 36860c5a..f1ed6647 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ }, "dependencies": { "@elek-io/core": "^0.1.1", + "@elek-io/ui": "^0.1.3", + "@heroicons/react": "^2.1.1", "@sentry/electron": "^4.17.0", "@sentry/react": "7.92.0", "@sentry/vite-plugin": "^2.13.0", @@ -33,6 +35,7 @@ "electron-squirrel-startup": "^1.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.50.1", "update-electron-app": "^3.0.0" }, "devDependencies": { @@ -51,10 +54,13 @@ "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", + "autoprefixer": "^10.4.17", "electron": "^28.2.1", "electron-devtools-installer": "^3.2.0", "eslint": "^8.0.1", "eslint-plugin-import": "^2.25.0", + "postcss": "^8.4.35", + "tailwindcss": "^3.4.1", "ts-node": "^10.0.0", "typescript": "~4.5.4" } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000..33ad091d --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/src/renderer/react/app.tsx b/src/renderer/react/app.tsx index 52ba94fa..16f4f638 100644 --- a/src/renderer/react/app.tsx +++ b/src/renderer/react/app.tsx @@ -6,6 +6,7 @@ import { import { StrictMode } from 'react'; import ReactDOM from 'react-dom/client'; import { type ContextBridgeApi } from '../preload'; +import './index.css'; // Import the generated route tree import { routeTree } from './routeTree.gen'; @@ -37,7 +38,6 @@ if (!rootElement.innerHTML) { const root = ReactDOM.createRoot(rootElement); root.render( - Current URL: "{window.location.href}" ); diff --git a/src/renderer/react/index.css b/src/renderer/react/index.css new file mode 100644 index 00000000..b5c61c95 --- /dev/null +++ b/src/renderer/react/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/src/renderer/react/routeTree.gen.ts b/src/renderer/react/routeTree.gen.ts index 4149c598..0ac442aa 100644 --- a/src/renderer/react/routeTree.gen.ts +++ b/src/renderer/react/routeTree.gen.ts @@ -11,19 +11,14 @@ // Import Routes import { Route as rootRoute } from './routes/__root' -import { Route as HasUserGuardImport } from './routes/_hasUserGuard' import { Route as IndexImport } from './routes/index' import { Route as ProjectsIndexImport } from './routes/projects/index' import { Route as UserSetImport } from './routes/user/set' +import { Route as ProjectsCreateImport } from './routes/projects/create' import { Route as ProjectsProjectIdIndexImport } from './routes/projects/$projectId/index' // Create/Update Routes -const HasUserGuardRoute = HasUserGuardImport.update({ - id: '/_hasUserGuard', - getParentRoute: () => rootRoute, -} as any) - const IndexRoute = IndexImport.update({ path: '/', getParentRoute: () => rootRoute, @@ -39,6 +34,11 @@ const UserSetRoute = UserSetImport.update({ getParentRoute: () => rootRoute, } as any) +const ProjectsCreateRoute = ProjectsCreateImport.update({ + path: '/projects/create', + getParentRoute: () => rootRoute, +} as any) + const ProjectsProjectIdIndexRoute = ProjectsProjectIdIndexImport.update({ path: '/projects/$projectId/', getParentRoute: () => rootRoute, @@ -52,8 +52,8 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexImport parentRoute: typeof rootRoute } - '/_hasUserGuard': { - preLoaderRoute: typeof HasUserGuardImport + '/projects/create': { + preLoaderRoute: typeof ProjectsCreateImport parentRoute: typeof rootRoute } '/user/set': { @@ -75,7 +75,7 @@ declare module '@tanstack/react-router' { export const routeTree = rootRoute.addChildren([ IndexRoute, - HasUserGuardRoute, + ProjectsCreateRoute, UserSetRoute, ProjectsIndexRoute, ProjectsProjectIdIndexRoute, diff --git a/src/renderer/react/routes/__root.tsx b/src/renderer/react/routes/__root.tsx index 0b741cb4..5dbc8e15 100644 --- a/src/renderer/react/routes/__root.tsx +++ b/src/renderer/react/routes/__root.tsx @@ -1,9 +1,19 @@ +import { Project, SearchResult, User } from '@elek-io/shared'; +import { + BaseLayout, + Breadcrumbs, + NotificationIntent, + NotificationProps, +} from '@elek-io/ui'; +import { SidebarNavigationItemGroup } from '@elek-io/ui/dist/components/Sidebar'; import { - createRootRouteWithContext, - Link, Outlet, + createRootRouteWithContext, + redirect, + useRouterState, } from '@tanstack/react-router'; import { TanStackRouterDevtools } from '@tanstack/router-devtools'; +import { ChangeEvent, useState } from 'react'; import { ContextBridgeApi } from '../../preload'; interface RouterContext { @@ -12,22 +22,79 @@ interface RouterContext { // Use the routerContext to create your root route export const Route = createRootRouteWithContext()({ - component: () => ( - <> -
- - Home - {' '} - - Projects - - - User - -
-
- - - - ), + beforeLoad: async ({ context, location }) => { + const user = await context.core.user.get(); + if (!user) { + throw redirect({ + to: '/user/set', + search: { + redirect: location.href, + }, + }); + } + return { currentUser: user }; + }, + component: RootRoute, }); + +function RootRoute() { + const router = useRouterState(); + const context = Route.useRouteContext(); + const [notifications, setNotifications] = useState([]); + const [breadcrumbs, setBreadcrumbs] = useState([]); + const [currentUser, setCurrentUser] = useState(context.currentUser); + const [currentProject, setCurrentProject] = useState(); + const [searchQuery, setSearchQuery] = useState(''); + const [searchResult, setSearchResult] = useState(); + const sidebarDisabledOnPaths = ['/', '/projects', '/projects/create']; + const [sidebarNavigation, setSidebarNavigation] = useState< + SidebarNavigationItemGroup[] + >([]); + + function addNotification(notification: NotificationProps) { + setNotifications([...notifications, notification]); + } + + async function onSearch(event: ChangeEvent) { + setSearchQuery(event.target.value); + if (!currentProject) { + return; + } + + try { + const searchResult = await context.core.projects.search( + currentProject.id, + searchQuery + ); + setSearchResult(searchResult); + console.log('Searched: ', { + query: searchQuery, + result: searchResult, + }); + } catch (error) { + console.error(error); + addNotification({ + intent: NotificationIntent.DANGER, + title: 'Search failed', + description: 'There was an error searching for the provided query.', + }); + } + } + + return ( + onSearch(event)} + sidebarDisabledOnPaths={sidebarDisabledOnPaths} + sidebarNavigation={sidebarNavigation} + userNavigation={[]} + searchQuery={searchQuery} + > + + + + ); +} diff --git a/src/renderer/react/routes/_hasUserGuard.tsx b/src/renderer/react/routes/_hasUserGuard.tsx deleted file mode 100644 index c1a66554..00000000 --- a/src/renderer/react/routes/_hasUserGuard.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { createFileRoute, redirect } from '@tanstack/react-router'; - -export const Route = createFileRoute('/_hasUserGuard')({ - beforeLoad: async ({ location, context }) => { - const user = await context.core.user.get(); - console.log('User:', user); - - if (!user) { - throw redirect({ - to: '/user/set', - search: { - // Use the current location to power a redirect after login - // (Do not use `router.state.resolvedLocation` as it can - // potentially lag behind the actual current location) - redirect: location.href, - }, - }); - } - }, -}); diff --git a/src/renderer/react/routes/index.tsx b/src/renderer/react/routes/index.tsx index 52e91d16..cfb4550d 100644 --- a/src/renderer/react/routes/index.tsx +++ b/src/renderer/react/routes/index.tsx @@ -1,13 +1,14 @@ -import { createFileRoute } from '@tanstack/react-router'; +import { createFileRoute, redirect } from '@tanstack/react-router'; export const Route = createFileRoute('/')({ + beforeLoad: async () => { + throw redirect({ + to: '/projects', + }); + }, component: Index, }); function Index() { - return ( -
-

Welcome Home!

-
- ); + return <>; } diff --git a/src/renderer/react/routes/projects/create.tsx b/src/renderer/react/routes/projects/create.tsx new file mode 100644 index 00000000..78d5cf13 --- /dev/null +++ b/src/renderer/react/routes/projects/create.tsx @@ -0,0 +1,109 @@ +import { Project } from '@elek-io/shared'; +import { Button, FormInput, Page } from '@elek-io/ui'; +import { CheckIcon } from '@heroicons/react/20/solid'; +import { createFileRoute, useRouter } from '@tanstack/react-router'; +import { ReactElement, useState } from 'react'; +import { SubmitHandler, useForm } from 'react-hook-form'; + +export const Route = createFileRoute('/projects/create')({ + component: CreateProjectPage, +}); + +function CreateProjectPage() { + const router = useRouter(); + const context = Route.useRouteContext(); + const data = Route.useLoaderData(); + const [isCreatingProject, setCreatingProject] = useState(false); + const { + register, + handleSubmit, + watch, + control, + formState: { errors }, + } = useForm(); + + function Description(): ReactElement { + return ( + <> + New Projects start with no history or data. +

+ Read more about{' '} + + Projects in the documentation + + . + + ); + } + + function Actions(): ReactElement { + return ( + <> + + + ); + } + + const onCreate: SubmitHandler = async (project) => { + try { + setCreatingProject(true); + const newProject = await context.core.projects.create({ + ...project, + }); + // props.addNotification({ + // intent: NotificationIntent.SUCCESS, + // title: 'Successfully created Project', + // description: `The Project "${project.name}" was successfully created.`, + // }); + router.navigate({ + to: '/projects/$projectId', + params: { projectId: newProject.id }, + }); + } catch (error) { + setCreatingProject(false); + console.error(error); + // props.addNotification({ + // intent: NotificationIntent.DANGER, + // title: 'Failed to create Project', + // description: 'There was an error creating the Project on disk.', + // }); + } + }; + + return ( + } + actions={} + > + {/* {JSON.stringify(watch())} +
*/} + + +
+ ); +} diff --git a/src/renderer/react/routes/projects/index.tsx b/src/renderer/react/routes/projects/index.tsx index 3c0ae443..3f5c4eb1 100644 --- a/src/renderer/react/routes/projects/index.tsx +++ b/src/renderer/react/routes/projects/index.tsx @@ -1,36 +1,92 @@ -import { Link, createFileRoute } from '@tanstack/react-router'; +import { Button, Page } from '@elek-io/ui'; +import { PlusIcon } from '@heroicons/react/20/solid'; +import { Link, createFileRoute, useRouter } from '@tanstack/react-router'; +import { ReactElement } from 'react'; export const Route = createFileRoute('/projects/')({ + loader: async ({ context }) => { + const projects = await context.core.projects.list(); + return { projects }; + }, component: ListProjectsPage, - loader: ({ context }) => context.core.projects.list(), }); function ListProjectsPage() { + const router = useRouter(); const context = Route.useRouteContext(); - const projects = Route.useLoaderData(); + const data = Route.useLoaderData(); - function createProject() { - context.core.projects.create({ name: 'A test project' }); + function Description(): ReactElement { + return ( + <> + A Project ... +

+ Read more about{' '} + + Projects in the documentation + + . + + ); + } + + function Actions(): ReactElement { + return ( + <> + + + ); } return ( -
- - Found the following Projects:{' '} -
    - {projects.list.map((project) => { - return ( -
  • - - {project.name} - + } + actions={} + layout="overlap" + > +
      + {data.projects.list.map((project) => ( + +
    • +
      +

      + {project.name} ({project.status}) +

      +

      {project.description}

      + + {project.version} + +

      + Created: {project.created} +

      Updated: {project.updated} +

      +
      + {/*
      +
      +
      + {JSON.stringify(project, undefined, 2)} +
      +
      +
      */}
    • - ); - })} + + ))}
    -
+ ); } diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 00000000..fb3685a4 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,18 @@ +import colors from 'tailwindcss/colors'; + +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './index.html', + './src/renderer/**/*.{js,ts,jsx,tsx}', + './node_modules/@elek-io/ui/**/*.{js,ts,jsx,tsx}', + ], + theme: { + extend: { + colors: { + brand: colors.cyan, + }, + }, + }, + plugins: [], +}; diff --git a/yarn.lock b/yarn.lock index 4aa45bfc..393405d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== +"@alloc/quick-lru@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + "@ampproject/remapping@^2.1.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -160,7 +165,7 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b" integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA== -"@babel/runtime@^7.20.1", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5": +"@babel/runtime@^7.20.1", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.7", "@babel/runtime@^7.5.5": version "7.23.9" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz" integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== @@ -807,7 +812,7 @@ p-queue "^8.0.1" semver "^7.5.4" -"@elek-io/shared@0.2.0": +"@elek-io/shared@0.2.0", "@elek-io/shared@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@elek-io/shared/-/shared-0.2.0.tgz#d0d2d52b2dbcdec905b494458c4665fa09ae79e4" integrity sha512-AxAgLG0O9sRwDjJ8Zae6EDSmMP8hqQf9ejbb++oMNlVopzvwJsMeNJtHp1uQMG4liabPNX5RdpBQyEcQvaqKuA== @@ -816,6 +821,21 @@ uuid "^9.0.1" zod "^3.22.4" +"@elek-io/ui@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@elek-io/ui/-/ui-0.1.3.tgz#0990b99b316124824a717ee4ca154a300f19f1df" + integrity sha512-w7r8yRJESWUnLtYhPUOMRLyABdH42DhgszHU+S/Vx2DecilU2yQAimGBAnCNrtNdsWeIuYWcpZCxgmDtKOwj1g== + dependencies: + "@elek-io/shared" "^0.2.0" + "@headlessui/react" "^1.7.18" + "@heroicons/react" "^2.1.1" + class-variance-authority "^0.7.0" + date-fns "^3.3.1" + react "^18.2.0" + react-dom "^18.2.0" + react-hook-form "^7.50.1" + tailwind-merge "^2.2.1" + "@esbuild/android-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" @@ -963,6 +983,19 @@ resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@headlessui/react@^1.7.18": + version "1.7.18" + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.18.tgz#30af4634d2215b2ca1aa29d07f33d02bea82d9d7" + integrity sha512-4i5DOrzwN4qSgNsL4Si61VMkUcWbcSKueUV7sFhpHzQcSShdlHENE5+QBntMSRvHt8NyoFO2AGG8si9lq+w4zQ== + dependencies: + "@tanstack/react-virtual" "^3.0.0-beta.60" + client-only "^0.0.1" + +"@heroicons/react@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-2.1.1.tgz#422deb80c4d6caf3371aec6f4bee8361a354dc13" + integrity sha512-JyyN9Lo66kirbCMuMMRPtJxtKJoIsXKS569ebHGGRKbl8s4CtUfLnyKJxteA+vIKySocO4s1SkTkGS4xtG/yEA== + "@humanwhocodes/config-array@^0.11.13": version "0.11.14" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz" @@ -982,6 +1015,18 @@ resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz" integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" @@ -1204,6 +1249,11 @@ dependencies: "@octokit/openapi-types" "^12.11.0" +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@sentry-internal/feedback@7.92.0": version "7.92.0" resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.92.0.tgz#1293b0a332f81cdf3970abd36894b9d25670c4e6" @@ -1455,6 +1505,13 @@ "@tanstack/store" "0.1.3" use-sync-external-store "^1.2.0" +"@tanstack/react-virtual@^3.0.0-beta.60": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.1.2.tgz#eb62b73cc82e34860604cd3d682a17db590f3c45" + integrity sha512-qibmxtctgOZo2I+3Rw5GR9kXgaa15U5r3/idDY1ItUKW15UK7GhCfyIfE6qYuJ1fxQF6dJDsD8SbpPyuJgpxuA== + dependencies: + "@tanstack/virtual-core" "3.1.2" + "@tanstack/router-devtools@^1.15.23": version "1.15.23" resolved "https://registry.yarnpkg.com/@tanstack/router-devtools/-/router-devtools-1.15.23.tgz#a5aebe5a6fb3bcde5014d9c0eb745d3f3f3ff222" @@ -1483,6 +1540,11 @@ resolved "https://registry.yarnpkg.com/@tanstack/store/-/store-0.1.3.tgz#b8410435dac0a0f6d3fe77d49509f296905d4c73" integrity sha512-GnolmC8Fr4mvsHE1fGQmR3Nm0eBO3KnZjDU0a+P3TeQNM/dDscFGxtA7p31NplQNW3KwBw4t1RVFmz0VeKLxcw== +"@tanstack/virtual-core@3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.1.2.tgz#ca76f28f826fbd3310f88c3cd355d9c4aba80abb" + integrity sha512-DATZJs8iejkIUqXZe6ruDAnjFo78BKnIIgqQZrc7CmEFqfLEN/TPD91n4hRfo6hpRB6xC00bwKxv7vdjFNEmOg== + "@tokenizer/token@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" @@ -1810,6 +1872,11 @@ ansi-regex@^5.0.1: resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" @@ -1824,6 +1891,16 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1867,6 +1944,11 @@ arg@^4.1.0: resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" @@ -1989,6 +2071,18 @@ author-regex@^1.0.0: resolved "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz" integrity sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g== +autoprefixer@^10.4.17: + version "10.4.17" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.17.tgz#35cd5695cbbe82f536a50fa025d561b01fdec8be" + integrity sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg== + dependencies: + browserslist "^4.22.2" + caniuse-lite "^1.0.30001578" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + available-typed-arrays@^1.0.5: version "1.0.6" resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz" @@ -2195,6 +2289,11 @@ callsites@^3.0.0: resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + camelcase-keys@^6.2.2: version "6.2.2" resolved "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz" @@ -2209,6 +2308,11 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== +caniuse-lite@^1.0.30001578: + version "1.0.30001589" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz#7ad6dba4c9bf6561aec8291976402339dc157dfb" + integrity sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg== + caniuse-lite@^1.0.30001580: version "1.0.30001583" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz#abb2970cc370801dc7e27bf290509dc132cfa390" @@ -2271,6 +2375,13 @@ ci-info@^3.7.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== +class-variance-authority@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.0.tgz#1c3134d634d80271b1837452b06d821915954522" + integrity sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A== + dependencies: + clsx "2.0.0" + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" @@ -2296,6 +2407,11 @@ cli-truncate@^2.1.0: slice-ansi "^3.0.0" string-width "^4.2.0" +client-only@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" + integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== + cliui@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz" @@ -2335,6 +2451,11 @@ clone@^1.0.2: resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== +clsx@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" + integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" @@ -2374,7 +2495,7 @@ colorette@^2.0.19: resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== -commander@^4.1.1: +commander@^4.0.0, commander@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== @@ -2465,7 +2586,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.1, cross-spawn@^7.0.2: +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -2479,6 +2600,11 @@ cross-zip@^4.0.0: resolved "https://registry.yarnpkg.com/cross-zip/-/cross-zip-4.0.1.tgz#1bbf5d3b0e5a77b5f5ca130a6d38f770786e1270" integrity sha512-n63i0lZ0rvQ6FXiGQ+/JFCKAUyPFhLQYJIqKaa+tSJtfKeULF/IDNDAbdnSIxgS4NTuw2b0+lj8LzfITuq+ZxQ== +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + csstype@^3.0.2: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" @@ -2516,6 +2642,11 @@ date-fns@^2.29.1: dependencies: "@babel/runtime" "^7.21.0" +date-fns@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.3.1.tgz#7581daca0892d139736697717a168afbb908cfed" + integrity sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw== + debug@2.6.9, debug@^2.2.0: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" @@ -2632,6 +2763,11 @@ detect-node@^2.0.4: resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + diff@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" @@ -2652,6 +2788,11 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz" @@ -2688,6 +2829,11 @@ dugite@^2.5.2: progress "^2.0.3" tar "^6.1.11" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ee-first@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" @@ -2794,6 +2940,11 @@ emoji-regex@^8.0.0: resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + encode-utf8@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" @@ -3222,7 +3373,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.7, fast-glob@^3.2.9: +fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.0: version "3.3.2" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -3381,11 +3532,24 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + fresh@0.5.2: version "0.5.2" resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" @@ -3626,6 +3790,17 @@ glob@9.3.2: minipass "^4.2.4" path-scurry "^1.6.1" +glob@^10.3.10: + version "10.3.10" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" + integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.3.5" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" + glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" @@ -4225,6 +4400,20 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +jackspeak@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" + integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jiti@^1.19.1: + version "1.21.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" + integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -4355,6 +4544,16 @@ lie@~3.3.0: dependencies: immediate "~3.0.5" +lilconfig@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + +lilconfig@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.1.tgz#9d8a246fa753106cfc205fd2d77042faca56e5e3" + integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" @@ -4634,7 +4833,7 @@ methods@~1.1.2: resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -4700,7 +4899,7 @@ minimatch@^7.4.1: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.3: +minimatch@^9.0.1, minimatch@^9.0.3: version "9.0.3" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== @@ -4831,6 +5030,15 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: fmix "^0.1.0" imul "^1.0.0" +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + nan@^2.4.0: version "2.18.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" @@ -4926,6 +5134,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + normalize-url@^6.0.1: version "6.1.0" resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz" @@ -4948,6 +5161,16 @@ npmlog@^6.0.0: gauge "^4.0.3" set-blocking "^2.0.0" +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + object-inspect@^1.13.1, object-inspect@^1.9.0: version "1.13.1" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz" @@ -5241,7 +5464,7 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.6.1: +path-scurry@^1.10.1, path-scurry@^1.6.1: version "1.10.1" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== @@ -5286,7 +5509,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pify@^2.0.0: +pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== @@ -5296,6 +5519,11 @@ pify@^4.0.1: resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +pirates@^4.0.1: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" @@ -5312,6 +5540,59 @@ plist@^3.0.0, plist@^3.0.5, plist@^3.1.0: base64-js "^1.5.1" xmlbuilder "^15.1.1" +postcss-import@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" + integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-js@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" + integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== + dependencies: + camelcase-css "^2.0.1" + +postcss-load-config@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3" + integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ== + dependencies: + lilconfig "^3.0.0" + yaml "^2.3.4" + +postcss-nested@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" + integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== + dependencies: + postcss-selector-parser "^6.0.11" + +postcss-selector-parser@^6.0.11: + version "6.0.15" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535" + integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.4.23, postcss@^8.4.35: + version "8.4.35" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.35.tgz#60997775689ce09011edf083a549cea44aabe2f7" + integrity sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + postcss@^8.4.27: version "8.4.33" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz" @@ -5460,6 +5741,11 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-hook-form@^7.50.1: + version "7.50.1" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.50.1.tgz#f6aeb17a863327e5a0252de8b35b4fc8990377ed" + integrity sha512-3PCY82oE0WgeOgUtIr3nYNNtNvqtJ7BZjsbxh6TnYNbXButaD5WpjOmTjdxZfheuHKR68qfeFnEDVYoSSFPMTQ== + react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -5479,6 +5765,13 @@ read-binary-file-arch@^1.0.6: dependencies: debug "^4.3.4" +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + read-pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz" @@ -5635,7 +5928,7 @@ resolve-package@^1.0.1: dependencies: get-installed-path "^2.0.3" -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.4: +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.2, resolve@^1.22.4: version "1.22.8" resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -5893,6 +6186,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.7: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + slash@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" @@ -6041,6 +6339,15 @@ stream-transform@^2.1.3: dependencies: mixme "^0.5.1" +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" @@ -6050,6 +6357,15 @@ stream-transform@^2.1.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string.prototype.trim@^1.2.8: version "1.2.8" resolved "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz" @@ -6091,6 +6407,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -6098,6 +6421,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" @@ -6140,6 +6470,19 @@ strtok3@^7.0.0: "@tokenizer/token" "^0.3.0" peek-readable "^5.0.0" +sucrase@^3.32.0: + version "3.35.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" + integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + glob "^10.3.10" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + sudo-prompt@^9.1.1: version "9.2.1" resolved "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz" @@ -6171,6 +6514,41 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +tailwind-merge@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.2.1.tgz#3f10f296a2dba1d88769de8244fafd95c3324aeb" + integrity sha512-o+2GTLkthfa5YUt4JxPfzMIpQzZ3adD1vLVkvKE1Twl9UAhGsEbIZhHHZVRttyW177S8PDJI3bTQNaebyofK3Q== + dependencies: + "@babel/runtime" "^7.23.7" + +tailwindcss@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.1.tgz#f512ca5d1dd4c9503c7d3d28a968f1ad8f5c839d" + integrity sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.5.3" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.3.0" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.19.1" + lilconfig "^2.1.0" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.23" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.1" + postcss-nested "^6.0.1" + postcss-selector-parser "^6.0.11" + resolve "^1.22.2" + sucrase "^3.32.0" + tar@^6.0.5, tar@^6.1.11, tar@^6.1.2: version "6.2.0" resolved "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz" @@ -6201,6 +6579,20 @@ text-table@^0.2.0: resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + through@^2.3.8: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" @@ -6296,6 +6688,11 @@ trim-repeated@^1.0.0: dependencies: escape-string-regexp "^1.0.2" +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + ts-node@^10.0.0: version "10.9.2" resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" @@ -6549,7 +6946,7 @@ username@^5.1.0: execa "^1.0.0" mem "^4.3.0" -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -6684,6 +7081,15 @@ word-wrap@^1.2.3: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" @@ -6702,6 +7108,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" @@ -6767,6 +7182,11 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@^2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" + integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== + yargs-parser@^18.1.2, yargs-parser@^18.1.3: version "18.1.3" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz" From 74a4f6494288cb699032e6df87bedafa08b88b3f Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Sun, 25 Feb 2024 20:56:14 +0100 Subject: [PATCH 002/197] Moved ipc to its own file --- src/renderer/react/app.tsx | 10 ++-------- src/renderer/react/ipc.ts | 9 +++++++++ 2 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 src/renderer/react/ipc.ts diff --git a/src/renderer/react/app.tsx b/src/renderer/react/app.tsx index 16f4f638..6101e886 100644 --- a/src/renderer/react/app.tsx +++ b/src/renderer/react/app.tsx @@ -5,24 +5,18 @@ import { } from '@tanstack/react-router'; import { StrictMode } from 'react'; import ReactDOM from 'react-dom/client'; -import { type ContextBridgeApi } from '../preload'; import './index.css'; +import { ipc } from './ipc'; // Import the generated route tree import { routeTree } from './routeTree.gen'; -declare global { - interface Window { - ipc: ContextBridgeApi; - } -} - // Create a new router instance const hashHistory = createHashHistory(); // Use hash based routing since in production electron just loads the index.html via the file protocol const router = createRouter({ routeTree, history: hashHistory, - context: { core: window.ipc.core }, + context: { core: ipc.core }, }); // Register the router instance for type safety diff --git a/src/renderer/react/ipc.ts b/src/renderer/react/ipc.ts new file mode 100644 index 00000000..111dc9f1 --- /dev/null +++ b/src/renderer/react/ipc.ts @@ -0,0 +1,9 @@ +import { ContextBridgeApi } from '../preload'; + +declare global { + interface Window { + ipc: ContextBridgeApi; + } +} + +export const ipc = window.ipc; From 9074b68e591b4031f25e74fc27a0e7f3dc6e3f0f Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Mon, 26 Feb 2024 22:33:01 +0100 Subject: [PATCH 003/197] Added roboto and montserrat as local fonts. Also added working example of new GUI --- index.html | 7 +- package.json | 2 + src/renderer/react/app.tsx | 2 + src/renderer/react/index.css | 43 +++++ src/renderer/react/routes/__root.tsx | 181 ++++++++++++------ .../routes/projects/$projectId/index.tsx | 131 ++++++++++++- src/renderer/react/routes/projects/index.tsx | 7 +- yarn.lock | 10 + 8 files changed, 312 insertions(+), 71 deletions(-) diff --git a/index.html b/index.html index 92713344..0281dcb9 100644 --- a/index.html +++ b/index.html @@ -1,12 +1,11 @@ - + Hello World! - -
-
+ +
diff --git a/package.json b/package.json index f1ed6647..2ec3ebdc 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,8 @@ "dependencies": { "@elek-io/core": "^0.1.1", "@elek-io/ui": "^0.1.3", + "@fontsource-variable/montserrat": "^5.0.17", + "@fontsource/roboto": "^5.0.8", "@heroicons/react": "^2.1.1", "@sentry/electron": "^4.17.0", "@sentry/react": "7.92.0", diff --git a/src/renderer/react/app.tsx b/src/renderer/react/app.tsx index 6101e886..a7aa6f3a 100644 --- a/src/renderer/react/app.tsx +++ b/src/renderer/react/app.tsx @@ -1,3 +1,5 @@ +import '@fontsource-variable/montserrat'; +import '@fontsource/roboto'; import { RouterProvider, createHashHistory, diff --git a/src/renderer/react/index.css b/src/renderer/react/index.css index b5c61c95..19f4069f 100644 --- a/src/renderer/react/index.css +++ b/src/renderer/react/index.css @@ -1,3 +1,46 @@ @tailwind base; @tailwind components; @tailwind utilities; + +/* Add default styling to some elements */ +@layer base { + html { + font-family: 'Roboto', sans-serif; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + font-family: 'Montserrat Variable', sans-serif; + } + + a { + color: theme('colors.brand.600'); + text-decoration: underline; + } +} + +/* + By default, the frameless window is non-draggable. + Apps need to specify -webkit-app-region: drag in CSS to tell Electron + which regions are draggable (like the OS's standard titlebar) + @see https://www.electronjs.org/de/docs/latest/tutorial/window-customization#set-custom-draggable-region +*/ +.window-draggable-area { + -webkit-app-region: drag; + /* When creating a draggable region, the dragging behavior may conflict with text selection */ + user-select: none; + -webkit-user-select: none; +} + +/* + Make all buttons and links in titlebar non-draggable + otherwise it would be impossible for users to click them +*/ +.window-draggable-area button, +.window-draggable-area a { + -webkit-app-region: no-drag; +} diff --git a/src/renderer/react/routes/__root.tsx b/src/renderer/react/routes/__root.tsx index 5dbc8e15..47de250a 100644 --- a/src/renderer/react/routes/__root.tsx +++ b/src/renderer/react/routes/__root.tsx @@ -1,19 +1,29 @@ -import { Project, SearchResult, User } from '@elek-io/shared'; +import { Project } from '@elek-io/shared'; import { - BaseLayout, - Breadcrumbs, - NotificationIntent, + Avatar, + Button, + Dropdown, + DropdownItemGroup, NotificationProps, } from '@elek-io/ui'; -import { SidebarNavigationItemGroup } from '@elek-io/ui/dist/components/Sidebar'; import { + ArrowLeftIcon, + ArrowRightIcon, + ChevronDownIcon, + DocumentDuplicateIcon, + HomeIcon, + PencilSquareIcon, +} from '@heroicons/react/20/solid'; +import { + Link, Outlet, createRootRouteWithContext, redirect, + useRouter, useRouterState, } from '@tanstack/react-router'; import { TanStackRouterDevtools } from '@tanstack/router-devtools'; -import { ChangeEvent, useState } from 'react'; +import { useState } from 'react'; import { ContextBridgeApi } from '../../preload'; interface RouterContext { @@ -23,8 +33,8 @@ interface RouterContext { // Use the routerContext to create your root route export const Route = createRootRouteWithContext()({ beforeLoad: async ({ context, location }) => { - const user = await context.core.user.get(); - if (!user) { + const currentUser = await context.core.user.get(); + if (!currentUser) { throw redirect({ to: '/user/set', search: { @@ -32,69 +42,128 @@ export const Route = createRootRouteWithContext()({ }, }); } - return { currentUser: user }; + const projects = await context.core.projects.list({ limit: 0 }); + return { currentUser, projects }; }, component: RootRoute, }); function RootRoute() { - const router = useRouterState(); + const router = useRouter(); const context = Route.useRouteContext(); + const state = useRouterState(); + const breadcrumbs = state.location.pathname + .substring(1) // remove first slash (home) + .split('/') + .map((part, index, array) => { + const path = array.slice(0, index + 1).join('/'); + + return { + part, + path, + full: state.location.pathname, + }; + }); const [notifications, setNotifications] = useState([]); - const [breadcrumbs, setBreadcrumbs] = useState([]); - const [currentUser, setCurrentUser] = useState(context.currentUser); const [currentProject, setCurrentProject] = useState(); - const [searchQuery, setSearchQuery] = useState(''); - const [searchResult, setSearchResult] = useState(); - const sidebarDisabledOnPaths = ['/', '/projects', '/projects/create']; - const [sidebarNavigation, setSidebarNavigation] = useState< - SidebarNavigationItemGroup[] - >([]); + const dropdownItemGroupsExample: DropdownItemGroup[] = [ + { + items: [ + { + name: 'Edit', + href: '#edit', + icon: PencilSquareIcon, + }, + { + name: 'Duplicate', + href: '#duplicate', + icon: DocumentDuplicateIcon, + }, + ], + }, + ]; function addNotification(notification: NotificationProps) { setNotifications([...notifications, notification]); } - async function onSearch(event: ChangeEvent) { - setSearchQuery(event.target.value); - if (!currentProject) { - return; - } - - try { - const searchResult = await context.core.projects.search( - currentProject.id, - searchQuery - ); - setSearchResult(searchResult); - console.log('Searched: ', { - query: searchQuery, - result: searchResult, - }); - } catch (error) { - console.error(error); - addNotification({ - intent: NotificationIntent.DANGER, - title: 'Search failed', - description: 'There was an error searching for the provided query.', - }); - } - } - return ( - onSearch(event)} - sidebarDisabledOnPaths={sidebarDisabledOnPaths} - sidebarNavigation={sidebarNavigation} - userNavigation={[]} - searchQuery={searchQuery} - > + <> +
+
+

+ elek.io{' '} + Client +

+
+ +
-
+ ); } diff --git a/src/renderer/react/routes/projects/$projectId/index.tsx b/src/renderer/react/routes/projects/$projectId/index.tsx index 4f7f2f2b..b752f809 100644 --- a/src/renderer/react/routes/projects/$projectId/index.tsx +++ b/src/renderer/react/routes/projects/$projectId/index.tsx @@ -1,13 +1,134 @@ -import { createFileRoute } from '@tanstack/react-router'; +import { SearchResult } from '@elek-io/shared'; +import { + FolderOpenIcon, + HomeIcon, + MagnifyingGlassIcon, +} from '@heroicons/react/20/solid'; +import { Link, createFileRoute } from '@tanstack/react-router'; +import { ChangeEvent, useState } from 'react'; export const Route = createFileRoute('/projects/$projectId/')({ + beforeLoad: async ({ context, params }) => { + const currentProject = await context.core.projects.read({ + id: params.projectId, + }); + + return { currentProject }; + }, component: ProjectDashboardPage, - loader: ({ context, params }) => - context.core.projects.read({ id: params.projectId }), }); function ProjectDashboardPage() { - const project = Route.useLoaderData(); + const context = Route.useRouteContext(); + const [searchQuery, setSearchQuery] = useState(''); + const [searchResult, setSearchResult] = useState(); + + async function onSearch(event: ChangeEvent) { + setSearchQuery(event.target.value); + try { + const searchResult = await context.core.projects.search( + context.currentProject.id, + searchQuery + ); + setSearchResult(searchResult); + console.log('Searched: ', { + query: searchQuery, + result: searchResult, + }); + } catch (error) { + console.error(error); + // addNotification({ + // intent: NotificationIntent.DANGER, + // title: 'Search failed', + // description: 'There was an error searching for the provided query.', + // }); + } + } - return
Dashboard for Project "{project.name}"
; + return ( +
+ +
+
Dashboard for Project "{context.currentProject.name}"
+
+
+ ); } diff --git a/src/renderer/react/routes/projects/index.tsx b/src/renderer/react/routes/projects/index.tsx index 3f5c4eb1..30ccfdd9 100644 --- a/src/renderer/react/routes/projects/index.tsx +++ b/src/renderer/react/routes/projects/index.tsx @@ -4,17 +4,12 @@ import { Link, createFileRoute, useRouter } from '@tanstack/react-router'; import { ReactElement } from 'react'; export const Route = createFileRoute('/projects/')({ - loader: async ({ context }) => { - const projects = await context.core.projects.list(); - return { projects }; - }, component: ListProjectsPage, }); function ListProjectsPage() { const router = useRouter(); const context = Route.useRouteContext(); - const data = Route.useLoaderData(); function Description(): ReactElement { return ( @@ -56,7 +51,7 @@ function ListProjectsPage() { role="list" className="grid grid-cols-1 gap-6 sm:grid-cols-2 xl:grid-cols-3" > - {data.projects.list.map((project) => ( + {context.projects.list.map((project) => ( Date: Thu, 29 Feb 2024 10:21:19 +0100 Subject: [PATCH 004/197] Added global state storage with zustand --- package.json | 4 ++- src/renderer/react/routes/__root.tsx | 16 +--------- .../routes/projects/$projectId/index.tsx | 13 +++++---- src/renderer/react/routes/projects/create.tsx | 24 ++++++++------- src/renderer/react/store.ts | 21 ++++++++++++++ yarn.lock | 29 ++++++++++++++++++- 6 files changed, 74 insertions(+), 33 deletions(-) create mode 100644 src/renderer/react/store.ts diff --git a/package.json b/package.json index 2ec3ebdc..75cdbded 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.50.1", - "update-electron-app": "^3.0.0" + "update-electron-app": "^3.0.0", + "zustand": "^4.5.1" }, "devDependencies": { "@changesets/cli": "^2.27.1", @@ -51,6 +52,7 @@ "@electron-forge/plugin-auto-unpack-natives": "^7.2.0", "@electron-forge/plugin-vite": "^7.2.0", "@electron-forge/publisher-github": "^7.2.0", + "@redux-devtools/extension": "^3.3.0", "@tanstack/router-vite-plugin": "^1.15.22", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", diff --git a/src/renderer/react/routes/__root.tsx b/src/renderer/react/routes/__root.tsx index 47de250a..b11b9f9a 100644 --- a/src/renderer/react/routes/__root.tsx +++ b/src/renderer/react/routes/__root.tsx @@ -1,11 +1,4 @@ -import { Project } from '@elek-io/shared'; -import { - Avatar, - Button, - Dropdown, - DropdownItemGroup, - NotificationProps, -} from '@elek-io/ui'; +import { Avatar, Button, Dropdown, DropdownItemGroup } from '@elek-io/ui'; import { ArrowLeftIcon, ArrowRightIcon, @@ -23,7 +16,6 @@ import { useRouterState, } from '@tanstack/react-router'; import { TanStackRouterDevtools } from '@tanstack/router-devtools'; -import { useState } from 'react'; import { ContextBridgeApi } from '../../preload'; interface RouterContext { @@ -64,8 +56,6 @@ function RootRoute() { full: state.location.pathname, }; }); - const [notifications, setNotifications] = useState([]); - const [currentProject, setCurrentProject] = useState(); const dropdownItemGroupsExample: DropdownItemGroup[] = [ { items: [ @@ -83,10 +73,6 @@ function RootRoute() { }, ]; - function addNotification(notification: NotificationProps) { - setNotifications([...notifications, notification]); - } - return ( <>
diff --git a/src/renderer/react/routes/projects/$projectId/index.tsx b/src/renderer/react/routes/projects/$projectId/index.tsx index b752f809..94bac96d 100644 --- a/src/renderer/react/routes/projects/$projectId/index.tsx +++ b/src/renderer/react/routes/projects/$projectId/index.tsx @@ -1,4 +1,5 @@ import { SearchResult } from '@elek-io/shared'; +import { NotificationIntent } from '@elek-io/ui'; import { FolderOpenIcon, HomeIcon, @@ -6,6 +7,7 @@ import { } from '@heroicons/react/20/solid'; import { Link, createFileRoute } from '@tanstack/react-router'; import { ChangeEvent, useState } from 'react'; +import { useStore } from '../../../store'; export const Route = createFileRoute('/projects/$projectId/')({ beforeLoad: async ({ context, params }) => { @@ -19,6 +21,7 @@ export const Route = createFileRoute('/projects/$projectId/')({ }); function ProjectDashboardPage() { + const addNotification = useStore((state) => state.addNotification); const context = Route.useRouteContext(); const [searchQuery, setSearchQuery] = useState(''); const [searchResult, setSearchResult] = useState(); @@ -37,11 +40,11 @@ function ProjectDashboardPage() { }); } catch (error) { console.error(error); - // addNotification({ - // intent: NotificationIntent.DANGER, - // title: 'Search failed', - // description: 'There was an error searching for the provided query.', - // }); + addNotification({ + intent: NotificationIntent.DANGER, + title: 'Search failed', + description: 'There was an error searching for the provided query.', + }); } } diff --git a/src/renderer/react/routes/projects/create.tsx b/src/renderer/react/routes/projects/create.tsx index 78d5cf13..a7222d4d 100644 --- a/src/renderer/react/routes/projects/create.tsx +++ b/src/renderer/react/routes/projects/create.tsx @@ -1,15 +1,17 @@ import { Project } from '@elek-io/shared'; -import { Button, FormInput, Page } from '@elek-io/ui'; +import { Button, FormInput, NotificationIntent, Page } from '@elek-io/ui'; import { CheckIcon } from '@heroicons/react/20/solid'; import { createFileRoute, useRouter } from '@tanstack/react-router'; import { ReactElement, useState } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; +import { useStore } from '../../store'; export const Route = createFileRoute('/projects/create')({ component: CreateProjectPage, }); function CreateProjectPage() { + const addNotification = useStore((state) => state.addNotification); const router = useRouter(); const context = Route.useRouteContext(); const data = Route.useLoaderData(); @@ -57,11 +59,11 @@ function CreateProjectPage() { const newProject = await context.core.projects.create({ ...project, }); - // props.addNotification({ - // intent: NotificationIntent.SUCCESS, - // title: 'Successfully created Project', - // description: `The Project "${project.name}" was successfully created.`, - // }); + addNotification({ + intent: NotificationIntent.SUCCESS, + title: 'Successfully created Project', + description: `The Project "${project.name}" was successfully created.`, + }); router.navigate({ to: '/projects/$projectId', params: { projectId: newProject.id }, @@ -69,11 +71,11 @@ function CreateProjectPage() { } catch (error) { setCreatingProject(false); console.error(error); - // props.addNotification({ - // intent: NotificationIntent.DANGER, - // title: 'Failed to create Project', - // description: 'There was an error creating the Project on disk.', - // }); + addNotification({ + intent: NotificationIntent.DANGER, + title: 'Failed to create Project', + description: 'There was an error creating the Project on disk.', + }); } }; diff --git a/src/renderer/react/store.ts b/src/renderer/react/store.ts new file mode 100644 index 00000000..53c3aef1 --- /dev/null +++ b/src/renderer/react/store.ts @@ -0,0 +1,21 @@ +import { NotificationProps } from '@elek-io/ui'; +import type {} from '@redux-devtools/extension'; // required for devtools typing +import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; + +interface StoreState { + notifications: NotificationProps[]; + addNotification: (notification: NotificationProps) => void; +} + +export const useStore = create()( + devtools((set) => ({ + notifications: [], + addNotification: (notification) => { + console.log('New notification:', notification); + set((state) => ({ + notifications: [...state.notifications, notification], + })); + }, + })) +); diff --git a/yarn.lock b/yarn.lock index a3bde720..3d25dbfd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -172,6 +172,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.23.2": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" + integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.16.7", "@babel/template@^7.22.15", "@babel/template@^7.23.9": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.23.9.tgz#f881d0487cba2828d3259dcb9ef5005a9731011a" @@ -1264,6 +1271,14 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@redux-devtools/extension@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@redux-devtools/extension/-/extension-3.3.0.tgz#bc775d289f15604c472112920beac2cf4dbb7907" + integrity sha512-X34S/rC8S/M1BIrkYD1mJ5f8vlH0BDqxXrs96cvxSBo4FhMdbhU+GUGsmNYov1xjSyLMHgo8NYrUG8bNX7525g== + dependencies: + "@babel/runtime" "^7.23.2" + immutable "^4.3.4" + "@sentry-internal/feedback@7.92.0": version "7.92.0" resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.92.0.tgz#1293b0a332f81cdf3970abd36894b9d25670c4e6" @@ -4096,6 +4111,11 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== +immutable@^4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0" + integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw== + import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" @@ -6943,7 +6963,7 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -use-sync-external-store@^1.2.0: +use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== @@ -7288,3 +7308,10 @@ zod@^3.22.4: version "3.22.4" resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== + +zustand@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.1.tgz#2088956ee454759fb8b866ca335a2373e76736c5" + integrity sha512-XlauQmH64xXSC1qGYNv00ODaQ3B+tNPoy22jv2diYiP4eoDKr9LA+Bh5Bc3gplTrFdb6JVI+N4kc1DZ/tbtfPg== + dependencies: + use-sync-external-store "1.2.0" From 4d79d6d4a0d330d8996a55ff96f3dbd20a1c9a55 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Thu, 29 Feb 2024 10:48:10 +0100 Subject: [PATCH 005/197] Added Project layout --- src/renderer/react/routeTree.gen.ts | 18 ++- .../react/routes/projects/$projectId.tsx | 137 ++++++++++++++++++ .../routes/projects/$projectId/index.tsx | 128 +--------------- 3 files changed, 153 insertions(+), 130 deletions(-) create mode 100644 src/renderer/react/routes/projects/$projectId.tsx diff --git a/src/renderer/react/routeTree.gen.ts b/src/renderer/react/routeTree.gen.ts index 0ac442aa..ff6af058 100644 --- a/src/renderer/react/routeTree.gen.ts +++ b/src/renderer/react/routeTree.gen.ts @@ -15,6 +15,7 @@ import { Route as IndexImport } from './routes/index' import { Route as ProjectsIndexImport } from './routes/projects/index' import { Route as UserSetImport } from './routes/user/set' import { Route as ProjectsCreateImport } from './routes/projects/create' +import { Route as ProjectsProjectIdImport } from './routes/projects/$projectId' import { Route as ProjectsProjectIdIndexImport } from './routes/projects/$projectId/index' // Create/Update Routes @@ -39,11 +40,16 @@ const ProjectsCreateRoute = ProjectsCreateImport.update({ getParentRoute: () => rootRoute, } as any) -const ProjectsProjectIdIndexRoute = ProjectsProjectIdIndexImport.update({ - path: '/projects/$projectId/', +const ProjectsProjectIdRoute = ProjectsProjectIdImport.update({ + path: '/projects/$projectId', getParentRoute: () => rootRoute, } as any) +const ProjectsProjectIdIndexRoute = ProjectsProjectIdIndexImport.update({ + path: '/', + getParentRoute: () => ProjectsProjectIdRoute, +} as any) + // Populate the FileRoutesByPath interface declare module '@tanstack/react-router' { @@ -52,6 +58,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexImport parentRoute: typeof rootRoute } + '/projects/$projectId': { + preLoaderRoute: typeof ProjectsProjectIdImport + parentRoute: typeof rootRoute + } '/projects/create': { preLoaderRoute: typeof ProjectsCreateImport parentRoute: typeof rootRoute @@ -66,7 +76,7 @@ declare module '@tanstack/react-router' { } '/projects/$projectId/': { preLoaderRoute: typeof ProjectsProjectIdIndexImport - parentRoute: typeof rootRoute + parentRoute: typeof ProjectsProjectIdImport } } } @@ -75,10 +85,10 @@ declare module '@tanstack/react-router' { export const routeTree = rootRoute.addChildren([ IndexRoute, + ProjectsProjectIdRoute.addChildren([ProjectsProjectIdIndexRoute]), ProjectsCreateRoute, UserSetRoute, ProjectsIndexRoute, - ProjectsProjectIdIndexRoute, ]) /* prettier-ignore-end */ diff --git a/src/renderer/react/routes/projects/$projectId.tsx b/src/renderer/react/routes/projects/$projectId.tsx new file mode 100644 index 00000000..088ff4c6 --- /dev/null +++ b/src/renderer/react/routes/projects/$projectId.tsx @@ -0,0 +1,137 @@ +import { SearchResult } from '@elek-io/shared'; +import { NotificationIntent } from '@elek-io/ui'; +import { + FolderOpenIcon, + HomeIcon, + MagnifyingGlassIcon, +} from '@heroicons/react/20/solid'; +import { Link, Outlet, createFileRoute } from '@tanstack/react-router'; +import { ChangeEvent, useState } from 'react'; +import { useStore } from '../../store'; + +export const Route = createFileRoute('/projects/$projectId')({ + beforeLoad: async ({ context, params }) => { + const currentProject = await context.core.projects.read({ + id: params.projectId, + }); + + return { currentProject }; + }, + component: ProjectLayout, +}); + +function ProjectLayout() { + const addNotification = useStore((state) => state.addNotification); + const context = Route.useRouteContext(); + const [searchQuery, setSearchQuery] = useState(''); + const [searchResult, setSearchResult] = useState(); + + async function onSearch(event: ChangeEvent) { + setSearchQuery(event.target.value); + try { + const searchResult = await context.core.projects.search( + context.currentProject.id, + searchQuery + ); + setSearchResult(searchResult); + console.log('Searched: ', { + query: searchQuery, + result: searchResult, + }); + } catch (error) { + console.error(error); + addNotification({ + intent: NotificationIntent.DANGER, + title: 'Search failed', + description: 'There was an error searching for the provided query.', + }); + } + } + + return ( +
+ +
+ +
+
+ ); +} diff --git a/src/renderer/react/routes/projects/$projectId/index.tsx b/src/renderer/react/routes/projects/$projectId/index.tsx index 94bac96d..2c13c14a 100644 --- a/src/renderer/react/routes/projects/$projectId/index.tsx +++ b/src/renderer/react/routes/projects/$projectId/index.tsx @@ -1,137 +1,13 @@ -import { SearchResult } from '@elek-io/shared'; -import { NotificationIntent } from '@elek-io/ui'; -import { - FolderOpenIcon, - HomeIcon, - MagnifyingGlassIcon, -} from '@heroicons/react/20/solid'; -import { Link, createFileRoute } from '@tanstack/react-router'; -import { ChangeEvent, useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router'; import { useStore } from '../../../store'; export const Route = createFileRoute('/projects/$projectId/')({ - beforeLoad: async ({ context, params }) => { - const currentProject = await context.core.projects.read({ - id: params.projectId, - }); - - return { currentProject }; - }, component: ProjectDashboardPage, }); function ProjectDashboardPage() { const addNotification = useStore((state) => state.addNotification); const context = Route.useRouteContext(); - const [searchQuery, setSearchQuery] = useState(''); - const [searchResult, setSearchResult] = useState(); - - async function onSearch(event: ChangeEvent) { - setSearchQuery(event.target.value); - try { - const searchResult = await context.core.projects.search( - context.currentProject.id, - searchQuery - ); - setSearchResult(searchResult); - console.log('Searched: ', { - query: searchQuery, - result: searchResult, - }); - } catch (error) { - console.error(error); - addNotification({ - intent: NotificationIntent.DANGER, - title: 'Search failed', - description: 'There was an error searching for the provided query.', - }); - } - } - return ( -
- -
-
Dashboard for Project "{context.currentProject.name}"
-
-
- ); + return
Dashboard for Project "{context.currentProject.name}"
; } From 4fce4c95d8c6613dadc4222f4491fa0a8063482e Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Thu, 29 Feb 2024 11:38:32 +0100 Subject: [PATCH 006/197] Added settings page --- src/renderer/react/routeTree.gen.ts | 28 ++- src/renderer/react/routes/__root.tsx | 2 +- src/renderer/react/routes/index.tsx | 5 - .../react/routes/projects/$projectId.tsx | 59 +++--- .../routes/projects/$projectId/dashboard.tsx | 49 +++++ .../routes/projects/$projectId/index.tsx | 16 +- .../routes/projects/$projectId/settings.tsx | 196 ++++++++++++++++++ src/renderer/react/routes/projects/index.tsx | 2 +- 8 files changed, 311 insertions(+), 46 deletions(-) create mode 100644 src/renderer/react/routes/projects/$projectId/dashboard.tsx create mode 100644 src/renderer/react/routes/projects/$projectId/settings.tsx diff --git a/src/renderer/react/routeTree.gen.ts b/src/renderer/react/routeTree.gen.ts index ff6af058..200d9956 100644 --- a/src/renderer/react/routeTree.gen.ts +++ b/src/renderer/react/routeTree.gen.ts @@ -17,6 +17,8 @@ import { Route as UserSetImport } from './routes/user/set' import { Route as ProjectsCreateImport } from './routes/projects/create' import { Route as ProjectsProjectIdImport } from './routes/projects/$projectId' import { Route as ProjectsProjectIdIndexImport } from './routes/projects/$projectId/index' +import { Route as ProjectsProjectIdSettingsImport } from './routes/projects/$projectId/settings' +import { Route as ProjectsProjectIdDashboardImport } from './routes/projects/$projectId/dashboard' // Create/Update Routes @@ -50,6 +52,18 @@ const ProjectsProjectIdIndexRoute = ProjectsProjectIdIndexImport.update({ getParentRoute: () => ProjectsProjectIdRoute, } as any) +const ProjectsProjectIdSettingsRoute = ProjectsProjectIdSettingsImport.update({ + path: '/settings', + getParentRoute: () => ProjectsProjectIdRoute, +} as any) + +const ProjectsProjectIdDashboardRoute = ProjectsProjectIdDashboardImport.update( + { + path: '/dashboard', + getParentRoute: () => ProjectsProjectIdRoute, + } as any, +) + // Populate the FileRoutesByPath interface declare module '@tanstack/react-router' { @@ -74,6 +88,14 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProjectsIndexImport parentRoute: typeof rootRoute } + '/projects/$projectId/dashboard': { + preLoaderRoute: typeof ProjectsProjectIdDashboardImport + parentRoute: typeof ProjectsProjectIdImport + } + '/projects/$projectId/settings': { + preLoaderRoute: typeof ProjectsProjectIdSettingsImport + parentRoute: typeof ProjectsProjectIdImport + } '/projects/$projectId/': { preLoaderRoute: typeof ProjectsProjectIdIndexImport parentRoute: typeof ProjectsProjectIdImport @@ -85,7 +107,11 @@ declare module '@tanstack/react-router' { export const routeTree = rootRoute.addChildren([ IndexRoute, - ProjectsProjectIdRoute.addChildren([ProjectsProjectIdIndexRoute]), + ProjectsProjectIdRoute.addChildren([ + ProjectsProjectIdDashboardRoute, + ProjectsProjectIdSettingsRoute, + ProjectsProjectIdIndexRoute, + ]), ProjectsCreateRoute, UserSetRoute, ProjectsIndexRoute, diff --git a/src/renderer/react/routes/__root.tsx b/src/renderer/react/routes/__root.tsx index b11b9f9a..59011f94 100644 --- a/src/renderer/react/routes/__root.tsx +++ b/src/renderer/react/routes/__root.tsx @@ -45,8 +45,8 @@ function RootRoute() { const context = Route.useRouteContext(); const state = useRouterState(); const breadcrumbs = state.location.pathname - .substring(1) // remove first slash (home) .split('/') + .filter((value) => value) // Filter out empty values for beginning or ending slashes .map((part, index, array) => { const path = array.slice(0, index + 1).join('/'); diff --git a/src/renderer/react/routes/index.tsx b/src/renderer/react/routes/index.tsx index cfb4550d..6c06987b 100644 --- a/src/renderer/react/routes/index.tsx +++ b/src/renderer/react/routes/index.tsx @@ -6,9 +6,4 @@ export const Route = createFileRoute('/')({ to: '/projects', }); }, - component: Index, }); - -function Index() { - return <>; -} diff --git a/src/renderer/react/routes/projects/$projectId.tsx b/src/renderer/react/routes/projects/$projectId.tsx index 088ff4c6..4838a38d 100644 --- a/src/renderer/react/routes/projects/$projectId.tsx +++ b/src/renderer/react/routes/projects/$projectId.tsx @@ -1,6 +1,7 @@ import { SearchResult } from '@elek-io/shared'; import { NotificationIntent } from '@elek-io/ui'; import { + Cog6ToothIcon, FolderOpenIcon, HomeIcon, MagnifyingGlassIcon, @@ -25,6 +26,18 @@ function ProjectLayout() { const context = Route.useRouteContext(); const [searchQuery, setSearchQuery] = useState(''); const [searchResult, setSearchResult] = useState(); + const projectNavigation = [ + { + name: 'Dashboard', + to: '/projects/$projectId/dashboard', + icon: HomeIcon, + }, + { + name: 'Settings', + to: '/projects/$projectId/settings', + icon: Cog6ToothIcon, + }, + ]; async function onSearch(event: ChangeEvent) { setSearchQuery(event.target.value); @@ -97,34 +110,24 @@ function ProjectLayout() { diff --git a/src/renderer/react/routes/projects/$projectId/dashboard.tsx b/src/renderer/react/routes/projects/$projectId/dashboard.tsx new file mode 100644 index 00000000..416b7d53 --- /dev/null +++ b/src/renderer/react/routes/projects/$projectId/dashboard.tsx @@ -0,0 +1,49 @@ +import { Page } from '@elek-io/ui'; +import { createFileRoute } from '@tanstack/react-router'; +import { ReactElement } from 'react'; + +export const Route = createFileRoute('/projects/$projectId/dashboard')({ + component: ProjectDashboardPage, +}); + +function ProjectDashboardPage() { + const context = Route.useRouteContext(); + + function Description(): ReactElement { + return <>The Dashboard gives you an overview of your project.; + } + + function Actions(): ReactElement { + return ( + <> + {/* */} + + ); + } + + return ( + } + actions={} + > +
+
+
+ Current Project: {JSON.stringify(context.currentProject)} +
+
+
+
Test
+
+
+
+ ); +} diff --git a/src/renderer/react/routes/projects/$projectId/index.tsx b/src/renderer/react/routes/projects/$projectId/index.tsx index 2c13c14a..d4b0ce48 100644 --- a/src/renderer/react/routes/projects/$projectId/index.tsx +++ b/src/renderer/react/routes/projects/$projectId/index.tsx @@ -1,13 +1,9 @@ -import { createFileRoute } from '@tanstack/react-router'; -import { useStore } from '../../../store'; +import { createFileRoute, redirect } from '@tanstack/react-router'; export const Route = createFileRoute('/projects/$projectId/')({ - component: ProjectDashboardPage, + beforeLoad: async () => { + throw redirect({ + to: '/projects/$projectId/dashboard', + }); + }, }); - -function ProjectDashboardPage() { - const addNotification = useStore((state) => state.addNotification); - const context = Route.useRouteContext(); - - return
Dashboard for Project "{context.currentProject.name}"
; -} diff --git a/src/renderer/react/routes/projects/$projectId/settings.tsx b/src/renderer/react/routes/projects/$projectId/settings.tsx new file mode 100644 index 00000000..7d2955fe --- /dev/null +++ b/src/renderer/react/routes/projects/$projectId/settings.tsx @@ -0,0 +1,196 @@ +import { Project } from '@elek-io/shared'; +import { + Button, + FormInput, + FormTextarea, + NotificationIntent, + Page, +} from '@elek-io/ui'; +import { CheckIcon, TrashIcon } from '@heroicons/react/20/solid'; +import { createFileRoute, useRouter } from '@tanstack/react-router'; +import { ReactElement, useState } from 'react'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { useStore } from '../../../store'; + +export const Route = createFileRoute('/projects/$projectId/settings')({ + component: ProjectSettingsPage, +}); + +function ProjectSettingsPage() { + const addNotification = useStore((state) => state.addNotification); + const router = useRouter(); + const context = Route.useRouteContext(); + const [isUpdatingProject, setIsUpdatingProject] = useState(false); + const { + register, + handleSubmit, + watch, + control, + formState: { errors, isDirty }, + } = useForm({ defaultValues: context.currentProject }); + + function Description(): ReactElement { + return <>Here you will be able to tweak this project to your liking; + } + + /** + * @todo Save Button should be in state "disabled" until there is a difference between props.currentProject and formData. + * Otherwise git is throwing an error without a message (probably because there is no change that can be committed) + */ + function Actions(): ReactElement { + return ( + <> + + + ); + } + + const onUpdate: SubmitHandler = async (project) => { + try { + setIsUpdatingProject(true); + await window.ipc.core.projects.update(project); + setIsUpdatingProject(false); + addNotification({ + intent: NotificationIntent.SUCCESS, + title: 'Successfully updated Project', + description: 'The Project was successfully updated.', + }); + // await props.reloadCurrentProject(); + } catch (error) { + setIsUpdatingProject(false); + console.error(error); + addNotification({ + intent: NotificationIntent.DANGER, + title: 'Failed to update Project', + description: 'There was an error updating the Project on disk.', + }); + } + }; + + const onDelete: SubmitHandler = async (project) => { + try { + await window.ipc.core.projects.delete({ id: project.id }); + addNotification({ + intent: NotificationIntent.SUCCESS, + title: 'Successfully deleted Project', + description: 'The Project was successfully deleted.', + }); + router.navigate({ to: '/projects' }); + } catch (error) { + console.error(error); + addNotification({ + intent: NotificationIntent.DANGER, + title: 'Failed to delete Project', + description: 'There was an error deleting the Project from disk.', + }); + } + }; + + return ( + } + actions={} + > +
+ + +
+ {/*

Original Project: {JSON.stringify(props.currentProject)}

+

Changed Project: {JSON.stringify(watch())}

+
*/} +
+
+ +
+
+ +
+
+
+

+ Danger Zone +

+
+
+

+ Delete this Project +

+

+ Once you delete a Project, there is no going back. Please be + certain. +

+
+
+ +
+
+
+
+
+
+ ); +} diff --git a/src/renderer/react/routes/projects/index.tsx b/src/renderer/react/routes/projects/index.tsx index 30ccfdd9..09b4d17e 100644 --- a/src/renderer/react/routes/projects/index.tsx +++ b/src/renderer/react/routes/projects/index.tsx @@ -54,7 +54,7 @@ function ListProjectsPage() { {context.projects.list.map((project) => (
  • From 71d86c6adeb8e527949d9f5df2a73b7b21a4a056 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Thu, 29 Feb 2024 12:16:29 +0100 Subject: [PATCH 007/197] Added Assets page. Also added electron ipc and zustand store to tanstack router context --- src/renderer/react/app.tsx | 3 +- src/renderer/react/routeTree.gen.ts | 12 ++ src/renderer/react/routes/__root.tsx | 4 + .../react/routes/projects/$projectId.tsx | 9 +- .../projects/$projectId/assets/index.tsx | 187 ++++++++++++++++++ .../routes/projects/$projectId/settings.tsx | 3 +- src/renderer/react/routes/projects/create.tsx | 3 +- src/renderer/react/store.ts | 2 +- 8 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 src/renderer/react/routes/projects/$projectId/assets/index.tsx diff --git a/src/renderer/react/app.tsx b/src/renderer/react/app.tsx index a7aa6f3a..64d6702d 100644 --- a/src/renderer/react/app.tsx +++ b/src/renderer/react/app.tsx @@ -9,6 +9,7 @@ import { StrictMode } from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import { ipc } from './ipc'; +import { useStore } from './store'; // Import the generated route tree import { routeTree } from './routeTree.gen'; @@ -18,7 +19,7 @@ const hashHistory = createHashHistory(); // Use hash based routing since in prod const router = createRouter({ routeTree, history: hashHistory, - context: { core: ipc.core }, + context: { electron: ipc.electron, core: ipc.core, store: useStore }, }); // Register the router instance for type safety diff --git a/src/renderer/react/routeTree.gen.ts b/src/renderer/react/routeTree.gen.ts index 200d9956..dce0abab 100644 --- a/src/renderer/react/routeTree.gen.ts +++ b/src/renderer/react/routeTree.gen.ts @@ -19,6 +19,7 @@ import { Route as ProjectsProjectIdImport } from './routes/projects/$projectId' import { Route as ProjectsProjectIdIndexImport } from './routes/projects/$projectId/index' import { Route as ProjectsProjectIdSettingsImport } from './routes/projects/$projectId/settings' import { Route as ProjectsProjectIdDashboardImport } from './routes/projects/$projectId/dashboard' +import { Route as ProjectsProjectIdAssetsIndexImport } from './routes/projects/$projectId/assets/index' // Create/Update Routes @@ -64,6 +65,12 @@ const ProjectsProjectIdDashboardRoute = ProjectsProjectIdDashboardImport.update( } as any, ) +const ProjectsProjectIdAssetsIndexRoute = + ProjectsProjectIdAssetsIndexImport.update({ + path: '/assets/', + getParentRoute: () => ProjectsProjectIdRoute, + } as any) + // Populate the FileRoutesByPath interface declare module '@tanstack/react-router' { @@ -100,6 +107,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProjectsProjectIdIndexImport parentRoute: typeof ProjectsProjectIdImport } + '/projects/$projectId/assets/': { + preLoaderRoute: typeof ProjectsProjectIdAssetsIndexImport + parentRoute: typeof ProjectsProjectIdImport + } } } @@ -111,6 +122,7 @@ export const routeTree = rootRoute.addChildren([ ProjectsProjectIdDashboardRoute, ProjectsProjectIdSettingsRoute, ProjectsProjectIdIndexRoute, + ProjectsProjectIdAssetsIndexRoute, ]), ProjectsCreateRoute, UserSetRoute, diff --git a/src/renderer/react/routes/__root.tsx b/src/renderer/react/routes/__root.tsx index 59011f94..68cf276b 100644 --- a/src/renderer/react/routes/__root.tsx +++ b/src/renderer/react/routes/__root.tsx @@ -16,10 +16,14 @@ import { useRouterState, } from '@tanstack/react-router'; import { TanStackRouterDevtools } from '@tanstack/router-devtools'; +import { StoreApi, UseBoundStore } from 'zustand'; import { ContextBridgeApi } from '../../preload'; +import { StoreState } from '../store'; interface RouterContext { + electron: ContextBridgeApi['electron']; core: ContextBridgeApi['core']; + store: UseBoundStore>; } // Use the routerContext to create your root route diff --git a/src/renderer/react/routes/projects/$projectId.tsx b/src/renderer/react/routes/projects/$projectId.tsx index 4838a38d..ed657caa 100644 --- a/src/renderer/react/routes/projects/$projectId.tsx +++ b/src/renderer/react/routes/projects/$projectId.tsx @@ -5,10 +5,10 @@ import { FolderOpenIcon, HomeIcon, MagnifyingGlassIcon, + PhotoIcon, } from '@heroicons/react/20/solid'; import { Link, Outlet, createFileRoute } from '@tanstack/react-router'; import { ChangeEvent, useState } from 'react'; -import { useStore } from '../../store'; export const Route = createFileRoute('/projects/$projectId')({ beforeLoad: async ({ context, params }) => { @@ -22,8 +22,8 @@ export const Route = createFileRoute('/projects/$projectId')({ }); function ProjectLayout() { - const addNotification = useStore((state) => state.addNotification); const context = Route.useRouteContext(); + const addNotification = context.store((state) => state.addNotification); const [searchQuery, setSearchQuery] = useState(''); const [searchResult, setSearchResult] = useState(); const projectNavigation = [ @@ -32,6 +32,11 @@ function ProjectLayout() { to: '/projects/$projectId/dashboard', icon: HomeIcon, }, + { + name: 'Assets', + to: '/projects/$projectId/assets', + icon: PhotoIcon, + }, { name: 'Settings', to: '/projects/$projectId/settings', diff --git a/src/renderer/react/routes/projects/$projectId/assets/index.tsx b/src/renderer/react/routes/projects/$projectId/assets/index.tsx new file mode 100644 index 00000000..04a811fe --- /dev/null +++ b/src/renderer/react/routes/projects/$projectId/assets/index.tsx @@ -0,0 +1,187 @@ +import { Asset } from '@elek-io/shared'; +import { AssetTeaser, EmptyState, NotificationIntent, Page } from '@elek-io/ui'; +import { PhotoIcon } from '@heroicons/react/20/solid'; +import { createFileRoute, useRouter } from '@tanstack/react-router'; +import { ReactElement } from 'react'; + +export const Route = createFileRoute('/projects/$projectId/assets/')({ + beforeLoad: async ({ context, params }) => { + const currentAssets = await context.core.assets.list({ + projectId: params.projectId, + }); + // Change path to use custom protocoll + currentAssets.list = currentAssets.list.map((asset) => { + return { + ...asset, + absolutePath: 'elek-io-local-file://' + asset.absolutePath, + }; + }); + + return { currentAssets }; + }, + component: ProjectAssetsPage, +}); + +function ProjectAssetsPage() { + const router = useRouter(); + const context = Route.useRouteContext(); + const addNotification = context.store((state) => state.addNotification); + + function Description(): ReactElement { + return ( + <> + An Asset is a file like an image, PDF or other document that can be used + inside your Collections. + + ); + } + + function Actions(): ReactElement { + return ( + <> + {/* */} + + ); + } + + async function onAddAssetClicked() { + try { + const result = await context.electron.dialog.showOpenDialog({ + title: 'Select Assets to add', + buttonLabel: 'Add to Assets', + properties: ['openFile', 'multiSelections'], + // filters: [ + // { name: 'Supported files', extensions: [...supportedExtensions] }, + // ], + }); + console.log('Selected files from dialog: ', result); + if (result.canceled === true) { + return; + } + await createAssetsFromPaths(result.filePaths); + router.invalidate(); + } catch (error) { + console.error(error); + addNotification({ + intent: NotificationIntent.DANGER, + title: 'Failed to open dialog', + description: 'There was an error showing the file select dialog.', + }); + } + } + + // async function onAssetDelete() { + // if (!selectedAsset || !props.currentProject) { + // addNotification({ + // intent: NotificationIntent.DANGER, + // title: 'Failed to delete Asset', + // description: 'There was an error deleting the Asset from disk.', + // }); + // return; + // } + + // try { + // await window.ipc.core.assets.delete({ + // ...selectedAsset, + // projectId: props.currentProject.id, + // }); + // addNotification({ + // intent: NotificationIntent.SUCCESS, + // title: 'Successfully deleted Asset', + // description: 'The Asset was deleted successfully.', + // }); + // setIsAsideOpen(false); + // setSelectedAsset(undefined); + // await reloadAssets(); + // } catch (error) { + // console.error(error); + // addNotification({ + // intent: NotificationIntent.DANGER, + // title: 'Failed to delete Asset', + // description: 'There was an error deleting the Asset from disk.', + // }); + // } + // } + + async function createAssetsFromPaths(paths: string[]) { + const assetPromisses: Promise[] = []; + + for (const path of paths) { + assetPromisses.push( + context.core.assets.create({ + name: path.split('/').pop() || '', + description: '', + projectId: context.currentProject.id, + language: 'en', // @todo user should select what language the file should be assigned to + filePath: path, + }) + ); + } + + const results = await Promise.all(assetPromisses); + console.log('Asset create results: ', results); + } + + /** + * @todo This creates one commit for all instead on one per uploaded file, how is this possible? + */ + async function onAssetsDropped(event: DragEvent) { + event.preventDefault(); + + console.log('Dropped: ', event); + + const paths = [...event.dataTransfer.items].map((item) => { + const file = item.getAsFile(); + if (file) { + return file.path; + } + return ''; + }); + await createAssetsFromPaths(paths); + Route.update({}); + } + + return ( + } + actions={} + layout="overlap" + > +
      +
    • + + {/* */} +
    • + {context.currentAssets.list.map((asset) => ( +
    • + +
    • + ))} +
    +
    + ); +} diff --git a/src/renderer/react/routes/projects/$projectId/settings.tsx b/src/renderer/react/routes/projects/$projectId/settings.tsx index 7d2955fe..76a9f6e6 100644 --- a/src/renderer/react/routes/projects/$projectId/settings.tsx +++ b/src/renderer/react/routes/projects/$projectId/settings.tsx @@ -10,16 +10,15 @@ import { CheckIcon, TrashIcon } from '@heroicons/react/20/solid'; import { createFileRoute, useRouter } from '@tanstack/react-router'; import { ReactElement, useState } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; -import { useStore } from '../../../store'; export const Route = createFileRoute('/projects/$projectId/settings')({ component: ProjectSettingsPage, }); function ProjectSettingsPage() { - const addNotification = useStore((state) => state.addNotification); const router = useRouter(); const context = Route.useRouteContext(); + const addNotification = context.store((state) => state.addNotification); const [isUpdatingProject, setIsUpdatingProject] = useState(false); const { register, diff --git a/src/renderer/react/routes/projects/create.tsx b/src/renderer/react/routes/projects/create.tsx index a7222d4d..0b254a4c 100644 --- a/src/renderer/react/routes/projects/create.tsx +++ b/src/renderer/react/routes/projects/create.tsx @@ -4,16 +4,15 @@ import { CheckIcon } from '@heroicons/react/20/solid'; import { createFileRoute, useRouter } from '@tanstack/react-router'; import { ReactElement, useState } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; -import { useStore } from '../../store'; export const Route = createFileRoute('/projects/create')({ component: CreateProjectPage, }); function CreateProjectPage() { - const addNotification = useStore((state) => state.addNotification); const router = useRouter(); const context = Route.useRouteContext(); + const addNotification = context.store((state) => state.addNotification); const data = Route.useLoaderData(); const [isCreatingProject, setCreatingProject] = useState(false); const { diff --git a/src/renderer/react/store.ts b/src/renderer/react/store.ts index 53c3aef1..a441297e 100644 --- a/src/renderer/react/store.ts +++ b/src/renderer/react/store.ts @@ -3,7 +3,7 @@ import type {} from '@redux-devtools/extension'; // required for devtools typing import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; -interface StoreState { +export interface StoreState { notifications: NotificationProps[]; addNotification: (notification: NotificationProps) => void; } From 6fb19c86f065300623feea2359a73ec120c960f9 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Thu, 29 Feb 2024 15:13:54 +0100 Subject: [PATCH 008/197] Added more Asset logic --- src/renderer/react/routes/__root.tsx | 2 +- .../react/routes/projects/$projectId.tsx | 6 +- .../projects/$projectId/assets/index.tsx | 201 +++++++++++++----- 3 files changed, 149 insertions(+), 60 deletions(-) diff --git a/src/renderer/react/routes/__root.tsx b/src/renderer/react/routes/__root.tsx index 68cf276b..581b6132 100644 --- a/src/renderer/react/routes/__root.tsx +++ b/src/renderer/react/routes/__root.tsx @@ -90,7 +90,7 @@ function RootRoute() { + ); } From f48301ffa89a0ad2f3e8462a1c70456ada36454d Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Thu, 29 Feb 2024 15:21:27 +0100 Subject: [PATCH 009/197] Removed EmptyState component in favor of standard button to add more Assets --- .../projects/$projectId/assets/index.tsx | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/src/renderer/react/routes/projects/$projectId/assets/index.tsx b/src/renderer/react/routes/projects/$projectId/assets/index.tsx index e6977e52..d35ff706 100644 --- a/src/renderer/react/routes/projects/$projectId/assets/index.tsx +++ b/src/renderer/react/routes/projects/$projectId/assets/index.tsx @@ -2,13 +2,12 @@ import { Asset } from '@elek-io/shared'; import { AssetTeaser, Button, - EmptyState, NotificationIntent, Page, formatBytes, formatTimestamp, } from '@elek-io/ui'; -import { PhotoIcon, TrashIcon } from '@heroicons/react/20/solid'; +import { PhotoIcon, PlusIcon, TrashIcon } from '@heroicons/react/20/solid'; import { createFileRoute, useRouter } from '@tanstack/react-router'; import { ReactElement, useState } from 'react'; @@ -64,13 +63,13 @@ function ProjectAssetsPage() { function Actions(): ReactElement { return ( <> - {/* */} + Add Assets + ); } @@ -178,30 +177,12 @@ function ProjectAssetsPage() { actions={} layout="overlap" > -
    +
      -
    • - - {/* */} -
    • {context.currentAssets.list.map((asset) => (
    • Date: Thu, 29 Feb 2024 19:57:53 +0100 Subject: [PATCH 010/197] Added basic Collections pages --- package.json | 1 + src/renderer/react/routeTree.gen.ts | 74 ++++ .../react/routes/projects/$projectId.tsx | 41 +- .../projects/$projectId/collections.tsx | 73 ++++ .../$projectId/collections/$collectionId.tsx | 17 + .../$collectionId/$entryId/index.tsx | 5 + .../collections/$collectionId/create.tsx | 146 +++++++ .../collections/$collectionId/index.tsx | 9 + .../$projectId/collections/create.tsx | 399 ++++++++++++++++++ yarn.lock | 5 + 10 files changed, 766 insertions(+), 4 deletions(-) create mode 100644 src/renderer/react/routes/projects/$projectId/collections.tsx create mode 100644 src/renderer/react/routes/projects/$projectId/collections/$collectionId.tsx create mode 100644 src/renderer/react/routes/projects/$projectId/collections/$collectionId/$entryId/index.tsx create mode 100644 src/renderer/react/routes/projects/$projectId/collections/$collectionId/create.tsx create mode 100644 src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx create mode 100644 src/renderer/react/routes/projects/$projectId/collections/create.tsx diff --git a/package.json b/package.json index 75cdbded..e0bba03a 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@fontsource-variable/montserrat": "^5.0.17", "@fontsource/roboto": "^5.0.8", "@heroicons/react": "^2.1.1", + "@hookform/resolvers": "^3.3.4", "@sentry/electron": "^4.17.0", "@sentry/react": "7.92.0", "@sentry/vite-plugin": "^2.13.0", diff --git a/src/renderer/react/routeTree.gen.ts b/src/renderer/react/routeTree.gen.ts index dce0abab..c9b34338 100644 --- a/src/renderer/react/routeTree.gen.ts +++ b/src/renderer/react/routeTree.gen.ts @@ -19,7 +19,13 @@ import { Route as ProjectsProjectIdImport } from './routes/projects/$projectId' import { Route as ProjectsProjectIdIndexImport } from './routes/projects/$projectId/index' import { Route as ProjectsProjectIdSettingsImport } from './routes/projects/$projectId/settings' import { Route as ProjectsProjectIdDashboardImport } from './routes/projects/$projectId/dashboard' +import { Route as ProjectsProjectIdCollectionsImport } from './routes/projects/$projectId/collections' import { Route as ProjectsProjectIdAssetsIndexImport } from './routes/projects/$projectId/assets/index' +import { Route as ProjectsProjectIdCollectionsCreateImport } from './routes/projects/$projectId/collections/create' +import { Route as ProjectsProjectIdCollectionsCollectionIdImport } from './routes/projects/$projectId/collections/$collectionId' +import { Route as ProjectsProjectIdCollectionsCollectionIdIndexImport } from './routes/projects/$projectId/collections/$collectionId/index' +import { Route as ProjectsProjectIdCollectionsCollectionIdCreateImport } from './routes/projects/$projectId/collections/$collectionId/create' +import { Route as ProjectsProjectIdCollectionsCollectionIdEntryIdIndexImport } from './routes/projects/$projectId/collections/$collectionId/$entryId/index' // Create/Update Routes @@ -65,12 +71,48 @@ const ProjectsProjectIdDashboardRoute = ProjectsProjectIdDashboardImport.update( } as any, ) +const ProjectsProjectIdCollectionsRoute = + ProjectsProjectIdCollectionsImport.update({ + path: '/collections', + getParentRoute: () => ProjectsProjectIdRoute, + } as any) + const ProjectsProjectIdAssetsIndexRoute = ProjectsProjectIdAssetsIndexImport.update({ path: '/assets/', getParentRoute: () => ProjectsProjectIdRoute, } as any) +const ProjectsProjectIdCollectionsCreateRoute = + ProjectsProjectIdCollectionsCreateImport.update({ + path: '/create', + getParentRoute: () => ProjectsProjectIdCollectionsRoute, + } as any) + +const ProjectsProjectIdCollectionsCollectionIdRoute = + ProjectsProjectIdCollectionsCollectionIdImport.update({ + path: '/$collectionId', + getParentRoute: () => ProjectsProjectIdCollectionsRoute, + } as any) + +const ProjectsProjectIdCollectionsCollectionIdIndexRoute = + ProjectsProjectIdCollectionsCollectionIdIndexImport.update({ + path: '/', + getParentRoute: () => ProjectsProjectIdCollectionsCollectionIdRoute, + } as any) + +const ProjectsProjectIdCollectionsCollectionIdCreateRoute = + ProjectsProjectIdCollectionsCollectionIdCreateImport.update({ + path: '/create', + getParentRoute: () => ProjectsProjectIdCollectionsCollectionIdRoute, + } as any) + +const ProjectsProjectIdCollectionsCollectionIdEntryIdIndexRoute = + ProjectsProjectIdCollectionsCollectionIdEntryIdIndexImport.update({ + path: '/$entryId/', + getParentRoute: () => ProjectsProjectIdCollectionsCollectionIdRoute, + } as any) + // Populate the FileRoutesByPath interface declare module '@tanstack/react-router' { @@ -95,6 +137,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProjectsIndexImport parentRoute: typeof rootRoute } + '/projects/$projectId/collections': { + preLoaderRoute: typeof ProjectsProjectIdCollectionsImport + parentRoute: typeof ProjectsProjectIdImport + } '/projects/$projectId/dashboard': { preLoaderRoute: typeof ProjectsProjectIdDashboardImport parentRoute: typeof ProjectsProjectIdImport @@ -107,10 +153,30 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProjectsProjectIdIndexImport parentRoute: typeof ProjectsProjectIdImport } + '/projects/$projectId/collections/$collectionId': { + preLoaderRoute: typeof ProjectsProjectIdCollectionsCollectionIdImport + parentRoute: typeof ProjectsProjectIdCollectionsImport + } + '/projects/$projectId/collections/create': { + preLoaderRoute: typeof ProjectsProjectIdCollectionsCreateImport + parentRoute: typeof ProjectsProjectIdCollectionsImport + } '/projects/$projectId/assets/': { preLoaderRoute: typeof ProjectsProjectIdAssetsIndexImport parentRoute: typeof ProjectsProjectIdImport } + '/projects/$projectId/collections/$collectionId/create': { + preLoaderRoute: typeof ProjectsProjectIdCollectionsCollectionIdCreateImport + parentRoute: typeof ProjectsProjectIdCollectionsCollectionIdImport + } + '/projects/$projectId/collections/$collectionId/': { + preLoaderRoute: typeof ProjectsProjectIdCollectionsCollectionIdIndexImport + parentRoute: typeof ProjectsProjectIdCollectionsCollectionIdImport + } + '/projects/$projectId/collections/$collectionId/$entryId/': { + preLoaderRoute: typeof ProjectsProjectIdCollectionsCollectionIdEntryIdIndexImport + parentRoute: typeof ProjectsProjectIdCollectionsCollectionIdImport + } } } @@ -119,6 +185,14 @@ declare module '@tanstack/react-router' { export const routeTree = rootRoute.addChildren([ IndexRoute, ProjectsProjectIdRoute.addChildren([ + ProjectsProjectIdCollectionsRoute.addChildren([ + ProjectsProjectIdCollectionsCollectionIdRoute.addChildren([ + ProjectsProjectIdCollectionsCollectionIdCreateRoute, + ProjectsProjectIdCollectionsCollectionIdIndexRoute, + ProjectsProjectIdCollectionsCollectionIdEntryIdIndexRoute, + ]), + ProjectsProjectIdCollectionsCreateRoute, + ]), ProjectsProjectIdDashboardRoute, ProjectsProjectIdSettingsRoute, ProjectsProjectIdIndexRoute, diff --git a/src/renderer/react/routes/projects/$projectId.tsx b/src/renderer/react/routes/projects/$projectId.tsx index c615ba8f..c5e37802 100644 --- a/src/renderer/react/routes/projects/$projectId.tsx +++ b/src/renderer/react/routes/projects/$projectId.tsx @@ -1,7 +1,8 @@ -import { SearchResult } from '@elek-io/shared'; +import { SearchResult, TranslatableString } from '@elek-io/shared'; import { NotificationIntent } from '@elek-io/ui'; import { Cog6ToothIcon, + CubeTransparentIcon, FolderOpenIcon, HomeIcon, MagnifyingGlassIcon, @@ -16,7 +17,34 @@ export const Route = createFileRoute('/projects/$projectId')({ id: params.projectId, }); - return { currentProject }; + /** + * Returns given TranslatableString in the language of the current user + * + * If the current users translation is not available, + * it shows it in the default language of the project. + * If this is not available either, show the 'en' value. + * If this is also not available, show the key instead along with a note, + * that a translation should be added. + */ + function translate(key: string, record: TranslatableString): string { + const toCurrentUserLocale = record[context.currentUser.locale.id]; + if (toCurrentUserLocale) { + return toCurrentUserLocale; + } + const toCurrentProjectsDefaultLocale = + currentProject.settings.locale.default.id && + record[currentProject.settings.locale.default.id]; + if (toCurrentProjectsDefaultLocale) { + return toCurrentProjectsDefaultLocale; + } + const toEnglish = record['en']; + if (toEnglish) { + return toEnglish; + } + return `Missing translation for key "${key}"`; + } + + return { currentProject, translate }; }, component: ProjectLayout, }); @@ -37,6 +65,11 @@ function ProjectLayout() { to: '/projects/$projectId/assets', icon: PhotoIcon, }, + { + name: 'Collections', + to: '/projects/$projectId/collections', + icon: CubeTransparentIcon, + }, { name: 'Settings', to: '/projects/$projectId/settings', @@ -135,9 +168,9 @@ function ProjectLayout() {
    -
    +
    -
    +
    ); } diff --git a/src/renderer/react/routes/projects/$projectId/collections.tsx b/src/renderer/react/routes/projects/$projectId/collections.tsx new file mode 100644 index 00000000..f38bbb7f --- /dev/null +++ b/src/renderer/react/routes/projects/$projectId/collections.tsx @@ -0,0 +1,73 @@ +import { CubeTransparentIcon, PlusIcon } from '@heroicons/react/20/solid'; +import { + Link, + Outlet, + createFileRoute, + useRouter, +} from '@tanstack/react-router'; + +export const Route = createFileRoute('/projects/$projectId/collections')({ + beforeLoad: async ({ context, params }) => { + const currentCollections = await context.core.collections.list({ + projectId: params.projectId, + }); + + return { currentCollections }; + }, + component: ProjectCollectionsLayout, +}); + +function ProjectCollectionsLayout() { + const router = useRouter(); + const context = Route.useRouteContext(); + + return ( +
    + +
    + +
    +
    + ); +} diff --git a/src/renderer/react/routes/projects/$projectId/collections/$collectionId.tsx b/src/renderer/react/routes/projects/$projectId/collections/$collectionId.tsx new file mode 100644 index 00000000..ed775cf1 --- /dev/null +++ b/src/renderer/react/routes/projects/$projectId/collections/$collectionId.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute( + '/projects/$projectId/collections/$collectionId' +)({ + beforeLoad: async ({ context, params }) => { + const currentCollection = await context.core.collections.read({ + projectId: params.projectId, + id: params.collectionId, + }); + + return { currentCollection }; + }, + component: () => ( +
    Hello /projects/$projectId/collections/$collectionId!
    + ), +}); diff --git a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/$entryId/index.tsx b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/$entryId/index.tsx new file mode 100644 index 00000000..825c4860 --- /dev/null +++ b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/$entryId/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/projects/$projectId/collections/$collectionId/$entryId/')({ + component: () =>
    Hello /projects/$projectId/collections/$collectionId/$entryId/!
    +}) \ No newline at end of file diff --git a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/create.tsx b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/create.tsx new file mode 100644 index 00000000..42864692 --- /dev/null +++ b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/create.tsx @@ -0,0 +1,146 @@ +import { Entry } from '@elek-io/shared'; +import { Button, FormSelect, NotificationIntent, Page } from '@elek-io/ui'; +import { CheckIcon } from '@heroicons/react/20/solid'; +import { createFileRoute, useRouter } from '@tanstack/react-router'; +import { ReactElement } from 'react'; +import { SubmitHandler, useForm } from 'react-hook-form'; + +export const Route = createFileRoute( + '/projects/$projectId/collections/$collectionId/create' +)({ + component: ProjectCollectionEntryCreate, +}); + +function ProjectCollectionEntryCreate() { + const router = useRouter(); + const context = Route.useRouteContext(); + const addNotification = context.store((state) => state.addNotification); + const { + register, + handleSubmit, + watch, + control, + formState: { errors }, + } = useForm(); + + const onCreate: SubmitHandler = async (newEntry) => { + if (!context.currentProject || !context.currentCollection) { + return; + } + + try { + const entry = await context.core.entries.create({ + projectId: context.currentProject.id, + collectionId: context.currentCollection.id, + language: newEntry.language, + valueReferences: newEntry.valueReferences, + }); + router.navigate({ + to: '/projects/$projectId/collections/$collectionId/$entryId', + params: { + projectId: context.currentProject.id, + collectionId: context.currentCollection.id, + entryId: entry.id, + }, + }); + } catch (error) { + console.error(error); + addNotification({ + intent: NotificationIntent.DANGER, + title: 'Failed to create new Item for this Collection', + description: + 'There was an error creating the new Item for this Collection.', + }); + } + }; + + function Title(): string { + if (!context.currentCollection) { + return ''; + } + return `Create a new ${context.translate( + 'currentCollection.name', + context.currentCollection.name.singular + )}`; + } + + function Description(): ReactElement { + if (!context.currentCollection) { + return <>; + } + return ( + <> + {context.translate( + 'currentCollection.description', + context.currentCollection.description + )} + + ); + } + + function Actions(): ReactElement { + return ( + <> + + + ); + } + + return ( + } + actions={} + > + {JSON.stringify(watch())} +
    + + +

    Definitions

    + { + // The Collections Field definitions are displayed here, so the user can either create a new field based on the definition or choose an existing one that matches the criterea + } +
      + {context.currentCollection && + context.currentCollection.valueDefinitions.map((definition) => { + return ( +
    • + Create a " + {context.translate('definition.name', definition.name)}" Value +
    • + ); + })} +
    + + {/*

    Definitions

    +
      + {context.currentCollection.fieldDefinitions.map((fieldDefinition) => { + return
    • {fieldDefinition.name[props.currentUser.locale.id]}
    • ; + })} +
    */} +
    +
    + ); +} diff --git a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx new file mode 100644 index 00000000..8ea3d177 --- /dev/null +++ b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute( + '/projects/$projectId/collections/$collectionId/' +)({ + component: () => ( +
    Hello /projects/$projectId/collections/$collectionId/!
    + ), +}); diff --git a/src/renderer/react/routes/projects/$projectId/collections/create.tsx b/src/renderer/react/routes/projects/$projectId/collections/create.tsx new file mode 100644 index 00000000..76f329f6 --- /dev/null +++ b/src/renderer/react/routes/projects/$projectId/collections/create.tsx @@ -0,0 +1,399 @@ +import { + CreateCollectionProps, + ValueDefinition, + createCollectionSchema, + supportedIconSchema, + uuid, + valueDefinitionSchema, +} from '@elek-io/shared'; +import { + Button, + FormInput, + FormSelect, + FormTextarea, + FormToggle, + Modal, + NotificationIntent, + Page, + PageSection, +} from '@elek-io/ui'; +import { CheckIcon, PlusIcon } from '@heroicons/react/20/solid'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { createFileRoute, useRouter } from '@tanstack/react-router'; +import { ReactElement, useState } from 'react'; +import { SubmitHandler, useFieldArray, useForm } from 'react-hook-form'; + +export const Route = createFileRoute('/projects/$projectId/collections/create')( + { + component: ProjectCollectionCreate, + } +); + +function ProjectCollectionCreate() { + const router = useRouter(); + const context = Route.useRouteContext(); + const addNotification = context.store((state) => state.addNotification); + let [isAddValueDefinitionModalOpen, setIsAddValueDefinitionModalOpen] = + useState(false); + const valueDefinitionForm = useForm({ + resolver: async (data, context, options) => { + // you can debug your validation schema here + console.log('formData', data); + console.log( + 'validation result', + await zodResolver(valueDefinitionSchema)(data, context, options) + ); + return zodResolver(valueDefinitionSchema)(data, context, options); + }, + defaultValues: { + id: uuid(), + valueType: 'string', + inputType: 'text', + inputWidth: '3', + defaultValue: '', + min: 0, + max: 255, + isRequired: true, + isUnique: false, + isDisabled: false, + }, + }); + const createCollectionForm = useForm({ + resolver: async (data, context, options) => { + // you can debug your validation schema here + console.log('formData', data); + console.log( + 'validation result', + await zodResolver(createCollectionSchema)(data, context, options) + ); + return zodResolver(createCollectionSchema)(data, context, options); + }, + defaultValues: { + projectId: context.currentProject.id, + valueDefinitions: [], + }, + }); + const valueDefinitions = useFieldArray({ + control: createCollectionForm.control, // control props comes from useForm (optional: if you are using FormContext) + name: 'valueDefinitions', // unique name for your Field Array + }); + + function Description(): ReactElement { + return ( + <> + A Collection holds information about how your content is structured. +

    + Read more about{' '} + + Collections in the documentation + + . + + ); + } + + function Actions(): ReactElement { + return ( + <> + + + ); + } + + const onCreate: SubmitHandler = async ( + createCollectionProps + ) => { + try { + const collection = await context.core.collections.create({ + ...createCollectionProps, + }); + router.navigate({ + to: '/projects/$projectId/collections/$collectionId', + params: { + projectId: context.currentProject.id, + collectionId: collection.id, + }, + }); + } catch (error) { + console.error(error); + addNotification({ + intent: NotificationIntent.DANGER, + title: 'Failed to create new collection', + description: 'There was an error creating the new Collection.', + }); + } + }; + + const onAddValueDefinition: SubmitHandler = (definition) => { + console.log(); + console.log('Adding Value definition: ', definition); + valueDefinitions.append(definition); + setIsAddValueDefinitionModalOpen(false); + console.log('New Collection props: ', createCollectionForm.getValues()); + // @todo resetting the new Field form values is not working right now + // newFieldDefinition.reset(defaultFieldDefinition); + }; + + return ( + } + actions={} + layout="overlap-card-no-space" + > + + onAddValueDefinition(definition) + )} + > + Add Field definition + + } + size="max-w-7xl" + > +

    + ToDo: Field type should be a grid with visual representation of the + different types available +

    + +

    + ToDo: From here on there should be an input field visible that is + configured like the user specified. It should also scroll down and up + with the user +

    +
    +
    + + + +
    + + + +
    +
    +
    + +
    +
    +
    +
    + {/* Content to create a new Collection.

    + Data: {JSON.stringify(watch())} +

    +

    */} +
    +
    +
    +
    + + + + + +
    +
    + { + return { + name: option, + value: option, + disabled: false, + }; + })} + label="Icon" + description="The icon is used to quickly identify this Collection from others in the Client's UI" + control={createCollectionForm.control} + errors={createCollectionForm.formState.errors} + > +
    +
    +
    + setIsAddValueDefinitionModalOpen(true)} + > + Add Field definition + + } + > +
    + {valueDefinitions.fields.map( + (fieldDefinition, fieldDefinitionIndex) => { + return ( +
    +
    +

    + {fieldDefinition.name[context.currentUser.locale.id]} ( + {fieldDefinition.inputType}) +

    +

    + { + fieldDefinition.description[ + context.currentUser.locale.id + ] + } +

    +
    +
    + ); + } + )} +
    +

    + Dynamic Field generation. See + https://react-hook-form.com/api/usefieldarray/ +

    + {JSON.stringify(createCollectionForm.watch())} +
    +
    +
    + ); +} diff --git a/yarn.lock b/yarn.lock index 3d25dbfd..86e72375 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1013,6 +1013,11 @@ resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-2.1.1.tgz#422deb80c4d6caf3371aec6f4bee8361a354dc13" integrity sha512-JyyN9Lo66kirbCMuMMRPtJxtKJoIsXKS569ebHGGRKbl8s4CtUfLnyKJxteA+vIKySocO4s1SkTkGS4xtG/yEA== +"@hookform/resolvers@^3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.3.4.tgz#de9b668c2835eb06892290192de6e2a5c906229b" + integrity sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ== + "@humanwhocodes/config-array@^0.11.13": version "0.11.14" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz" From 2c0fe5c8958c0e76421725bcda1bc7f9d5a73e07 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Thu, 29 Feb 2024 21:01:22 +0100 Subject: [PATCH 011/197] Added Collection and Entry pages --- src/renderer/react/routeTree.gen.ts | 44 ++++- .../projects/$projectId/collections.tsx | 5 +- .../$projectId/collections/$collectionId.tsx | 16 +- .../$entryId/$entryLanguage/index.tsx | 12 ++ .../$collectionId/$entryId/index.tsx | 5 - .../collections/$collectionId/create.tsx | 3 +- .../collections/$collectionId/index.tsx | 180 +++++++++++++++++- .../collections/$collectionId/update.tsx | 9 + .../projects/$projectId/collections/index.tsx | 19 ++ .../routes/projects/$projectId/index.tsx | 2 +- 10 files changed, 269 insertions(+), 26 deletions(-) create mode 100644 src/renderer/react/routes/projects/$projectId/collections/$collectionId/$entryId/$entryLanguage/index.tsx delete mode 100644 src/renderer/react/routes/projects/$projectId/collections/$collectionId/$entryId/index.tsx create mode 100644 src/renderer/react/routes/projects/$projectId/collections/$collectionId/update.tsx create mode 100644 src/renderer/react/routes/projects/$projectId/collections/index.tsx diff --git a/src/renderer/react/routeTree.gen.ts b/src/renderer/react/routeTree.gen.ts index c9b34338..506f99a4 100644 --- a/src/renderer/react/routeTree.gen.ts +++ b/src/renderer/react/routeTree.gen.ts @@ -20,12 +20,14 @@ import { Route as ProjectsProjectIdIndexImport } from './routes/projects/$projec import { Route as ProjectsProjectIdSettingsImport } from './routes/projects/$projectId/settings' import { Route as ProjectsProjectIdDashboardImport } from './routes/projects/$projectId/dashboard' import { Route as ProjectsProjectIdCollectionsImport } from './routes/projects/$projectId/collections' +import { Route as ProjectsProjectIdCollectionsIndexImport } from './routes/projects/$projectId/collections/index' import { Route as ProjectsProjectIdAssetsIndexImport } from './routes/projects/$projectId/assets/index' import { Route as ProjectsProjectIdCollectionsCreateImport } from './routes/projects/$projectId/collections/create' import { Route as ProjectsProjectIdCollectionsCollectionIdImport } from './routes/projects/$projectId/collections/$collectionId' import { Route as ProjectsProjectIdCollectionsCollectionIdIndexImport } from './routes/projects/$projectId/collections/$collectionId/index' +import { Route as ProjectsProjectIdCollectionsCollectionIdUpdateImport } from './routes/projects/$projectId/collections/$collectionId/update' import { Route as ProjectsProjectIdCollectionsCollectionIdCreateImport } from './routes/projects/$projectId/collections/$collectionId/create' -import { Route as ProjectsProjectIdCollectionsCollectionIdEntryIdIndexImport } from './routes/projects/$projectId/collections/$collectionId/$entryId/index' +import { Route as ProjectsProjectIdCollectionsCollectionIdEntryIdEntryLanguageIndexImport } from './routes/projects/$projectId/collections/$collectionId/$entryId/$entryLanguage/index' // Create/Update Routes @@ -77,6 +79,12 @@ const ProjectsProjectIdCollectionsRoute = getParentRoute: () => ProjectsProjectIdRoute, } as any) +const ProjectsProjectIdCollectionsIndexRoute = + ProjectsProjectIdCollectionsIndexImport.update({ + path: '/', + getParentRoute: () => ProjectsProjectIdCollectionsRoute, + } as any) + const ProjectsProjectIdAssetsIndexRoute = ProjectsProjectIdAssetsIndexImport.update({ path: '/assets/', @@ -101,17 +109,25 @@ const ProjectsProjectIdCollectionsCollectionIdIndexRoute = getParentRoute: () => ProjectsProjectIdCollectionsCollectionIdRoute, } as any) +const ProjectsProjectIdCollectionsCollectionIdUpdateRoute = + ProjectsProjectIdCollectionsCollectionIdUpdateImport.update({ + path: '/update', + getParentRoute: () => ProjectsProjectIdCollectionsCollectionIdRoute, + } as any) + const ProjectsProjectIdCollectionsCollectionIdCreateRoute = ProjectsProjectIdCollectionsCollectionIdCreateImport.update({ path: '/create', getParentRoute: () => ProjectsProjectIdCollectionsCollectionIdRoute, } as any) -const ProjectsProjectIdCollectionsCollectionIdEntryIdIndexRoute = - ProjectsProjectIdCollectionsCollectionIdEntryIdIndexImport.update({ - path: '/$entryId/', - getParentRoute: () => ProjectsProjectIdCollectionsCollectionIdRoute, - } as any) +const ProjectsProjectIdCollectionsCollectionIdEntryIdEntryLanguageIndexRoute = + ProjectsProjectIdCollectionsCollectionIdEntryIdEntryLanguageIndexImport.update( + { + path: '/$entryId/$entryLanguage/', + getParentRoute: () => ProjectsProjectIdCollectionsCollectionIdRoute, + } as any, + ) // Populate the FileRoutesByPath interface @@ -165,16 +181,24 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProjectsProjectIdAssetsIndexImport parentRoute: typeof ProjectsProjectIdImport } + '/projects/$projectId/collections/': { + preLoaderRoute: typeof ProjectsProjectIdCollectionsIndexImport + parentRoute: typeof ProjectsProjectIdCollectionsImport + } '/projects/$projectId/collections/$collectionId/create': { preLoaderRoute: typeof ProjectsProjectIdCollectionsCollectionIdCreateImport parentRoute: typeof ProjectsProjectIdCollectionsCollectionIdImport } + '/projects/$projectId/collections/$collectionId/update': { + preLoaderRoute: typeof ProjectsProjectIdCollectionsCollectionIdUpdateImport + parentRoute: typeof ProjectsProjectIdCollectionsCollectionIdImport + } '/projects/$projectId/collections/$collectionId/': { preLoaderRoute: typeof ProjectsProjectIdCollectionsCollectionIdIndexImport parentRoute: typeof ProjectsProjectIdCollectionsCollectionIdImport } - '/projects/$projectId/collections/$collectionId/$entryId/': { - preLoaderRoute: typeof ProjectsProjectIdCollectionsCollectionIdEntryIdIndexImport + '/projects/$projectId/collections/$collectionId/$entryId/$entryLanguage/': { + preLoaderRoute: typeof ProjectsProjectIdCollectionsCollectionIdEntryIdEntryLanguageIndexImport parentRoute: typeof ProjectsProjectIdCollectionsCollectionIdImport } } @@ -188,10 +212,12 @@ export const routeTree = rootRoute.addChildren([ ProjectsProjectIdCollectionsRoute.addChildren([ ProjectsProjectIdCollectionsCollectionIdRoute.addChildren([ ProjectsProjectIdCollectionsCollectionIdCreateRoute, + ProjectsProjectIdCollectionsCollectionIdUpdateRoute, ProjectsProjectIdCollectionsCollectionIdIndexRoute, - ProjectsProjectIdCollectionsCollectionIdEntryIdIndexRoute, + ProjectsProjectIdCollectionsCollectionIdEntryIdEntryLanguageIndexRoute, ]), ProjectsProjectIdCollectionsCreateRoute, + ProjectsProjectIdCollectionsIndexRoute, ]), ProjectsProjectIdDashboardRoute, ProjectsProjectIdSettingsRoute, diff --git a/src/renderer/react/routes/projects/$projectId/collections.tsx b/src/renderer/react/routes/projects/$projectId/collections.tsx index f38bbb7f..0ba4e53f 100644 --- a/src/renderer/react/routes/projects/$projectId/collections.tsx +++ b/src/renderer/react/routes/projects/$projectId/collections.tsx @@ -26,7 +26,6 @@ function ProjectCollectionsLayout() {
    diff --git a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/update.tsx b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/update.tsx index 38ce0c19..047ad465 100644 --- a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/update.tsx +++ b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/update.tsx @@ -1,9 +1,210 @@ -import { createFileRoute } from '@tanstack/react-router'; +import { Collection, supportedIconSchema } from '@elek-io/shared'; +import { + Button, + FormInput, + FormSelect, + FormTextarea, + NotificationIntent, + Page, + formatTimestamp, +} from '@elek-io/ui'; +import { CheckIcon, TrashIcon } from '@heroicons/react/20/solid'; +import { createFileRoute, useRouter } from '@tanstack/react-router'; +import { ReactElement, useState } from 'react'; +import { SubmitHandler, useForm } from 'react-hook-form'; export const Route = createFileRoute( '/projects/$projectId/collections/$collectionId/update' )({ - component: () => ( -
    Hello /projects/$projectId/collections/$collectionId/update!
    - ), + component: ProjectCollectionUpdate, }); + +function ProjectCollectionUpdate() { + const router = useRouter(); + const context = Route.useRouteContext(); + const addNotification = context.store((state) => state.addNotification); + const [isUpdatingCollection, setIsUpdatingCollection] = useState(false); + const { + register, + handleSubmit, + watch, + control, + formState: { errors, isDirty }, + } = useForm({ defaultValues: context.currentCollection }); + + function Description(): ReactElement { + return ( + <> + + Created:{' '} + { + formatTimestamp(context.currentCollection?.created || 0, 'en') + .absolute + } + + + Updated:{' '} + { + formatTimestamp(context.currentCollection?.updated || 0, 'en') + .absolute + } + + + ); + } + + function Actions(): ReactElement { + return ( + <> + + + ); + } + + const onUpdate: SubmitHandler = async (collection) => { + try { + await context.core.collections.update({ + ...collection, + projectId: context.currentProject.id, + }); + router.navigate({ + to: '/projects/$projectId/collections/$collectionId', + params: { + projectId: context.currentProject.id, + collectionId: context.currentCollection.id, + }, + }); + } catch (error) { + console.error(error); + addNotification({ + intent: NotificationIntent.DANGER, + title: 'Failed to update Collection', + description: 'There was an error updating the Collection.', + }); + } + }; + + const onDelete: SubmitHandler = async (collection) => { + try { + await context.core.collections.delete({ + projectId: context.currentProject.id, + id: collection.id, + }); + addNotification({ + intent: NotificationIntent.SUCCESS, + title: 'Successfully deleted Collection', + description: 'The Collection was successfully deleted.', + }); + router.navigate({ + to: '/projects/$projectId/collections', + params: { + projectId: context.currentProject.id, + }, + }); + } catch (error) { + console.error(error); + addNotification({ + intent: NotificationIntent.DANGER, + title: 'Failed to delete Collection', + description: 'There was an error deleting the Collection from disk.', + }); + } + }; + + return ( + } + description={} + layout="overlap-card-no-space" + > + {/* {JSON.stringify(watch())} +
    */} +
    +
    +
    +
    + { + return { + name: option, + value: option, + disabled: false, + }; + })} + label="Icon" + description="The icon of this Collection" + control={control} + errors={errors} + > +
    +
    + +
    +
    +
    + +
    +
    +
    +

    + Danger Zone +

    +
    +
    +

    + Delete this Project +

    +

    + Once you delete a Project, there is no going back. Please be + certain. +

    +
    +
    + +
    +
    +
    +
    +
    + ); +} From ea28917f228c85a28b0b524273c9d2ea980955c3 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Sun, 3 Mar 2024 14:28:05 +0100 Subject: [PATCH 013/197] Changed page title --- .../$projectId/collections/$collectionId/create.tsx | 9 +++++---- .../$projectId/collections/$collectionId/index.tsx | 4 ++-- .../$projectId/collections/$collectionId/update.tsx | 11 ++++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/create.tsx b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/create.tsx index d717eb55..3fb544b9 100644 --- a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/create.tsx +++ b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/create.tsx @@ -56,9 +56,6 @@ function ProjectCollectionEntryCreate() { }; function Title(): string { - if (!context.currentCollection) { - return ''; - } return `Create a new ${context.translate( 'currentCollection.name', context.currentCollection.name.singular @@ -87,7 +84,11 @@ function ProjectCollectionEntryCreate() { prependIcon={CheckIcon} onClick={handleSubmit(onCreate)} > - Create Item + Create{' '} + {context.translate( + 'currentCollection.name.singular', + context.currentCollection.name.singular + )} ); diff --git a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx index 7375e710..7a75f296 100644 --- a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx +++ b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx @@ -16,8 +16,8 @@ function ProjectCollectionEntries() { function Title(): string { return context.translate( - 'currentCollection.name', - context.currentCollection.name.singular + 'currentCollection.name.plural', + context.currentCollection.name.plural ); } diff --git a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/update.tsx b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/update.tsx index 047ad465..ddfbabdc 100644 --- a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/update.tsx +++ b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/update.tsx @@ -32,6 +32,13 @@ function ProjectCollectionUpdate() { formState: { errors, isDirty }, } = useForm({ defaultValues: context.currentCollection }); + function Title(): string { + return `Configure ${context.translate( + 'currentCollection.name.plural', + context.currentCollection.name.plural + )}`; + } + function Description(): ReactElement { return ( <> @@ -126,9 +133,7 @@ function ProjectCollectionUpdate() { return ( } description={} layout="overlap-card-no-space" From e62265fbf9f3325be1305d45c8d54c69e366a1c1 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Sun, 3 Mar 2024 14:32:35 +0100 Subject: [PATCH 014/197] Added paths config and react plugin for vite --- package.json | 1 + tsconfig.json | 3 + vite.renderer.config.ts | 8 ++ yarn.lock | 178 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 187 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index e0bba03a..00b75da5 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", + "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.17", "electron": "^28.2.1", "electron-devtools-installer": "^3.2.0", diff --git a/tsconfig.json b/tsconfig.json index 8ffd777e..c9e5726f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,9 @@ "noImplicitAny": true, "sourceMap": true, "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, "outDir": "dist", "moduleResolution": "node", "resolveJsonModule": true, diff --git a/vite.renderer.config.ts b/vite.renderer.config.ts index 4ce3d008..f4bf6442 100644 --- a/vite.renderer.config.ts +++ b/vite.renderer.config.ts @@ -1,12 +1,20 @@ import { TanStackRouterVite } from '@tanstack/router-vite-plugin'; +import react from '@vitejs/plugin-react'; +import Path from 'path'; import { defineConfig } from 'vite'; // https://vitejs.dev/config export default defineConfig({ plugins: [ + react(), TanStackRouterVite({ routesDirectory: './src/renderer/react/routes', generatedRouteTree: './src/renderer/react/routeTree.gen.ts', }), ], + resolve: { + alias: { + '@': Path.resolve(__dirname, './src'), + }, + }, }); diff --git a/yarn.lock b/yarn.lock index 86e72375..769b82a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20,6 +20,14 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.23.5": version "7.23.5" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz" @@ -54,6 +62,27 @@ json5 "^2.2.1" semver "^6.3.0" +"@babel/core@^7.23.5": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.0.tgz#56cbda6b185ae9d9bed369816a8f4423c5f2ff1b" + integrity sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.24.0" + "@babel/parser" "^7.24.0" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.0" + "@babel/types" "^7.24.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@^7.18.2", "@babel/generator@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" @@ -64,7 +93,7 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.18.2": +"@babel/helper-compilation-targets@^7.18.2", "@babel/helper-compilation-targets@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== @@ -102,7 +131,7 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-module-transforms@^7.18.0": +"@babel/helper-module-transforms@^7.18.0", "@babel/helper-module-transforms@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== @@ -113,6 +142,11 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.20" +"@babel/helper-plugin-utils@^7.22.5": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" + integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== + "@babel/helper-simple-access@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" @@ -151,6 +185,15 @@ "@babel/traverse" "^7.23.9" "@babel/types" "^7.23.9" +"@babel/helpers@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.0.tgz#a3dd462b41769c95db8091e49cfe019389a9409b" + integrity sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.0" + "@babel/types" "^7.24.0" + "@babel/highlight@^7.23.4": version "7.23.4" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz" @@ -160,11 +203,30 @@ chalk "^2.4.2" js-tokens "^4.0.0" +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.0.tgz#26a3d1ff49031c53a97d03b604375f028746a9ac" + integrity sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg== + "@babel/parser@^7.18.5", "@babel/parser@^7.23.9": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b" integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA== +"@babel/plugin-transform-react-jsx-self@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz#ed3e7dadde046cce761a8e3cf003a13d1a7972d9" + integrity sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-jsx-source@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz#03527006bdc8775247a78643c51d4e715fe39a3e" + integrity sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/runtime@^7.20.1", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.7", "@babel/runtime@^7.5.5": version "7.23.9" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz" @@ -188,6 +250,15 @@ "@babel/parser" "^7.23.9" "@babel/types" "^7.23.9" +"@babel/template@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + "@babel/traverse@^7.18.5", "@babel/traverse@^7.23.9": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.9.tgz#2f9d6aead6b564669394c5ce0f9302bb65b9d950" @@ -204,6 +275,31 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.0.tgz#4a408fbf364ff73135c714a2ab46a5eab2831b1e" + integrity sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@babel/types@^7.18.4", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.23.9": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.9.tgz#1dd7b59a9a2b5c87f8b41e52770b5ecbf492e002" @@ -1058,6 +1154,15 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": version "3.1.1" resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz" @@ -1068,6 +1173,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14": version "1.4.15" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" @@ -1089,6 +1199,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@^0.3.24": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@malept/cross-spawn-promise@^1.0.0", "@malept/cross-spawn-promise@^1.1.0": version "1.1.1" resolved "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz" @@ -1605,6 +1723,39 @@ resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@types/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.5.tgz#7b7502be0aa80cc4ef22978846b983edaafcd4dd" + integrity sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ== + dependencies: + "@babel/types" "^7.20.7" + "@types/cacheable-request@^6.0.1": version "6.0.3" resolved "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz" @@ -1820,6 +1971,17 @@ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@vitejs/plugin-react@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz#744d8e4fcb120fc3dbaa471dadd3483f5a304bb9" + integrity sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ== + dependencies: + "@babel/core" "^7.23.5" + "@babel/plugin-transform-react-jsx-self" "^7.23.3" + "@babel/plugin-transform-react-jsx-source" "^7.23.3" + "@types/babel__core" "^7.20.5" + react-refresh "^0.14.0" + "@xmldom/xmldom@^0.8.8": version "0.8.10" resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz" @@ -2567,6 +2729,11 @@ convert-source-map@^1.7.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" @@ -4506,7 +4673,7 @@ json5@^1.0.2: dependencies: minimist "^1.2.0" -json5@^2.2.1: +json5@^2.2.1, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -5786,6 +5953,11 @@ react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-refresh@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" + integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== + react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" From abe14705c4281d74e1efe8ac3a38c19d864a67ff Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Sun, 3 Mar 2024 14:53:30 +0100 Subject: [PATCH 015/197] shadcn/ui init --- components.json | 17 +++++++++++++++++ package.json | 5 +++++ src/util.ts | 7 +++++++ tailwind.config.js | 27 +++++++++++++++++++++++++-- yarn.lock | 15 +++++++++++++++ 5 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 components.json diff --git a/components.json b/components.json new file mode 100644 index 00000000..5ca5a4db --- /dev/null +++ b/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/renderer/react/index.css", + "baseColor": "zinc", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/renderer/react/components", + "utils": "@/util" + } +} \ No newline at end of file diff --git a/package.json b/package.json index 00b75da5..9fc47350 100644 --- a/package.json +++ b/package.json @@ -29,16 +29,21 @@ "@fontsource/roboto": "^5.0.8", "@heroicons/react": "^2.1.1", "@hookform/resolvers": "^3.3.4", + "@radix-ui/react-icons": "^1.3.0", "@sentry/electron": "^4.17.0", "@sentry/react": "7.92.0", "@sentry/vite-plugin": "^2.13.0", "@tanstack/react-router": "^1.15.23", "@tanstack/router-devtools": "^1.15.23", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", "dugite": "^2.5.2", "electron-squirrel-startup": "^1.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.50.1", + "tailwind-merge": "^2.2.1", + "tailwindcss-animate": "^1.0.7", "update-electron-app": "^3.0.0", "zustand": "^4.5.1" }, diff --git a/src/util.ts b/src/util.ts index 5fe3bb5b..5e77c9a8 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,3 +1,10 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + export class SecurityError extends Error { constructor(message: string) { super(message); diff --git a/tailwind.config.js b/tailwind.config.js index fb3685a4..57703073 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,17 +2,40 @@ import colors from 'tailwindcss/colors'; /** @type {import('tailwindcss').Config} */ module.exports = { + darkMode: ['class'], content: [ './index.html', - './src/renderer/**/*.{js,ts,jsx,tsx}', + './src/renderer/**/*.{ts,tsx}', './node_modules/@elek-io/ui/**/*.{js,ts,jsx,tsx}', ], + prefix: '', theme: { + container: { + center: true, + padding: '2rem', + screens: { + '2xl': '1400px', + }, + }, extend: { colors: { brand: colors.cyan, }, + keyframes: { + 'accordion-down': { + from: { height: '0' }, + to: { height: 'var(--radix-accordion-content-height)' }, + }, + 'accordion-up': { + from: { height: 'var(--radix-accordion-content-height)' }, + to: { height: '0' }, + }, + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out', + }, }, }, - plugins: [], + plugins: [require('tailwindcss-animate')], }; diff --git a/yarn.lock b/yarn.lock index 769b82a9..c06294eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1394,6 +1394,11 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@radix-ui/react-icons@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.0.tgz#c61af8f323d87682c5ca76b856d60c2312dbcb69" + integrity sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw== + "@redux-devtools/extension@^3.3.0": version "3.3.0" resolved "https://registry.yarnpkg.com/@redux-devtools/extension/-/extension-3.3.0.tgz#bc775d289f15604c472112920beac2cf4dbb7907" @@ -2648,6 +2653,11 @@ clsx@2.0.0: resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== +clsx@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" + integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" @@ -6728,6 +6738,11 @@ tailwind-merge@^2.2.1: dependencies: "@babel/runtime" "^7.23.7" +tailwindcss-animate@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz#318b692c4c42676cc9e67b19b78775742388bef4" + integrity sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA== + tailwindcss@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.1.tgz#f512ca5d1dd4c9503c7d3d28a968f1ad8f5c839d" From 586bb2ab16ebd810ff453b2dd49b4a95392bb2eb Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Sun, 3 Mar 2024 15:10:05 +0100 Subject: [PATCH 016/197] Added chadcn/ui button and theme provider --- package.json | 1 + src/renderer/react/app.tsx | 5 +- .../react/components/theme-provider.tsx | 73 ++++++++++++++ src/renderer/react/components/ui/button.tsx | 57 +++++++++++ src/renderer/react/routes/__root.tsx | 98 ++++++++++--------- yarn.lock | 29 ++++-- 6 files changed, 209 insertions(+), 54 deletions(-) create mode 100644 src/renderer/react/components/theme-provider.tsx create mode 100644 src/renderer/react/components/ui/button.tsx diff --git a/package.json b/package.json index 9fc47350..5d769d27 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@heroicons/react": "^2.1.1", "@hookform/resolvers": "^3.3.4", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-slot": "^1.0.2", "@sentry/electron": "^4.17.0", "@sentry/react": "7.92.0", "@sentry/vite-plugin": "^2.13.0", diff --git a/src/renderer/react/app.tsx b/src/renderer/react/app.tsx index 64d6702d..618a06b5 100644 --- a/src/renderer/react/app.tsx +++ b/src/renderer/react/app.tsx @@ -12,6 +12,7 @@ import { ipc } from './ipc'; import { useStore } from './store'; // Import the generated route tree +import { ThemeProvider } from './components/theme-provider'; import { routeTree } from './routeTree.gen'; // Create a new router instance @@ -35,7 +36,9 @@ if (!rootElement.innerHTML) { const root = ReactDOM.createRoot(rootElement); root.render( - + + + ); } diff --git a/src/renderer/react/components/theme-provider.tsx b/src/renderer/react/components/theme-provider.tsx new file mode 100644 index 00000000..afc6bbc6 --- /dev/null +++ b/src/renderer/react/components/theme-provider.tsx @@ -0,0 +1,73 @@ +import { createContext, useContext, useEffect, useState } from 'react'; + +type Theme = 'dark' | 'light' | 'system'; + +type ThemeProviderProps = { + children: React.ReactNode; + defaultTheme?: Theme; + storageKey?: string; +}; + +type ThemeProviderState = { + theme: Theme; + setTheme: (theme: Theme) => void; +}; + +const initialState: ThemeProviderState = { + theme: 'system', + setTheme: () => null, +}; + +const ThemeProviderContext = createContext(initialState); + +export function ThemeProvider({ + children, + defaultTheme = 'system', + storageKey = 'vite-ui-theme', + ...props +}: ThemeProviderProps) { + const [theme, setTheme] = useState( + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme + ); + + useEffect(() => { + const root = window.document.documentElement; + + root.classList.remove('light', 'dark'); + + if (theme === 'system') { + const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') + .matches + ? 'dark' + : 'light'; + + root.classList.add(systemTheme); + return; + } + + root.classList.add(theme); + }, [theme]); + + const value = { + theme, + setTheme: (theme: Theme) => { + localStorage.setItem(storageKey, theme); + setTheme(theme); + }, + }; + + return ( + + {children} + + ); +} + +export const useTheme = () => { + const context = useContext(ThemeProviderContext); + + if (context === undefined) + throw new Error('useTheme must be used within a ThemeProvider'); + + return context; +}; diff --git a/src/renderer/react/components/ui/button.tsx b/src/renderer/react/components/ui/button.tsx new file mode 100644 index 00000000..f5bde095 --- /dev/null +++ b/src/renderer/react/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/util" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-zinc-950 disabled:pointer-events-none disabled:opacity-50 dark:focus-visible:ring-zinc-300", + { + variants: { + variant: { + default: + "bg-zinc-900 text-zinc-50 shadow hover:bg-zinc-900/90 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/90", + destructive: + "bg-red-500 text-zinc-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/90", + outline: + "border border-zinc-200 bg-white shadow-sm hover:bg-zinc-100 hover:text-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-800 dark:hover:text-zinc-50", + secondary: + "bg-zinc-100 text-zinc-900 shadow-sm hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80", + ghost: "hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800 dark:hover:text-zinc-50", + link: "text-zinc-900 underline-offset-4 hover:underline dark:text-zinc-50", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/src/renderer/react/routes/__root.tsx b/src/renderer/react/routes/__root.tsx index 581b6132..a5c28d4a 100644 --- a/src/renderer/react/routes/__root.tsx +++ b/src/renderer/react/routes/__root.tsx @@ -1,12 +1,10 @@ -import { Avatar, Button, Dropdown, DropdownItemGroup } from '@elek-io/ui'; +import { DropdownItemGroup } from '@elek-io/ui'; import { - ArrowLeftIcon, - ArrowRightIcon, - ChevronDownIcon, DocumentDuplicateIcon, HomeIcon, PencilSquareIcon, } from '@heroicons/react/20/solid'; +import { ArrowLeftIcon, ArrowRightIcon } from '@radix-ui/react-icons'; import { Link, Outlet, @@ -18,6 +16,7 @@ import { import { TanStackRouterDevtools } from '@tanstack/router-devtools'; import { StoreApi, UseBoundStore } from 'zustand'; import { ContextBridgeApi } from '../../preload'; +import { Button } from '../components/ui/button'; import { StoreState } from '../store'; interface RouterContext { @@ -91,63 +90,70 @@ function RootRoute() {
    diff --git a/yarn.lock b/yarn.lock index c06294eb..c57e1259 100644 --- a/yarn.lock +++ b/yarn.lock @@ -227,6 +227,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/runtime@^7.13.10", "@babel/runtime@^7.23.2": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" + integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.20.1", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.7", "@babel/runtime@^7.5.5": version "7.23.9" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz" @@ -234,13 +241,6 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.23.2": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" - integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/template@^7.16.7", "@babel/template@^7.22.15", "@babel/template@^7.23.9": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.23.9.tgz#f881d0487cba2828d3259dcb9ef5005a9731011a" @@ -1394,11 +1394,26 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@radix-ui/react-compose-refs@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989" + integrity sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-icons@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.0.tgz#c61af8f323d87682c5ca76b856d60c2312dbcb69" integrity sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw== +"@radix-ui/react-slot@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab" + integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@redux-devtools/extension@^3.3.0": version "3.3.0" resolved "https://registry.yarnpkg.com/@redux-devtools/extension/-/extension-3.3.0.tgz#bc775d289f15604c472112920beac2cf4dbb7907" From 7dcb8bb3cd92d50a7c6becd1743f633682bf8adc Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Mon, 4 Mar 2024 21:51:26 +0100 Subject: [PATCH 017/197] Added chadcn avatar and dropdown-menu components --- package.json | 3 + src/renderer/react/components/ui/avatar.tsx | 81 +++++ .../react/components/ui/dropdown-menu.tsx | 205 +++++++++++ yarn.lock | 343 +++++++++++++++++- 4 files changed, 629 insertions(+), 3 deletions(-) create mode 100644 src/renderer/react/components/ui/avatar.tsx create mode 100644 src/renderer/react/components/ui/dropdown-menu.tsx diff --git a/package.json b/package.json index 5d769d27..26c62ba1 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,8 @@ "@fontsource/roboto": "^5.0.8", "@heroicons/react": "^2.1.1", "@hookform/resolvers": "^3.3.4", + "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-slot": "^1.0.2", "@sentry/electron": "^4.17.0", @@ -40,6 +42,7 @@ "clsx": "^2.1.0", "dugite": "^2.5.2", "electron-squirrel-startup": "^1.0.0", + "lucide-react": "^0.344.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.50.1", diff --git a/src/renderer/react/components/ui/avatar.tsx b/src/renderer/react/components/ui/avatar.tsx new file mode 100644 index 00000000..d287b07b --- /dev/null +++ b/src/renderer/react/components/ui/avatar.tsx @@ -0,0 +1,81 @@ +'use client'; + +import * as AvatarPrimitive from '@radix-ui/react-avatar'; +import * as React from 'react'; + +import { cn } from '@/util'; + +interface AvatarProps extends AvatarPrimitive.PrimitiveSpanProps { + name: string; + src?: string; +} + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef< + React.ForwardRefExoticComponent< + AvatarProps & React.RefAttributes + > + > +>(({ className, ...props }, ref) => { + function initials(name: string) { + const rgx = new RegExp(/(\p{L}{1})\p{L}+/, 'gu'); + let initials = [...name.matchAll(rgx)] || []; + + return ( + (initials.shift()?.[1] || '') + (initials.pop()?.[1] || '') + ).toUpperCase(); + } + + return ( + + {props.src && } + {initials(props.name)} + + ); +}); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarBase = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarBase.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarBase, AvatarFallback, AvatarImage }; diff --git a/src/renderer/react/components/ui/dropdown-menu.tsx b/src/renderer/react/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..4c1531d8 --- /dev/null +++ b/src/renderer/react/components/ui/dropdown-menu.tsx @@ -0,0 +1,205 @@ +'use client'; + +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; +import { + CheckIcon, + ChevronRightIcon, + DotFilledIcon, +} from '@radix-ui/react-icons'; +import * as React from 'react'; + +import { cn } from '@/util'; + +const DropdownMenu = DropdownMenuPrimitive.Root; + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuGroup = DropdownMenuPrimitive.Group; + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; + +const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'; + +export { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +}; diff --git a/yarn.lock b/yarn.lock index c57e1259..bb7743f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1081,6 +1081,33 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz" integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== +"@floating-ui/core@^1.0.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" + integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== + dependencies: + "@floating-ui/utils" "^0.2.1" + +"@floating-ui/dom@^1.6.1": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef" + integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + +"@floating-ui/react-dom@^2.0.0": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" + integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== + dependencies: + "@floating-ui/dom" "^1.6.1" + +"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" + integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== + "@fontsource-variable/montserrat@^5.0.17": version "5.0.17" resolved "https://registry.yarnpkg.com/@fontsource-variable/montserrat/-/montserrat-5.0.17.tgz#6e7b2d6a29ebf0cbd891a824698e299f8fad05e9" @@ -1394,6 +1421,43 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@radix-ui/primitive@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" + integrity sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-arrow@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" + integrity sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + +"@radix-ui/react-avatar@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz#de9a5349d9e3de7bbe990334c4d2011acbbb9623" + integrity sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + +"@radix-ui/react-collection@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159" + integrity sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-compose-refs@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989" @@ -1401,12 +1465,160 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-context@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c" + integrity sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-direction@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b" + integrity sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-dismissable-layer@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4" + integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-escape-keydown" "1.0.3" + +"@radix-ui/react-dropdown-menu@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz#cdf13c956c5e263afe4e5f3587b3071a25755b63" + integrity sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-menu" "2.0.6" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-controllable-state" "1.0.1" + +"@radix-ui/react-focus-guards@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad" + integrity sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-focus-scope@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525" + integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-icons@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.0.tgz#c61af8f323d87682c5ca76b856d60c2312dbcb69" integrity sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw== -"@radix-ui/react-slot@^1.0.2": +"@radix-ui/react-id@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" + integrity sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-layout-effect" "1.0.1" + +"@radix-ui/react-menu@2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.0.6.tgz#2c9e093c1a5d5daa87304b2a2f884e32288ae79e" + integrity sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-collection" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-direction" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-focus-guards" "1.0.1" + "@radix-ui/react-focus-scope" "1.0.4" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-popper" "1.1.3" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-roving-focus" "1.0.4" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-use-callback-ref" "1.0.1" + aria-hidden "^1.1.1" + react-remove-scroll "2.5.5" + +"@radix-ui/react-popper@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42" + integrity sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w== + dependencies: + "@babel/runtime" "^7.13.10" + "@floating-ui/react-dom" "^2.0.0" + "@radix-ui/react-arrow" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-use-rect" "1.0.1" + "@radix-ui/react-use-size" "1.0.1" + "@radix-ui/rect" "1.0.1" + +"@radix-ui/react-portal@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15" + integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + +"@radix-ui/react-presence@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba" + integrity sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + +"@radix-ui/react-primitive@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0" + integrity sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-slot" "1.0.2" + +"@radix-ui/react-roving-focus@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974" + integrity sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-collection" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-direction" "1.0.1" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-controllable-state" "1.0.1" + +"@radix-ui/react-slot@1.0.2", "@radix-ui/react-slot@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab" integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg== @@ -1414,6 +1626,59 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.1" +"@radix-ui/react-use-callback-ref@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a" + integrity sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-use-controllable-state@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz#ecd2ced34e6330caf89a82854aa2f77e07440286" + integrity sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "1.0.1" + +"@radix-ui/react-use-escape-keydown@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" + integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "1.0.1" + +"@radix-ui/react-use-layout-effect@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" + integrity sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-use-rect@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz#fde50b3bb9fd08f4a1cd204572e5943c244fcec2" + integrity sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/rect" "1.0.1" + +"@radix-ui/react-use-size@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz#1c5f5fea940a7d7ade77694bb98116fb49f870b2" + integrity sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-layout-effect" "1.0.1" + +"@radix-ui/rect@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.1.tgz#bf8e7d947671996da2e30f4904ece343bc4a883f" + integrity sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@redux-devtools/extension@^3.3.0": version "3.3.0" resolved "https://registry.yarnpkg.com/@redux-devtools/extension/-/extension-3.3.0.tgz#bc775d289f15604c472112920beac2cf4dbb7907" @@ -2173,6 +2438,13 @@ argparse@^2.0.1: resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +aria-hidden@^1.1.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.3.tgz#14aeb7fb692bbb72d69bebfa47279c1fd725e954" + integrity sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ== + dependencies: + tslib "^2.0.0" + array-buffer-byte-length@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz" @@ -2980,6 +3252,11 @@ detect-libc@^2.0.1: resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz" integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + detect-node@^2.0.4: version "2.1.0" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" @@ -3949,6 +4226,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== + get-package-info@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz" @@ -4373,6 +4655,13 @@ interpret@^3.1.1: resolved "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz" integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + ip@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz" @@ -4905,7 +5194,7 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" -loose-envify@^1.1.0: +loose-envify@^1.0.0, loose-envify@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -4949,6 +5238,11 @@ lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== +lucide-react@^0.344.0: + version "0.344.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.344.0.tgz#fcbc7cf855e6baedbc14aab6ddca09b7c1afc46d" + integrity sha512-6YyBnn91GB45VuVT96bYCOKElbJzUHqp65vX8cDcu55MQL9T969v4dhGClpljamuI/+KMO9P6w9Acq1CVQGvIQ== + macos-alias@~0.2.5: version "0.2.11" resolved "https://registry.yarnpkg.com/macos-alias/-/macos-alias-0.2.11.tgz#feeea6c13ba119814a43fc43c470b31e59ef718a" @@ -5983,6 +6277,34 @@ react-refresh@^0.14.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== +react-remove-scroll-bar@^2.3.3: + version "2.3.5" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.5.tgz#cd2543b3ed7716c7c5b446342d21b0e0b303f47c" + integrity sha512-3cqjOqg6s0XbOjWvmasmqHch+RLxIEk2r/70rzGXuz3iIGQsQheEQyqYCBb5EECoD01Vo2SIbDqW4paLeLTASw== + dependencies: + react-style-singleton "^2.2.1" + tslib "^2.0.0" + +react-remove-scroll@2.5.5: + version "2.5.5" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77" + integrity sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw== + dependencies: + react-remove-scroll-bar "^2.3.3" + react-style-singleton "^2.2.1" + tslib "^2.1.0" + use-callback-ref "^1.3.0" + use-sidecar "^1.1.2" + +react-style-singleton@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" + integrity sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g== + dependencies: + get-nonce "^1.0.0" + invariant "^2.2.4" + tslib "^2.0.0" + react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" @@ -6964,7 +7286,7 @@ tslib@^1.8.1: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.1.0, tslib@^2.5.0: +tslib@^2.0.0, tslib@^2.1.0, tslib@^2.5.0: version "2.6.2" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -7170,6 +7492,21 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +use-callback-ref@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.1.tgz#9be64c3902cbd72b07fe55e56408ae3a26036fd0" + integrity sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ== + dependencies: + tslib "^2.0.0" + +use-sidecar@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2" + integrity sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw== + dependencies: + detect-node-es "^1.1.0" + tslib "^2.0.0" + use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" From d1b02104a7c9a381a82bf02a732165a292a23694 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Mon, 4 Mar 2024 21:53:02 +0100 Subject: [PATCH 018/197] Added theme switch and store for project sidebar. Now using avatar and dropdown-menu components --- index.html | 6 +- src/renderer/react/routes/__root.tsx | 159 +++++++++++++++--- .../react/routes/projects/$projectId.tsx | 116 +++++++------ src/renderer/react/store.ts | 8 + 4 files changed, 215 insertions(+), 74 deletions(-) diff --git a/index.html b/index.html index 0281dcb9..4b29b91e 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,12 @@ - + Hello World! - +
    diff --git a/src/renderer/react/routes/__root.tsx b/src/renderer/react/routes/__root.tsx index a5c28d4a..59f0a9a6 100644 --- a/src/renderer/react/routes/__root.tsx +++ b/src/renderer/react/routes/__root.tsx @@ -1,10 +1,16 @@ import { DropdownItemGroup } from '@elek-io/ui'; import { DocumentDuplicateIcon, - HomeIcon, PencilSquareIcon, } from '@heroicons/react/20/solid'; -import { ArrowLeftIcon, ArrowRightIcon } from '@radix-ui/react-icons'; +import { + ArrowLeftIcon, + ArrowRightIcon, + CaretDownIcon, + HomeIcon, + PinLeftIcon, + PinRightIcon, +} from '@radix-ui/react-icons'; import { Link, Outlet, @@ -14,9 +20,28 @@ import { useRouterState, } from '@tanstack/react-router'; import { TanStackRouterDevtools } from '@tanstack/router-devtools'; +import { Moon, Sun } from 'lucide-react'; import { StoreApi, UseBoundStore } from 'zustand'; import { ContextBridgeApi } from '../../preload'; +import { useTheme } from '../components/theme-provider'; +import { Avatar } from '../components/ui/avatar'; import { Button } from '../components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from '../components/ui/dropdown-menu'; import { StoreState } from '../store'; interface RouterContext { @@ -47,6 +72,10 @@ function RootRoute() { const router = useRouter(); const context = Route.useRouteContext(); const state = useRouterState(); + const [isProjectSidebarNarrow, setIsProjectSidebarNarrow] = context.store( + (state) => [state.isProjectSidebarNarrow, state.setIsProjectSidebarNarrow] + ); + const { theme, setTheme } = useTheme(); const breadcrumbs = state.location.pathname .split('/') .filter((value) => value) // Filter out empty values for beginning or ending slashes @@ -78,40 +107,60 @@ function RootRoute() { return ( <> -
    +

    elek.io{' '} Client

    -
    + ); diff --git a/src/renderer/react/store.ts b/src/renderer/react/store.ts index afce581e..079ff130 100644 --- a/src/renderer/react/store.ts +++ b/src/renderer/react/store.ts @@ -1,10 +1,11 @@ -import { NotificationProps } from '@elek-io/ui'; +import { NotificationIntent, NotificationProps } from '@elek-io/ui'; import type {} from '@redux-devtools/extension'; // required for devtools typing import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; +import { toast } from './components/ui/toast'; export interface StoreState { - notifications: NotificationProps[]; + notifications: {}[]; addNotification: (notification: NotificationProps) => void; isProjectSidebarNarrow: boolean; setIsProjectSidebarNarrow: (isNarrow: boolean) => void; @@ -15,6 +16,28 @@ export const useStore = create()( notifications: [], addNotification: (notification) => { console.log('New notification:', notification); + switch (notification.intent) { + case NotificationIntent.INFO: + toast.info(notification.title, { + description: notification.description, + }); + break; + case NotificationIntent.SUCCESS: + toast.success(notification.title, { + description: notification.description, + }); + case NotificationIntent.WARNING: + toast.warning(notification.title, { + description: notification.description, + }); + case NotificationIntent.DANGER: + toast.error(notification.title, { + description: notification.description, + important: true, + }); + default: + break; + } set((state) => ({ notifications: [...state.notifications, notification], })); diff --git a/yarn.lock b/yarn.lock index db370396..314ce498 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6834,6 +6834,11 @@ socks@^2.6.2: ip "^2.0.0" smart-buffer "^4.2.0" +sonner@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/sonner/-/sonner-1.4.3.tgz#b0c1cc175a0e9184460911ca9c396daec5311ae9" + integrity sha512-SArYlHbkjqRuLiR0iGY2ZSr09oOrxw081ZZkQPfXrs8aZQLIBOLOdzTYxGJB5yIZ7qL56UEPmrX1YqbODwG0Lw== + source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" From d0cdd72af5fbc1d724c98ee12810163a2d41375f Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Sat, 9 Mar 2024 13:31:21 +0100 Subject: [PATCH 022/197] Added project search with dialog --- package.json | 2 + src/renderer/react/components/ui/badge.tsx | 36 ++++ src/renderer/react/components/ui/command.tsx | 155 ++++++++++++++++++ src/renderer/react/components/ui/dialog.tsx | 122 ++++++++++++++ .../react/routes/projects/$projectId.tsx | 119 ++++++++++---- src/renderer/react/store.ts | 10 +- yarn.lock | 29 ++++ 7 files changed, 444 insertions(+), 29 deletions(-) create mode 100644 src/renderer/react/components/ui/badge.tsx create mode 100644 src/renderer/react/components/ui/command.tsx create mode 100644 src/renderer/react/components/ui/dialog.tsx diff --git a/package.json b/package.json index 663fe5c8..f5d22945 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@heroicons/react": "^2.1.1", "@hookform/resolvers": "^3.3.4", "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-slot": "^1.0.2", @@ -41,6 +42,7 @@ "@tanstack/router-devtools": "^1.15.23", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", + "cmdk": "^1.0.0", "dugite": "^2.5.2", "electron-squirrel-startup": "^1.0.0", "lucide-react": "^0.344.0", diff --git a/src/renderer/react/components/ui/badge.tsx b/src/renderer/react/components/ui/badge.tsx new file mode 100644 index 00000000..739a3b55 --- /dev/null +++ b/src/renderer/react/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/util" + +const badgeVariants = cva( + "inline-flex items-center rounded-md border border-zinc-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-zinc-950 focus:ring-offset-2 dark:border-zinc-800 dark:focus:ring-zinc-300", + { + variants: { + variant: { + default: + "border-transparent bg-zinc-900 text-zinc-50 shadow hover:bg-zinc-900/80 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/80", + secondary: + "border-transparent bg-zinc-100 text-zinc-900 hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80", + destructive: + "border-transparent bg-red-500 text-zinc-50 shadow hover:bg-red-500/80 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/80", + outline: "text-zinc-950 dark:text-zinc-50", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
    + ) +} + +export { Badge, badgeVariants } diff --git a/src/renderer/react/components/ui/command.tsx b/src/renderer/react/components/ui/command.tsx new file mode 100644 index 00000000..2f73b411 --- /dev/null +++ b/src/renderer/react/components/ui/command.tsx @@ -0,0 +1,155 @@ +"use client" + +import * as React from "react" +import { type DialogProps } from "@radix-ui/react-dialog" +import { MagnifyingGlassIcon } from "@radix-ui/react-icons" +import { Command as CommandPrimitive } from "cmdk" + +import { cn } from "@/util" +import { Dialog, DialogContent } from "@/renderer/react/components/ui/dialog" + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Command.displayName = CommandPrimitive.displayName + +interface CommandDialogProps extends DialogProps {} + +const CommandDialog = ({ children, ...props }: CommandDialogProps) => { + return ( + + + + {children} + + + + ) +} + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
    + + +
    +)) + +CommandInput.displayName = CommandPrimitive.Input.displayName + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandList.displayName = CommandPrimitive.List.displayName + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)) + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandGroup.displayName = CommandPrimitive.Group.displayName + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +CommandShortcut.displayName = "CommandShortcut" + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/src/renderer/react/components/ui/dialog.tsx b/src/renderer/react/components/ui/dialog.tsx new file mode 100644 index 00000000..bba3c824 --- /dev/null +++ b/src/renderer/react/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { Cross2Icon } from "@radix-ui/react-icons" + +import { cn } from "@/util" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
    +) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
    +) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/src/renderer/react/routes/projects/$projectId.tsx b/src/renderer/react/routes/projects/$projectId.tsx index c0853c72..178b262b 100644 --- a/src/renderer/react/routes/projects/$projectId.tsx +++ b/src/renderer/react/routes/projects/$projectId.tsx @@ -8,8 +8,23 @@ import { LayersIcon, MagnifyingGlassIcon, } from '@radix-ui/react-icons'; -import { Link, Outlet, createFileRoute } from '@tanstack/react-router'; -import { ChangeEvent, useState } from 'react'; +import { + Link, + Outlet, + createFileRoute, + useRouter, +} from '@tanstack/react-router'; +import { useState } from 'react'; +import { Badge } from '../../components/ui/badge'; +import { Button } from '../../components/ui/button'; +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from '../../components/ui/command'; import { Tooltip, TooltipContent, @@ -56,13 +71,20 @@ export const Route = createFileRoute('/projects/$projectId')({ }); function ProjectLayout() { + const router = useRouter(); const context = Route.useRouteContext(); const addNotification = context.store((state) => state.addNotification); const isProjectSidebarNarrow = context.store( (state) => state.isProjectSidebarNarrow ); + const [isProjectSearchDialogOpen, setProjectSearchDialogOpen] = context.store( + (state) => [ + state.isProjectSearchDialogOpen, + state.setProjectSearchDialogOpen, + ] + ); const [searchQuery, setSearchQuery] = useState(''); - const [searchResult, setSearchResult] = useState(); + const [searchResult, setSearchResult] = useState([]); const projectNavigation = [ { name: 'Dashboard', @@ -86,8 +108,8 @@ function ProjectLayout() { }, ]; - async function onSearch(event: ChangeEvent) { - setSearchQuery(event.target.value); + async function onSearch(value: string) { + setSearchQuery(value); try { const searchResult = await context.core.projects.search( context.currentProject.id, @@ -141,31 +163,21 @@ function ProjectLayout() { )}
    - {!isProjectSidebarNarrow && ( -
    - -
    -
    -
    - onSearch(event)} - /> -
    -
    - )}
    ); } diff --git a/src/renderer/react/store.ts b/src/renderer/react/store.ts index 079ff130..a478218d 100644 --- a/src/renderer/react/store.ts +++ b/src/renderer/react/store.ts @@ -5,10 +5,12 @@ import { devtools } from 'zustand/middleware'; import { toast } from './components/ui/toast'; export interface StoreState { - notifications: {}[]; + notifications: NotificationProps[]; addNotification: (notification: NotificationProps) => void; isProjectSidebarNarrow: boolean; setIsProjectSidebarNarrow: (isNarrow: boolean) => void; + isProjectSearchDialogOpen: boolean; + setProjectSearchDialogOpen: (isOpen: boolean) => void; } export const useStore = create()( @@ -48,5 +50,11 @@ export const useStore = create()( isProjectSidebarNarrow: isNarrow, })); }, + isProjectSearchDialogOpen: false, + setProjectSearchDialogOpen: (isOpen) => { + set((state) => ({ + isProjectSearchDialogOpen: isOpen, + })); + }, })) ); diff --git a/yarn.lock b/yarn.lock index 314ce498..e78f30a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1472,6 +1472,27 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-dialog@1.0.5", "@radix-ui/react-dialog@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300" + integrity sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-focus-guards" "1.0.1" + "@radix-ui/react-focus-scope" "1.0.4" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-use-controllable-state" "1.0.1" + aria-hidden "^1.1.1" + react-remove-scroll "2.5.5" + "@radix-ui/react-direction@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b" @@ -2972,6 +2993,14 @@ clsx@^2.1.0: resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== +cmdk@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cmdk/-/cmdk-1.0.0.tgz#0a095fdafca3dfabed82d1db78a6262fb163ded9" + integrity sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q== + dependencies: + "@radix-ui/react-dialog" "1.0.5" + "@radix-ui/react-primitive" "1.0.3" + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" From e781eebd91e07125e2eeb31bab7522d081ffb277 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Sun, 10 Mar 2024 18:24:16 +0100 Subject: [PATCH 023/197] Dialog is now using provided options --- src/main/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/index.ts b/src/main/index.ts index 2e124579..20490117 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -251,7 +251,7 @@ class Main { private registerIpcMain(core: ElekIoCore) { ipcMain.handle('electron:dialog:showOpenDialog', async (event, args) => { - return await dialog.showOpenDialog(args[0], args[1]); + return await dialog.showOpenDialog(undefined, args); }); ipcMain.handle('core:user:get', async () => { return await core.user.get(); From a01745f9bf11b570f0ec117d19d4308007fe3935 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Sun, 10 Mar 2024 18:25:17 +0100 Subject: [PATCH 024/197] Modified Asset display --- src/renderer/react/app.tsx | 2 +- .../react/components/ui/asset-display.tsx | 51 +++++++ .../react/components/ui/asset-teaser.tsx | 28 ++++ src/renderer/react/components/ui/page.tsx | 50 +++++++ .../projects/$projectId/assets/index.tsx | 121 ++++++++--------- src/util.ts | 128 ++++++++++++++++++ tailwind.config.js | 3 + 7 files changed, 320 insertions(+), 63 deletions(-) create mode 100644 src/renderer/react/components/ui/asset-display.tsx create mode 100644 src/renderer/react/components/ui/asset-teaser.tsx create mode 100644 src/renderer/react/components/ui/page.tsx diff --git a/src/renderer/react/app.tsx b/src/renderer/react/app.tsx index 618a06b5..683d31f5 100644 --- a/src/renderer/react/app.tsx +++ b/src/renderer/react/app.tsx @@ -36,7 +36,7 @@ if (!rootElement.innerHTML) { const root = ReactDOM.createRoot(rootElement); root.render( - + diff --git a/src/renderer/react/components/ui/asset-display.tsx b/src/renderer/react/components/ui/asset-display.tsx new file mode 100644 index 00000000..5868aaac --- /dev/null +++ b/src/renderer/react/components/ui/asset-display.tsx @@ -0,0 +1,51 @@ +import type { Asset } from '@elek-io/shared'; +import { FolderArchive } from 'lucide-react'; + +export interface AssetDisplayProps extends Asset { + preview?: boolean; +} + +export function AssetDisplay(props: AssetDisplayProps) { + switch (props.mimeType) { + case 'image/avif': + case 'image/gif': + case 'image/jpeg': + case 'image/png': + case 'image/svg+xml': + case 'image/webp': + return ( + {`Asset + ); + case 'application/pdf': + return ( + + ); + case 'application/zip': + return ; + case 'video/mp4': + case 'video/webm': + return ( + + ); + case 'audio/webm': + case 'audio/flac': + return ( + + ); + + default: + return `Mime type "${props.mimeType}" not supported`; + } +} diff --git a/src/renderer/react/components/ui/asset-teaser.tsx b/src/renderer/react/components/ui/asset-teaser.tsx new file mode 100644 index 00000000..d1309d59 --- /dev/null +++ b/src/renderer/react/components/ui/asset-teaser.tsx @@ -0,0 +1,28 @@ +import { formatBytes } from '@/util'; +import type { Asset } from '@elek-io/shared'; +import { cva, type VariantProps } from 'class-variance-authority'; +import type { MouseEventHandler } from 'react'; +import { AssetDisplay } from './asset-display'; + +const styles = cva(''); + +export interface AssetTeaserProps extends VariantProps, Asset { + onClick?: MouseEventHandler; +} + +export function AssetTeaser(props: AssetTeaserProps) { + return ( +
    +
    +
    + +
    +

    {props.name}

    +

    {formatBytes(props.size)}

    +
    +
    + ); +} diff --git a/src/renderer/react/components/ui/page.tsx b/src/renderer/react/components/ui/page.tsx new file mode 100644 index 00000000..27f7cc79 --- /dev/null +++ b/src/renderer/react/components/ui/page.tsx @@ -0,0 +1,50 @@ +import { cva, type VariantProps } from 'class-variance-authority'; +import { type ReactElement, type ReactNode } from 'react'; + +const styles = cva(''); + +export interface PageProps extends VariantProps { + title: string; + description?: ReactElement; + actions?: ReactElement; + layout?: 'overlap' | 'overlap-card' | 'overlap-card-no-space'; + children: ReactNode; +} + +export function Page(props: PageProps) { + return ( +
    +
    +
    +
    +
    +

    {props.title}

    + {props.description && ( +

    {props.description}

    + )} +
    +
    + {props.actions && props.actions} +
    +
    +
    +
    + +
    +
    +
    + {props.layout === 'overlap' ? ( + <>{props.children} + ) : props.layout === 'overlap-card-no-space' ? ( +
    {props.children}
    + ) : ( +
    + {props.children} +
    + )} +
    +
    +
    +
    + ); +} diff --git a/src/renderer/react/routes/projects/$projectId/assets/index.tsx b/src/renderer/react/routes/projects/$projectId/assets/index.tsx index d35ff706..46ad4515 100644 --- a/src/renderer/react/routes/projects/$projectId/assets/index.tsx +++ b/src/renderer/react/routes/projects/$projectId/assets/index.tsx @@ -1,15 +1,18 @@ -import { Asset } from '@elek-io/shared'; -import { - AssetTeaser, - Button, - NotificationIntent, - Page, - formatBytes, - formatTimestamp, -} from '@elek-io/ui'; -import { PhotoIcon, PlusIcon, TrashIcon } from '@heroicons/react/20/solid'; +import { AssetDisplay } from '@/renderer/react/components/ui/asset-display'; +import { Asset, supportedExtensionSchema } from '@elek-io/shared'; +import { NotificationIntent, formatBytes, formatTimestamp } from '@elek-io/ui'; +import { FilePlusIcon, TrashIcon } from '@radix-ui/react-icons'; import { createFileRoute, useRouter } from '@tanstack/react-router'; import { ReactElement, useState } from 'react'; +import { AssetTeaser } from '../../../../components/ui/asset-teaser'; +import { Button } from '../../../../components/ui/button'; +import { Page } from '../../../../components/ui/page'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '../../../../components/ui/tooltip'; export const Route = createFileRoute('/projects/$projectId/assets/')({ beforeLoad: async ({ context, params }) => { @@ -39,15 +42,18 @@ function ProjectAssetsPage() { const information = [ { key: 'Created', - value: `${createdTime.absolute} (${createdTime.relative})`, + value: createdTime.relative, + tooltip: createdTime.absolute, }, { key: 'Updated', - value: `${updatedTime.absolute} (${updatedTime.relative})`, + value: updatedTime.relative, + tooltip: updatedTime.absolute, }, { key: 'Type', - value: `${selectedAsset?.mimeType} (${selectedAsset?.extension})`, + value: selectedAsset?.extension.toUpperCase(), + tooltip: selectedAsset?.mimeType, }, ]; @@ -63,11 +69,8 @@ function ProjectAssetsPage() { function Actions(): ReactElement { return ( <> - @@ -80,9 +83,12 @@ function ProjectAssetsPage() { title: 'Select Assets to add', buttonLabel: 'Add to Assets', properties: ['openFile', 'multiSelections'], - // filters: [ - // { name: 'Supported files', extensions: [...supportedExtensions] }, - // ], + filters: [ + { + name: 'Supported files', + extensions: [...supportedExtensionSchema.options], + }, + ], }); console.log('Selected files from dialog: ', result); if (result.canceled === true) { @@ -179,42 +185,30 @@ function ProjectAssetsPage() { >
    -
      +
      {context.currentAssets.list.map((asset) => ( -
    • - setSelectedAsset(asset)} - > -
    • + setSelectedAsset(asset)} + > ))} -
    -
    -
    -
    - {selectedAsset ? ( - {selectedAsset.description} - ) : ( - - )}
    -
    - {selectedAsset ? ( - <> +
    +
    + {selectedAsset && ( + <> +
    + +
    +

    {selectedAsset.name}

    {formatBytes(selectedAsset.size)}

    -
    +

    Information

    -
    +
    {information.map((info) => { return (
    {info.key}
    -
    {info.value}
    +
    + + + {info.value} + +

    {info.tooltip}

    +
    +
    +
    +
    ); })} @@ -237,21 +240,15 @@ function ProjectAssetsPage() {
    - - +
    - - ) : ( - 'Placeholder' - )} -
    +
    + + )}
    diff --git a/src/util.ts b/src/util.ts index 5e77c9a8..41ed9cfc 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,4 +1,38 @@ +import type { SupportedLanguage } from '@elek-io/shared'; import { clsx, type ClassValue } from 'clsx'; +import { format, formatDistanceToNow, fromUnixTime } from 'date-fns'; +import { + bg, + cs, + da, + de, + el, + enUS, + es, + et, + fi, + fr, + hu, + it, + ja, + lt, + lv, + nl, + pl, + pt, + ro, + ru, + sk, + sl, + sv, + zhCN, +} from 'date-fns/locale'; +import type { + ForwardRefExoticComponent, + PropsWithoutRef, + RefAttributes, + SVGProps, +} from 'react'; import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { @@ -12,3 +46,97 @@ export class SecurityError extends Error { this.name = 'SecurityError'; } } + +export type Icon = ForwardRefExoticComponent< + PropsWithoutRef> & { + title?: string; + titleId?: string; + } & RefAttributes +>; + +/** + * Map between the imported locales and our supported locales + * + * We use english (US) and Chinese (Simplified) + */ +const importedLocales = { + bg, + cs, + da, + de, + el, + en: enUS, + es, + et, + fi, + fr, + hu, + it, + ja, + lt, + lv, + nl, + pl, + pt, + ro, + ru, + sk, + sl, + sv, + zh: zhCN, +}; + +/** + * Formats given Unix timestamp to be human readable + * and be in the user selected locale + * + * @param timestamp Unix timestamp to format + * @param language The language to format into - uses the users language if empty + */ +export function formatTimestamp( + timestamp: number | undefined, + language: SupportedLanguage +) { + if (!timestamp) { + // e.g. in case of a file not being updated yet, show a dash + return { + relative: '-', + absolute: '-', + }; + } + return { + relative: formatDistanceToNow(fromUnixTime(timestamp), { + addSuffix: true, + locale: importedLocales[language], + }), + absolute: format(fromUnixTime(timestamp), 'Pp', { + locale: importedLocales[language], + }), + }; +} + +/** + * Formats given number of bytes into a human readable format + * + * @param bytes Number of bytes + */ +export function formatBytes(bytes: number) { + if (bytes == 0) return '0 Bytes'; + const k = 1024, + decimals = 2, + sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], + i = Math.floor(Math.log(bytes) / Math.log(k)); + return ( + parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i] + ); +} + +/** + * Often used by tailwind components to join multiple strings of classes + * + * @param classes Array of classes to join + * @returns Joined class string + */ +export function classNames(...classes: string[]) { + return classes.filter(Boolean).join(' '); +} diff --git a/tailwind.config.js b/tailwind.config.js index 57703073..490eb23f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -21,6 +21,9 @@ module.exports = { colors: { brand: colors.cyan, }, + aspectRatio: { + '4/3': '4 / 3', + }, keyframes: { 'accordion-down': { from: { height: '0' }, From 27fa6ca07c1258c661643070c5ecbfac409ae0e8 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Wed, 13 Mar 2024 16:12:53 +0100 Subject: [PATCH 025/197] Fixed triggering multiple notifications --- src/renderer/react/store.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/react/store.ts b/src/renderer/react/store.ts index a478218d..97b0dcd8 100644 --- a/src/renderer/react/store.ts +++ b/src/renderer/react/store.ts @@ -28,16 +28,17 @@ export const useStore = create()( toast.success(notification.title, { description: notification.description, }); + break; case NotificationIntent.WARNING: toast.warning(notification.title, { description: notification.description, }); + break; case NotificationIntent.DANGER: toast.error(notification.title, { description: notification.description, important: true, }); - default: break; } set((state) => ({ From c758eb89cb2abfaa10ef1b348d1c943445ab6abc Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Wed, 13 Mar 2024 16:13:33 +0100 Subject: [PATCH 026/197] Page now supports darkmode --- src/renderer/react/components/ui/page.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/renderer/react/components/ui/page.tsx b/src/renderer/react/components/ui/page.tsx index 27f7cc79..5f02edb8 100644 --- a/src/renderer/react/components/ui/page.tsx +++ b/src/renderer/react/components/ui/page.tsx @@ -36,9 +36,11 @@ export function Page(props: PageProps) { {props.layout === 'overlap' ? ( <>{props.children} ) : props.layout === 'overlap-card-no-space' ? ( -
    {props.children}
    +
    + {props.children} +
    ) : ( -
    +
    {props.children}
    )} From 3b915ea44480c82265eb72d439b11f6f5931ea65 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Wed, 13 Mar 2024 16:13:53 +0100 Subject: [PATCH 027/197] Dashboard now supports darkmode --- .../react/routes/projects/$projectId/dashboard.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/renderer/react/routes/projects/$projectId/dashboard.tsx b/src/renderer/react/routes/projects/$projectId/dashboard.tsx index 416b7d53..9889a101 100644 --- a/src/renderer/react/routes/projects/$projectId/dashboard.tsx +++ b/src/renderer/react/routes/projects/$projectId/dashboard.tsx @@ -1,6 +1,6 @@ -import { Page } from '@elek-io/ui'; import { createFileRoute } from '@tanstack/react-router'; import { ReactElement } from 'react'; +import { Page } from '../../../components/ui/page'; export const Route = createFileRoute('/projects/$projectId/dashboard')({ component: ProjectDashboardPage, @@ -36,12 +36,14 @@ function ProjectDashboardPage() { >
    -
    +
    Current Project: {JSON.stringify(context.currentProject)}
    -
    Test
    +
    + Test +
    From 1f510eb8703d8299715ff657e0659af56aaed708 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Wed, 13 Mar 2024 16:14:36 +0100 Subject: [PATCH 028/197] Using new Page component for Collection index page --- .../projects/$projectId/collections/$collectionId/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx index 7a75f296..95fa5c19 100644 --- a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx +++ b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx @@ -1,7 +1,8 @@ -import { Button, Page, formatTimestamp } from '@elek-io/ui'; +import { Button, formatTimestamp } from '@elek-io/ui'; import { ChevronDownIcon, CogIcon, PlusIcon } from '@heroicons/react/20/solid'; import { createFileRoute, useRouter } from '@tanstack/react-router'; import { ReactElement } from 'react'; +import { Page } from '../../../../../components/ui/page'; export const Route = createFileRoute( '/projects/$projectId/collections/$collectionId/' From c10a3be7acc1ac5cda8d73cfcb9600078c4218d3 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Wed, 13 Mar 2024 20:09:42 +0100 Subject: [PATCH 029/197] Added chadcn form --- package.json | 4 +++- yarn.lock | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index f5d22945..8af3485e 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.7", "@sentry/electron": "^4.17.0", @@ -48,11 +49,12 @@ "lucide-react": "^0.344.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-hook-form": "^7.50.1", + "react-hook-form": "^7.51.0", "sonner": "^1.4.3", "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7", "update-electron-app": "^3.0.0", + "zod": "^3.22.4", "zustand": "^4.5.1" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index e78f30a6..a7059c83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1556,6 +1556,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-label@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-2.0.2.tgz#9c72f1d334aac996fdc27b48a8bdddd82108fb6d" + integrity sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-menu@2.0.6": version "2.0.6" resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.0.6.tgz#2c9e093c1a5d5daa87304b2a2f884e32288ae79e" @@ -6323,6 +6331,11 @@ react-hook-form@^7.50.1: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.50.1.tgz#f6aeb17a863327e5a0252de8b35b4fc8990377ed" integrity sha512-3PCY82oE0WgeOgUtIr3nYNNtNvqtJ7BZjsbxh6TnYNbXButaD5WpjOmTjdxZfheuHKR68qfeFnEDVYoSSFPMTQ== +react-hook-form@^7.51.0: + version "7.51.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.51.0.tgz#757ae71b37c26e00590bd3788508287dcc5ecdaf" + integrity sha512-BggOy5j58RdhdMzzRUHGOYhSz1oeylFAv6jUSG86OvCIvlAvS7KvnRY7yoAf2pfEiPN7BesnR0xx73nEk3qIiw== + react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" From 6341ad0b9def67fa742b0f2aaef7ca171c6a8a8c Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Wed, 13 Mar 2024 20:10:07 +0100 Subject: [PATCH 030/197] Added label, form and breadcrumb components --- .../react/components/ui/breadcrumb.tsx | 115 ++++++++++++ src/renderer/react/components/ui/form.tsx | 176 ++++++++++++++++++ src/renderer/react/components/ui/label.tsx | 26 +++ 3 files changed, 317 insertions(+) create mode 100644 src/renderer/react/components/ui/breadcrumb.tsx create mode 100644 src/renderer/react/components/ui/form.tsx create mode 100644 src/renderer/react/components/ui/label.tsx diff --git a/src/renderer/react/components/ui/breadcrumb.tsx b/src/renderer/react/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..df356f0d --- /dev/null +++ b/src/renderer/react/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import * as React from "react" +import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons" +import { Slot } from "@radix-ui/react-slot" + +import { cn } from "@/util" + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) =>
    - +
    diff --git a/src/renderer/react/routes/projects/$projectId/collections.tsx b/src/renderer/react/routes/projects/$projectId/collections.tsx index b488c723..20f7e899 100644 --- a/src/renderer/react/routes/projects/$projectId/collections.tsx +++ b/src/renderer/react/routes/projects/$projectId/collections.tsx @@ -5,6 +5,7 @@ import { createFileRoute, useRouter, } from '@tanstack/react-router'; +import { Sidebar } from '../../../components/ui/sidebar'; export const Route = createFileRoute('/projects/$projectId/collections')({ beforeLoad: async ({ context, params }) => { @@ -23,7 +24,7 @@ function ProjectCollectionsLayout() { return (
    - +
    From 571ad1fea7e3fa6859c92ef05f586b4708ac94c2 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Wed, 13 Mar 2024 20:53:47 +0100 Subject: [PATCH 035/197] Fixed translation and using new Buttons --- .../collections/$collectionId/index.tsx | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx index 95fa5c19..a5acda66 100644 --- a/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx +++ b/src/renderer/react/routes/projects/$projectId/collections/$collectionId/index.tsx @@ -1,16 +1,18 @@ -import { Button, formatTimestamp } from '@elek-io/ui'; -import { ChevronDownIcon, CogIcon, PlusIcon } from '@heroicons/react/20/solid'; +import { formatTimestamp } from '@elek-io/ui'; +import { ChevronDownIcon } from '@heroicons/react/20/solid'; import { createFileRoute, useRouter } from '@tanstack/react-router'; +import { Plus, Settings } from 'lucide-react'; import { ReactElement } from 'react'; +import { Button } from '../../../../../components/ui/button'; import { Page } from '../../../../../components/ui/page'; export const Route = createFileRoute( '/projects/$projectId/collections/$collectionId/' )({ - component: ProjectCollectionEntries, + component: ProjectCollectionIndexPage, }); -function ProjectCollectionEntries() { +function ProjectCollectionIndexPage() { const router = useRouter(); const context = Route.useRouteContext(); const addNotification = context.store((state) => state.addNotification); @@ -23,15 +25,20 @@ function ProjectCollectionEntries() { } function Description(): ReactElement { - return <>{context.currentCollection?.description['en']}; + return ( + <> + {context.translate( + 'currentCollection.description', + context.currentCollection.description + )} + + ); } function Actions(): ReactElement { return ( <> ); From c6c67b4557618bfb084f46a4daf43493b5960e85 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Thu, 14 Mar 2024 01:28:48 +0100 Subject: [PATCH 036/197] Added alert dialog and scroll area components --- index.html | 2 +- package.json | 2 + .../react/components/ui/alert-dialog.tsx | 141 ++++++++++++++++++ .../react/components/ui/asset-teaser.tsx | 6 +- src/renderer/react/components/ui/page.tsx | 61 ++++---- .../react/components/ui/scroll-area.tsx | 48 ++++++ .../components/ui/sidebar-navigation-item.tsx | 36 +++++ .../components/ui/sidebar-navigation.tsx | 19 +++ .../react/routes/projects/$projectId.tsx | 136 +++++++++-------- .../projects/$projectId/assets/index.tsx | 41 ++++- yarn.lock | 36 +++++ 11 files changed, 428 insertions(+), 100 deletions(-) create mode 100644 src/renderer/react/components/ui/alert-dialog.tsx create mode 100644 src/renderer/react/components/ui/scroll-area.tsx create mode 100644 src/renderer/react/components/ui/sidebar-navigation-item.tsx create mode 100644 src/renderer/react/components/ui/sidebar-navigation.tsx diff --git a/index.html b/index.html index 4b29b91e..22e07c8f 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ Hello World!
    diff --git a/package.json b/package.json index 8af3485e..719fbb91 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,13 @@ "@fontsource/roboto": "^5.0.8", "@heroicons/react": "^2.1.1", "@hookform/resolvers": "^3.3.4", + "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.7", "@sentry/electron": "^4.17.0", diff --git a/src/renderer/react/components/ui/alert-dialog.tsx b/src/renderer/react/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..7f4bd76f --- /dev/null +++ b/src/renderer/react/components/ui/alert-dialog.tsx @@ -0,0 +1,141 @@ +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/util" +import { buttonVariants } from "@/renderer/react/components/ui/button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
    +) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
    +) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/src/renderer/react/components/ui/asset-teaser.tsx b/src/renderer/react/components/ui/asset-teaser.tsx index d1309d59..dd1c9e84 100644 --- a/src/renderer/react/components/ui/asset-teaser.tsx +++ b/src/renderer/react/components/ui/asset-teaser.tsx @@ -12,8 +12,8 @@ export interface AssetTeaserProps extends VariantProps, Asset { export function AssetTeaser(props: AssetTeaserProps) { return ( -
    @@ -23,6 +23,6 @@ export function AssetTeaser(props: AssetTeaserProps) {

    {props.name}

    {formatBytes(props.size)}

    -
    + ); } diff --git a/src/renderer/react/components/ui/page.tsx b/src/renderer/react/components/ui/page.tsx index 224af153..e21265a1 100644 --- a/src/renderer/react/components/ui/page.tsx +++ b/src/renderer/react/components/ui/page.tsx @@ -1,5 +1,6 @@ import { cva, type VariantProps } from 'class-variance-authority'; import { type ReactElement, type ReactNode } from 'react'; +import { ScrollArea } from './scroll-area'; const styles = cva(''); @@ -13,40 +14,42 @@ export interface PageProps extends VariantProps { export function Page(props: PageProps) { return ( -
    -
    -
    -
    -
    -

    {props.title}

    - {props.description && ( -

    {props.description}

    - )} -
    -
    - {props.actions && props.actions} + +
    +
    +
    +
    +
    +

    {props.title}

    + {props.description && ( +

    {props.description}

    + )} +
    +
    + {props.actions && props.actions} +
    -
    -
    -
    -
    - {props.layout === 'overlap' ? ( - <>{props.children} - ) : props.layout === 'overlap-card-no-space' ? ( -
    - {props.children} -
    - ) : ( -
    - {props.children} -
    - )} +
    +
    +
    + {props.layout === 'overlap' ? ( + <>{props.children} + ) : props.layout === 'overlap-card-no-space' ? ( +
    + {props.children} +
    + ) : ( +
    + {props.children} +
    + )} +
    -
    -
    + + ); } diff --git a/src/renderer/react/components/ui/scroll-area.tsx b/src/renderer/react/components/ui/scroll-area.tsx new file mode 100644 index 00000000..ff410156 --- /dev/null +++ b/src/renderer/react/components/ui/scroll-area.tsx @@ -0,0 +1,48 @@ +'use client'; + +import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'; +import * as React from 'react'; + +import { cn } from '@/util'; + +const ScrollArea = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + {children} + + + + +)); +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; + +const ScrollBar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, orientation = 'vertical', ...props }, ref) => ( + + + +)); +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; + +export { ScrollArea, ScrollBar }; diff --git a/src/renderer/react/components/ui/sidebar-navigation-item.tsx b/src/renderer/react/components/ui/sidebar-navigation-item.tsx new file mode 100644 index 00000000..2efd53ba --- /dev/null +++ b/src/renderer/react/components/ui/sidebar-navigation-item.tsx @@ -0,0 +1,36 @@ +import { cn } from '@/util'; +import { Link, LinkProps } from '@tanstack/react-router'; +import { ReactNode } from 'react'; + +export interface SidebarNavigationItemProps extends LinkProps {} + +/** + * @todo this whole component is more like a hack + */ +function SidebarNavigationItem(props: SidebarNavigationItemProps) { + const className = cn( + 'cursor-pointer no-underline inline-flex items-center whitespace-nowrap text-sm 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 text-zinc-800 dark:text-zinc-200 hover:bg-zinc-300 dark:hover:bg-zinc-700 h-9 rounded-md px-3 justify-start', + props.className + ); + + if (props.to) { + return ( + + ); + } + + return ( + + {props.children as ReactNode} + + ); +} + +export { SidebarNavigationItem }; diff --git a/src/renderer/react/components/ui/sidebar-navigation.tsx b/src/renderer/react/components/ui/sidebar-navigation.tsx new file mode 100644 index 00000000..85534e76 --- /dev/null +++ b/src/renderer/react/components/ui/sidebar-navigation.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; + +import { cn } from '@/util'; + +export interface SidebarNavigationProps + extends React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > {} + +function SidebarNavigation({ className, ...props }: SidebarNavigationProps) { + return ( +
    + +
    + ); +} + +export { SidebarNavigation }; diff --git a/src/renderer/react/routes/projects/$projectId.tsx b/src/renderer/react/routes/projects/$projectId.tsx index 5f908a45..b274a712 100644 --- a/src/renderer/react/routes/projects/$projectId.tsx +++ b/src/renderer/react/routes/projects/$projectId.tsx @@ -1,22 +1,24 @@ import { SearchResult, TranslatableString } from '@elek-io/shared'; import { NotificationIntent } from '@elek-io/ui'; -import { - BackpackIcon, - DashboardIcon, - GearIcon, - ImageIcon, - LayersIcon, - MagnifyingGlassIcon, -} from '@radix-ui/react-icons'; import { Link, Outlet, + ToPathOption, createFileRoute, useRouter, } from '@tanstack/react-router'; +import { + FolderGit2, + FolderOutput, + Image, + Layers, + LayoutDashboard, + LucideIcon, + Search, + Settings, +} from 'lucide-react'; import { useState } from 'react'; import { Badge } from '../../components/ui/badge'; -import { Button } from '../../components/ui/button'; import { CommandDialog, CommandEmpty, @@ -25,7 +27,10 @@ import { CommandItem, CommandList, } from '../../components/ui/command'; +import { ScrollArea } from '../../components/ui/scroll-area'; import { Sidebar } from '../../components/ui/sidebar'; +import { SidebarNavigation } from '../../components/ui/sidebar-navigation'; +import { SidebarNavigationItem } from '../../components/ui/sidebar-navigation-item'; import { Tooltip, TooltipContent, @@ -86,26 +91,30 @@ function ProjectLayout() { ); const [searchQuery, setSearchQuery] = useState(''); const [searchResult, setSearchResult] = useState([]); - const projectNavigation = [ + const projectNavigation: { + name: string; + to: ToPathOption; + icon: LucideIcon; + }[] = [ { name: 'Dashboard', to: '/projects/$projectId/dashboard', - icon: DashboardIcon, + icon: LayoutDashboard, }, { name: 'Assets', to: '/projects/$projectId/assets', - icon: ImageIcon, + icon: Image, }, { name: 'Collections', to: '/projects/$projectId/collections', - icon: LayersIcon, + icon: Layers, }, { name: 'Settings', to: '/projects/$projectId/settings', - icon: GearIcon, + icon: Settings, }, ]; @@ -138,7 +147,7 @@ function ProjectLayout() {
    - +

    @@ -159,64 +168,65 @@ function ProjectLayout() {

    )} -
    - -
    + {projectNavigation.map((navigation) => { + const item = ( + + + {!isProjectSidebarNarrow && ( + {navigation.name} + )} + + ); + + if (isProjectSidebarNarrow) { + return ( + + + {item} + +

    {navigation.name}

    +
    +
    +
    + ); + } + + return item; + })} + + -
    + +
    +
    - + + + + + + + + You are about to delete this Asset + + + This action cannot be undone. This will permanently + delete your account and remove your data from our + servers. + + + + Cancel + + Continue + + + +
    diff --git a/yarn.lock b/yarn.lock index a7059c83..275ff224 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1421,6 +1421,13 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@radix-ui/number@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.0.1.tgz#644161a3557f46ed38a042acf4a770e826021674" + integrity sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" @@ -1428,6 +1435,19 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-alert-dialog@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.5.tgz#70dd529cbf1e4bff386814d3776901fcaa131b8c" + integrity sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dialog" "1.0.5" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-arrow@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" @@ -1647,6 +1667,22 @@ "@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-controllable-state" "1.0.1" +"@radix-ui/react-scroll-area@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.5.tgz#01160c6893f24a2ddb5aa399ae5b3ba84ad4d3cc" + integrity sha512-b6PAgH4GQf9QEn8zbT2XUHpW5z8BzqEc7Kl11TwDrvuTrxlkcjTD5qa/bxgKr+nmuXKu4L/W5UZ4mlP/VG/5Gw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/number" "1.0.1" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-direction" "1.0.1" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-slot@1.0.2", "@radix-ui/react-slot@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab" From b222b4915519f893dc91f868c0fa6bf0ca10300a Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Thu, 14 Mar 2024 02:05:09 +0100 Subject: [PATCH 037/197] Using new components for collections sidebar --- .../projects/$projectId/collections.tsx | 78 ++++++++----------- 1 file changed, 31 insertions(+), 47 deletions(-) diff --git a/src/renderer/react/routes/projects/$projectId/collections.tsx b/src/renderer/react/routes/projects/$projectId/collections.tsx index 20f7e899..0a4099da 100644 --- a/src/renderer/react/routes/projects/$projectId/collections.tsx +++ b/src/renderer/react/routes/projects/$projectId/collections.tsx @@ -1,11 +1,9 @@ -import { CubeTransparentIcon, PlusIcon } from '@heroicons/react/20/solid'; -import { - Link, - Outlet, - createFileRoute, - useRouter, -} from '@tanstack/react-router'; +import { Outlet, createFileRoute, useRouter } from '@tanstack/react-router'; +import { Layers, Plus } from 'lucide-react'; +import { ScrollArea } from '../../../components/ui/scroll-area'; import { Sidebar } from '../../../components/ui/sidebar'; +import { SidebarNavigation } from '../../../components/ui/sidebar-navigation'; +import { SidebarNavigationItem } from '../../../components/ui/sidebar-navigation-item'; export const Route = createFileRoute('/projects/$projectId/collections')({ beforeLoad: async ({ context, params }) => { @@ -25,50 +23,36 @@ function ProjectCollectionsLayout() { return (
    - + + + ))} + {context.currentCollections.total === 0 && ( +

    No Collections found

    + )} + +
    From c6059c2aa43afb5e1a4432ab75b680c55dc6ae6b Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Thu, 14 Mar 2024 02:22:09 +0100 Subject: [PATCH 038/197] Added spacing around user dropdown --- src/renderer/react/routes/__root.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/react/routes/__root.tsx b/src/renderer/react/routes/__root.tsx index ee253ae9..e53ec111 100644 --- a/src/renderer/react/routes/__root.tsx +++ b/src/renderer/react/routes/__root.tsx @@ -171,7 +171,7 @@ function RootRoute() { - + My Account From 0c984c88d56c6ab105de549e27c0bafe73ad0c5c Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Thu, 14 Mar 2024 02:22:46 +0100 Subject: [PATCH 039/197] Using new page component for create collection page --- .../react/routes/projects/$projectId/collections/create.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/react/routes/projects/$projectId/collections/create.tsx b/src/renderer/react/routes/projects/$projectId/collections/create.tsx index 76f329f6..eb98adf5 100644 --- a/src/renderer/react/routes/projects/$projectId/collections/create.tsx +++ b/src/renderer/react/routes/projects/$projectId/collections/create.tsx @@ -14,7 +14,6 @@ import { FormToggle, Modal, NotificationIntent, - Page, PageSection, } from '@elek-io/ui'; import { CheckIcon, PlusIcon } from '@heroicons/react/20/solid'; @@ -22,6 +21,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { createFileRoute, useRouter } from '@tanstack/react-router'; import { ReactElement, useState } from 'react'; import { SubmitHandler, useFieldArray, useForm } from 'react-hook-form'; +import { Page } from '../../../../components/ui/page'; export const Route = createFileRoute('/projects/$projectId/collections/create')( { From 0094bfe3346849a2f454bf643947c186b0d4a7f9 Mon Sep 17 00:00:00 2001 From: Nils Kolvenbach Date: Fri, 15 Mar 2024 18:46:18 +0100 Subject: [PATCH 040/197] Creating a Collection now workiing with new components --- package.json | 2 + src/renderer/react/components/ui/input.tsx | 25 + .../react/components/ui/page-section.tsx | 30 + src/renderer/react/components/ui/select.tsx | 164 ++++ src/renderer/react/components/ui/switch.tsx | 29 + src/renderer/react/components/ui/textarea.tsx | 24 + .../$projectId/collections/create.tsx | 698 +++++++++++------- yarn.lock | 49 ++ 8 files changed, 763 insertions(+), 258 deletions(-) create mode 100644 src/renderer/react/components/ui/input.tsx create mode 100644 src/renderer/react/components/ui/page-section.tsx create mode 100644 src/renderer/react/components/ui/select.tsx create mode 100644 src/renderer/react/components/ui/switch.tsx create mode 100644 src/renderer/react/components/ui/textarea.tsx diff --git a/package.json b/package.json index 719fbb91..9ac8dc99 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,9 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-scroll-area": "^1.0.5", + "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.7", "@sentry/electron": "^4.17.0", "@sentry/react": "7.92.0", diff --git a/src/renderer/react/components/ui/input.tsx b/src/renderer/react/components/ui/input.tsx new file mode 100644 index 00000000..81b1b6c8 --- /dev/null +++ b/src/renderer/react/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react" + +import { cn } from "@/util" + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/src/renderer/react/components/ui/page-section.tsx b/src/renderer/react/components/ui/page-section.tsx new file mode 100644 index 00000000..ace2b1ac --- /dev/null +++ b/src/renderer/react/components/ui/page-section.tsx @@ -0,0 +1,30 @@ +import { cva, type VariantProps } from 'class-variance-authority'; +import { type ReactElement, type ReactNode } from 'react'; + +const styles = cva(''); + +export interface PageSectionProps extends VariantProps { + children?: ReactNode; + title?: string; + description?: string; + actions?: ReactElement; +} + +export function PageSection(props: PageSectionProps) { + return ( +
    +
    +
    +

    + {props.title} +

    +

    {props.description}

    +
    +
    + {props.actions && props.actions} +
    +
    + {props.children} +
    + ); +} diff --git a/src/renderer/react/components/ui/select.tsx b/src/renderer/react/components/ui/select.tsx new file mode 100644 index 00000000..e6e60d82 --- /dev/null +++ b/src/renderer/react/components/ui/select.tsx @@ -0,0 +1,164 @@ +"use client" + +import * as React from "react" +import { + CaretSortIcon, + CheckIcon, + ChevronDownIcon, + ChevronUpIcon, +} from "@radix-ui/react-icons" +import * as SelectPrimitive from "@radix-ui/react-select" + +import { cn } from "@/util" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1 dark:border-zinc-800 dark:ring-offset-zinc-950 dark:placeholder:text-zinc-400 dark:focus:ring-zinc-300", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/src/renderer/react/components/ui/switch.tsx b/src/renderer/react/components/ui/switch.tsx new file mode 100644 index 00000000..7a0165cb --- /dev/null +++ b/src/renderer/react/components/ui/switch.tsx @@ -0,0 +1,29 @@ +"use client" + +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" + +import { cn } from "@/util" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/src/renderer/react/components/ui/textarea.tsx b/src/renderer/react/components/ui/textarea.tsx new file mode 100644 index 00000000..d5a5e28f --- /dev/null +++ b/src/renderer/react/components/ui/textarea.tsx @@ -0,0 +1,24 @@ +import * as React from "react" + +import { cn } from "@/util" + +export interface TextareaProps + extends React.TextareaHTMLAttributes {} + +const Textarea = React.forwardRef( + ({ className, ...props }, ref) => { + return ( +