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, "useTabs": true,
"printWidth": 100, "printWidth": 100,
"singleQuote": true, "singleQuote": true,

View file

@ -5,9 +5,12 @@
<link rel="icon" type="image/svg+xml" href="/favicon.ico" /> <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spacedrive — A file manager from the future.</title> <title>Spacedrive — A file manager from the future.</title>
<meta name="description" content="Combine your drives and clouds into one database <meta
name="description"
content="Combine your drives and clouds into one database
that you can organize and explore from any device. Designed for creators, hoarders and the that you can organize and explore from any device. Designed for creators, hoarders and the
painfully disorganized." /> painfully disorganized."
/>
<meta <meta
name="keywords" name="keywords"
content="files,file manager,spacedrive,file explorer,vdfs,distributed filesystem,cas,content addressable storage,virtual filesystem,photos app, video organizer,video encoder,tags,tag based filesystem" content="files,file manager,spacedrive,file explorer,vdfs,distributed filesystem,cas,content addressable storage,virtual filesystem,photos app, video organizer,video encoder,tags,tag based filesystem"

View file

@ -84,7 +84,7 @@ export default function AppEmbed() {
)} )}
src={`${ src={`${
import.meta.env.VITE_SDWEB_BASE_URL || 'http://localhost:8002' import.meta.env.VITE_SDWEB_BASE_URL || 'http://localhost:8002'
}?library_id=9068c6ec-cf90-451b-bb30-4174781e7bc6`} }?showControls&library_id=9068c6ec-cf90-451b-bb30-4174781e7bc6`}
/> />
)} )}

View file

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

View file

@ -9,7 +9,7 @@
"db:migrate": "pnpm core prisma migrate dev", "db:migrate": "pnpm core prisma migrate dev",
"db:gen": "pnpm core prisma generate", "db:gen": "pnpm core prisma generate",
"lint": "turbo run lint", "lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"", "format": "prettier --config .prettierrc.cli-only.js --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"",
"desktop": "pnpm --filter @sd/desktop --", "desktop": "pnpm --filter @sd/desktop --",
"mobile": "pnpm --filter @sd/mobile -- ", "mobile": "pnpm --filter @sd/mobile -- ",
"web": "pnpm --filter @sd/web -- ", "web": "pnpm --filter @sd/web -- ",

View file

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

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", "name": "@sd/interface",
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
".": "./src/index.ts", ".": "./src/index.ts",
"./types": "./src/types" "./types": "./src/types"
}, },
"scripts": { "scripts": {
"icons": "ts-node ./scripts/generateSvgImports.mjs" "icons": "ts-node ./scripts/generateSvgImports.mjs"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.5.10", "@apollo/client": "^3.5.10",
"@fontsource/inter": "^4.5.7", "@fontsource/inter": "^4.5.7",
"@headlessui/react": "^1.5.0", "@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.6", "@heroicons/react": "^1.0.6",
"@radix-ui/react-dialog": "^0.1.7", "@radix-ui/react-dialog": "^0.1.7",
"@radix-ui/react-dropdown-menu": "^0.1.6", "@radix-ui/react-dropdown-menu": "^0.1.6",
"@radix-ui/react-icons": "^1.1.0", "@radix-ui/react-icons": "^1.1.0",
"@radix-ui/react-progress": "^0.1.4", "@radix-ui/react-progress": "^0.1.4",
"@radix-ui/react-slider": "^0.1.4", "@radix-ui/react-slider": "^0.1.4",
"@sd/client": "workspace:*", "@sd/client": "workspace:*",
"@sd/core": "workspace:*", "@sd/core": "workspace:*",
"@sd/ui": "workspace:*", "@sd/ui": "workspace:*",
"@vitejs/plugin-react": "^1.3.1", "@vitejs/plugin-react": "^1.3.1",
"autoprefixer": "^10.4.4", "autoprefixer": "^10.4.4",
"byte-size": "^8.1.0", "byte-size": "^8.1.0",
"clsx": "^1.1.1", "clsx": "^1.1.1",
"immer": "^9.0.12", "immer": "^9.0.12",
"jotai": "^1.6.2", "jotai": "^1.6.2",
"moment": "^2.29.2", "moment": "^2.29.2",
"phosphor-react": "^1.4.1", "phosphor-react": "^1.4.1",
"pretty-bytes": "^6.0.0", "pretty-bytes": "^6.0.0",
"react": "^18.0.0", "react": "^18.0.0",
"react-countup": "^6.2.0", "react-countup": "^6.2.0",
"react-dom": "^18.0.0", "react-dom": "^18.0.0",
"react-dropzone": "^12.0.4", "react-dropzone": "^12.0.4",
"react-error-boundary": "^3.1.4", "react-error-boundary": "^3.1.4",
"react-hotkeys-hook": "^3.4.4", "react-hotkeys-hook": "^3.4.4",
"react-json-view": "^1.21.3", "react-json-view": "^1.21.3",
"react-loading-icons": "^1.0.8", "react-loading-icons": "^1.0.8",
"react-portal": "^4.2.2", "react-portal": "^4.2.2",
"react-query": "^3.34.19", "react-query": "^3.34.19",
"react-router": "6.3.0", "react-router": "6.3.0",
"react-router-dom": "6.3.0", "react-router-dom": "6.3.0",
"react-scrollbars-custom": "^4.0.27", "react-scrollbars-custom": "^4.0.27",
"react-spline": "^1.2.1", "react-spline": "^1.2.1",
"react-transition-group": "^4.4.2", "react-transition-group": "^4.4.2",
"react-virtuoso": "^2.9.0", "react-virtuoso": "^2.9.0",
"rooks": "^5.11.0", "rooks": "^5.11.0",
"tailwindcss": "^3.0.23", "tailwindcss": "^3.0.23",
"zustand": "^3.7.2" "zustand": "^3.7.2"
}, },
"devDependencies": { "devDependencies": {
"@types/babel-core": "^6.25.7", "@types/babel-core": "^6.25.7",
"@types/byte-size": "^8.1.0", "@types/byte-size": "^8.1.0",
"@types/lodash": "^4.14.182", "@types/lodash": "^4.14.182",
"@types/node": "^17.0.23", "@types/node": "^17.0.23",
"@types/pretty-bytes": "^5.2.0", "@types/pretty-bytes": "^5.2.0",
"@types/react": "^18.0.8", "@types/react": "^18.0.8",
"@types/react-dom": "^18.0.0", "@types/react-dom": "^18.0.0",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"@types/react-table": "^7.7.10", "@types/react-table": "^7.7.10",
"@types/react-window": "^1.8.5", "@types/react-window": "^1.8.5",
"@types/tailwindcss": "^3.0.10", "@types/tailwindcss": "^3.0.10",
"@vitejs/plugin-react": "^1.3.1", "@vitejs/plugin-react": "^1.3.1",
"concurrently": "^7.1.0", "concurrently": "^7.1.0",
"prettier": "^2.6.2", "prettier": "^2.6.2",
"typescript": "^4.6.3", "typescript": "^4.6.3",
"vite": "^2.9.1", "vite": "^2.9.1",
"vite-plugin-svgr": "^1.1.0" "vite-plugin-svgr": "^1.1.0"
} }
} }

View file

@ -9,13 +9,13 @@ import React, { useContext, useEffect, useState } from 'react';
import { ErrorBoundary, FallbackProps } from 'react-error-boundary'; import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
import { QueryClient, QueryClientProvider } from 'react-query'; import { QueryClient, QueryClientProvider } from 'react-query';
import { import {
Location, Location,
MemoryRouter, MemoryRouter,
Outlet, Outlet,
Route, Route,
Routes, Routes,
useLocation, useLocation,
useNavigate useNavigate
} from 'react-router-dom'; } from 'react-router-dom';
import { Sidebar } from './components/file/Sidebar'; import { Sidebar } from './components/file/Sidebar';
import { Modal } from './components/layout/Modal'; import { Modal } from './components/layout/Modal';
@ -58,63 +58,63 @@ export interface AppProps {
} }
function AppLayout() { function AppLayout() {
const appPropsContext = useContext(AppPropsContext); const appPropsContext = useContext(AppPropsContext);
const [isWindowRounded, setIsWindowRounded] = useState(false); const [isWindowRounded, setIsWindowRounded] = useState(false);
const [hasWindowBorder, setHasWindowBorder] = useState(true); const [hasWindowBorder, setHasWindowBorder] = useState(true);
useEffect(() => { useEffect(() => {
if (appPropsContext?.platform === 'macOS') { if (appPropsContext?.platform === 'macOS') {
setIsWindowRounded(true); setIsWindowRounded(true);
} }
if (appPropsContext?.platform === 'browser') { if (appPropsContext?.platform === 'browser') {
setHasWindowBorder(false); setHasWindowBorder(false);
} }
}, []); }, []);
return ( return (
<div <div
className={clsx( className={clsx(
'flex flex-row h-screen overflow-hidden text-gray-900 bg-white select-none dark:text-white dark:bg-gray-650', 'flex flex-row h-screen overflow-hidden text-gray-900 bg-white select-none dark:text-white dark:bg-gray-650',
isWindowRounded && 'rounded-xl', isWindowRounded && 'rounded-xl',
hasWindowBorder && 'border border-gray-200 dark:border-gray-500' hasWindowBorder && 'border border-gray-200 dark:border-gray-500'
)} )}
> >
<Sidebar /> <Sidebar />
<div className="flex flex-col w-full min-h-full"> <div className="flex flex-col w-full min-h-full">
{/* <TopBar /> */} {/* <TopBar /> */}
<div className="relative flex w-full"> <div className="relative flex w-full">
<Outlet /> <Outlet />
</div> </div>
</div> </div>
</div> </div>
); );
} }
function SettingsRoutes({ modal = false }) { function SettingsRoutes({ modal = false }) {
return ( return (
<SlideUp> <SlideUp>
<Routes> <Routes>
<Route <Route
path={modal ? '/settings' : '/'} path={modal ? '/settings' : '/'}
element={modal ? <Modal children={<SettingsScreen />} /> : <SettingsScreen />} element={modal ? <Modal children={<SettingsScreen />} /> : <SettingsScreen />}
> >
<Route index element={<GeneralSettings />} /> <Route index element={<GeneralSettings />} />
<Route path="general" element={<GeneralSettings />} /> <Route path="general" element={<GeneralSettings />} />
<Route path="security" element={<SecuritySettings />} /> <Route path="security" element={<SecuritySettings />} />
<Route path="appearance" element={<></>} /> <Route path="appearance" element={<></>} />
<Route path="experimental" element={<ExperimentalSettings />} /> <Route path="experimental" element={<ExperimentalSettings />} />
<Route path="locations" element={<LocationSettings />} /> <Route path="locations" element={<LocationSettings />} />
<Route path="library" element={<LibrarySettings />} /> <Route path="library" element={<LibrarySettings />} />
<Route path="media" element={<></>} /> <Route path="media" element={<></>} />
<Route path="keys" element={<></>} /> <Route path="keys" element={<></>} />
<Route path="tags" element={<></>} /> <Route path="tags" element={<></>} />
<Route path="sync" element={<></>} /> <Route path="sync" element={<></>} />
<Route path="contacts" element={<></>} /> <Route path="contacts" element={<></>} />
</Route> </Route>
</Routes> </Routes>
</SlideUp> </SlideUp>
); );
} }
function Router() { function Router() {
@ -146,89 +146,89 @@ function Router() {
} }
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return ( return (
<div <div
data-tauri-drag-region data-tauri-drag-region
role="alert" role="alert"
className="flex flex-col items-center justify-center w-screen h-screen p-4 border border-gray-200 rounded-lg dark:border-gray-650 bg-gray-50 dark:bg-gray-650 dark:text-white" className="flex flex-col items-center justify-center w-screen h-screen p-4 border border-gray-200 rounded-lg dark:border-gray-650 bg-gray-50 dark:bg-gray-650 dark:text-white"
> >
<p className="m-3 text-sm font-bold text-gray-400">APP CRASHED</p> <p className="m-3 text-sm font-bold text-gray-400">APP CRASHED</p>
<h1 className="text-2xl font-bold">We're past the event horizon...</h1> <h1 className="text-2xl font-bold">We're past the event horizon...</h1>
<pre className="m-2">Error: {error.message}</pre> <pre className="m-2">Error: {error.message}</pre>
<div className="flex flex-row space-x-2"> <div className="flex flex-row space-x-2">
<Button variant="primary" className="mt-2" onClick={resetErrorBoundary}> <Button variant="primary" className="mt-2" onClick={resetErrorBoundary}>
Reload Reload
</Button> </Button>
<Button className="mt-2" onClick={resetErrorBoundary}> <Button className="mt-2" onClick={resetErrorBoundary}>
Send report Send report
</Button> </Button>
</div> </div>
</div> </div>
); );
} }
function NotFound() { function NotFound() {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<div <div
data-tauri-drag-region data-tauri-drag-region
role="alert" role="alert"
className="flex flex-col items-center justify-center w-full h-full p-4 rounded-lg dark:text-white" className="flex flex-col items-center justify-center w-full h-full p-4 rounded-lg dark:text-white"
> >
<p className="m-3 mt-20 text-sm font-semibold text-gray-500 uppercase">Error: 404</p> <p className="m-3 mt-20 text-sm font-semibold text-gray-500 uppercase">Error: 404</p>
<h1 className="text-4xl font-bold">You chose nothingness.</h1> <h1 className="text-4xl font-bold">You chose nothingness.</h1>
<div className="flex flex-row space-x-2"> <div className="flex flex-row space-x-2">
<Button variant="primary" className="mt-4" onClick={() => navigate(-1)}> <Button variant="primary" className="mt-4" onClick={() => navigate(-1)}>
Go Back Go Back
</Button> </Button>
</div> </div>
</div> </div>
); );
} }
function MemoryRouterContainer() { function MemoryRouterContainer() {
useCoreEvents(); useCoreEvents();
return ( return (
<MemoryRouter> <MemoryRouter>
<Router /> <Router />
</MemoryRouter> </MemoryRouter>
); );
} }
function BrowserRouterContainer() { function BrowserRouterContainer() {
useCoreEvents(); useCoreEvents();
return ( return (
<MemoryRouter> <MemoryRouter>
<Router /> <Router />
</MemoryRouter> </MemoryRouter>
); );
} }
export function bindCoreEvent() {} export function bindCoreEvent() {}
export default function App(props: AppProps) { export default function App(props: AppProps) {
// TODO: This is a hack and a better solution should probably be found. // TODO: This is a hack and a better solution should probably be found.
// This exists so that the queryClient can be accessed within the subpackage '@sd/client'. // This exists so that the queryClient can be accessed within the subpackage '@sd/client'.
// Refer to <ClientProvider /> for where this is used. // Refer to <ClientProvider /> for where this is used.
window.ReactQueryClient ??= queryClient; window.ReactQueryClient ??= queryClient;
setTransport(props.transport); setTransport(props.transport);
console.log('App props', props); console.log('App props', props);
return ( return (
<> <>
{/* @ts-ignore */} {/* @ts-ignore */}
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}> <ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}>
{/* @ts-ignore */} {/* @ts-ignore */}
<QueryClientProvider client={queryClient} contextSharing={false}> <QueryClientProvider client={queryClient} contextSharing={false}>
<AppPropsContext.Provider value={Object.assign({ isFocused: true }, props)}> <AppPropsContext.Provider value={Object.assign({ isFocused: true }, props)}>
<ClientProvider> <ClientProvider>
{props.useMemoryRouter ? <MemoryRouterContainer /> : <BrowserRouterContainer />} {props.useMemoryRouter ? <MemoryRouterContainer /> : <BrowserRouterContainer />}
</ClientProvider> </ClientProvider>
</AppPropsContext.Provider> </AppPropsContext.Provider>
</QueryClientProvider> </QueryClientProvider>
</ErrorBoundary> </ErrorBoundary>
</> </>
); );
} }

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 Folder_open } from './folder_open.svg';
import { ReactComponent as Fontotf } from './fontotf.svg'; import { ReactComponent as Fontotf } from './fontotf.svg';
import { ReactComponent as Fontttf } from './fontttf.svg'; import { ReactComponent as Fontttf } from './fontttf.svg';
import { ReactComponent as Fontwoff } from './fontwoff.svg';
import { ReactComponent as Fontwoff2 } from './fontwoff2.svg'; import { ReactComponent as Fontwoff2 } from './fontwoff2.svg';
import { ReactComponent as Fontwoff } from './fontwoff.svg';
import { ReactComponent as Git } from './git.svg'; import { ReactComponent as Git } from './git.svg';
import { ReactComponent as Go } from './go.svg'; import { ReactComponent as Go } from './go.svg';
import { ReactComponent as Gopackage } from './gopackage.svg'; import { ReactComponent as Gopackage } from './gopackage.svg';
@ -172,176 +172,176 @@ import { ReactComponent as Yarnerror } from './yarnerror.svg';
import { ReactComponent as Zip } from './zip.svg'; import { ReactComponent as Zip } from './zip.svg';
export default { export default {
ai: Ai, ai: Ai,
angular: Angular, angular: Angular,
audio: Audio, audio: Audio,
audiomp3: Audiomp3, audiomp3: Audiomp3,
audioogg: Audioogg, audioogg: Audioogg,
audiowav: Audiowav, audiowav: Audiowav,
babel: Babel, babel: Babel,
bat: Bat, bat: Bat,
bicep: Bicep, bicep: Bicep,
binary: Binary, binary: Binary,
blade: Blade, blade: Blade,
browserslist: Browserslist, browserslist: Browserslist,
bsconfig: Bsconfig, bsconfig: Bsconfig,
bundler: Bundler, bundler: Bundler,
c: C, c: C,
cert: Cert, cert: Cert,
cheader: Cheader, cheader: Cheader,
cli: Cli, cli: Cli,
compodoc: Compodoc, compodoc: Compodoc,
composer: Composer, composer: Composer,
conf: Conf, conf: Conf,
cpp: Cpp, cpp: Cpp,
csharp: Csharp, csharp: Csharp,
cshtml: Cshtml, cshtml: Cshtml,
css: Css, css: Css,
cssmap: Cssmap, cssmap: Cssmap,
csv: Csv, csv: Csv,
dartlang: Dartlang, dartlang: Dartlang,
docker: Docker, docker: Docker,
dockerdebug: Dockerdebug, dockerdebug: Dockerdebug,
dockerignore: Dockerignore, dockerignore: Dockerignore,
editorconfig: Editorconfig, editorconfig: Editorconfig,
eex: Eex, eex: Eex,
elixir: Elixir, elixir: Elixir,
elm: Elm, elm: Elm,
env: Env, env: Env,
erb: Erb, erb: Erb,
erlang: Erlang, erlang: Erlang,
eslint: Eslint, eslint: Eslint,
exs: Exs, exs: Exs,
exx: Exx, exx: Exx,
file: File, file: File,
folder: Folder, folder: Folder,
folder_light: Folder_light, folder_light: Folder_light,
folder_open: Folder_open, folder_open: Folder_open,
fontotf: Fontotf, fontotf: Fontotf,
fontttf: Fontttf, fontttf: Fontttf,
fontwoff: Fontwoff, fontwoff: Fontwoff,
fontwoff2: Fontwoff2, fontwoff2: Fontwoff2,
git: Git, git: Git,
go: Go, go: Go,
gopackage: Gopackage, gopackage: Gopackage,
gradle: Gradle, gradle: Gradle,
graphql: Graphql, graphql: Graphql,
groovy: Groovy, groovy: Groovy,
grunt: Grunt, grunt: Grunt,
gulp: Gulp, gulp: Gulp,
haml: Haml, haml: Haml,
handlebars: Handlebars, handlebars: Handlebars,
haskell: Haskell, haskell: Haskell,
html: Html, html: Html,
image: Image, image: Image,
imagegif: Imagegif, imagegif: Imagegif,
imageico: Imageico, imageico: Imageico,
imagejpg: Imagejpg, imagejpg: Imagejpg,
imagepng: Imagepng, imagepng: Imagepng,
imagewebp: Imagewebp, imagewebp: Imagewebp,
info: Info, info: Info,
ipynb: Ipynb, ipynb: Ipynb,
java: Java, java: Java,
jenkins: Jenkins, jenkins: Jenkins,
jest: Jest, jest: Jest,
jinja: Jinja, jinja: Jinja,
js: Js, js: Js,
jsmap: Jsmap, jsmap: Jsmap,
json: Json, json: Json,
jsp: Jsp, jsp: Jsp,
julia: Julia, julia: Julia,
karma: Karma, karma: Karma,
key: Key, key: Key,
less: Less, less: Less,
license: License, license: License,
lighteditorconfig: Lighteditorconfig, lighteditorconfig: Lighteditorconfig,
liquid: Liquid, liquid: Liquid,
llvm: Llvm, llvm: Llvm,
log: Log, log: Log,
lua: Lua, lua: Lua,
m: M, m: M,
markdown: Markdown, markdown: Markdown,
mint: Mint, mint: Mint,
mov: Mov, mov: Mov,
mp4: Mp4, mp4: Mp4,
nestjs: Nestjs, nestjs: Nestjs,
nestjscontroller: Nestjscontroller, nestjscontroller: Nestjscontroller,
nestjsdecorator: Nestjsdecorator, nestjsdecorator: Nestjsdecorator,
nestjsfilter: Nestjsfilter, nestjsfilter: Nestjsfilter,
nestjsguard: Nestjsguard, nestjsguard: Nestjsguard,
nestjsmodule: Nestjsmodule, nestjsmodule: Nestjsmodule,
nestjsservice: Nestjsservice, nestjsservice: Nestjsservice,
netlify: Netlify, netlify: Netlify,
nginx: Nginx, nginx: Nginx,
nim: Nim, nim: Nim,
njk: Njk, njk: Njk,
nodemon: Nodemon, nodemon: Nodemon,
npm: Npm, npm: Npm,
npmlock: Npmlock, npmlock: Npmlock,
nuxt: Nuxt, nuxt: Nuxt,
nvm: Nvm, nvm: Nvm,
opengl: Opengl, opengl: Opengl,
pdf: Pdf, pdf: Pdf,
photoshop: Photoshop, photoshop: Photoshop,
php: Php, php: Php,
postcssconfig: Postcssconfig, postcssconfig: Postcssconfig,
powershell: Powershell, powershell: Powershell,
powershelldata: Powershelldata, powershelldata: Powershelldata,
powershellmodule: Powershellmodule, powershellmodule: Powershellmodule,
prettier: Prettier, prettier: Prettier,
prisma: Prisma, prisma: Prisma,
prolog: Prolog, prolog: Prolog,
pug: Pug, pug: Pug,
python: Python, python: Python,
qt: Qt, qt: Qt,
razor: Razor, razor: Razor,
reactjs: Reactjs, reactjs: Reactjs,
reactts: Reactts, reactts: Reactts,
readme: Readme, readme: Readme,
rescript: Rescript, rescript: Rescript,
rjson: Rjson, rjson: Rjson,
robots: Robots, robots: Robots,
rollup: Rollup, rollup: Rollup,
ruby: Ruby, ruby: Ruby,
rust: Rust, rust: Rust,
sass: Sass, sass: Sass,
scss: Scss, scss: Scss,
shell: Shell, shell: Shell,
smarty: Smarty, smarty: Smarty,
sol: Sol, sol: Sol,
sql: Sql, sql: Sql,
storybook: Storybook, storybook: Storybook,
stylelint: Stylelint, stylelint: Stylelint,
stylus: Stylus, stylus: Stylus,
svelte: Svelte, svelte: Svelte,
svg: Svg, svg: Svg,
swift: Swift, swift: Swift,
symfony: Symfony, symfony: Symfony,
tailwind: Tailwind, tailwind: Tailwind,
testjs: Testjs, testjs: Testjs,
testts: Testts, testts: Testts,
tmpl: Tmpl, tmpl: Tmpl,
toml: Toml, toml: Toml,
travis: Travis, travis: Travis,
tsconfig: Tsconfig, tsconfig: Tsconfig,
tsx: Tsx, tsx: Tsx,
twig: Twig, twig: Twig,
txt: Txt, txt: Txt,
typescript: Typescript, typescript: Typescript,
typescriptdef: Typescriptdef, typescriptdef: Typescriptdef,
ui: Ui, ui: Ui,
user: User, user: User,
vercel: Vercel, vercel: Vercel,
video: Video, video: Video,
vite: Vite, vite: Vite,
vscode: Vscode, vscode: Vscode,
vue: Vue, vue: Vue,
wasm: Wasm, wasm: Wasm,
webpack: Webpack, webpack: Webpack,
windi: Windi, windi: Windi,
xml: Xml, xml: Xml,
yaml: Yaml, yaml: Yaml,
yarn: Yarn, yarn: Yarn,
yarnerror: Yarnerror, yarnerror: Yarnerror,
zip: Zip zip: Zip
}; };

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

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"> <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="25.6391" y="87.1574" width="87" height="14.1887" transform="rotate(-45 25.6391 87.1574)" fill="#7E0508"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M3.05806 8.94194C3.30214 9.18602 3.69786 9.18602 3.94194 8.94194L8.94194 3.94194C9.18602 3.69786 9.18602 3.30214 8.94194 3.05806C8.69786 2.81398 8.30214 2.81398 8.05806 3.05806L3.05806 8.05806C2.81398 8.30214 2.81398 8.69786 3.05806 8.94194Z" fill="#691109"/>
<rect x="26.5762" y="34.9421" width="13.7508" height="87" transform="rotate(-45 26.5762 34.9421)" fill="#7E0508"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M8.94194 8.94194C8.69786 9.18602 8.30214 9.18602 8.05806 8.94194L3.05806 3.94194C2.81398 3.69786 2.81398 3.30214 3.05806 3.05806C3.30214 2.81398 3.69786 2.81398 3.94194 3.05806L8.94194 8.05806C9.18602 8.30214 9.18602 8.69786 8.94194 8.94194Z" fill="#691109"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 337 B

