mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 13:23:28 +00:00
Merge branch 'main' into jamie-unsorted-changes
This commit is contained in:
parent
5175dcbbfb
commit
84827b1f9e
6
.prettierrc.cli.js
Normal file
6
.prettierrc.cli.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
var mainConfig = require('./.prettierrc.json');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...mainConfig,
|
||||||
|
plugins: ['@trivago/prettier-plugin-sort-imports']
|
||||||
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"plugins": ["@trivago/prettier-plugin-sort-imports"],
|
"pluginSearchDirs": ["."],
|
||||||
"useTabs": true,
|
"useTabs": true,
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
|
@ -5,9 +5,12 @@
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Spacedrive — A file manager from the future.</title>
|
<title>Spacedrive — A file manager from the future.</title>
|
||||||
<meta name="description" content="Combine your drives and clouds into one database
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Combine your drives and clouds into one database
|
||||||
that you can organize and explore from any device. Designed for creators, hoarders and the
|
that you can organize and explore from any device. Designed for creators, hoarders and the
|
||||||
painfully disorganized." />
|
painfully disorganized."
|
||||||
|
/>
|
||||||
<meta
|
<meta
|
||||||
name="keywords"
|
name="keywords"
|
||||||
content="files,file manager,spacedrive,file explorer,vdfs,distributed filesystem,cas,content addressable storage,virtual filesystem,photos app, video organizer,video encoder,tags,tag based filesystem"
|
content="files,file manager,spacedrive,file explorer,vdfs,distributed filesystem,cas,content addressable storage,virtual filesystem,photos app, video organizer,video encoder,tags,tag based filesystem"
|
||||||
|
|
|
@ -84,7 +84,7 @@ export default function AppEmbed() {
|
||||||
)}
|
)}
|
||||||
src={`${
|
src={`${
|
||||||
import.meta.env.VITE_SDWEB_BASE_URL || 'http://localhost:8002'
|
import.meta.env.VITE_SDWEB_BASE_URL || 'http://localhost:8002'
|
||||||
}?library_id=9068c6ec-cf90-451b-bb30-4174781e7bc6`}
|
}?showControls&library_id=9068c6ec-cf90-451b-bb30-4174781e7bc6`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {
|
import {
|
||||||
Twitter,
|
|
||||||
Discord,
|
Discord,
|
||||||
Instagram,
|
|
||||||
Github,
|
Github,
|
||||||
|
Instagram,
|
||||||
Opencollective,
|
Opencollective,
|
||||||
Twitch
|
Twitch,
|
||||||
|
Twitter
|
||||||
} from '@icons-pack/react-simple-icons';
|
} from '@icons-pack/react-simple-icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"db:migrate": "pnpm core prisma migrate dev",
|
"db:migrate": "pnpm core prisma migrate dev",
|
||||||
"db:gen": "pnpm core prisma generate",
|
"db:gen": "pnpm core prisma generate",
|
||||||
"lint": "turbo run lint",
|
"lint": "turbo run lint",
|
||||||
"format": "prettier --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"",
|
"format": "prettier --config .prettierrc.cli-only.js --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"",
|
||||||
"desktop": "pnpm --filter @sd/desktop --",
|
"desktop": "pnpm --filter @sd/desktop --",
|
||||||
"mobile": "pnpm --filter @sd/mobile -- ",
|
"mobile": "pnpm --filter @sd/mobile -- ",
|
||||||
"web": "pnpm --filter @sd/web -- ",
|
"web": "pnpm --filter @sd/web -- ",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"extends": "./base.tsconfig.json",
|
"extends": "./base.tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"types": ["vite-plugin-svgr/client"]
|
"types": ["vite-plugin-svgr/client", "vite/client"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"useTabs": false,
|
|
||||||
"printWidth": 100,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "none",
|
|
||||||
"jsxBracketSameLine": false,
|
|
||||||
"semi": true,
|
|
||||||
"quoteProps": "consistent",
|
|
||||||
"importOrder": [
|
|
||||||
"^@sd/core/(.*)$",
|
|
||||||
"^@sd/interface/(.*)$",
|
|
||||||
"^@sd/client/(.*)$",
|
|
||||||
"^@sd/ui/(.*)$",
|
|
||||||
"<THIRD_PARTY_MODULES>",
|
|
||||||
"^[./]"
|
|
||||||
],
|
|
||||||
"importOrderSeparation": true,
|
|
||||||
"importOrderSortSpecifiers": true
|
|
||||||
}
|
|
|
@ -1,75 +1,75 @@
|
||||||
{
|
{
|
||||||
"name": "@sd/interface",
|
"name": "@sd/interface",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
"./types": "./src/types"
|
"./types": "./src/types"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"icons": "ts-node ./scripts/generateSvgImports.mjs"
|
"icons": "ts-node ./scripts/generateSvgImports.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.5.10",
|
"@apollo/client": "^3.5.10",
|
||||||
"@fontsource/inter": "^4.5.7",
|
"@fontsource/inter": "^4.5.7",
|
||||||
"@headlessui/react": "^1.5.0",
|
"@headlessui/react": "^1.5.0",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@radix-ui/react-dialog": "^0.1.7",
|
"@radix-ui/react-dialog": "^0.1.7",
|
||||||
"@radix-ui/react-dropdown-menu": "^0.1.6",
|
"@radix-ui/react-dropdown-menu": "^0.1.6",
|
||||||
"@radix-ui/react-icons": "^1.1.0",
|
"@radix-ui/react-icons": "^1.1.0",
|
||||||
"@radix-ui/react-progress": "^0.1.4",
|
"@radix-ui/react-progress": "^0.1.4",
|
||||||
"@radix-ui/react-slider": "^0.1.4",
|
"@radix-ui/react-slider": "^0.1.4",
|
||||||
"@sd/client": "workspace:*",
|
"@sd/client": "workspace:*",
|
||||||
"@sd/core": "workspace:*",
|
"@sd/core": "workspace:*",
|
||||||
"@sd/ui": "workspace:*",
|
"@sd/ui": "workspace:*",
|
||||||
"@vitejs/plugin-react": "^1.3.1",
|
"@vitejs/plugin-react": "^1.3.1",
|
||||||
"autoprefixer": "^10.4.4",
|
"autoprefixer": "^10.4.4",
|
||||||
"byte-size": "^8.1.0",
|
"byte-size": "^8.1.0",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"immer": "^9.0.12",
|
"immer": "^9.0.12",
|
||||||
"jotai": "^1.6.2",
|
"jotai": "^1.6.2",
|
||||||
"moment": "^2.29.2",
|
"moment": "^2.29.2",
|
||||||
"phosphor-react": "^1.4.1",
|
"phosphor-react": "^1.4.1",
|
||||||
"pretty-bytes": "^6.0.0",
|
"pretty-bytes": "^6.0.0",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-countup": "^6.2.0",
|
"react-countup": "^6.2.0",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"react-dropzone": "^12.0.4",
|
"react-dropzone": "^12.0.4",
|
||||||
"react-error-boundary": "^3.1.4",
|
"react-error-boundary": "^3.1.4",
|
||||||
"react-hotkeys-hook": "^3.4.4",
|
"react-hotkeys-hook": "^3.4.4",
|
||||||
"react-json-view": "^1.21.3",
|
"react-json-view": "^1.21.3",
|
||||||
"react-loading-icons": "^1.0.8",
|
"react-loading-icons": "^1.0.8",
|
||||||
"react-portal": "^4.2.2",
|
"react-portal": "^4.2.2",
|
||||||
"react-query": "^3.34.19",
|
"react-query": "^3.34.19",
|
||||||
"react-router": "6.3.0",
|
"react-router": "6.3.0",
|
||||||
"react-router-dom": "6.3.0",
|
"react-router-dom": "6.3.0",
|
||||||
"react-scrollbars-custom": "^4.0.27",
|
"react-scrollbars-custom": "^4.0.27",
|
||||||
"react-spline": "^1.2.1",
|
"react-spline": "^1.2.1",
|
||||||
"react-transition-group": "^4.4.2",
|
"react-transition-group": "^4.4.2",
|
||||||
"react-virtuoso": "^2.9.0",
|
"react-virtuoso": "^2.9.0",
|
||||||
"rooks": "^5.11.0",
|
"rooks": "^5.11.0",
|
||||||
"tailwindcss": "^3.0.23",
|
"tailwindcss": "^3.0.23",
|
||||||
"zustand": "^3.7.2"
|
"zustand": "^3.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/babel-core": "^6.25.7",
|
"@types/babel-core": "^6.25.7",
|
||||||
"@types/byte-size": "^8.1.0",
|
"@types/byte-size": "^8.1.0",
|
||||||
"@types/lodash": "^4.14.182",
|
"@types/lodash": "^4.14.182",
|
||||||
"@types/node": "^17.0.23",
|
"@types/node": "^17.0.23",
|
||||||
"@types/pretty-bytes": "^5.2.0",
|
"@types/pretty-bytes": "^5.2.0",
|
||||||
"@types/react": "^18.0.8",
|
"@types/react": "^18.0.8",
|
||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@types/react-table": "^7.7.10",
|
"@types/react-table": "^7.7.10",
|
||||||
"@types/react-window": "^1.8.5",
|
"@types/react-window": "^1.8.5",
|
||||||
"@types/tailwindcss": "^3.0.10",
|
"@types/tailwindcss": "^3.0.10",
|
||||||
"@vitejs/plugin-react": "^1.3.1",
|
"@vitejs/plugin-react": "^1.3.1",
|
||||||
"concurrently": "^7.1.0",
|
"concurrently": "^7.1.0",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
"typescript": "^4.6.3",
|
"typescript": "^4.6.3",
|
||||||
"vite": "^2.9.1",
|
"vite": "^2.9.1",
|
||||||
"vite-plugin-svgr": "^1.1.0"
|
"vite-plugin-svgr": "^1.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,13 @@ import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
|
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
import {
|
import {
|
||||||
Location,
|
Location,
|
||||||
MemoryRouter,
|
MemoryRouter,
|
||||||
Outlet,
|
Outlet,
|
||||||
Route,
|
Route,
|
||||||
Routes,
|
Routes,
|
||||||
useLocation,
|
useLocation,
|
||||||
useNavigate
|
useNavigate
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import { Sidebar } from './components/file/Sidebar';
|
import { Sidebar } from './components/file/Sidebar';
|
||||||
import { Modal } from './components/layout/Modal';
|
import { Modal } from './components/layout/Modal';
|
||||||
|
@ -58,63 +58,63 @@ export interface AppProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function AppLayout() {
|
function AppLayout() {
|
||||||
const appPropsContext = useContext(AppPropsContext);
|
const appPropsContext = useContext(AppPropsContext);
|
||||||
const [isWindowRounded, setIsWindowRounded] = useState(false);
|
const [isWindowRounded, setIsWindowRounded] = useState(false);
|
||||||
const [hasWindowBorder, setHasWindowBorder] = useState(true);
|
const [hasWindowBorder, setHasWindowBorder] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appPropsContext?.platform === 'macOS') {
|
if (appPropsContext?.platform === 'macOS') {
|
||||||
setIsWindowRounded(true);
|
setIsWindowRounded(true);
|
||||||
}
|
}
|
||||||
if (appPropsContext?.platform === 'browser') {
|
if (appPropsContext?.platform === 'browser') {
|
||||||
setHasWindowBorder(false);
|
setHasWindowBorder(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex flex-row h-screen overflow-hidden text-gray-900 bg-white select-none dark:text-white dark:bg-gray-650',
|
'flex flex-row h-screen overflow-hidden text-gray-900 bg-white select-none dark:text-white dark:bg-gray-650',
|
||||||
isWindowRounded && 'rounded-xl',
|
isWindowRounded && 'rounded-xl',
|
||||||
hasWindowBorder && 'border border-gray-200 dark:border-gray-500'
|
hasWindowBorder && 'border border-gray-200 dark:border-gray-500'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div className="flex flex-col w-full min-h-full">
|
<div className="flex flex-col w-full min-h-full">
|
||||||
{/* <TopBar /> */}
|
{/* <TopBar /> */}
|
||||||
|
|
||||||
<div className="relative flex w-full">
|
<div className="relative flex w-full">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SettingsRoutes({ modal = false }) {
|
function SettingsRoutes({ modal = false }) {
|
||||||
return (
|
return (
|
||||||
<SlideUp>
|
<SlideUp>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path={modal ? '/settings' : '/'}
|
path={modal ? '/settings' : '/'}
|
||||||
element={modal ? <Modal children={<SettingsScreen />} /> : <SettingsScreen />}
|
element={modal ? <Modal children={<SettingsScreen />} /> : <SettingsScreen />}
|
||||||
>
|
>
|
||||||
<Route index element={<GeneralSettings />} />
|
<Route index element={<GeneralSettings />} />
|
||||||
<Route path="general" element={<GeneralSettings />} />
|
<Route path="general" element={<GeneralSettings />} />
|
||||||
<Route path="security" element={<SecuritySettings />} />
|
<Route path="security" element={<SecuritySettings />} />
|
||||||
<Route path="appearance" element={<></>} />
|
<Route path="appearance" element={<></>} />
|
||||||
<Route path="experimental" element={<ExperimentalSettings />} />
|
<Route path="experimental" element={<ExperimentalSettings />} />
|
||||||
<Route path="locations" element={<LocationSettings />} />
|
<Route path="locations" element={<LocationSettings />} />
|
||||||
<Route path="library" element={<LibrarySettings />} />
|
<Route path="library" element={<LibrarySettings />} />
|
||||||
<Route path="media" element={<></>} />
|
<Route path="media" element={<></>} />
|
||||||
<Route path="keys" element={<></>} />
|
<Route path="keys" element={<></>} />
|
||||||
<Route path="tags" element={<></>} />
|
<Route path="tags" element={<></>} />
|
||||||
<Route path="sync" element={<></>} />
|
<Route path="sync" element={<></>} />
|
||||||
<Route path="contacts" element={<></>} />
|
<Route path="contacts" element={<></>} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</SlideUp>
|
</SlideUp>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Router() {
|
function Router() {
|
||||||
|
@ -146,89 +146,89 @@ function Router() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
role="alert"
|
role="alert"
|
||||||
className="flex flex-col items-center justify-center w-screen h-screen p-4 border border-gray-200 rounded-lg dark:border-gray-650 bg-gray-50 dark:bg-gray-650 dark:text-white"
|
className="flex flex-col items-center justify-center w-screen h-screen p-4 border border-gray-200 rounded-lg dark:border-gray-650 bg-gray-50 dark:bg-gray-650 dark:text-white"
|
||||||
>
|
>
|
||||||
<p className="m-3 text-sm font-bold text-gray-400">APP CRASHED</p>
|
<p className="m-3 text-sm font-bold text-gray-400">APP CRASHED</p>
|
||||||
<h1 className="text-2xl font-bold">We're past the event horizon...</h1>
|
<h1 className="text-2xl font-bold">We're past the event horizon...</h1>
|
||||||
<pre className="m-2">Error: {error.message}</pre>
|
<pre className="m-2">Error: {error.message}</pre>
|
||||||
<div className="flex flex-row space-x-2">
|
<div className="flex flex-row space-x-2">
|
||||||
<Button variant="primary" className="mt-2" onClick={resetErrorBoundary}>
|
<Button variant="primary" className="mt-2" onClick={resetErrorBoundary}>
|
||||||
Reload
|
Reload
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="mt-2" onClick={resetErrorBoundary}>
|
<Button className="mt-2" onClick={resetErrorBoundary}>
|
||||||
Send report
|
Send report
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotFound() {
|
function NotFound() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
role="alert"
|
role="alert"
|
||||||
className="flex flex-col items-center justify-center w-full h-full p-4 rounded-lg dark:text-white"
|
className="flex flex-col items-center justify-center w-full h-full p-4 rounded-lg dark:text-white"
|
||||||
>
|
>
|
||||||
<p className="m-3 mt-20 text-sm font-semibold text-gray-500 uppercase">Error: 404</p>
|
<p className="m-3 mt-20 text-sm font-semibold text-gray-500 uppercase">Error: 404</p>
|
||||||
<h1 className="text-4xl font-bold">You chose nothingness.</h1>
|
<h1 className="text-4xl font-bold">You chose nothingness.</h1>
|
||||||
<div className="flex flex-row space-x-2">
|
<div className="flex flex-row space-x-2">
|
||||||
<Button variant="primary" className="mt-4" onClick={() => navigate(-1)}>
|
<Button variant="primary" className="mt-4" onClick={() => navigate(-1)}>
|
||||||
Go Back
|
Go Back
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MemoryRouterContainer() {
|
function MemoryRouterContainer() {
|
||||||
useCoreEvents();
|
useCoreEvents();
|
||||||
return (
|
return (
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Router />
|
<Router />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BrowserRouterContainer() {
|
function BrowserRouterContainer() {
|
||||||
useCoreEvents();
|
useCoreEvents();
|
||||||
return (
|
return (
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Router />
|
<Router />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bindCoreEvent() {}
|
export function bindCoreEvent() {}
|
||||||
|
|
||||||
export default function App(props: AppProps) {
|
export default function App(props: AppProps) {
|
||||||
// TODO: This is a hack and a better solution should probably be found.
|
// TODO: This is a hack and a better solution should probably be found.
|
||||||
// This exists so that the queryClient can be accessed within the subpackage '@sd/client'.
|
// This exists so that the queryClient can be accessed within the subpackage '@sd/client'.
|
||||||
// Refer to <ClientProvider /> for where this is used.
|
// Refer to <ClientProvider /> for where this is used.
|
||||||
window.ReactQueryClient ??= queryClient;
|
window.ReactQueryClient ??= queryClient;
|
||||||
|
|
||||||
setTransport(props.transport);
|
setTransport(props.transport);
|
||||||
|
|
||||||
console.log('App props', props);
|
console.log('App props', props);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}>
|
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}>
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
<QueryClientProvider client={queryClient} contextSharing={false}>
|
<QueryClientProvider client={queryClient} contextSharing={false}>
|
||||||
<AppPropsContext.Provider value={Object.assign({ isFocused: true }, props)}>
|
<AppPropsContext.Provider value={Object.assign({ isFocused: true }, props)}>
|
||||||
<ClientProvider>
|
<ClientProvider>
|
||||||
{props.useMemoryRouter ? <MemoryRouterContainer /> : <BrowserRouterContainer />}
|
{props.useMemoryRouter ? <MemoryRouterContainer /> : <BrowserRouterContainer />}
|
||||||
</ClientProvider>
|
</ClientProvider>
|
||||||
</AppPropsContext.Provider>
|
</AppPropsContext.Provider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,8 @@ import { ReactComponent as Folder_light } from './folder_light.svg';
|
||||||
import { ReactComponent as Folder_open } from './folder_open.svg';
|
import { ReactComponent as Folder_open } from './folder_open.svg';
|
||||||
import { ReactComponent as Fontotf } from './fontotf.svg';
|
import { ReactComponent as Fontotf } from './fontotf.svg';
|
||||||
import { ReactComponent as Fontttf } from './fontttf.svg';
|
import { ReactComponent as Fontttf } from './fontttf.svg';
|
||||||
import { ReactComponent as Fontwoff } from './fontwoff.svg';
|
|
||||||
import { ReactComponent as Fontwoff2 } from './fontwoff2.svg';
|
import { ReactComponent as Fontwoff2 } from './fontwoff2.svg';
|
||||||
|
import { ReactComponent as Fontwoff } from './fontwoff.svg';
|
||||||
import { ReactComponent as Git } from './git.svg';
|
import { ReactComponent as Git } from './git.svg';
|
||||||
import { ReactComponent as Go } from './go.svg';
|
import { ReactComponent as Go } from './go.svg';
|
||||||
import { ReactComponent as Gopackage } from './gopackage.svg';
|
import { ReactComponent as Gopackage } from './gopackage.svg';
|
||||||
|
@ -172,176 +172,176 @@ import { ReactComponent as Yarnerror } from './yarnerror.svg';
|
||||||
import { ReactComponent as Zip } from './zip.svg';
|
import { ReactComponent as Zip } from './zip.svg';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
ai: Ai,
|
ai: Ai,
|
||||||
angular: Angular,
|
angular: Angular,
|
||||||
audio: Audio,
|
audio: Audio,
|
||||||
audiomp3: Audiomp3,
|
audiomp3: Audiomp3,
|
||||||
audioogg: Audioogg,
|
audioogg: Audioogg,
|
||||||
audiowav: Audiowav,
|
audiowav: Audiowav,
|
||||||
babel: Babel,
|
babel: Babel,
|
||||||
bat: Bat,
|
bat: Bat,
|
||||||
bicep: Bicep,
|
bicep: Bicep,
|
||||||
binary: Binary,
|
binary: Binary,
|
||||||
blade: Blade,
|
blade: Blade,
|
||||||
browserslist: Browserslist,
|
browserslist: Browserslist,
|
||||||
bsconfig: Bsconfig,
|
bsconfig: Bsconfig,
|
||||||
bundler: Bundler,
|
bundler: Bundler,
|
||||||
c: C,
|
c: C,
|
||||||
cert: Cert,
|
cert: Cert,
|
||||||
cheader: Cheader,
|
cheader: Cheader,
|
||||||
cli: Cli,
|
cli: Cli,
|
||||||
compodoc: Compodoc,
|
compodoc: Compodoc,
|
||||||
composer: Composer,
|
composer: Composer,
|
||||||
conf: Conf,
|
conf: Conf,
|
||||||
cpp: Cpp,
|
cpp: Cpp,
|
||||||
csharp: Csharp,
|
csharp: Csharp,
|
||||||
cshtml: Cshtml,
|
cshtml: Cshtml,
|
||||||
css: Css,
|
css: Css,
|
||||||
cssmap: Cssmap,
|
cssmap: Cssmap,
|
||||||
csv: Csv,
|
csv: Csv,
|
||||||
dartlang: Dartlang,
|
dartlang: Dartlang,
|
||||||
docker: Docker,
|
docker: Docker,
|
||||||
dockerdebug: Dockerdebug,
|
dockerdebug: Dockerdebug,
|
||||||
dockerignore: Dockerignore,
|
dockerignore: Dockerignore,
|
||||||
editorconfig: Editorconfig,
|
editorconfig: Editorconfig,
|
||||||
eex: Eex,
|
eex: Eex,
|
||||||
elixir: Elixir,
|
elixir: Elixir,
|
||||||
elm: Elm,
|
elm: Elm,
|
||||||
env: Env,
|
env: Env,
|
||||||
erb: Erb,
|
erb: Erb,
|
||||||
erlang: Erlang,
|
erlang: Erlang,
|
||||||
eslint: Eslint,
|
eslint: Eslint,
|
||||||
exs: Exs,
|
exs: Exs,
|
||||||
exx: Exx,
|
exx: Exx,
|
||||||
file: File,
|
file: File,
|
||||||
folder: Folder,
|
folder: Folder,
|
||||||
folder_light: Folder_light,
|
folder_light: Folder_light,
|
||||||
folder_open: Folder_open,
|
folder_open: Folder_open,
|
||||||
fontotf: Fontotf,
|
fontotf: Fontotf,
|
||||||
fontttf: Fontttf,
|
fontttf: Fontttf,
|
||||||
fontwoff: Fontwoff,
|
fontwoff: Fontwoff,
|
||||||
fontwoff2: Fontwoff2,
|
fontwoff2: Fontwoff2,
|
||||||
git: Git,
|
git: Git,
|
||||||
go: Go,
|
go: Go,
|
||||||
gopackage: Gopackage,
|
gopackage: Gopackage,
|
||||||
gradle: Gradle,
|
gradle: Gradle,
|
||||||
graphql: Graphql,
|
graphql: Graphql,
|
||||||
groovy: Groovy,
|
groovy: Groovy,
|
||||||
grunt: Grunt,
|
grunt: Grunt,
|
||||||
gulp: Gulp,
|
gulp: Gulp,
|
||||||
haml: Haml,
|
haml: Haml,
|
||||||
handlebars: Handlebars,
|
handlebars: Handlebars,
|
||||||
haskell: Haskell,
|
haskell: Haskell,
|
||||||
html: Html,
|
html: Html,
|
||||||
image: Image,
|
image: Image,
|
||||||
imagegif: Imagegif,
|
imagegif: Imagegif,
|
||||||
imageico: Imageico,
|
imageico: Imageico,
|
||||||
imagejpg: Imagejpg,
|
imagejpg: Imagejpg,
|
||||||
imagepng: Imagepng,
|
imagepng: Imagepng,
|
||||||
imagewebp: Imagewebp,
|
imagewebp: Imagewebp,
|
||||||
info: Info,
|
info: Info,
|
||||||
ipynb: Ipynb,
|
ipynb: Ipynb,
|
||||||
java: Java,
|
java: Java,
|
||||||
jenkins: Jenkins,
|
jenkins: Jenkins,
|
||||||
jest: Jest,
|
jest: Jest,
|
||||||
jinja: Jinja,
|
jinja: Jinja,
|
||||||
js: Js,
|
js: Js,
|
||||||
jsmap: Jsmap,
|
jsmap: Jsmap,
|
||||||
json: Json,
|
json: Json,
|
||||||
jsp: Jsp,
|
jsp: Jsp,
|
||||||
julia: Julia,
|
julia: Julia,
|
||||||
karma: Karma,
|
karma: Karma,
|
||||||
key: Key,
|
key: Key,
|
||||||
less: Less,
|
less: Less,
|
||||||
license: License,
|
license: License,
|
||||||
lighteditorconfig: Lighteditorconfig,
|
lighteditorconfig: Lighteditorconfig,
|
||||||
liquid: Liquid,
|
liquid: Liquid,
|
||||||
llvm: Llvm,
|
llvm: Llvm,
|
||||||
log: Log,
|
log: Log,
|
||||||
lua: Lua,
|
lua: Lua,
|
||||||
m: M,
|
m: M,
|
||||||
markdown: Markdown,
|
markdown: Markdown,
|
||||||
mint: Mint,
|
mint: Mint,
|
||||||
mov: Mov,
|
mov: Mov,
|
||||||
mp4: Mp4,
|
mp4: Mp4,
|
||||||
nestjs: Nestjs,
|
nestjs: Nestjs,
|
||||||
nestjscontroller: Nestjscontroller,
|
nestjscontroller: Nestjscontroller,
|
||||||
nestjsdecorator: Nestjsdecorator,
|
nestjsdecorator: Nestjsdecorator,
|
||||||
nestjsfilter: Nestjsfilter,
|
nestjsfilter: Nestjsfilter,
|
||||||
nestjsguard: Nestjsguard,
|
nestjsguard: Nestjsguard,
|
||||||
nestjsmodule: Nestjsmodule,
|
nestjsmodule: Nestjsmodule,
|
||||||
nestjsservice: Nestjsservice,
|
nestjsservice: Nestjsservice,
|
||||||
netlify: Netlify,
|
netlify: Netlify,
|
||||||
nginx: Nginx,
|
nginx: Nginx,
|
||||||
nim: Nim,
|
nim: Nim,
|
||||||
njk: Njk,
|
njk: Njk,
|
||||||
nodemon: Nodemon,
|
nodemon: Nodemon,
|
||||||
npm: Npm,
|
npm: Npm,
|
||||||
npmlock: Npmlock,
|
npmlock: Npmlock,
|
||||||
nuxt: Nuxt,
|
nuxt: Nuxt,
|
||||||
nvm: Nvm,
|
nvm: Nvm,
|
||||||
opengl: Opengl,
|
opengl: Opengl,
|
||||||
pdf: Pdf,
|
pdf: Pdf,
|
||||||
photoshop: Photoshop,
|
photoshop: Photoshop,
|
||||||
php: Php,
|
php: Php,
|
||||||
postcssconfig: Postcssconfig,
|
postcssconfig: Postcssconfig,
|
||||||
powershell: Powershell,
|
powershell: Powershell,
|
||||||
powershelldata: Powershelldata,
|
powershelldata: Powershelldata,
|
||||||
powershellmodule: Powershellmodule,
|
powershellmodule: Powershellmodule,
|
||||||
prettier: Prettier,
|
prettier: Prettier,
|
||||||
prisma: Prisma,
|
prisma: Prisma,
|
||||||
prolog: Prolog,
|
prolog: Prolog,
|
||||||
pug: Pug,
|
pug: Pug,
|
||||||
python: Python,
|
python: Python,
|
||||||
qt: Qt,
|
qt: Qt,
|
||||||
razor: Razor,
|
razor: Razor,
|
||||||
reactjs: Reactjs,
|
reactjs: Reactjs,
|
||||||
reactts: Reactts,
|
reactts: Reactts,
|
||||||
readme: Readme,
|
readme: Readme,
|
||||||
rescript: Rescript,
|
rescript: Rescript,
|
||||||
rjson: Rjson,
|
rjson: Rjson,
|
||||||
robots: Robots,
|
robots: Robots,
|
||||||
rollup: Rollup,
|
rollup: Rollup,
|
||||||
ruby: Ruby,
|
ruby: Ruby,
|
||||||
rust: Rust,
|
rust: Rust,
|
||||||
sass: Sass,
|
sass: Sass,
|
||||||
scss: Scss,
|
scss: Scss,
|
||||||
shell: Shell,
|
shell: Shell,
|
||||||
smarty: Smarty,
|
smarty: Smarty,
|
||||||
sol: Sol,
|
sol: Sol,
|
||||||
sql: Sql,
|
sql: Sql,
|
||||||
storybook: Storybook,
|
storybook: Storybook,
|
||||||
stylelint: Stylelint,
|
stylelint: Stylelint,
|
||||||
stylus: Stylus,
|
stylus: Stylus,
|
||||||
svelte: Svelte,
|
svelte: Svelte,
|
||||||
svg: Svg,
|
svg: Svg,
|
||||||
swift: Swift,
|
swift: Swift,
|
||||||
symfony: Symfony,
|
symfony: Symfony,
|
||||||
tailwind: Tailwind,
|
tailwind: Tailwind,
|
||||||
testjs: Testjs,
|
testjs: Testjs,
|
||||||
testts: Testts,
|
testts: Testts,
|
||||||
tmpl: Tmpl,
|
tmpl: Tmpl,
|
||||||
toml: Toml,
|
toml: Toml,
|
||||||
travis: Travis,
|
travis: Travis,
|
||||||
tsconfig: Tsconfig,
|
tsconfig: Tsconfig,
|
||||||
tsx: Tsx,
|
tsx: Tsx,
|
||||||
twig: Twig,
|
twig: Twig,
|
||||||
txt: Txt,
|
txt: Txt,
|
||||||
typescript: Typescript,
|
typescript: Typescript,
|
||||||
typescriptdef: Typescriptdef,
|
typescriptdef: Typescriptdef,
|
||||||
ui: Ui,
|
ui: Ui,
|
||||||
user: User,
|
user: User,
|
||||||
vercel: Vercel,
|
vercel: Vercel,
|
||||||
video: Video,
|
video: Video,
|
||||||
vite: Vite,
|
vite: Vite,
|
||||||
vscode: Vscode,
|
vscode: Vscode,
|
||||||
vue: Vue,
|
vue: Vue,
|
||||||
wasm: Wasm,
|
wasm: Wasm,
|
||||||
webpack: Webpack,
|
webpack: Webpack,
|
||||||
windi: Windi,
|
windi: Windi,
|
||||||
xml: Xml,
|
xml: Xml,
|
||||||
yaml: Yaml,
|
yaml: Yaml,
|
||||||
yarn: Yarn,
|
yarn: Yarn,
|
||||||
yarnerror: Yarnerror,
|
yarnerror: Yarnerror,
|
||||||
zip: Zip
|
zip: Zip
|
||||||
};
|
};
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
||||||
<svg width="124" height="124" viewBox="0 0 124 124" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect x="25.6391" y="87.1574" width="87" height="14.1887" transform="rotate(-45 25.6391 87.1574)" fill="#7E0508"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.05806 8.94194C3.30214 9.18602 3.69786 9.18602 3.94194 8.94194L8.94194 3.94194C9.18602 3.69786 9.18602 3.30214 8.94194 3.05806C8.69786 2.81398 8.30214 2.81398 8.05806 3.05806L3.05806 8.05806C2.81398 8.30214 2.81398 8.69786 3.05806 8.94194Z" fill="#691109"/>
|
||||||
<rect x="26.5762" y="34.9421" width="13.7508" height="87" transform="rotate(-45 26.5762 34.9421)" fill="#7E0508"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.94194 8.94194C8.69786 9.18602 8.30214 9.18602 8.05806 8.94194L3.05806 3.94194C2.81398 3.69786 2.81398 3.30214 3.05806 3.05806C3.30214 2.81398 3.69786 2.81398 3.94194 3.05806L8.94194 8.05806C9.18602 8.30214 9.18602 8.69786 8.94194 8.94194Z" fill="#691109"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 721 B |
|
@ -1,4 +1,4 @@
|
||||||
<svg width="143" height="143" viewBox="0 0 143 143" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M108.242 32.8833C108.797 32.8817 109.247 33.3317 109.245 33.8862L109.092 87.5891C109.09 88.4786 108.014 88.9223 107.385 88.2933L53.8351 34.7432C53.2061 34.1142 53.6499 33.0387 54.5394 33.0361L108.242 32.8833Z" fill="#0B650D"/>
|
<path d="M9 8C9 8.55228 8.55228 9 8 9H4.5L9 4.5V8Z" fill="#286017"/>
|
||||||
<path d="M33.8862 109.245C33.3317 109.247 32.8818 108.797 32.8833 108.242L33.0361 54.5394C33.0387 53.6499 34.1142 53.2061 34.7432 53.8351L88.2934 107.385C88.9223 108.014 88.4786 109.09 87.5891 109.092L33.8862 109.245Z" fill="#0B650D"/>
|
<path d="M3 4C3 3.44772 3.44772 3 4 3L7.5 3L3 7.5L3 4Z" fill="#286017"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 579 B After Width: | Height: | Size: 245 B |
|
@ -1,3 +1,3 @@
|
||||||
<svg width="87" height="16" viewBox="0 0 87 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M87 0H0V16H87V0Z" fill="#985712"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.75 6C9.75 5.58579 9.41421 5.25 9 5.25H3C2.58579 5.25 2.25 5.58579 2.25 6C2.25 6.41421 2.58579 6.75 3 6.75H9C9.41421 6.75 9.75 6.41421 9.75 6Z" fill="#8E591D"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 147 B After Width: | Height: | Size: 315 B |
|
@ -1,112 +1,113 @@
|
||||||
|
import { KeyIcon } from '@heroicons/react/outline';
|
||||||
|
import { CogIcon, LockClosedIcon } from '@heroicons/react/solid';
|
||||||
|
import { Button } from '@sd/ui';
|
||||||
import {
|
import {
|
||||||
Cloud,
|
Cloud,
|
||||||
Desktop,
|
Desktop,
|
||||||
DeviceMobileCamera,
|
DeviceMobileCamera,
|
||||||
DotsSixVertical,
|
DotsSixVertical,
|
||||||
Laptop,
|
Laptop,
|
||||||
Phone,
|
Phone,
|
||||||
PhoneX
|
PhoneX
|
||||||
} from 'phosphor-react';
|
} from 'phosphor-react';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import FileItem from '../file/FileItem';
|
|
||||||
import { Button } from '@sd/ui';
|
|
||||||
import ProgressBar from '../primitive/ProgressBar';
|
|
||||||
import { CogIcon, LockClosedIcon } from '@heroicons/react/solid';
|
|
||||||
import { KeyIcon } from '@heroicons/react/outline';
|
|
||||||
import LoadingIcons, { Rings } from 'react-loading-icons';
|
import LoadingIcons, { Rings } from 'react-loading-icons';
|
||||||
|
|
||||||
|
import FileItem from '../file/FileItem';
|
||||||
|
import ProgressBar from '../primitive/ProgressBar';
|
||||||
|
|
||||||
export interface DeviceProps {
|
export interface DeviceProps {
|
||||||
name: string;
|
name: string;
|
||||||
size: string;
|
size: string;
|
||||||
type: 'laptop' | 'desktop' | 'phone' | 'server';
|
type: 'laptop' | 'desktop' | 'phone' | 'server';
|
||||||
locations: { name: string }[];
|
locations: { name: string }[];
|
||||||
runningJob?: { amount: number; task: string };
|
runningJob?: { amount: number; task: string };
|
||||||
removeThisSoon?: boolean;
|
removeThisSoon?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Device(props: DeviceProps) {
|
export function Device(props: DeviceProps) {
|
||||||
const [selectedFile, setSelectedFile] = useState<null | string>(null);
|
const [selectedFile, setSelectedFile] = useState<null | string>(null);
|
||||||
|
|
||||||
function handleSelect(key: string) {
|
function handleSelect(key: string) {
|
||||||
if (selectedFile === key) setSelectedFile(null);
|
if (selectedFile === key) setSelectedFile(null);
|
||||||
else setSelectedFile(key);
|
else setSelectedFile(key);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="w-full bg-gray-600 border rounded-md border-gray-550 ">
|
<div className="w-full bg-gray-600 border rounded-md border-gray-550 ">
|
||||||
<div className="flex flex-row items-center px-4 pt-2 pb-2">
|
<div className="flex flex-row items-center px-4 pt-2 pb-2">
|
||||||
<DotsSixVertical weight="bold" className="mr-3 opacity-30" />
|
<DotsSixVertical weight="bold" className="mr-3 opacity-30" />
|
||||||
{props.type === 'phone' && <DeviceMobileCamera weight="fill" size={20} className="mr-2" />}
|
{props.type === 'phone' && <DeviceMobileCamera weight="fill" size={20} className="mr-2" />}
|
||||||
{props.type === 'laptop' && <Laptop weight="fill" size={20} className="mr-2" />}
|
{props.type === 'laptop' && <Laptop weight="fill" size={20} className="mr-2" />}
|
||||||
{props.type === 'desktop' && <Desktop weight="fill" size={20} className="mr-2" />}
|
{props.type === 'desktop' && <Desktop weight="fill" size={20} className="mr-2" />}
|
||||||
{props.type === 'server' && <Cloud weight="fill" size={20} className="mr-2" />}
|
{props.type === 'server' && <Cloud weight="fill" size={20} className="mr-2" />}
|
||||||
<h3 className="font-semibold text-md">{props.name || 'Unnamed Device'}</h3>
|
<h3 className="font-semibold text-md">{props.name || 'Unnamed Device'}</h3>
|
||||||
<div className="flex flex-row space-x-1.5 mt-0.5">
|
<div className="flex flex-row space-x-1.5 mt-0.5">
|
||||||
<span className="font-semibold flex flex-row h-[19px] -mt-0.5 ml-3 py-0.5 px-1.5 text-[10px] rounded bg-gray-500 text-gray-400">
|
<span className="font-semibold flex flex-row h-[19px] -mt-0.5 ml-3 py-0.5 px-1.5 text-[10px] rounded bg-gray-500 text-gray-400">
|
||||||
<LockClosedIcon className="w-3 h-3 mr-1 -ml-0.5 m-[1px]" />
|
<LockClosedIcon className="w-3 h-3 mr-1 -ml-0.5 m-[1px]" />
|
||||||
P2P
|
P2P
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-semibold py-0.5 px-1.5 text-sm ml-2 text-gray-400 ">
|
<span className="font-semibold py-0.5 px-1.5 text-sm ml-2 text-gray-400 ">
|
||||||
{props.size}
|
{props.size}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex flex-grow" />
|
<div className="flex flex-grow" />
|
||||||
{props.runningJob && (
|
{props.runningJob && (
|
||||||
<div className="flex flex-row ml-5 bg-opacity-50 rounded-md bg-gray-550 ">
|
<div className="flex flex-row ml-5 bg-opacity-50 rounded-md bg-gray-550 ">
|
||||||
<Rings
|
<Rings
|
||||||
stroke="#2599FF"
|
stroke="#2599FF"
|
||||||
strokeOpacity={4}
|
strokeOpacity={4}
|
||||||
strokeWidth={10}
|
strokeWidth={10}
|
||||||
speed={0.5}
|
speed={0.5}
|
||||||
className="ml-0.5 mt-[2px] -mr-1 w-7 h-7"
|
className="ml-0.5 mt-[2px] -mr-1 w-7 h-7"
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col p-2">
|
<div className="flex flex-col p-2">
|
||||||
<span className="mb-[2px] -mt-1 truncate text-gray-450 text-tiny">
|
<span className="mb-[2px] -mt-1 truncate text-gray-450 text-tiny">
|
||||||
{props.runningJob.task}...
|
{props.runningJob.task}...
|
||||||
</span>
|
</span>
|
||||||
<ProgressBar value={props.runningJob?.amount} total={100} />
|
<ProgressBar value={props.runningJob?.amount} total={100} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-row ml-3 space-x-1">
|
<div className="flex flex-row ml-3 space-x-1">
|
||||||
<Button className="!p-1 ">
|
<Button className="!p-1 ">
|
||||||
<KeyIcon className="w-5 h-5" />
|
<KeyIcon className="w-5 h-5" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="!p-1 ">
|
<Button className="!p-1 ">
|
||||||
<CogIcon className="w-5 h-5" />
|
<CogIcon className="w-5 h-5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* <hr className="border-gray-700" />
|
{/* <hr className="border-gray-700" />
|
||||||
<hr className="border-gray-550" /> */}
|
<hr className="border-gray-550" /> */}
|
||||||
<div className="px-4 pb-3 mt-3">
|
<div className="px-4 pb-3 mt-3">
|
||||||
{props.locations.map((location, key) => (
|
{props.locations.map((location, key) => (
|
||||||
<FileItem
|
<FileItem
|
||||||
key={key}
|
key={key}
|
||||||
selected={selectedFile == location.name}
|
selected={selectedFile == location.name}
|
||||||
onClick={() => handleSelect(location.name)}
|
onClick={() => handleSelect(location.name)}
|
||||||
fileName={location.name}
|
fileName={location.name}
|
||||||
folder
|
folder
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{props.removeThisSoon && (
|
{props.removeThisSoon && (
|
||||||
<>
|
<>
|
||||||
<FileItem
|
<FileItem
|
||||||
selected={selectedFile == 'tsx'}
|
selected={selectedFile == 'tsx'}
|
||||||
onClick={() => handleSelect('tsx')}
|
onClick={() => handleSelect('tsx')}
|
||||||
fileName="App.tsx"
|
fileName="App.tsx"
|
||||||
format="tsx"
|
format="tsx"
|
||||||
iconName="reactts"
|
iconName="reactts"
|
||||||
/>
|
/>
|
||||||
<FileItem
|
<FileItem
|
||||||
selected={selectedFile == 'vite'}
|
selected={selectedFile == 'vite'}
|
||||||
onClick={() => handleSelect('vite')}
|
onClick={() => handleSelect('vite')}
|
||||||
fileName="vite.config.js"
|
fileName="vite.config.js"
|
||||||
format="vite"
|
format="vite"
|
||||||
iconName="vite"
|
iconName="vite"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import create from 'zustand';
|
import create from 'zustand';
|
||||||
|
|
||||||
export const useStore = create((set) => ({
|
export const useStore = create((set) => ({
|
||||||
experimental: false
|
experimental: false
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,103 +1,103 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import icons from '../../assets/icons';
|
||||||
|
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
|
||||||
import { DefaultProps } from '../primitive/types';
|
import { DefaultProps } from '../primitive/types';
|
||||||
|
|
||||||
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
|
|
||||||
import icons from '../../assets/icons';
|
|
||||||
|
|
||||||
interface Props extends DefaultProps {
|
interface Props extends DefaultProps {
|
||||||
fileName: string;
|
fileName: string;
|
||||||
iconName?: string;
|
iconName?: string;
|
||||||
format?: string;
|
format?: string;
|
||||||
folder?: boolean;
|
folder?: boolean;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FileItem(props: Props) {
|
export default function FileItem(props: Props) {
|
||||||
// const Shadow = () => {
|
// const Shadow = () => {
|
||||||
// return (
|
// return (
|
||||||
// <div
|
// <div
|
||||||
// className={clsx(
|
// className={clsx(
|
||||||
// 'absolute opacity-100 transition-opacity duration-200 top-auto bottom-auto w-[64px] h-[40px] shadow-xl shadow-red-500',
|
// 'absolute opacity-100 transition-opacity duration-200 top-auto bottom-auto w-[64px] h-[40px] shadow-xl shadow-red-500',
|
||||||
// { 'opacity-100': props.selected }
|
// { 'opacity-100': props.selected }
|
||||||
// )}
|
// )}
|
||||||
// />
|
// />
|
||||||
// );
|
// );
|
||||||
// };
|
// };
|
||||||
return (
|
return (
|
||||||
<div onClick={props.onClick} className="inline-block w-[100px] mb-3" draggable>
|
<div onClick={props.onClick} className="inline-block w-[100px] mb-3" draggable>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'border-2 border-transparent rounded-lg text-center w-[100px] h-[100px] mb-1',
|
'border-2 border-transparent rounded-lg text-center w-[100px] h-[100px] mb-1',
|
||||||
{
|
{
|
||||||
'bg-gray-50 dark:bg-gray-650': props.selected
|
'bg-gray-50 dark:bg-gray-650': props.selected
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{props.folder ? (
|
{props.folder ? (
|
||||||
<div className="flex items-center justify-center w-full h-full active:translate-y-[1px]">
|
<div className="flex items-center justify-center w-full h-full active:translate-y-[1px]">
|
||||||
<div className="w-[70px]">
|
<div className="w-[70px]">
|
||||||
<Folder className="" />
|
<Folder className="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'w-[64px] mt-1.5 m-auto transition duration-200 rounded-lg h-[90px] relative active:translate-y-[1px]',
|
'w-[64px] mt-1.5 m-auto transition duration-200 rounded-lg h-[90px] relative active:translate-y-[1px]',
|
||||||
{
|
{
|
||||||
'': props.selected
|
'': props.selected
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="absolute top-0 left-0 pointer-events-none fill-gray-150 dark:fill-gray-550"
|
className="absolute top-0 left-0 pointer-events-none fill-gray-150 dark:fill-gray-550"
|
||||||
width="65"
|
width="65"
|
||||||
height="85"
|
height="85"
|
||||||
viewBox="0 0 65 81"
|
viewBox="0 0 65 81"
|
||||||
>
|
>
|
||||||
<path d="M0 8C0 3.58172 3.58172 0 8 0H39.6863C41.808 0 43.8429 0.842855 45.3431 2.34315L53.5 10.5L62.6569 19.6569C64.1571 21.1571 65 23.192 65 25.3137V73C65 77.4183 61.4183 81 57 81H8C3.58172 81 0 77.4183 0 73V8Z" />
|
<path d="M0 8C0 3.58172 3.58172 0 8 0H39.6863C41.808 0 43.8429 0.842855 45.3431 2.34315L53.5 10.5L62.6569 19.6569C64.1571 21.1571 65 23.192 65 25.3137V73C65 77.4183 61.4183 81 57 81H8C3.58172 81 0 77.4183 0 73V8Z" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg
|
<svg
|
||||||
width="22"
|
width="22"
|
||||||
height="22"
|
height="22"
|
||||||
className="absolute top-1 -right-[1px] z-10 fill-gray-50 dark:fill-gray-500 pointer-events-none"
|
className="absolute top-1 -right-[1px] z-10 fill-gray-50 dark:fill-gray-500 pointer-events-none"
|
||||||
viewBox="0 0 41 41"
|
viewBox="0 0 41 41"
|
||||||
>
|
>
|
||||||
<path d="M41.4116 40.5577H11.234C5.02962 40.5577 0 35.5281 0 29.3238V0L41.4116 40.5577Z" />
|
<path d="M41.4116 40.5577H11.234C5.02962 40.5577 0 35.5281 0 29.3238V0L41.4116 40.5577Z" />
|
||||||
</svg>
|
</svg>
|
||||||
<div className="absolute flex flex-col items-center justify-center w-full h-full">
|
<div className="absolute flex flex-col items-center justify-center w-full h-full">
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
{props.iconName && icons[props.iconName] ? (
|
{props.iconName && icons[props.iconName] ? (
|
||||||
(() => {
|
(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let Icon = icons[props.iconName];
|
let Icon = icons[props.iconName];
|
||||||
return (
|
return (
|
||||||
<Icon className="mt-2 pointer-events-none margin-auto w-[40px] h-[40px]" />
|
<Icon className="mt-2 pointer-events-none margin-auto w-[40px] h-[40px]" />
|
||||||
);
|
);
|
||||||
})()
|
})()
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
<span className="mt-1 text-xs font-bold text-center uppercase cursor-default text-gray-450">
|
<span className="mt-1 text-xs font-bold text-center uppercase cursor-default text-gray-450">
|
||||||
{props.format}
|
{props.format}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<span
|
<span
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'px-1.5 py-[1px] rounded-md text-sm font-medium text-gray-300 cursor-default',
|
'px-1.5 py-[1px] rounded-md text-sm font-medium text-gray-300 cursor-default',
|
||||||
{
|
{
|
||||||
'bg-primary !text-white': props.selected
|
'bg-primary !text-white': props.selected
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{props.fileName}
|
{props.fileName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,236 +4,250 @@ import { FilePath } from '@sd/core';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import byteSize from 'pretty-bytes';
|
import byteSize from 'pretty-bytes';
|
||||||
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
|
|
||||||
import create from 'zustand';
|
|
||||||
import { useKey, useWindowSize } from 'rooks';
|
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
|
||||||
|
import { useKey, useWindowSize } from 'rooks';
|
||||||
|
import create from 'zustand';
|
||||||
|
|
||||||
import { AppPropsContext } from '../../App';
|
import { AppPropsContext } from '../../App';
|
||||||
import FileThumb from './FileThumb';
|
import FileThumb from './FileThumb';
|
||||||
|
|
||||||
type ExplorerState = {
|
type ExplorerState = {
|
||||||
selectedRowIndex: number;
|
selectedRowIndex: number;
|
||||||
setSelectedRowIndex: (index: number) => void;
|
setSelectedRowIndex: (index: number) => void;
|
||||||
locationId: number;
|
locationId: number;
|
||||||
setLocationId: (index: number) => void;
|
setLocationId: (index: number) => void;
|
||||||
newThumbnails: Record<string, boolean>;
|
newThumbnails: Record<string, boolean>;
|
||||||
addNewThumbnail: (cas_id: string) => void;
|
addNewThumbnail: (cas_id: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useExplorerState = create<ExplorerState>((set) => ({
|
export const useExplorerState = create<ExplorerState>((set) => ({
|
||||||
selectedRowIndex: 1,
|
selectedRowIndex: 1,
|
||||||
setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index })),
|
setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index })),
|
||||||
locationId: -1,
|
locationId: -1,
|
||||||
setLocationId: (id: number) => set((state) => ({ ...state, locationId: id })),
|
setLocationId: (id: number) => set((state) => ({ ...state, locationId: id })),
|
||||||
newThumbnails: {},
|
newThumbnails: {},
|
||||||
addNewThumbnail: (cas_id: string) =>
|
addNewThumbnail: (cas_id: string) =>
|
||||||
set((state) => ({ ...state, newThumbnails: { ...state.newThumbnails, [cas_id]: true } }))
|
set((state) => ({
|
||||||
|
...state,
|
||||||
|
newThumbnails: { ...state.newThumbnails, [cas_id]: true }
|
||||||
|
}))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface IColumn {
|
interface IColumn {
|
||||||
column: string;
|
column: string;
|
||||||
key: string;
|
key: string;
|
||||||
width: number;
|
width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PADDING_SIZE = 130;
|
const PADDING_SIZE = 130;
|
||||||
|
|
||||||
// Function ensure no types are loss, but guarantees that they are Column[]
|
// Function ensure no types are loss, but guarantees that they are Column[]
|
||||||
function ensureIsColumns<T extends IColumn[]>(data: T) {
|
function ensureIsColumns<T extends IColumn[]>(data: T) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = ensureIsColumns([
|
const columns = ensureIsColumns([
|
||||||
{ column: 'Name', key: 'name', width: 280 } as const,
|
{ column: 'Name', key: 'name', width: 280 } as const,
|
||||||
// { column: 'Size', key: 'size_in_bytes', width: 120 } as const,
|
// { column: 'Size', key: 'size_in_bytes', width: 120 } as const,
|
||||||
{ column: 'Type', key: 'extension', width: 100 } as const
|
{ column: 'Type', key: 'extension', width: 100 } as const
|
||||||
]);
|
]);
|
||||||
|
|
||||||
type ColumnKey = typeof columns[number]['key'];
|
type ColumnKey = typeof columns[number]['key'];
|
||||||
|
|
||||||
const LocationContext = React.createContext<{ location_id: number; data_path: string }>({
|
const LocationContext = React.createContext<{
|
||||||
location_id: 1,
|
location_id: number;
|
||||||
data_path: ''
|
data_path: string;
|
||||||
|
}>({
|
||||||
|
location_id: 1,
|
||||||
|
data_path: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
export const FileList: React.FC<{ location_id: number; path: string; limit: number }> = (props) => {
|
export const FileList: React.FC<{
|
||||||
const size = useWindowSize();
|
location_id: number;
|
||||||
const tableContainer = useRef<null | HTMLDivElement>(null);
|
path: string;
|
||||||
const VList = useRef<null | VirtuosoHandle>(null);
|
limit: number;
|
||||||
|
}> = (props) => {
|
||||||
|
const size = useWindowSize();
|
||||||
|
const tableContainer = useRef<null | HTMLDivElement>(null);
|
||||||
|
const VList = useRef<null | VirtuosoHandle>(null);
|
||||||
|
|
||||||
const { data: client } = useBridgeQuery('ClientGetState', undefined, {
|
const { data: client } = useBridgeQuery('ClientGetState', undefined, {
|
||||||
refetchOnWindowFocus: false
|
refetchOnWindowFocus: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const path = props.path;
|
const path = props.path;
|
||||||
|
|
||||||
const { selectedRowIndex, setSelectedRowIndex, setLocationId } = useExplorerState();
|
const { selectedRowIndex, setSelectedRowIndex, setLocationId } = useExplorerState();
|
||||||
const [goingUp, setGoingUp] = useState(false);
|
const [goingUp, setGoingUp] = useState(false);
|
||||||
|
|
||||||
const { data: currentDir } = useBridgeQuery('LibGetExplorerDir', {
|
const { data: currentDir } = useBridgeQuery('LibGetExplorerDir', {
|
||||||
location_id: props.location_id,
|
location_id: props.location_id,
|
||||||
path,
|
path,
|
||||||
limit: props.limit
|
limit: props.limit
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedRowIndex === 0 && goingUp) {
|
if (selectedRowIndex === 0 && goingUp) {
|
||||||
VList.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
VList.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
if (selectedRowIndex != -1) {
|
if (selectedRowIndex != -1) {
|
||||||
VList.current?.scrollIntoView({
|
VList.current?.scrollIntoView({
|
||||||
index: goingUp ? selectedRowIndex - 1 : selectedRowIndex
|
index: goingUp ? selectedRowIndex - 1 : selectedRowIndex
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [selectedRowIndex]);
|
}, [selectedRowIndex]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocationId(props.location_id);
|
setLocationId(props.location_id);
|
||||||
}, [props.location_id]);
|
}, [props.location_id]);
|
||||||
|
|
||||||
useKey('ArrowUp', (e) => {
|
useKey('ArrowUp', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setGoingUp(true);
|
setGoingUp(true);
|
||||||
if (selectedRowIndex != -1 && selectedRowIndex !== 0) setSelectedRowIndex(selectedRowIndex - 1);
|
if (selectedRowIndex != -1 && selectedRowIndex !== 0) setSelectedRowIndex(selectedRowIndex - 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
useKey('ArrowDown', (e) => {
|
useKey('ArrowDown', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setGoingUp(false);
|
setGoingUp(false);
|
||||||
if (selectedRowIndex != -1 && selectedRowIndex !== (currentDir?.contents.length ?? 1) - 1)
|
if (selectedRowIndex != -1 && selectedRowIndex !== (currentDir?.contents.length ?? 1) - 1)
|
||||||
setSelectedRowIndex(selectedRowIndex + 1);
|
setSelectedRowIndex(selectedRowIndex + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
const Row = (index: number) => {
|
const Row = (index: number) => {
|
||||||
const row = currentDir?.contents?.[index];
|
const row = currentDir?.contents?.[index];
|
||||||
|
|
||||||
if (!row) return null;
|
if (!row) return null;
|
||||||
|
|
||||||
return <RenderRow key={index} row={row} rowIndex={index} dirId={currentDir?.directory.id} />;
|
return <RenderRow key={index} row={row} rowIndex={index} dirId={currentDir?.directory.id} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Header = () => (
|
const Header = () => (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="pt-20 pl-4 text-xl font-bold ">{currentDir?.directory.name}</h1>
|
<h1 className="pt-20 pl-4 text-xl font-bold ">{currentDir?.directory.name}</h1>
|
||||||
<div className="table-head">
|
<div className="table-head">
|
||||||
<div className="flex flex-row p-2 table-head-row">
|
<div className="flex flex-row p-2 table-head-row">
|
||||||
{columns.map((col) => (
|
{columns.map((col) => (
|
||||||
<div
|
<div
|
||||||
key={col.key}
|
key={col.key}
|
||||||
className="relative flex flex-row items-center pl-2 table-head-cell group"
|
className="relative flex flex-row items-center pl-2 table-head-cell group"
|
||||||
style={{ width: col.width }}
|
style={{ width: col.width }}
|
||||||
>
|
>
|
||||||
<DotsVerticalIcon className="absolute hidden w-5 h-5 -ml-5 cursor-move group-hover:block drag-handle opacity-10" />
|
<DotsVerticalIcon className="absolute hidden w-5 h-5 -ml-5 cursor-move group-hover:block drag-handle opacity-10" />
|
||||||
<span className="text-sm font-medium text-gray-500">{col.column}</span>
|
<span className="text-sm font-medium text-gray-500">{col.column}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => (
|
() => (
|
||||||
<div
|
<div
|
||||||
ref={tableContainer}
|
ref={tableContainer}
|
||||||
style={{ marginTop: -44 }}
|
style={{ marginTop: -44 }}
|
||||||
className="w-full pl-2 bg-white cursor-default table-container dark:bg-gray-650"
|
className="w-full pl-2 bg-white cursor-default table-container dark:bg-gray-650"
|
||||||
>
|
>
|
||||||
<LocationContext.Provider
|
<LocationContext.Provider
|
||||||
value={{ location_id: props.location_id, data_path: client?.data_path as string }}
|
value={{
|
||||||
>
|
location_id: props.location_id,
|
||||||
<Virtuoso
|
data_path: client?.data_path as string
|
||||||
data={currentDir?.contents}
|
}}
|
||||||
ref={VList}
|
>
|
||||||
style={{ height: size.innerHeight ?? 600 }}
|
<Virtuoso
|
||||||
totalCount={currentDir?.contents.length || 0}
|
data={currentDir?.contents}
|
||||||
itemContent={Row}
|
ref={VList}
|
||||||
components={{ Header, Footer: () => <div className="w-full " /> }}
|
style={{ height: size.innerHeight ?? 600 }}
|
||||||
increaseViewportBy={{ top: 400, bottom: 200 }}
|
totalCount={currentDir?.contents.length || 0}
|
||||||
className="outline-none explorer-scroll"
|
itemContent={Row}
|
||||||
/>
|
components={{ Header, Footer: () => <div className="w-full " /> }}
|
||||||
</LocationContext.Provider>
|
increaseViewportBy={{ top: 400, bottom: 200 }}
|
||||||
</div>
|
className="outline-none explorer-scroll"
|
||||||
),
|
/>
|
||||||
[props.location_id, size.innerWidth, currentDir?.directory.id, tableContainer.current]
|
</LocationContext.Provider>
|
||||||
);
|
</div>
|
||||||
|
),
|
||||||
|
[props.location_id, size.innerWidth, currentDir?.directory.id, tableContainer.current]
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RenderRow: React.FC<{
|
const RenderRow: React.FC<{
|
||||||
row: FilePath;
|
row: FilePath;
|
||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
dirId: number;
|
dirId: number;
|
||||||
}> = ({ row, rowIndex, dirId }) => {
|
}> = ({ row, rowIndex, dirId }) => {
|
||||||
const { selectedRowIndex, setSelectedRowIndex } = useExplorerState();
|
const { selectedRowIndex, setSelectedRowIndex } = useExplorerState();
|
||||||
const isActive = selectedRowIndex === rowIndex;
|
const isActive = selectedRowIndex === rowIndex;
|
||||||
|
|
||||||
let [_, setSearchParams] = useSearchParams();
|
let [_, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
function selectFileHandler() {
|
function selectFileHandler() {
|
||||||
if (selectedRowIndex == rowIndex) setSelectedRowIndex(-1);
|
if (selectedRowIndex == rowIndex) setSelectedRowIndex(-1);
|
||||||
else setSelectedRowIndex(rowIndex);
|
else setSelectedRowIndex(rowIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => (
|
() => (
|
||||||
<div
|
<div
|
||||||
onClick={selectFileHandler}
|
onClick={selectFileHandler}
|
||||||
onDoubleClick={() => {
|
onDoubleClick={() => {
|
||||||
if (row.is_dir) {
|
if (row.is_dir) {
|
||||||
setSearchParams({ path: row.materialized_path });
|
setSearchParams({ path: row.materialized_path });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'table-body-row mr-2 flex flex-row rounded-lg border-2',
|
'table-body-row mr-2 flex flex-row rounded-lg border-2',
|
||||||
isActive ? 'border-primary-500' : 'border-transparent',
|
isActive ? 'border-primary-500' : 'border-transparent',
|
||||||
rowIndex % 2 == 0 && 'bg-[#00000006] dark:bg-[#00000030]'
|
rowIndex % 2 == 0 && 'bg-[#00000006] dark:bg-[#00000030]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{columns.map((col) => (
|
{columns.map((col) => (
|
||||||
<div
|
<div
|
||||||
key={col.key}
|
key={col.key}
|
||||||
className="flex items-center px-4 py-2 pr-2 table-body-cell"
|
className="flex items-center px-4 py-2 pr-2 table-body-cell"
|
||||||
style={{ width: col.width }}
|
style={{ width: col.width }}
|
||||||
>
|
>
|
||||||
<RenderCell file={row} dirId={dirId} colKey={col?.key} />
|
<RenderCell file={row} dirId={dirId} colKey={col?.key} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
[row.id, isActive]
|
[row.id, isActive]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RenderCell: React.FC<{ colKey?: ColumnKey; dirId?: number; file?: FilePath }> = ({
|
const RenderCell: React.FC<{
|
||||||
colKey,
|
colKey?: ColumnKey;
|
||||||
file,
|
dirId?: number;
|
||||||
dirId
|
file?: FilePath;
|
||||||
}) => {
|
}> = ({ colKey, file, dirId }) => {
|
||||||
if (!file || !colKey || !dirId) return <></>;
|
if (!file || !colKey || !dirId) return <></>;
|
||||||
const row = file;
|
const row = file;
|
||||||
if (!row) return <></>;
|
if (!row) return <></>;
|
||||||
const appPropsContext = useContext(AppPropsContext);
|
const appPropsContext = useContext(AppPropsContext);
|
||||||
|
|
||||||
const value = row[colKey];
|
const value = row[colKey];
|
||||||
if (!value) return <></>;
|
if (!value) return <></>;
|
||||||
|
|
||||||
const location = useContext(LocationContext);
|
const location = useContext(LocationContext);
|
||||||
const { newThumbnails } = useExplorerState();
|
const { newThumbnails } = useExplorerState();
|
||||||
|
|
||||||
const hasNewThumbnail = !!newThumbnails[row?.temp_cas_id ?? ''];
|
const hasNewThumbnail = !!newThumbnails[row?.temp_cas_id ?? ''];
|
||||||
|
|
||||||
switch (colKey) {
|
switch (colKey) {
|
||||||
case 'name':
|
case 'name':
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row items-center overflow-hidden">
|
<div className="flex flex-row items-center overflow-hidden">
|
||||||
<div className="flex items-center justify-center shrink-0 w-6 h-6 mr-3">
|
<div className="flex items-center justify-center shrink-0 w-6 h-6 mr-3">
|
||||||
<FileThumb
|
<FileThumb
|
||||||
hasThumbnailOverride={hasNewThumbnail}
|
hasThumbnailOverride={hasNewThumbnail}
|
||||||
file={row}
|
file={row}
|
||||||
locationId={location.location_id}
|
locationId={location.location_id}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* {colKey == 'name' &&
|
{/* {colKey == 'name' &&
|
||||||
(() => {
|
(() => {
|
||||||
switch (row.extension.toLowerCase()) {
|
switch (row.extension.toLowerCase()) {
|
||||||
case 'mov' || 'mp4':
|
case 'mov' || 'mp4':
|
||||||
|
@ -245,19 +259,19 @@ const RenderCell: React.FC<{ colKey?: ColumnKey; dirId?: number; file?: FilePath
|
||||||
return <DocumentIcon className="flex-shrink-0 w-5 h-5 mr-3 text-gray-300" />;
|
return <DocumentIcon className="flex-shrink-0 w-5 h-5 mr-3 text-gray-300" />;
|
||||||
}
|
}
|
||||||
})()} */}
|
})()} */}
|
||||||
<span className="text-xs truncate">{row[colKey]}</span>
|
<span className="text-xs truncate">{row[colKey]}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
// case 'size_in_bytes':
|
// case 'size_in_bytes':
|
||||||
// return <span className="text-xs text-left">{byteSize(Number(value || 0))}</span>;
|
// return <span className="text-xs text-left">{byteSize(Number(value || 0))}</span>;
|
||||||
case 'extension':
|
case 'extension':
|
||||||
return <span className="text-xs text-left">{value}</span>;
|
return <span className="text-xs text-left">{value}</span>;
|
||||||
// case 'meta_integrity_hash':
|
// case 'meta_integrity_hash':
|
||||||
// return <span className="truncate">{value}</span>;
|
// return <span className="truncate">{value}</span>;
|
||||||
// case 'tags':
|
// case 'tags':
|
||||||
// return renderCellWithIcon(MusicNoteIcon);
|
// return renderCellWithIcon(MusicNoteIcon);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,37 +2,38 @@ import { useBridgeQuery } from '@sd/client';
|
||||||
import { FilePath } from '@sd/core';
|
import { FilePath } from '@sd/core';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
import { AppPropsContext } from '../../App';
|
import { AppPropsContext } from '../../App';
|
||||||
import icons from '../../assets/icons';
|
import icons from '../../assets/icons';
|
||||||
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
|
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
|
||||||
|
|
||||||
export default function FileThumb(props: {
|
export default function FileThumb(props: {
|
||||||
file: FilePath;
|
file: FilePath;
|
||||||
locationId: number;
|
locationId: number;
|
||||||
hasThumbnailOverride: boolean;
|
hasThumbnailOverride: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const appPropsContext = useContext(AppPropsContext);
|
const appPropsContext = useContext(AppPropsContext);
|
||||||
const { data: client } = useBridgeQuery('ClientGetState');
|
const { data: client } = useBridgeQuery('ClientGetState');
|
||||||
|
|
||||||
if (props.file.is_dir) {
|
if (props.file.is_dir) {
|
||||||
return <Folder className="max-w-[170px]" />;
|
return <Folder className="max-w-[170px]" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client?.data_path && (props.file.has_local_thumbnail || props.hasThumbnailOverride)) {
|
if (client?.data_path && (props.file.has_local_thumbnail || props.hasThumbnailOverride)) {
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
className="pointer-events-none z-90"
|
className="pointer-events-none z-90"
|
||||||
src={appPropsContext?.convertFileSrc(
|
src={appPropsContext?.convertFileSrc(
|
||||||
`${client.data_path}/thumbnails/${props.locationId}/${props.file.temp_cas_id}.webp`
|
`${client.data_path}/thumbnails/${props.locationId}/${props.file.temp_cas_id}.webp`
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (icons[props.file.extension as keyof typeof icons]) {
|
if (icons[props.file.extension as keyof typeof icons]) {
|
||||||
let Icon = icons[props.file.extension as keyof typeof icons];
|
let Icon = icons[props.file.extension as keyof typeof icons];
|
||||||
return <Icon className={clsx('max-w-[170px] w-full h-full', props.className)} />;
|
return <Icon className={clsx('max-w-[170px] w-full h-full', props.className)} />;
|
||||||
}
|
}
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,121 +1,122 @@
|
||||||
import React from 'react';
|
|
||||||
import { Transition } from '@headlessui/react';
|
import { Transition } from '@headlessui/react';
|
||||||
import moment from 'moment';
|
|
||||||
import { Input, TextArea } from '../primitive';
|
|
||||||
import { Button } from '@sd/ui';
|
|
||||||
import { ShareIcon } from '@heroicons/react/solid';
|
import { ShareIcon } from '@heroicons/react/solid';
|
||||||
import { Heart, Link } from 'phosphor-react';
|
|
||||||
import { useExplorerState } from './FileList';
|
|
||||||
import { FilePath } from '@sd/core';
|
import { FilePath } from '@sd/core';
|
||||||
import FileThumb from './FileThumb';
|
import { Button } from '@sd/ui';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Heart, Link } from 'phosphor-react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { default as types } from '../../constants/file-types.json';
|
import { default as types } from '../../constants/file-types.json';
|
||||||
|
import { Input, TextArea } from '../primitive';
|
||||||
|
import { useExplorerState } from './FileList';
|
||||||
|
import FileThumb from './FileThumb';
|
||||||
|
|
||||||
interface MetaItemProps {
|
interface MetaItemProps {
|
||||||
title: string;
|
title: string;
|
||||||
value: string | React.ReactNode;
|
value: string | React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MetaItem = (props: MetaItemProps) => {
|
const MetaItem = (props: MetaItemProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col px-3 py-1 meta-item">
|
<div className="flex flex-col px-3 py-1 meta-item">
|
||||||
<h5 className="text-xs font-bold">{props.title}</h5>
|
<h5 className="text-xs font-bold">{props.title}</h5>
|
||||||
{typeof props.value === 'string' ? (
|
{typeof props.value === 'string' ? (
|
||||||
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">{props.value}</p>
|
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">{props.value}</p>
|
||||||
) : (
|
) : (
|
||||||
props.value
|
props.value
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Divider = () => <div className="w-full my-1 h-[1px] bg-gray-100 dark:bg-gray-550" />;
|
const Divider = () => <div className="w-full my-1 h-[1px] bg-gray-100 dark:bg-gray-550" />;
|
||||||
|
|
||||||
export const Inspector = (props: { selectedFile?: FilePath; locationId: number }) => {
|
export const Inspector = (props: { selectedFile?: FilePath; locationId: number }) => {
|
||||||
// const { selectedRowIndex } = useExplorerState();
|
// const { selectedRowIndex } = useExplorerState();
|
||||||
// const isOpen = !!props.selectedFile;
|
// const isOpen = !!props.selectedFile;
|
||||||
|
|
||||||
const file = props.selectedFile;
|
const file = props.selectedFile;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition
|
<Transition
|
||||||
show={true}
|
show={true}
|
||||||
enter="transition-translate ease-in-out duration-200"
|
enter="transition-translate ease-in-out duration-200"
|
||||||
enterFrom="translate-x-64"
|
enterFrom="translate-x-64"
|
||||||
enterTo="translate-x-0"
|
enterTo="translate-x-0"
|
||||||
leave="transition-translate ease-in-out duration-200"
|
leave="transition-translate ease-in-out duration-200"
|
||||||
leaveFrom="translate-x-0"
|
leaveFrom="translate-x-0"
|
||||||
leaveTo="translate-x-64"
|
leaveTo="translate-x-64"
|
||||||
>
|
>
|
||||||
<div className="top-0 right-0 h-full m-2 border border-gray-100 rounded-lg w-60 dark:border-gray-850 ">
|
<div className="top-0 right-0 h-full m-2 border border-gray-100 rounded-lg w-60 dark:border-gray-850 ">
|
||||||
{!!file && (
|
{!!file && (
|
||||||
<div className="flex flex-col h-full overflow-hidden bg-white rounded-lg select-text dark:bg-gray-600 bg-opacity-70">
|
<div className="flex flex-col h-full overflow-hidden bg-white rounded-lg select-text dark:bg-gray-600 bg-opacity-70">
|
||||||
<div className="flex items-center justify-center w-full h-64 overflow-hidden rounded-t-lg bg-gray-50 dark:bg-gray-900">
|
<div className="flex items-center justify-center w-full h-64 overflow-hidden rounded-t-lg bg-gray-50 dark:bg-gray-900">
|
||||||
<FileThumb
|
<FileThumb
|
||||||
hasThumbnailOverride={false}
|
hasThumbnailOverride={false}
|
||||||
className="!m-0 flex flex-shrink flex-grow-0"
|
className="!m-0 flex flex-shrink flex-grow-0"
|
||||||
file={file}
|
file={file}
|
||||||
locationId={props.locationId}
|
locationId={props.locationId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="pt-3 pl-3 text-base font-bold">{file?.name}</h3>
|
<h3 className="pt-3 pl-3 text-base font-bold">{file?.name}</h3>
|
||||||
<div className="flex flex-row m-3 space-x-2">
|
<div className="flex flex-row m-3 space-x-2">
|
||||||
<Button size="sm" noPadding>
|
<Button size="sm" noPadding>
|
||||||
<Heart className="w-[18px] h-[18px]" />
|
<Heart className="w-[18px] h-[18px]" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" noPadding>
|
<Button size="sm" noPadding>
|
||||||
<ShareIcon className="w-[18px] h-[18px]" />
|
<ShareIcon className="w-[18px] h-[18px]" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" noPadding>
|
<Button size="sm" noPadding>
|
||||||
<Link className="w-[18px] h-[18px]" />
|
<Link className="w-[18px] h-[18px]" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{file?.temp_cas_id && (
|
{file?.temp_cas_id && (
|
||||||
<MetaItem title="Unique Content ID" value={file.temp_cas_id as string} />
|
<MetaItem title="Unique Content ID" value={file.temp_cas_id as string} />
|
||||||
)}
|
)}
|
||||||
<Divider />
|
<Divider />
|
||||||
<MetaItem title="Uri" value={file?.materialized_path as string} />
|
<MetaItem title="Uri" value={file?.materialized_path as string} />
|
||||||
<Divider />
|
<Divider />
|
||||||
<MetaItem
|
<MetaItem
|
||||||
title="Date Created"
|
title="Date Created"
|
||||||
value={moment(file?.date_created).format('MMMM Do YYYY, h:mm:ss a')}
|
value={moment(file?.date_created).format('MMMM Do YYYY, h:mm:ss a')}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<MetaItem
|
<MetaItem
|
||||||
title="Date Indexed"
|
title="Date Indexed"
|
||||||
value={moment(file?.date_indexed).format('MMMM Do YYYY, h:mm:ss a')}
|
value={moment(file?.date_indexed).format('MMMM Do YYYY, h:mm:ss a')}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
{!file?.is_dir && (
|
{!file?.is_dir && (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row items-center px-3 py-2 meta-item">
|
<div className="flex flex-row items-center px-3 py-2 meta-item">
|
||||||
{file?.extension && (
|
{file?.extension && (
|
||||||
<span className="inline px-1 mr-1 text-xs font-bold uppercase bg-gray-500 rounded-md text-gray-150">
|
<span className="inline px-1 mr-1 text-xs font-bold uppercase bg-gray-500 rounded-md text-gray-150">
|
||||||
{file?.extension}
|
{file?.extension}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">
|
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">
|
||||||
{file?.extension
|
{file?.extension
|
||||||
? //@ts-ignore
|
? //@ts-ignore
|
||||||
types[file.extension.toUpperCase()]?.descriptions.join(' / ')
|
types[file.extension.toUpperCase()]?.descriptions.join(' / ')
|
||||||
: 'Unknown'}
|
: 'Unknown'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<MetaItem
|
<MetaItem
|
||||||
title="Comment"
|
title="Comment"
|
||||||
value={<TextArea className="mt-2 text-xs leading-snug !py-2" />}
|
value={<TextArea className="mt-2 text-xs leading-snug !py-2" />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <div className="flex flex-row m-3">
|
{/* <div className="flex flex-row m-3">
|
||||||
<Button size="sm">Mint</Button>
|
<Button size="sm">Mint</Button>
|
||||||
</div> */}
|
</div> */}
|
||||||
{/* <MetaItem title="Date Last Modified" value={file?.date_modified} />
|
{/* <MetaItem title="Date Last Modified" value={file?.date_modified} />
|
||||||
<MetaItem title="Date Indexed" value={file?.date_indexed} /> */}
|
<MetaItem title="Date Indexed" value={file?.date_indexed} /> */}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,91 +1,104 @@
|
||||||
import { CameraIcon, LockClosedIcon, PhotographIcon } from '@heroicons/react/outline';
|
import { LockClosedIcon, PhotographIcon } from '@heroicons/react/outline';
|
||||||
import { CogIcon, EyeOffIcon, PlusIcon, ServerIcon } from '@heroicons/react/solid';
|
import { CogIcon, EyeOffIcon, PlusIcon } from '@heroicons/react/solid';
|
||||||
import clsx from 'clsx';
|
|
||||||
import { Camera, CirclesFour, Code, EjectSimple, MonitorPlay, Planet } from 'phosphor-react';
|
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
|
||||||
import { NavLink, NavLinkProps } from 'react-router-dom';
|
|
||||||
import { TrafficLights } from '../os/TrafficLights';
|
|
||||||
import { Button, Dropdown } from '@sd/ui';
|
|
||||||
import { DefaultProps } from '../primitive/types';
|
|
||||||
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||||
import RunningJobsWidget from '../jobs/RunningJobsWidget';
|
import { Button, Dropdown } from '@sd/ui';
|
||||||
import { AppPropsContext } from '../../App';
|
import clsx from 'clsx';
|
||||||
|
import { CirclesFour, Code, Planet } from 'phosphor-react';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { NavLink, NavLinkProps } from 'react-router-dom';
|
||||||
|
|
||||||
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
|
import { AppPropsContext } from '../../App';
|
||||||
import { ReactComponent as FolderWhite } from '../../assets/svg/folder-white.svg';
|
import { ReactComponent as FolderWhite } from '../../assets/svg/folder-white.svg';
|
||||||
|
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
|
||||||
import { useStore } from '../device/Stores';
|
import { useStore } from '../device/Stores';
|
||||||
|
import RunningJobsWidget from '../jobs/RunningJobsWidget';
|
||||||
|
import { MacTrafficLights } from '../os/TrafficLights';
|
||||||
|
import { DefaultProps } from '../primitive/types';
|
||||||
|
|
||||||
interface SidebarProps extends DefaultProps {}
|
interface SidebarProps extends DefaultProps {}
|
||||||
|
|
||||||
export const SidebarLink = (props: NavLinkProps & { children: React.ReactNode }) => (
|
export const SidebarLink = (props: NavLinkProps & { children: React.ReactNode }) => (
|
||||||
<NavLink {...props}>
|
<NavLink {...props}>
|
||||||
{({ isActive }) => (
|
{({ isActive }) => (
|
||||||
<span
|
<span
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'max-w mb-[2px] text-gray-550 dark:text-gray-150 rounded px-2 py-1 flex flex-row flex-grow items-center font-medium hover:bg-gray-100 dark:hover:bg-gray-600 text-sm',
|
'max-w mb-[2px] text-gray-550 dark:text-gray-150 rounded px-2 py-1 flex flex-row flex-grow items-center font-medium hover:bg-gray-100 dark:hover:bg-gray-600 text-sm',
|
||||||
{ '!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive },
|
{
|
||||||
props.className
|
'!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive
|
||||||
)}
|
},
|
||||||
>
|
props.className
|
||||||
{props.children}
|
)}
|
||||||
</span>
|
>
|
||||||
)}
|
{props.children}
|
||||||
</NavLink>
|
</span>
|
||||||
|
)}
|
||||||
|
</NavLink>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Icon = ({ component: Icon, ...props }: any) => (
|
const Icon = ({ component: Icon, ...props }: any) => (
|
||||||
<Icon weight="bold" {...props} className={clsx('w-4 h-4 mr-2', props.className)} />
|
<Icon weight="bold" {...props} className={clsx('w-4 h-4 mr-2', props.className)} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const Heading: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
const Heading: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
<div className="mt-5 mb-1 ml-1 text-xs font-semibold text-gray-300">{children}</div>
|
<div className="mt-5 mb-1 ml-1 text-xs font-semibold text-gray-300">{children}</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export function MacOSTrafficLights() {
|
export const MacWindowControlsSpace: React.FC<{
|
||||||
const appPropsContext = useContext(AppPropsContext);
|
children?: React.ReactNode;
|
||||||
|
}> = (props) => {
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-tauri-drag-region className="h-7">
|
<div data-tauri-drag-region className="h-7 flex-shrink-0">
|
||||||
<div className="mt-2 mb-1 -ml-1 ">
|
{children}
|
||||||
<TrafficLights
|
</div>
|
||||||
onClose={appPropsContext?.onClose}
|
);
|
||||||
onFullscreen={appPropsContext?.onFullscreen}
|
};
|
||||||
onMinimize={appPropsContext?.onMinimize}
|
|
||||||
className="p-1.5 z-50 absolute"
|
export function MacWindowControls() {
|
||||||
/>
|
const appPropsContext = useContext(AppPropsContext);
|
||||||
</div>
|
|
||||||
</div>
|
return (
|
||||||
);
|
<MacWindowControlsSpace>
|
||||||
|
<MacTrafficLights
|
||||||
|
onClose={appPropsContext?.onClose}
|
||||||
|
onFullscreen={appPropsContext?.onFullscreen}
|
||||||
|
onMinimize={appPropsContext?.onMinimize}
|
||||||
|
className="z-50 absolute top-[13px] left-[13px]"
|
||||||
|
/>
|
||||||
|
</MacWindowControlsSpace>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Sidebar: React.FC<SidebarProps> = (props) => {
|
export const Sidebar: React.FC<SidebarProps> = (props) => {
|
||||||
const experimental = useStore((state) => state.experimental);
|
const experimental = useStore((state) => state.experimental);
|
||||||
|
|
||||||
const appPropsContext = useContext(AppPropsContext);
|
const appPropsContext = useContext(AppPropsContext);
|
||||||
const { data: locations } = useBridgeQuery('SysGetLocations');
|
const { data: locations } = useBridgeQuery('SysGetLocations');
|
||||||
const { data: clientState } = useBridgeQuery('ClientGetState');
|
const { data: clientState } = useBridgeQuery('ClientGetState');
|
||||||
|
|
||||||
const { mutate: createLocation } = useBridgeCommand('LocCreate');
|
const { mutate: createLocation } = useBridgeCommand('LocCreate');
|
||||||
|
|
||||||
const tags = [
|
const tags = [
|
||||||
{ id: 1, name: 'Keepsafe', color: '#FF6788' },
|
{ id: 1, name: 'Keepsafe', color: '#FF6788' },
|
||||||
{ id: 2, name: 'OBS', color: '#BF88FF' },
|
{ id: 2, name: 'OBS', color: '#BF88FF' },
|
||||||
{ id: 3, name: 'BlackMagic', color: '#F0C94A' },
|
{ id: 3, name: 'BlackMagic', color: '#F0C94A' },
|
||||||
{ id: 4, name: 'Camera Roll', color: '#00F0DB' },
|
{ id: 4, name: 'Camera Roll', color: '#00F0DB' },
|
||||||
{ id: 5, name: 'Spacedrive', color: '#00F079' }
|
{ id: 5, name: 'Spacedrive', color: '#00F079' }
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-grow-0 flex-shrink-0 w-48 min-h-full px-2.5 overflow-x-hidden overflow-y-scroll border-r border-gray-100 no-scrollbar bg-gray-50 dark:bg-gray-850 dark:border-gray-600">
|
<div className="flex flex-col flex-grow-0 flex-shrink-0 w-48 min-h-full px-2.5 overflow-x-hidden overflow-y-scroll border-r border-gray-100 no-scrollbar bg-gray-50 dark:bg-gray-850 dark:border-gray-600">
|
||||||
{appPropsContext?.platform === 'browser' ? <MacOSTrafficLights /> : null}
|
{appPropsContext?.platform === 'browser' &&
|
||||||
{appPropsContext?.platform === 'macOS' ? (
|
window.location.search.includes('showControls') ? (
|
||||||
<div data-tauri-drag-region className="h-[23px]" />
|
<MacWindowControls />
|
||||||
) : null}
|
) : null}
|
||||||
|
{appPropsContext?.platform === 'macOS' ? <MacWindowControlsSpace /> : null}
|
||||||
|
|
||||||
<Dropdown
|
<Dropdown
|
||||||
buttonProps={{
|
buttonProps={{
|
||||||
justifyLeft: true,
|
justifyLeft: true,
|
||||||
className: `flex w-full text-left max-w-full mb-1 mt-1 -mr-0.5 shadow-xs rounded
|
className: `flex w-full text-left max-w-full mb-1 mt-1 -mr-0.5 shadow-xs rounded
|
||||||
!bg-gray-50
|
!bg-gray-50
|
||||||
border-gray-150
|
border-gray-150
|
||||||
hover:!bg-gray-1000
|
hover:!bg-gray-1000
|
||||||
|
@ -95,127 +108,127 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
|
||||||
|
|
||||||
dark:!border-gray-550
|
dark:!border-gray-550
|
||||||
dark:hover:!border-gray-500`,
|
dark:hover:!border-gray-500`,
|
||||||
variant: 'gray'
|
variant: 'gray'
|
||||||
}}
|
}}
|
||||||
// buttonIcon={<Book weight="bold" className="w-4 h-4 mt-0.5 mr-1" />}
|
// buttonIcon={<Book weight="bold" className="w-4 h-4 mt-0.5 mr-1" />}
|
||||||
buttonText={clientState?.client_name || 'Loading...'}
|
buttonText={clientState?.client_name || 'Loading...'}
|
||||||
items={[
|
items={[
|
||||||
[{ name: clientState?.client_name || '', selected: true }, { name: 'Private Library' }],
|
[{ name: clientState?.client_name || '', selected: true }, { name: 'Private Library' }],
|
||||||
[
|
[
|
||||||
{ name: 'Library Settings', icon: CogIcon },
|
{ name: 'Library Settings', icon: CogIcon },
|
||||||
{ name: 'Add Library', icon: PlusIcon },
|
{ name: 'Add Library', icon: PlusIcon },
|
||||||
{ name: 'Lock', icon: LockClosedIcon },
|
{ name: 'Lock', icon: LockClosedIcon },
|
||||||
{ name: 'Hide', icon: EyeOffIcon }
|
{ name: 'Hide', icon: EyeOffIcon }
|
||||||
]
|
]
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="pt-1">
|
<div className="pt-1">
|
||||||
<SidebarLink to="/overview">
|
<SidebarLink to="/overview">
|
||||||
<Icon component={Planet} />
|
<Icon component={Planet} />
|
||||||
Overview
|
Overview
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
<SidebarLink to="content">
|
<SidebarLink to="content">
|
||||||
<Icon component={CirclesFour} />
|
<Icon component={CirclesFour} />
|
||||||
Content
|
Content
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
<SidebarLink to="photos">
|
<SidebarLink to="photos">
|
||||||
<Icon component={PhotographIcon} />
|
<Icon component={PhotographIcon} />
|
||||||
Photos
|
Photos
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
|
|
||||||
{experimental ? (
|
{experimental ? (
|
||||||
<SidebarLink to="debug">
|
<SidebarLink to="debug">
|
||||||
<Icon component={Code} />
|
<Icon component={Code} />
|
||||||
Debug
|
Debug
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* <SidebarLink to="explorer">
|
{/* <SidebarLink to="explorer">
|
||||||
<Icon component={MonitorPlay} />
|
<Icon component={MonitorPlay} />
|
||||||
Explorer
|
Explorer
|
||||||
</SidebarLink> */}
|
</SidebarLink> */}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Heading>Locations</Heading>
|
<Heading>Locations</Heading>
|
||||||
{locations?.map((location, index) => {
|
{locations?.map((location, index) => {
|
||||||
return (
|
return (
|
||||||
<div key={index} className="flex flex-row items-center">
|
<div key={index} className="flex flex-row items-center">
|
||||||
<NavLink
|
<NavLink
|
||||||
className="'relative w-full group'"
|
className="'relative w-full group'"
|
||||||
to={{
|
to={{
|
||||||
pathname: `explorer/${location.id}`
|
pathname: `explorer/${location.id}`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ isActive }) => (
|
{({ isActive }) => (
|
||||||
<span
|
<span
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'max-w mb-[2px] text-gray-550 dark:text-gray-150 rounded px-2 py-1 flex flex-row flex-grow items-center hover:bg-gray-100 dark:hover:bg-gray-600 text-sm',
|
'max-w mb-[2px] text-gray-550 dark:text-gray-150 rounded px-2 py-1 flex flex-row flex-grow items-center hover:bg-gray-100 dark:hover:bg-gray-600 text-sm',
|
||||||
{
|
{
|
||||||
'!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive
|
'!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="w-[18px] mr-2 -mt-0.5">
|
<div className="w-[18px] mr-2 -mt-0.5">
|
||||||
<FolderWhite className={clsx(!isActive && 'hidden')} />
|
<FolderWhite className={clsx(!isActive && 'hidden')} />
|
||||||
<Folder className={clsx(isActive && 'hidden')} />
|
<Folder className={clsx(isActive && 'hidden')} />
|
||||||
</div>
|
</div>
|
||||||
{location.name}
|
{location.name}
|
||||||
<div className="flex-grow" />
|
<div className="flex-grow" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appPropsContext?.openDialog({ directory: true }).then((result) => {
|
appPropsContext?.openDialog({ directory: true }).then((result) => {
|
||||||
createLocation({ path: result });
|
createLocation({ path: result });
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="w-full px-2 py-1.5 mt-1 text-xs font-bold text-center text-gray-400 dark:text-gray-500 border border-dashed rounded border-transparent cursor-normal border-gray-350 dark:border-gray-550 hover:dark:border-gray-500 transition"
|
className="w-full px-2 py-1.5 mt-1 text-xs font-bold text-center text-gray-400 dark:text-gray-500 border border-dashed rounded border-transparent cursor-normal border-gray-350 dark:border-gray-550 hover:dark:border-gray-500 transition"
|
||||||
>
|
>
|
||||||
Add Location
|
Add Location
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Heading>Tags</Heading>
|
<Heading>Tags</Heading>
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
{tags.map((tag, index) => (
|
{tags.map((tag, index) => (
|
||||||
<SidebarLink key={index} to={`tag/${tag.id}`} className="">
|
<SidebarLink key={index} to={`tag/${tag.id}`} className="">
|
||||||
<div
|
<div
|
||||||
className="w-[12px] h-[12px] rounded-full"
|
className="w-[12px] h-[12px] rounded-full"
|
||||||
style={{ backgroundColor: tag.color }}
|
style={{ backgroundColor: tag.color }}
|
||||||
/>
|
/>
|
||||||
<span className="ml-2 text-sm">{tag.name}</span>
|
<span className="ml-2 text-sm">{tag.name}</span>
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow" />
|
<div className="flex-grow" />
|
||||||
<RunningJobsWidget />
|
<RunningJobsWidget />
|
||||||
{/* <div className="flex w-full">
|
{/* <div className="flex w-full">
|
||||||
</div> */}
|
</div> */}
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<NavLink to="/settings/general">
|
<NavLink to="/settings/general">
|
||||||
{({ isActive }) => (
|
{({ isActive }) => (
|
||||||
<Button
|
<Button
|
||||||
noPadding
|
noPadding
|
||||||
variant={isActive ? 'default' : 'default'}
|
variant={isActive ? 'default' : 'default'}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'px-[4px] mb-1'
|
'px-[4px] mb-1'
|
||||||
// isActive && '!bg-gray-550'
|
// isActive && '!bg-gray-550'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CogIcon className="w-5 h-5" />
|
<CogIcon className="w-5 h-5" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { DefaultProps } from '../primitive/types';
|
import { DefaultProps } from '../primitive/types';
|
||||||
|
|
||||||
export interface DriveListItemProps extends DefaultProps {
|
export interface DriveListItemProps extends DefaultProps {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DriveListItem: React.FC<DriveListItemProps> = (props) => {
|
export const DriveListItem: React.FC<DriveListItemProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'rounded px-1.5 py-1 text-xs font-medium inline-block cursor-default',
|
'rounded px-1.5 py-1 text-xs font-medium inline-block cursor-default',
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,72 +1,79 @@
|
||||||
import React, { DetailedHTMLProps, HTMLAttributes } from 'react';
|
|
||||||
import { useBridgeQuery } from '@sd/client';
|
|
||||||
import ProgressBar from '../primitive/ProgressBar';
|
|
||||||
import { Transition } from '@headlessui/react';
|
import { Transition } from '@headlessui/react';
|
||||||
|
import { useBridgeQuery } from '@sd/client';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import React, { DetailedHTMLProps, HTMLAttributes } from 'react';
|
||||||
|
|
||||||
|
import ProgressBar from '../primitive/ProgressBar';
|
||||||
|
|
||||||
const MiddleTruncatedText = ({
|
const MiddleTruncatedText = ({
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>) => {
|
}: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>) => {
|
||||||
const text = children?.toString() ?? '';
|
const text = children?.toString() ?? '';
|
||||||
const first = text.substring(0, text.length / 2);
|
const first = text.substring(0, text.length / 2);
|
||||||
const last = text.substring(first.length);
|
const last = text.substring(first.length);
|
||||||
|
|
||||||
// Literally black magic
|
// Literally black magic
|
||||||
const fontFaceScaleFactor = 1.61;
|
const fontFaceScaleFactor = 1.61;
|
||||||
const startWidth = fontFaceScaleFactor * 5;
|
const startWidth = fontFaceScaleFactor * 5;
|
||||||
const endWidth = fontFaceScaleFactor * 4;
|
const endWidth = fontFaceScaleFactor * 4;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="whitespace-nowrap overflow-hidden w-full">
|
<div className="whitespace-nowrap overflow-hidden w-full">
|
||||||
<span
|
<span
|
||||||
{...props}
|
{...props}
|
||||||
style={{ maxWidth: `calc(100% - (1em * ${endWidth}))`, minWidth: startWidth }}
|
style={{
|
||||||
className={clsx(
|
maxWidth: `calc(100% - (1em * ${endWidth}))`,
|
||||||
props?.className,
|
minWidth: startWidth
|
||||||
'text-ellipsis inline-block align-bottom whitespace-nowrap overflow-hidden'
|
}}
|
||||||
)}
|
className={clsx(
|
||||||
>
|
props?.className,
|
||||||
{first}
|
'text-ellipsis inline-block align-bottom whitespace-nowrap overflow-hidden'
|
||||||
</span>
|
)}
|
||||||
<span
|
>
|
||||||
{...props}
|
{first}
|
||||||
style={{ maxWidth: `calc(100% - (1em * ${startWidth}))`, direction: 'rtl' }}
|
</span>
|
||||||
className={clsx(
|
<span
|
||||||
props?.className,
|
{...props}
|
||||||
'inline-block align-bottom whitespace-nowrap overflow-hidden'
|
style={{
|
||||||
)}
|
maxWidth: `calc(100% - (1em * ${startWidth}))`,
|
||||||
>
|
direction: 'rtl'
|
||||||
{last}
|
}}
|
||||||
</span>
|
className={clsx(
|
||||||
</div>
|
props?.className,
|
||||||
);
|
'inline-block align-bottom whitespace-nowrap overflow-hidden'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{last}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RunningJobsWidget() {
|
export default function RunningJobsWidget() {
|
||||||
const { data: jobs } = useBridgeQuery('JobGetRunning');
|
const { data: jobs } = useBridgeQuery('JobGetRunning');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
{jobs?.map((job) => (
|
{jobs?.map((job) => (
|
||||||
<Transition
|
<Transition
|
||||||
show={true}
|
show={true}
|
||||||
enter="transition-translate ease-in-out duration-200"
|
enter="transition-translate ease-in-out duration-200"
|
||||||
enterFrom="translate-y-24"
|
enterFrom="translate-y-24"
|
||||||
enterTo="translate-y-0"
|
enterTo="translate-y-0"
|
||||||
leave="transition-translate ease-in-out duration-200"
|
leave="transition-translate ease-in-out duration-200"
|
||||||
leaveFrom="translate-y-0"
|
leaveFrom="translate-y-0"
|
||||||
leaveTo="translate-y-24"
|
leaveTo="translate-y-24"
|
||||||
>
|
>
|
||||||
<div key={job.id} className="flex flex-col px-2 pt-1.5 pb-2 bg-gray-700 rounded">
|
<div key={job.id} className="flex flex-col px-2 pt-1.5 pb-2 bg-gray-700 rounded">
|
||||||
{/* <span className="mb-0.5 text-tiny font-bold text-gray-400">{job.status} Job</span> */}
|
{/* <span className="mb-0.5 text-tiny font-bold text-gray-400">{job.status} Job</span> */}
|
||||||
<MiddleTruncatedText className="mb-1.5 text-gray-450 text-tiny">
|
<MiddleTruncatedText className="mb-1.5 text-gray-450 text-tiny">
|
||||||
{job.message}
|
{job.message}
|
||||||
</MiddleTruncatedText>
|
</MiddleTruncatedText>
|
||||||
<ProgressBar value={job.completed_task_count} total={job.task_count} />
|
<ProgressBar value={job.completed_task_count} total={job.task_count} />
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,43 @@
|
||||||
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
import { Button } from '@sd/ui';
|
import { Button } from '@sd/ui';
|
||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
||||||
|
|
||||||
export interface DialogProps {
|
export interface DialogProps {
|
||||||
trigger: ReactNode;
|
trigger: ReactNode;
|
||||||
ctaLabel?: string;
|
ctaLabel?: string;
|
||||||
ctaAction?: () => void;
|
ctaAction?: () => void;
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Dialog(props: DialogProps) {
|
export default function Dialog(props: DialogProps) {
|
||||||
return (
|
return (
|
||||||
<DialogPrimitive.Root>
|
<DialogPrimitive.Root>
|
||||||
<DialogPrimitive.Trigger asChild>{props.trigger}</DialogPrimitive.Trigger>
|
<DialogPrimitive.Trigger asChild>{props.trigger}</DialogPrimitive.Trigger>
|
||||||
<DialogPrimitive.Portal>
|
<DialogPrimitive.Portal>
|
||||||
<DialogPrimitive.Overlay className="fixed top-0 dialog-overlay bottom-0 left-0 right-0 z-50 grid overflow-y-auto bg-black bg-opacity-50 rounded-xl place-items-center m-[1px]">
|
<DialogPrimitive.Overlay className="fixed top-0 dialog-overlay bottom-0 left-0 right-0 z-50 grid overflow-y-auto bg-black bg-opacity-50 rounded-xl place-items-center m-[1px]">
|
||||||
<DialogPrimitive.Content className="min-w-[300px] max-w-[400px] dialog-content rounded-md bg-gray-650 text-white border border-gray-550 shadow-deep">
|
<DialogPrimitive.Content className="min-w-[300px] max-w-[400px] dialog-content rounded-md bg-gray-650 text-white border border-gray-550 shadow-deep">
|
||||||
<div className="p-5">
|
<div className="p-5">
|
||||||
<DialogPrimitive.Title className="font-bold ">{props.title}</DialogPrimitive.Title>
|
<DialogPrimitive.Title className="font-bold ">{props.title}</DialogPrimitive.Title>
|
||||||
<DialogPrimitive.Description className="text-sm text-gray-300">
|
<DialogPrimitive.Description className="text-sm text-gray-300">
|
||||||
{props.description}
|
{props.description}
|
||||||
</DialogPrimitive.Description>
|
</DialogPrimitive.Description>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-end px-3 py-3 space-x-2 bg-gray-600 border-t border-gray-550">
|
<div className="flex flex-row justify-end px-3 py-3 space-x-2 bg-gray-600 border-t border-gray-550">
|
||||||
<DialogPrimitive.Close asChild>
|
<DialogPrimitive.Close asChild>
|
||||||
<Button size="sm" variant="gray">
|
<Button size="sm" variant="gray">
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
<Button onClick={props.ctaAction} size="sm" variant="primary">
|
<Button onClick={props.ctaAction} size="sm" variant="primary">
|
||||||
{props.ctaLabel}
|
{props.ctaLabel}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
</DialogPrimitive.Overlay>
|
</DialogPrimitive.Overlay>
|
||||||
</DialogPrimitive.Portal>
|
</DialogPrimitive.Portal>
|
||||||
</DialogPrimitive.Root>
|
</DialogPrimitive.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +1,64 @@
|
||||||
import { Transition } from '@headlessui/react';
|
import { Transition } from '@headlessui/react';
|
||||||
import clsx from 'clsx';
|
|
||||||
import React from 'react';
|
|
||||||
import { XIcon } from '@heroicons/react/solid';
|
import { XIcon } from '@heroicons/react/solid';
|
||||||
import { Button } from '@sd/ui';
|
import { Button } from '@sd/ui';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { MacOSTrafficLights } from '../file/Sidebar';
|
|
||||||
|
import { MacWindowControls } from '../file/Sidebar';
|
||||||
|
|
||||||
export interface ModalProps {
|
export interface ModalProps {
|
||||||
full?: boolean;
|
full?: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Modal: React.FC<ModalProps> = (props) => {
|
export const Modal: React.FC<ModalProps> = (props) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx('absolute w-screen h-screen z-30', { 'pointer-events-none hidden': !open })}
|
className={clsx('absolute w-screen h-screen z-30', {
|
||||||
>
|
'pointer-events-none hidden': !open
|
||||||
<div className="flex w-screen h-screen p-2 pt-12">
|
})}
|
||||||
<Transition
|
>
|
||||||
show
|
<div className="flex w-screen h-screen p-2 pt-12">
|
||||||
enter="transition duration-150"
|
<Transition
|
||||||
enterFrom="opacity-0"
|
show
|
||||||
enterTo="opacity-100"
|
enter="transition duration-150"
|
||||||
leave="transition duration-200"
|
enterFrom="opacity-0"
|
||||||
leaveFrom="opacity-100"
|
enterTo="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leave="transition duration-200"
|
||||||
>
|
leaveFrom="opacity-100"
|
||||||
<div
|
leaveTo="opacity-0"
|
||||||
data-tauri-drag-region
|
>
|
||||||
onClick={() => navigate('/')}
|
<div
|
||||||
className="absolute top-0 left-0 w-screen h-screen bg-white -z-50 rounded-2xl dark:bg-gray-800 bg-opacity-90"
|
data-tauri-drag-region
|
||||||
/>
|
onClick={() => navigate('/')}
|
||||||
</Transition>
|
className="absolute top-0 left-0 w-screen h-screen bg-white -z-50 rounded-2xl dark:bg-gray-800 bg-opacity-90"
|
||||||
<Button
|
/>
|
||||||
onClick={() => navigate('/')}
|
</Transition>
|
||||||
variant="gray"
|
<Button
|
||||||
className="!px-1.5 absolute top-2 right-2"
|
onClick={() => navigate('/')}
|
||||||
>
|
variant="gray"
|
||||||
<XIcon className="w-4 h-4" />
|
className="!px-1.5 absolute top-2 right-2"
|
||||||
</Button>
|
>
|
||||||
<Transition
|
<XIcon className="w-4 h-4" />
|
||||||
show
|
</Button>
|
||||||
className="flex flex-grow"
|
<Transition
|
||||||
appear
|
show
|
||||||
enter="transition ease-in-out-back duration-200"
|
className="flex flex-grow"
|
||||||
enterFrom="opacity-0 translate-y-5"
|
appear
|
||||||
enterTo="opacity-100"
|
enter="transition ease-in-out-back duration-200"
|
||||||
leave="transition duration-200"
|
enterFrom="opacity-0 translate-y-5"
|
||||||
leaveFrom="opacity-100"
|
enterTo="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leave="transition duration-200"
|
||||||
>
|
leaveFrom="opacity-100"
|
||||||
<div className="z-30 flex flex-grow mx-auto bg-white rounded-lg shadow-2xl max-w-7xl dark:bg-gray-650 ">
|
leaveTo="opacity-0"
|
||||||
{props.children}
|
>
|
||||||
</div>
|
<div className="z-30 flex flex-grow mx-auto bg-white rounded-lg shadow-2xl max-w-7xl dark:bg-gray-650 ">
|
||||||
</Transition>
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Transition>
|
||||||
);
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,111 +1,112 @@
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/outline';
|
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/outline';
|
||||||
|
import { useBridgeCommand } from '@sd/client';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import {
|
import {
|
||||||
ArrowsClockwise,
|
ArrowsClockwise,
|
||||||
Cloud,
|
Cloud,
|
||||||
FolderPlus,
|
FolderPlus,
|
||||||
IconProps,
|
IconProps,
|
||||||
Key,
|
Key,
|
||||||
Tag,
|
Tag,
|
||||||
TerminalWindow
|
TerminalWindow
|
||||||
} from 'phosphor-react';
|
} from 'phosphor-react';
|
||||||
import React, { DetailedHTMLProps, HTMLAttributes } from 'react';
|
import React, { DetailedHTMLProps, HTMLAttributes } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useExplorerState } from '../file/FileList';
|
||||||
import { Shortcut } from '../primitive/Shortcut';
|
import { Shortcut } from '../primitive/Shortcut';
|
||||||
import { DefaultProps } from '../primitive/types';
|
import { DefaultProps } from '../primitive/types';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { useBridgeCommand } from '@sd/client';
|
|
||||||
import { useExplorerState } from '../file/FileList';
|
|
||||||
|
|
||||||
export interface TopBarProps extends DefaultProps {}
|
export interface TopBarProps extends DefaultProps {}
|
||||||
export interface TopBarButtonProps
|
export interface TopBarButtonProps
|
||||||
extends DetailedHTMLProps<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
|
extends DetailedHTMLProps<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
|
||||||
icon: React.ComponentType<IconProps>;
|
icon: React.ComponentType<IconProps>;
|
||||||
group?: boolean;
|
group?: boolean;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
left?: boolean;
|
left?: boolean;
|
||||||
right?: boolean;
|
right?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopBarButton: React.FC<TopBarButtonProps> = ({ icon: Icon, ...props }) => {
|
const TopBarButton: React.FC<TopBarButtonProps> = ({ icon: Icon, ...props }) => {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'mr-[1px] py-0.5 px-0.5 text-md font-medium hover:bg-gray-150 dark:transparent dark:hover:bg-gray-550 dark:active:bg-gray-500 rounded-md transition-colors duration-100',
|
'mr-[1px] py-0.5 px-0.5 text-md font-medium hover:bg-gray-150 dark:transparent dark:hover:bg-gray-550 dark:active:bg-gray-500 rounded-md transition-colors duration-100',
|
||||||
{
|
{
|
||||||
'rounded-r-none rounded-l-none': props.group && !props.left && !props.right,
|
'rounded-r-none rounded-l-none': props.group && !props.left && !props.right,
|
||||||
'rounded-r-none': props.group && props.left,
|
'rounded-r-none': props.group && props.left,
|
||||||
'rounded-l-none': props.group && props.right,
|
'rounded-l-none': props.group && props.right,
|
||||||
'dark:bg-gray-450 dark:hover:bg-gray-450 dark:active:bg-gray-450': props.active
|
'dark:bg-gray-450 dark:hover:bg-gray-450 dark:active:bg-gray-450': props.active
|
||||||
},
|
},
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon weight={'regular'} className="m-0.5 w-5 h-5 text-gray-450 dark:text-gray-150" />
|
<Icon weight={'regular'} className="m-0.5 w-5 h-5 text-gray-450 dark:text-gray-150" />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TopBar: React.FC<TopBarProps> = (props) => {
|
export const TopBar: React.FC<TopBarProps> = (props) => {
|
||||||
const { locationId } = useExplorerState();
|
const { locationId } = useExplorerState();
|
||||||
const { mutate: generateThumbsForLocation } = useBridgeCommand('GenerateThumbsForLocation', {
|
const { mutate: generateThumbsForLocation } = useBridgeCommand('GenerateThumbsForLocation', {
|
||||||
onMutate: (data) => {
|
onMutate: (data) => {
|
||||||
console.log('GenerateThumbsForLocation', data);
|
console.log('GenerateThumbsForLocation', data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className="flex h-[2.95rem] -mt-0.5 max-w z-10 pl-3 rounded-tr-2xl flex-shrink-0 items-center border-b dark:bg-gray-600 border-gray-100 dark:border-gray-800 !bg-opacity-60 backdrop-blur"
|
className="flex h-[2.95rem] -mt-0.5 max-w z-10 pl-3 rounded-tr-2xl flex-shrink-0 items-center border-b dark:bg-gray-600 border-gray-100 dark:border-gray-800 !bg-opacity-60 backdrop-blur"
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<TopBarButton icon={ChevronLeftIcon} onClick={() => navigate(-1)} />
|
<TopBarButton icon={ChevronLeftIcon} onClick={() => navigate(-1)} />
|
||||||
<TopBarButton icon={ChevronRightIcon} onClick={() => navigate(1)} />
|
<TopBarButton icon={ChevronRightIcon} onClick={() => navigate(1)} />
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="flex mx-8 space-x-[1px]">
|
{/* <div className="flex mx-8 space-x-[1px]">
|
||||||
<TopBarButton active group left icon={List} />
|
<TopBarButton active group left icon={List} />
|
||||||
<TopBarButton group icon={Columns} />
|
<TopBarButton group icon={Columns} />
|
||||||
<TopBarButton group right icon={SquaresFour} />
|
<TopBarButton group right icon={SquaresFour} />
|
||||||
</div> */}
|
</div> */}
|
||||||
<div data-tauri-drag-region className="flex flex-row justify-center flex-grow ">
|
<div data-tauri-drag-region className="flex flex-row justify-center flex-grow ">
|
||||||
<div className="flex mx-8 space-x-2 pointer-events-auto">
|
<div className="flex mx-8 space-x-2 pointer-events-auto">
|
||||||
<TopBarButton icon={Tag} />
|
<TopBarButton icon={Tag} />
|
||||||
<TopBarButton icon={FolderPlus} />
|
<TopBarButton icon={FolderPlus} />
|
||||||
<TopBarButton icon={TerminalWindow} />
|
<TopBarButton icon={TerminalWindow} />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex h-7">
|
<div className="relative flex h-7">
|
||||||
<input
|
<input
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
className="w-32 h-[30px] focus:w-52 text-sm p-3 rounded-lg outline-none focus:ring-2 placeholder-gray-400 dark:placeholder-gray-500 bg-[#F6F2F6] border border-gray-50 dark:bg-gray-650 dark:border-gray-550 focus:ring-gray-100 dark:focus:ring-gray-600 transition-all"
|
className="w-32 h-[30px] focus:w-52 text-sm p-3 rounded-lg outline-none focus:ring-2 placeholder-gray-400 dark:placeholder-gray-500 bg-[#F6F2F6] border border-gray-50 dark:bg-gray-650 dark:border-gray-550 focus:ring-gray-100 dark:focus:ring-gray-600 transition-all"
|
||||||
/>
|
/>
|
||||||
<div className="space-x-1 absolute top-[2px] right-1">
|
<div className="space-x-1 absolute top-[2px] right-1">
|
||||||
<Shortcut chars="⌘K" />
|
<Shortcut chars="⌘K" />
|
||||||
{/* <Shortcut chars="S" /> */}
|
{/* <Shortcut chars="S" /> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex mx-8 space-x-2">
|
<div className="flex mx-8 space-x-2">
|
||||||
<TopBarButton icon={Key} />
|
<TopBarButton icon={Key} />
|
||||||
<TopBarButton icon={Cloud} />
|
<TopBarButton icon={Cloud} />
|
||||||
<TopBarButton
|
<TopBarButton
|
||||||
icon={ArrowsClockwise}
|
icon={ArrowsClockwise}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
generateThumbsForLocation({ id: locationId, path: '' });
|
generateThumbsForLocation({ id: locationId, path: '' });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* <img
|
{/* <img
|
||||||
alt="spacedrive-logo"
|
alt="spacedrive-logo"
|
||||||
src="/images/spacedrive_logo.png"
|
src="/images/spacedrive_logo.png"
|
||||||
className="w-8 h-8 mt-[1px] mr-2 pointer-events-none"
|
className="w-8 h-8 mt-[1px] mr-2 pointer-events-none"
|
||||||
/> */}
|
/> */}
|
||||||
{/*<TopBarButton onClick={() => {*/}
|
{/*<TopBarButton onClick={() => {*/}
|
||||||
{/* setSettingsOpen(!settingsOpen);*/}
|
{/* setSettingsOpen(!settingsOpen);*/}
|
||||||
{/*}} className="mr-[8px]" icon={CogIcon} />*/}
|
{/*}} className="mr-[8px]" icon={CogIcon} />*/}
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="h-[1px] flex-shrink-0 max-w bg-gray-200 dark:bg-gray-700" /> */}
|
{/* <div className="h-[1px] flex-shrink-0 max-w bg-gray-200 dark:bg-gray-700" /> */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,65 +1,77 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
|
import closeIconPath from '../../assets/svg/macos_close.svg';
|
||||||
|
import fullscreenIconPath from '../../assets/svg/macos_fullscreen.svg';
|
||||||
|
import minimizeIconPath from '../../assets/svg/macos_minimize.svg';
|
||||||
import { useFocusState } from '../../hooks/useFocusState';
|
import { useFocusState } from '../../hooks/useFocusState';
|
||||||
import { DefaultProps } from '../primitive/types';
|
import { DefaultProps } from '../primitive/types';
|
||||||
|
|
||||||
import { ReactComponent as Close } from '../../assets/svg/macos_close.svg';
|
|
||||||
import { ReactComponent as Minimize } from '../../assets/svg/macos_minimize.svg';
|
|
||||||
import { ReactComponent as Fullscreen } from '../../assets/svg/macos_fullscreen.svg';
|
|
||||||
|
|
||||||
export interface TrafficLightsProps extends DefaultProps {
|
export interface TrafficLightsProps extends DefaultProps {
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
onMinimize?: () => void;
|
onMinimize?: () => void;
|
||||||
onFullscreen?: () => void;
|
onFullscreen?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TrafficLights: React.FC<TrafficLightsProps> = (props) => {
|
export const MacTrafficLights: React.FC<TrafficLightsProps> = (props) => {
|
||||||
const [focused] = useFocusState();
|
const [focused] = useFocusState();
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className={clsx('flex flex-row space-x-2 px-2 group', props.className)}
|
className={clsx('flex flex-row space-x-[8px] group', props.className)}
|
||||||
>
|
>
|
||||||
<Light mode="close" action={props.onClose} focused={focused} />
|
<TrafficLight type="close" onClick={props.onClose} colorful={focused} />
|
||||||
<Light mode="minimize" action={props.onMinimize} focused={focused} />
|
<TrafficLight type="minimize" onClick={props.onMinimize} colorful={focused} />
|
||||||
<Light mode="fullscreen" action={props.onFullscreen} focused={focused} />
|
<TrafficLight type="fullscreen" onClick={props.onFullscreen} colorful={focused} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface LightProps {
|
interface TrafficLightProps {
|
||||||
mode: 'close' | 'minimize' | 'fullscreen';
|
type: 'close' | 'minimize' | 'fullscreen';
|
||||||
focused: boolean;
|
colorful: boolean;
|
||||||
action?: () => void;
|
onClick?: React.HTMLAttributes<HTMLDivElement>['onClick'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const Light: React.FC<LightProps> = (props) => {
|
const TrafficLight: React.FC<TrafficLightProps> = (props) => {
|
||||||
return (
|
const { onClick = () => undefined, colorful = false, type } = props;
|
||||||
<div
|
const iconPath = React.useRef<string>(closeIconPath);
|
||||||
onClick={props.action}
|
|
||||||
className={clsx('w-[13px] h-[13px] rounded-full bg-gray-500', {
|
useEffect(() => {
|
||||||
'!bg-red-400 active:!bg-red-300': props.mode == 'close' && props.focused,
|
switch (type) {
|
||||||
'!bg-green-400 active:!bg-green-300': props.mode == 'fullscreen' && props.focused,
|
case 'close':
|
||||||
'!bg-yellow-400 active:!bg-yellow-300': props.mode == 'minimize' && props.focused
|
iconPath.current = closeIconPath;
|
||||||
})}
|
break;
|
||||||
>
|
case 'minimize':
|
||||||
{(() => {
|
iconPath.current = minimizeIconPath;
|
||||||
if (!props.focused) return <></>;
|
break;
|
||||||
switch (props.mode) {
|
case 'fullscreen':
|
||||||
case 'close':
|
iconPath.current = fullscreenIconPath;
|
||||||
return (
|
break;
|
||||||
<Close className=" w-[13px] -mt-[1px] h-[15px] opacity-0 group-hover:opacity-100" />
|
}
|
||||||
);
|
}, [type]);
|
||||||
case 'minimize':
|
|
||||||
return (
|
return (
|
||||||
<Minimize className="ml-[2px] w-[9px] -mt-[1px] h-[15px] opacity-0 group-hover:opacity-100" />
|
<div
|
||||||
);
|
onClick={onClick}
|
||||||
case 'fullscreen':
|
className={clsx(
|
||||||
return (
|
'rounded-full box-border w-[12px] h-[12px] border-[0.5px] border-transparent bg-[#CDCED0] dark:bg-[#2B2C2F] flex justify-center items-center',
|
||||||
<Fullscreen className="ml-[1px] w-[11px] -mt-[1px] h-[15px] opacity-0 group-hover:opacity-100" />
|
{
|
||||||
);
|
'border-red-900 !bg-[#EC6A5E] active:hover:!bg-red-700 dark:active:hover:!bg-red-400':
|
||||||
}
|
type === 'close' && colorful,
|
||||||
})()}
|
'border-yellow-900 !bg-[#F4BE4F] active:hover:!bg-yellow-600 dark:active:hover:!bg-yellow-200':
|
||||||
</div>
|
type === 'minimize' && colorful,
|
||||||
);
|
'border-green-900 !bg-[#61C253] active:hover:!bg-green-700 dark:active:hover:!bg-green-300':
|
||||||
|
type === 'fullscreen' && colorful
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{colorful && (
|
||||||
|
<img
|
||||||
|
src={iconPath.current}
|
||||||
|
className="opacity-0 group-hover:opacity-100 group-active:opacity-100 pointer-events-none"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,22 +2,22 @@ import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||||
containerClasname?: string;
|
containerClasname?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Checkbox: React.FC<CheckboxProps> = (props) => {
|
export const Checkbox: React.FC<CheckboxProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex items-center text-sm font-medium text-gray-700 dark:text-gray-100',
|
'flex items-center text-sm font-medium text-gray-700 dark:text-gray-100',
|
||||||
props.containerClasname
|
props.containerClasname
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
{...props}
|
{...props}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className={clsx(
|
className={clsx(
|
||||||
`
|
`
|
||||||
bg-gray-50
|
bg-gray-50
|
||||||
hover:bg-gray-100
|
hover:bg-gray-100
|
||||||
dark:bg-gray-800
|
dark:bg-gray-800
|
||||||
|
@ -32,10 +32,10 @@ export const Checkbox: React.FC<CheckboxProps> = (props) => {
|
||||||
text-primary
|
text-primary
|
||||||
checked:ring-2 checked:ring-primary-500
|
checked:ring-2 checked:ring-primary-500
|
||||||
`,
|
`,
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span className="select-none">Checkbox</span>
|
<span className="select-none">Checkbox</span>
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,20 +4,20 @@ import ReactJson, { ReactJsonViewProps } from 'react-json-view';
|
||||||
export interface CodeBlockProps extends ReactJsonViewProps {}
|
export interface CodeBlockProps extends ReactJsonViewProps {}
|
||||||
|
|
||||||
export default function CodeBlock(props: CodeBlockProps) {
|
export default function CodeBlock(props: CodeBlockProps) {
|
||||||
return (
|
return (
|
||||||
<ReactJson
|
<ReactJson
|
||||||
enableClipboard={false}
|
enableClipboard={false}
|
||||||
displayDataTypes={false}
|
displayDataTypes={false}
|
||||||
theme="ocean"
|
theme="ocean"
|
||||||
style={{
|
style={{
|
||||||
padding: 20,
|
padding: 20,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
backgroundColor: '#101016',
|
backgroundColor: '#101016',
|
||||||
border: 1,
|
border: 1,
|
||||||
borderColor: '#1E1E27',
|
borderColor: '#1E1E27',
|
||||||
borderStyle: 'solid'
|
borderStyle: 'solid'
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const variants = {
|
const variants = {
|
||||||
default: `
|
default: `
|
||||||
shadow-sm
|
shadow-sm
|
||||||
bg-white
|
bg-white
|
||||||
hover:bg-white
|
hover:bg-white
|
||||||
|
@ -26,46 +26,46 @@ const variants = {
|
||||||
dark:text-white
|
dark:text-white
|
||||||
placeholder-gray-300
|
placeholder-gray-300
|
||||||
`,
|
`,
|
||||||
primary: ''
|
primary: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||||
variant?: keyof typeof variants;
|
variant?: keyof typeof variants;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Input = ({ ...props }: InputProps) => {
|
export const Input = ({ ...props }: InputProps) => {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
{...props}
|
{...props}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
`px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`,
|
`px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`,
|
||||||
variants[props.variant || 'default'],
|
variants[props.variant || 'default'],
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface TextAreaProps extends React.InputHTMLAttributes<HTMLTextAreaElement> {
|
interface TextAreaProps extends React.InputHTMLAttributes<HTMLTextAreaElement> {
|
||||||
variant?: keyof typeof variants;
|
variant?: keyof typeof variants;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TextArea = ({ size, ...props }: TextAreaProps) => {
|
export const TextArea = ({ size, ...props }: TextAreaProps) => {
|
||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
{...props}
|
{...props}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
`px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`,
|
`px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`,
|
||||||
variants[props.variant || 'default'],
|
variants[props.variant || 'default'],
|
||||||
size && '',
|
size && '',
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Label: React.FC<{ slug?: string; children: string }> = (props) => (
|
export const Label: React.FC<{ slug?: string; children: string }> = (props) => (
|
||||||
<label className="text-sm font-bold" htmlFor={props.slug}>
|
<label className="text-sm font-bold" htmlFor={props.slug}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,26 +1,27 @@
|
||||||
import React from 'react';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { DefaultProps } from './types';
|
import { DefaultProps } from './types';
|
||||||
|
|
||||||
interface InputContainerProps extends DefaultProps {
|
interface InputContainerProps extends DefaultProps {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
mini?: boolean;
|
mini?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InputContainer: React.FC<InputContainerProps> = (props) => {
|
export const InputContainer: React.FC<InputContainerProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<div
|
<div
|
||||||
className={clsx('flex flex-col w-full', !props.mini && 'pb-6', props.className)}
|
className={clsx('flex flex-col w-full', !props.mini && 'pb-6', props.className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<h3 className="mb-1 font-medium text-gray-700 dark:text-gray-100">{props.title}</h3>
|
<h3 className="mb-1 font-medium text-gray-700 dark:text-gray-100">{props.title}</h3>
|
||||||
{!!props.description && <p className="mb-2 text-sm text-gray-400 ">{props.description}</p>}
|
{!!props.description && <p className="mb-2 text-sm text-gray-400 ">{props.description}</p>}
|
||||||
{!props.mini && props.children}
|
{!props.mini && props.children}
|
||||||
</div>
|
</div>
|
||||||
{props.mini && props.children}
|
{props.mini && props.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,101 +1,101 @@
|
||||||
import React, { Fragment, useEffect, useState } from 'react';
|
|
||||||
import { Listbox as ListboxPrimitive, Transition } from '@headlessui/react';
|
import { Listbox as ListboxPrimitive, Transition } from '@headlessui/react';
|
||||||
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid';
|
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import React, { Fragment, useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface ListboxOption {
|
interface ListboxOption {
|
||||||
option: string;
|
option: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
key: string;
|
key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Listbox(props: { options: ListboxOption[]; className?: string }) {
|
export default function Listbox(props: { options: ListboxOption[]; className?: string }) {
|
||||||
const [selected, setSelected] = useState(props.options[0]);
|
const [selected, setSelected] = useState(props.options[0]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
setSelected(props.options[0]);
|
setSelected(props.options[0]);
|
||||||
}
|
}
|
||||||
}, [props.options]);
|
}, [props.options]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListboxPrimitive value={selected} onChange={setSelected}>
|
<ListboxPrimitive value={selected} onChange={setSelected}>
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<ListboxPrimitive.Button
|
<ListboxPrimitive.Button
|
||||||
className={clsx(
|
className={clsx(
|
||||||
`relative w-full py-2 pl-3 pr-10 text-left bg-white dark:bg-gray-500
|
`relative w-full py-2 pl-3 pr-10 text-left bg-white dark:bg-gray-500
|
||||||
rounded-lg shadow-md cursor-default focus:outline-none focus-visible:ring-2
|
rounded-lg shadow-md cursor-default focus:outline-none focus-visible:ring-2
|
||||||
focus-visible:ring-opacity-75 focus-visible:ring-white focus-visible:ring-offset-orange-300
|
focus-visible:ring-opacity-75 focus-visible:ring-white focus-visible:ring-offset-orange-300
|
||||||
focus-visible:ring-offset-2 focus-visible:border-indigo-500 sm:text-sm`,
|
focus-visible:ring-offset-2 focus-visible:border-indigo-500 sm:text-sm`,
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{selected?.option ? (
|
{selected?.option ? (
|
||||||
<span className="block truncate">{selected?.option}</span>
|
<span className="block truncate">{selected?.option}</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="block truncate opacity-70">Nothing selected...</span>
|
<span className="block truncate opacity-70">Nothing selected...</span>
|
||||||
)}
|
)}
|
||||||
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
||||||
<SelectorIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
<SelectorIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
</ListboxPrimitive.Button>
|
</ListboxPrimitive.Button>
|
||||||
<Transition
|
<Transition
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
leave="transition ease-in duration-100"
|
leave="transition ease-in duration-100"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<ListboxPrimitive.Options
|
<ListboxPrimitive.Options
|
||||||
className={`
|
className={`
|
||||||
absolute w-full mt-1 overflow-auto rounded-md sm:text-sm
|
absolute w-full mt-1 overflow-auto rounded-md sm:text-sm
|
||||||
text-base bg-white dark:bg-gray-500 shadow-lg max-h-60
|
text-base bg-white dark:bg-gray-500 shadow-lg max-h-60
|
||||||
ring-1 ring-black ring-opacity-5 focus:outline-none
|
ring-1 ring-black ring-opacity-5 focus:outline-none
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{props.options.map((person, personIdx) => (
|
{props.options.map((person, personIdx) => (
|
||||||
<ListboxPrimitive.Option
|
<ListboxPrimitive.Option
|
||||||
key={personIdx}
|
key={personIdx}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
`cursor-default select-none relative rounded m-1 py-2 pl-8 pr-4 dark:text-white focus:outline-none ${
|
`cursor-default select-none relative rounded m-1 py-2 pl-8 pr-4 dark:text-white focus:outline-none ${
|
||||||
active
|
active
|
||||||
? 'text-primary-900 bg-primary-600'
|
? 'text-primary-900 bg-primary-600'
|
||||||
: 'text-gray-900 dark:hover:bg-gray-600 dark:hover:bg-opacity-20'
|
: 'text-gray-900 dark:hover:bg-gray-600 dark:hover:bg-opacity-20'
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
value={person}
|
value={person}
|
||||||
>
|
>
|
||||||
{({ selected }) => (
|
{({ selected }) => (
|
||||||
<>
|
<>
|
||||||
<span
|
<span
|
||||||
className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}
|
className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}
|
||||||
>
|
>
|
||||||
{person.option}
|
{person.option}
|
||||||
{person.description && (
|
{person.description && (
|
||||||
<span
|
<span
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'text-gray-300 leading-5 ml-3 text-xs',
|
'text-gray-300 leading-5 ml-3 text-xs',
|
||||||
selected && 'text-white'
|
selected && 'text-white'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{person.description}
|
{person.description}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{selected ? (
|
{selected ? (
|
||||||
<span className="absolute inset-y-0 left-0 flex items-center pl-2 text-white">
|
<span className="absolute inset-y-0 left-0 flex items-center pl-2 text-white">
|
||||||
<CheckIcon className="w-5 h-5" aria-hidden="true" />
|
<CheckIcon className="w-5 h-5" aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ListboxPrimitive.Option>
|
</ListboxPrimitive.Option>
|
||||||
))}
|
))}
|
||||||
</ListboxPrimitive.Options>
|
</ListboxPrimitive.Options>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</ListboxPrimitive>
|
</ListboxPrimitive>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,23 +2,23 @@ import * as ProgressPrimitive from '@radix-ui/react-progress';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: number;
|
value: number;
|
||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProgressBar = (props: Props) => {
|
const ProgressBar = (props: Props) => {
|
||||||
const percentage = Math.round((props.value / props.total) * 100);
|
const percentage = Math.round((props.value / props.total) * 100);
|
||||||
return (
|
return (
|
||||||
<ProgressPrimitive.Root
|
<ProgressPrimitive.Root
|
||||||
value={percentage}
|
value={percentage}
|
||||||
className="w-full h-1 overflow-hidden bg-gray-200 rounded-full dark:bg-gray-500"
|
className="w-full h-1 overflow-hidden bg-gray-200 rounded-full dark:bg-gray-500"
|
||||||
>
|
>
|
||||||
<ProgressPrimitive.Indicator
|
<ProgressPrimitive.Indicator
|
||||||
style={{ width: `${percentage}%` }}
|
style={{ width: `${percentage}%` }}
|
||||||
className="h-full duration-300 ease-in-out bg-primary "
|
className="h-full duration-300 ease-in-out bg-primary "
|
||||||
/>
|
/>
|
||||||
</ProgressPrimitive.Root>
|
</ProgressPrimitive.Root>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProgressBar;
|
export default ProgressBar;
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { DefaultProps } from './types';
|
import { DefaultProps } from './types';
|
||||||
|
|
||||||
export interface ShortcutProps extends DefaultProps {
|
export interface ShortcutProps extends DefaultProps {
|
||||||
chars: string;
|
chars: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Shortcut: React.FC<ShortcutProps> = (props) => {
|
export const Shortcut: React.FC<ShortcutProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={clsx(
|
className={clsx(
|
||||||
`
|
`
|
||||||
px-1
|
px-1
|
||||||
py-0.5
|
py-0.5
|
||||||
text-xs
|
text-xs
|
||||||
|
@ -24,10 +25,10 @@ export const Shortcut: React.FC<ShortcutProps> = (props) => {
|
||||||
border-t-2
|
border-t-2
|
||||||
rounded-lg
|
rounded-lg
|
||||||
`,
|
`,
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{props.chars}
|
{props.chars}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import React from 'react';
|
|
||||||
import * as SliderPrimitive from '@radix-ui/react-slider';
|
import * as SliderPrimitive from '@radix-ui/react-slider';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
const Slider = (props: SliderPrimitive.SliderProps) => (
|
const Slider = (props: SliderPrimitive.SliderProps) => (
|
||||||
<SliderPrimitive.Root {...props} className="relative flex items-center w-full h-6 select-none">
|
<SliderPrimitive.Root {...props} className="relative flex items-center w-full h-6 select-none">
|
||||||
<SliderPrimitive.Track className="relative flex-grow h-2 bg-gray-500 rounded-full outline-none">
|
<SliderPrimitive.Track className="relative flex-grow h-2 bg-gray-500 rounded-full outline-none">
|
||||||
<SliderPrimitive.Range className="absolute h-full rounded-full outline-none bg-primary-500" />
|
<SliderPrimitive.Range className="absolute h-full rounded-full outline-none bg-primary-500" />
|
||||||
</SliderPrimitive.Track>
|
</SliderPrimitive.Track>
|
||||||
<SliderPrimitive.Thumb
|
<SliderPrimitive.Thumb
|
||||||
className="z-50 block w-5 h-5 font-bold transition rounded-full shadow-lg outline-none shadow-black bg-primary-500 ring-primary-500 ring-opacity-30 focus:ring-4"
|
className="z-50 block w-5 h-5 font-bold transition rounded-full shadow-lg outline-none shadow-black bg-primary-500 ring-primary-500 ring-opacity-30 focus:ring-4"
|
||||||
data-tip="1.0"
|
data-tip="1.0"
|
||||||
/>
|
/>
|
||||||
</SliderPrimitive.Root>
|
</SliderPrimitive.Root>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Slider;
|
export default Slider;
|
||||||
|
|
|
@ -1,30 +1,31 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
|
|
||||||
import { DefaultProps } from './types';
|
import { DefaultProps } from './types';
|
||||||
|
|
||||||
export interface TagProps extends DefaultProps {
|
export interface TagProps extends DefaultProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
color: 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple' | 'pink';
|
color: 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple' | 'pink';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Tag: React.FC<TagProps> = (props) => {
|
export const Tag: React.FC<TagProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'rounded px-1.5 py-1 text-xs font-medium inline-block cursor-default',
|
'rounded px-1.5 py-1 text-xs font-medium inline-block cursor-default',
|
||||||
{
|
{
|
||||||
'bg-red-500 hover:bg-red-400': props.color === 'red',
|
'bg-red-500 hover:bg-red-400': props.color === 'red',
|
||||||
'bg-orange-500 hover:bg-orange-400': props.color === 'orange',
|
'bg-orange-500 hover:bg-orange-400': props.color === 'orange',
|
||||||
'bg-yellow-500 hover:bg-yellow-400': props.color === 'yellow',
|
'bg-yellow-500 hover:bg-yellow-400': props.color === 'yellow',
|
||||||
'bg-green-500 hover:bg-green-400': props.color === 'green',
|
'bg-green-500 hover:bg-green-400': props.color === 'green',
|
||||||
'bg-blue-500 hover:bg-blue-400': props.color === 'blue',
|
'bg-blue-500 hover:bg-blue-400': props.color === 'blue',
|
||||||
'bg-purple-500 hover:bg-purple-400': props.color === 'purple',
|
'bg-purple-500 hover:bg-purple-400': props.color === 'purple',
|
||||||
'bg-pink-500 hover:bg-pink-400': props.color === 'pink'
|
'bg-pink-500 hover:bg-pink-400': props.color === 'pink'
|
||||||
},
|
},
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
import React from 'react';
|
|
||||||
import { Switch } from '@headlessui/react';
|
import { Switch } from '@headlessui/react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export interface ToggleProps {
|
export interface ToggleProps {
|
||||||
value: boolean;
|
value: boolean;
|
||||||
onChange?: (newValue: boolean) => void;
|
onChange?: (newValue: boolean) => void;
|
||||||
size?: 'sm' | 'md';
|
size?: 'sm' | 'md';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Toggle: React.FC<ToggleProps> = (props) => {
|
export const Toggle: React.FC<ToggleProps> = (props) => {
|
||||||
const { value: isEnabled = false, onChange = (val) => null, size = 'sm' } = props;
|
const { value: isEnabled = false, onChange = (val) => null, size = 'sm' } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
checked={isEnabled}
|
checked={isEnabled}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'transition relative flex-shrink-0 inline-flex items-center h-6 w-11 rounded-full bg-gray-200 dark:bg-gray-550',
|
'transition relative flex-shrink-0 inline-flex items-center h-6 w-11 rounded-full bg-gray-200 dark:bg-gray-550',
|
||||||
{
|
{
|
||||||
'bg-primary-500 dark:bg-primary-500': isEnabled,
|
'bg-primary-500 dark:bg-primary-500': isEnabled,
|
||||||
'h-6 w-11': size === 'sm',
|
'h-6 w-11': size === 'sm',
|
||||||
'h-8 w-[55px]': size === 'md'
|
'h-8 w-[55px]': size === 'md'
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'transition inline-block w-4 h-4 transform bg-white rounded-full',
|
'transition inline-block w-4 h-4 transform bg-white rounded-full',
|
||||||
isEnabled ? 'translate-x-6' : 'translate-x-1',
|
isEnabled ? 'translate-x-6' : 'translate-x-1',
|
||||||
{
|
{
|
||||||
'w-4 h-4': size === 'sm',
|
'w-4 h-4': size === 'sm',
|
||||||
'h-6 w-6': size === 'md',
|
'h-6 w-6': size === 'md',
|
||||||
'translate-x-7': size === 'md' && isEnabled
|
'translate-x-7': size === 'md' && isEnabled
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface StyleState {
|
interface StyleState {
|
||||||
active: string[];
|
active: string[];
|
||||||
hover: string[];
|
hover: string[];
|
||||||
normal: string[];
|
normal: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Variant {
|
interface Variant {
|
||||||
base: string;
|
base: string;
|
||||||
light: StyleState;
|
light: StyleState;
|
||||||
dark: StyleState;
|
dark: StyleState;
|
||||||
}
|
}
|
||||||
|
|
||||||
function tw(variant: Variant): string {
|
function tw(variant: Variant): string {
|
||||||
return `${variant.base} ${variant.light}`;
|
return `${variant.base} ${variant.light}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const variants: Record<string, string> = {
|
const variants: Record<string, string> = {
|
||||||
default: tw({
|
default: tw({
|
||||||
base: 'shadow-sm',
|
base: 'shadow-sm',
|
||||||
light: {
|
light: {
|
||||||
normal: ['bg-gray-50', 'border-gray-100', 'text-gray-700'],
|
normal: ['bg-gray-50', 'border-gray-100', 'text-gray-700'],
|
||||||
hover: ['bg-gray-100', 'border-gray-200', 'text-gray-900'],
|
hover: ['bg-gray-100', 'border-gray-200', 'text-gray-900'],
|
||||||
active: ['bg-gray-50', 'border-gray-200', 'text-gray-600']
|
active: ['bg-gray-50', 'border-gray-200', 'text-gray-600']
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
normal: ['bg-gray-800 ', 'border-gray-100', ' text-gray-200'],
|
normal: ['bg-gray-800 ', 'border-gray-100', ' text-gray-200'],
|
||||||
active: ['bg-gray-700 ', 'border-gray-200 ', 'text-white'],
|
active: ['bg-gray-700 ', 'border-gray-200 ', 'text-white'],
|
||||||
hover: ['bg-gray-700 ', 'border-gray-600 ', 'text-white']
|
hover: ['bg-gray-700 ', 'border-gray-600 ', 'text-white']
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export interface DefaultProps {
|
export interface DefaultProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import React from 'react';
|
|
||||||
import { Transition } from '@headlessui/react';
|
import { Transition } from '@headlessui/react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export default function SlideUp(props: { children: React.ReactNode }) {
|
export default function SlideUp(props: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<Transition
|
<Transition
|
||||||
show
|
show
|
||||||
className="flex flex-grow"
|
className="flex flex-grow"
|
||||||
appear
|
appear
|
||||||
enter="transition ease-in-out-back duration-200"
|
enter="transition ease-in-out-back duration-200"
|
||||||
enterFrom="opacity-0 translate-y-5"
|
enterFrom="opacity-0 translate-y-5"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="transition duration-200"
|
leave="transition duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Transition>
|
</Transition>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +1,58 @@
|
||||||
{
|
{
|
||||||
"node_state": {
|
"node_state": {
|
||||||
"node_pub_id": "f4deae23-e578-456e-8897-9ff59444c08b",
|
"node_pub_id": "f4deae23-e578-456e-8897-9ff59444c08b",
|
||||||
"node_id": 1,
|
"node_id": 1,
|
||||||
"node_name": "Jamie's MacBook Pro",
|
"node_name": "Jamie's MacBook Pro",
|
||||||
"data_path": "/Users/jamie/Library/Application Support/spacedrive",
|
"data_path": "/Users/jamie/Library/Application Support/spacedrive",
|
||||||
"tcp_port": 0,
|
"tcp_port": 0,
|
||||||
"libraries": [
|
"libraries": [
|
||||||
{
|
{
|
||||||
"library_uuid": "9816efb4-5f2d-4c84-8674-818767bb1184",
|
"library_uuid": "9816efb4-5f2d-4c84-8674-818767bb1184",
|
||||||
"library_id": 0,
|
"library_id": 0,
|
||||||
"library_path": "/Users/jamie/Library/Application Support/spacedrive/library.db",
|
"library_path": "/Users/jamie/Library/Application Support/spacedrive/library.db",
|
||||||
"offline": false
|
"offline": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"current_library_uuid": "9816efb4-5f2d-4c84-8674-818767bb1184"
|
"current_library_uuid": "9816efb4-5f2d-4c84-8674-818767bb1184"
|
||||||
},
|
},
|
||||||
"libraries": [
|
"libraries": [
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"pub_id": "9816efb4-5f2d-4c84-8674-818767bb1184",
|
"pub_id": "9816efb4-5f2d-4c84-8674-818767bb1184",
|
||||||
"date_created": "2020-04-01T00:00:00.000Z",
|
"date_created": "2020-04-01T00:00:00.000Z",
|
||||||
"is_primary": true
|
"is_primary": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"pub_id": "f4deae23-e578-456e-8897-9ff59444c08b",
|
"pub_id": "f4deae23-e578-456e-8897-9ff59444c08b",
|
||||||
"name": "Jamie's MacBook Pro",
|
"name": "Jamie's MacBook Pro",
|
||||||
"platform": "macos",
|
"platform": "macos",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"date_created": "2020-04-01T00:00:00.000Z",
|
"date_created": "2020-04-01T00:00:00.000Z",
|
||||||
"last_seen": "2020-04-01T00:00:00.000Z",
|
"last_seen": "2020-04-01T00:00:00.000Z",
|
||||||
"timezone": "America/Los_Angeles"
|
"timezone": "America/Los_Angeles"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"pub_id": "8fec7649-08d8-4828-baa3-e1078d9f15ee",
|
"pub_id": "8fec7649-08d8-4828-baa3-e1078d9f15ee",
|
||||||
"name": "Jamie's iPhone 12",
|
"name": "Jamie's iPhone 12",
|
||||||
"platform": "ios",
|
"platform": "ios",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"date_created": "2020-04-01T00:00:00.000Z",
|
"date_created": "2020-04-01T00:00:00.000Z",
|
||||||
"last_seen": "2020-04-01T00:00:00.000Z",
|
"last_seen": "2020-04-01T00:00:00.000Z",
|
||||||
"timezone": "America/Los_Angeles"
|
"timezone": "America/Los_Angeles"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"pub_id": "67f66377-d38e-4fb7-961a-c1193a58b458",
|
"pub_id": "67f66377-d38e-4fb7-961a-c1193a58b458",
|
||||||
"name": "Spacedrive Server",
|
"name": "Spacedrive Server",
|
||||||
"platform": "server",
|
"platform": "server",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"date_created": "2020-04-01T00:00:00.000Z",
|
"date_created": "2020-04-01T00:00:00.000Z",
|
||||||
"last_seen": "2020-04-01T00:00:00.000Z",
|
"last_seen": "2020-04-01T00:00:00.000Z",
|
||||||
"timezone": "America/Los_Angeles"
|
"timezone": "America/Los_Angeles"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,45 +1,46 @@
|
||||||
import { useContext, useEffect } from 'react';
|
|
||||||
import { CoreEvent } from '@sd/core';
|
|
||||||
import { transport } from '@sd/client';
|
import { transport } from '@sd/client';
|
||||||
|
import { CoreEvent } from '@sd/core';
|
||||||
|
import { useContext, useEffect } from 'react';
|
||||||
import { useQueryClient } from 'react-query';
|
import { useQueryClient } from 'react-query';
|
||||||
import { useExplorerState } from '../components/file/FileList';
|
|
||||||
import { AppPropsContext } from '../App';
|
import { AppPropsContext } from '../App';
|
||||||
|
import { useExplorerState } from '../components/file/FileList';
|
||||||
|
|
||||||
export function useCoreEvents() {
|
export function useCoreEvents() {
|
||||||
const client = useQueryClient();
|
const client = useQueryClient();
|
||||||
|
|
||||||
const { addNewThumbnail } = useExplorerState();
|
const { addNewThumbnail } = useExplorerState();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleCoreEvent(e: CoreEvent) {
|
function handleCoreEvent(e: CoreEvent) {
|
||||||
switch (e?.key) {
|
switch (e?.key) {
|
||||||
case 'NewThumbnail':
|
case 'NewThumbnail':
|
||||||
addNewThumbnail(e.data.cas_id);
|
addNewThumbnail(e.data.cas_id);
|
||||||
break;
|
break;
|
||||||
case 'InvalidateQuery':
|
case 'InvalidateQuery':
|
||||||
case 'InvalidateQueryDebounced':
|
case 'InvalidateQueryDebounced':
|
||||||
let query = [e.data.key];
|
let query = [e.data.key];
|
||||||
// TODO: find a way to make params accessible in TS
|
// TODO: find a way to make params accessible in TS
|
||||||
// also this method will only work for queries that use the whole params obj as the second key
|
// also this method will only work for queries that use the whole params obj as the second key
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
if (e.data.params) {
|
if (e.data.params) {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
query.push(e.data.params);
|
query.push(e.data.params);
|
||||||
}
|
}
|
||||||
client.invalidateQueries(e.data.key);
|
client.invalidateQueries(e.data.key);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check Tauri Event type
|
// check Tauri Event type
|
||||||
transport?.on('core_event', handleCoreEvent);
|
transport?.on('core_event', handleCoreEvent);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
transport?.off('core_event', handleCoreEvent);
|
transport?.off('core_event', handleCoreEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
// listen('core_event', (e: { payload: CoreEvent }) => {
|
// listen('core_event', (e: { payload: CoreEvent }) => {
|
||||||
// });
|
// });
|
||||||
}, [transport]);
|
}, [transport]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
export function useFocusState() {
|
export function useFocusState() {
|
||||||
const [focused, setFocused] = useState(true);
|
const [focused, setFocused] = useState(true);
|
||||||
const focus = () => setFocused(true);
|
const focus = () => setFocused(true);
|
||||||
const blur = () => setFocused(false);
|
const blur = () => setFocused(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('focus', focus);
|
window.addEventListener('focus', focus);
|
||||||
window.addEventListener('blur', blur);
|
window.addEventListener('blur', blur);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('focus', focus);
|
window.removeEventListener('focus', focus);
|
||||||
window.removeEventListener('blur', blur);
|
window.removeEventListener('blur', blur);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return [focused];
|
return [focused];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
export function useInputState<T = any>(initialValue: T) {
|
export function useInputState<T = any>(initialValue: T) {
|
||||||
const [value, setValue] = useState<T>(initialValue);
|
const [value, setValue] = useState<T>(initialValue);
|
||||||
return {
|
return {
|
||||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
|
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
setValue(event.target.value as unknown as T),
|
setValue(event.target.value as unknown as T),
|
||||||
value
|
value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
// import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
// import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export const ContentScreen: React.FC<{}> = (props) => {
|
export const ContentScreen: React.FC<{}> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
|
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
|
||||||
<div className="flex flex-col space-y-5 pb-7">
|
<div className="flex flex-col space-y-5 pb-7">
|
||||||
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
||||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||||
functional.
|
functional.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,54 +1,55 @@
|
||||||
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||||
import { Button } from '@sd/ui';
|
import { Button } from '@sd/ui';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
import { AppPropsContext } from '../App';
|
import { AppPropsContext } from '../App';
|
||||||
import CodeBlock from '../components/primitive/Codeblock';
|
import CodeBlock from '../components/primitive/Codeblock';
|
||||||
|
|
||||||
export const DebugScreen: React.FC<{}> = (props) => {
|
export const DebugScreen: React.FC<{}> = (props) => {
|
||||||
const appPropsContext = useContext(AppPropsContext);
|
const appPropsContext = useContext(AppPropsContext);
|
||||||
const { data: client } = useBridgeQuery('ClientGetState');
|
const { data: client } = useBridgeQuery('ClientGetState');
|
||||||
const { data: jobs } = useBridgeQuery('JobGetRunning');
|
const { data: jobs } = useBridgeQuery('JobGetRunning');
|
||||||
const { data: jobHistory } = useBridgeQuery('JobGetHistory');
|
const { data: jobHistory } = useBridgeQuery('JobGetHistory');
|
||||||
// const { mutate: purgeDB } = useBridgeCommand('PurgeDatabase', {
|
// const { mutate: purgeDB } = useBridgeCommand('PurgeDatabase', {
|
||||||
// onMutate: () => {
|
// onMutate: () => {
|
||||||
// alert('Database purged');
|
// alert('Database purged');
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
const { mutate: identifyFiles } = useBridgeCommand('IdentifyUniqueFiles');
|
const { mutate: identifyFiles } = useBridgeCommand('IdentifyUniqueFiles');
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
|
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
|
||||||
<div className="flex flex-col space-y-5 pb-7">
|
<div className="flex flex-col space-y-5 pb-7">
|
||||||
<h1 className="text-lg font-bold ">Developer Debugger</h1>
|
<h1 className="text-lg font-bold ">Developer Debugger</h1>
|
||||||
<div className="flex flex-row pb-4 space-x-2">
|
<div className="flex flex-row pb-4 space-x-2">
|
||||||
<Button
|
<Button
|
||||||
className="w-40"
|
className="w-40"
|
||||||
variant="gray"
|
variant="gray"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (client && appPropsContext?.onOpen) {
|
if (client && appPropsContext?.onOpen) {
|
||||||
appPropsContext.onOpen(client.data_path);
|
appPropsContext.onOpen(client.data_path);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Open data folder
|
Open data folder
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className="w-40"
|
className="w-40"
|
||||||
variant="gray"
|
variant="gray"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => identifyFiles(undefined)}
|
onClick={() => identifyFiles(undefined)}
|
||||||
>
|
>
|
||||||
Identify unique files
|
Identify unique files
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-sm font-bold ">Running Jobs</h1>
|
<h1 className="text-sm font-bold ">Running Jobs</h1>
|
||||||
<CodeBlock src={{ ...jobs }} />
|
<CodeBlock src={{ ...jobs }} />
|
||||||
<h1 className="text-sm font-bold ">Job History</h1>
|
<h1 className="text-sm font-bold ">Job History</h1>
|
||||||
<CodeBlock src={{ ...jobHistory }} />
|
<CodeBlock src={{ ...jobHistory }} />
|
||||||
<h1 className="text-sm font-bold ">Client State</h1>
|
<h1 className="text-sm font-bold ">Client State</h1>
|
||||||
<CodeBlock src={{ ...client }} />
|
<CodeBlock src={{ ...client }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,43 +1,44 @@
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import { FileList, useExplorerState } from '../components/file/FileList';
|
|
||||||
import { TopBar } from '../components/layout/TopBar';
|
|
||||||
import { useParams, useSearchParams } from 'react-router-dom';
|
|
||||||
import { useBridgeQuery } from '@sd/client';
|
import { useBridgeQuery } from '@sd/client';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { FileList, useExplorerState } from '../components/file/FileList';
|
||||||
import { Inspector } from '../components/file/Inspector';
|
import { Inspector } from '../components/file/Inspector';
|
||||||
|
import { TopBar } from '../components/layout/TopBar';
|
||||||
|
|
||||||
export const ExplorerScreen: React.FC<{}> = () => {
|
export const ExplorerScreen: React.FC<{}> = () => {
|
||||||
let [searchParams] = useSearchParams();
|
let [searchParams] = useSearchParams();
|
||||||
let path = searchParams.get('path') || '';
|
let path = searchParams.get('path') || '';
|
||||||
|
|
||||||
let { id } = useParams();
|
let { id } = useParams();
|
||||||
let location_id = Number(id);
|
let location_id = Number(id);
|
||||||
|
|
||||||
let [limit, setLimit] = React.useState(100);
|
let [limit, setLimit] = React.useState(100);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log({ location_id, path, limit });
|
console.log({ location_id, path, limit });
|
||||||
}, [location_id, path]);
|
}, [location_id, path]);
|
||||||
|
|
||||||
const { selectedRowIndex } = useExplorerState();
|
const { selectedRowIndex } = useExplorerState();
|
||||||
|
|
||||||
const { data: currentDir } = useBridgeQuery(
|
const { data: currentDir } = useBridgeQuery(
|
||||||
'LibGetExplorerDir',
|
'LibGetExplorerDir',
|
||||||
{ location_id: location_id!, path, limit },
|
{ location_id: location_id!, path, limit },
|
||||||
{ enabled: !!location_id }
|
{ enabled: !!location_id }
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-full">
|
<div className="flex flex-col w-full h-full">
|
||||||
<TopBar />
|
<TopBar />
|
||||||
<div className="relative flex flex-row w-full ">
|
<div className="relative flex flex-row w-full ">
|
||||||
<FileList location_id={location_id} path={path} limit={limit} />
|
<FileList location_id={location_id} path={path} limit={limit} />
|
||||||
{currentDir?.contents && (
|
{currentDir?.contents && (
|
||||||
<Inspector
|
<Inspector
|
||||||
selectedFile={currentDir.contents[selectedRowIndex]}
|
selectedFile={currentDir.contents[selectedRowIndex]}
|
||||||
locationId={location_id}
|
locationId={location_id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,196 +1,193 @@
|
||||||
import { CloudIcon } from '@heroicons/react/outline';
|
import { MenuIcon, PlusIcon } from '@heroicons/react/solid';
|
||||||
import { CogIcon, MenuIcon, PlusIcon } from '@heroicons/react/solid';
|
|
||||||
import { useBridgeQuery } from '@sd/client';
|
import { useBridgeQuery } from '@sd/client';
|
||||||
import { Statistics } from '@sd/core';
|
import { Statistics } from '@sd/core';
|
||||||
import { Button } from '@sd/ui';
|
import { Button } from '@sd/ui';
|
||||||
import byteSize from 'byte-size';
|
import byteSize from 'byte-size';
|
||||||
import { DotsSixVertical, Laptop, LineSegments, Plus } from 'phosphor-react';
|
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { AppPropsContext } from '../App';
|
|
||||||
import { Device } from '../components/device/Device';
|
|
||||||
import FileItem from '../components/file/FileItem';
|
|
||||||
import Dialog from '../components/layout/Dialog';
|
|
||||||
import { Input } from '../components/primitive';
|
|
||||||
import { InputContainer } from '../components/primitive/InputContainer';
|
|
||||||
import { useCountUp } from 'react-countup';
|
import { useCountUp } from 'react-countup';
|
||||||
|
|
||||||
|
import { AppPropsContext } from '../App';
|
||||||
|
import { Device } from '../components/device/Device';
|
||||||
|
import Dialog from '../components/layout/Dialog';
|
||||||
|
import { Input } from '../components/primitive';
|
||||||
|
|
||||||
interface StatItemProps {
|
interface StatItemProps {
|
||||||
name: string;
|
name: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
unit?: string;
|
unit?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatItem: React.FC<StatItemProps> = (props) => {
|
const StatItem: React.FC<StatItemProps> = (props) => {
|
||||||
const countUpRef = React.useRef(null);
|
const countUpRef = React.useRef(null);
|
||||||
const appPropsContext = useContext(AppPropsContext);
|
const appPropsContext = useContext(AppPropsContext);
|
||||||
let size = byteSize(Number(props.value) || 0);
|
let size = byteSize(Number(props.value) || 0);
|
||||||
|
|
||||||
let amount = parseFloat(size.value);
|
let amount = parseFloat(size.value);
|
||||||
|
|
||||||
const [hasRun, setHasRun] = useState(false);
|
const [hasRun, setHasRun] = useState(false);
|
||||||
|
|
||||||
const { update } = useCountUp({
|
const { update } = useCountUp({
|
||||||
startOnMount: !hasRun,
|
startOnMount: !hasRun,
|
||||||
ref: countUpRef,
|
ref: countUpRef,
|
||||||
// start: amount / 2,
|
// start: amount / 2,
|
||||||
end: amount,
|
end: amount,
|
||||||
delay: 0.1,
|
delay: 0.1,
|
||||||
decimals: 1,
|
decimals: 1,
|
||||||
duration: appPropsContext?.demoMode ? 2 : 1,
|
duration: appPropsContext?.demoMode ? 2 : 1,
|
||||||
useEasing: true,
|
useEasing: true,
|
||||||
onEnd: () => {
|
onEnd: () => {
|
||||||
setHasRun(true);
|
setHasRun(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
update(amount);
|
update(amount);
|
||||||
}, [amount]);
|
}, [amount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-shrink-0 w-32 px-4 py-3 duration-75 transform rounded-md cursor-default hover:bg-gray-50 hover:dark:bg-gray-600">
|
<div className="flex flex-col flex-shrink-0 w-32 px-4 py-3 duration-75 transform rounded-md cursor-default hover:bg-gray-50 hover:dark:bg-gray-600">
|
||||||
<span className="text-sm text-gray-400">{props.name}</span>
|
<span className="text-sm text-gray-400">{props.name}</span>
|
||||||
<span className="text-2xl font-bold">
|
<span className="text-2xl font-bold">
|
||||||
<span ref={countUpRef} />
|
<span ref={countUpRef} />
|
||||||
<span className="ml-1 text-[16px] text-gray-400">{size.unit}</span>
|
<span className="ml-1 text-[16px] text-gray-400">{size.unit}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OverviewScreen: React.FC<{}> = (props) => {
|
export const OverviewScreen: React.FC<{}> = (props) => {
|
||||||
const { data: libraryStatistics } = useBridgeQuery('GetLibraryStatistics');
|
const { data: libraryStatistics } = useBridgeQuery('GetLibraryStatistics');
|
||||||
const { data: clientState } = useBridgeQuery('ClientGetState');
|
const { data: clientState } = useBridgeQuery('ClientGetState');
|
||||||
|
|
||||||
const [stats, setStats] = useState<Statistics>(libraryStatistics || ({} as Statistics));
|
const [stats, setStats] = useState<Statistics>(libraryStatistics || ({} as Statistics));
|
||||||
|
|
||||||
// get app props context
|
// get app props context
|
||||||
const appPropsContext = useContext(AppPropsContext);
|
const appPropsContext = useContext(AppPropsContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appPropsContext?.demoMode == true && !libraryStatistics?.library_db_size) {
|
if (appPropsContext?.demoMode == true && !libraryStatistics?.library_db_size) {
|
||||||
setStats({
|
setStats({
|
||||||
total_bytes_capacity: '8093333345230',
|
total_bytes_capacity: '8093333345230',
|
||||||
preview_media_bytes: '2304387532',
|
preview_media_bytes: '2304387532',
|
||||||
library_db_size: '83345230',
|
library_db_size: '83345230',
|
||||||
total_file_count: 20342345,
|
total_file_count: 20342345,
|
||||||
total_bytes_free: '89734502034',
|
total_bytes_free: '89734502034',
|
||||||
total_bytes_used: '8093333345230',
|
total_bytes_used: '8093333345230',
|
||||||
total_unique_bytes: '9347397'
|
total_unique_bytes: '9347397'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setStats(libraryStatistics as Statistics);
|
setStats(libraryStatistics as Statistics);
|
||||||
}
|
}
|
||||||
}, [appPropsContext, libraryStatistics]);
|
}, [appPropsContext, libraryStatistics]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-screen overflow-x-hidden custom-scroll page-scroll">
|
<div className="flex flex-col w-full h-screen overflow-x-hidden custom-scroll page-scroll">
|
||||||
<div data-tauri-drag-region className="flex flex-shrink-0 w-full h-5" />
|
<div data-tauri-drag-region className="flex flex-shrink-0 w-full h-5" />
|
||||||
{/* PAGE */}
|
{/* PAGE */}
|
||||||
<div className="flex flex-col w-full h-screen px-3">
|
<div className="flex flex-col w-full h-screen px-3">
|
||||||
{/* STAT HEADER */}
|
{/* STAT HEADER */}
|
||||||
<div className="flex w-full">
|
<div className="flex w-full">
|
||||||
{/* STAT CONTAINER */}
|
{/* STAT CONTAINER */}
|
||||||
<div className="flex pb-4 overflow-hidden">
|
<div className="flex pb-4 overflow-hidden">
|
||||||
<StatItem
|
<StatItem
|
||||||
name="Total capacity"
|
name="Total capacity"
|
||||||
value={stats?.total_bytes_capacity}
|
value={stats?.total_bytes_capacity}
|
||||||
unit={stats?.total_bytes_capacity}
|
unit={stats?.total_bytes_capacity}
|
||||||
/>
|
/>
|
||||||
<StatItem
|
<StatItem
|
||||||
name="Index size"
|
name="Index size"
|
||||||
value={stats?.library_db_size}
|
value={stats?.library_db_size}
|
||||||
unit={stats?.library_db_size}
|
unit={stats?.library_db_size}
|
||||||
/>
|
/>
|
||||||
<StatItem
|
<StatItem
|
||||||
name="Preview media"
|
name="Preview media"
|
||||||
value={stats?.preview_media_bytes}
|
value={stats?.preview_media_bytes}
|
||||||
unit={stats?.preview_media_bytes}
|
unit={stats?.preview_media_bytes}
|
||||||
/>
|
/>
|
||||||
<StatItem
|
<StatItem
|
||||||
name="Free space"
|
name="Free space"
|
||||||
value={stats?.total_bytes_free}
|
value={stats?.total_bytes_free}
|
||||||
unit={stats?.total_bytes_free}
|
unit={stats?.total_bytes_free}
|
||||||
/>
|
/>
|
||||||
<StatItem name="Total at-risk" value={'0'} unit={stats?.preview_media_bytes} />
|
<StatItem name="Total at-risk" value={'0'} unit={stats?.preview_media_bytes} />
|
||||||
{/* <StatItem
|
{/* <StatItem
|
||||||
name="Total at-risk"
|
name="Total at-risk"
|
||||||
value={'0'}
|
value={'0'}
|
||||||
unit={stats?.preview_media_bytes}
|
unit={stats?.preview_media_bytes}
|
||||||
/>
|
/>
|
||||||
<StatItem name="Total backed up" value={'0'} unit={''} /> */}
|
<StatItem name="Total backed up" value={'0'} unit={''} /> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow" />
|
<div className="flex-grow" />
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<Dialog
|
<Dialog
|
||||||
title="Add Device"
|
title="Add Device"
|
||||||
description="Connect a new device to your library. Either enter another device's code or copy this one."
|
description="Connect a new device to your library. Either enter another device's code or copy this one."
|
||||||
ctaAction={() => {}}
|
ctaAction={() => {}}
|
||||||
ctaLabel="Connect"
|
ctaLabel="Connect"
|
||||||
trigger={
|
trigger={
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
icon={<PlusIcon className="inline w-4 h-4 -mt-0.5 mr-1" />}
|
icon={<PlusIcon className="inline w-4 h-4 -mt-0.5 mr-1" />}
|
||||||
variant="gray"
|
variant="gray"
|
||||||
className="hidden sm:visible"
|
className="hidden sm:visible"
|
||||||
>
|
>
|
||||||
Add Device
|
Add Device
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col mt-2 space-y-3">
|
<div className="flex flex-col mt-2 space-y-3">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="mb-1 text-xs font-bold uppercase text-gray-450">
|
<span className="mb-1 text-xs font-bold uppercase text-gray-450">
|
||||||
This Device
|
This Device
|
||||||
</span>
|
</span>
|
||||||
<Input readOnly disabled value="06ffd64309b24fb09e7c2188963d0207" />
|
<Input readOnly disabled value="06ffd64309b24fb09e7c2188963d0207" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="mb-1 text-xs font-bold uppercase text-gray-450">
|
<span className="mb-1 text-xs font-bold uppercase text-gray-450">
|
||||||
Enter a device code
|
Enter a device code
|
||||||
</span>
|
</span>
|
||||||
<Input value="" />
|
<Input value="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="w-8"
|
className="w-8"
|
||||||
noPadding
|
noPadding
|
||||||
icon={<MenuIcon className="inline w-4 h-4" />}
|
icon={<MenuIcon className="inline w-4 h-4" />}
|
||||||
variant="gray"
|
variant="gray"
|
||||||
></Button>
|
></Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col pb-4 space-y-4">
|
<div className="flex flex-col pb-4 space-y-4">
|
||||||
<Device
|
<Device
|
||||||
name="James' MacBook Pro"
|
name="James' MacBook Pro"
|
||||||
size="1.4TB"
|
size="1.4TB"
|
||||||
runningJob={{ amount: 65, task: 'Generating preview media' }}
|
runningJob={{ amount: 65, task: 'Generating preview media' }}
|
||||||
locations={[{ name: 'Pictures' }, { name: 'Downloads' }, { name: 'Minecraft' }]}
|
locations={[{ name: 'Pictures' }, { name: 'Downloads' }, { name: 'Minecraft' }]}
|
||||||
type="laptop"
|
type="laptop"
|
||||||
/>
|
/>
|
||||||
<Device
|
<Device
|
||||||
name={`James' iPhone 12`}
|
name={`James' iPhone 12`}
|
||||||
size="47.7GB"
|
size="47.7GB"
|
||||||
locations={[{ name: 'Camera Roll' }, { name: 'Notes' }]}
|
locations={[{ name: 'Camera Roll' }, { name: 'Notes' }]}
|
||||||
type="phone"
|
type="phone"
|
||||||
removeThisSoon
|
removeThisSoon
|
||||||
/>
|
/>
|
||||||
<Device
|
<Device
|
||||||
name={`Spacedrive Server`}
|
name={`Spacedrive Server`}
|
||||||
size="5GB"
|
size="5GB"
|
||||||
locations={[{ name: 'Cached' }, { name: 'Photos' }, { name: 'Documents' }]}
|
locations={[{ name: 'Cached' }, { name: 'Photos' }, { name: 'Documents' }]}
|
||||||
type="server"
|
type="server"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-5 py-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
<div className="px-5 py-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
||||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||||
functional.
|
functional.
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-shrink-0 w-full h-4" />
|
<div className="flex flex-shrink-0 w-full h-4" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
// import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
// import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export const PhotosScreen: React.FC<{}> = (props) => {
|
export const PhotosScreen: React.FC<{}> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
|
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
|
||||||
<div className="flex flex-col space-y-5 pb-7">
|
<div className="flex flex-col space-y-5 pb-7">
|
||||||
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
||||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||||
functional.
|
functional.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,17 +2,17 @@ import React, { useEffect } from 'react';
|
||||||
import { useLocation, useNavigate } from 'react-router';
|
import { useLocation, useNavigate } from 'react-router';
|
||||||
|
|
||||||
export interface RedirectPageProps {
|
export interface RedirectPageProps {
|
||||||
to: string;
|
to: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RedirectPage: React.FC<RedirectPageProps> = (props) => {
|
export const RedirectPage: React.FC<RedirectPageProps> = (props) => {
|
||||||
const { to: destination } = props;
|
const { to: destination } = props;
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
navigate(destination);
|
navigate(destination);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,97 +1,98 @@
|
||||||
import {
|
|
||||||
CloudIcon,
|
|
||||||
CogIcon,
|
|
||||||
KeyIcon,
|
|
||||||
LockClosedIcon,
|
|
||||||
PhotographIcon,
|
|
||||||
TagIcon,
|
|
||||||
TerminalIcon,
|
|
||||||
UsersIcon
|
|
||||||
} from '@heroicons/react/outline';
|
|
||||||
import React from 'react';
|
|
||||||
// import { dummyIFile, FileList } from '../components/file/FileList';
|
// import { dummyIFile, FileList } from '../components/file/FileList';
|
||||||
import { SidebarLink } from '../components/file/Sidebar';
|
import {
|
||||||
import { Book, Database, HardDrive, PaintBrush } from 'phosphor-react';
|
CloudIcon,
|
||||||
|
CogIcon,
|
||||||
|
KeyIcon,
|
||||||
|
LockClosedIcon,
|
||||||
|
PhotographIcon,
|
||||||
|
TagIcon,
|
||||||
|
TerminalIcon,
|
||||||
|
UsersIcon
|
||||||
|
} from '@heroicons/react/outline';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { Book, Database, HardDrive, PaintBrush } from 'phosphor-react';
|
||||||
|
import React from 'react';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { SidebarLink } from '../components/file/Sidebar';
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
// import { Spline } from 'react-spline';
|
// import { Spline } from 'react-spline';
|
||||||
// import WINDOWS_SCENE from '../assets/spline/scene.json';
|
// import WINDOWS_SCENE from '../assets/spline/scene.json';
|
||||||
|
|
||||||
const Icon = ({ component: Icon, ...props }: any) => (
|
const Icon = ({ component: Icon, ...props }: any) => (
|
||||||
<Icon weight="bold" {...props} className={clsx('w-4 h-4 mr-2', props.className)} />
|
<Icon weight="bold" {...props} className={clsx('w-4 h-4 mr-2', props.className)} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const Heading: React.FC<{ className?: string; children: string }> = ({ children, className }) => (
|
const Heading: React.FC<{ className?: string; children: string }> = ({ children, className }) => (
|
||||||
<div className={clsx('mt-5 mb-1 ml-1 text-xs font-semibold text-gray-300', className)}>
|
<div className={clsx('mt-5 mb-1 ml-1 text-xs font-semibold text-gray-300', className)}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SettingsScreen: React.FC<{}> = () => {
|
export const SettingsScreen: React.FC<{}> = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row w-full">
|
<div className="flex flex-row w-full">
|
||||||
<div className="h-full border-r border-gray-100 w-60 dark:border-gray-550">
|
<div className="h-full border-r border-gray-100 w-60 dark:border-gray-550">
|
||||||
<div data-tauri-drag-region className="w-full h-7" />
|
<div data-tauri-drag-region className="w-full h-7" />
|
||||||
<div className="p-5 pt-0">
|
<div className="p-5 pt-0">
|
||||||
<Heading className="mt-0">Client</Heading>
|
<Heading className="mt-0">Client</Heading>
|
||||||
<SidebarLink to="/settings/general">
|
<SidebarLink to="/settings/general">
|
||||||
<Icon component={CogIcon} />
|
<Icon component={CogIcon} />
|
||||||
General
|
General
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
<SidebarLink to="/settings/security">
|
<SidebarLink to="/settings/security">
|
||||||
<Icon component={LockClosedIcon} />
|
<Icon component={LockClosedIcon} />
|
||||||
Security
|
Security
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
<SidebarLink to="/settings/appearance">
|
<SidebarLink to="/settings/appearance">
|
||||||
<Icon component={PaintBrush} />
|
<Icon component={PaintBrush} />
|
||||||
Appearance
|
Appearance
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
<SidebarLink to="/settings/experimental">
|
<SidebarLink to="/settings/experimental">
|
||||||
<Icon component={TerminalIcon} />
|
<Icon component={TerminalIcon} />
|
||||||
Experimental
|
Experimental
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
|
|
||||||
<Heading>Library</Heading>
|
<Heading>Library</Heading>
|
||||||
<SidebarLink to="/settings/library">
|
<SidebarLink to="/settings/library">
|
||||||
<Icon component={Database} />
|
<Icon component={Database} />
|
||||||
Database
|
Database
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
<SidebarLink to="/settings/locations">
|
<SidebarLink to="/settings/locations">
|
||||||
<Icon component={HardDrive} />
|
<Icon component={HardDrive} />
|
||||||
Locations
|
Locations
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
|
|
||||||
<SidebarLink to="/settings/keys">
|
<SidebarLink to="/settings/keys">
|
||||||
<Icon component={KeyIcon} />
|
<Icon component={KeyIcon} />
|
||||||
Keys
|
Keys
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
<SidebarLink to="/settings/tags">
|
<SidebarLink to="/settings/tags">
|
||||||
<Icon component={TagIcon} />
|
<Icon component={TagIcon} />
|
||||||
Tags
|
Tags
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
|
|
||||||
<Heading>Cloud</Heading>
|
<Heading>Cloud</Heading>
|
||||||
<SidebarLink to="/settings/sync">
|
<SidebarLink to="/settings/sync">
|
||||||
<Icon component={CloudIcon} />
|
<Icon component={CloudIcon} />
|
||||||
Sync
|
Sync
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
<SidebarLink to="/settings/contacts">
|
<SidebarLink to="/settings/contacts">
|
||||||
<Icon component={UsersIcon} />
|
<Icon component={UsersIcon} />
|
||||||
Contacts
|
Contacts
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="">
|
<div className="">
|
||||||
<div data-tauri-drag-region className="w-full h-7" />
|
<div data-tauri-drag-region className="w-full h-7" />
|
||||||
<div className="flex flex-grow-0 w-full h-full max-h-screen custom-scroll page-scroll">
|
<div className="flex flex-grow-0 w-full h-full max-h-screen custom-scroll page-scroll">
|
||||||
<div className="flex flex-grow px-12 pb-5">
|
<div className="flex flex-grow px-12 pb-5">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<div className="block h-20" />
|
<div className="block h-20" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,17 +2,17 @@ import React, { useEffect } from 'react';
|
||||||
import { useParams, useSearchParams } from 'react-router-dom';
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
export const TagScreen: React.FC<{}> = () => {
|
export const TagScreen: React.FC<{}> = () => {
|
||||||
let [searchParams] = useSearchParams();
|
let [searchParams] = useSearchParams();
|
||||||
let path = searchParams.get('path') || '';
|
let path = searchParams.get('path') || '';
|
||||||
|
|
||||||
let { id } = useParams();
|
let { id } = useParams();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full p-5">
|
<div className="w-full p-5">
|
||||||
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
||||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||||
functional.
|
functional.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,38 +1,39 @@
|
||||||
import React from 'react';
|
|
||||||
import { Button } from '@sd/ui';
|
import { Button } from '@sd/ui';
|
||||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
import React from 'react';
|
||||||
import { Toggle } from '../../components/primitive';
|
|
||||||
import { useStore } from '../../components/device/Stores';
|
import { useStore } from '../../components/device/Stores';
|
||||||
|
import { Toggle } from '../../components/primitive';
|
||||||
|
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||||
|
|
||||||
export default function ExperimentalSettings() {
|
export default function ExperimentalSettings() {
|
||||||
// const locations = useBridgeQuery("SysGetLocation")
|
// const locations = useBridgeQuery("SysGetLocation")
|
||||||
|
|
||||||
const experimental = useStore((state) => state.experimental);
|
const experimental = useStore((state) => state.experimental);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||||
{/*<Button size="sm">Add Location</Button>*/}
|
{/*<Button size="sm">Add Location</Button>*/}
|
||||||
<div className="mt-3 mb-3">
|
<div className="mt-3 mb-3">
|
||||||
<h1 className="text-2xl font-bold">Experimental</h1>
|
<h1 className="text-2xl font-bold">Experimental</h1>
|
||||||
<p className="mt-1 text-sm text-gray-400">Experimental features within Spacedrive.</p>
|
<p className="mt-1 text-sm text-gray-400">Experimental features within Spacedrive.</p>
|
||||||
</div>
|
</div>
|
||||||
<InputContainer
|
<InputContainer
|
||||||
mini
|
mini
|
||||||
title="Debug Menu"
|
title="Debug Menu"
|
||||||
description="Shows data about Spacedrive such as Jobs, Job History and Client State."
|
description="Shows data about Spacedrive such as Jobs, Job History and Client State."
|
||||||
>
|
>
|
||||||
<div className="flex items-center h-full pl-10">
|
<div className="flex items-center h-full pl-10">
|
||||||
<Toggle
|
<Toggle
|
||||||
value={experimental}
|
value={experimental}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
onChange={(newValue) => {
|
onChange={(newValue) => {
|
||||||
useStore.setState({
|
useStore.setState({
|
||||||
experimental: newValue
|
experimental: newValue
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||||
import { Input } from '../../components/primitive';
|
|
||||||
import { Button } from '@sd/ui';
|
import { Button } from '@sd/ui';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { Input } from '../../components/primitive';
|
||||||
|
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||||
import Listbox from '../../components/primitive/Listbox';
|
import Listbox from '../../components/primitive/Listbox';
|
||||||
import Slider from '../../components/primitive/Slider';
|
import Slider from '../../components/primitive/Slider';
|
||||||
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
|
||||||
|
|
||||||
export default function GeneralSettings() {
|
export default function GeneralSettings() {
|
||||||
const { data: volumes } = useBridgeQuery('SysGetVolumes');
|
const { data: volumes } = useBridgeQuery('SysGetVolumes');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||||
<div className="mt-3 mb-3">
|
<div className="mt-3 mb-3">
|
||||||
<h1 className="text-2xl font-bold">General Settings</h1>
|
<h1 className="text-2xl font-bold">General Settings</h1>
|
||||||
<p className="mt-1 text-sm text-gray-400">Basic settings related to this client</p>
|
<p className="mt-1 text-sm text-gray-400">Basic settings related to this client</p>
|
||||||
{/* <hr className="mt-4 border-gray-550" /> */}
|
{/* <hr className="mt-4 border-gray-550" /> */}
|
||||||
</div>
|
</div>
|
||||||
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
||||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||||
functional.
|
functional.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* <InputContainer
|
{/* <InputContainer
|
||||||
title="Test scan directory"
|
title="Test scan directory"
|
||||||
description="This will create a job to scan the directory you specify to the database."
|
description="This will create a job to scan the directory you specify to the database."
|
||||||
>
|
>
|
||||||
|
@ -47,23 +47,23 @@ export default function GeneralSettings() {
|
||||||
</div>
|
</div>
|
||||||
</InputContainer> */}
|
</InputContainer> */}
|
||||||
|
|
||||||
<InputContainer title="Volumes" description="A list of volumes running on this device.">
|
<InputContainer title="Volumes" description="A list of volumes running on this device.">
|
||||||
<div className="flex flex-row space-x-2">
|
<div className="flex flex-row space-x-2">
|
||||||
<div className="flex flex-grow">
|
<div className="flex flex-grow">
|
||||||
<Listbox
|
<Listbox
|
||||||
options={
|
options={
|
||||||
volumes?.map((volume) => ({
|
volumes?.map((volume) => ({
|
||||||
key: volume.name,
|
key: volume.name,
|
||||||
option: volume.name,
|
option: volume.name,
|
||||||
description: volume.mount_point
|
description: volume.mount_point
|
||||||
})) ?? []
|
})) ?? []
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
|
|
||||||
{/* <div className="">{JSON.stringify({ config })}</div> */}
|
{/* <div className="">{JSON.stringify({ config })}</div> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,32 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
|
||||||
import { Toggle } from '../../components/primitive';
|
import { Toggle } from '../../components/primitive';
|
||||||
|
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||||
|
|
||||||
type LibrarySecurity = 'public' | 'password' | 'vault';
|
type LibrarySecurity = 'public' | 'password' | 'vault';
|
||||||
|
|
||||||
export default function LibrarySettings() {
|
export default function LibrarySettings() {
|
||||||
// const locations = useBridgeQuery("SysGetLocation")
|
// const locations = useBridgeQuery("SysGetLocation")
|
||||||
const [encryptOnCloud, setEncryptOnCloud] = React.useState<boolean>(false);
|
const [encryptOnCloud, setEncryptOnCloud] = React.useState<boolean>(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||||
{/*<Button size="sm">Add Location</Button>*/}
|
{/*<Button size="sm">Add Location</Button>*/}
|
||||||
<div className="mt-3 mb-3">
|
<div className="mt-3 mb-3">
|
||||||
<h1 className="text-2xl font-bold">Library database</h1>
|
<h1 className="text-2xl font-bold">Library database</h1>
|
||||||
<p className="mt-1 text-sm text-gray-400">
|
<p className="mt-1 text-sm text-gray-400">
|
||||||
The database contains all library data and file metadata.
|
The database contains all library data and file metadata.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<InputContainer
|
<InputContainer
|
||||||
mini
|
mini
|
||||||
title="Encrypt on cloud"
|
title="Encrypt on cloud"
|
||||||
description="Enable if library contains sensitive data and should not be synced to the cloud without full encryption."
|
description="Enable if library contains sensitive data and should not be synced to the cloud without full encryption."
|
||||||
>
|
>
|
||||||
<div className="flex items-center h-full pl-10">
|
<div className="flex items-center h-full pl-10">
|
||||||
<Toggle value={encryptOnCloud} onChange={setEncryptOnCloud} size={'sm'} />
|
<Toggle value={encryptOnCloud} onChange={setEncryptOnCloud} size={'sm'} />
|
||||||
</div>
|
</div>
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
import React from 'react';
|
|
||||||
import { Button } from '@sd/ui';
|
import { Button } from '@sd/ui';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||||
|
|
||||||
const exampleLocations = [
|
const exampleLocations = [
|
||||||
{ option: 'Macintosh HD', key: 'macintosh_hd' },
|
{ option: 'Macintosh HD', key: 'macintosh_hd' },
|
||||||
{ option: 'LaCie External', key: 'lacie_external' },
|
{ option: 'LaCie External', key: 'lacie_external' },
|
||||||
{ option: 'Seagate 8TB', key: 'seagate_8tb' }
|
{ option: 'Seagate 8TB', key: 'seagate_8tb' }
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function LocationSettings() {
|
export default function LocationSettings() {
|
||||||
// const locations = useBridgeQuery("SysGetLocation")
|
// const locations = useBridgeQuery("SysGetLocation")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||||
{/*<Button size="sm">Add Location</Button>*/}
|
{/*<Button size="sm">Add Location</Button>*/}
|
||||||
<InputContainer
|
<InputContainer
|
||||||
title="Something about a vault"
|
title="Something about a vault"
|
||||||
description="Local cache storage for media previews and thumbnails."
|
description="Local cache storage for media previews and thumbnails."
|
||||||
>
|
>
|
||||||
<div className="flex flex-row space-x-2"></div>
|
<div className="flex flex-row space-x-2"></div>
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
import React from 'react';
|
|
||||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
|
||||||
import { Button } from '@sd/ui';
|
import { Button } from '@sd/ui';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||||
|
|
||||||
export default function SecuritySettings() {
|
export default function SecuritySettings() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<InputContainer
|
<InputContainer
|
||||||
title="Vault"
|
title="Vault"
|
||||||
description="You'll need to set a passphrase to enable the vault."
|
description="You'll need to set a passphrase to enable the vault."
|
||||||
>
|
>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<Button variant="primary">Enable Vault</Button>
|
<Button variant="primary">Enable Vault</Button>
|
||||||
{/*<Input className="flex-grow" value="jeff" placeholder="/users/jamie/Desktop" />*/}
|
{/*<Input className="flex-grow" value="jeff" placeholder="/users/jamie/Desktop" />*/}
|
||||||
</div>
|
</div>
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,114 +1,114 @@
|
||||||
a,
|
a,
|
||||||
button {
|
button {
|
||||||
@apply cursor-default;
|
@apply cursor-default;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'InterVariable', sans-serif;
|
font-family: 'InterVariable', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-scrollbar {
|
.no-scrollbar {
|
||||||
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
||||||
scrollbar-width: none; /* for Firefox */
|
scrollbar-width: none; /* for Firefox */
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-scrollbar::-webkit-scrollbar {
|
.no-scrollbar::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-scroll {
|
.custom-scroll {
|
||||||
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
||||||
scrollbar-width: none; /* for Firefox */
|
scrollbar-width: none; /* for Firefox */
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.explorer-scroll {
|
.explorer-scroll {
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
height: 6px;
|
height: 6px;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
}
|
}
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
@apply bg-[#00000006] dark:bg-[#00000030] mt-[55px] rounded-[6px];
|
@apply bg-[#00000006] dark:bg-[#00000030] mt-[55px] rounded-[6px];
|
||||||
}
|
}
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.default-scroll {
|
.default-scroll {
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
height: 6px;
|
height: 6px;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
}
|
}
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
@apply bg-transparent rounded-[6px];
|
@apply bg-transparent rounded-[6px];
|
||||||
}
|
}
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.page-scroll {
|
.page-scroll {
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
height: 6px;
|
height: 6px;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
}
|
}
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
@apply bg-transparent rounded-[6px] my-[10px];
|
@apply bg-transparent rounded-[6px] my-[10px];
|
||||||
}
|
}
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeOut {
|
@keyframes fadeOut {
|
||||||
from {
|
from {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@-webkit-keyframes slide-top {
|
@-webkit-keyframes slide-top {
|
||||||
0% {
|
0% {
|
||||||
-webkit-transform: translateY(0);
|
-webkit-transform: translateY(0);
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
-webkit-transform: translateY(-50px);
|
-webkit-transform: translateY(-50px);
|
||||||
transform: translateY(-50px);
|
transform: translateY(-50px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@keyframes slide-top {
|
@keyframes slide-top {
|
||||||
0% {
|
0% {
|
||||||
-webkit-transform: translateY(0);
|
-webkit-transform: translateY(0);
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
-webkit-transform: translateY(-50px);
|
-webkit-transform: translateY(-50px);
|
||||||
transform: translateY(-50px);
|
transform: translateY(-50px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-overlay[data-state='open'] {
|
.dialog-overlay[data-state='open'] {
|
||||||
animation: fadeIn 200ms ease-out forwards;
|
animation: fadeIn 200ms ease-out forwards;
|
||||||
}
|
}
|
||||||
.dialog-overlay[data-state='closed'] {
|
.dialog-overlay[data-state='closed'] {
|
||||||
animation: fadeIn 200ms ease-out forwards;
|
animation: fadeIn 200ms ease-out forwards;
|
||||||
}
|
}
|
||||||
.dialog-content[data-state='open'] {
|
.dialog-content[data-state='open'] {
|
||||||
-webkit-animation: slide-top 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) both;
|
-webkit-animation: slide-top 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) both;
|
||||||
animation: slide-top 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) both;
|
animation: slide-top 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) both;
|
||||||
}
|
}
|
||||||
.dialog-content[data-state='closed'] {
|
.dialog-content[data-state='closed'] {
|
||||||
animation: bounceDown 100ms ease-in forwards;
|
animation: bounceDown 100ms ease-in forwards;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": "../config/interface.tsconfig.json",
|
"extends": "../config/interface.tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist"
|
"outDir": "./dist"
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue