mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-08 07:13:21 -04:00
Merge branch 'landing-preview'
This commit is contained in:
8
apps/landing/env.d.ts
vendored
8
apps/landing/env.d.ts
vendored
@@ -21,12 +21,4 @@ declare module '*.md' {
|
||||
// When "Mode.React" is requested. VFC could take a generic like React.VFC<{ MyComponent: TypeOfMyComponent }>
|
||||
import React from 'react';
|
||||
const ReactComponent: React.VFC;
|
||||
|
||||
// When "Mode.Vue" is requested
|
||||
import { ComponentOptions, Component } from 'vue';
|
||||
const VueComponent: ComponentOptions;
|
||||
const VueComponentWith: (components: Record<string, Component>) => ComponentOptions;
|
||||
|
||||
// Modify below per your usage
|
||||
export { attributes, toc, html, ReactComponent, VueComponent, VueComponentWith };
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^4.5.7",
|
||||
"@headlessui/react": "^1.5.0",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@icons-pack/react-simple-icons": "^4.6.1",
|
||||
"@sd/client": "workspace:*",
|
||||
@@ -23,6 +25,7 @@
|
||||
"phosphor-react": "^1.4.1",
|
||||
"prismjs": "^1.28.0",
|
||||
"react": "^18.0.0",
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-router-dom": "6.3.0",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 KiB |
49
apps/landing/src/components/AppEmbed.tsx
Normal file
49
apps/landing/src/components/AppEmbed.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { isMobile } from 'react-device-detect';
|
||||
|
||||
export default function AppEmbed() {
|
||||
const [showApp, setShowApp] = useState(false);
|
||||
const [iFrameAppReady, setIframeAppReady] = useState(false);
|
||||
const [imgFallback, setImageFallback] = useState(false);
|
||||
|
||||
function handleEvent(e: any) {
|
||||
if (e.data === 'spacedrive-hello') {
|
||||
if (!iFrameAppReady && !isMobile) setIframeAppReady(true);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('message', handleEvent, false);
|
||||
setShowApp(true);
|
||||
|
||||
return () => window.removeEventListener('message', handleEvent);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (!iFrameAppReady) setImageFallback(true);
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-screen ">
|
||||
<div className="relative z-30 h-[300px] lg:h-[628px] mt-16 overflow-hidden ">
|
||||
{imgFallback && <div className="h-full fade-in-image landing-img" />}
|
||||
{showApp && (
|
||||
<iframe
|
||||
referrerPolicy="origin-when-cross-origin"
|
||||
className={clsx(
|
||||
'opacity-0 pointer-events-none absolute w-[1200px] h-[300px] lg:h-[628px] z-30 border rounded-lg shadow-2xl inset-center bg-gray-850 border-gray-550',
|
||||
iFrameAppReady && 'fade-in-image !opacity-100 !pointer-events-auto'
|
||||
)}
|
||||
src={`${
|
||||
import.meta.env.VITE_SDWEB_BASE_URL || 'http://localhost:8002'
|
||||
}?library_id=9068c6ec-cf90-451b-bb30-4174781e7bc6`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Button } from '@sd/ui';
|
||||
import { Bubbles } from '../components/Bubbles';
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Footer } from '../components/Footer';
|
||||
import { Apple, Github, Linux, Windows } from '@icons-pack/react-simple-icons';
|
||||
import { useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import AppEmbed from '../components/AppEmbed';
|
||||
|
||||
interface SectionProps {
|
||||
orientation: 'left' | 'right';
|
||||
@@ -40,7 +41,18 @@ function Section(props: SectionProps = { orientation: 'left' }) {
|
||||
}
|
||||
|
||||
function Page() {
|
||||
const [showApp, setShowApp] = useState(false);
|
||||
// const [appLoaded, setAppLoaded] = useState(false);
|
||||
|
||||
// function handleResize(event: Event) {
|
||||
// if (window.innerWidth > 1000) setShowApp(true);
|
||||
// else if (showApp) setShowApp(false);
|
||||
// }
|
||||
|
||||
// useEffect(() => {
|
||||
// window.addEventListener('resize', handleResize);
|
||||
// return () => window.removeEventListener('resize', handleResize);
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mt-28 lg:mt-36" />
|
||||
@@ -85,21 +97,7 @@ function Page() {
|
||||
Shortly after to iOS & Android.
|
||||
</p>
|
||||
|
||||
<div className="h-[300px] lg:h-[600px] mt-16 w-screen max-w-[100vw] relative overflow-hidden relative">
|
||||
<iframe
|
||||
className={clsx(
|
||||
'absolute w-[1200px] h-[300px] lg:h-[600px] z-30 border rounded-lg shadow-2xl inset-center bg-gray-850 border-gray-550',
|
||||
showApp ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
onLoad={(event) => {
|
||||
setShowApp(true);
|
||||
}}
|
||||
src={`${
|
||||
import.meta.env.VITE_SDWEB_BASE_URL || 'http://localhost:8002'
|
||||
}?library_id=9068c6ec-cf90-451b-bb30-4174781e7bc6`}
|
||||
/>
|
||||
<div className="w-[800px] ml-[230px] md:ml-[100px] lg:ml-0 lg:w-[1200px] h-[300px] lg:h-[600px] inset-center absolute z-20 landing-img" />
|
||||
</div>
|
||||
<AppEmbed />
|
||||
<Section
|
||||
orientation="right"
|
||||
heading="Never leave a file behind."
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^4.5.7",
|
||||
"@sd/client": "*",
|
||||
"@sd/core": "*",
|
||||
"@sd/interface": "*",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import SpacedriveInterface from '@sd/interface';
|
||||
import { ClientCommand, ClientQuery, CoreEvent } from '@sd/core';
|
||||
@@ -64,6 +64,10 @@ class Transport extends BaseTransport {
|
||||
}
|
||||
|
||||
function App() {
|
||||
useEffect(() => {
|
||||
window.parent.postMessage('spacedrive-hello', '*');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
{/* <header className="App-header"></header> */}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.5.10",
|
||||
"@fontsource/inter": "^4.5.7",
|
||||
"@headlessui/react": "^1.5.0",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@radix-ui/react-dialog": "^0.1.7",
|
||||
|
||||
@@ -28,6 +28,9 @@ import { CoreEvent } from '@sd/core';
|
||||
import clsx from 'clsx';
|
||||
import './style.scss';
|
||||
import { ContentScreen } from './screens/Content';
|
||||
import LibrarySettings from './screens/settings/LibrarySettings';
|
||||
|
||||
import '@fontsource/inter/variable.css';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
@@ -92,9 +95,12 @@ function SettingsRoutes({ modal = false }) {
|
||||
<Route path="security" element={<SecuritySettings />} />
|
||||
<Route path="appearance" element={<></>} />
|
||||
<Route path="locations" element={<LocationSettings />} />
|
||||
<Route path="library" element={<LibrarySettings />} />
|
||||
<Route path="media" element={<></>} />
|
||||
<Route path="keys" element={<></>} />
|
||||
<Route path="tags" element={<></>} />
|
||||
<Route path="sync" element={<></>} />
|
||||
<Route path="contacts" element={<></>} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</SlideUp>
|
||||
|
||||
@@ -78,8 +78,9 @@ export function Device(props: DeviceProps) {
|
||||
{/* <hr className="border-gray-700" />
|
||||
<hr className="border-gray-550" /> */}
|
||||
<div className="px-4 pb-3 mt-3">
|
||||
{props.locations.map((location) => (
|
||||
{props.locations.map((location, key) => (
|
||||
<FileItem
|
||||
key={key}
|
||||
selected={selectedFile == location.name}
|
||||
onClick={() => handleSelect(location.name)}
|
||||
fileName={location.name}
|
||||
|
||||
@@ -21,7 +21,7 @@ export const SidebarLink = (props: NavLinkProps & { children: React.ReactNode })
|
||||
{({ isActive }) => (
|
||||
<span
|
||||
className={clsx(
|
||||
'max-w mb-[2px] text-gray-550 dark:text-gray-150 rounded px-2 py-1 flex flex-row flex-grow items-center hover:bg-gray-100 dark:hover:bg-gray-600 text-sm',
|
||||
'max-w mb-[2px] text-gray-550 dark:text-gray-150 rounded px-2 py-1 flex flex-row flex-grow items-center font-medium hover:bg-gray-100 dark:hover:bg-gray-600 text-sm',
|
||||
{ '!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive },
|
||||
props.className
|
||||
)}
|
||||
@@ -81,7 +81,7 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
|
||||
<Dropdown
|
||||
buttonProps={{
|
||||
justifyLeft: true,
|
||||
className: `flex w-full text-left max-w-full mb-1 mt-1 shadow-xs rounded
|
||||
className: `flex w-full text-left max-w-full mb-1 mt-1 -mr-0.5 shadow-xs rounded
|
||||
!bg-gray-50
|
||||
border-gray-150
|
||||
hover:!bg-gray-1000
|
||||
|
||||
@@ -12,7 +12,10 @@ interface InputContainerProps extends DefaultProps {
|
||||
export const InputContainer: React.FC<InputContainerProps> = (props) => {
|
||||
return (
|
||||
<div className="flex flex-row">
|
||||
<div className={clsx('flex flex-col w-full pb-6', props.className)} {...props}>
|
||||
<div
|
||||
className={clsx('flex flex-col w-full', !props.mini && 'pb-6', props.className)}
|
||||
{...props}
|
||||
>
|
||||
<h3 className="mb-1 font-medium text-gray-700 dark:text-gray-100">{props.title}</h3>
|
||||
{!!props.description && <p className="mb-2 text-sm text-gray-400 ">{props.description}</p>}
|
||||
{!props.mini && props.children}
|
||||
|
||||
@@ -33,8 +33,8 @@ export const OverviewScreen: React.FC<{}> = (props) => {
|
||||
<div className="flex flex-col w-full h-screen overflow-x-hidden overflow-y-scroll no-scrollbar">
|
||||
<div data-tauri-drag-region className="flex flex-shrink-0 w-full h-7" />
|
||||
<div className="flex flex-col w-full h-screen px-3">
|
||||
<div className="flex items-center w-full ml-4">
|
||||
<div className="flex flex-wrap p-2 space-x-6">
|
||||
<div className="flex items-center w-full">
|
||||
<div className="flex flex-wrap pb-4 space-x-6">
|
||||
<StatItem name="Total capacity" value="26.5" unit="TB" />
|
||||
<StatItem name="Index size" value="103" unit="MB" />
|
||||
<StatItem name="Preview media" value="23.5" unit="GB" />
|
||||
@@ -43,7 +43,7 @@ export const OverviewScreen: React.FC<{}> = (props) => {
|
||||
<StatItem name="Total backed up" value="25.3" unit="TB" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5" />
|
||||
{/* <div className="mt-5" /> */}
|
||||
<div className="flex flex-col pb-4 space-y-4">
|
||||
<Device
|
||||
name="James' MBP"
|
||||
|
||||
@@ -6,11 +6,11 @@ import {
|
||||
PhotographIcon,
|
||||
TagIcon,
|
||||
UsersIcon
|
||||
} from '@heroicons/react/solid';
|
||||
} from '@heroicons/react/outline';
|
||||
import React from 'react';
|
||||
// import { dummyIFile, FileList } from '../components/file/FileList';
|
||||
import { SidebarLink } from '../components/file/Sidebar';
|
||||
import { HardDrive, PaintBrush } from 'phosphor-react';
|
||||
import { Book, Database, HardDrive, PaintBrush } from 'phosphor-react';
|
||||
import clsx from 'clsx';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
@@ -47,14 +47,15 @@ export const SettingsScreen: React.FC<{}> = () => {
|
||||
</SidebarLink>
|
||||
|
||||
<Heading>Library</Heading>
|
||||
<SidebarLink to="/settings/library">
|
||||
<Icon component={Database} />
|
||||
Database
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/locations">
|
||||
<Icon component={HardDrive} />
|
||||
Locations
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/media">
|
||||
<Icon component={PhotographIcon} />
|
||||
Media
|
||||
</SidebarLink>
|
||||
|
||||
<SidebarLink to="/settings/keys">
|
||||
<Icon component={KeyIcon} />
|
||||
Keys
|
||||
|
||||
@@ -48,10 +48,7 @@ export default function GeneralSettings() {
|
||||
</div>
|
||||
</InputContainer> */}
|
||||
|
||||
<InputContainer
|
||||
title="Locations"
|
||||
description="Local cache storage for media previews and thumbnails."
|
||||
>
|
||||
<InputContainer title="Volumes" description="A list of volumes running on this device.">
|
||||
<div className="flex flex-row space-x-2">
|
||||
<div className="flex flex-grow">
|
||||
<Listbox
|
||||
|
||||
31
packages/interface/src/screens/settings/LibrarySettings.tsx
Normal file
31
packages/interface/src/screens/settings/LibrarySettings.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@sd/ui';
|
||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
import { Toggle } from '../../components/primitive';
|
||||
|
||||
type LibrarySecurity = 'public' | 'password' | 'vault';
|
||||
|
||||
export default function LibrarySettings() {
|
||||
// const locations = useBridgeQuery("SysGetLocation")
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||
{/*<Button size="sm">Add Location</Button>*/}
|
||||
<div className="mt-3 mb-3">
|
||||
<h1 className="text-2xl font-bold">Library database</h1>
|
||||
<p className="mt-1 text-sm text-gray-400">
|
||||
The database contains all library data and file metadata.
|
||||
</p>
|
||||
</div>
|
||||
<InputContainer
|
||||
mini
|
||||
title="Encrypt on cloud"
|
||||
description="Enable if library contains sensitive data and should not be synced to the cloud without full encryption."
|
||||
>
|
||||
<div className="flex items-center h-full">
|
||||
<Toggle initialState={true} size={'sm'} />
|
||||
</div>
|
||||
</InputContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,8 +6,8 @@ export default function SecuritySettings() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<InputContainer
|
||||
title="Something about a vault"
|
||||
description="Local cache storage for media previews and thumbnails."
|
||||
title="Vault"
|
||||
description="You'll need to set a passphrase to enable the vault."
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<Button variant="primary">Enable Vault</Button>
|
||||
|
||||
@@ -3,7 +3,7 @@ button {
|
||||
@apply cursor-default;
|
||||
}
|
||||
|
||||
|
||||
body { font-family: "InterVariable", sans-serif; }
|
||||
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
||||
|
||||
@@ -22,4 +22,38 @@
|
||||
background-image: url('/app.png');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
|
||||
.fade-in-image {
|
||||
animation: fadeIn 1s;
|
||||
-webkit-animation: fadeIn 1s;
|
||||
-moz-animation: fadeIn 1s;
|
||||
-o-animation: fadeIn 1s;
|
||||
-ms-animation: fadeIn 1s;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
0% {opacity:0;}
|
||||
100% {opacity:1;}
|
||||
}
|
||||
|
||||
@-moz-keyframes fadeIn {
|
||||
0% {opacity:0;}
|
||||
100% {opacity:1;}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeIn {
|
||||
0% {opacity:0;}
|
||||
100% {opacity:1;}
|
||||
}
|
||||
|
||||
@-o-keyframes fadeIn {
|
||||
0% {opacity:0;}
|
||||
100% {opacity:1;}
|
||||
}
|
||||
|
||||
@-ms-keyframes fadeIn {
|
||||
0% {opacity:0;}
|
||||
100% {opacity:1;}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,13 @@ module.exports = function (app, options) {
|
||||
darkMode: app == 'landing' ? 'class' : 'class',
|
||||
mode: 'jit',
|
||||
theme: {
|
||||
// fontFamily: {
|
||||
// sans: ['Inter', 'ui-sans-serif', 'system-ui'],
|
||||
// serif: ['Inter', 'ui-serif', 'Georgia'],
|
||||
// mono: ['ui-monospace', 'SFMono-Regular'],
|
||||
// display: ['Inter'],
|
||||
// body: ['"Inter"']
|
||||
// },
|
||||
fontSize: {
|
||||
'tiny': '.65rem',
|
||||
'xs': '.75rem',
|
||||
|
||||
BIN
pnpm-lock.yaml
generated
BIN
pnpm-lock.yaml
generated
Binary file not shown.
Reference in New Issue
Block a user