After

Width:  |  Height:  |  Size: 721 B

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"> <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M108.242 32.8833C108.797 32.8817 109.247 33.3317 109.245 33.8862L109.092 87.5891C109.09 88.4786 108.014 88.9223 107.385 88.2933L53.8351 34.7432C53.2061 34.1142 53.6499 33.0387 54.5394 33.0361L108.242 32.8833Z" fill="#0B650D"/> <path d="M9 8C9 8.55228 8.55228 9 8 9H4.5L9 4.5V8Z" fill="#286017"/>
<path d="M33.8862 109.245C33.3317 109.247 32.8818 108.797 32.8833 108.242L33.0361 54.5394C33.0387 53.6499 34.1142 53.2061 34.7432 53.8351L88.2934 107.385C88.9223 108.014 88.4786 109.09 87.5891 109.092L33.8862 109.245Z" fill="#0B650D"/> <path d="M3 4C3 3.44772 3.44772 3 4 3L7.5 3L3 7.5L3 4Z" fill="#286017"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 579 B

After

Width:  |  Height:  |  Size: 245 B

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"> <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M87 0H0V16H87V0Z" fill="#985712"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M9.75 6C9.75 5.58579 9.41421 5.25 9 5.25H3C2.58579 5.25 2.25 5.58579 2.25 6C2.25 6.41421 2.58579 6.75 3 6.75H9C9.41421 6.75 9.75 6.41421 9.75 6Z" fill="#8E591D"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 147 B

After

Width:  |  Height:  |  Size: 315 B

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 { import {
Cloud, Cloud,
Desktop, Desktop,
DeviceMobileCamera, DeviceMobileCamera,
DotsSixVertical, DotsSixVertical,
Laptop, Laptop,
Phone, Phone,
PhoneX PhoneX
} from 'phosphor-react'; } from 'phosphor-react';
import React, { useState } from 'react'; import React, { useState } from 'react';
import FileItem from '../file/FileItem';
import { Button } from '@sd/ui';
import ProgressBar from '../primitive/ProgressBar';
import { CogIcon, LockClosedIcon } from '@heroicons/react/solid';
import { KeyIcon } from '@heroicons/react/outline';
import LoadingIcons, { Rings } from 'react-loading-icons'; import LoadingIcons, { Rings } from 'react-loading-icons';
import FileItem from '../file/FileItem';
import ProgressBar from '../primitive/ProgressBar';
export interface DeviceProps { export interface DeviceProps {
name: string; name: string;
size: string; size: string;
type: 'laptop' | 'desktop' | 'phone' | 'server'; type: 'laptop' | 'desktop' | 'phone' | 'server';
locations: { name: string }[]; locations: { name: string }[];
runningJob?: { amount: number; task: string }; runningJob?: { amount: number; task: string };
removeThisSoon?: boolean; removeThisSoon?: boolean;
} }
export function Device(props: DeviceProps) { export function Device(props: DeviceProps) {
const [selectedFile, setSelectedFile] = useState<null | string>(null); const [selectedFile, setSelectedFile] = useState<null | string>(null);
function handleSelect(key: string) { function handleSelect(key: string) {
if (selectedFile === key) setSelectedFile(null); if (selectedFile === key) setSelectedFile(null);
else setSelectedFile(key); else setSelectedFile(key);
} }
return ( return (
<div className="w-full bg-gray-600 border rounded-md border-gray-550 "> <div className="w-full bg-gray-600 border rounded-md border-gray-550 ">
<div className="flex flex-row items-center px-4 pt-2 pb-2"> <div className="flex flex-row items-center px-4 pt-2 pb-2">
<DotsSixVertical weight="bold" className="mr-3 opacity-30" /> <DotsSixVertical weight="bold" className="mr-3 opacity-30" />
{props.type === 'phone' && <DeviceMobileCamera weight="fill" size={20} className="mr-2" />} {props.type === 'phone' && <DeviceMobileCamera weight="fill" size={20} className="mr-2" />}
{props.type === 'laptop' && <Laptop weight="fill" size={20} className="mr-2" />} {props.type === 'laptop' && <Laptop weight="fill" size={20} className="mr-2" />}
{props.type === 'desktop' && <Desktop weight="fill" size={20} className="mr-2" />} {props.type === 'desktop' && <Desktop weight="fill" size={20} className="mr-2" />}
{props.type === 'server' && <Cloud weight="fill" size={20} className="mr-2" />} {props.type === 'server' && <Cloud weight="fill" size={20} className="mr-2" />}
<h3 className="font-semibold text-md">{props.name || 'Unnamed Device'}</h3> <h3 className="font-semibold text-md">{props.name || 'Unnamed Device'}</h3>
<div className="flex flex-row space-x-1.5 mt-0.5"> <div className="flex flex-row space-x-1.5 mt-0.5">
<span className="font-semibold flex flex-row h-[19px] -mt-0.5 ml-3 py-0.5 px-1.5 text-[10px] rounded bg-gray-500 text-gray-400"> <span className="font-semibold flex flex-row h-[19px] -mt-0.5 ml-3 py-0.5 px-1.5 text-[10px] rounded bg-gray-500 text-gray-400">
<LockClosedIcon className="w-3 h-3 mr-1 -ml-0.5 m-[1px]" /> <LockClosedIcon className="w-3 h-3 mr-1 -ml-0.5 m-[1px]" />
P2P P2P
</span> </span>
</div> </div>
<span className="font-semibold py-0.5 px-1.5 text-sm ml-2 text-gray-400 "> <span className="font-semibold py-0.5 px-1.5 text-sm ml-2 text-gray-400 ">
{props.size} {props.size}
</span> </span>
<div className="flex flex-grow" /> <div className="flex flex-grow" />
{props.runningJob && ( {props.runningJob && (
<div className="flex flex-row ml-5 bg-opacity-50 rounded-md bg-gray-550 "> <div className="flex flex-row ml-5 bg-opacity-50 rounded-md bg-gray-550 ">
<Rings <Rings
stroke="#2599FF" stroke="#2599FF"
strokeOpacity={4} strokeOpacity={4}
strokeWidth={10} strokeWidth={10}
speed={0.5} speed={0.5}
className="ml-0.5 mt-[2px] -mr-1 w-7 h-7" className="ml-0.5 mt-[2px] -mr-1 w-7 h-7"
/> />
<div className="flex flex-col p-2"> <div className="flex flex-col p-2">
<span className="mb-[2px] -mt-1 truncate text-gray-450 text-tiny"> <span className="mb-[2px] -mt-1 truncate text-gray-450 text-tiny">
{props.runningJob.task}... {props.runningJob.task}...
</span> </span>
<ProgressBar value={props.runningJob?.amount} total={100} /> <ProgressBar value={props.runningJob?.amount} total={100} />
</div> </div>
</div> </div>
)} )}
<div className="flex flex-row ml-3 space-x-1"> <div className="flex flex-row ml-3 space-x-1">
<Button className="!p-1 "> <Button className="!p-1 ">
<KeyIcon className="w-5 h-5" /> <KeyIcon className="w-5 h-5" />
</Button> </Button>
<Button className="!p-1 "> <Button className="!p-1 ">
<CogIcon className="w-5 h-5" /> <CogIcon className="w-5 h-5" />
</Button> </Button>
</div> </div>
</div> </div>
{/* <hr className="border-gray-700" /> {/* <hr className="border-gray-700" />
<hr className="border-gray-550" /> */} <hr className="border-gray-550" /> */}
<div className="px-4 pb-3 mt-3"> <div className="px-4 pb-3 mt-3">
{props.locations.map((location, key) => ( {props.locations.map((location, key) => (
<FileItem <FileItem
key={key} key={key}
selected={selectedFile == location.name} selected={selectedFile == location.name}
onClick={() => handleSelect(location.name)} onClick={() => handleSelect(location.name)}
fileName={location.name} fileName={location.name}
folder folder
/> />
))} ))}
{props.removeThisSoon && ( {props.removeThisSoon && (
<> <>
<FileItem <FileItem
selected={selectedFile == 'tsx'} selected={selectedFile == 'tsx'}
onClick={() => handleSelect('tsx')} onClick={() => handleSelect('tsx')}
fileName="App.tsx" fileName="App.tsx"
format="tsx" format="tsx"
iconName="reactts" iconName="reactts"
/> />
<FileItem <FileItem
selected={selectedFile == 'vite'} selected={selectedFile == 'vite'}
onClick={() => handleSelect('vite')} onClick={() => handleSelect('vite')}
fileName="vite.config.js" fileName="vite.config.js"
format="vite" format="vite"
iconName="vite" iconName="vite"
/> />
</> </>
)} )}
</div> </div>
</div> </div>
); );
} }

View file

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

View file

@ -1,103 +1,103 @@
import clsx from 'clsx'; import clsx from 'clsx';
import React from 'react'; import React from 'react';
import icons from '../../assets/icons';
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
import { DefaultProps } from '../primitive/types'; import { DefaultProps } from '../primitive/types';
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
import icons from '../../assets/icons';
interface Props extends DefaultProps { interface Props extends DefaultProps {
fileName: string; fileName: string;
iconName?: string; iconName?: string;
format?: string; format?: string;
folder?: boolean; folder?: boolean;
selected?: boolean; selected?: boolean;
onClick?: () => void; onClick?: () => void;
} }
export default function FileItem(props: Props) { export default function FileItem(props: Props) {
// const Shadow = () => { // const Shadow = () => {
// return ( // return (
// <div // <div
// className={clsx( // className={clsx(
// 'absolute opacity-100 transition-opacity duration-200 top-auto bottom-auto w-[64px] h-[40px] shadow-xl shadow-red-500', // 'absolute opacity-100 transition-opacity duration-200 top-auto bottom-auto w-[64px] h-[40px] shadow-xl shadow-red-500',
// { 'opacity-100': props.selected } // { 'opacity-100': props.selected }
// )} // )}
// /> // />
// ); // );
// }; // };
return ( return (
<div onClick={props.onClick} className="inline-block w-[100px] mb-3" draggable> <div onClick={props.onClick} className="inline-block w-[100px] mb-3" draggable>
<div <div
className={clsx( className={clsx(
'border-2 border-transparent rounded-lg text-center w-[100px] h-[100px] mb-1', 'border-2 border-transparent rounded-lg text-center w-[100px] h-[100px] mb-1',
{ {
'bg-gray-50 dark:bg-gray-650': props.selected 'bg-gray-50 dark:bg-gray-650': props.selected
} }
)} )}
> >
{props.folder ? ( {props.folder ? (
<div className="flex items-center justify-center w-full h-full active:translate-y-[1px]"> <div className="flex items-center justify-center w-full h-full active:translate-y-[1px]">
<div className="w-[70px]"> <div className="w-[70px]">
<Folder className="" /> <Folder className="" />
</div> </div>
</div> </div>
) : ( ) : (
<div <div
className={clsx( className={clsx(
'w-[64px] mt-1.5 m-auto transition duration-200 rounded-lg h-[90px] relative active:translate-y-[1px]', 'w-[64px] mt-1.5 m-auto transition duration-200 rounded-lg h-[90px] relative active:translate-y-[1px]',
{ {
'': props.selected '': props.selected
} }
)} )}
> >
<svg <svg
className="absolute top-0 left-0 pointer-events-none fill-gray-150 dark:fill-gray-550" className="absolute top-0 left-0 pointer-events-none fill-gray-150 dark:fill-gray-550"
width="65" width="65"
height="85" height="85"
viewBox="0 0 65 81" viewBox="0 0 65 81"
> >
<path d="M0 8C0 3.58172 3.58172 0 8 0H39.6863C41.808 0 43.8429 0.842855 45.3431 2.34315L53.5 10.5L62.6569 19.6569C64.1571 21.1571 65 23.192 65 25.3137V73C65 77.4183 61.4183 81 57 81H8C3.58172 81 0 77.4183 0 73V8Z" /> <path d="M0 8C0 3.58172 3.58172 0 8 0H39.6863C41.808 0 43.8429 0.842855 45.3431 2.34315L53.5 10.5L62.6569 19.6569C64.1571 21.1571 65 23.192 65 25.3137V73C65 77.4183 61.4183 81 57 81H8C3.58172 81 0 77.4183 0 73V8Z" />
</svg> </svg>
<svg <svg
width="22" width="22"
height="22" height="22"
className="absolute top-1 -right-[1px] z-10 fill-gray-50 dark:fill-gray-500 pointer-events-none" className="absolute top-1 -right-[1px] z-10 fill-gray-50 dark:fill-gray-500 pointer-events-none"
viewBox="0 0 41 41" viewBox="0 0 41 41"
> >
<path d="M41.4116 40.5577H11.234C5.02962 40.5577 0 35.5281 0 29.3238V0L41.4116 40.5577Z" /> <path d="M41.4116 40.5577H11.234C5.02962 40.5577 0 35.5281 0 29.3238V0L41.4116 40.5577Z" />
</svg> </svg>
<div className="absolute flex flex-col items-center justify-center w-full h-full"> <div className="absolute flex flex-col items-center justify-center w-full h-full">
{/* @ts-ignore */} {/* @ts-ignore */}
{props.iconName && icons[props.iconName] ? ( {props.iconName && icons[props.iconName] ? (
(() => { (() => {
// @ts-ignore // @ts-ignore
let Icon = icons[props.iconName]; let Icon = icons[props.iconName];
return ( return (
<Icon className="mt-2 pointer-events-none margin-auto w-[40px] h-[40px]" /> <Icon className="mt-2 pointer-events-none margin-auto w-[40px] h-[40px]" />
); );
})() })()
) : ( ) : (
<></> <></>
)} )}
<span className="mt-1 text-xs font-bold text-center uppercase cursor-default text-gray-450"> <span className="mt-1 text-xs font-bold text-center uppercase cursor-default text-gray-450">
{props.format} {props.format}
</span> </span>
</div> </div>
</div> </div>
)} )}
</div> </div>
<div className="flex justify-center"> <div className="flex justify-center">
<span <span
className={clsx( className={clsx(
'px-1.5 py-[1px] rounded-md text-sm font-medium text-gray-300 cursor-default', 'px-1.5 py-[1px] rounded-md text-sm font-medium text-gray-300 cursor-default',
{ {
'bg-primary !text-white': props.selected 'bg-primary !text-white': props.selected
} }
)} )}
> >
{props.fileName} {props.fileName}
</span> </span>
</div> </div>
</div> </div>
); );
} }

View file

