Merge branch 'main' into jamie-unsorted-changes

This commit is contained in:
maxichrome 2022-05-23 04:40:50 -05:00
parent 5175dcbbfb
commit 84827b1f9e
No known key found for this signature in database
GPG key ID: DDC459310E98B6AB
62 changed files with 16052 additions and 15793 deletions

6
.prettierrc.cli.js Normal file
View file

@ -0,0 +1,6 @@
var mainConfig = require('./.prettierrc.json');
module.exports = {
...mainConfig,
plugins: ['@trivago/prettier-plugin-sort-imports']
};

View file

@ -1,5 +1,5 @@
{
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"pluginSearchDirs": ["."],
"useTabs": true,
"printWidth": 100,
"singleQuote": true,

View file

@ -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"

View file

@ -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`}
/>
)}

View file

@ -1,10 +1,10 @@
import {
Twitter,
Discord,
Instagram,
Github,
Instagram,
Opencollective,
Twitch
Twitch,
Twitter
} from '@icons-pack/react-simple-icons';
import React from 'react';

View file

@ -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 -- ",

View file

@ -1,6 +1,6 @@
{
"extends": "./base.tsconfig.json",
"compilerOptions": {
"types": ["vite-plugin-svgr/client"]
"types": ["vite-plugin-svgr/client", "vite/client"]
}
}

View file

@ -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
}

View file

@ -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"
}
}

View file

@ -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>
</>
);
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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>
);
}

View file

@ -1,5 +1,5 @@
import create from 'zustand';
export const useStore = create((set) => ({
experimental: false
experimental: false
}));

View file

@ -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>
);
}

View file

@ -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 <></>;
}
};

View file

@ -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>;
}

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>
);
};

View file

@ -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" /> */}
</>
);
};

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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}
/>
);
}

View file

@ -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>
);

View file

@ -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>
);
};

View file

@ -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>
</>
);
}

View file

@ -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;

View file

@ -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>
);
};

View file

@ -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;

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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']
}
})
};

View file

@ -1,3 +1,3 @@
export interface DefaultProps {
className?: string;
className?: string;
}

View file

@ -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>
);
}

View file

@ -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

View file

@ -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]);
}

View file

@ -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];
}

View file

@ -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
};
}

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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;
};

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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;
}

View file

@ -1,7 +1,7 @@
{
"extends": "../config/interface.tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src"]
"extends": "../config/interface.tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src"]
}