mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-08 03:42:49 +00:00
[ENG-363] Spacedrop UI + Misc Improvements (#568)
* begin spacedrop ui + misc ui improvements * better 404 xox * Update extensions.rs I think I prefer Container * added DragRegion component, ot tested cuz im on my fone * Update DragRegion.tsx fix import * added dummy drop items * better dummy data * added clouds & search bar * added action buttons to spacedrop items * customize subtle button * added support for apng, thanks luka big pants * use relative path in sidebar * use BYTES const --------- Co-authored-by: Brendan Allan <brendonovich@outlook.com>
This commit is contained in:
parent
7b739d0b33
commit
3f44d6f521
|
@ -48,6 +48,7 @@ sharma
|
||||||
skippable
|
skippable
|
||||||
spacedrive
|
spacedrive
|
||||||
spacedriveapp
|
spacedriveapp
|
||||||
|
spacetunnel
|
||||||
specta
|
specta
|
||||||
storedkey
|
storedkey
|
||||||
stringly
|
stringly
|
||||||
|
|
|
@ -19,6 +19,11 @@ export default defineConfig({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
|
css: {
|
||||||
|
modules: {
|
||||||
|
localsConvention: 'camelCaseOnly'
|
||||||
|
}
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: [relativeAliasResolver]
|
alias: [relativeAliasResolver]
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,6 +17,11 @@ export default defineConfig({
|
||||||
md({ mode: [Mode.REACT] }),
|
md({ mode: [Mode.REACT] }),
|
||||||
visualizer()
|
visualizer()
|
||||||
],
|
],
|
||||||
|
css: {
|
||||||
|
modules: {
|
||||||
|
localsConvention: 'camelCaseOnly'
|
||||||
|
}
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -113,6 +113,19 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||||
.await?)
|
.await?)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
// .library_mutation("create", |t| {
|
||||||
|
// #[derive(Type, Deserialize)]
|
||||||
|
// pub struct TagCreateArgs {
|
||||||
|
// pub name: String,
|
||||||
|
// pub color: String,
|
||||||
|
// }
|
||||||
|
// t(|_, args: TagCreateArgs, library| async move {
|
||||||
|
// let created_tag = Tag::new(args.name, args.color);
|
||||||
|
// created_tag.save(&library.db).await?;
|
||||||
|
// invalidate_query!(library, "tags.list");
|
||||||
|
// Ok(created_tag)
|
||||||
|
// })
|
||||||
|
// })
|
||||||
.library_mutation("create", |t| {
|
.library_mutation("create", |t| {
|
||||||
#[derive(Type, Deserialize)]
|
#[derive(Type, Deserialize)]
|
||||||
pub struct TagCreateArgs {
|
pub struct TagCreateArgs {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use tokio::fs::File;
|
||||||
|
|
||||||
use crate::job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext};
|
use crate::job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext};
|
||||||
|
|
||||||
use super::{context_menu_fs_info, FsInfo};
|
use super::{context_menu_fs_info, FsInfo, BYTES};
|
||||||
pub struct FileDecryptorJob;
|
pub struct FileDecryptorJob;
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct FileDecryptorJobState {}
|
pub struct FileDecryptorJobState {}
|
||||||
|
@ -74,7 +74,7 @@ impl StatefulJob for FileDecryptorJob {
|
||||||
|| {
|
|| {
|
||||||
let mut path = info.fs_path.clone();
|
let mut path = info.fs_path.clone();
|
||||||
let extension = path.extension().map_or("decrypted", |ext| {
|
let extension = path.extension().map_or("decrypted", |ext| {
|
||||||
if ext == ".sdenc" {
|
if ext == BYTES {
|
||||||
""
|
""
|
||||||
} else {
|
} else {
|
||||||
"decrypted"
|
"decrypted"
|
||||||
|
|
|
@ -98,7 +98,7 @@ impl StatefulJob for FileEncryptorJob {
|
||||||
|| {
|
|| {
|
||||||
let mut path = info.fs_path.clone();
|
let mut path = info.fs_path.clone();
|
||||||
let extension = path.extension().map_or_else(
|
let extension = path.extension().map_or_else(
|
||||||
|| Ok("sdenc".to_string()),
|
|| Ok("bytes".to_string()),
|
||||||
|extension| {
|
|extension| {
|
||||||
Ok::<String, JobError>(
|
Ok::<String, JobError>(
|
||||||
extension
|
extension
|
||||||
|
@ -108,7 +108,7 @@ impl StatefulJob for FileEncryptorJob {
|
||||||
"path contents when converted to string",
|
"path contents when converted to string",
|
||||||
),
|
),
|
||||||
})?
|
})?
|
||||||
.to_string() + ".sdenc",
|
.to_string() + ".bytes",
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
|
@ -22,6 +22,8 @@ pub mod error;
|
||||||
|
|
||||||
pub mod erase;
|
pub mod erase;
|
||||||
|
|
||||||
|
pub const BYTES: &str = "bytes";
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum ObjectType {
|
pub enum ObjectType {
|
||||||
File,
|
File,
|
||||||
|
|
|
@ -2,6 +2,7 @@ pub mod cas;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod identifier_job;
|
pub mod identifier_job;
|
||||||
pub mod preview;
|
pub mod preview;
|
||||||
|
pub mod tag;
|
||||||
pub mod validation;
|
pub mod validation;
|
||||||
|
|
||||||
// Objects are primarily created by the identifier from Paths
|
// Objects are primarily created by the identifier from Paths
|
||||||
|
|
33
core/src/object/tag.rs
Normal file
33
core/src/object/tag.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use prisma_client_rust::QueryError;
|
||||||
|
use rspc::Type;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::prisma::{tag, PrismaClient};
|
||||||
|
|
||||||
|
#[derive(Type, Deserialize)]
|
||||||
|
pub struct Tag {
|
||||||
|
pub name: String,
|
||||||
|
pub color: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tag {
|
||||||
|
pub fn new(name: String, color: String) -> Self {
|
||||||
|
Self { name, color }
|
||||||
|
}
|
||||||
|
pub async fn save(self, db: &PrismaClient) -> Result<(), QueryError> {
|
||||||
|
db.tag()
|
||||||
|
.create(
|
||||||
|
Uuid::new_v4().as_bytes().to_vec(),
|
||||||
|
vec![
|
||||||
|
tag::name::set(Some(self.name)),
|
||||||
|
tag::color::set(Some(self.color)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.exec()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -67,6 +67,7 @@ extension_category_enum! {
|
||||||
Jpg = [0xFF, 0xD8],
|
Jpg = [0xFF, 0xD8],
|
||||||
Jpeg = [0xFF, 0xD8],
|
Jpeg = [0xFF, 0xD8],
|
||||||
Png = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A],
|
Png = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A],
|
||||||
|
Apng = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52],
|
||||||
Gif = [0x47, 0x49, 0x46, 0x38, _, 0x61],
|
Gif = [0x47, 0x49, 0x46, 0x38, _, 0x61],
|
||||||
Bmp = [0x42, 0x4D],
|
Bmp = [0x42, 0x4D],
|
||||||
Tiff = [0x49, 0x49, 0x2A, 0x00],
|
Tiff = [0x49, 0x49, 0x2A, 0x00],
|
||||||
|
@ -183,11 +184,11 @@ extension_category_enum! {
|
||||||
extension_category_enum! {
|
extension_category_enum! {
|
||||||
EncryptedExtension _ALL_ENCRYPTED_EXTENSIONS {
|
EncryptedExtension _ALL_ENCRYPTED_EXTENSIONS {
|
||||||
// Spacedrive encrypted file
|
// Spacedrive encrypted file
|
||||||
SdEnc = [0x62, 0x61, 0x6C, 0x6C, 0x61, 0x70, 0x70],
|
Bytes = [0x62, 0x61, 0x6C, 0x6C, 0x61, 0x70, 0x70],
|
||||||
// Spacedrive container
|
// Spacedrive container
|
||||||
SdContainer = [0x73, 0x64, 0x62, 0x6F, 0x78],
|
Container = [0x73, 0x64, 0x62, 0x6F, 0x78],
|
||||||
// Spacedrive block storage,
|
// Spacedrive block storage,
|
||||||
SdBlock = [0x73, 0x64, 0x62, 0x6C, 0x6F, 0x63, 0x6B],
|
Block = [0x73, 0x64, 0x62, 0x6C, 0x6F, 0x63, 0x6B],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
packages/assets/images/Compressed.png
Normal file
BIN
packages/assets/images/Compressed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
packages/assets/images/Encrypted.png
Normal file
BIN
packages/assets/images/Encrypted.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
BIN
packages/assets/images/GoogleDrive.png
Normal file
BIN
packages/assets/images/GoogleDrive.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
BIN
packages/assets/images/Mega.png
Normal file
BIN
packages/assets/images/Mega.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
BIN
packages/assets/images/iCloud.png
Normal file
BIN
packages/assets/images/iCloud.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 89 KiB |
|
@ -1,5 +1,6 @@
|
||||||
import { captureException } from '@sentry/browser';
|
import { captureException } from '@sentry/browser';
|
||||||
import { FallbackProps } from 'react-error-boundary';
|
import { FallbackProps } from 'react-error-boundary';
|
||||||
|
import { useDebugState } from '@sd/client';
|
||||||
import { Button } from '@sd/ui';
|
import { Button } from '@sd/ui';
|
||||||
|
|
||||||
export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||||
|
@ -8,6 +9,8 @@ export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||||
resetErrorBoundary();
|
resetErrorBoundary();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const debug = useDebugState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
|
@ -17,6 +20,11 @@ export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||||
<p className="text-ink-faint m-3 text-sm font-bold">APP CRASHED</p>
|
<p className="text-ink-faint m-3 text-sm font-bold">APP CRASHED</p>
|
||||||
<h1 className="text-ink text-2xl font-bold">We're past the event horizon...</h1>
|
<h1 className="text-ink text-2xl font-bold">We're past the event horizon...</h1>
|
||||||
<pre className="text-ink m-2">Error: {error.message}</pre>
|
<pre className="text-ink m-2">Error: {error.message}</pre>
|
||||||
|
{debug.enabled && (
|
||||||
|
<pre className="text-ink-dull m-2 text-sm">
|
||||||
|
Check the console (CMD/CRTL + OPTION + i) for stack trace.
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
<div className="text-ink flex flex-row space-x-2">
|
<div className="text-ink flex flex-row space-x-2">
|
||||||
<Button variant="accent" className="mt-2" onClick={resetErrorBoundary}>
|
<Button variant="accent" className="mt-2" onClick={resetErrorBoundary}>
|
||||||
Reload
|
Reload
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
Copy,
|
Copy,
|
||||||
FileX,
|
FileX,
|
||||||
Image,
|
Image,
|
||||||
|
Info,
|
||||||
LockSimple,
|
LockSimple,
|
||||||
LockSimpleOpen,
|
LockSimpleOpen,
|
||||||
Package,
|
Package,
|
||||||
|
@ -12,6 +13,7 @@ import {
|
||||||
Scissors,
|
Scissors,
|
||||||
Share,
|
Share,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
|
Sidebar,
|
||||||
TagSimple,
|
TagSimple,
|
||||||
Trash,
|
Trash,
|
||||||
TrashSimple
|
TrashSimple
|
||||||
|
@ -244,6 +246,19 @@ export function FileItemContextMenu({ data, ...props }: FileItemContextMenuProps
|
||||||
|
|
||||||
<CM.Separator />
|
<CM.Separator />
|
||||||
|
|
||||||
|
{!store.showInspector && (
|
||||||
|
<>
|
||||||
|
<CM.Item
|
||||||
|
label="Details"
|
||||||
|
// icon={Sidebar}
|
||||||
|
onClick={(e) => {
|
||||||
|
getExplorerStore().showInspector = true;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<CM.Separator />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<CM.Item label="Quick view" keybind="␣" />
|
<CM.Item label="Quick view" keybind="␣" />
|
||||||
<OpenInNativeExplorer />
|
<OpenInNativeExplorer />
|
||||||
|
|
||||||
|
@ -259,7 +274,7 @@ export function FileItemContextMenu({ data, ...props }: FileItemContextMenuProps
|
||||||
source_path_id: data.item.id,
|
source_path_id: data.item.id,
|
||||||
target_location_id: store.locationId!,
|
target_location_id: store.locationId!,
|
||||||
target_path: params.path,
|
target_path: params.path,
|
||||||
target_file_name_suffix: ' - Clone'
|
target_file_name_suffix: ' copy'
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { forwardRef, useEffect, useRef } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Button, Input, Popover, cva } from '@sd/ui';
|
import { Button, Input, Popover, cva } from '@sd/ui';
|
||||||
|
import DragRegion from '~/components/layout/DragRegion';
|
||||||
import { getExplorerStore, useExplorerStore } from '../../hooks/useExplorerStore';
|
import { getExplorerStore, useExplorerStore } from '../../hooks/useExplorerStore';
|
||||||
import { useOperatingSystem } from '../../hooks/useOperatingSystem';
|
import { useOperatingSystem } from '../../hooks/useOperatingSystem';
|
||||||
import { KeybindEvent } from '../../util/keybind';
|
import { KeybindEvent } from '../../util/keybind';
|
||||||
|
@ -69,7 +70,7 @@ const TopBarButton = forwardRef<HTMLButtonElement, TopBarButtonProps>(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const SearchBar = forwardRef<HTMLInputElement, DefaultProps>((props, forwardedRef) => {
|
export const SearchBar = forwardRef<HTMLInputElement, DefaultProps>((props, forwardedRef) => {
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
@ -97,11 +98,14 @@ const SearchBar = forwardRef<HTMLInputElement, DefaultProps>((props, forwardedRe
|
||||||
else if (forwardedRef) forwardedRef.current = el;
|
else if (forwardedRef) forwardedRef.current = el;
|
||||||
}}
|
}}
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
className="w-32 transition-all focus:w-52"
|
className={clsx('w-32 transition-all focus:w-52', props.className)}
|
||||||
{...searchField}
|
{...searchField}
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
<div className={clsx('pointer-events-none absolute right-1 space-x-1 peer-focus:invisible')}>
|
className={clsx(
|
||||||
|
'pointer-events-none absolute right-1 flex h-7 items-center space-x-1 opacity-70 peer-focus:invisible'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{platform === 'browser' ? (
|
{platform === 'browser' ? (
|
||||||
<Shortcut chars="⌘F" aria-label={'Press Command-F to focus search bar'} />
|
<Shortcut chars="⌘F" aria-label={'Press Command-F to focus search bar'} />
|
||||||
) : os === 'macOS' ? (
|
) : os === 'macOS' ? (
|
||||||
|
@ -331,11 +335,10 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
|
||||||
onClick={() => (getExplorerStore().showInspector = !store.showInspector)}
|
onClick={() => (getExplorerStore().showInspector = !store.showInspector)}
|
||||||
className="my-2"
|
className="my-2"
|
||||||
>
|
>
|
||||||
{store.showInspector ? (
|
<SidebarSimple
|
||||||
<SidebarSimple className={TOP_BAR_ICON_STYLE} />
|
weight={store.showInspector ? 'fill' : 'regular'}
|
||||||
) : (
|
className={clsx(TOP_BAR_ICON_STYLE, 'scale-x-[-1] transform')}
|
||||||
<SidebarSimple className={TOP_BAR_ICON_STYLE} />
|
/>
|
||||||
)}
|
|
||||||
</TopBarButton>
|
</TopBarButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/* <Dropdown
|
{/* <Dropdown
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import archive from '@sd/assets/images/Archive.png';
|
import Archive from '@sd/assets/images/Archive.png';
|
||||||
import documentPdf from '@sd/assets/images/Document_pdf.png';
|
import Compressed from '@sd/assets/images/Compressed.png';
|
||||||
import executable from '@sd/assets/images/Executable.png';
|
import DocumentPdf from '@sd/assets/images/Document_pdf.png';
|
||||||
import file from '@sd/assets/images/File.png';
|
import Encrypted from '@sd/assets/images/Encrypted.png';
|
||||||
import video from '@sd/assets/images/Video.png';
|
import Executable from '@sd/assets/images/Executable.png';
|
||||||
|
import File from '@sd/assets/images/File.png';
|
||||||
|
import Video from '@sd/assets/images/Video.png';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { ExplorerItem, isObject, isPath } from '@sd/client';
|
import { ExplorerItem, isObject, isPath } from '@sd/client';
|
||||||
import { useExplorerStore } from '~/hooks/useExplorerStore';
|
import { useExplorerStore } from '~/hooks/useExplorerStore';
|
||||||
|
@ -35,13 +37,14 @@ export default function FileThumb({ data, ...props }: Props) {
|
||||||
|
|
||||||
if (isPath(data) && data.item.is_dir) return <Folder size={props.size * 0.7} />;
|
if (isPath(data) && data.item.is_dir) return <Folder size={props.size * 0.7} />;
|
||||||
|
|
||||||
|
if (data.has_thumbnail) {
|
||||||
const cas_id = isObject(data) ? data.item.file_paths[0]?.cas_id : data.item.cas_id;
|
const cas_id = isObject(data) ? data.item.file_paths[0]?.cas_id : data.item.cas_id;
|
||||||
|
|
||||||
if (!cas_id) return <div></div>;
|
if (!cas_id) return <div></div>;
|
||||||
|
|
||||||
const url = platform.getThumbnailUrlById(cas_id);
|
const url = platform.getThumbnailUrlById(cas_id);
|
||||||
|
|
||||||
if (data.has_thumbnail && url)
|
if (url)
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
style={props.style}
|
style={props.style}
|
||||||
|
@ -51,14 +54,16 @@ export default function FileThumb({ data, ...props }: Props) {
|
||||||
src={url}
|
src={url}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let icon = file;
|
let icon = File;
|
||||||
// Hacky (and temporary) way to integrate thumbnails
|
// Hacky (and temporary) way to integrate thumbnails
|
||||||
if (props.kind === 'Archive') icon = archive;
|
if (props.kind === 'Archive') icon = Archive;
|
||||||
else if (props.kind === 'Video') icon = video;
|
else if (props.kind === 'Video') icon = Video;
|
||||||
else if (props.kind === 'Document' && data.item.extension === 'pdf') icon = documentPdf;
|
else if (props.kind === 'Document' && data.item.extension === 'pdf') icon = DocumentPdf;
|
||||||
else if (props.kind === 'Executable') icon = executable;
|
else if (props.kind === 'Executable') icon = Executable;
|
||||||
else if (props.kind === 'Encrypted') icon = archive;
|
else if (props.kind === 'Encrypted') icon = Encrypted;
|
||||||
|
else if (props.kind === 'Compressed') icon = Compressed;
|
||||||
|
|
||||||
return <img src={icon} className={clsx('h-full overflow-hidden', props.iconClassNames)} />;
|
return <img src={icon} className={clsx('h-full overflow-hidden', props.iconClassNames)} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// import types from '../../constants/file-types.json';
|
// import types from '../../constants/file-types.json';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { Barcode, CircleWavyCheck, Clock, Cube, Link, Lock, Snowflake } from 'phosphor-react';
|
import { Barcode, CircleWavyCheck, Clock, Cube, Hash, Link, Lock, Snowflake } from 'phosphor-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
ExplorerContext,
|
ExplorerContext,
|
||||||
|
@ -27,6 +27,8 @@ export const MetaContainer = tw.div`flex flex-col px-4 py-1.5`;
|
||||||
|
|
||||||
export const MetaTitle = tw.h5`text-xs font-bold`;
|
export const MetaTitle = tw.h5`text-xs font-bold`;
|
||||||
|
|
||||||
|
export const MetaKeyName = tw.h5`text-xs flex-shrink-0 flex-wrap-0`;
|
||||||
|
|
||||||
export const MetaValue = tw.p`text-xs break-all text-ink truncate`;
|
export const MetaValue = tw.p`text-xs break-all text-ink truncate`;
|
||||||
|
|
||||||
const MetaTextLine = tw.div`flex items-center my-0.5 text-xs text-ink-dull`;
|
const MetaTextLine = tw.div`flex items-center my-0.5 text-xs text-ink-dull`;
|
||||||
|
@ -66,6 +68,9 @@ export const Inspector = ({ data, context, ...elementProps }: Props) => {
|
||||||
|
|
||||||
const item = data?.item;
|
const item = data?.item;
|
||||||
|
|
||||||
|
// map array of numbers into string
|
||||||
|
const pub_id = fullObjectData?.data?.pub_id.map((n: number) => n.toString(16)).join('');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...elementProps}
|
{...elementProps}
|
||||||
|
@ -154,14 +159,14 @@ export const Inspector = ({ data, context, ...elementProps }: Props) => {
|
||||||
<Tooltip label={dayjs(item?.date_created).format('h:mm:ss a')}>
|
<Tooltip label={dayjs(item?.date_created).format('h:mm:ss a')}>
|
||||||
<MetaTextLine>
|
<MetaTextLine>
|
||||||
<InspectorIcon component={Clock} />
|
<InspectorIcon component={Clock} />
|
||||||
<span className="mr-1.5">Created</span>
|
<MetaKeyName className="mr-1.5">Created</MetaKeyName>
|
||||||
<MetaValue>{dayjs(item?.date_created).format('MMM Do YYYY')}</MetaValue>
|
<MetaValue>{dayjs(item?.date_created).format('MMM Do YYYY')}</MetaValue>
|
||||||
</MetaTextLine>
|
</MetaTextLine>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={dayjs(item?.date_created).format('h:mm:ss a')}>
|
<Tooltip label={dayjs(item?.date_created).format('h:mm:ss a')}>
|
||||||
<MetaTextLine>
|
<MetaTextLine>
|
||||||
<InspectorIcon component={Barcode} />
|
<InspectorIcon component={Barcode} />
|
||||||
<span className="mr-1.5">Indexed</span>
|
<MetaKeyName className="mr-1.5">Indexed</MetaKeyName>
|
||||||
<MetaValue>{dayjs(item?.date_indexed).format('MMM Do YYYY')}</MetaValue>
|
<MetaValue>{dayjs(item?.date_indexed).format('MMM Do YYYY')}</MetaValue>
|
||||||
</MetaTextLine>
|
</MetaTextLine>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -175,7 +180,7 @@ export const Inspector = ({ data, context, ...elementProps }: Props) => {
|
||||||
<Tooltip label={filePathData?.cas_id || ''}>
|
<Tooltip label={filePathData?.cas_id || ''}>
|
||||||
<MetaTextLine>
|
<MetaTextLine>
|
||||||
<InspectorIcon component={Snowflake} />
|
<InspectorIcon component={Snowflake} />
|
||||||
<span className="mr-1.5">Content ID</span>
|
<MetaKeyName className="mr-1.5">Content ID</MetaKeyName>
|
||||||
<MetaValue>{filePathData?.cas_id || ''}</MetaValue>
|
<MetaValue>{filePathData?.cas_id || ''}</MetaValue>
|
||||||
</MetaTextLine>
|
</MetaTextLine>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -183,11 +188,20 @@ export const Inspector = ({ data, context, ...elementProps }: Props) => {
|
||||||
<Tooltip label={filePathData?.integrity_checksum || ''}>
|
<Tooltip label={filePathData?.integrity_checksum || ''}>
|
||||||
<MetaTextLine>
|
<MetaTextLine>
|
||||||
<InspectorIcon component={CircleWavyCheck} />
|
<InspectorIcon component={CircleWavyCheck} />
|
||||||
<span className="mr-1.5">Checksum</span>
|
<MetaKeyName className="mr-1.5">Checksum</MetaKeyName>
|
||||||
<MetaValue>{filePathData?.integrity_checksum}</MetaValue>
|
<MetaValue>{filePathData?.integrity_checksum}</MetaValue>
|
||||||
</MetaTextLine>
|
</MetaTextLine>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{pub_id && (
|
||||||
|
<Tooltip label={pub_id || ''}>
|
||||||
|
<MetaTextLine>
|
||||||
|
<InspectorIcon component={Hash} />
|
||||||
|
<MetaKeyName className="mr-1.5">Object ID</MetaKeyName>
|
||||||
|
<MetaValue>{pub_id}</MetaValue>
|
||||||
|
</MetaTextLine>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</MetaContainer>
|
</MetaContainer>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -115,7 +115,7 @@ export const VirtualizedList = memo(({ data, context, onScroll }: Props) => {
|
||||||
// );
|
// );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: -TOP_BAR_HEIGHT }} className="w-full cursor-default pl-2">
|
<div style={{ marginTop: -TOP_BAR_HEIGHT }} className="w-full cursor-default pl-4">
|
||||||
<div
|
<div
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
className="custom-scroll explorer-scroll h-screen"
|
className="custom-scroll explorer-scroll h-screen"
|
||||||
|
|
10
packages/interface/src/components/layout/DragRegion.tsx
Normal file
10
packages/interface/src/components/layout/DragRegion.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
import { cx } from '@sd/ui';
|
||||||
|
|
||||||
|
export default function DragRegion(props: PropsWithChildren & { className?: string }) {
|
||||||
|
return (
|
||||||
|
<div data-tauri-drag-region className={cx('flex flex-shrink-0 w-full h-5', props.className)}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,17 +1,21 @@
|
||||||
import { ReactComponent as Ellipsis } from '@sd/assets/svgs/ellipsis.svg';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import {
|
import {
|
||||||
|
ArchiveBox,
|
||||||
|
Broadcast,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
CirclesFour,
|
CirclesFour,
|
||||||
|
CopySimple,
|
||||||
|
Crosshair,
|
||||||
|
Eraser,
|
||||||
|
FilmStrip,
|
||||||
Gear,
|
Gear,
|
||||||
Lock,
|
Lock,
|
||||||
MonitorPlay,
|
MonitorPlay,
|
||||||
Planet,
|
Planet,
|
||||||
Plus,
|
Plus
|
||||||
UsersThree
|
|
||||||
} from 'phosphor-react';
|
} from 'phosphor-react';
|
||||||
import React, { PropsWithChildren, useEffect } from 'react';
|
import React, { PropsWithChildren, useEffect } from 'react';
|
||||||
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom';
|
import { Link, NavLink, NavLinkProps, useLocation } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Location,
|
Location,
|
||||||
LocationCreateArgs,
|
LocationCreateArgs,
|
||||||
|
@ -46,6 +50,7 @@ import { Folder } from '../icons/Folder';
|
||||||
import { JobsManager } from '../jobs/JobManager';
|
import { JobsManager } from '../jobs/JobManager';
|
||||||
import { MacTrafficLights } from '../os/TrafficLights';
|
import { MacTrafficLights } from '../os/TrafficLights';
|
||||||
import { InputContainer } from '../primitive/InputContainer';
|
import { InputContainer } from '../primitive/InputContainer';
|
||||||
|
import { SubtleButton } from '../primitive/SubtleButton';
|
||||||
import { Tooltip } from '../tooltip/Tooltip';
|
import { Tooltip } from '../tooltip/Tooltip';
|
||||||
|
|
||||||
const SidebarBody = tw.div`flex relative flex-col flex-grow-0 flex-shrink-0 w-44 min-h-full border-r border-sidebar-divider bg-sidebar`;
|
const SidebarBody = tw.div`flex relative flex-col flex-grow-0 flex-shrink-0 w-44 min-h-full border-r border-sidebar-divider bg-sidebar`;
|
||||||
|
@ -136,17 +141,43 @@ export function Sidebar() {
|
||||||
<Icon component={CirclesFour} />
|
<Icon component={CirclesFour} />
|
||||||
Spaces
|
Spaces
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
<SidebarLink to="people">
|
{/* <SidebarLink to="people">
|
||||||
<Icon component={UsersThree} />
|
<Icon component={UsersThree} />
|
||||||
People
|
People
|
||||||
</SidebarLink>
|
</SidebarLink> */}
|
||||||
<SidebarLink to="media">
|
<SidebarLink to="media">
|
||||||
<Icon component={MonitorPlay} />
|
<Icon component={MonitorPlay} />
|
||||||
Media
|
Media
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
|
<SidebarLink to="spacedrop">
|
||||||
|
<Icon component={Broadcast} />
|
||||||
|
Spacedrop
|
||||||
|
</SidebarLink>
|
||||||
|
<SidebarLink to="imports">
|
||||||
|
<Icon component={ArchiveBox} />
|
||||||
|
Imports
|
||||||
|
</SidebarLink>
|
||||||
</div>
|
</div>
|
||||||
{library && <LibraryScopedSection key={library.uuid} />}
|
{library && <LibraryScopedSection />}
|
||||||
<div className="grow" />
|
<SidebarSection name="Tools" actionArea={<SubtleButton />}>
|
||||||
|
<SidebarLink to="duplicate-finder">
|
||||||
|
<Icon component={CopySimple} />
|
||||||
|
Duplicate Finder
|
||||||
|
</SidebarLink>
|
||||||
|
<SidebarLink to="lost-and-found">
|
||||||
|
<Icon component={Crosshair} />
|
||||||
|
Find a File
|
||||||
|
</SidebarLink>
|
||||||
|
<SidebarLink to="cache-cleaner">
|
||||||
|
<Icon component={Eraser} />
|
||||||
|
Cache Cleaner
|
||||||
|
</SidebarLink>
|
||||||
|
<SidebarLink to="media-encoder">
|
||||||
|
<Icon component={FilmStrip} />
|
||||||
|
Media Encoder
|
||||||
|
</SidebarLink>
|
||||||
|
</SidebarSection>
|
||||||
|
<div className="flex-grow" />
|
||||||
</SidebarContents>
|
</SidebarContents>
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
|
@ -326,17 +357,6 @@ const SidebarSection = (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidebarHeadingOptionsButton: React.FC<{ to: string; icon?: React.FC }> = (props) => {
|
|
||||||
const Icon = props.icon ?? Ellipsis;
|
|
||||||
return (
|
|
||||||
<NavLink to={props.to}>
|
|
||||||
<Button className="!p-[5px]" variant="subtle">
|
|
||||||
<Icon className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</NavLink>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function LibraryScopedSection() {
|
function LibraryScopedSection() {
|
||||||
const platform = usePlatform();
|
const platform = usePlatform();
|
||||||
|
|
||||||
|
@ -352,10 +372,9 @@ function LibraryScopedSection() {
|
||||||
<SidebarSection
|
<SidebarSection
|
||||||
name="Locations"
|
name="Locations"
|
||||||
actionArea={
|
actionArea={
|
||||||
<>
|
<Link to="settings/locations">
|
||||||
{/* <SidebarHeadingOptionsButton to="/settings/locations" icon={CogIcon} /> */}
|
<SubtleButton />
|
||||||
<SidebarHeadingOptionsButton to="settings/locations" />
|
</Link>
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{locations.data?.map((location) => {
|
{locations.data?.map((location) => {
|
||||||
|
@ -399,7 +418,11 @@ function LibraryScopedSection() {
|
||||||
{!!tags.data?.length && (
|
{!!tags.data?.length && (
|
||||||
<SidebarSection
|
<SidebarSection
|
||||||
name="Tags"
|
name="Tags"
|
||||||
actionArea={<SidebarHeadingOptionsButton to="/settings/tags" />}
|
actionArea={
|
||||||
|
<NavLink to="settings/tags">
|
||||||
|
<SubtleButton />
|
||||||
|
</NavLink>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div className="mt-1 mb-2">
|
<div className="mt-1 mb-2">
|
||||||
{tags.data?.slice(0, 6).map((tag, index) => (
|
{tags.data?.slice(0, 6).map((tag, index) => (
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useEffect } from 'react';
|
||||||
import { Navigate, Outlet, RouteObject, useNavigate } from 'react-router';
|
import { Navigate, Outlet, RouteObject, useNavigate } from 'react-router';
|
||||||
import { getOnboardingStore } from '@sd/client';
|
import { getOnboardingStore } from '@sd/client';
|
||||||
import { tw } from '@sd/ui';
|
import { tw } from '@sd/ui';
|
||||||
|
import DragRegion from '~/components/layout/DragRegion';
|
||||||
import { useOperatingSystem } from '../../hooks/useOperatingSystem';
|
import { useOperatingSystem } from '../../hooks/useOperatingSystem';
|
||||||
import OnboardingCreatingLibrary from './OnboardingCreatingLibrary';
|
import OnboardingCreatingLibrary from './OnboardingCreatingLibrary';
|
||||||
import OnboardingMasterPassword from './OnboardingMasterPassword';
|
import OnboardingMasterPassword from './OnboardingMasterPassword';
|
||||||
|
@ -64,7 +65,7 @@ export default function OnboardingRoot() {
|
||||||
'bg-sidebar text-ink flex h-screen flex-col'
|
'bg-sidebar text-ink flex h-screen flex-col'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div data-tauri-drag-region className="z-50 flex h-9 w-full shrink-0" />
|
<DragRegion className="z-50 h-9" />
|
||||||
|
|
||||||
<div className="-mt-5 flex grow flex-col p-10">
|
<div className="-mt-5 flex grow flex-col p-10">
|
||||||
<div className="flex grow flex-col items-center justify-center">
|
<div className="flex grow flex-col items-center justify-center">
|
||||||
|
|
|
@ -19,7 +19,7 @@ export const PopoverPicker = ({ className, ...props }: PopoverPickerProps) => {
|
||||||
return (
|
return (
|
||||||
<div className={clsx('relative mt-3 flex items-center', className)}>
|
<div className={clsx('relative mt-3 flex items-center', className)}>
|
||||||
<div
|
<div
|
||||||
className={clsx('h-5 w-5 rounded-full shadow ', isOpen && 'dark:border-gray-500')}
|
className={clsx('h-4 w-4 rounded-full shadow', isOpen && 'dark:border-gray-500')}
|
||||||
style={{ backgroundColor: field.value }}
|
style={{ backgroundColor: field.value }}
|
||||||
onClick={() => toggle(true)}
|
onClick={() => toggle(true)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -11,8 +11,8 @@ export const Shortcut: React.FC<ShortcutProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<kbd
|
<kbd
|
||||||
className={clsx(
|
className={clsx(
|
||||||
`border border-b-2 px-1`,
|
`px-1 border border-b-2`,
|
||||||
`rounded-md text-xs font-bold`,
|
`rounded-md text-xs font-ink-dull font-bold`,
|
||||||
`border-app-line dark:border-transparent`,
|
`border-app-line dark:border-transparent`,
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|
14
packages/interface/src/components/primitive/SubtleButton.tsx
Normal file
14
packages/interface/src/components/primitive/SubtleButton.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { ReactComponent as Ellipsis } from '@sd/assets/svgs/ellipsis.svg';
|
||||||
|
import { Button, tw } from '@sd/ui';
|
||||||
|
|
||||||
|
export const SubtleButton: React.FC<{ icon?: React.FC }> = (props) => {
|
||||||
|
const Icon = props.icon ?? Ellipsis;
|
||||||
|
return (
|
||||||
|
<Button className="!p-[5px]" variant="subtle">
|
||||||
|
{/* @ts-expect-error */}
|
||||||
|
<Icon weight="bold" className="w-3 h-3" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SubtleButtonContainer = tw.div`opacity-0 text-ink-faint group-hover:opacity-30 hover:!opacity-100`;
|
|
@ -2,6 +2,7 @@ import { ReactComponent as CaretDown } from '@sd/assets/svgs/caret.svg';
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { Button, tw } from '@sd/ui';
|
import { Button, tw } from '@sd/ui';
|
||||||
|
import DragRegion from '~/components/layout/DragRegion';
|
||||||
import { Divider } from '../explorer/inspector/Divider';
|
import { Divider } from '../explorer/inspector/Divider';
|
||||||
|
|
||||||
interface Props extends PropsWithChildren {
|
interface Props extends PropsWithChildren {
|
||||||
|
@ -20,7 +21,7 @@ export const SettingsSubPage = ({ children, title, topRight }: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageOuter>
|
<PageOuter>
|
||||||
<div data-tauri-drag-region className="absolute h-5 w-full" />
|
<DragRegion />
|
||||||
<Page>
|
<Page>
|
||||||
<PageInner>
|
<PageInner>
|
||||||
<HeaderArea>
|
<HeaderArea>
|
||||||
|
|
|
@ -20,7 +20,7 @@ const state = {
|
||||||
listItemSize: 40,
|
listItemSize: 40,
|
||||||
selectedRowIndex: 1,
|
selectedRowIndex: 1,
|
||||||
tagAssignMode: false,
|
tagAssignMode: false,
|
||||||
showInspector: true,
|
showInspector: false,
|
||||||
multiSelectIndexes: [] as number[],
|
multiSelectIndexes: [] as number[],
|
||||||
contextMenuObjectId: null as number | null,
|
contextMenuObjectId: null as number | null,
|
||||||
contextMenuActiveObject: null as object | null,
|
contextMenuActiveObject: null as object | null,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { useBridgeQuery, useLibraryMutation, useLibraryQuery } from '@sd/client';
|
import { useBridgeQuery, useLibraryMutation, useLibraryQuery } from '@sd/client';
|
||||||
import CodeBlock from '~/components/primitive/Codeblock';
|
import CodeBlock from '~/components/primitive/Codeblock';
|
||||||
import { usePlatform } from '~/util/Platform';
|
import { usePlatform } from '~/util/Platform';
|
||||||
|
import { ScreenContainer } from './_Layout';
|
||||||
|
|
||||||
// TODO: Bring this back with a button in the sidebar near settings at the bottom
|
// TODO: Bring this back with a button in the sidebar near settings at the bottom
|
||||||
export default function DebugScreen() {
|
export default function DebugScreen() {
|
||||||
|
@ -16,9 +17,8 @@ export default function DebugScreen() {
|
||||||
// });
|
// });
|
||||||
const { mutate: identifyFiles } = useLibraryMutation('jobs.identifyUniqueFiles');
|
const { mutate: identifyFiles } = useLibraryMutation('jobs.identifyUniqueFiles');
|
||||||
return (
|
return (
|
||||||
<div className="custom-scroll page-scroll app-background flex h-screen w-full flex-col">
|
<ScreenContainer>
|
||||||
<div data-tauri-drag-region className="flex h-5 w-full shrink-0" />
|
<div className="flex flex-col p-5 pt-2 space-y-5 pb-7">
|
||||||
<div className="flex flex-col space-y-5 p-5 pt-2 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
|
||||||
|
@ -43,6 +43,6 @@ export default function DebugScreen() {
|
||||||
<h1 className="text-sm font-bold ">Libraries</h1>
|
<h1 className="text-sm font-bold ">Libraries</h1>
|
||||||
<CodeBlock src={{ ...libraryState }} />
|
<CodeBlock src={{ ...libraryState }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ScreenContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { ScreenHeading } from '@sd/ui';
|
import { ScreenHeading } from '@sd/ui';
|
||||||
|
import { ScreenContainer } from './_Layout';
|
||||||
|
|
||||||
export default function MediaScreen() {
|
export default function MediaScreen() {
|
||||||
return (
|
return (
|
||||||
<div className="custom-scroll page-scroll app-background flex h-screen w-full flex-col p-5">
|
<ScreenContainer>
|
||||||
<ScreenHeading>Media</ScreenHeading>
|
<ScreenHeading>Media</ScreenHeading>
|
||||||
</div>
|
</ScreenContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,18 +4,22 @@ import { Button } from '@sd/ui';
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
|
<div className="bg-app/80 w-full">
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
|
||||||
role="alert"
|
role="alert"
|
||||||
className="flex h-full w-full flex-col items-center justify-center rounded-lg p-4"
|
className="flex h-full w-full flex-col items-center justify-center rounded-lg p-4"
|
||||||
>
|
>
|
||||||
<p className="text-ink-faint m-3 text-sm font-semibold uppercase">Error: 404</p>
|
<p className="text-ink-faint m-3 text-sm font-semibold uppercase">Error: 404</p>
|
||||||
<h1 className="text-4xl font-bold">You chose nothingness.</h1>
|
<h1 className="text-4xl font-bold">There's nothing here.</h1>
|
||||||
|
<p className="text-ink-dull mt-2 text-sm">
|
||||||
|
Its likely that this page has not been built yet, if so we're on it!
|
||||||
|
</p>
|
||||||
<div className="flex flex-row space-x-2">
|
<div className="flex flex-row space-x-2">
|
||||||
<Button variant="accent" className="mt-4" onClick={() => navigate(-1)}>
|
<Button variant="outline" className="mt-4" onClick={() => navigate(-1)}>
|
||||||
Go Back
|
← Go Back
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { Card } from '@sd/ui';
|
||||||
import useCounter from '~/hooks/useCounter';
|
import useCounter from '~/hooks/useCounter';
|
||||||
import { useLibraryId } from '~/util';
|
import { useLibraryId } from '~/util';
|
||||||
import { usePlatform } from '~/util/Platform';
|
import { usePlatform } from '~/util/Platform';
|
||||||
|
import { ScreenContainer } from './_Layout';
|
||||||
|
|
||||||
interface StatItemProps {
|
interface StatItemProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -97,11 +98,8 @@ export default function OverviewScreen() {
|
||||||
overviewMounted = true;
|
overviewMounted = true;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="custom-scroll page-scroll app-background flex h-screen w-full flex-col overflow-x-hidden">
|
<ScreenContainer>
|
||||||
<div data-tauri-drag-region className="flex h-5 w-full shrink-0" />
|
<div className="flex h-screen w-full flex-col">
|
||||||
{/* PAGE */}
|
|
||||||
|
|
||||||
<div className="flex h-screen w-full flex-col px-4">
|
|
||||||
{/* STAT HEADER */}
|
{/* STAT HEADER */}
|
||||||
<div className="flex w-full">
|
<div className="flex w-full">
|
||||||
{/* STAT CONTAINER */}
|
{/* STAT CONTAINER */}
|
||||||
|
@ -138,7 +136,7 @@ export default function OverviewScreen() {
|
||||||
</Card>
|
</Card>
|
||||||
<div className="flex h-4 w-full shrink-0" />
|
<div className="flex h-4 w-full shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ScreenContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { ScreenHeading } from '@sd/ui';
|
import { ScreenHeading } from '@sd/ui';
|
||||||
|
import { ScreenContainer } from './_Layout';
|
||||||
|
|
||||||
export default function PeopleScreen() {
|
export default function PeopleScreen() {
|
||||||
return (
|
return (
|
||||||
<div className="custom-scroll page-scroll app-background flex h-screen w-full flex-col p-5">
|
<ScreenContainer>
|
||||||
<ScreenHeading>People</ScreenHeading>
|
<ScreenHeading>People</ScreenHeading>
|
||||||
</div>
|
</ScreenContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
25
packages/interface/src/screens/Spacedrop.module.scss
Normal file
25
packages/interface/src/screens/Spacedrop.module.scss
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
.honeycomb-outer {
|
||||||
|
font-size: 0; /*disable white space between inline block element */
|
||||||
|
display: flex;
|
||||||
|
--s: 150px; /* size */
|
||||||
|
--m: 4px; /* margin */
|
||||||
|
--f: calc(1.732 * var(--s) + 4 * var(--m) - 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.honeycomb-container .honeycomb-item {
|
||||||
|
width: var(--s);
|
||||||
|
margin: var(--m);
|
||||||
|
height: calc(var(--s) * 1.1547);
|
||||||
|
display: inline-block;
|
||||||
|
clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);
|
||||||
|
// background: rgba(48, 48, 55, 0.272);
|
||||||
|
margin-bottom: calc(var(--m) - var(--s) * 0.2885);
|
||||||
|
}
|
||||||
|
|
||||||
|
.honeycomb-container::before {
|
||||||
|
content: '';
|
||||||
|
width: calc(var(--s) / 2 + var(--m));
|
||||||
|
float: left;
|
||||||
|
height: 120%;
|
||||||
|
shape-outside: repeating-linear-gradient(#0000 0 calc(var(--f) - 3px), #000 0 var(--f));
|
||||||
|
}
|
151
packages/interface/src/screens/Spacedrop.tsx
Normal file
151
packages/interface/src/screens/Spacedrop.tsx
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
import GoogleDrive from '@sd/assets/images/GoogleDrive.png';
|
||||||
|
import Mega from '@sd/assets/images/Mega.png';
|
||||||
|
import iCloud from '@sd/assets/images/iCloud.png';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { DeviceMobile, HardDrives, Heart, Icon, Laptop, PhoneX, Star, User } from 'phosphor-react';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { Button, tw } from '@sd/ui';
|
||||||
|
import { SearchBar } from '../components/explorer/ExplorerTopBar';
|
||||||
|
import { SubtleButton, SubtleButtonContainer } from '../components/primitive/SubtleButton';
|
||||||
|
import { OperatingSystem } from '../util/Platform';
|
||||||
|
import classes from './Spacedrop.module.scss';
|
||||||
|
import { ScreenContainer } from './_Layout';
|
||||||
|
|
||||||
|
// TODO: move this to UI, copied from Inspector
|
||||||
|
const Pill = tw.span`mt-1 inline border border-transparent px-0.5 text-[9px] font-medium shadow shadow-app-shade/5 bg-app-selected rounded text-ink-dull`;
|
||||||
|
|
||||||
|
type DropItemProps = {
|
||||||
|
// TODO: remove optionals when dummy data is removed (except for icon)
|
||||||
|
name?: string;
|
||||||
|
connectionType?: 'lan' | 'bluetooth' | 'usb' | 'p2p' | 'cloud';
|
||||||
|
receivingNodeOsType?: Omit<OperatingSystem, 'unknown'>;
|
||||||
|
} & ({ image: string } | { icon?: Icon } | { brandIcon: string });
|
||||||
|
|
||||||
|
function DropItem(props: DropItemProps) {
|
||||||
|
let icon;
|
||||||
|
if ('image' in props) {
|
||||||
|
icon = <img className="rounded-full" src={props.image} alt={props.name} />;
|
||||||
|
} else if ('brandIcon' in props) {
|
||||||
|
let brandIconSrc;
|
||||||
|
switch (props.brandIcon) {
|
||||||
|
case 'google-drive':
|
||||||
|
brandIconSrc = GoogleDrive;
|
||||||
|
break;
|
||||||
|
case 'icloud':
|
||||||
|
brandIconSrc = iCloud;
|
||||||
|
break;
|
||||||
|
case 'mega':
|
||||||
|
brandIconSrc = Mega;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (brandIconSrc) {
|
||||||
|
icon = (
|
||||||
|
<div className="flex items-center justify-center h-full p-3">
|
||||||
|
<img className="rounded-full " src={brandIconSrc} alt={props.name} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//
|
||||||
|
const Icon = props.icon || User;
|
||||||
|
icon = <Icon className={clsx('w-8 h-8 m-3', !props.name && 'opacity-20')} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(classes.honeycombItem, 'overflow-hidden bg-app-box/20 hover:bg-app-box/50')}
|
||||||
|
>
|
||||||
|
<div className="relative flex flex-col items-center justify-center w-full h-full group ">
|
||||||
|
<SubtleButtonContainer className="absolute left-[12px] top-[55px]">
|
||||||
|
<SubtleButton icon={Star} />
|
||||||
|
</SubtleButtonContainer>
|
||||||
|
<div className="rounded-full w-14 h-14 bg-app-button">{icon}</div>
|
||||||
|
<SubtleButtonContainer className="absolute right-[12px] top-[55px] rotate-90">
|
||||||
|
<SubtleButton />
|
||||||
|
</SubtleButtonContainer>
|
||||||
|
{props.name && <span className="mt-1 text-xs font-medium">{props.name}</span>}
|
||||||
|
<div className="flex flex-row space-x-1">
|
||||||
|
{props.receivingNodeOsType && <Pill>{props.receivingNodeOsType}</Pill>}
|
||||||
|
{props.connectionType && (
|
||||||
|
<Pill
|
||||||
|
className={clsx(
|
||||||
|
'!text-white uppercase',
|
||||||
|
props.connectionType === 'lan' && 'bg-green-500',
|
||||||
|
props.connectionType === 'p2p' && 'bg-blue-500'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.connectionType}
|
||||||
|
</Pill>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SpacedropScreen() {
|
||||||
|
const searchRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScreenContainer
|
||||||
|
dragRegionChildren={
|
||||||
|
<div className="flex flex-row items-center justify-center w-full h-8 pt-3">
|
||||||
|
<SearchBar className="ml-[13px]" ref={searchRef} />
|
||||||
|
{/* <Button variant="outline">Add</Button> */}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
className={classes.honeycombOuter}
|
||||||
|
>
|
||||||
|
<div className={clsx(classes.honeycombContainer, 'mt-8')}>
|
||||||
|
<DropItem
|
||||||
|
name="Jamie's MacBook Pro"
|
||||||
|
receivingNodeOsType="macOs"
|
||||||
|
connectionType="lan"
|
||||||
|
icon={Laptop}
|
||||||
|
/>
|
||||||
|
<DropItem
|
||||||
|
name="Jamie's iPhone"
|
||||||
|
receivingNodeOsType="iOS"
|
||||||
|
connectionType="lan"
|
||||||
|
icon={DeviceMobile}
|
||||||
|
/>
|
||||||
|
<DropItem
|
||||||
|
name="Titan NAS"
|
||||||
|
receivingNodeOsType="linux"
|
||||||
|
connectionType="p2p"
|
||||||
|
icon={HardDrives}
|
||||||
|
/>
|
||||||
|
<DropItem
|
||||||
|
name="Jamie's iPad"
|
||||||
|
receivingNodeOsType="iOS"
|
||||||
|
connectionType="lan"
|
||||||
|
icon={DeviceMobile}
|
||||||
|
/>
|
||||||
|
<DropItem name="Jamie's Google Drive" brandIcon="google-drive" connectionType="cloud" />
|
||||||
|
<DropItem name="Jamie's iCloud" brandIcon="icloud" connectionType="cloud" />
|
||||||
|
<DropItem name="Mega" brandIcon="mega" connectionType="cloud" />
|
||||||
|
<DropItem
|
||||||
|
name="maxichrome"
|
||||||
|
image="https://github.com/maxichrome.png"
|
||||||
|
connectionType="p2p"
|
||||||
|
/>
|
||||||
|
<DropItem
|
||||||
|
name="Brendan Alan"
|
||||||
|
image="https://github.com/brendonovich.png"
|
||||||
|
connectionType="p2p"
|
||||||
|
/>
|
||||||
|
<DropItem
|
||||||
|
name="Oscar Beaumont"
|
||||||
|
image="https://github.com/oscartbeaumont.png"
|
||||||
|
connectionType="p2p"
|
||||||
|
/>
|
||||||
|
<DropItem name="Polar" image="https://github.com/polargh.png" connectionType="p2p" />
|
||||||
|
<DropItem
|
||||||
|
name="Andrew Haskell"
|
||||||
|
image="https://github.com/andrewtechx.png"
|
||||||
|
connectionType="p2p"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ScreenContainer>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
import { ScreenHeading } from '@sd/ui';
|
import { ScreenHeading } from '@sd/ui';
|
||||||
|
import { ScreenContainer } from './_Layout';
|
||||||
|
|
||||||
export default function SpacesScreen() {
|
export default function SpacesScreen() {
|
||||||
return (
|
return (
|
||||||
<div className="custom-scroll page-scroll app-background flex h-screen w-full flex-col p-5">
|
<ScreenContainer>
|
||||||
<ScreenHeading>Spaces</ScreenHeading>
|
<ScreenHeading>Spaces</ScreenHeading>
|
||||||
</div>
|
</ScreenContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
19
packages/interface/src/screens/_Layout.tsx
Normal file
19
packages/interface/src/screens/_Layout.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { PropsWithChildren, ReactNode, createContext } from 'react';
|
||||||
|
import DragRegion from '~/components/layout/DragRegion';
|
||||||
|
|
||||||
|
export function ScreenContainer(
|
||||||
|
props: PropsWithChildren & { className?: string; dragRegionChildren?: ReactNode }
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'custom-scroll page-scroll app-background flex h-screen w-full flex-col',
|
||||||
|
props.className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<DragRegion>{props.dragRegionChildren}</DragRegion>
|
||||||
|
<div className="flex h-screen w-full flex-col p-5 pt-0">{props.children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -11,11 +11,12 @@ const screens: RouteObject[] = [
|
||||||
{ path: 'media', element: lazyEl(() => import('./Media')) },
|
{ path: 'media', element: lazyEl(() => import('./Media')) },
|
||||||
{ path: 'spaces', element: lazyEl(() => import('./Spaces')) },
|
{ path: 'spaces', element: lazyEl(() => import('./Spaces')) },
|
||||||
{ path: 'debug', element: lazyEl(() => import('./Debug')) },
|
{ path: 'debug', element: lazyEl(() => import('./Debug')) },
|
||||||
|
{ path: 'spacedrop', element: lazyEl(() => import('./Spacedrop')) },
|
||||||
{ path: 'location/:id', element: lazyEl(() => import('./LocationExplorer')) },
|
{ path: 'location/:id', element: lazyEl(() => import('./LocationExplorer')) },
|
||||||
{ path: 'tag/:id', element: lazyEl(() => import('./TagExplorer')) },
|
{ path: 'tag/:id', element: lazyEl(() => import('./TagExplorer')) },
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
element: lazyEl(() => import('./settings/Layout')),
|
element: lazyEl(() => import('./settings/_Layout')),
|
||||||
children: settingsScreens
|
children: settingsScreens
|
||||||
},
|
},
|
||||||
{ path: '*', element: lazyEl(() => import('./NotFound')) }
|
{ path: '*', element: lazyEl(() => import('./NotFound')) }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { Outlet } from 'react-router';
|
import { Outlet } from 'react-router';
|
||||||
|
|
||||||
export default function SettingsScreen() {
|
export default function SettingsSubPageScreen() {
|
||||||
return (
|
return (
|
||||||
<div className="app-background flex w-full flex-row">
|
<div className="app-background flex w-full flex-row">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Suspense } from 'react';
|
||||||
import { Outlet } from 'react-router';
|
import { Outlet } from 'react-router';
|
||||||
import { SettingsSidebar } from '~/components/settings/SettingsSidebar';
|
import { SettingsSidebar } from '~/components/settings/SettingsSidebar';
|
||||||
|
|
||||||
export default function SettingsScreen() {
|
export default function SettingsScreenContainer() {
|
||||||
return (
|
return (
|
||||||
<div className="app-background flex w-full flex-row">
|
<div className="app-background flex w-full flex-row">
|
||||||
<SettingsSidebar />
|
<SettingsSidebar />
|
|
@ -1,5 +1,5 @@
|
||||||
import { MagnifyingGlass } from 'phosphor-react';
|
import { MagnifyingGlass } from 'phosphor-react';
|
||||||
import { Button, Card, GridLayout, Input } from '@sd/ui';
|
import { Button, Card, GridLayout, Input, SearchInput } from '@sd/ui';
|
||||||
import { SettingsContainer } from '~/components/settings/SettingsContainer';
|
import { SettingsContainer } from '~/components/settings/SettingsContainer';
|
||||||
import { SettingsHeader } from '~/components/settings/SettingsHeader';
|
import { SettingsHeader } from '~/components/settings/SettingsHeader';
|
||||||
|
|
||||||
|
@ -63,12 +63,7 @@ export default function ExtensionSettings() {
|
||||||
<SettingsHeader
|
<SettingsHeader
|
||||||
title="Extensions"
|
title="Extensions"
|
||||||
description="Install extensions to extend the functionality of this client."
|
description="Install extensions to extend the functionality of this client."
|
||||||
rightArea={
|
rightArea={<SearchInput outerClassnames="mt-1.5" placeholder="Search extensions" />}
|
||||||
<div className="relative mt-6">
|
|
||||||
<MagnifyingGlass className="text-gray-350 absolute top-[8px] left-[11px] h-auto w-[18px]" />
|
|
||||||
<Input className="w-56 !p-0.5 !pl-9" placeholder="Search extensions" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<GridLayout>
|
<GridLayout>
|
||||||
|
|
|
@ -1,26 +1,30 @@
|
||||||
import Logo from '@sd/assets/images/logo.png';
|
import Logo from '@sd/assets/images/logo.png';
|
||||||
import { useBridgeQuery } from '@sd/client';
|
import { useBridgeQuery } from '@sd/client';
|
||||||
import { SettingsContainer } from '../../../components/settings/SettingsContainer';
|
import { SettingsContainer } from '~/components/settings/SettingsContainer';
|
||||||
import { SettingsHeader } from '../../../components/settings/SettingsHeader';
|
import { useOperatingSystem } from '~/hooks/useOperatingSystem';
|
||||||
|
|
||||||
export default function AboutSpacedrive() {
|
export default function AboutSpacedrive() {
|
||||||
const buildInfo = useBridgeQuery(['buildInfo']);
|
const buildInfo = useBridgeQuery(['buildInfo']);
|
||||||
|
|
||||||
|
const os = useOperatingSystem();
|
||||||
|
|
||||||
|
const currentPlatformNiceName =
|
||||||
|
os === 'browser' ? 'Web' : os == 'macOS' ? os : os.charAt(0).toUpperCase() + os.slice(1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
<SettingsHeader
|
<div className="flex flex-row items-center">
|
||||||
title="Spacedrive"
|
<img src={Logo} className="w-[88px] h-[88px] mr-8" />
|
||||||
description={
|
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span>The file manager from the future.</span>
|
<h1 className="text-2xl font-bold">
|
||||||
<span className="text-ink-faint/80 mt-2 text-xs">
|
Spacedrive {os !== 'unknown' && <>for {currentPlatformNiceName}</>}
|
||||||
|
</h1>
|
||||||
|
<span className="mt-1 text-sm text-ink-dull">The file manager from the future.</span>
|
||||||
|
<span className="mt-1 text-xs text-ink-faint/80">
|
||||||
v{buildInfo.data?.version || '-.-.-'} - {buildInfo.data?.commit || 'dev'}
|
v{buildInfo.data?.version || '-.-.-'} - {buildInfo.data?.commit || 'dev'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
>
|
|
||||||
<img src={Logo} className="mr-8 w-[88px]" />
|
|
||||||
</SettingsHeader>
|
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,15 @@ export default function LibraryGeneralSettings() {
|
||||||
<div className="flex flex-row space-x-5 pb-3">
|
<div className="flex flex-row space-x-5 pb-3">
|
||||||
<div className="flex grow flex-col">
|
<div className="flex grow flex-col">
|
||||||
<span className="mb-1 text-sm font-medium">Name</span>
|
<span className="mb-1 text-sm font-medium">Name</span>
|
||||||
<Input {...form.register('name', { required: true })} defaultValue="My Default Library" />
|
<Input
|
||||||
|
size="md"
|
||||||
|
{...form.register('name', { required: true })}
|
||||||
|
defaultValue="My Default Library"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex grow flex-col">
|
<div className="flex grow flex-col">
|
||||||
<span className="mb-1 text-sm font-medium">Description</span>
|
<span className="mb-1 text-sm font-medium">Description</span>
|
||||||
<Input {...form.register('description')} placeholder="" />
|
<Input size="md" {...form.register('description')} placeholder="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { MagnifyingGlass } from 'phosphor-react';
|
import { MagnifyingGlass } from 'phosphor-react';
|
||||||
import { useLibraryMutation, useLibraryQuery } from '@sd/client';
|
import { useLibraryMutation, useLibraryQuery } from '@sd/client';
|
||||||
import { LocationCreateArgs } from '@sd/client';
|
import { LocationCreateArgs } from '@sd/client';
|
||||||
import { Button, Input, dialogManager } from '@sd/ui';
|
import { Button, Input, SearchInput, dialogManager } from '@sd/ui';
|
||||||
import AddLocationDialog from '~/components/dialog/AddLocationDialog';
|
import AddLocationDialog from '~/components/dialog/AddLocationDialog';
|
||||||
import LocationListItem from '~/components/location/LocationListItem';
|
import LocationListItem from '~/components/location/LocationListItem';
|
||||||
import { SettingsContainer } from '~/components/settings/SettingsContainer';
|
import { SettingsContainer } from '~/components/settings/SettingsContainer';
|
||||||
|
@ -20,14 +20,11 @@ export default function LocationSettings() {
|
||||||
description="Manage your storage locations."
|
description="Manage your storage locations."
|
||||||
rightArea={
|
rightArea={
|
||||||
<div className="flex flex-row items-center space-x-5">
|
<div className="flex flex-row items-center space-x-5">
|
||||||
<div className="relative hidden lg:block">
|
<SearchInput placeholder="Search locations" />
|
||||||
<MagnifyingGlass className="text-gray-350 absolute top-[8px] left-[11px] h-auto w-[18px]" />
|
|
||||||
<Input className="!p-0.5 !pl-9" placeholder="Search locations" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="accent"
|
variant="accent"
|
||||||
size="sm"
|
size="md"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (platform.platform === 'web') {
|
if (platform.platform === 'web') {
|
||||||
dialogManager.create((dp) => <AddLocationDialog {...dp} />);
|
dialogManager.create((dp) => <AddLocationDialog {...dp} />);
|
||||||
|
|
|
@ -89,7 +89,7 @@ export default function TagsSettings() {
|
||||||
</span>
|
</span>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<PopoverPicker
|
<PopoverPicker
|
||||||
className="!absolute left-[9px] top-[-3px]"
|
className="!absolute left-[9px] -top-[5px]"
|
||||||
{...updateForm.register('color')}
|
{...updateForm.register('color')}
|
||||||
/>
|
/>
|
||||||
<Input className="w-28 pl-[40px]" {...updateForm.register('color')} />
|
<Input className="w-28 pl-[40px]" {...updateForm.register('color')} />
|
||||||
|
@ -163,7 +163,7 @@ function CreateTagDialog(props: UseDialogProps) {
|
||||||
ctaLabel="Create"
|
ctaLabel="Create"
|
||||||
>
|
>
|
||||||
<div className="relative mt-3 ">
|
<div className="relative mt-3 ">
|
||||||
<PopoverPicker className="!absolute left-[9px] top-[-3px]" {...form.register('color')} />
|
<PopoverPicker className="!absolute left-[9px] -top-[5px]" {...form.register('color')} />
|
||||||
<Input
|
<Input
|
||||||
{...form.register('name', { required: true })}
|
{...form.register('name', { required: true })}
|
||||||
className="w-full pl-[40px]"
|
className="w-full pl-[40px]"
|
||||||
|
|
|
@ -34,7 +34,8 @@ const styles = cva(
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
icon: '!p-1',
|
icon: '!p-1',
|
||||||
md: 'text-md py-1 px-3 font-medium',
|
lg: 'py-1.5 px-3 text-md font-medium',
|
||||||
|
md: 'py-1.5 px-2.5 text-sm font-medium',
|
||||||
sm: 'py-1 px-2 text-sm font-medium'
|
sm: 'py-1 px-2 text-sm font-medium'
|
||||||
},
|
},
|
||||||
variant: {
|
variant: {
|
||||||
|
@ -60,7 +61,7 @@ const styles = cva(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
size: 'md',
|
size: 'sm',
|
||||||
variant: 'default'
|
variant: 'default'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { VariantProps, cva } from 'class-variance-authority';
|
import { VariantProps, cva } from 'class-variance-authority';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { Eye, EyeSlash } from 'phosphor-react';
|
import { Eye, EyeSlash, MagnifyingGlass } from 'phosphor-react';
|
||||||
import { PropsWithChildren, forwardRef, useState } from 'react';
|
import { PropsWithChildren, forwardRef, useState } from 'react';
|
||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
|
|
||||||
|
@ -12,8 +12,8 @@ export type TextareaProps = InputBaseProps & React.ComponentProps<'textarea'>;
|
||||||
|
|
||||||
const styles = cva(
|
const styles = cva(
|
||||||
[
|
[
|
||||||
'rounded-md border px-3 py-1 text-sm leading-7',
|
'px-3 text-sm rounded-md border leading-7',
|
||||||
'shadow-sm outline-none transition-all focus:ring-2'
|
'outline-none shadow-sm focus:ring-2 transition-all'
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
|
@ -24,8 +24,8 @@ const styles = cva(
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
sm: 'text-sm',
|
sm: 'text-sm py-0.5',
|
||||||
md: 'text-base'
|
md: 'text-sm py-1'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
@ -40,6 +40,19 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const SearchInput = forwardRef<HTMLInputElement, InputProps & { outerClassnames?: string }>(
|
||||||
|
({ variant, size, className, outerClassnames, ...props }, ref) => (
|
||||||
|
<div className={clsx('relative', outerClassnames)}>
|
||||||
|
<MagnifyingGlass className="text-gray-350 absolute top-[8px] left-[11px] h-auto w-[18px]" />
|
||||||
|
<Input
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
className={clsx(styles({ variant, size, className }), '!p-0.5 !pl-9')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
export const TextArea = ({ size, variant, ...props }: TextareaProps) => {
|
export const TextArea = ({ size, variant, ...props }: TextareaProps) => {
|
||||||
return <textarea {...props} className={clsx(styles({ size, variant }), props.className)} />;
|
return <textarea {...props} className={clsx(styles({ size, variant }), props.className)} />;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,4 +2,4 @@ import { tw } from './utils';
|
||||||
|
|
||||||
export const CategoryHeading = tw.h3`text-xs font-semibold text-ink-dull`;
|
export const CategoryHeading = tw.h3`text-xs font-semibold text-ink-dull`;
|
||||||
|
|
||||||
export const ScreenHeading = tw.h3`text-2xl font-bold`;
|
export const ScreenHeading = tw.h3`ml-1 text-xl font-medium`;
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
|
|
||||||
.mask-fade-out {
|
.mask-fade-out {
|
||||||
// -webkit-mask-image: linear-gradient(to top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 1));
|
// -webkit-mask-image: linear-gradient(to top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 1));
|
||||||
mask-image: linear-gradient(to top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100px);
|
mask-image: linear-gradient(to top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 50px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cool-shadow {
|
.cool-shadow {
|
||||||
|
|
|
@ -8394,7 +8394,7 @@ packages:
|
||||||
'@babel/plugin-transform-react-jsx-source': 7.19.6_@babel+core@7.20.12
|
'@babel/plugin-transform-react-jsx-source': 7.19.6_@babel+core@7.20.12
|
||||||
magic-string: 0.26.7
|
magic-string: 0.26.7
|
||||||
react-refresh: 0.14.0
|
react-refresh: 0.14.0
|
||||||
vite: 4.0.4_sass@1.57.1
|
vite: 4.0.4_@types+node@18.11.18
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
@ -21023,7 +21023,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rollup/pluginutils': 5.0.2
|
'@rollup/pluginutils': 5.0.2
|
||||||
'@svgr/core': 6.5.1
|
'@svgr/core': 6.5.1
|
||||||
vite: 4.0.4_sass@1.57.1
|
vite: 4.0.4_@types+node@18.11.18
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- rollup
|
- rollup
|
||||||
- supports-color
|
- supports-color
|
||||||
|
@ -21119,7 +21119,6 @@ packages:
|
||||||
rollup: 3.10.0
|
rollup: 3.10.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/vite/4.0.4_ovmyjmuuyckt3r3gpaexj2onji:
|
/vite/4.0.4_ovmyjmuuyckt3r3gpaexj2onji:
|
||||||
resolution: {integrity: sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==}
|
resolution: {integrity: sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==}
|
||||||
|
@ -21187,6 +21186,7 @@ packages:
|
||||||
sass: 1.57.1
|
sass: 1.57.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
/vlq/1.0.1:
|
/vlq/1.0.1:
|
||||||
resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==}
|
resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==}
|
||||||
|
|
Loading…
Reference in a new issue