@ -4,236 +4,250 @@ import { FilePath } from '@sd/core';
import clsx from 'clsx'; import clsx from 'clsx';
import byteSize from 'pretty-bytes'; import byteSize from 'pretty-bytes';
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
import create from 'zustand';
import { useKey, useWindowSize } from 'rooks';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
import { useKey, useWindowSize } from 'rooks';
import create from 'zustand';
import { AppPropsContext } from '../../App'; import { AppPropsContext } from '../../App';
import FileThumb from './FileThumb'; import FileThumb from './FileThumb';
type ExplorerState = { type ExplorerState = {
selectedRowIndex: number; selectedRowIndex: number;
setSelectedRowIndex: (index: number) => void; setSelectedRowIndex: (index: number) => void;
locationId: number; locationId: number;
setLocationId: (index: number) => void; setLocationId: (index: number) => void;
newThumbnails: Record<string, boolean>; newThumbnails: Record<string, boolean>;
addNewThumbnail: (cas_id: string) => void; addNewThumbnail: (cas_id: string) => void;
}; };
export const useExplorerState = create<ExplorerState>((set) => ({ export const useExplorerState = create<ExplorerState>((set) => ({
selectedRowIndex: 1, selectedRowIndex: 1,
setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index })), setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index })),
locationId: -1, locationId: -1,
setLocationId: (id: number) => set((state) => ({ ...state, locationId: id })), setLocationId: (id: number) => set((state) => ({ ...state, locationId: id })),
newThumbnails: {}, newThumbnails: {},
addNewThumbnail: (cas_id: string) => addNewThumbnail: (cas_id: string) =>
set((state) => ({ ...state, newThumbnails: { ...state.newThumbnails, [cas_id]: true } })) set((state) => ({
...state,
newThumbnails: { ...state.newThumbnails, [cas_id]: true }
}))
})); }));
interface IColumn { interface IColumn {
column: string; column: string;
key: string; key: string;
width: number; width: number;
} }
const PADDING_SIZE = 130; const PADDING_SIZE = 130;
// Function ensure no types are loss, but guarantees that they are Column[] // Function ensure no types are loss, but guarantees that they are Column[]
function ensureIsColumns<T extends IColumn[]>(data: T) { function ensureIsColumns<T extends IColumn[]>(data: T) {
return data; return data;
} }
const columns = ensureIsColumns([ const columns = ensureIsColumns([
{ column: 'Name', key: 'name', width: 280 } as const, { column: 'Name', key: 'name', width: 280 } as const,
// { column: 'Size', key: 'size_in_bytes', width: 120 } as const, // { column: 'Size', key: 'size_in_bytes', width: 120 } as const,
{ column: 'Type', key: 'extension', width: 100 } as const { column: 'Type', key: 'extension', width: 100 } as const
]); ]);
type ColumnKey = typeof columns[number]['key']; type ColumnKey = typeof columns[number]['key'];
const LocationContext = React.createContext<{ location_id: number; data_path: string }>({ const LocationContext = React.createContext<{
location_id: 1, location_id: number;
data_path: '' data_path: string;
}>({
location_id: 1,
data_path: ''
}); });
export const FileList: React.FC<{ location_id: number; path: string; limit: number }> = (props) => { export const FileList: React.FC<{
const size = useWindowSize(); location_id: number;
const tableContainer = useRef<null | HTMLDivElement>(null); path: string;
const VList = useRef<null | VirtuosoHandle>(null); limit: number;
}> = (props) => {
const size = useWindowSize();
const tableContainer = useRef<null | HTMLDivElement>(null);
const VList = useRef<null | VirtuosoHandle>(null);
const { data: client } = useBridgeQuery('ClientGetState', undefined, { const { data: client } = useBridgeQuery('ClientGetState', undefined, {
refetchOnWindowFocus: false refetchOnWindowFocus: false
}); });
const path = props.path; const path = props.path;
const { selectedRowIndex, setSelectedRowIndex, setLocationId } = useExplorerState(); const { selectedRowIndex, setSelectedRowIndex, setLocationId } = useExplorerState();
const [goingUp, setGoingUp] = useState(false); const [goingUp, setGoingUp] = useState(false);
const { data: currentDir } = useBridgeQuery('LibGetExplorerDir', { const { data: currentDir } = useBridgeQuery('LibGetExplorerDir', {
location_id: props.location_id, location_id: props.location_id,
path, path,
limit: props.limit limit: props.limit
}); });
useEffect(() => { useEffect(() => {
if (selectedRowIndex === 0 && goingUp) { if (selectedRowIndex === 0 && goingUp) {
VList.current?.scrollTo({ top: 0, behavior: 'smooth' }); VList.current?.scrollTo({ top: 0, behavior: 'smooth' });
} }
if (selectedRowIndex != -1) { if (selectedRowIndex != -1) {
VList.current?.scrollIntoView({ VList.current?.scrollIntoView({
index: goingUp ? selectedRowIndex - 1 : selectedRowIndex index: goingUp ? selectedRowIndex - 1 : selectedRowIndex
}); });
} }
}, [selectedRowIndex]); }, [selectedRowIndex]);
useEffect(() => { useEffect(() => {
setLocationId(props.location_id); setLocationId(props.location_id);
}, [props.location_id]); }, [props.location_id]);
useKey('ArrowUp', (e) => { useKey('ArrowUp', (e) => {
e.preventDefault(); e.preventDefault();
setGoingUp(true); setGoingUp(true);
if (selectedRowIndex != -1 && selectedRowIndex !== 0) setSelectedRowIndex(selectedRowIndex - 1); if (selectedRowIndex != -1 && selectedRowIndex !== 0) setSelectedRowIndex(selectedRowIndex - 1);
}); });
useKey('ArrowDown', (e) => { useKey('ArrowDown', (e) => {
e.preventDefault(); e.preventDefault();
setGoingUp(false); setGoingUp(false);
if (selectedRowIndex != -1 && selectedRowIndex !== (currentDir?.contents.length ?? 1) - 1) if (selectedRowIndex != -1 && selectedRowIndex !== (currentDir?.contents.length ?? 1) - 1)
setSelectedRowIndex(selectedRowIndex + 1); setSelectedRowIndex(selectedRowIndex + 1);
}); });
const Row = (index: number) => { const Row = (index: number) => {
const row = currentDir?.contents?.[index]; const row = currentDir?.contents?.[index];
if (!row) return null; if (!row) return null;
return <RenderRow key={index} row={row} rowIndex={index} dirId={currentDir?.directory.id} />; return <RenderRow key={index} row={row} rowIndex={index} dirId={currentDir?.directory.id} />;
}; };
const Header = () => ( const Header = () => (
<div> <div>
<h1 className="pt-20 pl-4 text-xl font-bold ">{currentDir?.directory.name}</h1> <h1 className="pt-20 pl-4 text-xl font-bold ">{currentDir?.directory.name}</h1>
<div className="table-head"> <div className="table-head">
<div className="flex flex-row p-2 table-head-row"> <div className="flex flex-row p-2 table-head-row">
{columns.map((col) => ( {columns.map((col) => (
<div <div
key={col.key} key={col.key}
className="relative flex flex-row items-center pl-2 table-head-cell group" className="relative flex flex-row items-center pl-2 table-head-cell group"
style={{ width: col.width }} style={{ width: col.width }}
> >
<DotsVerticalIcon className="absolute hidden w-5 h-5 -ml-5 cursor-move group-hover:block drag-handle opacity-10" /> <DotsVerticalIcon className="absolute hidden w-5 h-5 -ml-5 cursor-move group-hover:block drag-handle opacity-10" />
<span className="text-sm font-medium text-gray-500">{col.column}</span> <span className="text-sm font-medium text-gray-500">{col.column}</span>
</div> </div>
))} ))}
</div> </div>
</div> </div>
</div> </div>
); );
return useMemo( return useMemo(
() => ( () => (
<div <div
ref={tableContainer} ref={tableContainer}
style={{ marginTop: -44 }} style={{ marginTop: -44 }}
className="w-full pl-2 bg-white cursor-default table-container dark:bg-gray-650" className="w-full pl-2 bg-white cursor-default table-container dark:bg-gray-650"
> >
<LocationContext.Provider <LocationContext.Provider
value={{ location_id: props.location_id, data_path: client?.data_path as string }} value={{
> location_id: props.location_id,
<Virtuoso data_path: client?.data_path as string
data={currentDir?.contents} }}
ref={VList} >
style={{ height: size.innerHeight ?? 600 }} <Virtuoso
totalCount={currentDir?.contents.length || 0} data={currentDir?.contents}
itemContent={Row} ref={VList}
components={{ Header, Footer: () => <div className="w-full " /> }} style={{ height: size.innerHeight ?? 600 }}
increaseViewportBy={{ top: 400, bottom: 200 }} totalCount={currentDir?.contents.length || 0}
className="outline-none explorer-scroll" itemContent={Row}
/> components={{ Header, Footer: () => <div className="w-full " /> }}
</LocationContext.Provider> increaseViewportBy={{ top: 400, bottom: 200 }}
</div> className="outline-none explorer-scroll"
), />
[props.location_id, size.innerWidth, currentDir?.directory.id, tableContainer.current] </LocationContext.Provider>
); </div>
),
[props.location_id, size.innerWidth, currentDir?.directory.id, tableContainer.current]
);
}; };
const RenderRow: React.FC<{ const RenderRow: React.FC<{
row: FilePath; row: FilePath;
rowIndex: number; rowIndex: number;
dirId: number; dirId: number;
}> = ({ row, rowIndex, dirId }) => { }> = ({ row, rowIndex, dirId }) => {
const { selectedRowIndex, setSelectedRowIndex } = useExplorerState(); const { selectedRowIndex, setSelectedRowIndex } = useExplorerState();
const isActive = selectedRowIndex === rowIndex; const isActive = selectedRowIndex === rowIndex;
let [_, setSearchParams] = useSearchParams(); let [_, setSearchParams] = useSearchParams();
function selectFileHandler() { function selectFileHandler() {
if (selectedRowIndex == rowIndex) setSelectedRowIndex(-1); if (selectedRowIndex == rowIndex) setSelectedRowIndex(-1);
else setSelectedRowIndex(rowIndex); else setSelectedRowIndex(rowIndex);
} }
return useMemo( return useMemo(
() => ( () => (
<div <div
onClick={selectFileHandler} onClick={selectFileHandler}
onDoubleClick={() => { onDoubleClick={() => {
if (row.is_dir) { if (row.is_dir) {
setSearchParams({ path: row.materialized_path }); setSearchParams({ path: row.materialized_path });
} }
}} }}
className={clsx( className={clsx(
'table-body-row mr-2 flex flex-row rounded-lg border-2', 'table-body-row mr-2 flex flex-row rounded-lg border-2',
isActive ? 'border-primary-500' : 'border-transparent', isActive ? 'border-primary-500' : 'border-transparent',
rowIndex % 2 == 0 && 'bg-[#00000006] dark:bg-[#00000030]' rowIndex % 2 == 0 && 'bg-[#00000006] dark:bg-[#00000030]'
)} )}
> >
{columns.map((col) => ( {columns.map((col) => (
<div <div
key={col.key} key={col.key}
className="flex items-center px-4 py-2 pr-2 table-body-cell" className="flex items-center px-4 py-2 pr-2 table-body-cell"
style={{ width: col.width }} style={{ width: col.width }}
> >
<RenderCell file={row} dirId={dirId} colKey={col?.key} /> <RenderCell file={row} dirId={dirId} colKey={col?.key} />
</div> </div>
))} ))}
</div> </div>
), ),
[row.id, isActive] [row.id, isActive]
); );
}; };
const RenderCell: React.FC<{ colKey?: ColumnKey; dirId?: number; file?: FilePath }> = ({ const RenderCell: React.FC<{
colKey, colKey?: ColumnKey;
file, dirId?: number;
dirId file?: FilePath;
}) => { }> = ({ colKey, file, dirId }) => {
if (!file || !colKey || !dirId) return <></>; if (!file || !colKey || !dirId) return <></>;
const row = file; const row = file;
if (!row) return <></>; if (!row) return <></>;
const appPropsContext = useContext(AppPropsContext); const appPropsContext = useContext(AppPropsContext);
const value = row[colKey]; const value = row[colKey];
if (!value) return <></>; if (!value) return <></>;
const location = useContext(LocationContext); const location = useContext(LocationContext);
const { newThumbnails } = useExplorerState(); const { newThumbnails } = useExplorerState();
const hasNewThumbnail = !!newThumbnails[row?.temp_cas_id ?? '']; const hasNewThumbnail = !!newThumbnails[row?.temp_cas_id ?? ''];
switch (colKey) { switch (colKey) {
case 'name': case 'name':
return ( return (
<div className="flex flex-row items-center overflow-hidden"> <div className="flex flex-row items-center overflow-hidden">
<div className="flex items-center justify-center shrink-0 w-6 h-6 mr-3"> <div className="flex items-center justify-center shrink-0 w-6 h-6 mr-3">
<FileThumb <FileThumb
hasThumbnailOverride={hasNewThumbnail} hasThumbnailOverride={hasNewThumbnail}
file={row} file={row}
locationId={location.location_id} locationId={location.location_id}
/> />
</div> </div>
{/* {colKey == 'name' && {/* {colKey == 'name' &&
(() => { (() => {
switch (row.extension.toLowerCase()) { switch (row.extension.toLowerCase()) {
case 'mov' || 'mp4': case 'mov' || 'mp4':
@ -245,19 +259,19 @@ const RenderCell: React.FC<{ colKey?: ColumnKey; dirId?: number; file?: FilePath
return <DocumentIcon className="flex-shrink-0 w-5 h-5 mr-3 text-gray-300" />; return <DocumentIcon className="flex-shrink-0 w-5 h-5 mr-3 text-gray-300" />;
} }
})()} */} })()} */}
<span className="text-xs truncate">{row[colKey]}</span> <span className="text-xs truncate">{row[colKey]}</span>
</div> </div>
); );
// case 'size_in_bytes': // case 'size_in_bytes':
// return <span className="text-xs text-left">{byteSize(Number(value || 0))}</span>; // return <span className="text-xs text-left">{byteSize(Number(value || 0))}</span>;
case 'extension': case 'extension':
return <span className="text-xs text-left">{value}</span>; return <span className="text-xs text-left">{value}</span>;
// case 'meta_integrity_hash': // case 'meta_integrity_hash':
// return <span className="truncate">{value}</span>; // return <span className="truncate">{value}</span>;
// case 'tags': // case 'tags':
// return renderCellWithIcon(MusicNoteIcon); // return renderCellWithIcon(MusicNoteIcon);
default: default:
return <></>; return <></>;
} }
}; };

View file

@ -2,37 +2,38 @@ import { useBridgeQuery } from '@sd/client';
import { FilePath } from '@sd/core'; import { FilePath } from '@sd/core';
import clsx from 'clsx'; import clsx from 'clsx';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { AppPropsContext } from '../../App'; import { AppPropsContext } from '../../App';
import icons from '../../assets/icons'; import icons from '../../assets/icons';
import { ReactComponent as Folder } from '../../assets/svg/folder.svg'; import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
export default function FileThumb(props: { export default function FileThumb(props: {
file: FilePath; file: FilePath;
locationId: number; locationId: number;
hasThumbnailOverride: boolean; hasThumbnailOverride: boolean;
className?: string; className?: string;
}) { }) {
const appPropsContext = useContext(AppPropsContext); const appPropsContext = useContext(AppPropsContext);
const { data: client } = useBridgeQuery('ClientGetState'); const { data: client } = useBridgeQuery('ClientGetState');
if (props.file.is_dir) { if (props.file.is_dir) {
return <Folder className="max-w-[170px]" />; return <Folder className="max-w-[170px]" />;
} }
if (client?.data_path && (props.file.has_local_thumbnail || props.hasThumbnailOverride)) { if (client?.data_path && (props.file.has_local_thumbnail || props.hasThumbnailOverride)) {
return ( return (
<img <img
className="pointer-events-none z-90" className="pointer-events-none z-90"
src={appPropsContext?.convertFileSrc( src={appPropsContext?.convertFileSrc(
`${client.data_path}/thumbnails/${props.locationId}/${props.file.temp_cas_id}.webp` `${client.data_path}/thumbnails/${props.locationId}/${props.file.temp_cas_id}.webp`
)} )}
/> />
); );
} }
if (icons[props.file.extension as keyof typeof icons]) { if (icons[props.file.extension as keyof typeof icons]) {
let Icon = icons[props.file.extension as keyof typeof icons]; let Icon = icons[props.file.extension as keyof typeof icons];
return <Icon className={clsx('max-w-[170px] w-full h-full', props.className)} />; return <Icon className={clsx('max-w-[170px] w-full h-full', props.className)} />;
} }
return <div></div>; return <div></div>;
} }

View file

@ -1,121 +1,122 @@
import React from 'react';
import { Transition } from '@headlessui/react'; import { Transition } from '@headlessui/react';
import moment from 'moment';
import { Input, TextArea } from '../primitive';
import { Button } from '@sd/ui';
import { ShareIcon } from '@heroicons/react/solid'; import { ShareIcon } from '@heroicons/react/solid';
import { Heart, Link } from 'phosphor-react';
import { useExplorerState } from './FileList';
import { FilePath } from '@sd/core'; import { FilePath } from '@sd/core';
import FileThumb from './FileThumb'; import { Button } from '@sd/ui';
import moment from 'moment';
import { Heart, Link } from 'phosphor-react';
import React from 'react';
import { default as types } from '../../constants/file-types.json'; import { default as types } from '../../constants/file-types.json';
import { Input, TextArea } from '../primitive';
import { useExplorerState } from './FileList';
import FileThumb from './FileThumb';
interface MetaItemProps { interface MetaItemProps {
title: string; title: string;
value: string | React.ReactNode; value: string | React.ReactNode;
} }
const MetaItem = (props: MetaItemProps) => { const MetaItem = (props: MetaItemProps) => {
return ( return (
<div className="flex flex-col px-3 py-1 meta-item"> <div className="flex flex-col px-3 py-1 meta-item">
<h5 className="text-xs font-bold">{props.title}</h5> <h5 className="text-xs font-bold">{props.title}</h5>
{typeof props.value === 'string' ? ( {typeof props.value === 'string' ? (
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">{props.value}</p> <p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">{props.value}</p>
) : ( ) : (
props.value props.value
)} )}
</div> </div>
); );
}; };
const Divider = () => <div className="w-full my-1 h-[1px] bg-gray-100 dark:bg-gray-550" />; const Divider = () => <div className="w-full my-1 h-[1px] bg-gray-100 dark:bg-gray-550" />;
export const Inspector = (props: { selectedFile?: FilePath; locationId: number }) => { export const Inspector = (props: { selectedFile?: FilePath; locationId: number }) => {
// const { selectedRowIndex } = useExplorerState(); // const { selectedRowIndex } = useExplorerState();
// const isOpen = !!props.selectedFile; // const isOpen = !!props.selectedFile;
const file = props.selectedFile; const file = props.selectedFile;
return ( return (
<Transition <Transition
show={true} show={true}
enter="transition-translate ease-in-out duration-200" enter="transition-translate ease-in-out duration-200"
enterFrom="translate-x-64" enterFrom="translate-x-64"
enterTo="translate-x-0" enterTo="translate-x-0"
leave="transition-translate ease-in-out duration-200" leave="transition-translate ease-in-out duration-200"
leaveFrom="translate-x-0" leaveFrom="translate-x-0"
leaveTo="translate-x-64" leaveTo="translate-x-64"
> >
<div className="top-0 right-0 h-full m-2 border border-gray-100 rounded-lg w-60 dark:border-gray-850 "> <div className="top-0 right-0 h-full m-2 border border-gray-100 rounded-lg w-60 dark:border-gray-850 ">
{!!file && ( {!!file && (
<div className="flex flex-col h-full overflow-hidden bg-white rounded-lg select-text dark:bg-gray-600 bg-opacity-70"> <div className="flex flex-col h-full overflow-hidden bg-white rounded-lg select-text dark:bg-gray-600 bg-opacity-70">
<div className="flex items-center justify-center w-full h-64 overflow-hidden rounded-t-lg bg-gray-50 dark:bg-gray-900"> <div className="flex items-center justify-center w-full h-64 overflow-hidden rounded-t-lg bg-gray-50 dark:bg-gray-900">
<FileThumb <FileThumb
hasThumbnailOverride={false} hasThumbnailOverride={false}
className="!m-0 flex flex-shrink flex-grow-0" className="!m-0 flex flex-shrink flex-grow-0"
file={file} file={file}
locationId={props.locationId} locationId={props.locationId}
/> />
</div> </div>
<h3 className="pt-3 pl-3 text-base font-bold">{file?.name}</h3> <h3 className="pt-3 pl-3 text-base font-bold">{file?.name}</h3>
<div className="flex flex-row m-3 space-x-2"> <div className="flex flex-row m-3 space-x-2">
<Button size="sm" noPadding> <Button size="sm" noPadding>
<Heart className="w-[18px] h-[18px]" /> <Heart className="w-[18px] h-[18px]" />
</Button> </Button>
<Button size="sm" noPadding> <Button size="sm" noPadding>
<ShareIcon className="w-[18px] h-[18px]" /> <ShareIcon className="w-[18px] h-[18px]" />
</Button> </Button>
<Button size="sm" noPadding> <Button size="sm" noPadding>
<Link className="w-[18px] h-[18px]" /> <Link className="w-[18px] h-[18px]" />
</Button> </Button>
</div> </div>
{file?.temp_cas_id && ( {file?.temp_cas_id && (
<MetaItem title="Unique Content ID" value={file.temp_cas_id as string} /> <MetaItem title="Unique Content ID" value={file.temp_cas_id as string} />
)} )}
<Divider /> <Divider />
<MetaItem title="Uri" value={file?.materialized_path as string} /> <MetaItem title="Uri" value={file?.materialized_path as string} />
<Divider /> <Divider />
<MetaItem <MetaItem
title="Date Created" title="Date Created"
value={moment(file?.date_created).format('MMMM Do YYYY, h:mm:ss a')} value={moment(file?.date_created).format('MMMM Do YYYY, h:mm:ss a')}
/> />
<Divider /> <Divider />
<MetaItem <MetaItem
title="Date Indexed" title="Date Indexed"
value={moment(file?.date_indexed).format('MMMM Do YYYY, h:mm:ss a')} value={moment(file?.date_indexed).format('MMMM Do YYYY, h:mm:ss a')}
/> />
<Divider /> <Divider />
{!file?.is_dir && ( {!file?.is_dir && (
<> <>
<div className="flex flex-row items-center px-3 py-2 meta-item"> <div className="flex flex-row items-center px-3 py-2 meta-item">
{file?.extension && ( {file?.extension && (
<span className="inline px-1 mr-1 text-xs font-bold uppercase bg-gray-500 rounded-md text-gray-150"> <span className="inline px-1 mr-1 text-xs font-bold uppercase bg-gray-500 rounded-md text-gray-150">
{file?.extension} {file?.extension}
</span> </span>
)} )}
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300"> <p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">
{file?.extension {file?.extension
? //@ts-ignore ? //@ts-ignore
types[file.extension.toUpperCase()]?.descriptions.join(' / ') types[file.extension.toUpperCase()]?.descriptions.join(' / ')
: 'Unknown'} : 'Unknown'}
</p> </p>
</div> </div>
<Divider /> <Divider />
</> </>
)} )}
<MetaItem <MetaItem
title="Comment" title="Comment"
value={<TextArea className="mt-2 text-xs leading-snug !py-2" />} value={<TextArea className="mt-2 text-xs leading-snug !py-2" />}
/> />
{/* <div className="flex flex-row m-3"> {/* <div className="flex flex-row m-3">
<Button size="sm">Mint</Button> <Button size="sm">Mint</Button>
</div> */} </div> */}
{/* <MetaItem title="Date Last Modified" value={file?.date_modified} /> {/* <MetaItem title="Date Last Modified" value={file?.date_modified} />
<MetaItem title="Date Indexed" value={file?.date_indexed} /> */} <MetaItem title="Date Indexed" value={file?.date_indexed} /> */}
</div> </div>
)} )}
</div> </div>
</Transition> </Transition>
); );
}; };

