mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 13:23:28 +00:00
Merge branch 'main' into jamie-unsorted-changes
This commit is contained in:
parent
5175dcbbfb
commit
84827b1f9e
6
.prettierrc.cli.js
Normal file
6
.prettierrc.cli.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
var mainConfig = require('./.prettierrc.json');
|
||||
|
||||
module.exports = {
|
||||
...mainConfig,
|
||||
plugins: ['@trivago/prettier-plugin-sort-imports']
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"plugins": ["@trivago/prettier-plugin-sort-imports"],
|
||||
"pluginSearchDirs": ["."],
|
||||
"useTabs": true,
|
||||
"printWidth": 100,
|
||||
"singleQuote": true,
|
|
@ -5,9 +5,12 @@
|
|||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Spacedrive — A file manager from the future.</title>
|
||||
<meta name="description" content="Combine your drives and clouds into one database
|
||||
<meta
|
||||
name="description"
|
||||
content="Combine your drives and clouds into one database
|
||||
that you can organize and explore from any device. Designed for creators, hoarders and the
|
||||
painfully disorganized." />
|
||||
painfully disorganized."
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="files,file manager,spacedrive,file explorer,vdfs,distributed filesystem,cas,content addressable storage,virtual filesystem,photos app, video organizer,video encoder,tags,tag based filesystem"
|
||||
|
|
|
@ -84,7 +84,7 @@ export default function AppEmbed() {
|
|||
)}
|
||||
src={`${
|
||||
import.meta.env.VITE_SDWEB_BASE_URL || 'http://localhost:8002'
|
||||
}?library_id=9068c6ec-cf90-451b-bb30-4174781e7bc6`}
|
||||
}?showControls&library_id=9068c6ec-cf90-451b-bb30-4174781e7bc6`}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {
|
||||
Twitter,
|
||||
Discord,
|
||||
Instagram,
|
||||
Github,
|
||||
Instagram,
|
||||
Opencollective,
|
||||
Twitch
|
||||
Twitch,
|
||||
Twitter
|
||||
} from '@icons-pack/react-simple-icons';
|
||||
import React from 'react';
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"db:migrate": "pnpm core prisma migrate dev",
|
||||
"db:gen": "pnpm core prisma generate",
|
||||
"lint": "turbo run lint",
|
||||
"format": "prettier --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"",
|
||||
"format": "prettier --config .prettierrc.cli-only.js --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"",
|
||||
"desktop": "pnpm --filter @sd/desktop --",
|
||||
"mobile": "pnpm --filter @sd/mobile -- ",
|
||||
"web": "pnpm --filter @sd/web -- ",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"extends": "./base.tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["vite-plugin-svgr/client"]
|
||||
"types": ["vite-plugin-svgr/client", "vite/client"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"useTabs": false,
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"jsxBracketSameLine": false,
|
||||
"semi": true,
|
||||
"quoteProps": "consistent",
|
||||
"importOrder": [
|
||||
"^@sd/core/(.*)$",
|
||||
"^@sd/interface/(.*)$",
|
||||
"^@sd/client/(.*)$",
|
||||
"^@sd/ui/(.*)$",
|
||||
"<THIRD_PARTY_MODULES>",
|
||||
"^[./]"
|
||||
],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderSortSpecifiers": true
|
||||
}
|
|
@ -1,75 +1,75 @@
|
|||
{
|
||||
"name": "@sd/interface",
|
||||
"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-countup": "^6.2.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-countup": "^6.2.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';
|
||||
|
@ -58,63 +58,63 @@ export interface AppProps {
|
|||
}
|
||||
|
||||
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() {
|
||||
|
@ -146,89 +146,89 @@ function Router() {
|
|||
}
|
||||
|
||||
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,4 +1,4 @@
|
|||
<svg width="124" height="124" viewBox="0 0 124 124" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="25.6391" y="87.1574" width="87" height="14.1887" transform="rotate(-45 25.6391 87.1574)" fill="#7E0508"/>
|
||||
<rect x="26.5762" y="34.9421" width="13.7508" height="87" transform="rotate(-45 26.5762 34.9421)" fill="#7E0508"/>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.05806 8.94194C3.30214 9.18602 3.69786 9.18602 3.94194 8.94194L8.94194 3.94194C9.18602 3.69786 9.18602 3.30214 8.94194 3.05806C8.69786 2.81398 8.30214 2.81398 8.05806 3.05806L3.05806 8.05806C2.81398 8.30214 2.81398 8.69786 3.05806 8.94194Z" fill="#691109"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.94194 8.94194C8.69786 9.18602 8.30214 9.18602 8.05806 8.94194L3.05806 3.94194C2.81398 3.69786 2.81398 3.30214 3.05806 3.05806C3.30214 2.81398 3.69786 2.81398 3.94194 3.05806L8.94194 8.05806C9.18602 8.30214 9.18602 8.69786 8.94194 8.94194Z" fill="#691109"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 721 B |
|
@ -1,4 +1,4 @@
|
|||
<svg width="143" height="143" viewBox="0 0 143 143" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M108.242 32.8833C108.797 32.8817 109.247 33.3317 109.245 33.8862L109.092 87.5891C109.09 88.4786 108.014 88.9223 107.385 88.2933L53.8351 34.7432C53.2061 34.1142 53.6499 33.0387 54.5394 33.0361L108.242 32.8833Z" fill="#0B650D"/>
|
||||
<path d="M33.8862 109.245C33.3317 109.247 32.8818 108.797 32.8833 108.242L33.0361 54.5394C33.0387 53.6499 34.1142 53.2061 34.7432 53.8351L88.2934 107.385C88.9223 108.014 88.4786 109.09 87.5891 109.092L33.8862 109.245Z" fill="#0B650D"/>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 8C9 8.55228 8.55228 9 8 9H4.5L9 4.5V8Z" fill="#286017"/>
|
||||
<path d="M3 4C3 3.44772 3.44772 3 4 3L7.5 3L3 7.5L3 4Z" fill="#286017"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 579 B After Width: | Height: | Size: 245 B |
|
@ -1,3 +1,3 @@
|
|||
<svg width="87" height="16" viewBox="0 0 87 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M87 0H0V16H87V0Z" fill="#985712"/>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.75 6C9.75 5.58579 9.41421 5.25 9 5.25H3C2.58579 5.25 2.25 5.58579 2.25 6C2.25 6.41421 2.58579 6.75 3 6.75H9C9.41421 6.75 9.75 6.41421 9.75 6Z" fill="#8E591D"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 147 B After Width: | Height: | Size: 315 B |
|
@ -1,112 +1,113 @@
|
|||
import { KeyIcon } from '@heroicons/react/outline';
|
||||
import { CogIcon, LockClosedIcon } from '@heroicons/react/solid';
|
||||
import { Button } from '@sd/ui';
|
||||
import {
|
||||
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 || 'Unnamed Device'}</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 || 'Unnamed Device'}</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,91 +1,104 @@
|
|||
import { CameraIcon, LockClosedIcon, PhotographIcon } from '@heroicons/react/outline';
|
||||
import { CogIcon, EyeOffIcon, PlusIcon, ServerIcon } from '@heroicons/react/solid';
|
||||
import clsx from 'clsx';
|
||||
import { Camera, CirclesFour, Code, EjectSimple, MonitorPlay, Planet } from 'phosphor-react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { NavLink, NavLinkProps } from 'react-router-dom';
|
||||
import { TrafficLights } from '../os/TrafficLights';
|
||||
import { Button, Dropdown } from '@sd/ui';
|
||||
import { DefaultProps } from '../primitive/types';
|
||||
import { LockClosedIcon, PhotographIcon } from '@heroicons/react/outline';
|
||||
import { CogIcon, EyeOffIcon, PlusIcon } from '@heroicons/react/solid';
|
||||
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||
import RunningJobsWidget from '../jobs/RunningJobsWidget';
|
||||
import { AppPropsContext } from '../../App';
|
||||
import { Button, Dropdown } from '@sd/ui';
|
||||
import clsx from 'clsx';
|
||||
import { CirclesFour, Code, Planet } from 'phosphor-react';
|
||||
import React, { useContext } from 'react';
|
||||
import { NavLink, NavLinkProps } from 'react-router-dom';
|
||||
|
||||
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
|
||||
import { AppPropsContext } from '../../App';
|
||||
import { ReactComponent as FolderWhite } from '../../assets/svg/folder-white.svg';
|
||||
import { ReactComponent as 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 function MacOSTrafficLights() {
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
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 ">
|
||||
<TrafficLights
|
||||
onClose={appPropsContext?.onClose}
|
||||
onFullscreen={appPropsContext?.onFullscreen}
|
||||
onMinimize={appPropsContext?.onMinimize}
|
||||
className="p-1.5 z-50 absolute"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div data-tauri-drag-region className="h-7 flex-shrink-0">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function MacWindowControls() {
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
|
||||
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-2.5 overflow-x-hidden overflow-y-scroll border-r border-gray-100 no-scrollbar bg-gray-50 dark:bg-gray-850 dark:border-gray-600">
|
||||
{appPropsContext?.platform === 'browser' ? <MacOSTrafficLights /> : null}
|
||||
{appPropsContext?.platform === 'macOS' ? (
|
||||
<div data-tauri-drag-region className="h-[23px]" />
|
||||
) : null}
|
||||
return (
|
||||
<div className="flex flex-col flex-grow-0 flex-shrink-0 w-48 min-h-full px-2.5 overflow-x-hidden overflow-y-scroll border-r border-gray-100 no-scrollbar bg-gray-50 dark:bg-gray-850 dark:border-gray-600">
|
||||
{appPropsContext?.platform === 'browser' &&
|
||||
window.location.search.includes('showControls') ? (
|
||||
<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
|
||||
|
@ -95,127 +108,127 @@ 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>
|
||||
<SidebarLink to="photos">
|
||||
<Icon component={PhotographIcon} />
|
||||
Photos
|
||||
</SidebarLink>
|
||||
<div className="pt-1">
|
||||
<SidebarLink to="/overview">
|
||||
<Icon component={Planet} />
|
||||
Overview
|
||||
</SidebarLink>
|
||||
<SidebarLink to="content">
|
||||
<Icon component={CirclesFour} />
|
||||
Content
|
||||
</SidebarLink>
|
||||
<SidebarLink to="photos">
|
||||
<Icon component={PhotographIcon} />
|
||||
Photos
|
||||
</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,65 +1,77 @@
|
|||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import closeIconPath from '../../assets/svg/macos_close.svg';
|
||||
import fullscreenIconPath from '../../assets/svg/macos_fullscreen.svg';
|
||||
import minimizeIconPath from '../../assets/svg/macos_minimize.svg';
|
||||
import { useFocusState } from '../../hooks/useFocusState';
|
||||
import { 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>
|
||||
);
|
||||
export const MacTrafficLights: React.FC<TrafficLightsProps> = (props) => {
|
||||
const [focused] = useFocusState();
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={clsx('flex flex-row space-x-[8px] group', props.className)}
|
||||
>
|
||||
<TrafficLight type="close" onClick={props.onClose} colorful={focused} />
|
||||
<TrafficLight type="minimize" onClick={props.onMinimize} colorful={focused} />
|
||||
<TrafficLight type="fullscreen" onClick={props.onFullscreen} colorful={focused} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface LightProps {
|
||||
mode: 'close' | 'minimize' | 'fullscreen';
|
||||
focused: boolean;
|
||||
action?: () => void;
|
||||
interface TrafficLightProps {
|
||||
type: 'close' | 'minimize' | 'fullscreen';
|
||||
colorful: boolean;
|
||||
onClick?: React.HTMLAttributes<HTMLDivElement>['onClick'];
|
||||
}
|
||||
|
||||
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
|
||||
})}
|
||||
>
|
||||
{(() => {
|
||||
if (!props.focused) return <></>;
|
||||
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>
|
||||
);
|
||||
const TrafficLight: React.FC<TrafficLightProps> = (props) => {
|
||||
const { onClick = () => undefined, colorful = false, type } = props;
|
||||
const iconPath = React.useRef<string>(closeIconPath);
|
||||
|
||||
useEffect(() => {
|
||||
switch (type) {
|
||||
case 'close':
|
||||
iconPath.current = closeIconPath;
|
||||
break;
|
||||
case 'minimize':
|
||||
iconPath.current = minimizeIconPath;
|
||||
break;
|
||||
case 'fullscreen':
|
||||
iconPath.current = fullscreenIconPath;
|
||||
break;
|
||||
}
|
||||
}, [type]);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={clsx(
|
||||
'rounded-full box-border w-[12px] h-[12px] border-[0.5px] border-transparent bg-[#CDCED0] dark:bg-[#2B2C2F] flex justify-center items-center',
|
||||
{
|
||||
'border-red-900 !bg-[#EC6A5E] active:hover:!bg-red-700 dark:active:hover:!bg-red-400':
|
||||
type === 'close' && colorful,
|
||||
'border-yellow-900 !bg-[#F4BE4F] active:hover:!bg-yellow-600 dark:active:hover:!bg-yellow-200':
|
||||
type === 'minimize' && colorful,
|
||||
'border-green-900 !bg-[#61C253] active:hover:!bg-green-700 dark:active:hover:!bg-green-300':
|
||||
type === 'fullscreen' && colorful
|
||||
}
|
||||
)}
|
||||
>
|
||||
{colorful && (
|
||||
<img
|
||||
src={iconPath.current}
|
||||
className="opacity-0 group-hover:opacity-100 group-active:opacity-100 pointer-events-none"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,22 +2,22 @@ import clsx from 'clsx';
|
|||
import React from 'react';
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,58 +1,58 @@
|
|||
{
|
||||
"node_state": {
|
||||
"node_pub_id": "f4deae23-e578-456e-8897-9ff59444c08b",
|
||||
"node_id": 1,
|
||||
"node_name": "Jamie's MacBook Pro",
|
||||
"data_path": "/Users/jamie/Library/Application Support/spacedrive",
|
||||
"tcp_port": 0,
|
||||
"libraries": [
|
||||
{
|
||||
"library_uuid": "9816efb4-5f2d-4c84-8674-818767bb1184",
|
||||
"library_id": 0,
|
||||
"library_path": "/Users/jamie/Library/Application Support/spacedrive/library.db",
|
||||
"offline": false
|
||||
}
|
||||
],
|
||||
"current_library_uuid": "9816efb4-5f2d-4c84-8674-818767bb1184"
|
||||
},
|
||||
"libraries": [
|
||||
{
|
||||
"id": 1,
|
||||
"pub_id": "9816efb4-5f2d-4c84-8674-818767bb1184",
|
||||
"date_created": "2020-04-01T00:00:00.000Z",
|
||||
"is_primary": true
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"pub_id": "f4deae23-e578-456e-8897-9ff59444c08b",
|
||||
"name": "Jamie's MacBook Pro",
|
||||
"platform": "macos",
|
||||
"version": "0.0.1",
|
||||
"date_created": "2020-04-01T00:00:00.000Z",
|
||||
"last_seen": "2020-04-01T00:00:00.000Z",
|
||||
"timezone": "America/Los_Angeles"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"pub_id": "8fec7649-08d8-4828-baa3-e1078d9f15ee",
|
||||
"name": "Jamie's iPhone 12",
|
||||
"platform": "ios",
|
||||
"version": "0.0.1",
|
||||
"date_created": "2020-04-01T00:00:00.000Z",
|
||||
"last_seen": "2020-04-01T00:00:00.000Z",
|
||||
"timezone": "America/Los_Angeles"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"pub_id": "67f66377-d38e-4fb7-961a-c1193a58b458",
|
||||
"name": "Spacedrive Server",
|
||||
"platform": "server",
|
||||
"version": "0.0.1",
|
||||
"date_created": "2020-04-01T00:00:00.000Z",
|
||||
"last_seen": "2020-04-01T00:00:00.000Z",
|
||||
"timezone": "America/Los_Angeles"
|
||||
}
|
||||
]
|
||||
"node_state": {
|
||||
"node_pub_id": "f4deae23-e578-456e-8897-9ff59444c08b",
|
||||
"node_id": 1,
|
||||
"node_name": "Jamie's MacBook Pro",
|
||||
"data_path": "/Users/jamie/Library/Application Support/spacedrive",
|
||||
"tcp_port": 0,
|
||||
"libraries": [
|
||||
{
|
||||
"library_uuid": "9816efb4-5f2d-4c84-8674-818767bb1184",
|
||||
"library_id": 0,
|
||||
"library_path": "/Users/jamie/Library/Application Support/spacedrive/library.db",
|
||||
"offline": false
|
||||
}
|
||||
],
|
||||
"current_library_uuid": "9816efb4-5f2d-4c84-8674-818767bb1184"
|
||||
},
|
||||
"libraries": [
|
||||
{
|
||||
"id": 1,
|
||||
"pub_id": "9816efb4-5f2d-4c84-8674-818767bb1184",
|
||||
"date_created": "2020-04-01T00:00:00.000Z",
|
||||
"is_primary": true
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"pub_id": "f4deae23-e578-456e-8897-9ff59444c08b",
|
||||
"name": "Jamie's MacBook Pro",
|
||||
"platform": "macos",
|
||||
"version": "0.0.1",
|
||||
"date_created": "2020-04-01T00:00:00.000Z",
|
||||
"last_seen": "2020-04-01T00:00:00.000Z",
|
||||
"timezone": "America/Los_Angeles"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"pub_id": "8fec7649-08d8-4828-baa3-e1078d9f15ee",
|
||||
"name": "Jamie's iPhone 12",
|
||||
"platform": "ios",
|
||||
"version": "0.0.1",
|
||||
"date_created": "2020-04-01T00:00:00.000Z",
|
||||
"last_seen": "2020-04-01T00:00:00.000Z",
|
||||
"timezone": "America/Los_Angeles"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"pub_id": "67f66377-d38e-4fb7-961a-c1193a58b458",
|
||||
"name": "Spacedrive Server",
|
||||
"platform": "server",
|
||||
"version": "0.0.1",
|
||||
"date_created": "2020-04-01T00:00:00.000Z",
|
||||
"last_seen": "2020-04-01T00:00:00.000Z",
|
||||
"timezone": "America/Los_Angeles"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,45 +1,46 @@
|
|||
import { useContext, useEffect } from 'react';
|
||||
import { CoreEvent } from '@sd/core';
|
||||
import { transport } from '@sd/client';
|
||||
import { 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,16 +1,15 @@
|
|||
// 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">
|
||||
<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>
|
||||
</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">
|
||||
<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>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,196 +1,193 @@
|
|||
import { CloudIcon } from '@heroicons/react/outline';
|
||||
import { CogIcon, MenuIcon, PlusIcon } from '@heroicons/react/solid';
|
||||
import { MenuIcon, PlusIcon } from '@heroicons/react/solid';
|
||||
import { useBridgeQuery } from '@sd/client';
|
||||
import { Statistics } from '@sd/core';
|
||||
import { Button } from '@sd/ui';
|
||||
import byteSize from 'byte-size';
|
||||
import { DotsSixVertical, Laptop, LineSegments, Plus } from 'phosphor-react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { AppPropsContext } from '../App';
|
||||
import { Device } from '../components/device/Device';
|
||||
import FileItem from '../components/file/FileItem';
|
||||
import Dialog from '../components/layout/Dialog';
|
||||
import { Input } from '../components/primitive';
|
||||
import { InputContainer } from '../components/primitive/InputContainer';
|
||||
import { useCountUp } from 'react-countup';
|
||||
|
||||
import { AppPropsContext } from '../App';
|
||||
import { Device } from '../components/device/Device';
|
||||
import Dialog from '../components/layout/Dialog';
|
||||
import { Input } from '../components/primitive';
|
||||
|
||||
interface StatItemProps {
|
||||
name: string;
|
||||
value?: string;
|
||||
unit?: string;
|
||||
name: string;
|
||||
value?: string;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
const StatItem: React.FC<StatItemProps> = (props) => {
|
||||
const countUpRef = React.useRef(null);
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
let size = byteSize(Number(props.value) || 0);
|
||||
const countUpRef = React.useRef(null);
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
let size = byteSize(Number(props.value) || 0);
|
||||
|
||||
let amount = parseFloat(size.value);
|
||||
let amount = parseFloat(size.value);
|
||||
|
||||
const [hasRun, setHasRun] = useState(false);
|
||||
const [hasRun, setHasRun] = useState(false);
|
||||
|
||||
const { update } = useCountUp({
|
||||
startOnMount: !hasRun,
|
||||
ref: countUpRef,
|
||||
// start: amount / 2,
|
||||
end: amount,
|
||||
delay: 0.1,
|
||||
decimals: 1,
|
||||
duration: appPropsContext?.demoMode ? 2 : 1,
|
||||
useEasing: true,
|
||||
onEnd: () => {
|
||||
setHasRun(true);
|
||||
}
|
||||
});
|
||||
const { update } = useCountUp({
|
||||
startOnMount: !hasRun,
|
||||
ref: countUpRef,
|
||||
// start: amount / 2,
|
||||
end: amount,
|
||||
delay: 0.1,
|
||||
decimals: 1,
|
||||
duration: appPropsContext?.demoMode ? 2 : 1,
|
||||
useEasing: true,
|
||||
onEnd: () => {
|
||||
setHasRun(true);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
update(amount);
|
||||
}, [amount]);
|
||||
useEffect(() => {
|
||||
update(amount);
|
||||
}, [amount]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-shrink-0 w-32 px-4 py-3 duration-75 transform rounded-md cursor-default hover:bg-gray-50 hover:dark:bg-gray-600">
|
||||
<span className="text-sm text-gray-400">{props.name}</span>
|
||||
<span className="text-2xl font-bold">
|
||||
<span ref={countUpRef} />
|
||||
<span className="ml-1 text-[16px] text-gray-400">{size.unit}</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col flex-shrink-0 w-32 px-4 py-3 duration-75 transform rounded-md cursor-default hover:bg-gray-50 hover:dark:bg-gray-600">
|
||||
<span className="text-sm text-gray-400">{props.name}</span>
|
||||
<span className="text-2xl font-bold">
|
||||
<span ref={countUpRef} />
|
||||
<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');
|
||||
|
||||
const [stats, setStats] = useState<Statistics>(libraryStatistics || ({} as Statistics));
|
||||
const [stats, setStats] = useState<Statistics>(libraryStatistics || ({} as Statistics));
|
||||
|
||||
// get app props context
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
// get app props context
|
||||
const appPropsContext = useContext(AppPropsContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (appPropsContext?.demoMode == true && !libraryStatistics?.library_db_size) {
|
||||
setStats({
|
||||
total_bytes_capacity: '8093333345230',
|
||||
preview_media_bytes: '2304387532',
|
||||
library_db_size: '83345230',
|
||||
total_file_count: 20342345,
|
||||
total_bytes_free: '89734502034',
|
||||
total_bytes_used: '8093333345230',
|
||||
total_unique_bytes: '9347397'
|
||||
});
|
||||
} else {
|
||||
setStats(libraryStatistics as Statistics);
|
||||
}
|
||||
}, [appPropsContext, libraryStatistics]);
|
||||
useEffect(() => {
|
||||
if (appPropsContext?.demoMode == true && !libraryStatistics?.library_db_size) {
|
||||
setStats({
|
||||
total_bytes_capacity: '8093333345230',
|
||||
preview_media_bytes: '2304387532',
|
||||
library_db_size: '83345230',
|
||||
total_file_count: 20342345,
|
||||
total_bytes_free: '89734502034',
|
||||
total_bytes_used: '8093333345230',
|
||||
total_unique_bytes: '9347397'
|
||||
});
|
||||
} else {
|
||||
setStats(libraryStatistics as Statistics);
|
||||
}
|
||||
}, [appPropsContext, libraryStatistics]);
|
||||
|
||||
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-5" />
|
||||
{/* PAGE */}
|
||||
<div className="flex flex-col w-full h-screen px-3">
|
||||
{/* STAT HEADER */}
|
||||
<div className="flex w-full">
|
||||
{/* STAT CONTAINER */}
|
||||
<div className="flex pb-4 overflow-hidden">
|
||||
<StatItem
|
||||
name="Total capacity"
|
||||
value={stats?.total_bytes_capacity}
|
||||
unit={stats?.total_bytes_capacity}
|
||||
/>
|
||||
<StatItem
|
||||
name="Index size"
|
||||
value={stats?.library_db_size}
|
||||
unit={stats?.library_db_size}
|
||||
/>
|
||||
<StatItem
|
||||
name="Preview media"
|
||||
value={stats?.preview_media_bytes}
|
||||
unit={stats?.preview_media_bytes}
|
||||
/>
|
||||
<StatItem
|
||||
name="Free space"
|
||||
value={stats?.total_bytes_free}
|
||||
unit={stats?.total_bytes_free}
|
||||
/>
|
||||
<StatItem name="Total at-risk" value={'0'} unit={stats?.preview_media_bytes} />
|
||||
{/* <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-5" />
|
||||
{/* PAGE */}
|
||||
<div className="flex flex-col w-full h-screen px-3">
|
||||
{/* STAT HEADER */}
|
||||
<div className="flex w-full">
|
||||
{/* STAT CONTAINER */}
|
||||
<div className="flex pb-4 overflow-hidden">
|
||||
<StatItem
|
||||
name="Total capacity"
|
||||
value={stats?.total_bytes_capacity}
|
||||
unit={stats?.total_bytes_capacity}
|
||||
/>
|
||||
<StatItem
|
||||
name="Index size"
|
||||
value={stats?.library_db_size}
|
||||
unit={stats?.library_db_size}
|
||||
/>
|
||||
<StatItem
|
||||
name="Preview media"
|
||||
value={stats?.preview_media_bytes}
|
||||
unit={stats?.preview_media_bytes}
|
||||
/>
|
||||
<StatItem
|
||||
name="Free space"
|
||||
value={stats?.total_bytes_free}
|
||||
unit={stats?.total_bytes_free}
|
||||
/>
|
||||
<StatItem name="Total at-risk" value={'0'} unit={stats?.preview_media_bytes} />
|
||||
{/* <StatItem
|
||||
name="Total at-risk"
|
||||
value={'0'}
|
||||
unit={stats?.preview_media_bytes}
|
||||
/>
|
||||
<StatItem name="Total backed up" value={'0'} unit={''} /> */}
|
||||
</div>
|
||||
<div className="flex-grow" />
|
||||
<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"
|
||||
className="hidden sm:visible"
|
||||
>
|
||||
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="flex-grow" />
|
||||
<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"
|
||||
className="hidden sm:visible"
|
||||
>
|
||||
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="flex flex-col pb-4 space-y-4">
|
||||
<Device
|
||||
name="James' MacBook Pro"
|
||||
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>
|
||||
<div className="px-5 py-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||
functional.
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 w-full h-4" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<Button
|
||||
size="sm"
|
||||
className="w-8"
|
||||
noPadding
|
||||
icon={<MenuIcon className="inline w-4 h-4" />}
|
||||
variant="gray"
|
||||
></Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col pb-4 space-y-4">
|
||||
<Device
|
||||
name="James' MacBook Pro"
|
||||
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>
|
||||
<div className="px-5 py-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||
functional.
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 w-full h-4" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
// import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const PhotosScreen: 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">
|
||||
<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>
|
||||
</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">
|
||||
<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>
|
||||
</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,17 +2,17 @@ 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="w-full p-5">
|
||||
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||
functional.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="w-full p-5">
|
||||
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||
functional.
|
||||
</p>
|
||||
</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 pl-10">
|
||||
<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 pl-10">
|
||||
<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 pl-10">
|
||||
<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 pl-10">
|
||||
<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