preview
This commit is contained in:
@@ -32,3 +32,4 @@ scripts/release.md
|
|||||||
src-tauri/binaries/*
|
src-tauri/binaries/*
|
||||||
!src-tauri/binaries/.gitkeep
|
!src-tauri/binaries/.gitkeep
|
||||||
/public/out.js
|
/public/out.js
|
||||||
|
video
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { type ReactNode, createContext, useContext } from 'react'
|
||||||
|
|
||||||
|
const PreviewContext = createContext(false)
|
||||||
|
|
||||||
|
export function PreviewProvider({ children }: { children: ReactNode }) {
|
||||||
|
return <PreviewContext.Provider value={true}>{children}</PreviewContext.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useIsPreview(): boolean {
|
||||||
|
return useContext(PreviewContext)
|
||||||
|
}
|
||||||
@@ -15,7 +15,10 @@ async function buildExternal() {
|
|||||||
// Compile TypeScript using local tsc
|
// Compile TypeScript using local tsc
|
||||||
console.log('[buildExternal] Compiling TypeScript...')
|
console.log('[buildExternal] Compiling TypeScript...')
|
||||||
execSync(
|
execSync(
|
||||||
'npx tsc main.ts --target es2020 --module esnext --moduleResolution bundler --listEmittedFiles',
|
// skipLibCheck mirrors tsconfig.json: a bare-CLI tsc ignores tsconfig and
|
||||||
|
// auto-includes every node_modules/@types package, and Remotion's transitive
|
||||||
|
// @types/dom-webcodecs collides with the WebCodecs types TS now bundles in lib.dom.
|
||||||
|
'npx tsc main.ts --target es2020 --module esnext --moduleResolution bundler --skipLibCheck --listEmittedFiles',
|
||||||
{
|
{
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
cwd: projectRoot,
|
cwd: projectRoot,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { platform } from '@tauri-apps/plugin-os'
|
|||||||
import { ExternalLinkIcon, RefreshCwIcon, SearchCheckIcon, SquareIcon } from 'lucide-react'
|
import { ExternalLinkIcon, RefreshCwIcon, SearchCheckIcon, SquareIcon } from 'lucide-react'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { formatBytes } from '../../lib/format'
|
import { formatBytes } from '../../lib/format'
|
||||||
|
import { useIsPreview } from '../../lib/preview'
|
||||||
import { notify } from '../../lib/notifications'
|
import { notify } from '../../lib/notifications'
|
||||||
import { startBatch } from '../../lib/rclone/api'
|
import { startBatch } from '../../lib/rclone/api'
|
||||||
import rclone from '../../lib/rclone/client'
|
import rclone from '../../lib/rclone/client'
|
||||||
@@ -35,6 +36,7 @@ export default function JobDetailsDrawer({
|
|||||||
onSelectJob?: (job: JobItem) => void
|
onSelectJob?: (job: JobItem) => void
|
||||||
}) {
|
}) {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
const isPreview = useIsPreview()
|
||||||
const [retryStatus, setRetryStatus] = useState<
|
const [retryStatus, setRetryStatus] = useState<
|
||||||
Map<string, { status: 'pending' | 'started' | 'error'; jobId?: number; error?: string }>
|
Map<string, { status: 'pending' | 'started' | 'error'; jobId?: number; error?: string }>
|
||||||
>(new Map())
|
>(new Map())
|
||||||
@@ -302,6 +304,7 @@ export default function JobDetailsDrawer({
|
|||||||
>
|
>
|
||||||
<Progress
|
<Progress
|
||||||
value={item.percentage}
|
value={item.percentage}
|
||||||
|
disableAnimation={isPreview}
|
||||||
classNames={{
|
classNames={{
|
||||||
base: 'overflow-hidden rounded-full max-w-lg',
|
base: 'overflow-hidden rounded-full max-w-lg',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||||||
import { Group, Panel, Separator } from 'react-resizable-panels'
|
import { Group, Panel, Separator } from 'react-resizable-panels'
|
||||||
import { onErrorDialog, reportError } from '../../lib/errors'
|
import { onErrorDialog, reportError } from '../../lib/errors'
|
||||||
import { getFsInfo } from '../../lib/format'
|
import { getFsInfo } from '../../lib/format'
|
||||||
|
import { useIsPreview } from '../../lib/preview'
|
||||||
// import { Document, Page, pdfjs } from 'react-pdf'
|
// import { Document, Page, pdfjs } from 'react-pdf'
|
||||||
import { formatBytes } from '../../lib/format.ts'
|
import { formatBytes } from '../../lib/format.ts'
|
||||||
import { notify } from '../../lib/notifications'
|
import { notify } from '../../lib/notifications'
|
||||||
@@ -524,6 +525,7 @@ function TransferItem({
|
|||||||
}
|
}
|
||||||
status: 'transferring' | 'checking' | 'done' | 'error'
|
status: 'transferring' | 'checking' | 'done' | 'error'
|
||||||
}) {
|
}) {
|
||||||
|
const isPreview = useIsPreview()
|
||||||
const fileName = item.name?.split('/').pop() || item.name || 'Unknown'
|
const fileName = item.name?.split('/').pop() || item.name || 'Unknown'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -562,6 +564,7 @@ function TransferItem({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Progress
|
<Progress
|
||||||
value={item.percentage || 0}
|
value={item.percentage || 0}
|
||||||
|
disableAnimation={isPreview}
|
||||||
size="sm"
|
size="sm"
|
||||||
color={status === 'checking' ? 'warning' : 'primary'}
|
color={status === 'checking' ? 'warning' : 'primary'}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
|
|||||||
@@ -275,12 +275,12 @@ export default function Delete() {
|
|||||||
{supportsPurge && (
|
{supportsPurge && (
|
||||||
<Alert
|
<Alert
|
||||||
color="primary"
|
color="primary"
|
||||||
title="LET ME SHARE A TIP"
|
title="LET ME SHARE A TIP!"
|
||||||
variant="faded"
|
variant="faded"
|
||||||
className="min-h-none h-fit max-h-fit"
|
className="min-h-none h-fit max-h-fit"
|
||||||
>
|
>
|
||||||
If you're deleting a entire folder, "{sourceRemoteName}" supports Purge
|
If you're deleting a entire folder, "{sourceRemoteName}" supports Purge
|
||||||
which is more efficient!
|
which is more efficient.
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -303,7 +303,6 @@ export default function Delete() {
|
|||||||
buttonText={buttonText}
|
buttonText={buttonText}
|
||||||
buttonIcon={buttonIcon}
|
buttonIcon={buttonIcon}
|
||||||
newLabel="NEW DELETE"
|
newLabel="NEW DELETE"
|
||||||
newButtonPrimary={false}
|
|
||||||
showViewTransfers={false}
|
showViewTransfers={false}
|
||||||
resetPathsLabel="Reset Path"
|
resetPathsLabel="Reset Path"
|
||||||
onResetPaths={handleResetPaths}
|
onResetPaths={handleResetPaths}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export default function NotificationsSection() {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<DummyProviderCard
|
<DummyProviderCard
|
||||||
label="Telegram (bot-less)"
|
label="Telegram (botless)"
|
||||||
description="Get messages without your own bot"
|
description="Get messages without your own bot"
|
||||||
icon={<TelegramIcon className="text-sky-500 size-8 shrink-0" />}
|
icon={<TelegramIcon className="text-sky-500 size-8 shrink-0" />}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
@@ -166,6 +166,7 @@ function ProviderCard({
|
|||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
className="h-24 bg-content2"
|
className="h-24 bg-content2"
|
||||||
data-focus-visible="false"
|
data-focus-visible="false"
|
||||||
|
data-provider={provider}
|
||||||
>
|
>
|
||||||
<CardBody className="relative flex flex-row items-center gap-3 px-4">
|
<CardBody className="relative flex flex-row items-center gap-3 px-4">
|
||||||
<ProviderIcon
|
<ProviderIcon
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ export default function RemotesSection() {
|
|||||||
if (remotes.length === 0 && !creatingDrawerOpen) {
|
if (remotes.length === 0 && !creatingDrawerOpen) {
|
||||||
return withRoot(
|
return withRoot(
|
||||||
<div className="flex flex-col items-center justify-center gap-8">
|
<div className="flex flex-col items-center justify-center gap-8">
|
||||||
<h1 className="text-2xl font-bold">No remotes found</h1>
|
<h1 className="text-2xl font-bold">Add your first remote!</h1>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => setCreatingDrawerOpen(true)}
|
onPress={() => setCreatingDrawerOpen(true)}
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -449,6 +449,7 @@ function RemoteCard({
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={remote}
|
key={remote}
|
||||||
|
data-remote={remote}
|
||||||
shadow="sm"
|
shadow="sm"
|
||||||
isBlurred={true}
|
isBlurred={true}
|
||||||
className="w-full h-20 border-[0.5px] dark:border-none border-divider bg-content3/50 dark:bg-content2/90"
|
className="w-full h-20 border-[0.5px] dark:border-none border-divider bg-content3/50 dark:bg-content2/90"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { useSearchParams } from 'react-router-dom'
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { LOCAL_HOST_ID } from '../../../lib/hosts'
|
import { LOCAL_HOST_ID } from '../../../lib/hosts'
|
||||||
|
import { useIsPreview } from '../../../lib/preview'
|
||||||
import rclone from '../../../lib/rclone/client'
|
import rclone from '../../../lib/rclone/client'
|
||||||
import { useStore } from '../../../store/memory'
|
import { useStore } from '../../../store/memory'
|
||||||
import { useCurrentHost, usePersistedStore } from '../../../store/persisted'
|
import { useCurrentHost, usePersistedStore } from '../../../store/persisted'
|
||||||
@@ -36,6 +37,7 @@ import RemotesSection from './RemotesSection'
|
|||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
|
const isPreview = useIsPreview()
|
||||||
const settingsPass = usePersistedStore((state) => state.settingsPass)
|
const settingsPass = usePersistedStore((state) => state.settingsPass)
|
||||||
const currentHost = useCurrentHost()
|
const currentHost = useCurrentHost()
|
||||||
const isRestartingRclone = useStore((state) => state.isRestartingRclone)
|
const isRestartingRclone = useStore((state) => state.isRestartingRclone)
|
||||||
@@ -144,8 +146,10 @@ export default function Settings() {
|
|||||||
className="flex-shrink-0 h-screen px-2 py-4 overflow-y-auto border-r w-52 dark:bg-transparent bg-content2 border-divider dark:border-neutral-700"
|
className="flex-shrink-0 h-screen px-2 py-4 overflow-y-auto border-r w-52 dark:bg-transparent bg-content2 border-divider dark:border-neutral-700"
|
||||||
classNames={{
|
classNames={{
|
||||||
// pb clears the fixed version bar (and the connected-host strip above it) so the
|
// pb clears the fixed version bar (and the connected-host strip above it) so the
|
||||||
// last tabs stay reachable once the list scrolls.
|
// last tabs stay reachable once the list scrolls. pt-6 clears macOS's
|
||||||
tabList: 'w-full gap-3 pb-10' + (platform() === 'macos' ? ' pt-6' : ''),
|
tabList:
|
||||||
|
'w-full gap-3 pb-10' +
|
||||||
|
(platform() === 'macos' && !isPreview ? ' pt-6' : ''),
|
||||||
tab: 'h-14 justify-start rounded-large',
|
tab: 'h-14 justify-start rounded-large',
|
||||||
tabContent: 'pl-8',
|
tabContent: 'pl-8',
|
||||||
}}
|
}}
|
||||||
@@ -316,19 +320,21 @@ export default function Settings() {
|
|||||||
>
|
>
|
||||||
<ProxySection />
|
<ProxySection />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
{!isPreview && (
|
||||||
key="license"
|
<Tab
|
||||||
title={
|
key="license"
|
||||||
<div className="flex items-center gap-2">
|
title={
|
||||||
<MedalIcon className="w-5 h-5" />
|
<div className="flex items-center gap-2">
|
||||||
<span>License</span>
|
<MedalIcon className="w-5 h-5" />
|
||||||
</div>
|
<span>License</span>
|
||||||
}
|
</div>
|
||||||
data-focus-visible="false"
|
}
|
||||||
className="w-full max-h-screen p-0 overflow-scroll overscroll-none"
|
data-focus-visible="false"
|
||||||
>
|
className="w-full max-h-screen p-0 overflow-scroll overscroll-none"
|
||||||
<LicenseSection />
|
>
|
||||||
</Tab>
|
<LicenseSection />
|
||||||
|
</Tab>
|
||||||
|
)}
|
||||||
<Tab
|
<Tab
|
||||||
key="about"
|
key="about"
|
||||||
title={
|
title={
|
||||||
@@ -352,10 +358,15 @@ export default function Settings() {
|
|||||||
)}
|
)}
|
||||||
<div className="absolute bottom-0 left-0 flex flex-col h-12 gap-4 p-4 border-t border-r w-52 bg-content3 dark:bg-content1 border-divider dark:border-neutral-700">
|
<div className="absolute bottom-0 left-0 flex flex-col h-12 gap-4 p-4 border-t border-r w-52 bg-content3 dark:bg-content1 border-divider dark:border-neutral-700">
|
||||||
<p
|
<p
|
||||||
className="text-[10px] text-center text-neutral-500 hover:text-neutral-400 cursor-pointer"
|
className={cn(
|
||||||
|
'text-[10px] text-center cursor-pointer',
|
||||||
|
isPreview
|
||||||
|
? 'text-white font-semibold'
|
||||||
|
: 'text-neutral-500 hover:text-neutral-400 '
|
||||||
|
)}
|
||||||
onClick={() => openUrl('https://github.com/rclone-ui/rclone-ui')}
|
onClick={() => openUrl('https://github.com/rclone-ui/rclone-ui')}
|
||||||
>
|
>
|
||||||
UI v{uiVersion}, CLI v{cliVersion}
|
{isPreview ? 'rcloneui.com' : `UI v${uiVersion}, CLI v${cliVersion}`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { message } from '@tauri-apps/plugin-dialog'
|
|||||||
import { ChevronRightIcon, RefreshCcwIcon, SearchCheckIcon } from 'lucide-react'
|
import { ChevronRightIcon, RefreshCcwIcon, SearchCheckIcon } from 'lucide-react'
|
||||||
import { startTransition, useCallback, useMemo, useState } from 'react'
|
import { startTransition, useCallback, useMemo, useState } from 'react'
|
||||||
import { buildReadablePathMultiple, formatBytes } from '../../lib/format'
|
import { buildReadablePathMultiple, formatBytes } from '../../lib/format'
|
||||||
|
import { useIsPreview } from '../../lib/preview'
|
||||||
import { listTransfers } from '../../lib/rclone/api'
|
import { listTransfers } from '../../lib/rclone/api'
|
||||||
import { usePersistedStore } from '../../store/persisted'
|
import { usePersistedStore } from '../../store/persisted'
|
||||||
import type { JobItem } from '../../types/jobs'
|
import type { JobItem } from '../../types/jobs'
|
||||||
@@ -121,6 +122,7 @@ export default function Transfers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function JobCard({ job, onSelect }: { job: JobItem; onSelect: (job: JobItem) => void }) {
|
function JobCard({ job, onSelect }: { job: JobItem; onSelect: (job: JobItem) => void }) {
|
||||||
|
const isPreview = useIsPreview()
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={job.id}
|
key={job.id}
|
||||||
@@ -191,7 +193,8 @@ function JobCard({ job, onSelect }: { job: JobItem; onSelect: (job: JobItem) =>
|
|||||||
>
|
>
|
||||||
<Progress
|
<Progress
|
||||||
value={job.progress}
|
value={job.progress}
|
||||||
isStriped={true}
|
disableAnimation={isPreview}
|
||||||
|
isStriped={!(job.isChecking && job.totalBytes === 0)}
|
||||||
isIndeterminate={job.isChecking && job.totalBytes === 0}
|
isIndeterminate={job.isChecking && job.totalBytes === 0}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export default {
|
|||||||
'./index.html',
|
'./index.html',
|
||||||
'./src/**/*.{js,ts,jsx,tsx}',
|
'./src/**/*.{js,ts,jsx,tsx}',
|
||||||
'./lib/**/*.{js,ts,jsx,tsx}',
|
'./lib/**/*.{js,ts,jsx,tsx}',
|
||||||
|
'./video/**/*.{js,ts,jsx,tsx}',
|
||||||
'./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}',
|
'./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}',
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
|
|||||||
Reference in New Issue
Block a user