View file

@ -1,91 +1,104 @@
import { CameraIcon, LockClosedIcon, PhotographIcon } from '@heroicons/react/outline'; import { LockClosedIcon, PhotographIcon } from '@heroicons/react/outline';
import { CogIcon, EyeOffIcon, PlusIcon, ServerIcon } from '@heroicons/react/solid'; import { CogIcon, EyeOffIcon, PlusIcon } from '@heroicons/react/solid';
import clsx from 'clsx';
import { Camera, CirclesFour, Code, EjectSimple, MonitorPlay, Planet } from 'phosphor-react';
import React, { useContext, useEffect, useState } from 'react';
import { NavLink, NavLinkProps } from 'react-router-dom';
import { TrafficLights } from '../os/TrafficLights';
import { Button, Dropdown } from '@sd/ui';
import { DefaultProps } from '../primitive/types';
import { useBridgeCommand, useBridgeQuery } from '@sd/client'; import { useBridgeCommand, useBridgeQuery } from '@sd/client';
import RunningJobsWidget from '../jobs/RunningJobsWidget'; import { Button, Dropdown } from '@sd/ui';
import { AppPropsContext } from '../../App'; import clsx from 'clsx';
import { CirclesFour, Code, Planet } from 'phosphor-react';
import React, { useContext } from 'react';
import { NavLink, NavLinkProps } from 'react-router-dom';
import { ReactComponent as Folder } from '../../assets/svg/folder.svg'; import { AppPropsContext } from '../../App';
import { ReactComponent as FolderWhite } from '../../assets/svg/folder-white.svg'; import { ReactComponent as FolderWhite } from '../../assets/svg/folder-white.svg';
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
import { useStore } from '../device/Stores'; import { useStore } from '../device/Stores';
import RunningJobsWidget from '../jobs/RunningJobsWidget';
import { MacTrafficLights } from '../os/TrafficLights';
import { DefaultProps } from '../primitive/types';
interface SidebarProps extends DefaultProps {} interface SidebarProps extends DefaultProps {}
export const SidebarLink = (props: NavLinkProps & { children: React.ReactNode }) => ( export const SidebarLink = (props: NavLinkProps & { children: React.ReactNode }) => (
<NavLink {...props}> <NavLink {...props}>
{({ isActive }) => ( {({ isActive }) => (
<span <span
className={clsx( className={clsx(
'max-w mb-[2px] text-gray-550 dark:text-gray-150 rounded px-2 py-1 flex flex-row flex-grow items-center font-medium hover:bg-gray-100 dark:hover:bg-gray-600 text-sm', 'max-w mb-[2px] text-gray-550 dark:text-gray-150 rounded px-2 py-1 flex flex-row flex-grow items-center font-medium hover:bg-gray-100 dark:hover:bg-gray-600 text-sm',
{ '!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive }, {
props.className '!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive
)} },
> props.className
{props.children} )}
</span> >
)} {props.children}
</NavLink> </span>
)}
</NavLink>
); );
const Icon = ({ component: Icon, ...props }: any) => ( const Icon = ({ component: Icon, ...props }: any) => (
<Icon weight="bold" {...props} className={clsx('w-4 h-4 mr-2', props.className)} /> <Icon weight="bold" {...props} className={clsx('w-4 h-4 mr-2', props.className)} />
); );
const Heading: React.FC<{ children: React.ReactNode }> = ({ children }) => ( const Heading: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="mt-5 mb-1 ml-1 text-xs font-semibold text-gray-300">{children}</div> <div className="mt-5 mb-1 ml-1 text-xs font-semibold text-gray-300">{children}</div>
); );
export function MacOSTrafficLights() { export const MacWindowControlsSpace: React.FC<{
const appPropsContext = useContext(AppPropsContext); children?: React.ReactNode;
}> = (props) => {
const { children } = props;
return ( return (
<div data-tauri-drag-region className="h-7"> <div data-tauri-drag-region className="h-7 flex-shrink-0">
<div className="mt-2 mb-1 -ml-1 "> {children}
<TrafficLights </div>
onClose={appPropsContext?.onClose} );
onFullscreen={appPropsContext?.onFullscreen} };
onMinimize={appPropsContext?.onMinimize}
className="p-1.5 z-50 absolute" export function MacWindowControls() {
/> const appPropsContext = useContext(AppPropsContext);
</div>
</div> return (
); <MacWindowControlsSpace>
<MacTrafficLights
onClose={appPropsContext?.onClose}
onFullscreen={appPropsContext?.onFullscreen}
onMinimize={appPropsContext?.onMinimize}
className="z-50 absolute top-[13px] left-[13px]"
/>
</MacWindowControlsSpace>
);
} }
export const Sidebar: React.FC<SidebarProps> = (props) => { export const Sidebar: React.FC<SidebarProps> = (props) => {
const experimental = useStore((state) => state.experimental); const experimental = useStore((state) => state.experimental);
const appPropsContext = useContext(AppPropsContext); const appPropsContext = useContext(AppPropsContext);
const { data: locations } = useBridgeQuery('SysGetLocations'); const { data: locations } = useBridgeQuery('SysGetLocations');
const { data: clientState } = useBridgeQuery('ClientGetState'); const { data: clientState } = useBridgeQuery('ClientGetState');
const { mutate: createLocation } = useBridgeCommand('LocCreate'); const { mutate: createLocation } = useBridgeCommand('LocCreate');
const tags = [ const tags = [
{ id: 1, name: 'Keepsafe', color: '#FF6788' }, { id: 1, name: 'Keepsafe', color: '#FF6788' },
{ id: 2, name: 'OBS', color: '#BF88FF' }, { id: 2, name: 'OBS', color: '#BF88FF' },
{ id: 3, name: 'BlackMagic', color: '#F0C94A' }, { id: 3, name: 'BlackMagic', color: '#F0C94A' },
{ id: 4, name: 'Camera Roll', color: '#00F0DB' }, { id: 4, name: 'Camera Roll', color: '#00F0DB' },
{ id: 5, name: 'Spacedrive', color: '#00F079' } { id: 5, name: 'Spacedrive', color: '#00F079' }
]; ];
return ( return (
<div className="flex flex-col flex-grow-0 flex-shrink-0 w-48 min-h-full px-2.5 overflow-x-hidden overflow-y-scroll border-r border-gray-100 no-scrollbar bg-gray-50 dark:bg-gray-850 dark:border-gray-600"> <div className="flex flex-col flex-grow-0 flex-shrink-0 w-48 min-h-full px-2.5 overflow-x-hidden overflow-y-scroll border-r border-gray-100 no-scrollbar bg-gray-50 dark:bg-gray-850 dark:border-gray-600">
{appPropsContext?.platform === 'browser' ? <MacOSTrafficLights /> : null} {appPropsContext?.platform === 'browser' &&
{appPropsContext?.platform === 'macOS' ? ( window.location.search.includes('showControls') ? (
<div data-tauri-drag-region className="h-[23px]" /> <MacWindowControls />
) : null} ) : null}
{appPropsContext?.platform === 'macOS' ? <MacWindowControlsSpace /> : null}
<Dropdown <Dropdown
buttonProps={{ buttonProps={{
justifyLeft: true, justifyLeft: true,
className: `flex w-full text-left max-w-full mb-1 mt-1 -mr-0.5 shadow-xs rounded className: `flex w-full text-left max-w-full mb-1 mt-1 -mr-0.5 shadow-xs rounded
!bg-gray-50 !bg-gray-50
border-gray-150 border-gray-150
hover:!bg-gray-1000 hover:!bg-gray-1000
@ -95,127 +108,127 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
dark:!border-gray-550 dark:!border-gray-550
dark:hover:!border-gray-500`, dark:hover:!border-gray-500`,
variant: 'gray' variant: 'gray'
}} }}
// buttonIcon={<Book weight="bold" className="w-4 h-4 mt-0.5 mr-1" />} // buttonIcon={<Book weight="bold" className="w-4 h-4 mt-0.5 mr-1" />}
buttonText={clientState?.client_name || 'Loading...'} buttonText={clientState?.client_name || 'Loading...'}
items={[ items={[
[{ name: clientState?.client_name || '', selected: true }, { name: 'Private Library' }], [{ name: clientState?.client_name || '', selected: true }, { name: 'Private Library' }],
[ [
{ name: 'Library Settings', icon: CogIcon }, { name: 'Library Settings', icon: CogIcon },
{ name: 'Add Library', icon: PlusIcon }, { name: 'Add Library', icon: PlusIcon },
{ name: 'Lock', icon: LockClosedIcon }, { name: 'Lock', icon: LockClosedIcon },
{ name: 'Hide', icon: EyeOffIcon } { name: 'Hide', icon: EyeOffIcon }
] ]
]} ]}
/> />
<div className="pt-1"> <div className="pt-1">
<SidebarLink to="/overview"> <SidebarLink to="/overview">
<Icon component={Planet} /> <Icon component={Planet} />
Overview Overview
</SidebarLink> </SidebarLink>
<SidebarLink to="content"> <SidebarLink to="content">
<Icon component={CirclesFour} /> <Icon component={CirclesFour} />
Content Content
</SidebarLink> </SidebarLink>
<SidebarLink to="photos"> <SidebarLink to="photos">
<Icon component={PhotographIcon} /> <Icon component={PhotographIcon} />
Photos Photos
</SidebarLink> </SidebarLink>
{experimental ? ( {experimental ? (
<SidebarLink to="debug"> <SidebarLink to="debug">
<Icon component={Code} /> <Icon component={Code} />
Debug Debug
</SidebarLink> </SidebarLink>
) : ( ) : (
<></> <></>
)} )}
{/* <SidebarLink to="explorer"> {/* <SidebarLink to="explorer">
<Icon component={MonitorPlay} /> <Icon component={MonitorPlay} />
Explorer Explorer
</SidebarLink> */} </SidebarLink> */}
</div> </div>
<div> <div>
<Heading>Locations</Heading> <Heading>Locations</Heading>
{locations?.map((location, index) => { {locations?.map((location, index) => {
return ( return (
<div key={index} className="flex flex-row items-center"> <div key={index} className="flex flex-row items-center">
<NavLink <NavLink
className="'relative w-full group'" className="'relative w-full group'"
to={{ to={{
pathname: `explorer/${location.id}` pathname: `explorer/${location.id}`
}} }}
> >
{({ isActive }) => ( {({ isActive }) => (
<span <span
className={clsx( className={clsx(
'max-w mb-[2px] text-gray-550 dark:text-gray-150 rounded px-2 py-1 flex flex-row flex-grow items-center hover:bg-gray-100 dark:hover:bg-gray-600 text-sm', 'max-w mb-[2px] text-gray-550 dark:text-gray-150 rounded px-2 py-1 flex flex-row flex-grow items-center hover:bg-gray-100 dark:hover:bg-gray-600 text-sm',
{ {
'!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive '!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive
} }
)} )}
> >
<div className="w-[18px] mr-2 -mt-0.5"> <div className="w-[18px] mr-2 -mt-0.5">
<FolderWhite className={clsx(!isActive && 'hidden')} /> <FolderWhite className={clsx(!isActive && 'hidden')} />
<Folder className={clsx(isActive && 'hidden')} /> <Folder className={clsx(isActive && 'hidden')} />
</div> </div>
{location.name} {location.name}
<div className="flex-grow" /> <div className="flex-grow" />
</span> </span>
)} )}
</NavLink> </NavLink>
</div> </div>
); );
})} })}
<button <button
onClick={() => { onClick={() => {
appPropsContext?.openDialog({ directory: true }).then((result) => { appPropsContext?.openDialog({ directory: true }).then((result) => {
createLocation({ path: result }); createLocation({ path: result });
}); });
}} }}
className="w-full px-2 py-1.5 mt-1 text-xs font-bold text-center text-gray-400 dark:text-gray-500 border border-dashed rounded border-transparent cursor-normal border-gray-350 dark:border-gray-550 hover:dark:border-gray-500 transition" className="w-full px-2 py-1.5 mt-1 text-xs font-bold text-center text-gray-400 dark:text-gray-500 border border-dashed rounded border-transparent cursor-normal border-gray-350 dark:border-gray-550 hover:dark:border-gray-500 transition"
> >
Add Location Add Location
</button> </button>
</div> </div>
<div> <div>
<Heading>Tags</Heading> <Heading>Tags</Heading>
<div className="mb-2"> <div className="mb-2">
{tags.map((tag, index) => ( {tags.map((tag, index) => (
<SidebarLink key={index} to={`tag/${tag.id}`} className=""> <SidebarLink key={index} to={`tag/${tag.id}`} className="">
<div <div
className="w-[12px] h-[12px] rounded-full" className="w-[12px] h-[12px] rounded-full"
style={{ backgroundColor: tag.color }} style={{ backgroundColor: tag.color }}
/> />
<span className="ml-2 text-sm">{tag.name}</span> <span className="ml-2 text-sm">{tag.name}</span>
</SidebarLink> </SidebarLink>
))} ))}
</div> </div>
</div> </div>
<div className="flex-grow" /> <div className="flex-grow" />
<RunningJobsWidget /> <RunningJobsWidget />
{/* <div className="flex w-full"> {/* <div className="flex w-full">
</div> */} </div> */}
<div className="mb-2"> <div className="mb-2">
<NavLink to="/settings/general"> <NavLink to="/settings/general">
{({ isActive }) => ( {({ isActive }) => (
<Button <Button
noPadding noPadding
variant={isActive ? 'default' : 'default'} variant={isActive ? 'default' : 'default'}
className={clsx( className={clsx(
'px-[4px] mb-1' 'px-[4px] mb-1'
// isActive && '!bg-gray-550' // isActive && '!bg-gray-550'
)} )}
> >
<CogIcon className="w-5 h-5" /> <CogIcon className="w-5 h-5" />
</Button> </Button>
)} )}
</NavLink> </NavLink>
</div> </div>
</div> </div>
); );
}; };

View file

@ -1,18 +1,19 @@
import clsx from 'clsx'; import clsx from 'clsx';
import React from 'react'; import React from 'react';
import { DefaultProps } from '../primitive/types'; import { DefaultProps } from '../primitive/types';
export interface DriveListItemProps extends DefaultProps { export interface DriveListItemProps extends DefaultProps {
name: string; name: string;
} }
export const DriveListItem: React.FC<DriveListItemProps> = (props) => { export const DriveListItem: React.FC<DriveListItemProps> = (props) => {
return ( return (
<div <div
className={clsx( className={clsx(
'rounded px-1.5 py-1 text-xs font-medium inline-block cursor-default', 'rounded px-1.5 py-1 text-xs font-medium inline-block cursor-default',
props.className props.className
)} )}
></div> ></div>
); );
}; };

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 { Transition } from '@headlessui/react';
import { useBridgeQuery } from '@sd/client';
import clsx from 'clsx'; import clsx from 'clsx';
import React, { DetailedHTMLProps, HTMLAttributes } from 'react';
import ProgressBar from '../primitive/ProgressBar';
const MiddleTruncatedText = ({ const MiddleTruncatedText = ({
children, children,
...props ...props
}: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>) => { }: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>) => {
const text = children?.toString() ?? ''; const text = children?.toString() ?? '';
const first = text.substring(0, text.length / 2); const first = text.substring(0, text.length / 2);
const last = text.substring(first.length); const last = text.substring(first.length);
// Literally black magic // Literally black magic
const fontFaceScaleFactor = 1.61; const fontFaceScaleFactor = 1.61;
const startWidth = fontFaceScaleFactor * 5; const startWidth = fontFaceScaleFactor * 5;
const endWidth = fontFaceScaleFactor * 4; const endWidth = fontFaceScaleFactor * 4;
return ( return (
<div className="whitespace-nowrap overflow-hidden w-full"> <div className="whitespace-nowrap overflow-hidden w-full">
<span <span
{...props} {...props}
style={{ maxWidth: `calc(100% - (1em * ${endWidth}))`, minWidth: startWidth }} style={{
className={clsx( maxWidth: `calc(100% - (1em * ${endWidth}))`,
props?.className, minWidth: startWidth
'text-ellipsis inline-block align-bottom whitespace-nowrap overflow-hidden' }}
)} className={clsx(
> props?.className,
{first} 'text-ellipsis inline-block align-bottom whitespace-nowrap overflow-hidden'
</span> )}
<span >
{...props} {first}
style={{ maxWidth: `calc(100% - (1em * ${startWidth}))`, direction: 'rtl' }} </span>
className={clsx( <span
props?.className, {...props}
'inline-block align-bottom whitespace-nowrap overflow-hidden' style={{
)} maxWidth: `calc(100% - (1em * ${startWidth}))`,
> direction: 'rtl'
{last} }}
</span> className={clsx(
</div> props?.className,
); 'inline-block align-bottom whitespace-nowrap overflow-hidden'
)}
>
{last}
</span>
</div>
);
}; };
export default function RunningJobsWidget() { export default function RunningJobsWidget() {
const { data: jobs } = useBridgeQuery('JobGetRunning'); const { data: jobs } = useBridgeQuery('JobGetRunning');
return ( return (
<div className="flex flex-col space-y-4"> <div className="flex flex-col space-y-4">
{jobs?.map((job) => ( {jobs?.map((job) => (
<Transition <Transition
show={true} show={true}
enter="transition-translate ease-in-out duration-200" enter="transition-translate ease-in-out duration-200"
enterFrom="translate-y-24" enterFrom="translate-y-24"
enterTo="translate-y-0" enterTo="translate-y-0"
leave="transition-translate ease-in-out duration-200" leave="transition-translate ease-in-out duration-200"
leaveFrom="translate-y-0" leaveFrom="translate-y-0"
leaveTo="translate-y-24" leaveTo="translate-y-24"
> >
<div key={job.id} className="flex flex-col px-2 pt-1.5 pb-2 bg-gray-700 rounded"> <div key={job.id} className="flex flex-col px-2 pt-1.5 pb-2 bg-gray-700 rounded">
{/* <span className="mb-0.5 text-tiny font-bold text-gray-400">{job.status} Job</span> */} {/* <span className="mb-0.5 text-tiny font-bold text-gray-400">{job.status} Job</span> */}
<MiddleTruncatedText className="mb-1.5 text-gray-450 text-tiny"> <MiddleTruncatedText className="mb-1.5 text-gray-450 text-tiny">
{job.message} {job.message}
</MiddleTruncatedText> </MiddleTruncatedText>
<ProgressBar value={job.completed_task_count} total={job.task_count} /> <ProgressBar value={job.completed_task_count} total={job.task_count} />
</div> </div>
</Transition> </Transition>
))} ))}
</div> </div>
); );
} }

View file

