mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-18 20:09:10 +00:00
format interface package
This commit is contained in:
parent
50f761775f
commit
3eb81490ab
|
@ -1,74 +1,74 @@
|
|||
{
|
||||
"name": "@sd/interface",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./types": "./src/types"
|
||||
},
|
||||
"scripts": {
|
||||
"icons": "ts-node ./scripts/generateSvgImports.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.5.10",
|
||||
"@fontsource/inter": "^4.5.7",
|
||||
"@headlessui/react": "^1.5.0",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@radix-ui/react-dialog": "^0.1.7",
|
||||
"@radix-ui/react-dropdown-menu": "^0.1.6",
|
||||
"@radix-ui/react-icons": "^1.1.0",
|
||||
"@radix-ui/react-progress": "^0.1.4",
|
||||
"@radix-ui/react-slider": "^0.1.4",
|
||||
"@sd/client": "workspace:*",
|
||||
"@sd/core": "workspace:*",
|
||||
"@sd/ui": "workspace:*",
|
||||
"@vitejs/plugin-react": "^1.3.1",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"byte-size": "^8.1.0",
|
||||
"clsx": "^1.1.1",
|
||||
"immer": "^9.0.12",
|
||||
"jotai": "^1.6.2",
|
||||
"moment": "^2.29.2",
|
||||
"phosphor-react": "^1.4.1",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-dropzone": "^12.0.4",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-hotkeys-hook": "^3.4.4",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-loading-icons": "^1.0.8",
|
||||
"react-portal": "^4.2.2",
|
||||
"react-query": "^3.34.19",
|
||||
"react-router": "6.3.0",
|
||||
"react-router-dom": "6.3.0",
|
||||
"react-scrollbars-custom": "^4.0.27",
|
||||
"react-spline": "^1.2.1",
|
||||
"react-transition-group": "^4.4.2",
|
||||
"react-virtuoso": "^2.9.0",
|
||||
"rooks": "^5.11.0",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"zustand": "^3.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/babel-core": "^6.25.7",
|
||||
"@types/byte-size": "^8.1.0",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "^17.0.23",
|
||||
"@types/pretty-bytes": "^5.2.0",
|
||||
"@types/react": "^18.0.8",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-table": "^7.7.10",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/tailwindcss": "^3.0.10",
|
||||
"@vitejs/plugin-react": "^1.3.1",
|
||||
"concurrently": "^7.1.0",
|
||||
"prettier": "^2.6.2",
|
||||
"typescript": "^4.6.3",
|
||||
"vite": "^2.9.1",
|
||||
"vite-plugin-svgr": "^1.1.0"
|
||||
}
|
||||
"name": "@sd/interface",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./types": "./src/types"
|
||||
},
|
||||
"scripts": {
|
||||
"icons": "ts-node ./scripts/generateSvgImports.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.5.10",
|
||||
"@fontsource/inter": "^4.5.7",
|
||||
"@headlessui/react": "^1.5.0",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@radix-ui/react-dialog": "^0.1.7",
|
||||
"@radix-ui/react-dropdown-menu": "^0.1.6",
|
||||
"@radix-ui/react-icons": "^1.1.0",
|
||||
"@radix-ui/react-progress": "^0.1.4",
|
||||
"@radix-ui/react-slider": "^0.1.4",
|
||||
"@sd/client": "workspace:*",
|
||||
"@sd/core": "workspace:*",
|
||||
"@sd/ui": "workspace:*",
|
||||
"@vitejs/plugin-react": "^1.3.1",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"byte-size": "^8.1.0",
|
||||
"clsx": "^1.1.1",
|
||||
"immer": "^9.0.12",
|
||||
"jotai": "^1.6.2",
|
||||
"moment": "^2.29.2",
|
||||
"phosphor-react": "^1.4.1",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-dropzone": "^12.0.4",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-hotkeys-hook": "^3.4.4",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-loading-icons": "^1.0.8",
|
||||
"react-portal": "^4.2.2",
|
||||
"react-query": "^3.34.19",
|
||||
"react-router": "6.3.0",
|
||||
"react-router-dom": "6.3.0",
|
||||
"react-scrollbars-custom": "^4.0.27",
|
||||
"react-spline": "^1.2.1",
|
||||
"react-transition-group": "^4.4.2",
|
||||
"react-virtuoso": "^2.9.0",
|
||||
"rooks": "^5.11.0",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"zustand": "^3.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/babel-core": "^6.25.7",
|
||||
"@types/byte-size": "^8.1.0",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "^17.0.23",
|
||||
"@types/pretty-bytes": "^5.2.0",
|
||||
"@types/react": "^18.0.8",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-table": "^7.7.10",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/tailwindcss": "^3.0.10",
|
||||
"@vitejs/plugin-react": "^1.3.1",
|
||||
"concurrently": "^7.1.0",
|
||||
"prettier": "^2.6.2",
|
||||
"typescript": "^4.6.3",
|
||||
"vite": "^2.9.1",
|
||||
"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 { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import {
|
||||
Location,
|
||||
MemoryRouter,
|
||||
Outlet,
|
||||
Route,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate
|
||||
Location,
|
||||
MemoryRouter,
|
||||
Outlet,
|
||||
Route,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate
|
||||
} from 'react-router-dom';
|
||||
import { Sidebar } from './components/file/Sidebar';
|
||||
import { Modal } from './components/layout/Modal';
|
||||
|
@ -42,190 +42,190 @@ export const AppPropsContext = React.createContext<AppProps | null>(null);
|
|||
export type Platform = 'browser' | 'macOS' | 'windows' | 'linux';
|
||||
|
||||
export interface AppProps {
|
||||
transport: BaseTransport;
|
||||
platform: Platform;
|
||||
convertFileSrc: (url: string) => string;
|
||||
openDialog: (options: { directory?: boolean }) => Promise<string | string[]>;
|
||||
onClose?: () => void;
|
||||
onMinimize?: () => void;
|
||||
onFullscreen?: () => void;
|
||||
onOpen?: (path: string) => void;
|
||||
isFocused?: boolean;
|
||||
useMemoryRouter: boolean;
|
||||
demoMode?: boolean;
|
||||
transport: BaseTransport;
|
||||
platform: Platform;
|
||||
convertFileSrc: (url: string) => string;
|
||||
openDialog: (options: { directory?: boolean }) => Promise<string | string[]>;
|
||||
onClose?: () => void;
|
||||
onMinimize?: () => void;
|
||||
onFullscreen?: () => void;
|
||||
onOpen?: (path: string) => void;
|
||||
isFocused?: boolean;
|
||||
useMemoryRouter: boolean;
|
||||
demoMode?: boolean;
|
||||
}
|
||||
|
||||
function AppLayout() {
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
const [isWindowRounded, setIsWindowRounded] = useState(false);
|
||||
const [hasWindowBorder, setHasWindowBorder] = useState(true);
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
const [isWindowRounded, setIsWindowRounded] = useState(false);
|
||||
const [hasWindowBorder, setHasWindowBorder] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (appPropsContext?.platform === 'macOS') {
|
||||
setIsWindowRounded(true);
|
||||
}
|
||||
if (appPropsContext?.platform === 'browser') {
|
||||
setHasWindowBorder(false);
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (appPropsContext?.platform === 'macOS') {
|
||||
setIsWindowRounded(true);
|
||||
}
|
||||
if (appPropsContext?.platform === 'browser') {
|
||||
setHasWindowBorder(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'flex flex-row h-screen overflow-hidden text-gray-900 bg-white select-none dark:text-white dark:bg-gray-650',
|
||||
isWindowRounded && 'rounded-xl',
|
||||
hasWindowBorder && 'border border-gray-200 dark:border-gray-500'
|
||||
)}
|
||||
>
|
||||
<Sidebar />
|
||||
<div className="flex flex-col w-full min-h-full">
|
||||
{/* <TopBar /> */}
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'flex flex-row h-screen overflow-hidden text-gray-900 bg-white select-none dark:text-white dark:bg-gray-650',
|
||||
isWindowRounded && 'rounded-xl',
|
||||
hasWindowBorder && 'border border-gray-200 dark:border-gray-500'
|
||||
)}
|
||||
>
|
||||
<Sidebar />
|
||||
<div className="flex flex-col w-full min-h-full">
|
||||
{/* <TopBar /> */}
|
||||
|
||||
<div className="relative flex w-full">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="relative flex w-full">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsRoutes({ modal = false }) {
|
||||
return (
|
||||
<SlideUp>
|
||||
<Routes>
|
||||
<Route
|
||||
path={modal ? '/settings' : '/'}
|
||||
element={modal ? <Modal children={<SettingsScreen />} /> : <SettingsScreen />}
|
||||
>
|
||||
<Route index element={<GeneralSettings />} />
|
||||
<Route path="general" element={<GeneralSettings />} />
|
||||
<Route path="security" element={<SecuritySettings />} />
|
||||
<Route path="appearance" element={<></>} />
|
||||
<Route path="experimental" element={<ExperimentalSettings />} />
|
||||
<Route path="locations" element={<LocationSettings />} />
|
||||
<Route path="library" element={<LibrarySettings />} />
|
||||
<Route path="media" element={<></>} />
|
||||
<Route path="keys" element={<></>} />
|
||||
<Route path="tags" element={<></>} />
|
||||
<Route path="sync" element={<></>} />
|
||||
<Route path="contacts" element={<></>} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</SlideUp>
|
||||
);
|
||||
return (
|
||||
<SlideUp>
|
||||
<Routes>
|
||||
<Route
|
||||
path={modal ? '/settings' : '/'}
|
||||
element={modal ? <Modal children={<SettingsScreen />} /> : <SettingsScreen />}
|
||||
>
|
||||
<Route index element={<GeneralSettings />} />
|
||||
<Route path="general" element={<GeneralSettings />} />
|
||||
<Route path="security" element={<SecuritySettings />} />
|
||||
<Route path="appearance" element={<></>} />
|
||||
<Route path="experimental" element={<ExperimentalSettings />} />
|
||||
<Route path="locations" element={<LocationSettings />} />
|
||||
<Route path="library" element={<LibrarySettings />} />
|
||||
<Route path="media" element={<></>} />
|
||||
<Route path="keys" element={<></>} />
|
||||
<Route path="tags" element={<></>} />
|
||||
<Route path="sync" element={<></>} />
|
||||
<Route path="contacts" element={<></>} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</SlideUp>
|
||||
);
|
||||
}
|
||||
|
||||
function Router() {
|
||||
let location = useLocation();
|
||||
let state = location.state as { backgroundLocation?: Location };
|
||||
let location = useLocation();
|
||||
let state = location.state as { backgroundLocation?: Location };
|
||||
|
||||
useEffect(() => {
|
||||
console.log({ url: location.pathname });
|
||||
}, [state]);
|
||||
useEffect(() => {
|
||||
console.log({ url: location.pathname });
|
||||
}, [state]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Routes location={state?.backgroundLocation || location}>
|
||||
<Route path="/" element={<AppLayout />}>
|
||||
<Route index element={<RedirectPage to="/overview" />} />
|
||||
<Route path="overview" element={<OverviewScreen />} />
|
||||
<Route path="content" element={<ContentScreen />} />
|
||||
<Route path="debug" element={<DebugScreen />} />
|
||||
<Route path="settings/*" element={<SettingsRoutes />} />
|
||||
<Route path="explorer/:id" element={<ExplorerScreen />} />
|
||||
<Route path="tag/:id" element={<TagScreen />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
{state?.backgroundLocation && <SettingsRoutes modal />}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Routes location={state?.backgroundLocation || location}>
|
||||
<Route path="/" element={<AppLayout />}>
|
||||
<Route index element={<RedirectPage to="/overview" />} />
|
||||
<Route path="overview" element={<OverviewScreen />} />
|
||||
<Route path="content" element={<ContentScreen />} />
|
||||
<Route path="debug" element={<DebugScreen />} />
|
||||
<Route path="settings/*" element={<SettingsRoutes />} />
|
||||
<Route path="explorer/:id" element={<ExplorerScreen />} />
|
||||
<Route path="tag/:id" element={<TagScreen />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
{state?.backgroundLocation && <SettingsRoutes modal />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
<pre className="m-2">Error: {error.message}</pre>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Button variant="primary" className="mt-2" onClick={resetErrorBoundary}>
|
||||
Reload
|
||||
</Button>
|
||||
<Button className="mt-2" onClick={resetErrorBoundary}>
|
||||
Send report
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
<pre className="m-2">Error: {error.message}</pre>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Button variant="primary" className="mt-2" onClick={resetErrorBoundary}>
|
||||
Reload
|
||||
</Button>
|
||||
<Button className="mt-2" onClick={resetErrorBoundary}>
|
||||
Send report
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NotFound() {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
role="alert"
|
||||
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>
|
||||
<h1 className="text-4xl font-bold">You chose nothingness.</h1>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Button variant="primary" className="mt-4" onClick={() => navigate(-1)}>
|
||||
Go Back
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
role="alert"
|
||||
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>
|
||||
<h1 className="text-4xl font-bold">You chose nothingness.</h1>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Button variant="primary" className="mt-4" onClick={() => navigate(-1)}>
|
||||
Go Back
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MemoryRouterContainer() {
|
||||
useCoreEvents();
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Router />
|
||||
</MemoryRouter>
|
||||
);
|
||||
useCoreEvents();
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Router />
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
function BrowserRouterContainer() {
|
||||
useCoreEvents();
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Router />
|
||||
</MemoryRouter>
|
||||
);
|
||||
useCoreEvents();
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Router />
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export function bindCoreEvent() {}
|
||||
|
||||
export default function App(props: AppProps) {
|
||||
// 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'.
|
||||
// Refer to <ClientProvider /> for where this is used.
|
||||
window.ReactQueryClient ??= queryClient;
|
||||
// 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'.
|
||||
// Refer to <ClientProvider /> for where this is used.
|
||||
window.ReactQueryClient ??= queryClient;
|
||||
|
||||
setTransport(props.transport);
|
||||
setTransport(props.transport);
|
||||
|
||||
console.log('App props', props);
|
||||
console.log('App props', props);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* @ts-ignore */}
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}>
|
||||
{/* @ts-ignore */}
|
||||
<QueryClientProvider client={queryClient} contextSharing={false}>
|
||||
<AppPropsContext.Provider value={Object.assign({ isFocused: true }, props)}>
|
||||
<ClientProvider>
|
||||
{props.useMemoryRouter ? <MemoryRouterContainer /> : <BrowserRouterContainer />}
|
||||
</ClientProvider>
|
||||
</AppPropsContext.Provider>
|
||||
</QueryClientProvider>
|
||||
</ErrorBoundary>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{/* @ts-ignore */}
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}>
|
||||
{/* @ts-ignore */}
|
||||
<QueryClientProvider client={queryClient} contextSharing={false}>
|
||||
<AppPropsContext.Provider value={Object.assign({ isFocused: true }, props)}>
|
||||
<ClientProvider>
|
||||
{props.useMemoryRouter ? <MemoryRouterContainer /> : <BrowserRouterContainer />}
|
||||
</ClientProvider>
|
||||
</AppPropsContext.Provider>
|
||||
</QueryClientProvider>
|
||||
</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 Fontotf } from './fontotf.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 Fontwoff } from './fontwoff.svg';
|
||||
import { ReactComponent as Git } from './git.svg';
|
||||
import { ReactComponent as Go } from './go.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';
|
||||
|
||||
export default {
|
||||
ai: Ai,
|
||||
angular: Angular,
|
||||
audio: Audio,
|
||||
audiomp3: Audiomp3,
|
||||
audioogg: Audioogg,
|
||||
audiowav: Audiowav,
|
||||
babel: Babel,
|
||||
bat: Bat,
|
||||
bicep: Bicep,
|
||||
binary: Binary,
|
||||
blade: Blade,
|
||||
browserslist: Browserslist,
|
||||
bsconfig: Bsconfig,
|
||||
bundler: Bundler,
|
||||
c: C,
|
||||
cert: Cert,
|
||||
cheader: Cheader,
|
||||
cli: Cli,
|
||||
compodoc: Compodoc,
|
||||
composer: Composer,
|
||||
conf: Conf,
|
||||
cpp: Cpp,
|
||||
csharp: Csharp,
|
||||
cshtml: Cshtml,
|
||||
css: Css,
|
||||
cssmap: Cssmap,
|
||||
csv: Csv,
|
||||
dartlang: Dartlang,
|
||||
docker: Docker,
|
||||
dockerdebug: Dockerdebug,
|
||||
dockerignore: Dockerignore,
|
||||
editorconfig: Editorconfig,
|
||||
eex: Eex,
|
||||
elixir: Elixir,
|
||||
elm: Elm,
|
||||
env: Env,
|
||||
erb: Erb,
|
||||
erlang: Erlang,
|
||||
eslint: Eslint,
|
||||
exs: Exs,
|
||||
exx: Exx,
|
||||
file: File,
|
||||
folder: Folder,
|
||||
folder_light: Folder_light,
|
||||
folder_open: Folder_open,
|
||||
fontotf: Fontotf,
|
||||
fontttf: Fontttf,
|
||||
fontwoff: Fontwoff,
|
||||
fontwoff2: Fontwoff2,
|
||||
git: Git,
|
||||
go: Go,
|
||||
gopackage: Gopackage,
|
||||
gradle: Gradle,
|
||||
graphql: Graphql,
|
||||
groovy: Groovy,
|
||||
grunt: Grunt,
|
||||
gulp: Gulp,
|
||||
haml: Haml,
|
||||
handlebars: Handlebars,
|
||||
haskell: Haskell,
|
||||
html: Html,
|
||||
image: Image,
|
||||
imagegif: Imagegif,
|
||||
imageico: Imageico,
|
||||
imagejpg: Imagejpg,
|
||||
imagepng: Imagepng,
|
||||
imagewebp: Imagewebp,
|
||||
info: Info,
|
||||
ipynb: Ipynb,
|
||||
java: Java,
|
||||
jenkins: Jenkins,
|
||||
jest: Jest,
|
||||
jinja: Jinja,
|
||||
js: Js,
|
||||
jsmap: Jsmap,
|
||||
json: Json,
|
||||
jsp: Jsp,
|
||||
julia: Julia,
|
||||
karma: Karma,
|
||||
key: Key,
|
||||
less: Less,
|
||||
license: License,
|
||||
lighteditorconfig: Lighteditorconfig,
|
||||
liquid: Liquid,
|
||||
llvm: Llvm,
|
||||
log: Log,
|
||||
lua: Lua,
|
||||
m: M,
|
||||
markdown: Markdown,
|
||||
mint: Mint,
|
||||
mov: Mov,
|
||||
mp4: Mp4,
|
||||
nestjs: Nestjs,
|
||||
nestjscontroller: Nestjscontroller,
|
||||
nestjsdecorator: Nestjsdecorator,
|
||||
nestjsfilter: Nestjsfilter,
|
||||
nestjsguard: Nestjsguard,
|
||||
nestjsmodule: Nestjsmodule,
|
||||
nestjsservice: Nestjsservice,
|
||||
netlify: Netlify,
|
||||
nginx: Nginx,
|
||||
nim: Nim,
|
||||
njk: Njk,
|
||||
nodemon: Nodemon,
|
||||
npm: Npm,
|
||||
npmlock: Npmlock,
|
||||
nuxt: Nuxt,
|
||||
nvm: Nvm,
|
||||
opengl: Opengl,
|
||||
pdf: Pdf,
|
||||
photoshop: Photoshop,
|
||||
php: Php,
|
||||
postcssconfig: Postcssconfig,
|
||||
powershell: Powershell,
|
||||
powershelldata: Powershelldata,
|
||||
powershellmodule: Powershellmodule,
|
||||
prettier: Prettier,
|
||||
prisma: Prisma,
|
||||
prolog: Prolog,
|
||||
pug: Pug,
|
||||
python: Python,
|
||||
qt: Qt,
|
||||
razor: Razor,
|
||||
reactjs: Reactjs,
|
||||
reactts: Reactts,
|
||||
readme: Readme,
|
||||
rescript: Rescript,
|
||||
rjson: Rjson,
|
||||
robots: Robots,
|
||||
rollup: Rollup,
|
||||
ruby: Ruby,
|
||||
rust: Rust,
|
||||
sass: Sass,
|
||||
scss: Scss,
|
||||
shell: Shell,
|
||||
smarty: Smarty,
|
||||
sol: Sol,
|
||||
sql: Sql,
|
||||
storybook: Storybook,
|
||||
stylelint: Stylelint,
|
||||
stylus: Stylus,
|
||||
svelte: Svelte,
|
||||
svg: Svg,
|
||||
swift: Swift,
|
||||
symfony: Symfony,
|
||||
tailwind: Tailwind,
|
||||
testjs: Testjs,
|
||||
testts: Testts,
|
||||
tmpl: Tmpl,
|
||||
toml: Toml,
|
||||
travis: Travis,
|
||||
tsconfig: Tsconfig,
|
||||
tsx: Tsx,
|
||||
twig: Twig,
|
||||
txt: Txt,
|
||||
typescript: Typescript,
|
||||
typescriptdef: Typescriptdef,
|
||||
ui: Ui,
|
||||
user: User,
|
||||
vercel: Vercel,
|
||||
video: Video,
|
||||
vite: Vite,
|
||||
vscode: Vscode,
|
||||
vue: Vue,
|
||||
wasm: Wasm,
|
||||
webpack: Webpack,
|
||||
windi: Windi,
|
||||
xml: Xml,
|
||||
yaml: Yaml,
|
||||
yarn: Yarn,
|
||||
yarnerror: Yarnerror,
|
||||
zip: Zip
|
||||
ai: Ai,
|
||||
angular: Angular,
|
||||
audio: Audio,
|
||||
audiomp3: Audiomp3,
|
||||
audioogg: Audioogg,
|
||||
audiowav: Audiowav,
|
||||
babel: Babel,
|
||||
bat: Bat,
|
||||
bicep: Bicep,
|
||||
binary: Binary,
|
||||
blade: Blade,
|
||||
browserslist: Browserslist,
|
||||
bsconfig: Bsconfig,
|
||||
bundler: Bundler,
|
||||
c: C,
|
||||
cert: Cert,
|
||||
cheader: Cheader,
|
||||
cli: Cli,
|
||||
compodoc: Compodoc,
|
||||
composer: Composer,
|
||||
conf: Conf,
|
||||
cpp: Cpp,
|
||||
csharp: Csharp,
|
||||
cshtml: Cshtml,
|
||||
css: Css,
|
||||
cssmap: Cssmap,
|
||||
csv: Csv,
|
||||
dartlang: Dartlang,
|
||||
docker: Docker,
|
||||
dockerdebug: Dockerdebug,
|
||||
dockerignore: Dockerignore,
|
||||
editorconfig: Editorconfig,
|
||||
eex: Eex,
|
||||
elixir: Elixir,
|
||||
elm: Elm,
|
||||
env: Env,
|
||||
erb: Erb,
|
||||
erlang: Erlang,
|
||||
eslint: Eslint,
|
||||
exs: Exs,
|
||||
exx: Exx,
|
||||
file: File,
|
||||
folder: Folder,
|
||||
folder_light: Folder_light,
|
||||
folder_open: Folder_open,
|
||||
fontotf: Fontotf,
|
||||
fontttf: Fontttf,
|
||||
fontwoff: Fontwoff,
|
||||
fontwoff2: Fontwoff2,
|
||||
git: Git,
|
||||
go: Go,
|
||||
gopackage: Gopackage,
|
||||
gradle: Gradle,
|
||||
graphql: Graphql,
|
||||
groovy: Groovy,
|
||||
grunt: Grunt,
|
||||
gulp: Gulp,
|
||||
haml: Haml,
|
||||
handlebars: Handlebars,
|
||||
haskell: Haskell,
|
||||
html: Html,
|
||||
image: Image,
|
||||
imagegif: Imagegif,
|
||||
imageico: Imageico,
|
||||
imagejpg: Imagejpg,
|
||||
imagepng: Imagepng,
|
||||
imagewebp: Imagewebp,
|
||||
info: Info,
|
||||
ipynb: Ipynb,
|
||||
java: Java,
|
||||
jenkins: Jenkins,
|
||||
jest: Jest,
|
||||
jinja: Jinja,
|
||||
js: Js,
|
||||
jsmap: Jsmap,
|
||||
json: Json,
|
||||
jsp: Jsp,
|
||||
julia: Julia,
|
||||
karma: Karma,
|
||||
key: Key,
|
||||
less: Less,
|
||||
license: License,
|
||||
lighteditorconfig: Lighteditorconfig,
|
||||
liquid: Liquid,
|
||||
llvm: Llvm,
|
||||
log: Log,
|
||||
lua: Lua,
|
||||
m: M,
|
||||
markdown: Markdown,
|
||||
mint: Mint,
|
||||
mov: Mov,
|
||||
mp4: Mp4,
|
||||
nestjs: Nestjs,
|
||||
nestjscontroller: Nestjscontroller,
|
||||
nestjsdecorator: Nestjsdecorator,
|
||||
nestjsfilter: Nestjsfilter,
|
||||
nestjsguard: Nestjsguard,
|
||||
nestjsmodule: Nestjsmodule,
|
||||
nestjsservice: Nestjsservice,
|
||||
netlify: Netlify,
|
||||
nginx: Nginx,
|
||||
nim: Nim,
|
||||
njk: Njk,
|
||||
nodemon: Nodemon,
|
||||
npm: Npm,
|
||||
npmlock: Npmlock,
|
||||
nuxt: Nuxt,
|
||||
nvm: Nvm,
|
||||
opengl: Opengl,
|
||||
pdf: Pdf,
|
||||
photoshop: Photoshop,
|
||||
php: Php,
|
||||
postcssconfig: Postcssconfig,
|
||||
powershell: Powershell,
|
||||
powershelldata: Powershelldata,
|
||||
powershellmodule: Powershellmodule,
|
||||
prettier: Prettier,
|
||||
prisma: Prisma,
|
||||
prolog: Prolog,
|
||||
pug: Pug,
|
||||
python: Python,
|
||||
qt: Qt,
|
||||
razor: Razor,
|
||||
reactjs: Reactjs,
|
||||
reactts: Reactts,
|
||||
readme: Readme,
|
||||
rescript: Rescript,
|
||||
rjson: Rjson,
|
||||
robots: Robots,
|
||||
rollup: Rollup,
|
||||
ruby: Ruby,
|
||||
rust: Rust,
|
||||
sass: Sass,
|
||||
scss: Scss,
|
||||
shell: Shell,
|
||||
smarty: Smarty,
|
||||
sol: Sol,
|
||||
sql: Sql,
|
||||
storybook: Storybook,
|
||||
stylelint: Stylelint,
|
||||
stylus: Stylus,
|
||||
svelte: Svelte,
|
||||
svg: Svg,
|
||||
swift: Swift,
|
||||
symfony: Symfony,
|
||||
tailwind: Tailwind,
|
||||
testjs: Testjs,
|
||||
testts: Testts,
|
||||
tmpl: Tmpl,
|
||||
toml: Toml,
|
||||
travis: Travis,
|
||||
tsconfig: Tsconfig,
|
||||
tsx: Tsx,
|
||||
twig: Twig,
|
||||
txt: Txt,
|
||||
typescript: Typescript,
|
||||
typescriptdef: Typescriptdef,
|
||||
ui: Ui,
|
||||
user: User,
|
||||
vercel: Vercel,
|
||||
video: Video,
|
||||
vite: Vite,
|
||||
vscode: Vscode,
|
||||
vue: Vue,
|
||||
wasm: Wasm,
|
||||
webpack: Webpack,
|
||||
windi: Windi,
|
||||
xml: Xml,
|
||||
yaml: Yaml,
|
||||
yarn: Yarn,
|
||||
yarnerror: Yarnerror,
|
||||
zip: Zip
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,112 +1,113 @@
|
|||
import { KeyIcon } from '@heroicons/react/outline';
|
||||
import { CogIcon, LockClosedIcon } from '@heroicons/react/solid';
|
||||
import { Button } from '@sd/ui';
|
||||
import {
|
||||
Cloud,
|
||||
Desktop,
|
||||
DeviceMobileCamera,
|
||||
DotsSixVertical,
|
||||
Laptop,
|
||||
Phone,
|
||||
PhoneX
|
||||
Cloud,
|
||||
Desktop,
|
||||
DeviceMobileCamera,
|
||||
DotsSixVertical,
|
||||
Laptop,
|
||||
Phone,
|
||||
PhoneX
|
||||
} from 'phosphor-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 FileItem from '../file/FileItem';
|
||||
import ProgressBar from '../primitive/ProgressBar';
|
||||
|
||||
export interface DeviceProps {
|
||||
name: string;
|
||||
size: string;
|
||||
type: 'laptop' | 'desktop' | 'phone' | 'server';
|
||||
locations: { name: string }[];
|
||||
runningJob?: { amount: number; task: string };
|
||||
removeThisSoon?: boolean;
|
||||
name: string;
|
||||
size: string;
|
||||
type: 'laptop' | 'desktop' | 'phone' | 'server';
|
||||
locations: { name: string }[];
|
||||
runningJob?: { amount: number; task: string };
|
||||
removeThisSoon?: boolean;
|
||||
}
|
||||
|
||||
export function Device(props: DeviceProps) {
|
||||
const [selectedFile, setSelectedFile] = useState<null | string>(null);
|
||||
const [selectedFile, setSelectedFile] = useState<null | string>(null);
|
||||
|
||||
function handleSelect(key: string) {
|
||||
if (selectedFile === key) setSelectedFile(null);
|
||||
else setSelectedFile(key);
|
||||
}
|
||||
return (
|
||||
<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">
|
||||
<DotsSixVertical weight="bold" className="mr-3 opacity-30" />
|
||||
{props.type === 'phone' && <DeviceMobileCamera 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 === 'server' && <Cloud weight="fill" size={20} className="mr-2" />}
|
||||
<h3 className="font-semibold text-md">{props.name}</h3>
|
||||
<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">
|
||||
<LockClosedIcon className="w-3 h-3 mr-1 -ml-0.5 m-[1px]" />
|
||||
P2P
|
||||
</span>
|
||||
</div>
|
||||
<span className="font-semibold py-0.5 px-1.5 text-sm ml-2 text-gray-400 ">
|
||||
{props.size}
|
||||
</span>
|
||||
<div className="flex flex-grow" />
|
||||
{props.runningJob && (
|
||||
<div className="flex flex-row ml-5 bg-opacity-50 rounded-md bg-gray-550 ">
|
||||
<Rings
|
||||
stroke="#2599FF"
|
||||
strokeOpacity={4}
|
||||
strokeWidth={10}
|
||||
speed={0.5}
|
||||
className="ml-0.5 mt-[2px] -mr-1 w-7 h-7"
|
||||
/>
|
||||
<div className="flex flex-col p-2">
|
||||
<span className="mb-[2px] -mt-1 truncate text-gray-450 text-tiny">
|
||||
{props.runningJob.task}...
|
||||
</span>
|
||||
<ProgressBar value={props.runningJob?.amount} total={100} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-row ml-3 space-x-1">
|
||||
<Button className="!p-1 ">
|
||||
<KeyIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
<Button className="!p-1 ">
|
||||
<CogIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* <hr className="border-gray-700" />
|
||||
function handleSelect(key: string) {
|
||||
if (selectedFile === key) setSelectedFile(null);
|
||||
else setSelectedFile(key);
|
||||
}
|
||||
return (
|
||||
<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">
|
||||
<DotsSixVertical weight="bold" className="mr-3 opacity-30" />
|
||||
{props.type === 'phone' && <DeviceMobileCamera 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 === 'server' && <Cloud weight="fill" size={20} className="mr-2" />}
|
||||
<h3 className="font-semibold text-md">{props.name}</h3>
|
||||
<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">
|
||||
<LockClosedIcon className="w-3 h-3 mr-1 -ml-0.5 m-[1px]" />
|
||||
P2P
|
||||
</span>
|
||||
</div>
|
||||
<span className="font-semibold py-0.5 px-1.5 text-sm ml-2 text-gray-400 ">
|
||||
{props.size}
|
||||
</span>
|
||||
<div className="flex flex-grow" />
|
||||
{props.runningJob && (
|
||||
<div className="flex flex-row ml-5 bg-opacity-50 rounded-md bg-gray-550 ">
|
||||
<Rings
|
||||
stroke="#2599FF"
|
||||
strokeOpacity={4}
|
||||
strokeWidth={10}
|
||||
speed={0.5}
|
||||
className="ml-0.5 mt-[2px] -mr-1 w-7 h-7"
|
||||
/>
|
||||
<div className="flex flex-col p-2">
|
||||
<span className="mb-[2px] -mt-1 truncate text-gray-450 text-tiny">
|
||||
{props.runningJob.task}...
|
||||
</span>
|
||||
<ProgressBar value={props.runningJob?.amount} total={100} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-row ml-3 space-x-1">
|
||||
<Button className="!p-1 ">
|
||||
<KeyIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
<Button className="!p-1 ">
|
||||
<CogIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* <hr className="border-gray-700" />
|
||||
<hr className="border-gray-550" /> */}
|
||||
<div className="px-4 pb-3 mt-3">
|
||||
{props.locations.map((location, key) => (
|
||||
<FileItem
|
||||
key={key}
|
||||
selected={selectedFile == location.name}
|
||||
onClick={() => handleSelect(location.name)}
|
||||
fileName={location.name}
|
||||
folder
|
||||
/>
|
||||
))}
|
||||
{props.removeThisSoon && (
|
||||
<>
|
||||
<FileItem
|
||||
selected={selectedFile == 'tsx'}
|
||||
onClick={() => handleSelect('tsx')}
|
||||
fileName="App.tsx"
|
||||
format="tsx"
|
||||
iconName="reactts"
|
||||
/>
|
||||
<FileItem
|
||||
selected={selectedFile == 'vite'}
|
||||
onClick={() => handleSelect('vite')}
|
||||
fileName="vite.config.js"
|
||||
format="vite"
|
||||
iconName="vite"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="px-4 pb-3 mt-3">
|
||||
{props.locations.map((location, key) => (
|
||||
<FileItem
|
||||
key={key}
|
||||
selected={selectedFile == location.name}
|
||||
onClick={() => handleSelect(location.name)}
|
||||
fileName={location.name}
|
||||
folder
|
||||
/>
|
||||
))}
|
||||
{props.removeThisSoon && (
|
||||
<>
|
||||
<FileItem
|
||||
selected={selectedFile == 'tsx'}
|
||||
onClick={() => handleSelect('tsx')}
|
||||
fileName="App.tsx"
|
||||
format="tsx"
|
||||
iconName="reactts"
|
||||
/>
|
||||
<FileItem
|
||||
selected={selectedFile == 'vite'}
|
||||
onClick={() => handleSelect('vite')}
|
||||
fileName="vite.config.js"
|
||||
format="vite"
|
||||
iconName="vite"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import create from 'zustand';
|
||||
|
||||
export const useStore = create((set) => ({
|
||||
experimental: false
|
||||
experimental: false
|
||||
}));
|
||||
|
|
|
@ -1,103 +1,103 @@
|
|||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import icons from '../../assets/icons';
|
||||
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
|
||||
import { DefaultProps } from '../primitive/types';
|
||||
|
||||
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
|
||||
import icons from '../../assets/icons';
|
||||
|
||||
interface Props extends DefaultProps {
|
||||
fileName: string;
|
||||
iconName?: string;
|
||||
format?: string;
|
||||
folder?: boolean;
|
||||
selected?: boolean;
|
||||
onClick?: () => void;
|
||||
fileName: string;
|
||||
iconName?: string;
|
||||
format?: string;
|
||||
folder?: boolean;
|
||||
selected?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export default function FileItem(props: Props) {
|
||||
// const Shadow = () => {
|
||||
// return (
|
||||
// <div
|
||||
// className={clsx(
|
||||
// 'absolute opacity-100 transition-opacity duration-200 top-auto bottom-auto w-[64px] h-[40px] shadow-xl shadow-red-500',
|
||||
// { 'opacity-100': props.selected }
|
||||
// )}
|
||||
// />
|
||||
// );
|
||||
// };
|
||||
return (
|
||||
<div onClick={props.onClick} className="inline-block w-[100px] mb-3" draggable>
|
||||
<div
|
||||
className={clsx(
|
||||
'border-2 border-transparent rounded-lg text-center w-[100px] h-[100px] mb-1',
|
||||
{
|
||||
'bg-gray-50 dark:bg-gray-650': props.selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.folder ? (
|
||||
<div className="flex items-center justify-center w-full h-full active:translate-y-[1px]">
|
||||
<div className="w-[70px]">
|
||||
<Folder className="" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={clsx(
|
||||
'w-[64px] mt-1.5 m-auto transition duration-200 rounded-lg h-[90px] relative active:translate-y-[1px]',
|
||||
{
|
||||
'': props.selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
<svg
|
||||
className="absolute top-0 left-0 pointer-events-none fill-gray-150 dark:fill-gray-550"
|
||||
width="65"
|
||||
height="85"
|
||||
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" />
|
||||
</svg>
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
className="absolute top-1 -right-[1px] z-10 fill-gray-50 dark:fill-gray-500 pointer-events-none"
|
||||
viewBox="0 0 41 41"
|
||||
>
|
||||
<path d="M41.4116 40.5577H11.234C5.02962 40.5577 0 35.5281 0 29.3238V0L41.4116 40.5577Z" />
|
||||
</svg>
|
||||
<div className="absolute flex flex-col items-center justify-center w-full h-full">
|
||||
{/* @ts-ignore */}
|
||||
{props.iconName && icons[props.iconName] ? (
|
||||
(() => {
|
||||
// @ts-ignore
|
||||
let Icon = icons[props.iconName];
|
||||
return (
|
||||
<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">
|
||||
{props.format}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<span
|
||||
className={clsx(
|
||||
'px-1.5 py-[1px] rounded-md text-sm font-medium text-gray-300 cursor-default',
|
||||
{
|
||||
'bg-primary !text-white': props.selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.fileName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// const Shadow = () => {
|
||||
// return (
|
||||
// <div
|
||||
// className={clsx(
|
||||
// 'absolute opacity-100 transition-opacity duration-200 top-auto bottom-auto w-[64px] h-[40px] shadow-xl shadow-red-500',
|
||||
// { 'opacity-100': props.selected }
|
||||
// )}
|
||||
// />
|
||||
// );
|
||||
// };
|
||||
return (
|
||||
<div onClick={props.onClick} className="inline-block w-[100px] mb-3" draggable>
|
||||
<div
|
||||
className={clsx(
|
||||
'border-2 border-transparent rounded-lg text-center w-[100px] h-[100px] mb-1',
|
||||
{
|
||||
'bg-gray-50 dark:bg-gray-650': props.selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.folder ? (
|
||||
<div className="flex items-center justify-center w-full h-full active:translate-y-[1px]">
|
||||
<div className="w-[70px]">
|
||||
<Folder className="" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={clsx(
|
||||
'w-[64px] mt-1.5 m-auto transition duration-200 rounded-lg h-[90px] relative active:translate-y-[1px]',
|
||||
{
|
||||
'': props.selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
<svg
|
||||
className="absolute top-0 left-0 pointer-events-none fill-gray-150 dark:fill-gray-550"
|
||||
width="65"
|
||||
height="85"
|
||||
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" />
|
||||
</svg>
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
className="absolute top-1 -right-[1px] z-10 fill-gray-50 dark:fill-gray-500 pointer-events-none"
|
||||
viewBox="0 0 41 41"
|
||||
>
|
||||
<path d="M41.4116 40.5577H11.234C5.02962 40.5577 0 35.5281 0 29.3238V0L41.4116 40.5577Z" />
|
||||
</svg>
|
||||
<div className="absolute flex flex-col items-center justify-center w-full h-full">
|
||||
{/* @ts-ignore */}
|
||||
{props.iconName && icons[props.iconName] ? (
|
||||
(() => {
|
||||
// @ts-ignore
|
||||
let Icon = icons[props.iconName];
|
||||
return (
|
||||
<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">
|
||||
{props.format}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<span
|
||||
className={clsx(
|
||||
'px-1.5 py-[1px] rounded-md text-sm font-medium text-gray-300 cursor-default',
|
||||
{
|
||||
'bg-primary !text-white': props.selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.fileName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,236 +4,250 @@ import { FilePath } from '@sd/core';
|
|||
import clsx from 'clsx';
|
||||
import byteSize from 'pretty-bytes';
|
||||
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 { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
|
||||
import { useKey, useWindowSize } from 'rooks';
|
||||
import create from 'zustand';
|
||||
|
||||
import { AppPropsContext } from '../../App';
|
||||
import FileThumb from './FileThumb';
|
||||
|
||||
type ExplorerState = {
|
||||
selectedRowIndex: number;
|
||||
setSelectedRowIndex: (index: number) => void;
|
||||
locationId: number;
|
||||
setLocationId: (index: number) => void;
|
||||
newThumbnails: Record<string, boolean>;
|
||||
addNewThumbnail: (cas_id: string) => void;
|
||||
selectedRowIndex: number;
|
||||
setSelectedRowIndex: (index: number) => void;
|
||||
locationId: number;
|
||||
setLocationId: (index: number) => void;
|
||||
newThumbnails: Record<string, boolean>;
|
||||
addNewThumbnail: (cas_id: string) => void;
|
||||
};
|
||||
|
||||
export const useExplorerState = create<ExplorerState>((set) => ({
|
||||
selectedRowIndex: 1,
|
||||
setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index })),
|
||||
locationId: -1,
|
||||
setLocationId: (id: number) => set((state) => ({ ...state, locationId: id })),
|
||||
newThumbnails: {},
|
||||
addNewThumbnail: (cas_id: string) =>
|
||||
set((state) => ({ ...state, newThumbnails: { ...state.newThumbnails, [cas_id]: true } }))
|
||||
selectedRowIndex: 1,
|
||||
setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index })),
|
||||
locationId: -1,
|
||||
setLocationId: (id: number) => set((state) => ({ ...state, locationId: id })),
|
||||
newThumbnails: {},
|
||||
addNewThumbnail: (cas_id: string) =>
|
||||
set((state) => ({
|
||||
...state,
|
||||
newThumbnails: { ...state.newThumbnails, [cas_id]: true }
|
||||
}))
|
||||
}));
|
||||
|
||||
interface IColumn {
|
||||
column: string;
|
||||
key: string;
|
||||
width: number;
|
||||
column: string;
|
||||
key: string;
|
||||
width: number;
|
||||
}
|
||||
|
||||
const PADDING_SIZE = 130;
|
||||
|
||||
// Function ensure no types are loss, but guarantees that they are Column[]
|
||||
function ensureIsColumns<T extends IColumn[]>(data: T) {
|
||||
return data;
|
||||
return data;
|
||||
}
|
||||
|
||||
const columns = ensureIsColumns([
|
||||
{ column: 'Name', key: 'name', width: 280 } as const,
|
||||
// { column: 'Size', key: 'size_in_bytes', width: 120 } as const,
|
||||
{ column: 'Type', key: 'extension', width: 100 } as const
|
||||
{ column: 'Name', key: 'name', width: 280 } as const,
|
||||
// { column: 'Size', key: 'size_in_bytes', width: 120 } as const,
|
||||
{ column: 'Type', key: 'extension', width: 100 } as const
|
||||
]);
|
||||
|
||||
type ColumnKey = typeof columns[number]['key'];
|
||||
|
||||
const LocationContext = React.createContext<{ location_id: number; data_path: string }>({
|
||||
location_id: 1,
|
||||
data_path: ''
|
||||
const LocationContext = React.createContext<{
|
||||
location_id: number;
|
||||
data_path: string;
|
||||
}>({
|
||||
location_id: 1,
|
||||
data_path: ''
|
||||
});
|
||||
|
||||
export const FileList: React.FC<{ location_id: number; path: string; limit: number }> = (props) => {
|
||||
const size = useWindowSize();
|
||||
const tableContainer = useRef<null | HTMLDivElement>(null);
|
||||
const VList = useRef<null | VirtuosoHandle>(null);
|
||||
export const FileList: React.FC<{
|
||||
location_id: number;
|
||||
path: string;
|
||||
limit: number;
|
||||
}> = (props) => {
|
||||
const size = useWindowSize();
|
||||
const tableContainer = useRef<null | HTMLDivElement>(null);
|
||||
const VList = useRef<null | VirtuosoHandle>(null);
|
||||
|
||||
const { data: client } = useBridgeQuery('ClientGetState', undefined, {
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
const { data: client } = useBridgeQuery('ClientGetState', undefined, {
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
const path = props.path;
|
||||
const path = props.path;
|
||||
|
||||
const { selectedRowIndex, setSelectedRowIndex, setLocationId } = useExplorerState();
|
||||
const [goingUp, setGoingUp] = useState(false);
|
||||
const { selectedRowIndex, setSelectedRowIndex, setLocationId } = useExplorerState();
|
||||
const [goingUp, setGoingUp] = useState(false);
|
||||
|
||||
const { data: currentDir } = useBridgeQuery('LibGetExplorerDir', {
|
||||
location_id: props.location_id,
|
||||
path,
|
||||
limit: props.limit
|
||||
});
|
||||
const { data: currentDir } = useBridgeQuery('LibGetExplorerDir', {
|
||||
location_id: props.location_id,
|
||||
path,
|
||||
limit: props.limit
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRowIndex === 0 && goingUp) {
|
||||
VList.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
if (selectedRowIndex != -1) {
|
||||
VList.current?.scrollIntoView({
|
||||
index: goingUp ? selectedRowIndex - 1 : selectedRowIndex
|
||||
});
|
||||
}
|
||||
}, [selectedRowIndex]);
|
||||
useEffect(() => {
|
||||
if (selectedRowIndex === 0 && goingUp) {
|
||||
VList.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
if (selectedRowIndex != -1) {
|
||||
VList.current?.scrollIntoView({
|
||||
index: goingUp ? selectedRowIndex - 1 : selectedRowIndex
|
||||
});
|
||||
}
|
||||
}, [selectedRowIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
setLocationId(props.location_id);
|
||||
}, [props.location_id]);
|
||||
useEffect(() => {
|
||||
setLocationId(props.location_id);
|
||||
}, [props.location_id]);
|
||||
|
||||
useKey('ArrowUp', (e) => {
|
||||
e.preventDefault();
|
||||
setGoingUp(true);
|
||||
if (selectedRowIndex != -1 && selectedRowIndex !== 0) setSelectedRowIndex(selectedRowIndex - 1);
|
||||
});
|
||||
useKey('ArrowUp', (e) => {
|
||||
e.preventDefault();
|
||||
setGoingUp(true);
|
||||
if (selectedRowIndex != -1 && selectedRowIndex !== 0) setSelectedRowIndex(selectedRowIndex - 1);
|
||||
});
|
||||
|
||||
useKey('ArrowDown', (e) => {
|
||||
e.preventDefault();
|
||||
setGoingUp(false);
|
||||
if (selectedRowIndex != -1 && selectedRowIndex !== (currentDir?.contents.length ?? 1) - 1)
|
||||
setSelectedRowIndex(selectedRowIndex + 1);
|
||||
});
|
||||
useKey('ArrowDown', (e) => {
|
||||
e.preventDefault();
|
||||
setGoingUp(false);
|
||||
if (selectedRowIndex != -1 && selectedRowIndex !== (currentDir?.contents.length ?? 1) - 1)
|
||||
setSelectedRowIndex(selectedRowIndex + 1);
|
||||
});
|
||||
|
||||
const Row = (index: number) => {
|
||||
const row = currentDir?.contents?.[index];
|
||||
const Row = (index: number) => {
|
||||
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 = () => (
|
||||
<div>
|
||||
<h1 className="pt-20 pl-4 text-xl font-bold ">{currentDir?.directory.name}</h1>
|
||||
<div className="table-head">
|
||||
<div className="flex flex-row p-2 table-head-row">
|
||||
{columns.map((col) => (
|
||||
<div
|
||||
key={col.key}
|
||||
className="relative flex flex-row items-center pl-2 table-head-cell group"
|
||||
style={{ width: col.width }}
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const Header = () => (
|
||||
<div>
|
||||
<h1 className="pt-20 pl-4 text-xl font-bold ">{currentDir?.directory.name}</h1>
|
||||
<div className="table-head">
|
||||
<div className="flex flex-row p-2 table-head-row">
|
||||
{columns.map((col) => (
|
||||
<div
|
||||
key={col.key}
|
||||
className="relative flex flex-row items-center pl-2 table-head-cell group"
|
||||
style={{ width: col.width }}
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => (
|
||||
<div
|
||||
ref={tableContainer}
|
||||
style={{ marginTop: -44 }}
|
||||
className="w-full pl-2 bg-white cursor-default table-container dark:bg-gray-650"
|
||||
>
|
||||
<LocationContext.Provider
|
||||
value={{ location_id: props.location_id, data_path: client?.data_path as string }}
|
||||
>
|
||||
<Virtuoso
|
||||
data={currentDir?.contents}
|
||||
ref={VList}
|
||||
style={{ height: size.innerHeight ?? 600 }}
|
||||
totalCount={currentDir?.contents.length || 0}
|
||||
itemContent={Row}
|
||||
components={{ Header, Footer: () => <div className="w-full " /> }}
|
||||
increaseViewportBy={{ top: 400, bottom: 200 }}
|
||||
className="outline-none explorer-scroll"
|
||||
/>
|
||||
</LocationContext.Provider>
|
||||
</div>
|
||||
),
|
||||
[props.location_id, size.innerWidth, currentDir?.directory.id, tableContainer.current]
|
||||
);
|
||||
return useMemo(
|
||||
() => (
|
||||
<div
|
||||
ref={tableContainer}
|
||||
style={{ marginTop: -44 }}
|
||||
className="w-full pl-2 bg-white cursor-default table-container dark:bg-gray-650"
|
||||
>
|
||||
<LocationContext.Provider
|
||||
value={{
|
||||
location_id: props.location_id,
|
||||
data_path: client?.data_path as string
|
||||
}}
|
||||
>
|
||||
<Virtuoso
|
||||
data={currentDir?.contents}
|
||||
ref={VList}
|
||||
style={{ height: size.innerHeight ?? 600 }}
|
||||
totalCount={currentDir?.contents.length || 0}
|
||||
itemContent={Row}
|
||||
components={{ Header, Footer: () => <div className="w-full " /> }}
|
||||
increaseViewportBy={{ top: 400, bottom: 200 }}
|
||||
className="outline-none explorer-scroll"
|
||||
/>
|
||||
</LocationContext.Provider>
|
||||
</div>
|
||||
),
|
||||
[props.location_id, size.innerWidth, currentDir?.directory.id, tableContainer.current]
|
||||
);
|
||||
};
|
||||
|
||||
const RenderRow: React.FC<{
|
||||
row: FilePath;
|
||||
rowIndex: number;
|
||||
dirId: number;
|
||||
row: FilePath;
|
||||
rowIndex: number;
|
||||
dirId: number;
|
||||
}> = ({ row, rowIndex, dirId }) => {
|
||||
const { selectedRowIndex, setSelectedRowIndex } = useExplorerState();
|
||||
const isActive = selectedRowIndex === rowIndex;
|
||||
const { selectedRowIndex, setSelectedRowIndex } = useExplorerState();
|
||||
const isActive = selectedRowIndex === rowIndex;
|
||||
|
||||
let [_, setSearchParams] = useSearchParams();
|
||||
let [_, setSearchParams] = useSearchParams();
|
||||
|
||||
function selectFileHandler() {
|
||||
if (selectedRowIndex == rowIndex) setSelectedRowIndex(-1);
|
||||
else setSelectedRowIndex(rowIndex);
|
||||
}
|
||||
function selectFileHandler() {
|
||||
if (selectedRowIndex == rowIndex) setSelectedRowIndex(-1);
|
||||
else setSelectedRowIndex(rowIndex);
|
||||
}
|
||||
|
||||
return useMemo(
|
||||
() => (
|
||||
<div
|
||||
onClick={selectFileHandler}
|
||||
onDoubleClick={() => {
|
||||
if (row.is_dir) {
|
||||
setSearchParams({ path: row.materialized_path });
|
||||
}
|
||||
}}
|
||||
className={clsx(
|
||||
'table-body-row mr-2 flex flex-row rounded-lg border-2',
|
||||
isActive ? 'border-primary-500' : 'border-transparent',
|
||||
rowIndex % 2 == 0 && 'bg-[#00000006] dark:bg-[#00000030]'
|
||||
)}
|
||||
>
|
||||
{columns.map((col) => (
|
||||
<div
|
||||
key={col.key}
|
||||
className="flex items-center px-4 py-2 pr-2 table-body-cell"
|
||||
style={{ width: col.width }}
|
||||
>
|
||||
<RenderCell file={row} dirId={dirId} colKey={col?.key} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
[row.id, isActive]
|
||||
);
|
||||
return useMemo(
|
||||
() => (
|
||||
<div
|
||||
onClick={selectFileHandler}
|
||||
onDoubleClick={() => {
|
||||
if (row.is_dir) {
|
||||
setSearchParams({ path: row.materialized_path });
|
||||
}
|
||||
}}
|
||||
className={clsx(
|
||||
'table-body-row mr-2 flex flex-row rounded-lg border-2',
|
||||
isActive ? 'border-primary-500' : 'border-transparent',
|
||||
rowIndex % 2 == 0 && 'bg-[#00000006] dark:bg-[#00000030]'
|
||||
)}
|
||||
>
|
||||
{columns.map((col) => (
|
||||
<div
|
||||
key={col.key}
|
||||
className="flex items-center px-4 py-2 pr-2 table-body-cell"
|
||||
style={{ width: col.width }}
|
||||
>
|
||||
<RenderCell file={row} dirId={dirId} colKey={col?.key} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
[row.id, isActive]
|
||||
);
|
||||
};
|
||||
|
||||
const RenderCell: React.FC<{ colKey?: ColumnKey; dirId?: number; file?: FilePath }> = ({
|
||||
colKey,
|
||||
file,
|
||||
dirId
|
||||
}) => {
|
||||
if (!file || !colKey || !dirId) return <></>;
|
||||
const row = file;
|
||||
if (!row) return <></>;
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
const RenderCell: React.FC<{
|
||||
colKey?: ColumnKey;
|
||||
dirId?: number;
|
||||
file?: FilePath;
|
||||
}> = ({ colKey, file, dirId }) => {
|
||||
if (!file || !colKey || !dirId) return <></>;
|
||||
const row = file;
|
||||
if (!row) return <></>;
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
|
||||
const value = row[colKey];
|
||||
if (!value) return <></>;
|
||||
const value = row[colKey];
|
||||
if (!value) return <></>;
|
||||
|
||||
const location = useContext(LocationContext);
|
||||
const { newThumbnails } = useExplorerState();
|
||||
const location = useContext(LocationContext);
|
||||
const { newThumbnails } = useExplorerState();
|
||||
|
||||
const hasNewThumbnail = !!newThumbnails[row?.temp_cas_id ?? ''];
|
||||
const hasNewThumbnail = !!newThumbnails[row?.temp_cas_id ?? ''];
|
||||
|
||||
switch (colKey) {
|
||||
case 'name':
|
||||
return (
|
||||
<div className="flex flex-row items-center overflow-hidden">
|
||||
<div className="flex items-center justify-center shrink-0 w-6 h-6 mr-3">
|
||||
<FileThumb
|
||||
hasThumbnailOverride={hasNewThumbnail}
|
||||
file={row}
|
||||
locationId={location.location_id}
|
||||
/>
|
||||
</div>
|
||||
{/* {colKey == 'name' &&
|
||||
switch (colKey) {
|
||||
case 'name':
|
||||
return (
|
||||
<div className="flex flex-row items-center overflow-hidden">
|
||||
<div className="flex items-center justify-center shrink-0 w-6 h-6 mr-3">
|
||||
<FileThumb
|
||||
hasThumbnailOverride={hasNewThumbnail}
|
||||
file={row}
|
||||
locationId={location.location_id}
|
||||
/>
|
||||
</div>
|
||||
{/* {colKey == 'name' &&
|
||||
(() => {
|
||||
switch (row.extension.toLowerCase()) {
|
||||
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" />;
|
||||
}
|
||||
})()} */}
|
||||
<span className="text-xs truncate">{row[colKey]}</span>
|
||||
</div>
|
||||
);
|
||||
// case 'size_in_bytes':
|
||||
// return <span className="text-xs text-left">{byteSize(Number(value || 0))}</span>;
|
||||
case 'extension':
|
||||
return <span className="text-xs text-left">{value}</span>;
|
||||
// case 'meta_integrity_hash':
|
||||
// return <span className="truncate">{value}</span>;
|
||||
// case 'tags':
|
||||
// return renderCellWithIcon(MusicNoteIcon);
|
||||
<span className="text-xs truncate">{row[colKey]}</span>
|
||||
</div>
|
||||
);
|
||||
// case 'size_in_bytes':
|
||||
// return <span className="text-xs text-left">{byteSize(Number(value || 0))}</span>;
|
||||
case 'extension':
|
||||
return <span className="text-xs text-left">{value}</span>;
|
||||
// case 'meta_integrity_hash':
|
||||
// return <span className="truncate">{value}</span>;
|
||||
// case 'tags':
|
||||
// return renderCellWithIcon(MusicNoteIcon);
|
||||
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,37 +2,38 @@ import { useBridgeQuery } from '@sd/client';
|
|||
import { FilePath } from '@sd/core';
|
||||
import clsx from 'clsx';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { AppPropsContext } from '../../App';
|
||||
import icons from '../../assets/icons';
|
||||
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
|
||||
|
||||
export default function FileThumb(props: {
|
||||
file: FilePath;
|
||||
locationId: number;
|
||||
hasThumbnailOverride: boolean;
|
||||
className?: string;
|
||||
file: FilePath;
|
||||
locationId: number;
|
||||
hasThumbnailOverride: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
const { data: client } = useBridgeQuery('ClientGetState');
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
const { data: client } = useBridgeQuery('ClientGetState');
|
||||
|
||||
if (props.file.is_dir) {
|
||||
return <Folder className="max-w-[170px]" />;
|
||||
}
|
||||
if (props.file.is_dir) {
|
||||
return <Folder className="max-w-[170px]" />;
|
||||
}
|
||||
|
||||
if (client?.data_path && (props.file.has_local_thumbnail || props.hasThumbnailOverride)) {
|
||||
return (
|
||||
<img
|
||||
className="pointer-events-none z-90"
|
||||
src={appPropsContext?.convertFileSrc(
|
||||
`${client.data_path}/thumbnails/${props.locationId}/${props.file.temp_cas_id}.webp`
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (client?.data_path && (props.file.has_local_thumbnail || props.hasThumbnailOverride)) {
|
||||
return (
|
||||
<img
|
||||
className="pointer-events-none z-90"
|
||||
src={appPropsContext?.convertFileSrc(
|
||||
`${client.data_path}/thumbnails/${props.locationId}/${props.file.temp_cas_id}.webp`
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (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 <div></div>;
|
||||
if (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 <div></div>;
|
||||
}
|
||||
|
|
|
@ -1,121 +1,122 @@
|
|||
import React from '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 { Heart, Link } from 'phosphor-react';
|
||||
import { useExplorerState } from './FileList';
|
||||
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 { Input, TextArea } from '../primitive';
|
||||
import { useExplorerState } from './FileList';
|
||||
import FileThumb from './FileThumb';
|
||||
|
||||
interface MetaItemProps {
|
||||
title: string;
|
||||
value: string | React.ReactNode;
|
||||
title: string;
|
||||
value: string | React.ReactNode;
|
||||
}
|
||||
|
||||
const MetaItem = (props: MetaItemProps) => {
|
||||
return (
|
||||
<div className="flex flex-col px-3 py-1 meta-item">
|
||||
<h5 className="text-xs font-bold">{props.title}</h5>
|
||||
{typeof props.value === 'string' ? (
|
||||
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">{props.value}</p>
|
||||
) : (
|
||||
props.value
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col px-3 py-1 meta-item">
|
||||
<h5 className="text-xs font-bold">{props.title}</h5>
|
||||
{typeof props.value === 'string' ? (
|
||||
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">{props.value}</p>
|
||||
) : (
|
||||
props.value
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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 }) => {
|
||||
// const { selectedRowIndex } = useExplorerState();
|
||||
// const isOpen = !!props.selectedFile;
|
||||
// const { selectedRowIndex } = useExplorerState();
|
||||
// const isOpen = !!props.selectedFile;
|
||||
|
||||
const file = props.selectedFile;
|
||||
const file = props.selectedFile;
|
||||
|
||||
return (
|
||||
<Transition
|
||||
show={true}
|
||||
enter="transition-translate ease-in-out duration-200"
|
||||
enterFrom="translate-x-64"
|
||||
enterTo="translate-x-0"
|
||||
leave="transition-translate ease-in-out duration-200"
|
||||
leaveFrom="translate-x-0"
|
||||
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 ">
|
||||
{!!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 items-center justify-center w-full h-64 overflow-hidden rounded-t-lg bg-gray-50 dark:bg-gray-900">
|
||||
<FileThumb
|
||||
hasThumbnailOverride={false}
|
||||
className="!m-0 flex flex-shrink flex-grow-0"
|
||||
file={file}
|
||||
locationId={props.locationId}
|
||||
/>
|
||||
</div>
|
||||
<h3 className="pt-3 pl-3 text-base font-bold">{file?.name}</h3>
|
||||
<div className="flex flex-row m-3 space-x-2">
|
||||
<Button size="sm" noPadding>
|
||||
<Heart className="w-[18px] h-[18px]" />
|
||||
</Button>
|
||||
<Button size="sm" noPadding>
|
||||
<ShareIcon className="w-[18px] h-[18px]" />
|
||||
</Button>
|
||||
<Button size="sm" noPadding>
|
||||
<Link className="w-[18px] h-[18px]" />
|
||||
</Button>
|
||||
</div>
|
||||
{file?.temp_cas_id && (
|
||||
<MetaItem title="Unique Content ID" value={file.temp_cas_id as string} />
|
||||
)}
|
||||
<Divider />
|
||||
<MetaItem title="Uri" value={file?.materialized_path as string} />
|
||||
<Divider />
|
||||
<MetaItem
|
||||
title="Date Created"
|
||||
value={moment(file?.date_created).format('MMMM Do YYYY, h:mm:ss a')}
|
||||
/>
|
||||
<Divider />
|
||||
<MetaItem
|
||||
title="Date Indexed"
|
||||
value={moment(file?.date_indexed).format('MMMM Do YYYY, h:mm:ss a')}
|
||||
/>
|
||||
<Divider />
|
||||
{!file?.is_dir && (
|
||||
<>
|
||||
<div className="flex flex-row items-center px-3 py-2 meta-item">
|
||||
{file?.extension && (
|
||||
<span className="inline px-1 mr-1 text-xs font-bold uppercase bg-gray-500 rounded-md text-gray-150">
|
||||
{file?.extension}
|
||||
</span>
|
||||
)}
|
||||
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">
|
||||
{file?.extension
|
||||
? //@ts-ignore
|
||||
types[file.extension.toUpperCase()]?.descriptions.join(' / ')
|
||||
: 'Unknown'}
|
||||
</p>
|
||||
</div>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<MetaItem
|
||||
title="Comment"
|
||||
value={<TextArea className="mt-2 text-xs leading-snug !py-2" />}
|
||||
/>
|
||||
return (
|
||||
<Transition
|
||||
show={true}
|
||||
enter="transition-translate ease-in-out duration-200"
|
||||
enterFrom="translate-x-64"
|
||||
enterTo="translate-x-0"
|
||||
leave="transition-translate ease-in-out duration-200"
|
||||
leaveFrom="translate-x-0"
|
||||
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 ">
|
||||
{!!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 items-center justify-center w-full h-64 overflow-hidden rounded-t-lg bg-gray-50 dark:bg-gray-900">
|
||||
<FileThumb
|
||||
hasThumbnailOverride={false}
|
||||
className="!m-0 flex flex-shrink flex-grow-0"
|
||||
file={file}
|
||||
locationId={props.locationId}
|
||||
/>
|
||||
</div>
|
||||
<h3 className="pt-3 pl-3 text-base font-bold">{file?.name}</h3>
|
||||
<div className="flex flex-row m-3 space-x-2">
|
||||
<Button size="sm" noPadding>
|
||||
<Heart className="w-[18px] h-[18px]" />
|
||||
</Button>
|
||||
<Button size="sm" noPadding>
|
||||
<ShareIcon className="w-[18px] h-[18px]" />
|
||||
</Button>
|
||||
<Button size="sm" noPadding>
|
||||
<Link className="w-[18px] h-[18px]" />
|
||||
</Button>
|
||||
</div>
|
||||
{file?.temp_cas_id && (
|
||||
<MetaItem title="Unique Content ID" value={file.temp_cas_id as string} />
|
||||
)}
|
||||
<Divider />
|
||||
<MetaItem title="Uri" value={file?.materialized_path as string} />
|
||||
<Divider />
|
||||
<MetaItem
|
||||
title="Date Created"
|
||||
value={moment(file?.date_created).format('MMMM Do YYYY, h:mm:ss a')}
|
||||
/>
|
||||
<Divider />
|
||||
<MetaItem
|
||||
title="Date Indexed"
|
||||
value={moment(file?.date_indexed).format('MMMM Do YYYY, h:mm:ss a')}
|
||||
/>
|
||||
<Divider />
|
||||
{!file?.is_dir && (
|
||||
<>
|
||||
<div className="flex flex-row items-center px-3 py-2 meta-item">
|
||||
{file?.extension && (
|
||||
<span className="inline px-1 mr-1 text-xs font-bold uppercase bg-gray-500 rounded-md text-gray-150">
|
||||
{file?.extension}
|
||||
</span>
|
||||
)}
|
||||
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">
|
||||
{file?.extension
|
||||
? //@ts-ignore
|
||||
types[file.extension.toUpperCase()]?.descriptions.join(' / ')
|
||||
: 'Unknown'}
|
||||
</p>
|
||||
</div>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<MetaItem
|
||||
title="Comment"
|
||||
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>
|
||||
</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} /> */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,97 +1,101 @@
|
|||
import { LockClosedIcon } from '@heroicons/react/outline';
|
||||
import { CogIcon, EyeOffIcon, PlusIcon, ServerIcon } from '@heroicons/react/solid';
|
||||
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||
import { Button, Dropdown } from '@sd/ui';
|
||||
import clsx from 'clsx';
|
||||
import { 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 RunningJobsWidget from '../jobs/RunningJobsWidget';
|
||||
import { AppPropsContext } from '../../App';
|
||||
|
||||
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 Folder } from '../../assets/svg/folder.svg';
|
||||
import { useStore } from '../device/Stores';
|
||||
import RunningJobsWidget from '../jobs/RunningJobsWidget';
|
||||
import { MacTrafficLights } from '../os/TrafficLights';
|
||||
import { DefaultProps } from '../primitive/types';
|
||||
|
||||
interface SidebarProps extends DefaultProps {}
|
||||
|
||||
export const SidebarLink = (props: NavLinkProps & { children: React.ReactNode }) => (
|
||||
<NavLink {...props}>
|
||||
{({ isActive }) => (
|
||||
<span
|
||||
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',
|
||||
{ '!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive },
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
<NavLink {...props}>
|
||||
{({ isActive }) => (
|
||||
<span
|
||||
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',
|
||||
{
|
||||
'!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive
|
||||
},
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
);
|
||||
|
||||
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 }) => (
|
||||
<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 const MacOSTrafficLightsSpace: React.FC<{ children?: React.ReactNode }> = (props) => {
|
||||
const { children } = props;
|
||||
export const MacWindowControlsSpace: React.FC<{
|
||||
children?: React.ReactNode;
|
||||
}> = (props) => {
|
||||
const { children } = props;
|
||||
|
||||
return (
|
||||
<div data-tauri-drag-region className="h-7">
|
||||
<div className="mt-2 mb-1 -ml-1 ">{children}</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div data-tauri-drag-region className="h-7">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function MacOSTrafficLights() {
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
export function MacWindowControls() {
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
|
||||
return (
|
||||
<MacOSTrafficLightsSpace>
|
||||
<TrafficLights
|
||||
onClose={appPropsContext?.onClose}
|
||||
onFullscreen={appPropsContext?.onFullscreen}
|
||||
onMinimize={appPropsContext?.onMinimize}
|
||||
className="p-1.5 z-50 absolute"
|
||||
/>
|
||||
</MacOSTrafficLightsSpace>
|
||||
);
|
||||
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) => {
|
||||
const experimental = useStore((state) => state.experimental);
|
||||
const experimental = useStore((state) => state.experimental);
|
||||
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
const { data: locations } = useBridgeQuery('SysGetLocations');
|
||||
const { data: clientState } = useBridgeQuery('ClientGetState');
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
const { data: locations } = useBridgeQuery('SysGetLocations');
|
||||
const { data: clientState } = useBridgeQuery('ClientGetState');
|
||||
|
||||
const { mutate: createLocation } = useBridgeCommand('LocCreate');
|
||||
const { mutate: createLocation } = useBridgeCommand('LocCreate');
|
||||
|
||||
const tags = [
|
||||
{ id: 1, name: 'Keepsafe', color: '#FF6788' },
|
||||
{ id: 2, name: 'OBS', color: '#BF88FF' },
|
||||
{ id: 3, name: 'BlackMagic', color: '#F0C94A' },
|
||||
{ id: 4, name: 'Camera Roll', color: '#00F0DB' },
|
||||
{ id: 5, name: 'Spacedrive', color: '#00F079' }
|
||||
];
|
||||
const tags = [
|
||||
{ id: 1, name: 'Keepsafe', color: '#FF6788' },
|
||||
{ id: 2, name: 'OBS', color: '#BF88FF' },
|
||||
{ id: 3, name: 'BlackMagic', color: '#F0C94A' },
|
||||
{ id: 4, name: 'Camera Roll', color: '#00F0DB' },
|
||||
{ id: 5, name: 'Spacedrive', color: '#00F079' }
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-grow-0 flex-shrink-0 w-48 min-h-full px-3 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 === 'macOS' ? <MacOSTrafficLightsSpace /> : null}
|
||||
return (
|
||||
<div className="flex flex-col flex-grow-0 flex-shrink-0 w-48 min-h-full px-3 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' ? <MacWindowControls /> : null}
|
||||
{appPropsContext?.platform === 'macOS' ? <MacWindowControlsSpace /> : null}
|
||||
|
||||
<Dropdown
|
||||
buttonProps={{
|
||||
justifyLeft: true,
|
||||
className: `flex w-full text-left max-w-full mb-1 mt-1 -mr-0.5 shadow-xs rounded
|
||||
<Dropdown
|
||||
buttonProps={{
|
||||
justifyLeft: true,
|
||||
className: `flex w-full text-left max-w-full mb-1 mt-1 -mr-0.5 shadow-xs rounded
|
||||
!bg-gray-50
|
||||
border-gray-150
|
||||
hover:!bg-gray-1000
|
||||
|
@ -101,123 +105,123 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
|
|||
|
||||
dark:!border-gray-550
|
||||
dark:hover:!border-gray-500`,
|
||||
variant: 'gray'
|
||||
}}
|
||||
// buttonIcon={<Book weight="bold" className="w-4 h-4 mt-0.5 mr-1" />}
|
||||
buttonText={clientState?.client_name || 'Loading...'}
|
||||
items={[
|
||||
[{ name: clientState?.client_name || '', selected: true }, { name: 'Private Library' }],
|
||||
[
|
||||
{ name: 'Library Settings', icon: CogIcon },
|
||||
{ name: 'Add Library', icon: PlusIcon },
|
||||
{ name: 'Lock', icon: LockClosedIcon },
|
||||
{ name: 'Hide', icon: EyeOffIcon }
|
||||
]
|
||||
]}
|
||||
/>
|
||||
variant: 'gray'
|
||||
}}
|
||||
// buttonIcon={<Book weight="bold" className="w-4 h-4 mt-0.5 mr-1" />}
|
||||
buttonText={clientState?.client_name || 'Loading...'}
|
||||
items={[
|
||||
[{ name: clientState?.client_name || '', selected: true }, { name: 'Private Library' }],
|
||||
[
|
||||
{ name: 'Library Settings', icon: CogIcon },
|
||||
{ name: 'Add Library', icon: PlusIcon },
|
||||
{ name: 'Lock', icon: LockClosedIcon },
|
||||
{ name: 'Hide', icon: EyeOffIcon }
|
||||
]
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="pt-1">
|
||||
<SidebarLink to="/overview">
|
||||
<Icon component={Planet} />
|
||||
Overview
|
||||
</SidebarLink>
|
||||
<SidebarLink to="content">
|
||||
<Icon component={CirclesFour} />
|
||||
Content
|
||||
</SidebarLink>
|
||||
<div className="pt-1">
|
||||
<SidebarLink to="/overview">
|
||||
<Icon component={Planet} />
|
||||
Overview
|
||||
</SidebarLink>
|
||||
<SidebarLink to="content">
|
||||
<Icon component={CirclesFour} />
|
||||
Content
|
||||
</SidebarLink>
|
||||
|
||||
{experimental ? (
|
||||
<SidebarLink to="debug">
|
||||
<Icon component={Code} />
|
||||
Debug
|
||||
</SidebarLink>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{experimental ? (
|
||||
<SidebarLink to="debug">
|
||||
<Icon component={Code} />
|
||||
Debug
|
||||
</SidebarLink>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{/* <SidebarLink to="explorer">
|
||||
{/* <SidebarLink to="explorer">
|
||||
<Icon component={MonitorPlay} />
|
||||
Explorer
|
||||
</SidebarLink> */}
|
||||
</div>
|
||||
<div>
|
||||
<Heading>Locations</Heading>
|
||||
{locations?.map((location, index) => {
|
||||
return (
|
||||
<div key={index} className="flex flex-row items-center">
|
||||
<NavLink
|
||||
className="'relative w-full group'"
|
||||
to={{
|
||||
pathname: `explorer/${location.id}`
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<span
|
||||
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',
|
||||
{
|
||||
'!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="w-[18px] mr-2 -mt-0.5">
|
||||
<FolderWhite className={clsx(!isActive && 'hidden')} />
|
||||
<Folder className={clsx(isActive && 'hidden')} />
|
||||
</div>
|
||||
{location.name}
|
||||
<div className="flex-grow" />
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<Heading>Locations</Heading>
|
||||
{locations?.map((location, index) => {
|
||||
return (
|
||||
<div key={index} className="flex flex-row items-center">
|
||||
<NavLink
|
||||
className="'relative w-full group'"
|
||||
to={{
|
||||
pathname: `explorer/${location.id}`
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<span
|
||||
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',
|
||||
{
|
||||
'!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="w-[18px] mr-2 -mt-0.5">
|
||||
<FolderWhite className={clsx(!isActive && 'hidden')} />
|
||||
<Folder className={clsx(isActive && 'hidden')} />
|
||||
</div>
|
||||
{location.name}
|
||||
<div className="flex-grow" />
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
appPropsContext?.openDialog({ directory: true }).then((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"
|
||||
>
|
||||
Add Location
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<Heading>Tags</Heading>
|
||||
<div className="mb-2">
|
||||
{tags.map((tag, index) => (
|
||||
<SidebarLink key={index} to={`tag/${tag.id}`} className="">
|
||||
<div
|
||||
className="w-[12px] h-[12px] rounded-full"
|
||||
style={{ backgroundColor: tag.color }}
|
||||
/>
|
||||
<span className="ml-2 text-sm">{tag.name}</span>
|
||||
</SidebarLink>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-grow" />
|
||||
<RunningJobsWidget />
|
||||
{/* <div className="flex w-full">
|
||||
<button
|
||||
onClick={() => {
|
||||
appPropsContext?.openDialog({ directory: true }).then((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"
|
||||
>
|
||||
Add Location
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<Heading>Tags</Heading>
|
||||
<div className="mb-2">
|
||||
{tags.map((tag, index) => (
|
||||
<SidebarLink key={index} to={`tag/${tag.id}`} className="">
|
||||
<div
|
||||
className="w-[12px] h-[12px] rounded-full"
|
||||
style={{ backgroundColor: tag.color }}
|
||||
/>
|
||||
<span className="ml-2 text-sm">{tag.name}</span>
|
||||
</SidebarLink>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-grow" />
|
||||
<RunningJobsWidget />
|
||||
{/* <div className="flex w-full">
|
||||
</div> */}
|
||||
<div className="mb-2">
|
||||
<NavLink to="/settings/general">
|
||||
{({ isActive }) => (
|
||||
<Button
|
||||
noPadding
|
||||
variant={isActive ? 'default' : 'default'}
|
||||
className={clsx(
|
||||
'px-[4px] mb-1'
|
||||
// isActive && '!bg-gray-550'
|
||||
)}
|
||||
>
|
||||
<CogIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
)}
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="mb-2">
|
||||
<NavLink to="/settings/general">
|
||||
{({ isActive }) => (
|
||||
<Button
|
||||
noPadding
|
||||
variant={isActive ? 'default' : 'default'}
|
||||
className={clsx(
|
||||
'px-[4px] mb-1'
|
||||
// isActive && '!bg-gray-550'
|
||||
)}
|
||||
>
|
||||
<CogIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
)}
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import { DefaultProps } from '../primitive/types';
|
||||
|
||||
export interface DriveListItemProps extends DefaultProps {
|
||||
name: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const DriveListItem: React.FC<DriveListItemProps> = (props) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'rounded px-1.5 py-1 text-xs font-medium inline-block cursor-default',
|
||||
props.className
|
||||
)}
|
||||
></div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'rounded px-1.5 py-1 text-xs font-medium inline-block cursor-default',
|
||||
props.className
|
||||
)}
|
||||
></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 { useBridgeQuery } from '@sd/client';
|
||||
import clsx from 'clsx';
|
||||
import React, { DetailedHTMLProps, HTMLAttributes } from 'react';
|
||||
|
||||
import ProgressBar from '../primitive/ProgressBar';
|
||||
|
||||
const MiddleTruncatedText = ({
|
||||
children,
|
||||
...props
|
||||
children,
|
||||
...props
|
||||
}: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>) => {
|
||||
const text = children?.toString() ?? '';
|
||||
const first = text.substring(0, text.length / 2);
|
||||
const last = text.substring(first.length);
|
||||
const text = children?.toString() ?? '';
|
||||
const first = text.substring(0, text.length / 2);
|
||||
const last = text.substring(first.length);
|
||||
|
||||
// Literally black magic
|
||||
const fontFaceScaleFactor = 1.61;
|
||||
const startWidth = fontFaceScaleFactor * 5;
|
||||
const endWidth = fontFaceScaleFactor * 4;
|
||||
// Literally black magic
|
||||
const fontFaceScaleFactor = 1.61;
|
||||
const startWidth = fontFaceScaleFactor * 5;
|
||||
const endWidth = fontFaceScaleFactor * 4;
|
||||
|
||||
return (
|
||||
<div className="whitespace-nowrap overflow-hidden w-full">
|
||||
<span
|
||||
{...props}
|
||||
style={{ maxWidth: `calc(100% - (1em * ${endWidth}))`, minWidth: startWidth }}
|
||||
className={clsx(
|
||||
props?.className,
|
||||
'text-ellipsis inline-block align-bottom whitespace-nowrap overflow-hidden'
|
||||
)}
|
||||
>
|
||||
{first}
|
||||
</span>
|
||||
<span
|
||||
{...props}
|
||||
style={{ maxWidth: `calc(100% - (1em * ${startWidth}))`, direction: 'rtl' }}
|
||||
className={clsx(
|
||||
props?.className,
|
||||
'inline-block align-bottom whitespace-nowrap overflow-hidden'
|
||||
)}
|
||||
>
|
||||
{last}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="whitespace-nowrap overflow-hidden w-full">
|
||||
<span
|
||||
{...props}
|
||||
style={{
|
||||
maxWidth: `calc(100% - (1em * ${endWidth}))`,
|
||||
minWidth: startWidth
|
||||
}}
|
||||
className={clsx(
|
||||
props?.className,
|
||||
'text-ellipsis inline-block align-bottom whitespace-nowrap overflow-hidden'
|
||||
)}
|
||||
>
|
||||
{first}
|
||||
</span>
|
||||
<span
|
||||
{...props}
|
||||
style={{
|
||||
maxWidth: `calc(100% - (1em * ${startWidth}))`,
|
||||
direction: 'rtl'
|
||||
}}
|
||||
className={clsx(
|
||||
props?.className,
|
||||
'inline-block align-bottom whitespace-nowrap overflow-hidden'
|
||||
)}
|
||||
>
|
||||
{last}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function RunningJobsWidget() {
|
||||
const { data: jobs } = useBridgeQuery('JobGetRunning');
|
||||
const { data: jobs } = useBridgeQuery('JobGetRunning');
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-4">
|
||||
{jobs?.map((job) => (
|
||||
<Transition
|
||||
show={true}
|
||||
enter="transition-translate ease-in-out duration-200"
|
||||
enterFrom="translate-y-24"
|
||||
enterTo="translate-y-0"
|
||||
leave="transition-translate ease-in-out duration-200"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="translate-y-24"
|
||||
>
|
||||
<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> */}
|
||||
<MiddleTruncatedText className="mb-1.5 text-gray-450 text-tiny">
|
||||
{job.message}
|
||||
</MiddleTruncatedText>
|
||||
<ProgressBar value={job.completed_task_count} total={job.task_count} />
|
||||
</div>
|
||||
</Transition>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col space-y-4">
|
||||
{jobs?.map((job) => (
|
||||
<Transition
|
||||
show={true}
|
||||
enter="transition-translate ease-in-out duration-200"
|
||||
enterFrom="translate-y-24"
|
||||
enterTo="translate-y-0"
|
||||
leave="transition-translate ease-in-out duration-200"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="translate-y-24"
|
||||
>
|
||||
<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> */}
|
||||
<MiddleTruncatedText className="mb-1.5 text-gray-450 text-tiny">
|
||||
{job.message}
|
||||
</MiddleTruncatedText>
|
||||
<ProgressBar value={job.completed_task_count} total={job.task_count} />
|
||||
</div>
|
||||
</Transition>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { Button } from '@sd/ui';
|
||||
import React, { ReactNode } from 'react';
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
|
||||
export interface DialogProps {
|
||||
trigger: ReactNode;
|
||||
ctaLabel?: string;
|
||||
ctaAction?: () => void;
|
||||
title?: string;
|
||||
description?: string;
|
||||
children: ReactNode;
|
||||
trigger: ReactNode;
|
||||
ctaLabel?: string;
|
||||
ctaAction?: () => void;
|
||||
title?: string;
|
||||
description?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export default function Dialog(props: DialogProps) {
|
||||
return (
|
||||
<DialogPrimitive.Root>
|
||||
<DialogPrimitive.Trigger asChild>{props.trigger}</DialogPrimitive.Trigger>
|
||||
<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.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">
|
||||
<DialogPrimitive.Title className="font-bold ">{props.title}</DialogPrimitive.Title>
|
||||
<DialogPrimitive.Description className="text-sm text-gray-300">
|
||||
{props.description}
|
||||
</DialogPrimitive.Description>
|
||||
{props.children}
|
||||
</div>
|
||||
<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>
|
||||
<Button size="sm" variant="gray">
|
||||
Close
|
||||
</Button>
|
||||
</DialogPrimitive.Close>
|
||||
<Button onClick={props.ctaAction} size="sm" variant="primary">
|
||||
{props.ctaLabel}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPrimitive.Overlay>
|
||||
</DialogPrimitive.Portal>
|
||||
</DialogPrimitive.Root>
|
||||
);
|
||||
return (
|
||||
<DialogPrimitive.Root>
|
||||
<DialogPrimitive.Trigger asChild>{props.trigger}</DialogPrimitive.Trigger>
|
||||
<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.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">
|
||||
<DialogPrimitive.Title className="font-bold ">{props.title}</DialogPrimitive.Title>
|
||||
<DialogPrimitive.Description className="text-sm text-gray-300">
|
||||
{props.description}
|
||||
</DialogPrimitive.Description>
|
||||
{props.children}
|
||||
</div>
|
||||
<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>
|
||||
<Button size="sm" variant="gray">
|
||||
Close
|
||||
</Button>
|
||||
</DialogPrimitive.Close>
|
||||
<Button onClick={props.ctaAction} size="sm" variant="primary">
|
||||
{props.ctaLabel}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPrimitive.Overlay>
|
||||
</DialogPrimitive.Portal>
|
||||
</DialogPrimitive.Root>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,61 +1,64 @@
|
|||
import { Transition } from '@headlessui/react';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { XIcon } from '@heroicons/react/solid';
|
||||
import { Button } from '@sd/ui';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { MacOSTrafficLights } from '../file/Sidebar';
|
||||
|
||||
import { MacWindowControls } from '../file/Sidebar';
|
||||
|
||||
export interface ModalProps {
|
||||
full?: boolean;
|
||||
children: React.ReactNode;
|
||||
full?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Modal: React.FC<ModalProps> = (props) => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div
|
||||
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
|
||||
enter="transition duration-150"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
onClick={() => navigate('/')}
|
||||
className="absolute top-0 left-0 w-screen h-screen bg-white -z-50 rounded-2xl dark:bg-gray-800 bg-opacity-90"
|
||||
/>
|
||||
</Transition>
|
||||
<Button
|
||||
onClick={() => navigate('/')}
|
||||
variant="gray"
|
||||
className="!px-1.5 absolute top-2 right-2"
|
||||
>
|
||||
<XIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
<Transition
|
||||
show
|
||||
className="flex flex-grow"
|
||||
appear
|
||||
enter="transition ease-in-out-back duration-200"
|
||||
enterFrom="opacity-0 translate-y-5"
|
||||
enterTo="opacity-100"
|
||||
leave="transition duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="z-30 flex flex-grow mx-auto bg-white rounded-lg shadow-2xl max-w-7xl dark:bg-gray-650 ">
|
||||
{props.children}
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div
|
||||
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
|
||||
enter="transition duration-150"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
onClick={() => navigate('/')}
|
||||
className="absolute top-0 left-0 w-screen h-screen bg-white -z-50 rounded-2xl dark:bg-gray-800 bg-opacity-90"
|
||||
/>
|
||||
</Transition>
|
||||
<Button
|
||||
onClick={() => navigate('/')}
|
||||
variant="gray"
|
||||
className="!px-1.5 absolute top-2 right-2"
|
||||
>
|
||||
<XIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
<Transition
|
||||
show
|
||||
className="flex flex-grow"
|
||||
appear
|
||||
enter="transition ease-in-out-back duration-200"
|
||||
enterFrom="opacity-0 translate-y-5"
|
||||
enterTo="opacity-100"
|
||||
leave="transition duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="z-30 flex flex-grow mx-auto bg-white rounded-lg shadow-2xl max-w-7xl dark:bg-gray-650 ">
|
||||
{props.children}
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,111 +1,112 @@
|
|||
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/outline';
|
||||
import { useBridgeCommand } from '@sd/client';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
ArrowsClockwise,
|
||||
Cloud,
|
||||
FolderPlus,
|
||||
IconProps,
|
||||
Key,
|
||||
Tag,
|
||||
TerminalWindow
|
||||
ArrowsClockwise,
|
||||
Cloud,
|
||||
FolderPlus,
|
||||
IconProps,
|
||||
Key,
|
||||
Tag,
|
||||
TerminalWindow
|
||||
} from 'phosphor-react';
|
||||
import React, { DetailedHTMLProps, HTMLAttributes } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useExplorerState } from '../file/FileList';
|
||||
import { Shortcut } from '../primitive/Shortcut';
|
||||
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 TopBarButtonProps
|
||||
extends DetailedHTMLProps<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
|
||||
icon: React.ComponentType<IconProps>;
|
||||
group?: boolean;
|
||||
active?: boolean;
|
||||
left?: boolean;
|
||||
right?: boolean;
|
||||
extends DetailedHTMLProps<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
|
||||
icon: React.ComponentType<IconProps>;
|
||||
group?: boolean;
|
||||
active?: boolean;
|
||||
left?: boolean;
|
||||
right?: boolean;
|
||||
}
|
||||
|
||||
const TopBarButton: React.FC<TopBarButtonProps> = ({ icon: Icon, ...props }) => {
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
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',
|
||||
{
|
||||
'rounded-r-none rounded-l-none': props.group && !props.left && !props.right,
|
||||
'rounded-r-none': props.group && props.left,
|
||||
'rounded-l-none': props.group && props.right,
|
||||
'dark:bg-gray-450 dark:hover:bg-gray-450 dark:active:bg-gray-450': props.active
|
||||
},
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
<Icon weight={'regular'} className="m-0.5 w-5 h-5 text-gray-450 dark:text-gray-150" />
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
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',
|
||||
{
|
||||
'rounded-r-none rounded-l-none': props.group && !props.left && !props.right,
|
||||
'rounded-r-none': props.group && props.left,
|
||||
'rounded-l-none': props.group && props.right,
|
||||
'dark:bg-gray-450 dark:hover:bg-gray-450 dark:active:bg-gray-450': props.active
|
||||
},
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
<Icon weight={'regular'} className="m-0.5 w-5 h-5 text-gray-450 dark:text-gray-150" />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export const TopBar: React.FC<TopBarProps> = (props) => {
|
||||
const { locationId } = useExplorerState();
|
||||
const { mutate: generateThumbsForLocation } = useBridgeCommand('GenerateThumbsForLocation', {
|
||||
onMutate: (data) => {
|
||||
console.log('GenerateThumbsForLocation', data);
|
||||
}
|
||||
});
|
||||
let navigate = useNavigate();
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
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"
|
||||
>
|
||||
<div className="flex">
|
||||
<TopBarButton icon={ChevronLeftIcon} onClick={() => navigate(-1)} />
|
||||
<TopBarButton icon={ChevronRightIcon} onClick={() => navigate(1)} />
|
||||
</div>
|
||||
{/* <div className="flex mx-8 space-x-[1px]">
|
||||
const { locationId } = useExplorerState();
|
||||
const { mutate: generateThumbsForLocation } = useBridgeCommand('GenerateThumbsForLocation', {
|
||||
onMutate: (data) => {
|
||||
console.log('GenerateThumbsForLocation', data);
|
||||
}
|
||||
});
|
||||
let navigate = useNavigate();
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
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"
|
||||
>
|
||||
<div className="flex">
|
||||
<TopBarButton icon={ChevronLeftIcon} onClick={() => navigate(-1)} />
|
||||
<TopBarButton icon={ChevronRightIcon} onClick={() => navigate(1)} />
|
||||
</div>
|
||||
{/* <div className="flex mx-8 space-x-[1px]">
|
||||
<TopBarButton active group left icon={List} />
|
||||
<TopBarButton group icon={Columns} />
|
||||
<TopBarButton group right icon={SquaresFour} />
|
||||
</div> */}
|
||||
<div data-tauri-drag-region className="flex flex-row justify-center flex-grow ">
|
||||
<div className="flex mx-8 space-x-2 pointer-events-auto">
|
||||
<TopBarButton icon={Tag} />
|
||||
<TopBarButton icon={FolderPlus} />
|
||||
<TopBarButton icon={TerminalWindow} />
|
||||
</div>
|
||||
<div className="relative flex h-7">
|
||||
<input
|
||||
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"
|
||||
/>
|
||||
<div className="space-x-1 absolute top-[2px] right-1">
|
||||
<Shortcut chars="⌘K" />
|
||||
{/* <Shortcut chars="S" /> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex mx-8 space-x-2">
|
||||
<TopBarButton icon={Key} />
|
||||
<TopBarButton icon={Cloud} />
|
||||
<TopBarButton
|
||||
icon={ArrowsClockwise}
|
||||
onClick={() => {
|
||||
generateThumbsForLocation({ id: locationId, path: '' });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <img
|
||||
<div data-tauri-drag-region className="flex flex-row justify-center flex-grow ">
|
||||
<div className="flex mx-8 space-x-2 pointer-events-auto">
|
||||
<TopBarButton icon={Tag} />
|
||||
<TopBarButton icon={FolderPlus} />
|
||||
<TopBarButton icon={TerminalWindow} />
|
||||
</div>
|
||||
<div className="relative flex h-7">
|
||||
<input
|
||||
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"
|
||||
/>
|
||||
<div className="space-x-1 absolute top-[2px] right-1">
|
||||
<Shortcut chars="⌘K" />
|
||||
{/* <Shortcut chars="S" /> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex mx-8 space-x-2">
|
||||
<TopBarButton icon={Key} />
|
||||
<TopBarButton icon={Cloud} />
|
||||
<TopBarButton
|
||||
icon={ArrowsClockwise}
|
||||
onClick={() => {
|
||||
generateThumbsForLocation({ id: locationId, path: '' });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <img
|
||||
alt="spacedrive-logo"
|
||||
src="/images/spacedrive_logo.png"
|
||||
className="w-8 h-8 mt-[1px] mr-2 pointer-events-none"
|
||||
/> */}
|
||||
{/*<TopBarButton onClick={() => {*/}
|
||||
{/* setSettingsOpen(!settingsOpen);*/}
|
||||
{/*}} className="mr-[8px]" icon={CogIcon} />*/}
|
||||
</div>
|
||||
{/* <div className="h-[1px] flex-shrink-0 max-w bg-gray-200 dark:bg-gray-700" /> */}
|
||||
</>
|
||||
);
|
||||
{/*<TopBarButton onClick={() => {*/}
|
||||
{/* setSettingsOpen(!settingsOpen);*/}
|
||||
{/*}} className="mr-[8px]" icon={CogIcon} />*/}
|
||||
</div>
|
||||
{/* <div className="h-[1px] flex-shrink-0 max-w bg-gray-200 dark:bg-gray-700" /> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,64 +1,64 @@
|
|||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import { ReactComponent as Close } from '../../assets/svg/macos_close.svg';
|
||||
import { ReactComponent as Fullscreen } from '../../assets/svg/macos_fullscreen.svg';
|
||||
import { ReactComponent as Minimize } from '../../assets/svg/macos_minimize.svg';
|
||||
import { useFocusState } from '../../hooks/useFocusState';
|
||||
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 {
|
||||
onClose?: () => void;
|
||||
onMinimize?: () => void;
|
||||
onFullscreen?: () => void;
|
||||
onClose?: () => void;
|
||||
onMinimize?: () => void;
|
||||
onFullscreen?: () => void;
|
||||
}
|
||||
|
||||
export const TrafficLights: React.FC<TrafficLightsProps> = (props) => {
|
||||
const [focused] = useFocusState();
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={clsx('flex flex-row space-x-2 px-2 group', props.className)}
|
||||
>
|
||||
<Light mode="close" action={props.onClose} focused={focused} />
|
||||
<Light mode="minimize" action={props.onMinimize} focused={focused} />
|
||||
<Light mode="fullscreen" action={props.onFullscreen} focused={focused} />
|
||||
</div>
|
||||
);
|
||||
const [focused] = useFocusState();
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={clsx('flex flex-row space-x-2 px-2 group', props.className)}
|
||||
>
|
||||
<Light mode="close" action={props.onClose} focused={focused} />
|
||||
<Light mode="minimize" action={props.onMinimize} focused={focused} />
|
||||
<Light mode="fullscreen" action={props.onFullscreen} focused={focused} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface LightProps {
|
||||
mode: 'close' | 'minimize' | 'fullscreen';
|
||||
focused: boolean;
|
||||
action?: () => void;
|
||||
mode: 'close' | 'minimize' | 'fullscreen';
|
||||
focused: boolean;
|
||||
action?: () => void;
|
||||
}
|
||||
|
||||
const Light: React.FC<LightProps> = (props) => {
|
||||
return (
|
||||
<div
|
||||
onClick={props.action}
|
||||
className={clsx('w-[13px] h-[13px] rounded-full bg-gray-500', {
|
||||
'!bg-red-400 active:!bg-red-300': props.mode == 'close' && props.focused,
|
||||
'!bg-green-400 active:!bg-green-300': props.mode == 'fullscreen' && props.focused,
|
||||
'!bg-yellow-400 active:!bg-yellow-300': props.mode == 'minimize' && props.focused
|
||||
})}
|
||||
>
|
||||
{(() => {
|
||||
switch (props.mode) {
|
||||
case 'close':
|
||||
return (
|
||||
<Close className=" w-[13px] -mt-[1px] h-[15px] opacity-0 group-hover:opacity-100" />
|
||||
);
|
||||
case 'minimize':
|
||||
return (
|
||||
<Minimize className="ml-[2px] w-[9px] -mt-[1px] h-[15px] opacity-0 group-hover:opacity-100" />
|
||||
);
|
||||
case 'fullscreen':
|
||||
return (
|
||||
<Fullscreen className="ml-[1px] w-[11px] -mt-[1px] h-[15px] opacity-0 group-hover:opacity-100" />
|
||||
);
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
onClick={props.action}
|
||||
className={clsx('w-[13px] h-[13px] rounded-full bg-gray-500', {
|
||||
'!bg-red-400 active:!bg-red-300': props.mode == 'close' && props.focused,
|
||||
'!bg-green-400 active:!bg-green-300': props.mode == 'fullscreen' && props.focused,
|
||||
'!bg-yellow-400 active:!bg-yellow-300': props.mode == 'minimize' && props.focused
|
||||
})}
|
||||
>
|
||||
{(() => {
|
||||
switch (props.mode) {
|
||||
case 'close':
|
||||
return (
|
||||
<Close className=" w-[13px] -mt-[1px] h-[15px] opacity-0 group-hover:opacity-100" />
|
||||
);
|
||||
case 'minimize':
|
||||
return (
|
||||
<Minimize className="ml-[2px] w-[9px] -mt-[1px] h-[15px] opacity-0 group-hover:opacity-100" />
|
||||
);
|
||||
case 'fullscreen':
|
||||
return (
|
||||
<Fullscreen className="ml-[1px] w-[11px] -mt-[1px] h-[15px] opacity-0 group-hover:opacity-100" />
|
||||
);
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,22 +2,22 @@ import clsx from 'clsx';
|
|||
import React from 'react';
|
||||
|
||||
export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
containerClasname?: string;
|
||||
containerClasname?: string;
|
||||
}
|
||||
|
||||
export const Checkbox: React.FC<CheckboxProps> = (props) => {
|
||||
return (
|
||||
<label
|
||||
className={clsx(
|
||||
'flex items-center text-sm font-medium text-gray-700 dark:text-gray-100',
|
||||
props.containerClasname
|
||||
)}
|
||||
>
|
||||
<input
|
||||
{...props}
|
||||
type="checkbox"
|
||||
className={clsx(
|
||||
`
|
||||
return (
|
||||
<label
|
||||
className={clsx(
|
||||
'flex items-center text-sm font-medium text-gray-700 dark:text-gray-100',
|
||||
props.containerClasname
|
||||
)}
|
||||
>
|
||||
<input
|
||||
{...props}
|
||||
type="checkbox"
|
||||
className={clsx(
|
||||
`
|
||||
bg-gray-50
|
||||
hover:bg-gray-100
|
||||
dark:bg-gray-800
|
||||
|
@ -32,10 +32,10 @@ export const Checkbox: React.FC<CheckboxProps> = (props) => {
|
|||
text-primary
|
||||
checked:ring-2 checked:ring-primary-500
|
||||
`,
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
<span className="select-none">Checkbox</span>
|
||||
</label>
|
||||
);
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
<span className="select-none">Checkbox</span>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,20 +4,20 @@ import ReactJson, { ReactJsonViewProps } from 'react-json-view';
|
|||
export interface CodeBlockProps extends ReactJsonViewProps {}
|
||||
|
||||
export default function CodeBlock(props: CodeBlockProps) {
|
||||
return (
|
||||
<ReactJson
|
||||
enableClipboard={false}
|
||||
displayDataTypes={false}
|
||||
theme="ocean"
|
||||
style={{
|
||||
padding: 20,
|
||||
borderRadius: 5,
|
||||
backgroundColor: '#101016',
|
||||
border: 1,
|
||||
borderColor: '#1E1E27',
|
||||
borderStyle: 'solid'
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<ReactJson
|
||||
enableClipboard={false}
|
||||
displayDataTypes={false}
|
||||
theme="ocean"
|
||||
style={{
|
||||
padding: 20,
|
||||
borderRadius: 5,
|
||||
backgroundColor: '#101016',
|
||||
border: 1,
|
||||
borderColor: '#1E1E27',
|
||||
borderStyle: 'solid'
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import clsx from 'clsx';
|
|||
import React from 'react';
|
||||
|
||||
const variants = {
|
||||
default: `
|
||||
default: `
|
||||
shadow-sm
|
||||
bg-white
|
||||
hover:bg-white
|
||||
|
@ -26,46 +26,46 @@ const variants = {
|
|||
dark:text-white
|
||||
placeholder-gray-300
|
||||
`,
|
||||
primary: ''
|
||||
primary: ''
|
||||
};
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
variant?: keyof typeof variants;
|
||||
variant?: keyof typeof variants;
|
||||
}
|
||||
|
||||
export const Input = ({ ...props }: InputProps) => {
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
className={clsx(
|
||||
`px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`,
|
||||
variants[props.variant || 'default'],
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
className={clsx(
|
||||
`px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`,
|
||||
variants[props.variant || 'default'],
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface TextAreaProps extends React.InputHTMLAttributes<HTMLTextAreaElement> {
|
||||
variant?: keyof typeof variants;
|
||||
variant?: keyof typeof variants;
|
||||
}
|
||||
|
||||
export const TextArea = ({ size, ...props }: TextAreaProps) => {
|
||||
return (
|
||||
<textarea
|
||||
{...props}
|
||||
className={clsx(
|
||||
`px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`,
|
||||
variants[props.variant || 'default'],
|
||||
size && '',
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<textarea
|
||||
{...props}
|
||||
className={clsx(
|
||||
`px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`,
|
||||
variants[props.variant || 'default'],
|
||||
size && '',
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Label: React.FC<{ slug?: string; children: string }> = (props) => (
|
||||
<label className="text-sm font-bold" htmlFor={props.slug}>
|
||||
{props.children}
|
||||
</label>
|
||||
<label className="text-sm font-bold" htmlFor={props.slug}>
|
||||
{props.children}
|
||||
</label>
|
||||
);
|
||||
|
|
|
@ -1,26 +1,27 @@
|
|||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import { DefaultProps } from './types';
|
||||
|
||||
interface InputContainerProps extends DefaultProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
children: React.ReactNode;
|
||||
mini?: boolean;
|
||||
title: string;
|
||||
description?: string;
|
||||
children: React.ReactNode;
|
||||
mini?: boolean;
|
||||
}
|
||||
|
||||
export const InputContainer: React.FC<InputContainerProps> = (props) => {
|
||||
return (
|
||||
<div className="flex flex-row">
|
||||
<div
|
||||
className={clsx('flex flex-col w-full', !props.mini && 'pb-6', props.className)}
|
||||
{...props}
|
||||
>
|
||||
<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.mini && props.children}
|
||||
</div>
|
||||
{props.mini && props.children}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-row">
|
||||
<div
|
||||
className={clsx('flex flex-col w-full', !props.mini && 'pb-6', props.className)}
|
||||
{...props}
|
||||
>
|
||||
<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.mini && props.children}
|
||||
</div>
|
||||
{props.mini && props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,101 +1,101 @@
|
|||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import { Listbox as ListboxPrimitive, Transition } from '@headlessui/react';
|
||||
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid';
|
||||
import clsx from 'clsx';
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
|
||||
interface ListboxOption {
|
||||
option: string;
|
||||
description?: string;
|
||||
key: string;
|
||||
option: string;
|
||||
description?: string;
|
||||
key: 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(() => {
|
||||
if (!selected) {
|
||||
setSelected(props.options[0]);
|
||||
}
|
||||
}, [props.options]);
|
||||
useEffect(() => {
|
||||
if (!selected) {
|
||||
setSelected(props.options[0]);
|
||||
}
|
||||
}, [props.options]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListboxPrimitive value={selected} onChange={setSelected}>
|
||||
<div className="relative w-full">
|
||||
<ListboxPrimitive.Button
|
||||
className={clsx(
|
||||
`relative w-full py-2 pl-3 pr-10 text-left bg-white dark:bg-gray-500
|
||||
return (
|
||||
<>
|
||||
<ListboxPrimitive value={selected} onChange={setSelected}>
|
||||
<div className="relative w-full">
|
||||
<ListboxPrimitive.Button
|
||||
className={clsx(
|
||||
`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
|
||||
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`,
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{selected?.option ? (
|
||||
<span className="block truncate">{selected?.option}</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">
|
||||
<SelectorIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
||||
</span>
|
||||
</ListboxPrimitive.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<ListboxPrimitive.Options
|
||||
className={`
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{selected?.option ? (
|
||||
<span className="block truncate">{selected?.option}</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">
|
||||
<SelectorIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
||||
</span>
|
||||
</ListboxPrimitive.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<ListboxPrimitive.Options
|
||||
className={`
|
||||
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
|
||||
ring-1 ring-black ring-opacity-5 focus:outline-none
|
||||
`}
|
||||
>
|
||||
{props.options.map((person, personIdx) => (
|
||||
<ListboxPrimitive.Option
|
||||
key={personIdx}
|
||||
className={({ active }) =>
|
||||
`cursor-default select-none relative rounded m-1 py-2 pl-8 pr-4 dark:text-white focus:outline-none ${
|
||||
active
|
||||
? 'text-primary-900 bg-primary-600'
|
||||
: 'text-gray-900 dark:hover:bg-gray-600 dark:hover:bg-opacity-20'
|
||||
}`
|
||||
}
|
||||
value={person}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<span
|
||||
className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}
|
||||
>
|
||||
{person.option}
|
||||
{person.description && (
|
||||
<span
|
||||
className={clsx(
|
||||
'text-gray-300 leading-5 ml-3 text-xs',
|
||||
selected && 'text-white'
|
||||
)}
|
||||
>
|
||||
{person.description}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
>
|
||||
{props.options.map((person, personIdx) => (
|
||||
<ListboxPrimitive.Option
|
||||
key={personIdx}
|
||||
className={({ active }) =>
|
||||
`cursor-default select-none relative rounded m-1 py-2 pl-8 pr-4 dark:text-white focus:outline-none ${
|
||||
active
|
||||
? 'text-primary-900 bg-primary-600'
|
||||
: 'text-gray-900 dark:hover:bg-gray-600 dark:hover:bg-opacity-20'
|
||||
}`
|
||||
}
|
||||
value={person}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<span
|
||||
className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}
|
||||
>
|
||||
{person.option}
|
||||
{person.description && (
|
||||
<span
|
||||
className={clsx(
|
||||
'text-gray-300 leading-5 ml-3 text-xs',
|
||||
selected && 'text-white'
|
||||
)}
|
||||
>
|
||||
{person.description}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
|
||||
{selected ? (
|
||||
<span className="absolute inset-y-0 left-0 flex items-center pl-2 text-white">
|
||||
<CheckIcon className="w-5 h-5" aria-hidden="true" />
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</ListboxPrimitive.Option>
|
||||
))}
|
||||
</ListboxPrimitive.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</ListboxPrimitive>
|
||||
</>
|
||||
);
|
||||
{selected ? (
|
||||
<span className="absolute inset-y-0 left-0 flex items-center pl-2 text-white">
|
||||
<CheckIcon className="w-5 h-5" aria-hidden="true" />
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</ListboxPrimitive.Option>
|
||||
))}
|
||||
</ListboxPrimitive.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</ListboxPrimitive>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,23 +2,23 @@ import * as ProgressPrimitive from '@radix-ui/react-progress';
|
|||
import React, { useEffect } from 'react';
|
||||
|
||||
interface Props {
|
||||
value: number;
|
||||
total: number;
|
||||
value: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
const ProgressBar = (props: Props) => {
|
||||
const percentage = Math.round((props.value / props.total) * 100);
|
||||
return (
|
||||
<ProgressPrimitive.Root
|
||||
value={percentage}
|
||||
className="w-full h-1 overflow-hidden bg-gray-200 rounded-full dark:bg-gray-500"
|
||||
>
|
||||
<ProgressPrimitive.Indicator
|
||||
style={{ width: `${percentage}%` }}
|
||||
className="h-full duration-300 ease-in-out bg-primary "
|
||||
/>
|
||||
</ProgressPrimitive.Root>
|
||||
);
|
||||
const percentage = Math.round((props.value / props.total) * 100);
|
||||
return (
|
||||
<ProgressPrimitive.Root
|
||||
value={percentage}
|
||||
className="w-full h-1 overflow-hidden bg-gray-200 rounded-full dark:bg-gray-500"
|
||||
>
|
||||
<ProgressPrimitive.Indicator
|
||||
style={{ width: `${percentage}%` }}
|
||||
className="h-full duration-300 ease-in-out bg-primary "
|
||||
/>
|
||||
</ProgressPrimitive.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProgressBar;
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import { DefaultProps } from './types';
|
||||
|
||||
export interface ShortcutProps extends DefaultProps {
|
||||
chars: string;
|
||||
chars: string;
|
||||
}
|
||||
|
||||
export const Shortcut: React.FC<ShortcutProps> = (props) => {
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
`
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
`
|
||||
px-1
|
||||
py-0.5
|
||||
text-xs
|
||||
|
@ -24,10 +25,10 @@ export const Shortcut: React.FC<ShortcutProps> = (props) => {
|
|||
border-t-2
|
||||
rounded-lg
|
||||
`,
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.chars}
|
||||
</span>
|
||||
);
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.chars}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import React from 'react';
|
||||
import * as SliderPrimitive from '@radix-ui/react-slider';
|
||||
import React from 'react';
|
||||
|
||||
const Slider = (props: SliderPrimitive.SliderProps) => (
|
||||
<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.Range className="absolute h-full rounded-full outline-none bg-primary-500" />
|
||||
</SliderPrimitive.Track>
|
||||
<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"
|
||||
data-tip="1.0"
|
||||
/>
|
||||
</SliderPrimitive.Root>
|
||||
<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.Range className="absolute h-full rounded-full outline-none bg-primary-500" />
|
||||
</SliderPrimitive.Track>
|
||||
<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"
|
||||
data-tip="1.0"
|
||||
/>
|
||||
</SliderPrimitive.Root>
|
||||
);
|
||||
|
||||
export default Slider;
|
||||
|
|
|
@ -1,30 +1,31 @@
|
|||
import clsx from 'clsx';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
import { DefaultProps } from './types';
|
||||
|
||||
export interface TagProps extends DefaultProps {
|
||||
children: ReactNode;
|
||||
color: 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple' | 'pink';
|
||||
children: ReactNode;
|
||||
color: 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple' | 'pink';
|
||||
}
|
||||
|
||||
export const Tag: React.FC<TagProps> = (props) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'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-orange-500 hover:bg-orange-400': props.color === 'orange',
|
||||
'bg-yellow-500 hover:bg-yellow-400': props.color === 'yellow',
|
||||
'bg-green-500 hover:bg-green-400': props.color === 'green',
|
||||
'bg-blue-500 hover:bg-blue-400': props.color === 'blue',
|
||||
'bg-purple-500 hover:bg-purple-400': props.color === 'purple',
|
||||
'bg-pink-500 hover:bg-pink-400': props.color === 'pink'
|
||||
},
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'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-orange-500 hover:bg-orange-400': props.color === 'orange',
|
||||
'bg-yellow-500 hover:bg-yellow-400': props.color === 'yellow',
|
||||
'bg-green-500 hover:bg-green-400': props.color === 'green',
|
||||
'bg-blue-500 hover:bg-blue-400': props.color === 'blue',
|
||||
'bg-purple-500 hover:bg-purple-400': props.color === 'purple',
|
||||
'bg-pink-500 hover:bg-pink-400': props.color === 'pink'
|
||||
},
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
import React from 'react';
|
||||
import { Switch } from '@headlessui/react';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
export interface ToggleProps {
|
||||
value: boolean;
|
||||
onChange?: (newValue: boolean) => void;
|
||||
size?: 'sm' | 'md';
|
||||
value: boolean;
|
||||
onChange?: (newValue: boolean) => void;
|
||||
size?: 'sm' | 'md';
|
||||
}
|
||||
|
||||
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 (
|
||||
<Switch
|
||||
checked={isEnabled}
|
||||
onChange={onChange}
|
||||
className={clsx(
|
||||
'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,
|
||||
'h-6 w-11': size === 'sm',
|
||||
'h-8 w-[55px]': size === 'md'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={clsx(
|
||||
'transition inline-block w-4 h-4 transform bg-white rounded-full',
|
||||
isEnabled ? 'translate-x-6' : 'translate-x-1',
|
||||
{
|
||||
'w-4 h-4': size === 'sm',
|
||||
'h-6 w-6': size === 'md',
|
||||
'translate-x-7': size === 'md' && isEnabled
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
return (
|
||||
<Switch
|
||||
checked={isEnabled}
|
||||
onChange={onChange}
|
||||
className={clsx(
|
||||
'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,
|
||||
'h-6 w-11': size === 'sm',
|
||||
'h-8 w-[55px]': size === 'md'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={clsx(
|
||||
'transition inline-block w-4 h-4 transform bg-white rounded-full',
|
||||
isEnabled ? 'translate-x-6' : 'translate-x-1',
|
||||
{
|
||||
'w-4 h-4': size === 'sm',
|
||||
'h-6 w-6': size === 'md',
|
||||
'translate-x-7': size === 'md' && isEnabled
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
import React from 'react';
|
||||
|
||||
interface StyleState {
|
||||
active: string[];
|
||||
hover: string[];
|
||||
normal: string[];
|
||||
active: string[];
|
||||
hover: string[];
|
||||
normal: string[];
|
||||
}
|
||||
|
||||
interface Variant {
|
||||
base: string;
|
||||
light: StyleState;
|
||||
dark: StyleState;
|
||||
base: string;
|
||||
light: StyleState;
|
||||
dark: StyleState;
|
||||
}
|
||||
|
||||
function tw(variant: Variant): string {
|
||||
return `${variant.base} ${variant.light}`;
|
||||
return `${variant.base} ${variant.light}`;
|
||||
}
|
||||
|
||||
const variants: Record<string, string> = {
|
||||
default: tw({
|
||||
base: 'shadow-sm',
|
||||
light: {
|
||||
normal: ['bg-gray-50', 'border-gray-100', 'text-gray-700'],
|
||||
hover: ['bg-gray-100', 'border-gray-200', 'text-gray-900'],
|
||||
active: ['bg-gray-50', 'border-gray-200', 'text-gray-600']
|
||||
},
|
||||
dark: {
|
||||
normal: ['bg-gray-800 ', 'border-gray-100', ' text-gray-200'],
|
||||
active: ['bg-gray-700 ', 'border-gray-200 ', 'text-white'],
|
||||
hover: ['bg-gray-700 ', 'border-gray-600 ', 'text-white']
|
||||
}
|
||||
})
|
||||
default: tw({
|
||||
base: 'shadow-sm',
|
||||
light: {
|
||||
normal: ['bg-gray-50', 'border-gray-100', 'text-gray-700'],
|
||||
hover: ['bg-gray-100', 'border-gray-200', 'text-gray-900'],
|
||||
active: ['bg-gray-50', 'border-gray-200', 'text-gray-600']
|
||||
},
|
||||
dark: {
|
||||
normal: ['bg-gray-800 ', 'border-gray-100', ' text-gray-200'],
|
||||
active: ['bg-gray-700 ', 'border-gray-200 ', 'text-white'],
|
||||
hover: ['bg-gray-700 ', 'border-gray-600 ', 'text-white']
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export interface DefaultProps {
|
||||
className?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import React from 'react';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import React from 'react';
|
||||
|
||||
export default function SlideUp(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<Transition
|
||||
show
|
||||
className="flex flex-grow"
|
||||
appear
|
||||
enter="transition ease-in-out-back duration-200"
|
||||
enterFrom="opacity-0 translate-y-5"
|
||||
enterTo="opacity-100"
|
||||
leave="transition duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
{props.children}
|
||||
</Transition>
|
||||
);
|
||||
return (
|
||||
<Transition
|
||||
show
|
||||
className="flex flex-grow"
|
||||
appear
|
||||
enter="transition ease-in-out-back duration-200"
|
||||
enterFrom="opacity-0 translate-y-5"
|
||||
enterTo="opacity-100"
|
||||
leave="transition duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
{props.children}
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
|
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 { CoreEvent } from '@sd/core';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { useExplorerState } from '../components/file/FileList';
|
||||
|
||||
import { AppPropsContext } from '../App';
|
||||
import { useExplorerState } from '../components/file/FileList';
|
||||
|
||||
export function useCoreEvents() {
|
||||
const client = useQueryClient();
|
||||
const client = useQueryClient();
|
||||
|
||||
const { addNewThumbnail } = useExplorerState();
|
||||
useEffect(() => {
|
||||
function handleCoreEvent(e: CoreEvent) {
|
||||
switch (e?.key) {
|
||||
case 'NewThumbnail':
|
||||
addNewThumbnail(e.data.cas_id);
|
||||
break;
|
||||
case 'InvalidateQuery':
|
||||
case 'InvalidateQueryDebounced':
|
||||
let query = [e.data.key];
|
||||
// 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
|
||||
// @ts-expect-error
|
||||
if (e.data.params) {
|
||||
// @ts-expect-error
|
||||
query.push(e.data.params);
|
||||
}
|
||||
client.invalidateQueries(e.data.key);
|
||||
break;
|
||||
const { addNewThumbnail } = useExplorerState();
|
||||
useEffect(() => {
|
||||
function handleCoreEvent(e: CoreEvent) {
|
||||
switch (e?.key) {
|
||||
case 'NewThumbnail':
|
||||
addNewThumbnail(e.data.cas_id);
|
||||
break;
|
||||
case 'InvalidateQuery':
|
||||
case 'InvalidateQueryDebounced':
|
||||
let query = [e.data.key];
|
||||
// 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
|
||||
// @ts-expect-error
|
||||
if (e.data.params) {
|
||||
// @ts-expect-error
|
||||
query.push(e.data.params);
|
||||
}
|
||||
client.invalidateQueries(e.data.key);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// check Tauri Event type
|
||||
transport?.on('core_event', handleCoreEvent);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// check Tauri Event type
|
||||
transport?.on('core_event', handleCoreEvent);
|
||||
|
||||
return () => {
|
||||
transport?.off('core_event', handleCoreEvent);
|
||||
};
|
||||
return () => {
|
||||
transport?.off('core_event', handleCoreEvent);
|
||||
};
|
||||
|
||||
// listen('core_event', (e: { payload: CoreEvent }) => {
|
||||
// });
|
||||
}, [transport]);
|
||||
// listen('core_event', (e: { payload: CoreEvent }) => {
|
||||
// });
|
||||
}, [transport]);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
export function useFocusState() {
|
||||
const [focused, setFocused] = useState(true);
|
||||
const focus = () => setFocused(true);
|
||||
const blur = () => setFocused(false);
|
||||
useEffect(() => {
|
||||
window.addEventListener('focus', focus);
|
||||
window.addEventListener('blur', blur);
|
||||
return () => {
|
||||
window.removeEventListener('focus', focus);
|
||||
window.removeEventListener('blur', blur);
|
||||
};
|
||||
}, []);
|
||||
const [focused, setFocused] = useState(true);
|
||||
const focus = () => setFocused(true);
|
||||
const blur = () => setFocused(false);
|
||||
useEffect(() => {
|
||||
window.addEventListener('focus', focus);
|
||||
window.addEventListener('blur', blur);
|
||||
return () => {
|
||||
window.removeEventListener('focus', focus);
|
||||
window.removeEventListener('blur', blur);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return [focused];
|
||||
return [focused];
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
export function useInputState<T = any>(initialValue: T) {
|
||||
const [value, setValue] = useState<T>(initialValue);
|
||||
return {
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValue(event.target.value as unknown as T),
|
||||
value
|
||||
};
|
||||
const [value, setValue] = useState<T>(initialValue);
|
||||
return {
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValue(event.target.value as unknown as T),
|
||||
value
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
// import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const ContentScreen: React.FC<{}> = (props) => {
|
||||
return (
|
||||
<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">
|
||||
<h1 className="text-lg font-bold ">Content</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<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">
|
||||
<h1 className="text-lg font-bold ">Content</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,54 +1,55 @@
|
|||
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||
import { Button } from '@sd/ui';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { AppPropsContext } from '../App';
|
||||
import CodeBlock from '../components/primitive/Codeblock';
|
||||
|
||||
export const DebugScreen: React.FC<{}> = (props) => {
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
const { data: client } = useBridgeQuery('ClientGetState');
|
||||
const { data: jobs } = useBridgeQuery('JobGetRunning');
|
||||
const { data: jobHistory } = useBridgeQuery('JobGetHistory');
|
||||
// const { mutate: purgeDB } = useBridgeCommand('PurgeDatabase', {
|
||||
// onMutate: () => {
|
||||
// alert('Database purged');
|
||||
// }
|
||||
// });
|
||||
const { mutate: identifyFiles } = useBridgeCommand('IdentifyUniqueFiles');
|
||||
return (
|
||||
<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">
|
||||
<h1 className="text-lg font-bold ">Developer Debugger</h1>
|
||||
<div className="flex flex-row pb-4 space-x-2">
|
||||
<Button
|
||||
className="w-40"
|
||||
variant="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (client && appPropsContext?.onOpen) {
|
||||
appPropsContext.onOpen(client.data_path);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Open data folder
|
||||
</Button>
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
const { data: client } = useBridgeQuery('ClientGetState');
|
||||
const { data: jobs } = useBridgeQuery('JobGetRunning');
|
||||
const { data: jobHistory } = useBridgeQuery('JobGetHistory');
|
||||
// const { mutate: purgeDB } = useBridgeCommand('PurgeDatabase', {
|
||||
// onMutate: () => {
|
||||
// alert('Database purged');
|
||||
// }
|
||||
// });
|
||||
const { mutate: identifyFiles } = useBridgeCommand('IdentifyUniqueFiles');
|
||||
return (
|
||||
<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">
|
||||
<h1 className="text-lg font-bold ">Developer Debugger</h1>
|
||||
<div className="flex flex-row pb-4 space-x-2">
|
||||
<Button
|
||||
className="w-40"
|
||||
variant="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (client && appPropsContext?.onOpen) {
|
||||
appPropsContext.onOpen(client.data_path);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Open data folder
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="w-40"
|
||||
variant="gray"
|
||||
size="sm"
|
||||
onClick={() => identifyFiles(undefined)}
|
||||
>
|
||||
Identify unique files
|
||||
</Button>
|
||||
</div>
|
||||
<h1 className="text-sm font-bold ">Running Jobs</h1>
|
||||
<CodeBlock src={{ ...jobs }} />
|
||||
<h1 className="text-sm font-bold ">Job History</h1>
|
||||
<CodeBlock src={{ ...jobHistory }} />
|
||||
<h1 className="text-sm font-bold ">Client State</h1>
|
||||
<CodeBlock src={{ ...client }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<Button
|
||||
className="w-40"
|
||||
variant="gray"
|
||||
size="sm"
|
||||
onClick={() => identifyFiles(undefined)}
|
||||
>
|
||||
Identify unique files
|
||||
</Button>
|
||||
</div>
|
||||
<h1 className="text-sm font-bold ">Running Jobs</h1>
|
||||
<CodeBlock src={{ ...jobs }} />
|
||||
<h1 className="text-sm font-bold ">Job History</h1>
|
||||
<CodeBlock src={{ ...jobHistory }} />
|
||||
<h1 className="text-sm font-bold ">Client State</h1>
|
||||
<CodeBlock src={{ ...client }} />
|
||||
</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 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 { TopBar } from '../components/layout/TopBar';
|
||||
|
||||
export const ExplorerScreen: React.FC<{}> = () => {
|
||||
let [searchParams] = useSearchParams();
|
||||
let path = searchParams.get('path') || '';
|
||||
let [searchParams] = useSearchParams();
|
||||
let path = searchParams.get('path') || '';
|
||||
|
||||
let { id } = useParams();
|
||||
let location_id = Number(id);
|
||||
let { id } = useParams();
|
||||
let location_id = Number(id);
|
||||
|
||||
let [limit, setLimit] = React.useState(100);
|
||||
let [limit, setLimit] = React.useState(100);
|
||||
|
||||
useEffect(() => {
|
||||
console.log({ location_id, path, limit });
|
||||
}, [location_id, path]);
|
||||
useEffect(() => {
|
||||
console.log({ location_id, path, limit });
|
||||
}, [location_id, path]);
|
||||
|
||||
const { selectedRowIndex } = useExplorerState();
|
||||
const { selectedRowIndex } = useExplorerState();
|
||||
|
||||
const { data: currentDir } = useBridgeQuery(
|
||||
'LibGetExplorerDir',
|
||||
{ location_id: location_id!, path, limit },
|
||||
{ enabled: !!location_id }
|
||||
);
|
||||
const { data: currentDir } = useBridgeQuery(
|
||||
'LibGetExplorerDir',
|
||||
{ location_id: location_id!, path, limit },
|
||||
{ enabled: !!location_id }
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full">
|
||||
<TopBar />
|
||||
<div className="relative flex flex-row w-full ">
|
||||
<FileList location_id={location_id} path={path} limit={limit} />
|
||||
{currentDir?.contents && (
|
||||
<Inspector
|
||||
selectedFile={currentDir.contents[selectedRowIndex]}
|
||||
locationId={location_id}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full">
|
||||
<TopBar />
|
||||
<div className="relative flex flex-row w-full ">
|
||||
<FileList location_id={location_id} path={path} limit={limit} />
|
||||
{currentDir?.contents && (
|
||||
<Inspector
|
||||
selectedFile={currentDir.contents[selectedRowIndex]}
|
||||
locationId={location_id}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Button } from '@sd/ui';
|
|||
import byteSize from 'byte-size';
|
||||
import { DotsSixVertical, Laptop, LineSegments, Plus } from 'phosphor-react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Device } from '../components/device/Device';
|
||||
import FileItem from '../components/file/FileItem';
|
||||
import Dialog from '../components/layout/Dialog';
|
||||
|
@ -12,128 +13,128 @@ import { Input } from '../components/primitive';
|
|||
import { InputContainer } from '../components/primitive/InputContainer';
|
||||
|
||||
interface StatItemProps {
|
||||
name: string;
|
||||
value?: string;
|
||||
unit?: string;
|
||||
name: string;
|
||||
value?: string;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
const StatItem: React.FC<StatItemProps> = (props) => {
|
||||
let size = byteSize(Number(props.value) || 0);
|
||||
return (
|
||||
<div className="flex flex-col 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-2xl font-bold">
|
||||
{size.value}
|
||||
<span className="ml-1 text-[16px] text-gray-400">{size.unit}</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
let size = byteSize(Number(props.value) || 0);
|
||||
return (
|
||||
<div className="flex flex-col 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-2xl font-bold">
|
||||
{size.value}
|
||||
<span className="ml-1 text-[16px] text-gray-400">{size.unit}</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const OverviewScreen: React.FC<{}> = (props) => {
|
||||
const { data: libraryStatistics } = useBridgeQuery('GetLibraryStatistics');
|
||||
const { data: clientState } = useBridgeQuery('ClientGetState');
|
||||
const { data: libraryStatistics } = useBridgeQuery('GetLibraryStatistics');
|
||||
const { data: clientState } = useBridgeQuery('ClientGetState');
|
||||
|
||||
return (
|
||||
<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-7" />
|
||||
<div className="flex flex-col w-full h-screen px-3">
|
||||
<div className="flex w-full">
|
||||
<div className="flex flex-wrap flex-grow pb-4 space-x-6">
|
||||
<StatItem
|
||||
name="Total capacity"
|
||||
value={libraryStatistics?.total_bytes_capacity}
|
||||
unit={libraryStatistics?.total_bytes_capacity}
|
||||
/>
|
||||
<StatItem
|
||||
name="Index size"
|
||||
value={libraryStatistics?.library_db_size}
|
||||
unit={libraryStatistics?.library_db_size}
|
||||
/>
|
||||
<StatItem
|
||||
name="Preview media"
|
||||
value={libraryStatistics?.preview_media_bytes}
|
||||
unit={libraryStatistics?.preview_media_bytes}
|
||||
/>
|
||||
<StatItem
|
||||
name="Free space"
|
||||
value={libraryStatistics?.total_bytes_free}
|
||||
unit={libraryStatistics?.total_bytes_free}
|
||||
/>
|
||||
{/* <StatItem
|
||||
return (
|
||||
<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-7" />
|
||||
<div className="flex flex-col w-full h-screen px-3">
|
||||
<div className="flex w-full">
|
||||
<div className="flex flex-wrap flex-grow pb-4 space-x-6">
|
||||
<StatItem
|
||||
name="Total capacity"
|
||||
value={libraryStatistics?.total_bytes_capacity}
|
||||
unit={libraryStatistics?.total_bytes_capacity}
|
||||
/>
|
||||
<StatItem
|
||||
name="Index size"
|
||||
value={libraryStatistics?.library_db_size}
|
||||
unit={libraryStatistics?.library_db_size}
|
||||
/>
|
||||
<StatItem
|
||||
name="Preview media"
|
||||
value={libraryStatistics?.preview_media_bytes}
|
||||
unit={libraryStatistics?.preview_media_bytes}
|
||||
/>
|
||||
<StatItem
|
||||
name="Free space"
|
||||
value={libraryStatistics?.total_bytes_free}
|
||||
unit={libraryStatistics?.total_bytes_free}
|
||||
/>
|
||||
{/* <StatItem
|
||||
name="Total at-risk"
|
||||
value={'0'}
|
||||
unit={libraryStatistics?.preview_media_bytes}
|
||||
/>
|
||||
<StatItem name="Total backed up" value={'0'} unit={''} /> */}
|
||||
</div>
|
||||
<div className="space-x-2">
|
||||
<Dialog
|
||||
title="Add Device"
|
||||
description="Connect a new device to your library. Either enter another device's code or copy this one."
|
||||
ctaAction={() => {}}
|
||||
ctaLabel="Connect"
|
||||
trigger={
|
||||
<Button
|
||||
size="sm"
|
||||
icon={<PlusIcon className="inline w-4 h-4 -mt-0.5 mr-1" />}
|
||||
variant="gray"
|
||||
>
|
||||
Add Device
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col mt-2 space-y-3">
|
||||
<div className="flex flex-col">
|
||||
<span className="mb-1 text-xs font-bold uppercase text-gray-450">
|
||||
This Device
|
||||
</span>
|
||||
<Input readOnly disabled value="06ffd64309b24fb09e7c2188963d0207" />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="mb-1 text-xs font-bold uppercase text-gray-450">
|
||||
Enter a device code
|
||||
</span>
|
||||
<Input value="" />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
<div className="space-x-2">
|
||||
<Dialog
|
||||
title="Add Device"
|
||||
description="Connect a new device to your library. Either enter another device's code or copy this one."
|
||||
ctaAction={() => {}}
|
||||
ctaLabel="Connect"
|
||||
trigger={
|
||||
<Button
|
||||
size="sm"
|
||||
icon={<PlusIcon className="inline w-4 h-4 -mt-0.5 mr-1" />}
|
||||
variant="gray"
|
||||
>
|
||||
Add Device
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col mt-2 space-y-3">
|
||||
<div className="flex flex-col">
|
||||
<span className="mb-1 text-xs font-bold uppercase text-gray-450">
|
||||
This Device
|
||||
</span>
|
||||
<Input readOnly disabled value="06ffd64309b24fb09e7c2188963d0207" />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="mb-1 text-xs font-bold uppercase text-gray-450">
|
||||
Enter a device code
|
||||
</span>
|
||||
<Input value="" />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
className="w-8"
|
||||
noPadding
|
||||
icon={<MenuIcon className="inline w-4 h-4" />}
|
||||
variant="gray"
|
||||
></Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="mt-5" /> */}
|
||||
<div className="flex flex-col pb-4 space-y-4">
|
||||
<Device
|
||||
name={clientState?.client_name ?? ''}
|
||||
size="1.4TB"
|
||||
runningJob={{ amount: 65, task: 'Generating preview media' }}
|
||||
locations={[{ name: 'Pictures' }, { name: 'Downloads' }, { name: 'Minecraft' }]}
|
||||
type="laptop"
|
||||
/>
|
||||
<Device
|
||||
name={`James' iPhone 12`}
|
||||
size="47.7GB"
|
||||
locations={[{ name: 'Camera Roll' }, { name: 'Notes' }]}
|
||||
type="phone"
|
||||
removeThisSoon
|
||||
/>
|
||||
<Device
|
||||
name={`Spacedrive Server`}
|
||||
size="5GB"
|
||||
locations={[{ name: 'Cached' }, { name: 'Photos' }, { name: 'Documents' }]}
|
||||
type="server"
|
||||
/>
|
||||
</div>
|
||||
{/* <hr className="my-4 border-none dark:border-gray-600" /> */}
|
||||
<Button
|
||||
size="sm"
|
||||
className="w-8"
|
||||
noPadding
|
||||
icon={<MenuIcon className="inline w-4 h-4" />}
|
||||
variant="gray"
|
||||
></Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="mt-5" /> */}
|
||||
<div className="flex flex-col pb-4 space-y-4">
|
||||
<Device
|
||||
name={clientState?.client_name ?? ''}
|
||||
size="1.4TB"
|
||||
runningJob={{ amount: 65, task: 'Generating preview media' }}
|
||||
locations={[{ name: 'Pictures' }, { name: 'Downloads' }, { name: 'Minecraft' }]}
|
||||
type="laptop"
|
||||
/>
|
||||
<Device
|
||||
name={`James' iPhone 12`}
|
||||
size="47.7GB"
|
||||
locations={[{ name: 'Camera Roll' }, { name: 'Notes' }]}
|
||||
type="phone"
|
||||
removeThisSoon
|
||||
/>
|
||||
<Device
|
||||
name={`Spacedrive Server`}
|
||||
size="5GB"
|
||||
locations={[{ name: 'Cached' }, { name: 'Photos' }, { name: 'Documents' }]}
|
||||
type="server"
|
||||
/>
|
||||
</div>
|
||||
{/* <hr className="my-4 border-none dark:border-gray-600" /> */}
|
||||
|
||||
{/* <div className="mt-2 space-x-1">
|
||||
{/* <div className="mt-2 space-x-1">
|
||||
<FileItem
|
||||
selected={selectedFile == 'assets'}
|
||||
onClick={() => handleSelect('assets')}
|
||||
|
@ -223,8 +224,8 @@ export const OverviewScreen: React.FC<{}> = (props) => {
|
|||
</div>
|
||||
<hr className="my-5 border-gray-50 dark:border-gray-600" /> */}
|
||||
|
||||
{/* <hr className="my-5 dark:border-gray-600" /> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
{/* <hr className="my-5 dark:border-gray-600" /> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,17 +2,17 @@ import React, { useEffect } from 'react';
|
|||
import { useLocation, useNavigate } from 'react-router';
|
||||
|
||||
export interface RedirectPageProps {
|
||||
to: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
export const RedirectPage: React.FC<RedirectPageProps> = (props) => {
|
||||
const { to: destination } = props;
|
||||
const { to: destination } = props;
|
||||
|
||||
const navigate = useNavigate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
navigate(destination);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
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 { SidebarLink } from '../components/file/Sidebar';
|
||||
import { Book, Database, HardDrive, PaintBrush } from 'phosphor-react';
|
||||
import {
|
||||
CloudIcon,
|
||||
CogIcon,
|
||||
KeyIcon,
|
||||
LockClosedIcon,
|
||||
PhotographIcon,
|
||||
TagIcon,
|
||||
TerminalIcon,
|
||||
UsersIcon
|
||||
} from '@heroicons/react/outline';
|
||||
import clsx from 'clsx';
|
||||
import { Book, Database, HardDrive, PaintBrush } from 'phosphor-react';
|
||||
import React from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { SidebarLink } from '../components/file/Sidebar';
|
||||
|
||||
//@ts-ignore
|
||||
// import { Spline } from 'react-spline';
|
||||
// import WINDOWS_SCENE from '../assets/spline/scene.json';
|
||||
|
||||
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 }) => (
|
||||
<div className={clsx('mt-5 mb-1 ml-1 text-xs font-semibold text-gray-300', className)}>
|
||||
{children}
|
||||
</div>
|
||||
<div className={clsx('mt-5 mb-1 ml-1 text-xs font-semibold text-gray-300', className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const SettingsScreen: React.FC<{}> = () => {
|
||||
return (
|
||||
<div className="flex flex-row w-full">
|
||||
<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 className="p-5 pt-0">
|
||||
<Heading className="mt-0">Client</Heading>
|
||||
<SidebarLink to="/settings/general">
|
||||
<Icon component={CogIcon} />
|
||||
General
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/security">
|
||||
<Icon component={LockClosedIcon} />
|
||||
Security
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/appearance">
|
||||
<Icon component={PaintBrush} />
|
||||
Appearance
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/experimental">
|
||||
<Icon component={TerminalIcon} />
|
||||
Experimental
|
||||
</SidebarLink>
|
||||
return (
|
||||
<div className="flex flex-row w-full">
|
||||
<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 className="p-5 pt-0">
|
||||
<Heading className="mt-0">Client</Heading>
|
||||
<SidebarLink to="/settings/general">
|
||||
<Icon component={CogIcon} />
|
||||
General
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/security">
|
||||
<Icon component={LockClosedIcon} />
|
||||
Security
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/appearance">
|
||||
<Icon component={PaintBrush} />
|
||||
Appearance
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/experimental">
|
||||
<Icon component={TerminalIcon} />
|
||||
Experimental
|
||||
</SidebarLink>
|
||||
|
||||
<Heading>Library</Heading>
|
||||
<SidebarLink to="/settings/library">
|
||||
<Icon component={Database} />
|
||||
Database
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/locations">
|
||||
<Icon component={HardDrive} />
|
||||
Locations
|
||||
</SidebarLink>
|
||||
<Heading>Library</Heading>
|
||||
<SidebarLink to="/settings/library">
|
||||
<Icon component={Database} />
|
||||
Database
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/locations">
|
||||
<Icon component={HardDrive} />
|
||||
Locations
|
||||
</SidebarLink>
|
||||
|
||||
<SidebarLink to="/settings/keys">
|
||||
<Icon component={KeyIcon} />
|
||||
Keys
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/tags">
|
||||
<Icon component={TagIcon} />
|
||||
Tags
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/keys">
|
||||
<Icon component={KeyIcon} />
|
||||
Keys
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/tags">
|
||||
<Icon component={TagIcon} />
|
||||
Tags
|
||||
</SidebarLink>
|
||||
|
||||
<Heading>Cloud</Heading>
|
||||
<SidebarLink to="/settings/sync">
|
||||
<Icon component={CloudIcon} />
|
||||
Sync
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/contacts">
|
||||
<Icon component={UsersIcon} />
|
||||
Contacts
|
||||
</SidebarLink>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<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 px-12 pb-5">
|
||||
<Outlet />
|
||||
<div className="block h-20" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<Heading>Cloud</Heading>
|
||||
<SidebarLink to="/settings/sync">
|
||||
<Icon component={CloudIcon} />
|
||||
Sync
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/contacts">
|
||||
<Icon component={UsersIcon} />
|
||||
Contacts
|
||||
</SidebarLink>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<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 px-12 pb-5">
|
||||
<Outlet />
|
||||
<div className="block h-20" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,10 +2,10 @@ import React, { useEffect } from 'react';
|
|||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
|
||||
export const TagScreen: React.FC<{}> = () => {
|
||||
let [searchParams] = useSearchParams();
|
||||
let path = searchParams.get('path') || '';
|
||||
let [searchParams] = useSearchParams();
|
||||
let path = searchParams.get('path') || '';
|
||||
|
||||
let { id } = useParams();
|
||||
let { id } = useParams();
|
||||
|
||||
return <div className="p-5 text-gray-450">Tag screen coming soon...</div>;
|
||||
return <div className="p-5 text-gray-450">Tag screen coming soon...</div>;
|
||||
};
|
||||
|
|
|
@ -1,38 +1,39 @@
|
|||
import React from 'react';
|
||||
import { Button } from '@sd/ui';
|
||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
import { Toggle } from '../../components/primitive';
|
||||
import React from 'react';
|
||||
|
||||
import { useStore } from '../../components/device/Stores';
|
||||
import { Toggle } from '../../components/primitive';
|
||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
|
||||
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 (
|
||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||
{/*<Button size="sm">Add Location</Button>*/}
|
||||
<div className="mt-3 mb-3">
|
||||
<h1 className="text-2xl font-bold">Experimental</h1>
|
||||
<p className="mt-1 text-sm text-gray-400">Experimental features within Spacedrive.</p>
|
||||
</div>
|
||||
<InputContainer
|
||||
mini
|
||||
title="Debug Menu"
|
||||
description="Shows data about Spacedrive such as Jobs, Job History and Client State."
|
||||
>
|
||||
<div className="flex items-center h-full">
|
||||
<Toggle
|
||||
value={experimental}
|
||||
size={'sm'}
|
||||
onChange={(newValue) => {
|
||||
useStore.setState({
|
||||
experimental: newValue
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</InputContainer>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||
{/*<Button size="sm">Add Location</Button>*/}
|
||||
<div className="mt-3 mb-3">
|
||||
<h1 className="text-2xl font-bold">Experimental</h1>
|
||||
<p className="mt-1 text-sm text-gray-400">Experimental features within Spacedrive.</p>
|
||||
</div>
|
||||
<InputContainer
|
||||
mini
|
||||
title="Debug Menu"
|
||||
description="Shows data about Spacedrive such as Jobs, Job History and Client State."
|
||||
>
|
||||
<div className="flex items-center h-full">
|
||||
<Toggle
|
||||
value={experimental}
|
||||
size={'sm'}
|
||||
onChange={(newValue) => {
|
||||
useStore.setState({
|
||||
experimental: newValue
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</InputContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
import { Input } from '../../components/primitive';
|
||||
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||
import { Button } from '@sd/ui';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Input } from '../../components/primitive';
|
||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
import Listbox from '../../components/primitive/Listbox';
|
||||
import Slider from '../../components/primitive/Slider';
|
||||
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||
|
||||
export default function GeneralSettings() {
|
||||
const { data: volumes } = useBridgeQuery('SysGetVolumes');
|
||||
const { data: volumes } = useBridgeQuery('SysGetVolumes');
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||
<div className="mt-3 mb-3">
|
||||
<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>
|
||||
{/* <hr className="mt-4 border-gray-550" /> */}
|
||||
</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">
|
||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||
functional.
|
||||
</p>
|
||||
return (
|
||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||
<div className="mt-3 mb-3">
|
||||
<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>
|
||||
{/* <hr className="mt-4 border-gray-550" /> */}
|
||||
</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">
|
||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||
functional.
|
||||
</p>
|
||||
|
||||
{/* <InputContainer
|
||||
{/* <InputContainer
|
||||
title="Test scan directory"
|
||||
description="This will create a job to scan the directory you specify to the database."
|
||||
>
|
||||
|
@ -47,23 +47,23 @@ export default function GeneralSettings() {
|
|||
</div>
|
||||
</InputContainer> */}
|
||||
|
||||
<InputContainer title="Volumes" description="A list of volumes running on this device.">
|
||||
<div className="flex flex-row space-x-2">
|
||||
<div className="flex flex-grow">
|
||||
<Listbox
|
||||
options={
|
||||
volumes?.map((volume) => ({
|
||||
key: volume.name,
|
||||
option: volume.name,
|
||||
description: volume.mount_point
|
||||
})) ?? []
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</InputContainer>
|
||||
<InputContainer title="Volumes" description="A list of volumes running on this device.">
|
||||
<div className="flex flex-row space-x-2">
|
||||
<div className="flex flex-grow">
|
||||
<Listbox
|
||||
options={
|
||||
volumes?.map((volume) => ({
|
||||
key: volume.name,
|
||||
option: volume.name,
|
||||
description: volume.mount_point
|
||||
})) ?? []
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</InputContainer>
|
||||
|
||||
{/* <div className="">{JSON.stringify({ config })}</div> */}
|
||||
</div>
|
||||
);
|
||||
{/* <div className="">{JSON.stringify({ config })}</div> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,31 +1,32 @@
|
|||
import React from 'react';
|
||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
|
||||
import { Toggle } from '../../components/primitive';
|
||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
|
||||
type LibrarySecurity = 'public' | 'password' | 'vault';
|
||||
|
||||
export default function LibrarySettings() {
|
||||
// const locations = useBridgeQuery("SysGetLocation")
|
||||
const [encryptOnCloud, setEncryptOnCloud] = React.useState<boolean>(false);
|
||||
// const locations = useBridgeQuery("SysGetLocation")
|
||||
const [encryptOnCloud, setEncryptOnCloud] = React.useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||
{/*<Button size="sm">Add Location</Button>*/}
|
||||
<div className="mt-3 mb-3">
|
||||
<h1 className="text-2xl font-bold">Library database</h1>
|
||||
<p className="mt-1 text-sm text-gray-400">
|
||||
The database contains all library data and file metadata.
|
||||
</p>
|
||||
</div>
|
||||
<InputContainer
|
||||
mini
|
||||
title="Encrypt on cloud"
|
||||
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">
|
||||
<Toggle value={encryptOnCloud} onChange={setEncryptOnCloud} size={'sm'} />
|
||||
</div>
|
||||
</InputContainer>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||
{/*<Button size="sm">Add Location</Button>*/}
|
||||
<div className="mt-3 mb-3">
|
||||
<h1 className="text-2xl font-bold">Library database</h1>
|
||||
<p className="mt-1 text-sm text-gray-400">
|
||||
The database contains all library data and file metadata.
|
||||
</p>
|
||||
</div>
|
||||
<InputContainer
|
||||
mini
|
||||
title="Encrypt on cloud"
|
||||
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">
|
||||
<Toggle value={encryptOnCloud} onChange={setEncryptOnCloud} size={'sm'} />
|
||||
</div>
|
||||
</InputContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
import React from 'react';
|
||||
import { Button } from '@sd/ui';
|
||||
import React from 'react';
|
||||
|
||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
|
||||
const exampleLocations = [
|
||||
{ option: 'Macintosh HD', key: 'macintosh_hd' },
|
||||
{ option: 'LaCie External', key: 'lacie_external' },
|
||||
{ option: 'Seagate 8TB', key: 'seagate_8tb' }
|
||||
{ option: 'Macintosh HD', key: 'macintosh_hd' },
|
||||
{ option: 'LaCie External', key: 'lacie_external' },
|
||||
{ option: 'Seagate 8TB', key: 'seagate_8tb' }
|
||||
];
|
||||
|
||||
export default function LocationSettings() {
|
||||
// const locations = useBridgeQuery("SysGetLocation")
|
||||
// const locations = useBridgeQuery("SysGetLocation")
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||
{/*<Button size="sm">Add Location</Button>*/}
|
||||
<InputContainer
|
||||
title="Something about a vault"
|
||||
description="Local cache storage for media previews and thumbnails."
|
||||
>
|
||||
<div className="flex flex-row space-x-2"></div>
|
||||
</InputContainer>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||
{/*<Button size="sm">Add Location</Button>*/}
|
||||
<InputContainer
|
||||
title="Something about a vault"
|
||||
description="Local cache storage for media previews and thumbnails."
|
||||
>
|
||||
<div className="flex flex-row space-x-2"></div>
|
||||
</InputContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
import React from 'react';
|
||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
import { Button } from '@sd/ui';
|
||||
import React from 'react';
|
||||
|
||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
|
||||
export default function SecuritySettings() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<InputContainer
|
||||
title="Vault"
|
||||
description="You'll need to set a passphrase to enable the vault."
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<Button variant="primary">Enable Vault</Button>
|
||||
{/*<Input className="flex-grow" value="jeff" placeholder="/users/jamie/Desktop" />*/}
|
||||
</div>
|
||||
</InputContainer>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<InputContainer
|
||||
title="Vault"
|
||||
description="You'll need to set a passphrase to enable the vault."
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<Button variant="primary">Enable Vault</Button>
|
||||
{/*<Input className="flex-grow" value="jeff" placeholder="/users/jamie/Desktop" />*/}
|
||||
</div>
|
||||
</InputContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,114 +1,114 @@
|
|||
a,
|
||||
button {
|
||||
@apply cursor-default;
|
||||
@apply cursor-default;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'InterVariable', sans-serif;
|
||||
font-family: 'InterVariable', sans-serif;
|
||||
}
|
||||
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
||||
scrollbar-width: none; /* for Firefox */
|
||||
overflow-y: scroll;
|
||||
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
||||
scrollbar-width: none; /* for Firefox */
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.custom-scroll {
|
||||
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
||||
scrollbar-width: none; /* for Firefox */
|
||||
overflow-y: scroll;
|
||||
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
||||
scrollbar-width: none; /* for Firefox */
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.explorer-scroll {
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
@apply bg-[#00000006] dark:bg-[#00000030] mt-[55px] rounded-[6px];
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
@apply bg-[#00000006] dark:bg-[#00000030] mt-[55px] rounded-[6px];
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
||||
}
|
||||
}
|
||||
.default-scroll {
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
@apply bg-transparent rounded-[6px];
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
@apply bg-transparent rounded-[6px];
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
||||
}
|
||||
}
|
||||
.page-scroll {
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
@apply bg-transparent rounded-[6px] my-[10px];
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
@apply bg-transparent rounded-[6px] my-[10px];
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes slide-top {
|
||||
0% {
|
||||
-webkit-transform: translateY(0);
|
||||
transform: translateY(0);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateY(-50px);
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
0% {
|
||||
-webkit-transform: translateY(0);
|
||||
transform: translateY(0);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateY(-50px);
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
}
|
||||
@keyframes slide-top {
|
||||
0% {
|
||||
-webkit-transform: translateY(0);
|
||||
transform: translateY(0);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateY(-50px);
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
0% {
|
||||
-webkit-transform: translateY(0);
|
||||
transform: translateY(0);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateY(-50px);
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-overlay[data-state='open'] {
|
||||
animation: fadeIn 200ms ease-out forwards;
|
||||
animation: fadeIn 200ms ease-out forwards;
|
||||
}
|
||||
.dialog-overlay[data-state='closed'] {
|
||||
animation: fadeIn 200ms ease-out forwards;
|
||||
animation: fadeIn 200ms ease-out forwards;
|
||||
}
|
||||
.dialog-content[data-state='open'] {
|
||||
-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;
|
||||
-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;
|
||||
}
|
||||
.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",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["src"]
|
||||
"extends": "../config/interface.tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue