mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 12:13:27 +00:00
Merge branch 'main' of https://github.com/spacedriveapp/spacedrive
This commit is contained in:
parent
acbd064f43
commit
4acb8cc594
|
@ -142,6 +142,7 @@ export default function NavBar() {
|
|||
buttonIcon={<DotsThreeVertical weight="bold" className="w-6 h-6 " />}
|
||||
buttonProps={{ className: '!p-1 ml-[140px] hover:!bg-transparent' }}
|
||||
/>
|
||||
|
||||
<div className="absolute flex-row hidden space-x-5 right-3 lg:flex">
|
||||
<a href="https://discord.gg/gTaF2Z44f5" target="_blank" rel="noreferrer">
|
||||
<Discord className="text-white" />
|
||||
|
|
|
@ -126,7 +126,7 @@ pub(crate) fn mount() -> rspc::RouterBuilder<
|
|||
.join(&object.cas_id)
|
||||
.with_extension("webp");
|
||||
|
||||
object.has_thumbnail = thumb_path.exists();
|
||||
object.has_thumbnail = thumb_path.try_exists().unwrap();
|
||||
}
|
||||
ExplorerItem::Path(Box::new(file_path))
|
||||
})
|
||||
|
|
|
@ -43,11 +43,6 @@ mod tags;
|
|||
pub mod utils;
|
||||
pub mod volumes;
|
||||
|
||||
pub use files::*;
|
||||
pub use jobs::*;
|
||||
pub use libraries::*;
|
||||
pub use tags::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Type)]
|
||||
struct NodeState {
|
||||
#[serde(flatten)]
|
||||
|
|
|
@ -61,7 +61,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
.join(&object.cas_id)
|
||||
.with_extension("webp");
|
||||
|
||||
object.has_thumbnail = thumb_path.exists();
|
||||
object.has_thumbnail = thumb_path.try_exists().unwrap();
|
||||
|
||||
ExplorerItem::Object(Box::new(object))
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use futures::{Future, Stream};
|
||||
use futures::Stream;
|
||||
use rspc::{
|
||||
internal::{
|
||||
specta, BuiltProcedureBuilder, MiddlewareBuilderLike, RequestResult,
|
||||
|
|
|
@ -93,7 +93,7 @@ impl LibraryManager {
|
|||
};
|
||||
|
||||
let db_path = config_path.clone().with_extension("db");
|
||||
if !db_path.exists() {
|
||||
if !db_path.try_exists().unwrap() {
|
||||
println!(
|
||||
"Found library '{}' but no matching database file was found. Skipping...",
|
||||
config_path.display()
|
||||
|
|
|
@ -44,7 +44,7 @@ impl LocationCreateArgs {
|
|||
ctx: &LibraryContext,
|
||||
) -> Result<indexer_job_location::Data, LocationError> {
|
||||
// check if we have access to this location
|
||||
if !self.path.exists() {
|
||||
if !self.path.try_exists().unwrap() {
|
||||
return Err(LocationError::PathNotFound(self.path));
|
||||
}
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ impl NodeConfigManager {
|
|||
async fn read(base_path: &PathBuf) -> Result<NodeConfig, NodeConfigError> {
|
||||
let path = Path::new(base_path).join(NODE_STATE_CONFIG_NAME);
|
||||
|
||||
match path.exists() {
|
||||
match path.try_exists().unwrap() {
|
||||
true => {
|
||||
let mut file = File::open(&path)?;
|
||||
let base_config: ConfigMetadata =
|
||||
|
|
|
@ -51,20 +51,20 @@ pub async fn generate_cas_id(path: PathBuf, size: u64) -> Result<String, io::Err
|
|||
Ok(hex)
|
||||
}
|
||||
|
||||
pub async fn full_checksum(path: &str) -> Result<String, io::Error> {
|
||||
const BLOCK_SIZE: usize = 1048576;
|
||||
//read file as buffer and convert to digest
|
||||
let mut reader = File::open(path).await?;
|
||||
let mut context = Hasher::new();
|
||||
let mut buffer = [0; 1048576];
|
||||
loop {
|
||||
let read_count = reader.read(&mut buffer).await?;
|
||||
context.update(&buffer[..read_count]);
|
||||
if read_count != BLOCK_SIZE {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let hex = to_hex_string(context.finalize().as_bytes());
|
||||
// pub async fn full_checksum(path: &str) -> Result<String, io::Error> {
|
||||
// const BLOCK_SIZE: usize = 1048576;
|
||||
// //read file as buffer and convert to digest
|
||||
// let mut reader = File::open(path).await?;
|
||||
// let mut context = Hasher::new();
|
||||
// let mut buffer = [0; 1048576];
|
||||
// loop {
|
||||
// let read_count = reader.read(&mut buffer).await?;
|
||||
// context.update(&buffer[..read_count]);
|
||||
// if read_count != BLOCK_SIZE {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// let hex = to_hex_string(context.finalize().as_bytes());
|
||||
|
||||
Ok(hex)
|
||||
}
|
||||
// Ok(hex)
|
||||
// }
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
library::LibraryContext,
|
||||
prisma::{file_path, location},
|
||||
};
|
||||
use sd_file_ext::extensions::{Extension, ImageExtension, VideoExtension, ALL_VIDEO_EXTENSIONS};
|
||||
use sd_file_ext::extensions::{Extension, ImageExtension, VideoExtension};
|
||||
|
||||
use image::{self, imageops, DynamicImage, GenericImageView};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -120,8 +120,8 @@ impl StatefulJob for ThumbnailJob {
|
|||
&library_ctx,
|
||||
state.init.location_id,
|
||||
&state.init.path,
|
||||
ALL_VIDEO_EXTENSIONS
|
||||
.into_iter()
|
||||
sd_file_ext::extensions::ALL_VIDEO_EXTENSIONS
|
||||
.iter()
|
||||
.map(Clone::clone)
|
||||
.filter(can_generate_thumbnail_for_video)
|
||||
.map(Extension::Video)
|
||||
|
@ -189,7 +189,7 @@ impl StatefulJob for ThumbnailJob {
|
|||
let output_path = data.thumbnail_dir.join(&cas_id).with_extension("webp");
|
||||
|
||||
// check if file exists at output path
|
||||
if !output_path.exists() {
|
||||
if !output_path.try_exists().unwrap() {
|
||||
info!("Writing {:?} to {:?}", path, output_path);
|
||||
|
||||
match step.kind {
|
||||
|
@ -317,10 +317,8 @@ async fn get_files_by_extensions(
|
|||
.collect())
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn can_generate_thumbnail_for_video(video_extension: &VideoExtension) -> bool {
|
||||
use VideoExtension::*;
|
||||
match video_extension {
|
||||
Mpg | Swf | M2v => false,
|
||||
_ => true,
|
||||
}
|
||||
!matches!(video_extension, Mpg | Swf | M2v)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
#![allow(dead_code)]
|
||||
use crate::extensions::{
|
||||
ArchiveExtension, AudioExtension, CodeExtension, DatabaseExtension, ExecutableExtension,
|
||||
Extension, FontExtension, ImageExtension, MeshExtension, VideoExtension,
|
||||
};
|
||||
use crate::extensions::{CodeExtension, Extension, VideoExtension};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ExtensionPossibility {
|
||||
Known(Extension),
|
||||
Conflicts(Vec<Extension>),
|
||||
|
@ -30,7 +27,7 @@ macro_rules! magic_byte_value {
|
|||
$val as u8
|
||||
}};
|
||||
}
|
||||
pub(crate) use magic_byte_value;
|
||||
// pub(crate) use magic_byte_value;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! magic_byte_offset {
|
||||
|
@ -41,7 +38,7 @@ macro_rules! magic_byte_offset {
|
|||
$val
|
||||
};
|
||||
}
|
||||
pub(crate) use magic_byte_offset;
|
||||
// pub(crate) use magic_byte_offset;
|
||||
|
||||
macro_rules! extension_enum {
|
||||
(
|
||||
|
@ -50,11 +47,12 @@ macro_rules! extension_enum {
|
|||
}
|
||||
) => {
|
||||
// construct enum
|
||||
#[derive(Debug, ::serde::Serialize, ::serde::Deserialize, PartialEq)]
|
||||
#[derive(Debug, ::serde::Serialize, ::serde::Deserialize, PartialEq, Eq)]
|
||||
pub enum Extension {
|
||||
$( $variant($type), )*
|
||||
}
|
||||
impl Extension {
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn from_str(s: &str) -> Option<ExtensionPossibility> {
|
||||
use std::str::FromStr;
|
||||
let mut exts = [$(
|
||||
|
@ -100,7 +98,7 @@ macro_rules! extension_category_enum {
|
|||
$($(#[$variant_attr:meta])* $variant:ident $(= $( [$($magic_bytes:tt),*] $(+ $offset:literal)? )|+ )? ,)*
|
||||
}
|
||||
) => {
|
||||
#[derive(Debug, ::serde::Serialize, ::serde::Deserialize, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, ::serde::Serialize, ::serde::Deserialize, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
$(#[$enum_attr])*
|
||||
|
||||
|
@ -195,10 +193,10 @@ impl Extension {
|
|||
Self::Audio(x) => verify_magic_bytes(x, file).map(Self::Audio),
|
||||
Self::Video(x) => verify_magic_bytes(x, file).map(Self::Video),
|
||||
Self::Executable(x) => verify_magic_bytes(x, file).map(Self::Executable),
|
||||
_ => return None,
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
Some(Extension::from(e))
|
||||
Some(e)
|
||||
}
|
||||
}
|
||||
ExtensionPossibility::Conflicts(ext) => match ext_str {
|
||||
|
|
|
@ -7,7 +7,7 @@ fn main() {
|
|||
println!("Issuing sdtunnel certificate...");
|
||||
|
||||
let env_file = Path::new("./.env");
|
||||
if env_file.exists() {
|
||||
if env_file.try_exists().unwrap() {
|
||||
println!("File '{}' already exists. Exiting...", env_file.display());
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
"@rspc/client": "^0.1.2",
|
||||
"@rspc/react": "^0.1.2",
|
||||
"@sd/config": "workspace:*",
|
||||
"@sd/interface": "workspace:*",
|
||||
"@tanstack/react-query": "^4.10.1",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"immer": "^9.0.15",
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"@fontsource/inter": "^4.5.13",
|
||||
"@headlessui/react": "^1.7.3",
|
||||
"@heroicons/react": "^2.0.12",
|
||||
"@loadable/component": "^5.15.2",
|
||||
"@radix-ui/react-dialog": "^1.0.0",
|
||||
"@radix-ui/react-dropdown-menu": "^1.0.0",
|
||||
"@radix-ui/react-icons": "^1.1.1",
|
||||
|
@ -77,6 +78,7 @@
|
|||
"@sd/config": "workspace:*",
|
||||
"@types/babel-core": "^6.25.7",
|
||||
"@types/byte-size": "^8.1.0",
|
||||
"@types/loadable__component": "^5.13.4",
|
||||
"@types/lodash": "^4.14.186",
|
||||
"@types/node": "^18.8.2",
|
||||
"@types/pretty-bytes": "^5.2.0",
|
||||
|
|
|
@ -1,42 +1,44 @@
|
|||
import loadable from '@loadable/component';
|
||||
import { useCurrentLibrary, useInvalidateQuery } from '@sd/client';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import { Suspense } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import { AppLayout } from './AppLayout';
|
||||
import { useKeybindHandler } from './hooks/useKeyboardHandler';
|
||||
|
||||
const DebugScreen = lazy(() => import('./screens/Debug'));
|
||||
const SettingsScreen = lazy(() => import('./screens/settings/Settings'));
|
||||
const TagExplorer = lazy(() => import('./screens/TagExplorer'));
|
||||
const PhotosScreen = lazy(() => import('./screens/Photos'));
|
||||
const OverviewScreen = lazy(() => import('./screens/Overview'));
|
||||
const ContentScreen = lazy(() => import('./screens/Content'));
|
||||
const LocationExplorer = lazy(() => import('./screens/LocationExplorer'));
|
||||
const OnboardingScreen = lazy(() => import('./components/onboarding/Onboarding'));
|
||||
const NotFound = lazy(() => import('./NotFound'));
|
||||
// Using React.lazy breaks hot reload so we don't use it.
|
||||
const DebugScreen = loadable(() => import('./screens/Debug'));
|
||||
const SettingsScreen = loadable(() => import('./screens/settings/Settings'));
|
||||
const TagExplorer = loadable(() => import('./screens/TagExplorer'));
|
||||
const PhotosScreen = loadable(() => import('./screens/Photos'));
|
||||
const OverviewScreen = loadable(() => import('./screens/Overview'));
|
||||
const ContentScreen = loadable(() => import('./screens/Content'));
|
||||
const LocationExplorer = loadable(() => import('./screens/LocationExplorer'));
|
||||
const OnboardingScreen = loadable(() => import('./components/onboarding/Onboarding'));
|
||||
const NotFound = loadable(() => import('./NotFound'));
|
||||
|
||||
const AppearanceSettings = lazy(() => import('./screens/settings/client/AppearanceSettings'));
|
||||
const ExtensionSettings = lazy(() => import('./screens/settings/client/ExtensionsSettings'));
|
||||
const GeneralSettings = lazy(() => import('./screens/settings/client/GeneralSettings'));
|
||||
const KeybindingSettings = lazy(() => import('./screens/settings/client/KeybindingSettings'));
|
||||
const PrivacySettings = lazy(() => import('./screens/settings/client/PrivacySettings'));
|
||||
const AboutSpacedrive = lazy(() => import('./screens/settings/info/AboutSpacedrive'));
|
||||
const Changelog = lazy(() => import('./screens/settings/info/Changelog'));
|
||||
const Support = lazy(() => import('./screens/settings/info/Support'));
|
||||
const ContactsSettings = lazy(() => import('./screens/settings/library/ContactsSettings'));
|
||||
const KeysSettings = lazy(() => import('./screens/settings/library/KeysSetting'));
|
||||
const LibraryGeneralSettings = lazy(
|
||||
const AppearanceSettings = loadable(() => import('./screens/settings/client/AppearanceSettings'));
|
||||
const ExtensionSettings = loadable(() => import('./screens/settings/client/ExtensionsSettings'));
|
||||
const GeneralSettings = loadable(() => import('./screens/settings/client/GeneralSettings'));
|
||||
const KeybindingSettings = loadable(() => import('./screens/settings/client/KeybindingSettings'));
|
||||
const PrivacySettings = loadable(() => import('./screens/settings/client/PrivacySettings'));
|
||||
const AboutSpacedrive = loadable(() => import('./screens/settings/info/AboutSpacedrive'));
|
||||
const Changelog = loadable(() => import('./screens/settings/info/Changelog'));
|
||||
const Support = loadable(() => import('./screens/settings/info/Support'));
|
||||
const ContactsSettings = loadable(() => import('./screens/settings/library/ContactsSettings'));
|
||||
const KeysSettings = loadable(() => import('./screens/settings/library/KeysSetting'));
|
||||
const LibraryGeneralSettings = loadable(
|
||||
() => import('./screens/settings/library/LibraryGeneralSettings')
|
||||
);
|
||||
const LocationSettings = lazy(() => import('./screens/settings/library/LocationSettings'));
|
||||
const NodesSettings = lazy(() => import('./screens/settings/library/NodesSettings'));
|
||||
const SecuritySettings = lazy(() => import('./screens/settings/library/SecuritySettings'));
|
||||
const SharingSettings = lazy(() => import('./screens/settings/library/SharingSettings'));
|
||||
const SyncSettings = lazy(() => import('./screens/settings/library/SyncSettings'));
|
||||
const TagsSettings = lazy(() => import('./screens/settings/library/TagsSettings'));
|
||||
const ExperimentalSettings = lazy(() => import('./screens/settings/node/ExperimentalSettings'));
|
||||
const LibrarySettings = lazy(() => import('./screens/settings/node/LibrariesSettings'));
|
||||
const P2PSettings = lazy(() => import('./screens/settings/node/P2PSettings'));
|
||||
const LocationSettings = loadable(() => import('./screens/settings/library/LocationSettings'));
|
||||
const NodesSettings = loadable(() => import('./screens/settings/library/NodesSettings'));
|
||||
const SecuritySettings = loadable(() => import('./screens/settings/library/SecuritySettings'));
|
||||
const SharingSettings = loadable(() => import('./screens/settings/library/SharingSettings'));
|
||||
const SyncSettings = loadable(() => import('./screens/settings/library/SyncSettings'));
|
||||
const TagsSettings = loadable(() => import('./screens/settings/library/TagsSettings'));
|
||||
const ExperimentalSettings = loadable(() => import('./screens/settings/node/ExperimentalSettings'));
|
||||
const LibrarySettings = loadable(() => import('./screens/settings/node/LibrariesSettings'));
|
||||
const P2PSettings = loadable(() => import('./screens/settings/node/P2PSettings'));
|
||||
|
||||
export function AppRouter() {
|
||||
const { library } = useCurrentLibrary();
|
||||
|
|
|
@ -16,8 +16,6 @@ export default function CreateLibraryDialog({
|
|||
'library.create',
|
||||
{
|
||||
onSuccess: (library: any) => {
|
||||
console.log('SUBMITTING');
|
||||
|
||||
setOpenCreateModal(false);
|
||||
|
||||
queryClient.setQueryData(['library.list'], (libraries: any) => [
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { Transition } from '@headlessui/react';
|
||||
import { XMarkIcon } from '@heroicons/react/24/solid';
|
||||
import { Button } from '@sd/ui';
|
||||
import { ButtonLink } from '@sd/ui';
|
||||
import clsx from 'clsx';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export interface ModalProps {
|
||||
full?: boolean;
|
||||
|
@ -10,7 +9,6 @@ export interface ModalProps {
|
|||
}
|
||||
|
||||
export const Modal: React.FC<ModalProps> = (props) => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div
|
||||
className={clsx('absolute w-screen h-screen z-30', {
|
||||
|
@ -29,17 +27,12 @@ export const Modal: React.FC<ModalProps> = (props) => {
|
|||
>
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
onClick={() => navigate('/')}
|
||||
className="absolute top-0 left-0 w-screen h-screen bg-white -z-50 rounded-2xl dark:bg-gray-800 bg-opacity-90"
|
||||
/>
|
||||
</Transition>
|
||||
<Button
|
||||
onClick={() => navigate('/')}
|
||||
variant="gray"
|
||||
className="!px-1.5 absolute top-2 right-2"
|
||||
>
|
||||
<ButtonLink to="/" variant="gray" className="!px-1.5 absolute top-2 right-2">
|
||||
<XMarkIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</ButtonLink>
|
||||
<Transition
|
||||
show
|
||||
className="flex flex-grow"
|
||||
|
|
|
@ -197,7 +197,7 @@ export function Sidebar() {
|
|||
{
|
||||
name: 'Library Settings',
|
||||
icon: CogIcon,
|
||||
onPress: () => navigate('settings/library')
|
||||
to: 'settings/library'
|
||||
},
|
||||
{
|
||||
name: 'Add Library',
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { PencilIcon, TrashIcon } from '@heroicons/react/24/outline';
|
||||
import { useBridgeMutation, useBridgeQuery } from '@sd/client';
|
||||
import { LibraryConfigWrapped } from '@sd/client';
|
||||
import { Button } from '@sd/ui';
|
||||
import { Button, ButtonLink } from '@sd/ui';
|
||||
import { DotsSixVertical } from 'phosphor-react';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import CreateLibraryDialog from '../../../components/dialog/CreateLibraryDialog';
|
||||
import DeleteLibraryDialog from '../../../components/dialog/DeleteLibraryDialog';
|
||||
|
@ -13,7 +12,6 @@ import { SettingsContainer } from '../../../components/settings/SettingsContaine
|
|||
import { SettingsHeader } from '../../../components/settings/SettingsHeader';
|
||||
|
||||
function LibraryListItem(props: { library: LibraryConfigWrapped }) {
|
||||
const navigate = useNavigate();
|
||||
const [openDeleteModal, setOpenDeleteModal] = useState(false);
|
||||
|
||||
const { mutate: deleteLib, isLoading: libDeletePending } = useBridgeMutation('library.delete', {
|
||||
|
@ -22,14 +20,6 @@ function LibraryListItem(props: { library: LibraryConfigWrapped }) {
|
|||
}
|
||||
});
|
||||
|
||||
function handleEditLibrary() {
|
||||
// switch library if requesting to edit non-current library
|
||||
navigate('/settings/library');
|
||||
// if (props.library.uuid !== store.currentLibraryUuid) {
|
||||
// switchLibrary(props.library.uuid);
|
||||
// }
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<DotsSixVertical weight="bold" className="mt-[15px] mr-3 opacity-30" />
|
||||
|
@ -38,9 +28,9 @@ function LibraryListItem(props: { library: LibraryConfigWrapped }) {
|
|||
<p className="mt-0.5 text-xs text-gray-200">{props.library.uuid}</p>
|
||||
</div>
|
||||
<div className="mt-2 space-x-2">
|
||||
<Button variant="gray" className="!p-1.5" onClick={handleEditLibrary}>
|
||||
<ButtonLink to="/settings/library" variant="gray" className="!p-1.5">
|
||||
<PencilIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</ButtonLink>
|
||||
<DeleteLibraryDialog libraryUuid={props.library.uuid}>
|
||||
<Button variant="gray" className="!p-1.5">
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
|
|
|
@ -20,17 +20,18 @@
|
|||
"@headlessui/react": "^1.7.3",
|
||||
"@heroicons/react": "^2.0.12",
|
||||
"@radix-ui/react-context-menu": "^1.0.0",
|
||||
"@radix-ui/react-select": "^1.0.0",
|
||||
"@radix-ui/react-dialog": "^1.0.0",
|
||||
"@radix-ui/react-dropdown-menu": "^1.0.0",
|
||||
"@radix-ui/react-select": "^1.0.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"class-variance-authority": "^0.2.3",
|
||||
"clsx": "^1.2.1",
|
||||
"@sd/assets": "workspace:*",
|
||||
"clsx": "^1.2.1",
|
||||
"phosphor-react": "^1.4.1",
|
||||
"postcss": "^8.4.17",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "6.4.2",
|
||||
"react-loading-icons": "^1.1.0",
|
||||
"react-spring": "^9.5.5",
|
||||
"storybook": "^6.5.12",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import clsx from 'clsx';
|
||||
import { forwardRef } from 'react';
|
||||
import { Link, LinkProps } from 'react-router-dom';
|
||||
|
||||
const sizes = {
|
||||
default: 'py-1 px-3 text-md font-medium',
|
||||
|
@ -147,3 +148,34 @@ export const Button = forwardRef<
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const ButtonLink = forwardRef<
|
||||
HTMLLinkElement,
|
||||
ButtonBaseProps & LinkProps & React.RefAttributes<HTMLAnchorElement>
|
||||
>(
|
||||
(
|
||||
{ loading, justifyLeft, className, pressEffect, noBorder, noPadding, size, variant, ...props },
|
||||
ref
|
||||
) => {
|
||||
className = clsx(
|
||||
'border rounded-md items-center transition-colors duration-100 cursor-default',
|
||||
{ 'opacity-70': loading, '!p-1': noPadding },
|
||||
{ 'justify-center': !justifyLeft },
|
||||
sizes[size || 'default'],
|
||||
variants[variant || 'default'],
|
||||
{ 'active:translate-y-[1px]': pressEffect },
|
||||
{ 'border-0': noBorder },
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
className
|
||||
);
|
||||
|
||||
return (
|
||||
<Link {...props} ref={ref as any} className={clsx(className, 'no-underline')}>
|
||||
<>
|
||||
{props.icon}
|
||||
{props.children}
|
||||
</>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -2,17 +2,28 @@ import { Menu, Transition } from '@headlessui/react';
|
|||
import { ChevronDownIcon } from '@heroicons/react/24/solid';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Button } from './Button';
|
||||
|
||||
export type DropdownItem = {
|
||||
name: string;
|
||||
icon?: any;
|
||||
disabled?: boolean;
|
||||
selected?: boolean;
|
||||
onPress?: () => any;
|
||||
wrapItemComponent?: React.FC<{ children: React.ReactNode }>;
|
||||
}[];
|
||||
export type DropdownItem = (
|
||||
| {
|
||||
name: string;
|
||||
icon?: any;
|
||||
selected?: boolean;
|
||||
to?: string;
|
||||
wrapItemComponent?: React.FC<{ children: React.ReactNode }>;
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
icon?: any;
|
||||
disabled?: boolean;
|
||||
selected?: boolean;
|
||||
onPress?: () => any;
|
||||
to?: string;
|
||||
wrapItemComponent?: React.FC<{ children: React.ReactNode }>;
|
||||
}
|
||||
)[];
|
||||
|
||||
export interface DropdownProps {
|
||||
items: DropdownItem[];
|
||||
|
@ -81,28 +92,54 @@ export const Dropdown: React.FC<DropdownProps> = (props) => {
|
|||
|
||||
return (
|
||||
<WrappedItem>
|
||||
<button
|
||||
onClick={button.onPress}
|
||||
disabled={button?.disabled === true}
|
||||
className={clsx(
|
||||
'text-sm group flex grow shrink-0 rounded items-center w-full whitespace-nowrap px-2 py-1 mb-[2px] dark:hover:bg-gray-650 disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
{
|
||||
'bg-gray-300 dark:bg-primary dark:hover:bg-primary': button.selected
|
||||
// 'text-gray-900 dark:text-gray-200': !active
|
||||
},
|
||||
props.itemButtonClassName
|
||||
)}
|
||||
>
|
||||
{button.icon && (
|
||||
<button.icon
|
||||
className={clsx('mr-2 w-4 h-4', {
|
||||
'dark:text-gray-100': active,
|
||||
'text-gray-600 dark:text-gray-200': !active
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
<span className="text-left">{button.name}</span>
|
||||
</button>
|
||||
{button.to ? (
|
||||
<Link
|
||||
to={button.to}
|
||||
className={clsx(
|
||||
'text-sm group flex grow shrink-0 rounded items-center w-full whitespace-nowrap px-2 py-1 mb-[2px] dark:hover:bg-gray-650 disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
{
|
||||
'bg-gray-300 dark:bg-primary dark:hover:bg-primary':
|
||||
button.selected
|
||||
// 'text-gray-900 dark:text-gray-200': !active
|
||||
},
|
||||
props.itemButtonClassName
|
||||
)}
|
||||
>
|
||||
{button.icon && (
|
||||
<button.icon
|
||||
className={clsx('mr-2 w-4 h-4', {
|
||||
'dark:text-gray-100': active,
|
||||
'text-gray-600 dark:text-gray-200': !active
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
<span className="text-left">{button.name}</span>
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
onClick={(button as any).onPress}
|
||||
disabled={(button as any)?.disabled === true}
|
||||
className={clsx(
|
||||
'text-sm group flex grow shrink-0 rounded items-center w-full whitespace-nowrap px-2 py-1 mb-[2px] dark:hover:bg-gray-650 disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
{
|
||||
'bg-gray-300 dark:bg-primary dark:hover:bg-primary':
|
||||
button.selected
|
||||
// 'text-gray-900 dark:text-gray-200': !active
|
||||
},
|
||||
props.itemButtonClassName
|
||||
)}
|
||||
>
|
||||
{button.icon && (
|
||||
<button.icon
|
||||
className={clsx('mr-2 w-4 h-4', {
|
||||
'dark:text-gray-100': active,
|
||||
'text-gray-600 dark:text-gray-200': !active
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
<span className="text-left">{button.name}</span>
|
||||
</button>
|
||||
)}
|
||||
</WrappedItem>
|
||||
);
|
||||
}}
|
||||
|
|
|
@ -278,7 +278,6 @@ importers:
|
|||
'@rspc/client': ^0.1.2
|
||||
'@rspc/react': ^0.1.2
|
||||
'@sd/config': workspace:*
|
||||
'@sd/interface': workspace:*
|
||||
'@tanstack/react-query': ^4.10.1
|
||||
'@types/lodash': ^4.14.186
|
||||
'@types/react': ^18.0.21
|
||||
|
@ -295,7 +294,6 @@ importers:
|
|||
'@rspc/client': 0.1.2
|
||||
'@rspc/react': 0.1.2_2j6p62nqyqxwo4xyixi65nw6l4
|
||||
'@sd/config': link:../config
|
||||
'@sd/interface': link:../interface
|
||||
'@tanstack/react-query': 4.10.1
|
||||
eventemitter3: 4.0.7
|
||||
immer: 9.0.15
|
||||
|
@ -331,6 +329,7 @@ importers:
|
|||
'@fontsource/inter': ^4.5.13
|
||||
'@headlessui/react': ^1.7.3
|
||||
'@heroicons/react': ^2.0.12
|
||||
'@loadable/component': ^5.15.2
|
||||
'@radix-ui/react-dialog': ^1.0.0
|
||||
'@radix-ui/react-dropdown-menu': ^1.0.0
|
||||
'@radix-ui/react-icons': ^1.1.1
|
||||
|
@ -349,6 +348,7 @@ importers:
|
|||
'@tanstack/react-virtual': 3.0.0-beta.18
|
||||
'@types/babel-core': ^6.25.7
|
||||
'@types/byte-size': ^8.1.0
|
||||
'@types/loadable__component': ^5.13.4
|
||||
'@types/lodash': ^4.14.186
|
||||
'@types/node': ^18.8.2
|
||||
'@types/pretty-bytes': ^5.2.0
|
||||
|
@ -406,6 +406,7 @@ importers:
|
|||
'@fontsource/inter': 4.5.13
|
||||
'@headlessui/react': 1.7.3_biqbaboplfbrettd7655fr4n2y
|
||||
'@heroicons/react': 2.0.12_react@18.2.0
|
||||
'@loadable/component': 5.15.2_react@18.2.0
|
||||
'@radix-ui/react-dialog': 1.0.0_rj7ozvcq3uehdlnj3cbwzbi5ce
|
||||
'@radix-ui/react-dropdown-menu': 1.0.0_rj7ozvcq3uehdlnj3cbwzbi5ce
|
||||
'@radix-ui/react-icons': 1.1.1_react@18.2.0
|
||||
|
@ -464,6 +465,7 @@ importers:
|
|||
'@sd/config': link:../config
|
||||
'@types/babel-core': 6.25.7
|
||||
'@types/byte-size': 8.1.0
|
||||
'@types/loadable__component': 5.13.4
|
||||
'@types/lodash': 4.14.186
|
||||
'@types/node': 18.8.2
|
||||
'@types/pretty-bytes': 5.2.0
|
||||
|
@ -516,6 +518,7 @@ importers:
|
|||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
react-loading-icons: ^1.1.0
|
||||
react-router-dom: 6.4.2
|
||||
react-spring: ^9.5.5
|
||||
sass: ^1.55.0
|
||||
sass-loader: ^13.0.2
|
||||
|
@ -541,6 +544,7 @@ importers:
|
|||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-loading-icons: 1.1.0
|
||||
react-router-dom: 6.4.2_biqbaboplfbrettd7655fr4n2y
|
||||
react-spring: 9.5.5_biqbaboplfbrettd7655fr4n2y
|
||||
storybook: 6.5.12_yalvw3r2waubxycyb7k7qsruca
|
||||
tailwindcss: 3.1.8
|
||||
|
@ -2977,6 +2981,18 @@ packages:
|
|||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
dev: false
|
||||
|
||||
/@loadable/component/5.15.2_react@18.2.0:
|
||||
resolution: {integrity: sha512-ryFAZOX5P2vFkUdzaAtTG88IGnr9qxSdvLRvJySXcUA4B4xVWurUNADu3AnKPksxOZajljqTrDEDcYjeL4lvLw==}
|
||||
engines: {node: '>=8'}
|
||||
peerDependencies:
|
||||
react: '>=16.3.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.19.0
|
||||
hoist-non-react-statics: 3.3.2
|
||||
react: 18.2.0
|
||||
react-is: 16.13.1
|
||||
dev: false
|
||||
|
||||
/@mdx-js/mdx/1.6.22:
|
||||
resolution: {integrity: sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==}
|
||||
dependencies:
|
||||
|
@ -6056,6 +6072,12 @@ packages:
|
|||
dependencies:
|
||||
'@types/node': 18.8.2
|
||||
|
||||
/@types/loadable__component/5.13.4:
|
||||
resolution: {integrity: sha512-YhoCCxyuvP2XeZNbHbi8Wb9EMaUJuA2VGHxJffcQYrJKIKSkymJrhbzsf9y4zpTmr5pExAAEh5hbF628PAZ8Dg==}
|
||||
dependencies:
|
||||
'@types/react': 18.0.21
|
||||
dev: true
|
||||
|
||||
/@types/lodash/4.14.186:
|
||||
resolution: {integrity: sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==}
|
||||
|
||||
|
@ -11484,6 +11506,12 @@ packages:
|
|||
minimalistic-assert: 1.0.1
|
||||
minimalistic-crypto-utils: 1.0.1
|
||||
|
||||
/hoist-non-react-statics/3.3.2:
|
||||
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||
dependencies:
|
||||
react-is: 16.13.1
|
||||
dev: false
|
||||
|
||||
/hosted-git-info/2.8.9:
|
||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||
|
||||
|
|
Loading…
Reference in a new issue