@ -1,43 +1,43 @@
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { Button } from '@sd/ui'; import { Button } from '@sd/ui';
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
export interface DialogProps { export interface DialogProps {
trigger: ReactNode; trigger: ReactNode;
ctaLabel?: string; ctaLabel?: string;
ctaAction?: () => void; ctaAction?: () => void;
title?: string; title?: string;
description?: string; description?: string;
children: ReactNode; children: ReactNode;
} }
export default function Dialog(props: DialogProps) { export default function Dialog(props: DialogProps) {
return ( return (
<DialogPrimitive.Root> <DialogPrimitive.Root>
<DialogPrimitive.Trigger asChild>{props.trigger}</DialogPrimitive.Trigger> <DialogPrimitive.Trigger asChild>{props.trigger}</DialogPrimitive.Trigger>
<DialogPrimitive.Portal> <DialogPrimitive.Portal>
<DialogPrimitive.Overlay className="fixed top-0 dialog-overlay bottom-0 left-0 right-0 z-50 grid overflow-y-auto bg-black bg-opacity-50 rounded-xl place-items-center m-[1px]"> <DialogPrimitive.Overlay className="fixed top-0 dialog-overlay bottom-0 left-0 right-0 z-50 grid overflow-y-auto bg-black bg-opacity-50 rounded-xl place-items-center m-[1px]">
<DialogPrimitive.Content className="min-w-[300px] max-w-[400px] dialog-content rounded-md bg-gray-650 text-white border border-gray-550 shadow-deep"> <DialogPrimitive.Content className="min-w-[300px] max-w-[400px] dialog-content rounded-md bg-gray-650 text-white border border-gray-550 shadow-deep">
<div className="p-5"> <div className="p-5">
<DialogPrimitive.Title className="font-bold ">{props.title}</DialogPrimitive.Title> <DialogPrimitive.Title className="font-bold ">{props.title}</DialogPrimitive.Title>
<DialogPrimitive.Description className="text-sm text-gray-300"> <DialogPrimitive.Description className="text-sm text-gray-300">
{props.description} {props.description}
</DialogPrimitive.Description> </DialogPrimitive.Description>
{props.children} {props.children}
</div> </div>
<div className="flex flex-row justify-end px-3 py-3 space-x-2 bg-gray-600 border-t border-gray-550"> <div className="flex flex-row justify-end px-3 py-3 space-x-2 bg-gray-600 border-t border-gray-550">
<DialogPrimitive.Close asChild> <DialogPrimitive.Close asChild>
<Button size="sm" variant="gray"> <Button size="sm" variant="gray">
Close Close
</Button> </Button>
</DialogPrimitive.Close> </DialogPrimitive.Close>
<Button onClick={props.ctaAction} size="sm" variant="primary"> <Button onClick={props.ctaAction} size="sm" variant="primary">
{props.ctaLabel} {props.ctaLabel}
</Button> </Button>
</div> </div>
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPrimitive.Overlay> </DialogPrimitive.Overlay>
</DialogPrimitive.Portal> </DialogPrimitive.Portal>
</DialogPrimitive.Root> </DialogPrimitive.Root>
); );
} }

View file

@ -1,61 +1,64 @@
import { Transition } from '@headlessui/react'; import { Transition } from '@headlessui/react';
import clsx from 'clsx';
import React from 'react';
import { XIcon } from '@heroicons/react/solid'; import { XIcon } from '@heroicons/react/solid';
import { Button } from '@sd/ui'; import { Button } from '@sd/ui';
import clsx from 'clsx';
import React from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { MacOSTrafficLights } from '../file/Sidebar';
import { MacWindowControls } from '../file/Sidebar';
export interface ModalProps { export interface ModalProps {
full?: boolean; full?: boolean;
children: React.ReactNode; children: React.ReactNode;
} }
export const Modal: React.FC<ModalProps> = (props) => { export const Modal: React.FC<ModalProps> = (props) => {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<div <div
className={clsx('absolute w-screen h-screen z-30', { 'pointer-events-none hidden': !open })} className={clsx('absolute w-screen h-screen z-30', {
> 'pointer-events-none hidden': !open
<div className="flex w-screen h-screen p-2 pt-12"> })}
<Transition >
show <div className="flex w-screen h-screen p-2 pt-12">
enter="transition duration-150" <Transition
enterFrom="opacity-0" show
enterTo="opacity-100" enter="transition duration-150"
leave="transition duration-200" enterFrom="opacity-0"
leaveFrom="opacity-100" enterTo="opacity-100"
leaveTo="opacity-0" leave="transition duration-200"
> leaveFrom="opacity-100"
<div leaveTo="opacity-0"
data-tauri-drag-region >
onClick={() => navigate('/')} <div
className="absolute top-0 left-0 w-screen h-screen bg-white -z-50 rounded-2xl dark:bg-gray-800 bg-opacity-90" data-tauri-drag-region
/> onClick={() => navigate('/')}
</Transition> className="absolute top-0 left-0 w-screen h-screen bg-white -z-50 rounded-2xl dark:bg-gray-800 bg-opacity-90"
<Button />
onClick={() => navigate('/')} </Transition>
variant="gray" <Button
className="!px-1.5 absolute top-2 right-2" onClick={() => navigate('/')}
> variant="gray"
<XIcon className="w-4 h-4" /> className="!px-1.5 absolute top-2 right-2"
</Button> >
<Transition <XIcon className="w-4 h-4" />
show </Button>
className="flex flex-grow" <Transition
appear show
enter="transition ease-in-out-back duration-200" className="flex flex-grow"
enterFrom="opacity-0 translate-y-5" appear
enterTo="opacity-100" enter="transition ease-in-out-back duration-200"
leave="transition duration-200" enterFrom="opacity-0 translate-y-5"
leaveFrom="opacity-100" enterTo="opacity-100"
leaveTo="opacity-0" leave="transition duration-200"
> leaveFrom="opacity-100"
<div className="z-30 flex flex-grow mx-auto bg-white rounded-lg shadow-2xl max-w-7xl dark:bg-gray-650 "> leaveTo="opacity-0"
{props.children} >
</div> <div className="z-30 flex flex-grow mx-auto bg-white rounded-lg shadow-2xl max-w-7xl dark:bg-gray-650 ">
</Transition> {props.children}
</div> </div>
</div> </Transition>
); </div>
</div>
);
}; };

View file

@ -1,111 +1,112 @@
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/outline'; import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/outline';
import { useBridgeCommand } from '@sd/client';
import clsx from 'clsx'; import clsx from 'clsx';
import { import {
ArrowsClockwise, ArrowsClockwise,
Cloud, Cloud,
FolderPlus, FolderPlus,
IconProps, IconProps,
Key, Key,
Tag, Tag,
TerminalWindow TerminalWindow
} from 'phosphor-react'; } from 'phosphor-react';
import React, { DetailedHTMLProps, HTMLAttributes } from 'react'; import React, { DetailedHTMLProps, HTMLAttributes } from 'react';
import { useNavigate } from 'react-router-dom';
import { useExplorerState } from '../file/FileList';
import { Shortcut } from '../primitive/Shortcut'; import { Shortcut } from '../primitive/Shortcut';
import { DefaultProps } from '../primitive/types'; import { DefaultProps } from '../primitive/types';
import { useNavigate } from 'react-router-dom';
import { useBridgeCommand } from '@sd/client';
import { useExplorerState } from '../file/FileList';
export interface TopBarProps extends DefaultProps {} export interface TopBarProps extends DefaultProps {}
export interface TopBarButtonProps export interface TopBarButtonProps
extends DetailedHTMLProps<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> { extends DetailedHTMLProps<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
icon: React.ComponentType<IconProps>; icon: React.ComponentType<IconProps>;
group?: boolean; group?: boolean;
active?: boolean; active?: boolean;
left?: boolean; left?: boolean;
right?: boolean; right?: boolean;
} }
const TopBarButton: React.FC<TopBarButtonProps> = ({ icon: Icon, ...props }) => { const TopBarButton: React.FC<TopBarButtonProps> = ({ icon: Icon, ...props }) => {
return ( return (
<button <button
{...props} {...props}
className={clsx( className={clsx(
'mr-[1px] py-0.5 px-0.5 text-md font-medium hover:bg-gray-150 dark:transparent dark:hover:bg-gray-550 dark:active:bg-gray-500 rounded-md transition-colors duration-100', 'mr-[1px] py-0.5 px-0.5 text-md font-medium hover:bg-gray-150 dark:transparent dark:hover:bg-gray-550 dark:active:bg-gray-500 rounded-md transition-colors duration-100',
{ {
'rounded-r-none rounded-l-none': props.group && !props.left && !props.right, 'rounded-r-none rounded-l-none': props.group && !props.left && !props.right,
'rounded-r-none': props.group && props.left, 'rounded-r-none': props.group && props.left,
'rounded-l-none': props.group && props.right, 'rounded-l-none': props.group && props.right,
'dark:bg-gray-450 dark:hover:bg-gray-450 dark:active:bg-gray-450': props.active 'dark:bg-gray-450 dark:hover:bg-gray-450 dark:active:bg-gray-450': props.active
}, },
props.className props.className
)} )}
> >
<Icon weight={'regular'} className="m-0.5 w-5 h-5 text-gray-450 dark:text-gray-150" /> <Icon weight={'regular'} className="m-0.5 w-5 h-5 text-gray-450 dark:text-gray-150" />
</button> </button>
); );
}; };
export const TopBar: React.FC<TopBarProps> = (props) => { export const TopBar: React.FC<TopBarProps> = (props) => {
const { locationId } = useExplorerState(); const { locationId } = useExplorerState();
const { mutate: generateThumbsForLocation } = useBridgeCommand('GenerateThumbsForLocation', { const { mutate: generateThumbsForLocation } = useBridgeCommand('GenerateThumbsForLocation', {
onMutate: (data) => { onMutate: (data) => {
console.log('GenerateThumbsForLocation', data); console.log('GenerateThumbsForLocation', data);
} }
}); });
let navigate = useNavigate(); let navigate = useNavigate();
return ( return (
<> <>
<div <div
data-tauri-drag-region data-tauri-drag-region
className="flex h-[2.95rem] -mt-0.5 max-w z-10 pl-3 rounded-tr-2xl flex-shrink-0 items-center border-b dark:bg-gray-600 border-gray-100 dark:border-gray-800 !bg-opacity-60 backdrop-blur" className="flex h-[2.95rem] -mt-0.5 max-w z-10 pl-3 rounded-tr-2xl flex-shrink-0 items-center border-b dark:bg-gray-600 border-gray-100 dark:border-gray-800 !bg-opacity-60 backdrop-blur"
> >
<div className="flex"> <div className="flex">
<TopBarButton icon={ChevronLeftIcon} onClick={() => navigate(-1)} /> <TopBarButton icon={ChevronLeftIcon} onClick={() => navigate(-1)} />
<TopBarButton icon={ChevronRightIcon} onClick={() => navigate(1)} /> <TopBarButton icon={ChevronRightIcon} onClick={() => navigate(1)} />
</div> </div>
{/* <div className="flex mx-8 space-x-[1px]"> {/* <div className="flex mx-8 space-x-[1px]">
<TopBarButton active group left icon={List} /> <TopBarButton active group left icon={List} />
<TopBarButton group icon={Columns} /> <TopBarButton group icon={Columns} />
<TopBarButton group right icon={SquaresFour} /> <TopBarButton group right icon={SquaresFour} />
</div> */} </div> */}
<div data-tauri-drag-region className="flex flex-row justify-center flex-grow "> <div data-tauri-drag-region className="flex flex-row justify-center flex-grow ">
<div className="flex mx-8 space-x-2 pointer-events-auto"> <div className="flex mx-8 space-x-2 pointer-events-auto">
<TopBarButton icon={Tag} /> <TopBarButton icon={Tag} />
<TopBarButton icon={FolderPlus} /> <TopBarButton icon={FolderPlus} />
<TopBarButton icon={TerminalWindow} /> <TopBarButton icon={TerminalWindow} />
</div> </div>
<div className="relative flex h-7"> <div className="relative flex h-7">
<input <input
placeholder="Search" placeholder="Search"
className="w-32 h-[30px] focus:w-52 text-sm p-3 rounded-lg outline-none focus:ring-2 placeholder-gray-400 dark:placeholder-gray-500 bg-[#F6F2F6] border border-gray-50 dark:bg-gray-650 dark:border-gray-550 focus:ring-gray-100 dark:focus:ring-gray-600 transition-all" className="w-32 h-[30px] focus:w-52 text-sm p-3 rounded-lg outline-none focus:ring-2 placeholder-gray-400 dark:placeholder-gray-500 bg-[#F6F2F6] border border-gray-50 dark:bg-gray-650 dark:border-gray-550 focus:ring-gray-100 dark:focus:ring-gray-600 transition-all"
/> />
<div className="space-x-1 absolute top-[2px] right-1"> <div className="space-x-1 absolute top-[2px] right-1">
<Shortcut chars="⌘K" /> <Shortcut chars="⌘K" />
{/* <Shortcut chars="S" /> */} {/* <Shortcut chars="S" /> */}
</div> </div>
</div> </div>
<div className="flex mx-8 space-x-2"> <div className="flex mx-8 space-x-2">
<TopBarButton icon={Key} /> <TopBarButton icon={Key} />
<TopBarButton icon={Cloud} /> <TopBarButton icon={Cloud} />
<TopBarButton <TopBarButton
icon={ArrowsClockwise} icon={ArrowsClockwise}
onClick={() => { onClick={() => {
generateThumbsForLocation({ id: locationId, path: '' }); generateThumbsForLocation({ id: locationId, path: '' });
}} }}
/> />
</div> </div>
</div> </div>
{/* <img {/* <img
alt="spacedrive-logo" alt="spacedrive-logo"
src="/images/spacedrive_logo.png" src="/images/spacedrive_logo.png"
className="w-8 h-8 mt-[1px] mr-2 pointer-events-none" className="w-8 h-8 mt-[1px] mr-2 pointer-events-none"
/> */} /> */}
{/*<TopBarButton onClick={() => {*/} {/*<TopBarButton onClick={() => {*/}
{/* setSettingsOpen(!settingsOpen);*/} {/* setSettingsOpen(!settingsOpen);*/}
{/*}} className="mr-[8px]" icon={CogIcon} />*/} {/*}} className="mr-[8px]" icon={CogIcon} />*/}
</div> </div>
{/* <div className="h-[1px] flex-shrink-0 max-w bg-gray-200 dark:bg-gray-700" /> */} {/* <div className="h-[1px] flex-shrink-0 max-w bg-gray-200 dark:bg-gray-700" /> */}
</> </>
); );
}; };

View file

@ -1,65 +1,77 @@
import clsx from 'clsx'; import clsx from 'clsx';
import React from 'react'; import React, { useEffect } from 'react';
import closeIconPath from '../../assets/svg/macos_close.svg';
import fullscreenIconPath from '../../assets/svg/macos_fullscreen.svg';
import minimizeIconPath from '../../assets/svg/macos_minimize.svg';
import { useFocusState } from '../../hooks/useFocusState'; import { useFocusState } from '../../hooks/useFocusState';
import { DefaultProps } from '../primitive/types'; import { DefaultProps } from '../primitive/types';
import { ReactComponent as Close } from '../../assets/svg/macos_close.svg';
import { ReactComponent as Minimize } from '../../assets/svg/macos_minimize.svg';
import { ReactComponent as Fullscreen } from '../../assets/svg/macos_fullscreen.svg';
export interface TrafficLightsProps extends DefaultProps { export interface TrafficLightsProps extends DefaultProps {
onClose?: () => void; onClose?: () => void;
onMinimize?: () => void; onMinimize?: () => void;
onFullscreen?: () => void; onFullscreen?: () => void;
} }
export const TrafficLights: React.FC<TrafficLightsProps> = (props) => { export const MacTrafficLights: React.FC<TrafficLightsProps> = (props) => {
const [focused] = useFocusState(); const [focused] = useFocusState();
return ( return (
<div <div
data-tauri-drag-region data-tauri-drag-region
className={clsx('flex flex-row space-x-2 px-2 group', props.className)} className={clsx('flex flex-row space-x-[8px] group', props.className)}
> >
<Light mode="close" action={props.onClose} focused={focused} /> <TrafficLight type="close" onClick={props.onClose} colorful={focused} />
<Light mode="minimize" action={props.onMinimize} focused={focused} /> <TrafficLight type="minimize" onClick={props.onMinimize} colorful={focused} />
<Light mode="fullscreen" action={props.onFullscreen} focused={focused} /> <TrafficLight type="fullscreen" onClick={props.onFullscreen} colorful={focused} />
</div> </div>
); );
}; };
interface LightProps { interface TrafficLightProps {
mode: 'close' | 'minimize' | 'fullscreen'; type: 'close' | 'minimize' | 'fullscreen';
focused: boolean; colorful: boolean;
action?: () => void; onClick?: React.HTMLAttributes<HTMLDivElement>['onClick'];
} }
const Light: React.FC<LightProps> = (props) => { const TrafficLight: React.FC<TrafficLightProps> = (props) => {
return ( const { onClick = () => undefined, colorful = false, type } = props;
<div const iconPath = React.useRef<string>(closeIconPath);
onClick={props.action}
className={clsx('w-[13px] h-[13px] rounded-full bg-gray-500', { useEffect(() => {
'!bg-red-400 active:!bg-red-300': props.mode == 'close' && props.focused, switch (type) {
'!bg-green-400 active:!bg-green-300': props.mode == 'fullscreen' && props.focused, case 'close':
'!bg-yellow-400 active:!bg-yellow-300': props.mode == 'minimize' && props.focused iconPath.current = closeIconPath;
})} break;
> case 'minimize':
{(() => { iconPath.current = minimizeIconPath;
if (!props.focused) return <></>; break;
switch (props.mode) { case 'fullscreen':
case 'close': iconPath.current = fullscreenIconPath;
return ( break;
<Close className=" w-[13px] -mt-[1px] h-[15px] opacity-0 group-hover:opacity-100" /> }
); }, [type]);
case 'minimize':
return ( return (
<Minimize className="ml-[2px] w-[9px] -mt-[1px] h-[15px] opacity-0 group-hover:opacity-100" /> <div
); onClick={onClick}
case 'fullscreen': className={clsx(
return ( 'rounded-full box-border w-[12px] h-[12px] border-[0.5px] border-transparent bg-[#CDCED0] dark:bg-[#2B2C2F] flex justify-center items-center',
<Fullscreen className="ml-[1px] w-[11px] -mt-[1px] h-[15px] opacity-0 group-hover:opacity-100" /> {
); 'border-red-900 !bg-[#EC6A5E] active:hover:!bg-red-700 dark:active:hover:!bg-red-400':
} type === 'close' && colorful,
})()} 'border-yellow-900 !bg-[#F4BE4F] active:hover:!bg-yellow-600 dark:active:hover:!bg-yellow-200':
</div> type === 'minimize' && colorful,
); 'border-green-900 !bg-[#61C253] active:hover:!bg-green-700 dark:active:hover:!bg-green-300':
type === 'fullscreen' && colorful
}
)}
>
{colorful && (
<img
src={iconPath.current}
className="opacity-0 group-hover:opacity-100 group-active:opacity-100 pointer-events-none"
/>
)}
</div>
);
}; };

View file

@ -2,22 +2,22 @@ import clsx from 'clsx';
import React from 'react'; import React from 'react';
export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> { export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
containerClasname?: string; containerClasname?: string;
} }
export const Checkbox: React.FC<CheckboxProps> = (props) => { export const Checkbox: React.FC<CheckboxProps> = (props) => {
return ( return (
<label <label
className={clsx( className={clsx(
'flex items-center text-sm font-medium text-gray-700 dark:text-gray-100', 'flex items-center text-sm font-medium text-gray-700 dark:text-gray-100',
props.containerClasname props.containerClasname
)} )}
> >
<input <input
{...props} {...props}
type="checkbox" type="checkbox"
className={clsx( className={clsx(
` `
bg-gray-50 bg-gray-50
hover:bg-gray-100 hover:bg-gray-100
dark:bg-gray-800 dark:bg-gray-800
@ -32,10 +32,10 @@ export const Checkbox: React.FC<CheckboxProps> = (props) => {
text-primary text-primary
checked:ring-2 checked:ring-primary-500 checked:ring-2 checked:ring-primary-500
`, `,
props.className props.className
)} )}
/> />
<span className="select-none">Checkbox</span> <span className="select-none">Checkbox</span>
</label> </label>
); );
}; };

View file

@ -4,20 +4,20 @@ import ReactJson, { ReactJsonViewProps } from 'react-json-view';
export interface CodeBlockProps extends ReactJsonViewProps {} export interface CodeBlockProps extends ReactJsonViewProps {}
export default function CodeBlock(props: CodeBlockProps) { export default function CodeBlock(props: CodeBlockProps) {
return ( return (
<ReactJson <ReactJson
enableClipboard={false} enableClipboard={false}
displayDataTypes={false} displayDataTypes={false}
theme="ocean" theme="ocean"
style={{ style={{
padding: 20, padding: 20,
borderRadius: 5, borderRadius: 5,
backgroundColor: '#101016', backgroundColor: '#101016',
border: 1, border: 1,
borderColor: '#1E1E27', borderColor: '#1E1E27',
borderStyle: 'solid' borderStyle: 'solid'
}} }}
{...props} {...props}
/> />
); );
} }

View file

@ -2,7 +2,7 @@ import clsx from 'clsx';
import React from 'react'; import React from 'react';
const variants = { const variants = {
default: ` default: `
shadow-sm shadow-sm
bg-white bg-white
hover:bg-white hover:bg-white
@ -26,46 +26,46 @@ const variants = {
dark:text-white dark:text-white
placeholder-gray-300 placeholder-gray-300
`, `,
primary: '' primary: ''
}; };
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
variant?: keyof typeof variants; variant?: keyof typeof variants;
} }
export const Input = ({ ...props }: InputProps) => { export const Input = ({ ...props }: InputProps) => {
return ( return (
<input <input
{...props} {...props}
className={clsx( className={clsx(
`px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`, `px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`,
variants[props.variant || 'default'], variants[props.variant || 'default'],
props.className props.className
)} )}
/> />
); );
}; };
interface TextAreaProps extends React.InputHTMLAttributes<HTMLTextAreaElement> { interface TextAreaProps extends React.InputHTMLAttributes<HTMLTextAreaElement> {
variant?: keyof typeof variants; variant?: keyof typeof variants;
} }
export const TextArea = ({ size, ...props }: TextAreaProps) => { export const TextArea = ({ size, ...props }: TextAreaProps) => {
return ( return (
<textarea <textarea
{...props} {...props}
className={clsx( className={clsx(
`px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`, `px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`,
variants[props.variant || 'default'], variants[props.variant || 'default'],
size && '', size && '',
props.className props.className
)} )}
/> />
); );
}; };
export const Label: React.FC<{ slug?: string; children: string }> = (props) => ( export const Label: React.FC<{ slug?: string; children: string }> = (props) => (
<label className="text-sm font-bold" htmlFor={props.slug}> <label className="text-sm font-bold" htmlFor={props.slug}>
{props.children} {props.children}
</label> </label>
); );

View file

@ -1,26 +1,27 @@
import React from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import React from 'react';
import { DefaultProps } from './types'; import { DefaultProps } from './types';
interface InputContainerProps extends DefaultProps { interface InputContainerProps extends DefaultProps {
title: string; title: string;
description?: string; description?: string;
children: React.ReactNode; children: React.ReactNode;
mini?: boolean; mini?: boolean;
} }
export const InputContainer: React.FC<InputContainerProps> = (props) => { export const InputContainer: React.FC<InputContainerProps> = (props) => {
return ( return (
<div className="flex flex-row"> <div className="flex flex-row">
<div <div
className={clsx('flex flex-col w-full', !props.mini && 'pb-6', props.className)} className={clsx('flex flex-col w-full', !props.mini && 'pb-6', props.className)}
{...props} {...props}
> >
<h3 className="mb-1 font-medium text-gray-700 dark:text-gray-100">{props.title}</h3> <h3 className="mb-1 font-medium text-gray-700 dark:text-gray-100">{props.title}</h3>
{!!props.description && <p className="mb-2 text-sm text-gray-400 ">{props.description}</p>} {!!props.description && <p className="mb-2 text-sm text-gray-400 ">{props.description}</p>}
{!props.mini && props.children} {!props.mini && props.children}
</div> </div>
{props.mini && props.children} {props.mini && props.children}
</div> </div>
); );
}; };

View file

@ -1,101 +1,101 @@
import React, { Fragment, useEffect, useState } from 'react';
import { Listbox as ListboxPrimitive, Transition } from '@headlessui/react'; import { Listbox as ListboxPrimitive, Transition } from '@headlessui/react';
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid'; import { CheckIcon, SelectorIcon } from '@heroicons/react/solid';
import clsx from 'clsx'; import clsx from 'clsx';
import React, { Fragment, useEffect, useState } from 'react';
interface ListboxOption { interface ListboxOption {
option: string; option: string;
description?: string; description?: string;
key: string; key: string;
} }
export default function Listbox(props: { options: ListboxOption[]; className?: string }) { export default function Listbox(props: { options: ListboxOption[]; className?: string }) {
const [selected, setSelected] = useState(props.options[0]); const [selected, setSelected] = useState(props.options[0]);
useEffect(() => { useEffect(() => {
if (!selected) { if (!selected) {
setSelected(props.options[0]); setSelected(props.options[0]);
} }
}, [props.options]); }, [props.options]);
return ( return (
<> <>
<ListboxPrimitive value={selected} onChange={setSelected}> <ListboxPrimitive value={selected} onChange={setSelected}>
<div className="relative w-full"> <div className="relative w-full">
<ListboxPrimitive.Button <ListboxPrimitive.Button
className={clsx( className={clsx(
`relative w-full py-2 pl-3 pr-10 text-left bg-white dark:bg-gray-500 `relative w-full py-2 pl-3 pr-10 text-left bg-white dark:bg-gray-500
rounded-lg shadow-md cursor-default focus:outline-none focus-visible:ring-2 rounded-lg shadow-md cursor-default focus:outline-none focus-visible:ring-2
focus-visible:ring-opacity-75 focus-visible:ring-white focus-visible:ring-offset-orange-300 focus-visible:ring-opacity-75 focus-visible:ring-white focus-visible:ring-offset-orange-300
focus-visible:ring-offset-2 focus-visible:border-indigo-500 sm:text-sm`, focus-visible:ring-offset-2 focus-visible:border-indigo-500 sm:text-sm`,
props.className props.className
)} )}
> >
{selected?.option ? ( {selected?.option ? (
<span className="block truncate">{selected?.option}</span> <span className="block truncate">{selected?.option}</span>
) : ( ) : (
<span className="block truncate opacity-70">Nothing selected...</span> <span className="block truncate opacity-70">Nothing selected...</span>
)} )}
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> <span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<SelectorIcon className="w-5 h-5 text-gray-400" aria-hidden="true" /> <SelectorIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
</span> </span>
</ListboxPrimitive.Button> </ListboxPrimitive.Button>
<Transition <Transition
as={Fragment} as={Fragment}
leave="transition ease-in duration-100" leave="transition ease-in duration-100"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<ListboxPrimitive.Options <ListboxPrimitive.Options
className={` className={`
absolute w-full mt-1 overflow-auto rounded-md sm:text-sm absolute w-full mt-1 overflow-auto rounded-md sm:text-sm
text-base bg-white dark:bg-gray-500 shadow-lg max-h-60 text-base bg-white dark:bg-gray-500 shadow-lg max-h-60
ring-1 ring-black ring-opacity-5 focus:outline-none ring-1 ring-black ring-opacity-5 focus:outline-none
`} `}
> >
{props.options.map((person, personIdx) => ( {props.options.map((person, personIdx) => (
<ListboxPrimitive.Option <ListboxPrimitive.Option
key={personIdx} key={personIdx}
className={({ active }) => className={({ active }) =>
`cursor-default select-none relative rounded m-1 py-2 pl-8 pr-4 dark:text-white focus:outline-none ${ `cursor-default select-none relative rounded m-1 py-2 pl-8 pr-4 dark:text-white focus:outline-none ${
active active
? 'text-primary-900 bg-primary-600' ? 'text-primary-900 bg-primary-600'
: 'text-gray-900 dark:hover:bg-gray-600 dark:hover:bg-opacity-20' : 'text-gray-900 dark:hover:bg-gray-600 dark:hover:bg-opacity-20'
}` }`
} }
value={person} value={person}
> >
{({ selected }) => ( {({ selected }) => (
<> <>
<span <span
className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`} className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}
> >
{person.option} {person.option}
{person.description && ( {person.description && (
<span <span
className={clsx( className={clsx(
'text-gray-300 leading-5 ml-3 text-xs', 'text-gray-300 leading-5 ml-3 text-xs',
selected && 'text-white' selected && 'text-white'
)} )}
> >
{person.description} {person.description}
</span> </span>
)} )}
</span> </span>
{selected ? ( {selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-2 text-white"> <span className="absolute inset-y-0 left-0 flex items-center pl-2 text-white">
<CheckIcon className="w-5 h-5" aria-hidden="true" /> <CheckIcon className="w-5 h-5" aria-hidden="true" />
</span> </span>
) : null} ) : null}
</> </>
)} )}
</ListboxPrimitive.Option> </ListboxPrimitive.Option>
))} ))}
</ListboxPrimitive.Options> </ListboxPrimitive.Options>
</Transition> </Transition>
</div> </div>
</ListboxPrimitive> </ListboxPrimitive>
</> </>
); );
} }

View file

@ -2,23 +2,23 @@ import * as ProgressPrimitive from '@radix-ui/react-progress';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
interface Props { interface Props {
value: number; value: number;
total: number; total: number;
} }
const ProgressBar = (props: Props) => { const ProgressBar = (props: Props) => {
const percentage = Math.round((props.value / props.total) * 100); const percentage = Math.round((props.value / props.total) * 100);
return ( return (
<ProgressPrimitive.Root <ProgressPrimitive.Root
value={percentage} value={percentage}
className="w-full h-1 overflow-hidden bg-gray-200 rounded-full dark:bg-gray-500" className="w-full h-1 overflow-hidden bg-gray-200 rounded-full dark:bg-gray-500"
> >
<ProgressPrimitive.Indicator <ProgressPrimitive.Indicator
style={{ width: `${percentage}%` }} style={{ width: `${percentage}%` }}
className="h-full duration-300 ease-in-out bg-primary " className="h-full duration-300 ease-in-out bg-primary "
/> />
</ProgressPrimitive.Root> </ProgressPrimitive.Root>
); );
}; };
export default ProgressBar; export default ProgressBar;

View file

@ -1,16 +1,17 @@
import clsx from 'clsx'; import clsx from 'clsx';
import React from 'react'; import React from 'react';
import { DefaultProps } from './types'; import { DefaultProps } from './types';
export interface ShortcutProps extends DefaultProps { export interface ShortcutProps extends DefaultProps {
chars: string; chars: string;
} }
export const Shortcut: React.FC<ShortcutProps> = (props) => { export const Shortcut: React.FC<ShortcutProps> = (props) => {
return ( return (
<span <span
className={clsx( className={clsx(
` `
px-1 px-1
py-0.5 py-0.5
text-xs text-xs
@ -24,10 +25,10 @@ export const Shortcut: React.FC<ShortcutProps> = (props) => {
border-t-2 border-t-2
rounded-lg rounded-lg
`, `,
props.className props.className
)} )}
> >
{props.chars} {props.chars}
</span> </span>
); );
}; };

View file

@ -1,16 +1,16 @@
import React from 'react';
import * as SliderPrimitive from '@radix-ui/react-slider'; import * as SliderPrimitive from '@radix-ui/react-slider';
import React from 'react';
const Slider = (props: SliderPrimitive.SliderProps) => ( const Slider = (props: SliderPrimitive.SliderProps) => (
<SliderPrimitive.Root {...props} className="relative flex items-center w-full h-6 select-none"> <SliderPrimitive.Root {...props} className="relative flex items-center w-full h-6 select-none">
<SliderPrimitive.Track className="relative flex-grow h-2 bg-gray-500 rounded-full outline-none"> <SliderPrimitive.Track className="relative flex-grow h-2 bg-gray-500 rounded-full outline-none">
<SliderPrimitive.Range className="absolute h-full rounded-full outline-none bg-primary-500" /> <SliderPrimitive.Range className="absolute h-full rounded-full outline-none bg-primary-500" />
</SliderPrimitive.Track> </SliderPrimitive.Track>
<SliderPrimitive.Thumb <SliderPrimitive.Thumb
className="z-50 block w-5 h-5 font-bold transition rounded-full shadow-lg outline-none shadow-black bg-primary-500 ring-primary-500 ring-opacity-30 focus:ring-4" className="z-50 block w-5 h-5 font-bold transition rounded-full shadow-lg outline-none shadow-black bg-primary-500 ring-primary-500 ring-opacity-30 focus:ring-4"
data-tip="1.0" data-tip="1.0"
/> />
</SliderPrimitive.Root> </SliderPrimitive.Root>
); );
export default Slider; export default Slider;

View file

@ -1,30 +1,31 @@
import clsx from 'clsx'; import clsx from 'clsx';
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { DefaultProps } from './types'; import { DefaultProps } from './types';
export interface TagProps extends DefaultProps { export interface TagProps extends DefaultProps {
children: ReactNode; children: ReactNode;
color: 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple' | 'pink'; color: 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple' | 'pink';
} }
export const Tag: React.FC<TagProps> = (props) => { export const Tag: React.FC<TagProps> = (props) => {
return ( return (
<div <div
className={clsx( className={clsx(
'rounded px-1.5 py-1 text-xs font-medium inline-block cursor-default', 'rounded px-1.5 py-1 text-xs font-medium inline-block cursor-default',
{ {
'bg-red-500 hover:bg-red-400': props.color === 'red', 'bg-red-500 hover:bg-red-400': props.color === 'red',
'bg-orange-500 hover:bg-orange-400': props.color === 'orange', 'bg-orange-500 hover:bg-orange-400': props.color === 'orange',
'bg-yellow-500 hover:bg-yellow-400': props.color === 'yellow', 'bg-yellow-500 hover:bg-yellow-400': props.color === 'yellow',
'bg-green-500 hover:bg-green-400': props.color === 'green', 'bg-green-500 hover:bg-green-400': props.color === 'green',
'bg-blue-500 hover:bg-blue-400': props.color === 'blue', 'bg-blue-500 hover:bg-blue-400': props.color === 'blue',
'bg-purple-500 hover:bg-purple-400': props.color === 'purple', 'bg-purple-500 hover:bg-purple-400': props.color === 'purple',
'bg-pink-500 hover:bg-pink-400': props.color === 'pink' 'bg-pink-500 hover:bg-pink-400': props.color === 'pink'
}, },
props.className props.className
)} )}
> >
{props.children} {props.children}
</div> </div>
); );
}; };

View file

@ -1,40 +1,40 @@
import React from 'react';
import { Switch } from '@headlessui/react'; import { Switch } from '@headlessui/react';
import clsx from 'clsx'; import clsx from 'clsx';
import React from 'react';
export interface ToggleProps { export interface ToggleProps {
value: boolean; value: boolean;
onChange?: (newValue: boolean) => void; onChange?: (newValue: boolean) => void;
size?: 'sm' | 'md'; size?: 'sm' | 'md';
} }
export const Toggle: React.FC<ToggleProps> = (props) => { export const Toggle: React.FC<ToggleProps> = (props) => {
const { value: isEnabled = false, onChange = (val) => null, size = 'sm' } = props; const { value: isEnabled = false, onChange = (val) => null, size = 'sm' } = props;
return ( return (
<Switch <Switch
checked={isEnabled} checked={isEnabled}
onChange={onChange} onChange={onChange}
className={clsx( className={clsx(
'transition relative flex-shrink-0 inline-flex items-center h-6 w-11 rounded-full bg-gray-200 dark:bg-gray-550', 'transition relative flex-shrink-0 inline-flex items-center h-6 w-11 rounded-full bg-gray-200 dark:bg-gray-550',
{ {
'bg-primary-500 dark:bg-primary-500': isEnabled, 'bg-primary-500 dark:bg-primary-500': isEnabled,
'h-6 w-11': size === 'sm', 'h-6 w-11': size === 'sm',
'h-8 w-[55px]': size === 'md' 'h-8 w-[55px]': size === 'md'
} }
)} )}
> >
<span <span
className={clsx( className={clsx(
'transition inline-block w-4 h-4 transform bg-white rounded-full', 'transition inline-block w-4 h-4 transform bg-white rounded-full',
isEnabled ? 'translate-x-6' : 'translate-x-1', isEnabled ? 'translate-x-6' : 'translate-x-1',
{ {
'w-4 h-4': size === 'sm', 'w-4 h-4': size === 'sm',
'h-6 w-6': size === 'md', 'h-6 w-6': size === 'md',
'translate-x-7': size === 'md' && isEnabled 'translate-x-7': size === 'md' && isEnabled
} }
)} )}
/> />
</Switch> </Switch>
); );
}; };

View file

@ -1,33 +1,33 @@
import React from 'react'; import React from 'react';
interface StyleState { interface StyleState {
active: string[]; active: string[];
hover: string[]; hover: string[];
normal: string[]; normal: string[];
} }
interface Variant { interface Variant {
base: string; base: string;
light: StyleState; light: StyleState;
dark: StyleState; dark: StyleState;
} }
function tw(variant: Variant): string { function tw(variant: Variant): string {
return `${variant.base} ${variant.light}`; return `${variant.base} ${variant.light}`;
} }
const variants: Record<string, string> = { const variants: Record<string, string> = {
default: tw({ default: tw({
base: 'shadow-sm', base: 'shadow-sm',
light: { light: {
normal: ['bg-gray-50', 'border-gray-100', 'text-gray-700'], normal: ['bg-gray-50', 'border-gray-100', 'text-gray-700'],
hover: ['bg-gray-100', 'border-gray-200', 'text-gray-900'], hover: ['bg-gray-100', 'border-gray-200', 'text-gray-900'],
active: ['bg-gray-50', 'border-gray-200', 'text-gray-600'] active: ['bg-gray-50', 'border-gray-200', 'text-gray-600']
}, },
dark: { dark: {
normal: ['bg-gray-800 ', 'border-gray-100', ' text-gray-200'], normal: ['bg-gray-800 ', 'border-gray-100', ' text-gray-200'],
active: ['bg-gray-700 ', 'border-gray-200 ', 'text-white'], active: ['bg-gray-700 ', 'border-gray-200 ', 'text-white'],
hover: ['bg-gray-700 ', 'border-gray-600 ', 'text-white'] hover: ['bg-gray-700 ', 'border-gray-600 ', 'text-white']
} }
}) })
}; };

View file

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

View file

@ -1,20 +1,20 @@
import React from 'react';
import { Transition } from '@headlessui/react'; import { Transition } from '@headlessui/react';
import React from 'react';
export default function SlideUp(props: { children: React.ReactNode }) { export default function SlideUp(props: { children: React.ReactNode }) {
return ( return (
<Transition <Transition
show show
className="flex flex-grow" className="flex flex-grow"
appear appear
enter="transition ease-in-out-back duration-200" enter="transition ease-in-out-back duration-200"
enterFrom="opacity-0 translate-y-5" enterFrom="opacity-0 translate-y-5"
enterTo="opacity-100" enterTo="opacity-100"
leave="transition duration-200" leave="transition duration-200"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
{props.children} {props.children}
</Transition> </Transition>
); );
} }

View file

@ -1,58 +1,58 @@
{ {
"node_state": { "node_state": {
"node_pub_id": "f4deae23-e578-456e-8897-9ff59444c08b", "node_pub_id": "f4deae23-e578-456e-8897-9ff59444c08b",
"node_id": 1, "node_id": 1,
"node_name": "Jamie's MacBook Pro", "node_name": "Jamie's MacBook Pro",
"data_path": "/Users/jamie/Library/Application Support/spacedrive", "data_path": "/Users/jamie/Library/Application Support/spacedrive",
"tcp_port": 0, "tcp_port": 0,
"libraries": [ "libraries": [
{ {
"library_uuid": "9816efb4-5f2d-4c84-8674-818767bb1184", "library_uuid": "9816efb4-5f2d-4c84-8674-818767bb1184",
"library_id": 0, "library_id": 0,
"library_path": "/Users/jamie/Library/Application Support/spacedrive/library.db", "library_path": "/Users/jamie/Library/Application Support/spacedrive/library.db",
"offline": false "offline": false
} }
], ],
"current_library_uuid": "9816efb4-5f2d-4c84-8674-818767bb1184" "current_library_uuid": "9816efb4-5f2d-4c84-8674-818767bb1184"
}, },
"libraries": [ "libraries": [
{ {
"id": 1, "id": 1,
"pub_id": "9816efb4-5f2d-4c84-8674-818767bb1184", "pub_id": "9816efb4-5f2d-4c84-8674-818767bb1184",
"date_created": "2020-04-01T00:00:00.000Z", "date_created": "2020-04-01T00:00:00.000Z",
"is_primary": true "is_primary": true
} }
], ],
"nodes": [ "nodes": [
{ {
"id": 1, "id": 1,
"pub_id": "f4deae23-e578-456e-8897-9ff59444c08b", "pub_id": "f4deae23-e578-456e-8897-9ff59444c08b",
"name": "Jamie's MacBook Pro", "name": "Jamie's MacBook Pro",
"platform": "macos", "platform": "macos",
"version": "0.0.1", "version": "0.0.1",
"date_created": "2020-04-01T00:00:00.000Z", "date_created": "2020-04-01T00:00:00.000Z",
"last_seen": "2020-04-01T00:00:00.000Z", "last_seen": "2020-04-01T00:00:00.000Z",
"timezone": "America/Los_Angeles" "timezone": "America/Los_Angeles"
}, },
{ {
"id": 1, "id": 1,
"pub_id": "8fec7649-08d8-4828-baa3-e1078d9f15ee", "pub_id": "8fec7649-08d8-4828-baa3-e1078d9f15ee",
"name": "Jamie's iPhone 12", "name": "Jamie's iPhone 12",
"platform": "ios", "platform": "ios",
"version": "0.0.1", "version": "0.0.1",
"date_created": "2020-04-01T00:00:00.000Z", "date_created": "2020-04-01T00:00:00.000Z",
"last_seen": "2020-04-01T00:00:00.000Z", "last_seen": "2020-04-01T00:00:00.000Z",
"timezone": "America/Los_Angeles" "timezone": "America/Los_Angeles"
}, },
{ {
"id": 1, "id": 1,
"pub_id": "67f66377-d38e-4fb7-961a-c1193a58b458", "pub_id": "67f66377-d38e-4fb7-961a-c1193a58b458",
"name": "Spacedrive Server", "name": "Spacedrive Server",
"platform": "server", "platform": "server",
"version": "0.0.1", "version": "0.0.1",
"date_created": "2020-04-01T00:00:00.000Z", "date_created": "2020-04-01T00:00:00.000Z",
"last_seen": "2020-04-01T00:00:00.000Z", "last_seen": "2020-04-01T00:00:00.000Z",
"timezone": "America/Los_Angeles" "timezone": "America/Los_Angeles"
} }
] ]
} }

File diff suppressed because it is too large Load diff

View file

@ -1,45 +1,46 @@
import { useContext, useEffect } from 'react';
import { CoreEvent } from '@sd/core';
import { transport } from '@sd/client'; import { transport } from '@sd/client';
import { CoreEvent } from '@sd/core';
import { useContext, useEffect } from 'react';
import { useQueryClient } from 'react-query'; import { useQueryClient } from 'react-query';
import { useExplorerState } from '../components/file/FileList';
import { AppPropsContext } from '../App'; import { AppPropsContext } from '../App';
import { useExplorerState } from '../components/file/FileList';
export function useCoreEvents() { export function useCoreEvents() {
const client = useQueryClient(); const client = useQueryClient();
const { addNewThumbnail } = useExplorerState(); const { addNewThumbnail } = useExplorerState();
useEffect(() => { useEffect(() => {
function handleCoreEvent(e: CoreEvent) { function handleCoreEvent(e: CoreEvent) {
switch (e?.key) { switch (e?.key) {
case 'NewThumbnail': case 'NewThumbnail':
addNewThumbnail(e.data.cas_id); addNewThumbnail(e.data.cas_id);
break; break;
case 'InvalidateQuery': case 'InvalidateQuery':
case 'InvalidateQueryDebounced': case 'InvalidateQueryDebounced':
let query = [e.data.key]; let query = [e.data.key];
// TODO: find a way to make params accessible in TS // TODO: find a way to make params accessible in TS
// also this method will only work for queries that use the whole params obj as the second key // also this method will only work for queries that use the whole params obj as the second key
// @ts-expect-error // @ts-expect-error
if (e.data.params) { if (e.data.params) {
// @ts-expect-error // @ts-expect-error
query.push(e.data.params); query.push(e.data.params);
} }
client.invalidateQueries(e.data.key); client.invalidateQueries(e.data.key);
break; break;
default: default:
break; break;
} }
} }
// check Tauri Event type // check Tauri Event type
transport?.on('core_event', handleCoreEvent); transport?.on('core_event', handleCoreEvent);
return () => { return () => {
transport?.off('core_event', handleCoreEvent); transport?.off('core_event', handleCoreEvent);
}; };
// listen('core_event', (e: { payload: CoreEvent }) => { // listen('core_event', (e: { payload: CoreEvent }) => {
// }); // });
}, [transport]); }, [transport]);
} }

View file

@ -1,17 +1,17 @@
import React, { useState, useEffect } from 'react'; import React, { useEffect, useState } from 'react';
export function useFocusState() { export function useFocusState() {
const [focused, setFocused] = useState(true); const [focused, setFocused] = useState(true);
const focus = () => setFocused(true); const focus = () => setFocused(true);
const blur = () => setFocused(false); const blur = () => setFocused(false);
useEffect(() => { useEffect(() => {
window.addEventListener('focus', focus); window.addEventListener('focus', focus);
window.addEventListener('blur', blur); window.addEventListener('blur', blur);
return () => { return () => {
window.removeEventListener('focus', focus); window.removeEventListener('focus', focus);
window.removeEventListener('blur', blur); window.removeEventListener('blur', blur);
}; };
}, []); }, []);
return [focused]; return [focused];
} }

View file

@ -1,10 +1,10 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
export function useInputState<T = any>(initialValue: T) { export function useInputState<T = any>(initialValue: T) {
const [value, setValue] = useState<T>(initialValue); const [value, setValue] = useState<T>(initialValue);
return { return {
onChange: (event: React.ChangeEvent<HTMLInputElement>) => onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
setValue(event.target.value as unknown as T), setValue(event.target.value as unknown as T),
value value
}; };
} }

View file

@ -1,16 +1,15 @@
// import { useBridgeCommand, useBridgeQuery } from '@sd/client'; // import { useBridgeCommand, useBridgeQuery } from '@sd/client';
import React from 'react'; import React from 'react';
export const ContentScreen: React.FC<{}> = (props) => { export const ContentScreen: React.FC<{}> = (props) => {
return ( return (
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll"> <div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
<div className="flex flex-col space-y-5 pb-7"> <div className="flex flex-col space-y-5 pb-7">
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600"> <p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be <b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
functional. functional.
</p> </p>
</div> </div>
</div> </div>
); );
}; };

View file

@ -1,54 +1,55 @@
import { useBridgeCommand, useBridgeQuery } from '@sd/client'; import { useBridgeCommand, useBridgeQuery } from '@sd/client';
import { Button } from '@sd/ui'; import { Button } from '@sd/ui';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { AppPropsContext } from '../App'; import { AppPropsContext } from '../App';
import CodeBlock from '../components/primitive/Codeblock'; import CodeBlock from '../components/primitive/Codeblock';
export const DebugScreen: React.FC<{}> = (props) => { export const DebugScreen: React.FC<{}> = (props) => {
const appPropsContext = useContext(AppPropsContext); const appPropsContext = useContext(AppPropsContext);
const { data: client } = useBridgeQuery('ClientGetState'); const { data: client } = useBridgeQuery('ClientGetState');
const { data: jobs } = useBridgeQuery('JobGetRunning'); const { data: jobs } = useBridgeQuery('JobGetRunning');
const { data: jobHistory } = useBridgeQuery('JobGetHistory'); const { data: jobHistory } = useBridgeQuery('JobGetHistory');
// const { mutate: purgeDB } = useBridgeCommand('PurgeDatabase', { // const { mutate: purgeDB } = useBridgeCommand('PurgeDatabase', {
// onMutate: () => { // onMutate: () => {
// alert('Database purged'); // alert('Database purged');
// } // }
// }); // });
const { mutate: identifyFiles } = useBridgeCommand('IdentifyUniqueFiles'); const { mutate: identifyFiles } = useBridgeCommand('IdentifyUniqueFiles');
return ( return (
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll"> <div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
<div className="flex flex-col space-y-5 pb-7"> <div className="flex flex-col space-y-5 pb-7">
<h1 className="text-lg font-bold ">Developer Debugger</h1> <h1 className="text-lg font-bold ">Developer Debugger</h1>
<div className="flex flex-row pb-4 space-x-2"> <div className="flex flex-row pb-4 space-x-2">
<Button <Button
className="w-40" className="w-40"
variant="gray" variant="gray"
size="sm" size="sm"
onClick={() => { onClick={() => {
if (client && appPropsContext?.onOpen) { if (client && appPropsContext?.onOpen) {
appPropsContext.onOpen(client.data_path); appPropsContext.onOpen(client.data_path);
} }
}} }}
> >
Open data folder Open data folder
</Button> </Button>
<Button <Button
className="w-40" className="w-40"
variant="gray" variant="gray"
size="sm" size="sm"
onClick={() => identifyFiles(undefined)} onClick={() => identifyFiles(undefined)}
> >
Identify unique files Identify unique files
</Button> </Button>
</div> </div>
<h1 className="text-sm font-bold ">Running Jobs</h1> <h1 className="text-sm font-bold ">Running Jobs</h1>
<CodeBlock src={{ ...jobs }} /> <CodeBlock src={{ ...jobs }} />
<h1 className="text-sm font-bold ">Job History</h1> <h1 className="text-sm font-bold ">Job History</h1>
<CodeBlock src={{ ...jobHistory }} /> <CodeBlock src={{ ...jobHistory }} />
<h1 className="text-sm font-bold ">Client State</h1> <h1 className="text-sm font-bold ">Client State</h1>
<CodeBlock src={{ ...client }} /> <CodeBlock src={{ ...client }} />
</div> </div>
</div> </div>
); );
}; };

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 { useBridgeQuery } from '@sd/client';
import React, { useEffect } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import { FileList, useExplorerState } from '../components/file/FileList';
import { Inspector } from '../components/file/Inspector'; import { Inspector } from '../components/file/Inspector';
import { TopBar } from '../components/layout/TopBar';
export const ExplorerScreen: React.FC<{}> = () => { export const ExplorerScreen: React.FC<{}> = () => {
let [searchParams] = useSearchParams(); let [searchParams] = useSearchParams();
let path = searchParams.get('path') || ''; let path = searchParams.get('path') || '';
let { id } = useParams(); let { id } = useParams();
let location_id = Number(id); let location_id = Number(id);
let [limit, setLimit] = React.useState(100); let [limit, setLimit] = React.useState(100);
useEffect(() => { useEffect(() => {
console.log({ location_id, path, limit }); console.log({ location_id, path, limit });
}, [location_id, path]); }, [location_id, path]);
const { selectedRowIndex } = useExplorerState(); const { selectedRowIndex } = useExplorerState();
const { data: currentDir } = useBridgeQuery( const { data: currentDir } = useBridgeQuery(
'LibGetExplorerDir', 'LibGetExplorerDir',
{ location_id: location_id!, path, limit }, { location_id: location_id!, path, limit },
{ enabled: !!location_id } { enabled: !!location_id }
); );
return ( return (
<div className="flex flex-col w-full h-full"> <div className="flex flex-col w-full h-full">
<TopBar /> <TopBar />
<div className="relative flex flex-row w-full "> <div className="relative flex flex-row w-full ">
<FileList location_id={location_id} path={path} limit={limit} /> <FileList location_id={location_id} path={path} limit={limit} />
{currentDir?.contents && ( {currentDir?.contents && (
<Inspector <Inspector
selectedFile={currentDir.contents[selectedRowIndex]} selectedFile={currentDir.contents[selectedRowIndex]}
locationId={location_id} locationId={location_id}
/> />
)} )}
</div> </div>
</div> </div>
); );
}; };

View file

@ -1,196 +1,193 @@
import { CloudIcon } from '@heroicons/react/outline'; import { MenuIcon, PlusIcon } from '@heroicons/react/solid';
import { CogIcon, MenuIcon, PlusIcon } from '@heroicons/react/solid';
import { useBridgeQuery } from '@sd/client'; import { useBridgeQuery } from '@sd/client';
import { Statistics } from '@sd/core'; import { Statistics } from '@sd/core';
import { Button } from '@sd/ui'; import { Button } from '@sd/ui';
import byteSize from 'byte-size'; import byteSize from 'byte-size';
import { DotsSixVertical, Laptop, LineSegments, Plus } from 'phosphor-react';
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { AppPropsContext } from '../App';
import { Device } from '../components/device/Device';
import FileItem from '../components/file/FileItem';
import Dialog from '../components/layout/Dialog';
import { Input } from '../components/primitive';
import { InputContainer } from '../components/primitive/InputContainer';
import { useCountUp } from 'react-countup'; import { useCountUp } from 'react-countup';
import { AppPropsContext } from '../App';
import { Device } from '../components/device/Device';
import Dialog from '../components/layout/Dialog';
import { Input } from '../components/primitive';
interface StatItemProps { interface StatItemProps {
name: string; name: string;
value?: string; value?: string;
unit?: string; unit?: string;
} }
const StatItem: React.FC<StatItemProps> = (props) => { const StatItem: React.FC<StatItemProps> = (props) => {
const countUpRef = React.useRef(null); const countUpRef = React.useRef(null);
const appPropsContext = useContext(AppPropsContext); const appPropsContext = useContext(AppPropsContext);
let size = byteSize(Number(props.value) || 0); let size = byteSize(Number(props.value) || 0);
let amount = parseFloat(size.value); let amount = parseFloat(size.value);
const [hasRun, setHasRun] = useState(false); const [hasRun, setHasRun] = useState(false);
const { update } = useCountUp({ const { update } = useCountUp({
startOnMount: !hasRun, startOnMount: !hasRun,
ref: countUpRef, ref: countUpRef,
// start: amount / 2, // start: amount / 2,
end: amount, end: amount,
delay: 0.1, delay: 0.1,
decimals: 1, decimals: 1,
duration: appPropsContext?.demoMode ? 2 : 1, duration: appPropsContext?.demoMode ? 2 : 1,
useEasing: true, useEasing: true,
onEnd: () => { onEnd: () => {
setHasRun(true); setHasRun(true);
} }
}); });
useEffect(() => { useEffect(() => {
update(amount); update(amount);
}, [amount]); }, [amount]);
return ( return (
<div className="flex flex-col flex-shrink-0 w-32 px-4 py-3 duration-75 transform rounded-md cursor-default hover:bg-gray-50 hover:dark:bg-gray-600"> <div className="flex flex-col flex-shrink-0 w-32 px-4 py-3 duration-75 transform rounded-md cursor-default hover:bg-gray-50 hover:dark:bg-gray-600">
<span className="text-sm text-gray-400">{props.name}</span> <span className="text-sm text-gray-400">{props.name}</span>
<span className="text-2xl font-bold"> <span className="text-2xl font-bold">
<span ref={countUpRef} /> <span ref={countUpRef} />
<span className="ml-1 text-[16px] text-gray-400">{size.unit}</span> <span className="ml-1 text-[16px] text-gray-400">{size.unit}</span>
</span> </span>
</div> </div>
); );
}; };
export const OverviewScreen: React.FC<{}> = (props) => { export const OverviewScreen: React.FC<{}> = (props) => {
const { data: libraryStatistics } = useBridgeQuery('GetLibraryStatistics'); const { data: libraryStatistics } = useBridgeQuery('GetLibraryStatistics');
const { data: clientState } = useBridgeQuery('ClientGetState'); const { data: clientState } = useBridgeQuery('ClientGetState');
const [stats, setStats] = useState<Statistics>(libraryStatistics || ({} as Statistics)); const [stats, setStats] = useState<Statistics>(libraryStatistics || ({} as Statistics));
// get app props context // get app props context
const appPropsContext = useContext(AppPropsContext); const appPropsContext = useContext(AppPropsContext);
useEffect(() => { useEffect(() => {
if (appPropsContext?.demoMode == true && !libraryStatistics?.library_db_size) { if (appPropsContext?.demoMode == true && !libraryStatistics?.library_db_size) {
setStats({ setStats({
total_bytes_capacity: '8093333345230', total_bytes_capacity: '8093333345230',
preview_media_bytes: '2304387532', preview_media_bytes: '2304387532',
library_db_size: '83345230', library_db_size: '83345230',
total_file_count: 20342345, total_file_count: 20342345,
total_bytes_free: '89734502034', total_bytes_free: '89734502034',
total_bytes_used: '8093333345230', total_bytes_used: '8093333345230',
total_unique_bytes: '9347397' total_unique_bytes: '9347397'
}); });
} else { } else {
setStats(libraryStatistics as Statistics); setStats(libraryStatistics as Statistics);
} }
}, [appPropsContext, libraryStatistics]); }, [appPropsContext, libraryStatistics]);
return ( return (
<div className="flex flex-col w-full h-screen overflow-x-hidden custom-scroll page-scroll"> <div className="flex flex-col w-full h-screen overflow-x-hidden custom-scroll page-scroll">
<div data-tauri-drag-region className="flex flex-shrink-0 w-full h-5" /> <div data-tauri-drag-region className="flex flex-shrink-0 w-full h-5" />
{/* PAGE */} {/* PAGE */}
<div className="flex flex-col w-full h-screen px-3"> <div className="flex flex-col w-full h-screen px-3">
{/* STAT HEADER */} {/* STAT HEADER */}
<div className="flex w-full"> <div className="flex w-full">
{/* STAT CONTAINER */} {/* STAT CONTAINER */}
<div className="flex pb-4 overflow-hidden"> <div className="flex pb-4 overflow-hidden">
<StatItem <StatItem
name="Total capacity" name="Total capacity"
value={stats?.total_bytes_capacity} value={stats?.total_bytes_capacity}
unit={stats?.total_bytes_capacity} unit={stats?.total_bytes_capacity}
/> />
<StatItem <StatItem
name="Index size" name="Index size"
value={stats?.library_db_size} value={stats?.library_db_size}
unit={stats?.library_db_size} unit={stats?.library_db_size}
/> />
<StatItem <StatItem
name="Preview media" name="Preview media"
value={stats?.preview_media_bytes} value={stats?.preview_media_bytes}
unit={stats?.preview_media_bytes} unit={stats?.preview_media_bytes}
/> />
<StatItem <StatItem
name="Free space" name="Free space"
value={stats?.total_bytes_free} value={stats?.total_bytes_free}
unit={stats?.total_bytes_free} unit={stats?.total_bytes_free}
/> />
<StatItem name="Total at-risk" value={'0'} unit={stats?.preview_media_bytes} /> <StatItem name="Total at-risk" value={'0'} unit={stats?.preview_media_bytes} />
{/* <StatItem {/* <StatItem
name="Total at-risk" name="Total at-risk"
value={'0'} value={'0'}
unit={stats?.preview_media_bytes} unit={stats?.preview_media_bytes}
/> />
<StatItem name="Total backed up" value={'0'} unit={''} /> */} <StatItem name="Total backed up" value={'0'} unit={''} /> */}
</div> </div>
<div className="flex-grow" /> <div className="flex-grow" />
<div className="space-x-2"> <div className="space-x-2">
<Dialog <Dialog
title="Add Device" title="Add Device"
description="Connect a new device to your library. Either enter another device's code or copy this one." description="Connect a new device to your library. Either enter another device's code or copy this one."
ctaAction={() => {}} ctaAction={() => {}}
ctaLabel="Connect" ctaLabel="Connect"
trigger={ trigger={
<Button <Button
size="sm" size="sm"
icon={<PlusIcon className="inline w-4 h-4 -mt-0.5 mr-1" />} icon={<PlusIcon className="inline w-4 h-4 -mt-0.5 mr-1" />}
variant="gray" variant="gray"
className="hidden sm:visible" className="hidden sm:visible"
> >
Add Device Add Device
</Button> </Button>
} }
> >
<div className="flex flex-col mt-2 space-y-3"> <div className="flex flex-col mt-2 space-y-3">
<div className="flex flex-col"> <div className="flex flex-col">
<span className="mb-1 text-xs font-bold uppercase text-gray-450"> <span className="mb-1 text-xs font-bold uppercase text-gray-450">
This Device This Device
</span> </span>
<Input readOnly disabled value="06ffd64309b24fb09e7c2188963d0207" /> <Input readOnly disabled value="06ffd64309b24fb09e7c2188963d0207" />
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
<span className="mb-1 text-xs font-bold uppercase text-gray-450"> <span className="mb-1 text-xs font-bold uppercase text-gray-450">
Enter a device code Enter a device code
</span> </span>
<Input value="" /> <Input value="" />
</div> </div>
</div> </div>
</Dialog> </Dialog>
<Button <Button
size="sm" size="sm"
className="w-8" className="w-8"
noPadding noPadding
icon={<MenuIcon className="inline w-4 h-4" />} icon={<MenuIcon className="inline w-4 h-4" />}
variant="gray" variant="gray"
></Button> ></Button>
</div> </div>
</div> </div>
<div className="flex flex-col pb-4 space-y-4"> <div className="flex flex-col pb-4 space-y-4">
<Device <Device
name="James' MacBook Pro" name="James' MacBook Pro"
size="1.4TB" size="1.4TB"
runningJob={{ amount: 65, task: 'Generating preview media' }} runningJob={{ amount: 65, task: 'Generating preview media' }}
locations={[{ name: 'Pictures' }, { name: 'Downloads' }, { name: 'Minecraft' }]} locations={[{ name: 'Pictures' }, { name: 'Downloads' }, { name: 'Minecraft' }]}
type="laptop" type="laptop"
/> />
<Device <Device
name={`James' iPhone 12`} name={`James' iPhone 12`}
size="47.7GB" size="47.7GB"
locations={[{ name: 'Camera Roll' }, { name: 'Notes' }]} locations={[{ name: 'Camera Roll' }, { name: 'Notes' }]}
type="phone" type="phone"
removeThisSoon removeThisSoon
/> />
<Device <Device
name={`Spacedrive Server`} name={`Spacedrive Server`}
size="5GB" size="5GB"
locations={[{ name: 'Cached' }, { name: 'Photos' }, { name: 'Documents' }]} locations={[{ name: 'Cached' }, { name: 'Photos' }, { name: 'Documents' }]}
type="server" type="server"
/> />
</div> </div>
<div className="px-5 py-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600"> <div className="px-5 py-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be <b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
functional. functional.
</div> </div>
<div className="flex flex-shrink-0 w-full h-4" /> <div className="flex flex-shrink-0 w-full h-4" />
</div> </div>
</div> </div>
); );
}; };

View file

@ -1,16 +1,15 @@
// import { useBridgeCommand, useBridgeQuery } from '@sd/client'; // import { useBridgeCommand, useBridgeQuery } from '@sd/client';
import React from 'react'; import React from 'react';
export const PhotosScreen: React.FC<{}> = (props) => { export const PhotosScreen: React.FC<{}> = (props) => {
return ( return (
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll"> <div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
<div className="flex flex-col space-y-5 pb-7"> <div className="flex flex-col space-y-5 pb-7">
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600"> <p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be <b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
functional. functional.
</p> </p>
</div> </div>
</div> </div>
); );
}; };

View file

@ -2,17 +2,17 @@ import React, { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router'; import { useLocation, useNavigate } from 'react-router';
export interface RedirectPageProps { export interface RedirectPageProps {
to: string; to: string;
} }
export const RedirectPage: React.FC<RedirectPageProps> = (props) => { export const RedirectPage: React.FC<RedirectPageProps> = (props) => {
const { to: destination } = props; const { to: destination } = props;
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
navigate(destination); navigate(destination);
}, []); }, []);
return null; return null;
}; };

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 { dummyIFile, FileList } from '../components/file/FileList';
import { SidebarLink } from '../components/file/Sidebar'; import {
import { Book, Database, HardDrive, PaintBrush } from 'phosphor-react'; CloudIcon,
CogIcon,
KeyIcon,
LockClosedIcon,
PhotographIcon,
TagIcon,
TerminalIcon,
UsersIcon
} from '@heroicons/react/outline';
import clsx from 'clsx'; import clsx from 'clsx';
import { Book, Database, HardDrive, PaintBrush } from 'phosphor-react';
import React from 'react';
import { Outlet } from 'react-router-dom'; import { Outlet } from 'react-router-dom';
import { SidebarLink } from '../components/file/Sidebar';
//@ts-ignore //@ts-ignore
// import { Spline } from 'react-spline'; // import { Spline } from 'react-spline';
// import WINDOWS_SCENE from '../assets/spline/scene.json'; // import WINDOWS_SCENE from '../assets/spline/scene.json';
const Icon = ({ component: Icon, ...props }: any) => ( const Icon = ({ component: Icon, ...props }: any) => (
<Icon weight="bold" {...props} className={clsx('w-4 h-4 mr-2', props.className)} /> <Icon weight="bold" {...props} className={clsx('w-4 h-4 mr-2', props.className)} />
); );
const Heading: React.FC<{ className?: string; children: string }> = ({ children, className }) => ( const Heading: React.FC<{ className?: string; children: string }> = ({ children, className }) => (
<div className={clsx('mt-5 mb-1 ml-1 text-xs font-semibold text-gray-300', className)}> <div className={clsx('mt-5 mb-1 ml-1 text-xs font-semibold text-gray-300', className)}>
{children} {children}
</div> </div>
); );
export const SettingsScreen: React.FC<{}> = () => { export const SettingsScreen: React.FC<{}> = () => {
return ( return (
<div className="flex flex-row w-full"> <div className="flex flex-row w-full">
<div className="h-full border-r border-gray-100 w-60 dark:border-gray-550"> <div className="h-full border-r border-gray-100 w-60 dark:border-gray-550">
<div data-tauri-drag-region className="w-full h-7" /> <div data-tauri-drag-region className="w-full h-7" />
<div className="p-5 pt-0"> <div className="p-5 pt-0">
<Heading className="mt-0">Client</Heading> <Heading className="mt-0">Client</Heading>
<SidebarLink to="/settings/general"> <SidebarLink to="/settings/general">
<Icon component={CogIcon} /> <Icon component={CogIcon} />
General General
</SidebarLink> </SidebarLink>
<SidebarLink to="/settings/security"> <SidebarLink to="/settings/security">
<Icon component={LockClosedIcon} /> <Icon component={LockClosedIcon} />
Security Security
</SidebarLink> </SidebarLink>
<SidebarLink to="/settings/appearance"> <SidebarLink to="/settings/appearance">
<Icon component={PaintBrush} /> <Icon component={PaintBrush} />
Appearance Appearance
</SidebarLink> </SidebarLink>
<SidebarLink to="/settings/experimental"> <SidebarLink to="/settings/experimental">
<Icon component={TerminalIcon} /> <Icon component={TerminalIcon} />
Experimental Experimental
</SidebarLink> </SidebarLink>
<Heading>Library</Heading> <Heading>Library</Heading>
<SidebarLink to="/settings/library"> <SidebarLink to="/settings/library">
<Icon component={Database} /> <Icon component={Database} />
Database Database
</SidebarLink> </SidebarLink>
<SidebarLink to="/settings/locations"> <SidebarLink to="/settings/locations">
<Icon component={HardDrive} /> <Icon component={HardDrive} />
Locations Locations
</SidebarLink> </SidebarLink>
<SidebarLink to="/settings/keys"> <SidebarLink to="/settings/keys">
<Icon component={KeyIcon} /> <Icon component={KeyIcon} />
Keys Keys
</SidebarLink> </SidebarLink>
<SidebarLink to="/settings/tags"> <SidebarLink to="/settings/tags">
<Icon component={TagIcon} /> <Icon component={TagIcon} />
Tags Tags
</SidebarLink> </SidebarLink>
<Heading>Cloud</Heading> <Heading>Cloud</Heading>
<SidebarLink to="/settings/sync"> <SidebarLink to="/settings/sync">
<Icon component={CloudIcon} /> <Icon component={CloudIcon} />
Sync Sync
</SidebarLink> </SidebarLink>
<SidebarLink to="/settings/contacts"> <SidebarLink to="/settings/contacts">
<Icon component={UsersIcon} /> <Icon component={UsersIcon} />
Contacts Contacts
</SidebarLink> </SidebarLink>
</div> </div>
</div> </div>
<div className=""> <div className="">
<div data-tauri-drag-region className="w-full h-7" /> <div data-tauri-drag-region className="w-full h-7" />
<div className="flex flex-grow-0 w-full h-full max-h-screen custom-scroll page-scroll"> <div className="flex flex-grow-0 w-full h-full max-h-screen custom-scroll page-scroll">
<div className="flex flex-grow px-12 pb-5"> <div className="flex flex-grow px-12 pb-5">
<Outlet /> <Outlet />
<div className="block h-20" /> <div className="block h-20" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); );
}; };

View file

@ -2,17 +2,17 @@ import React, { useEffect } from 'react';
import { useParams, useSearchParams } from 'react-router-dom'; import { useParams, useSearchParams } from 'react-router-dom';
export const TagScreen: React.FC<{}> = () => { export const TagScreen: React.FC<{}> = () => {
let [searchParams] = useSearchParams(); let [searchParams] = useSearchParams();
let path = searchParams.get('path') || ''; let path = searchParams.get('path') || '';
let { id } = useParams(); let { id } = useParams();
return ( return (
<div className="w-full p-5"> <div className="w-full p-5">
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600"> <p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be <b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
functional. functional.
</p> </p>
</div> </div>
); );
}; };

View file

@ -1,38 +1,39 @@
import React from 'react';
import { Button } from '@sd/ui'; import { Button } from '@sd/ui';
import { InputContainer } from '../../components/primitive/InputContainer'; import React from 'react';
import { Toggle } from '../../components/primitive';
import { useStore } from '../../components/device/Stores'; import { useStore } from '../../components/device/Stores';
import { Toggle } from '../../components/primitive';
import { InputContainer } from '../../components/primitive/InputContainer';
export default function ExperimentalSettings() { export default function ExperimentalSettings() {
// const locations = useBridgeQuery("SysGetLocation") // const locations = useBridgeQuery("SysGetLocation")
const experimental = useStore((state) => state.experimental); const experimental = useStore((state) => state.experimental);
return ( return (
<div className="flex flex-col flex-grow max-w-4xl space-y-4"> <div className="flex flex-col flex-grow max-w-4xl space-y-4">
{/*<Button size="sm">Add Location</Button>*/} {/*<Button size="sm">Add Location</Button>*/}
<div className="mt-3 mb-3"> <div className="mt-3 mb-3">
<h1 className="text-2xl font-bold">Experimental</h1> <h1 className="text-2xl font-bold">Experimental</h1>
<p className="mt-1 text-sm text-gray-400">Experimental features within Spacedrive.</p> <p className="mt-1 text-sm text-gray-400">Experimental features within Spacedrive.</p>
</div> </div>
<InputContainer <InputContainer
mini mini
title="Debug Menu" title="Debug Menu"
description="Shows data about Spacedrive such as Jobs, Job History and Client State." description="Shows data about Spacedrive such as Jobs, Job History and Client State."
> >
<div className="flex items-center h-full pl-10"> <div className="flex items-center h-full pl-10">
<Toggle <Toggle
value={experimental} value={experimental}
size={'sm'} size={'sm'}
onChange={(newValue) => { onChange={(newValue) => {
useStore.setState({ useStore.setState({
experimental: newValue experimental: newValue
}); });
}} }}
/> />
</div> </div>
</InputContainer> </InputContainer>
</div> </div>
); );
} }

View file

@ -1,28 +1,28 @@
import { InputContainer } from '../../components/primitive/InputContainer'; import { useBridgeCommand, useBridgeQuery } from '@sd/client';
import { Input } from '../../components/primitive';
import { Button } from '@sd/ui'; import { Button } from '@sd/ui';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Input } from '../../components/primitive';
import { InputContainer } from '../../components/primitive/InputContainer';
import Listbox from '../../components/primitive/Listbox'; import Listbox from '../../components/primitive/Listbox';
import Slider from '../../components/primitive/Slider'; import Slider from '../../components/primitive/Slider';
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
export default function GeneralSettings() { export default function GeneralSettings() {
const { data: volumes } = useBridgeQuery('SysGetVolumes'); const { data: volumes } = useBridgeQuery('SysGetVolumes');
return ( return (
<div className="flex flex-col flex-grow max-w-4xl space-y-4"> <div className="flex flex-col flex-grow max-w-4xl space-y-4">
<div className="mt-3 mb-3"> <div className="mt-3 mb-3">
<h1 className="text-2xl font-bold">General Settings</h1> <h1 className="text-2xl font-bold">General Settings</h1>
<p className="mt-1 text-sm text-gray-400">Basic settings related to this client</p> <p className="mt-1 text-sm text-gray-400">Basic settings related to this client</p>
{/* <hr className="mt-4 border-gray-550" /> */} {/* <hr className="mt-4 border-gray-550" /> */}
</div> </div>
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600"> <p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be <b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
functional. functional.
</p> </p>
{/* <InputContainer {/* <InputContainer
title="Test scan directory" title="Test scan directory"
description="This will create a job to scan the directory you specify to the database." description="This will create a job to scan the directory you specify to the database."
> >
@ -47,23 +47,23 @@ export default function GeneralSettings() {
</div> </div>
</InputContainer> */} </InputContainer> */}
<InputContainer title="Volumes" description="A list of volumes running on this device."> <InputContainer title="Volumes" description="A list of volumes running on this device.">
<div className="flex flex-row space-x-2"> <div className="flex flex-row space-x-2">
<div className="flex flex-grow"> <div className="flex flex-grow">
<Listbox <Listbox
options={ options={
volumes?.map((volume) => ({ volumes?.map((volume) => ({
key: volume.name, key: volume.name,
option: volume.name, option: volume.name,
description: volume.mount_point description: volume.mount_point
})) ?? [] })) ?? []
} }
/> />
</div> </div>
</div> </div>
</InputContainer> </InputContainer>
{/* <div className="">{JSON.stringify({ config })}</div> */} {/* <div className="">{JSON.stringify({ config })}</div> */}
</div> </div>
); );
} }

View file

@ -1,31 +1,32 @@
import React from 'react'; import React from 'react';
import { InputContainer } from '../../components/primitive/InputContainer';
import { Toggle } from '../../components/primitive'; import { Toggle } from '../../components/primitive';
import { InputContainer } from '../../components/primitive/InputContainer';
type LibrarySecurity = 'public' | 'password' | 'vault'; type LibrarySecurity = 'public' | 'password' | 'vault';
export default function LibrarySettings() { export default function LibrarySettings() {
// const locations = useBridgeQuery("SysGetLocation") // const locations = useBridgeQuery("SysGetLocation")
const [encryptOnCloud, setEncryptOnCloud] = React.useState<boolean>(false); const [encryptOnCloud, setEncryptOnCloud] = React.useState<boolean>(false);
return ( return (
<div className="flex flex-col flex-grow max-w-4xl space-y-4"> <div className="flex flex-col flex-grow max-w-4xl space-y-4">
{/*<Button size="sm">Add Location</Button>*/} {/*<Button size="sm">Add Location</Button>*/}
<div className="mt-3 mb-3"> <div className="mt-3 mb-3">
<h1 className="text-2xl font-bold">Library database</h1> <h1 className="text-2xl font-bold">Library database</h1>
<p className="mt-1 text-sm text-gray-400"> <p className="mt-1 text-sm text-gray-400">
The database contains all library data and file metadata. The database contains all library data and file metadata.
</p> </p>
</div> </div>
<InputContainer <InputContainer
mini mini
title="Encrypt on cloud" title="Encrypt on cloud"
description="Enable if library contains sensitive data and should not be synced to the cloud without full encryption." description="Enable if library contains sensitive data and should not be synced to the cloud without full encryption."
> >
<div className="flex items-center h-full pl-10"> <div className="flex items-center h-full pl-10">
<Toggle value={encryptOnCloud} onChange={setEncryptOnCloud} size={'sm'} /> <Toggle value={encryptOnCloud} onChange={setEncryptOnCloud} size={'sm'} />
</div> </div>
</InputContainer> </InputContainer>
</div> </div>
); );
} }

View file

@ -1,25 +1,26 @@
import React from 'react';
import { Button } from '@sd/ui'; import { Button } from '@sd/ui';
import React from 'react';
import { InputContainer } from '../../components/primitive/InputContainer'; import { InputContainer } from '../../components/primitive/InputContainer';
const exampleLocations = [ const exampleLocations = [
{ option: 'Macintosh HD', key: 'macintosh_hd' }, { option: 'Macintosh HD', key: 'macintosh_hd' },
{ option: 'LaCie External', key: 'lacie_external' }, { option: 'LaCie External', key: 'lacie_external' },
{ option: 'Seagate 8TB', key: 'seagate_8tb' } { option: 'Seagate 8TB', key: 'seagate_8tb' }
]; ];
export default function LocationSettings() { export default function LocationSettings() {
// const locations = useBridgeQuery("SysGetLocation") // const locations = useBridgeQuery("SysGetLocation")
return ( return (
<div className="flex flex-col flex-grow max-w-4xl space-y-4"> <div className="flex flex-col flex-grow max-w-4xl space-y-4">
{/*<Button size="sm">Add Location</Button>*/} {/*<Button size="sm">Add Location</Button>*/}
<InputContainer <InputContainer
title="Something about a vault" title="Something about a vault"
description="Local cache storage for media previews and thumbnails." description="Local cache storage for media previews and thumbnails."
> >
<div className="flex flex-row space-x-2"></div> <div className="flex flex-row space-x-2"></div>
</InputContainer> </InputContainer>
</div> </div>
); );
} }

View file

@ -1,19 +1,20 @@
import React from 'react';
import { InputContainer } from '../../components/primitive/InputContainer';
import { Button } from '@sd/ui'; import { Button } from '@sd/ui';
import React from 'react';
import { InputContainer } from '../../components/primitive/InputContainer';
export default function SecuritySettings() { export default function SecuritySettings() {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<InputContainer <InputContainer
title="Vault" title="Vault"
description="You'll need to set a passphrase to enable the vault." description="You'll need to set a passphrase to enable the vault."
> >
<div className="flex flex-row"> <div className="flex flex-row">
<Button variant="primary">Enable Vault</Button> <Button variant="primary">Enable Vault</Button>
{/*<Input className="flex-grow" value="jeff" placeholder="/users/jamie/Desktop" />*/} {/*<Input className="flex-grow" value="jeff" placeholder="/users/jamie/Desktop" />*/}
</div> </div>
</InputContainer> </InputContainer>
</div> </div>
); );
} }

View file

@ -1,114 +1,114 @@
a, a,
button { button {
@apply cursor-default; @apply cursor-default;
} }
body { body {
font-family: 'InterVariable', sans-serif; font-family: 'InterVariable', sans-serif;
} }
.no-scrollbar { .no-scrollbar {
-ms-overflow-style: none; /* for Internet Explorer, Edge */ -ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */ scrollbar-width: none; /* for Firefox */
overflow-y: scroll; overflow-y: scroll;
} }
.no-scrollbar::-webkit-scrollbar { .no-scrollbar::-webkit-scrollbar {
display: none; display: none;
} }
.custom-scroll { .custom-scroll {
-ms-overflow-style: none; /* for Internet Explorer, Edge */ -ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */ scrollbar-width: none; /* for Firefox */
overflow-y: scroll; overflow-y: scroll;
} }
.explorer-scroll { .explorer-scroll {
&::-webkit-scrollbar { &::-webkit-scrollbar {
height: 6px; height: 6px;
width: 8px; width: 8px;
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
@apply bg-[#00000006] dark:bg-[#00000030] mt-[55px] rounded-[6px]; @apply bg-[#00000006] dark:bg-[#00000030] mt-[55px] rounded-[6px];
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550; @apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
} }
} }
.default-scroll { .default-scroll {
&::-webkit-scrollbar { &::-webkit-scrollbar {
height: 6px; height: 6px;
width: 8px; width: 8px;
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
@apply bg-transparent rounded-[6px]; @apply bg-transparent rounded-[6px];
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550; @apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
} }
} }
.page-scroll { .page-scroll {
&::-webkit-scrollbar { &::-webkit-scrollbar {
height: 6px; height: 6px;
width: 8px; width: 8px;
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
@apply bg-transparent rounded-[6px] my-[10px]; @apply bg-transparent rounded-[6px] my-[10px];
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550; @apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
} }
} }
@keyframes fadeIn { @keyframes fadeIn {
from { from {
opacity: 0; opacity: 0;
} }
to { to {
opacity: 1; opacity: 1;
} }
} }
@keyframes fadeOut { @keyframes fadeOut {
from { from {
opacity: 1; opacity: 1;
} }
to { to {
opacity: 0; opacity: 0;
} }
} }
@-webkit-keyframes slide-top { @-webkit-keyframes slide-top {
0% { 0% {
-webkit-transform: translateY(0); -webkit-transform: translateY(0);
transform: translateY(0); transform: translateY(0);
} }
100% { 100% {
-webkit-transform: translateY(-50px); -webkit-transform: translateY(-50px);
transform: translateY(-50px); transform: translateY(-50px);
} }
} }
@keyframes slide-top { @keyframes slide-top {
0% { 0% {
-webkit-transform: translateY(0); -webkit-transform: translateY(0);
transform: translateY(0); transform: translateY(0);
} }
100% { 100% {
-webkit-transform: translateY(-50px); -webkit-transform: translateY(-50px);
transform: translateY(-50px); transform: translateY(-50px);
} }
} }
.dialog-overlay[data-state='open'] { .dialog-overlay[data-state='open'] {
animation: fadeIn 200ms ease-out forwards; animation: fadeIn 200ms ease-out forwards;
} }
.dialog-overlay[data-state='closed'] { .dialog-overlay[data-state='closed'] {
animation: fadeIn 200ms ease-out forwards; animation: fadeIn 200ms ease-out forwards;
} }
.dialog-content[data-state='open'] { .dialog-content[data-state='open'] {
-webkit-animation: slide-top 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) both; -webkit-animation: slide-top 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) both;
animation: slide-top 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) both; animation: slide-top 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) both;
} }
.dialog-content[data-state='closed'] { .dialog-content[data-state='closed'] {
animation: bounceDown 100ms ease-in forwards; animation: bounceDown 100ms ease-in forwards;
} }

View file

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