feat: add new search panel components and update existing ones with headers and close buttons
This commit is contained in:
parent
48f75a1acc
commit
e5c2a7fca5
BIN
public/assets/favicon.a64.png
Normal file
BIN
public/assets/favicon.a64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/assets/favicon.cbm.png
Normal file
BIN
public/assets/favicon.cbm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 701 B |
|
|
@ -1,5 +1,6 @@
|
|||
import { lazy, Suspense, useEffect, useState } from 'react';
|
||||
import { Cpu, Wifi, Network, HardDrive, Activity, Search, Wrench, User, FileText, AppWindow, Folder, Edit, Eye, Database, Upload, Download, Code2, LayoutList, Image, ChevronLeft, Loader2, Terminal, Link, Printer, Maximize2, Minimize2 } from 'lucide-react';
|
||||
import { AnimatePresence } from 'motion/react';
|
||||
import { Toaster, toast } from 'sonner';
|
||||
import StatusPage from './components/StatusPage';
|
||||
import DevicesPage from './components/DevicesPage';
|
||||
|
|
@ -54,8 +55,7 @@ type AppId =
|
|||
export default function App() {
|
||||
const [currentPage, setCurrentPage] = useState<Page>('status');
|
||||
const { config, setConfig, saveStatus, pendingCount, flushNow, reload } = useSettings();
|
||||
const [showSearch, setShowSearch] = useState(false);
|
||||
const [searchInitialTab, setSearchInitialTab] = useState<0 | 1 | 2 | 3 | undefined>(undefined);
|
||||
const [searchPanel, setSearchPanel] = useState<'local' | 'assembly64' | 'commoserve' | 'csdb' | 'last' | null>(null);
|
||||
const [devicesOpenId, setDevicesOpenId] = useState<string | null>(null);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const [fileManagerInitialPath, setFileManagerInitialPath] = useState<string | undefined>(undefined);
|
||||
|
|
@ -118,9 +118,9 @@ export default function App() {
|
|||
<h2 className="text-lg font-semibold mb-4 text-blue-700">Management</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<AppCard icon={<Folder className="w-7 h-7" />} label="Media Manager" onClick={() => { setFileManagerInitialPath(undefined); setCurrentPage('file-manager'); }} />
|
||||
<AppCard icon={<Database className="w-7 h-7" />} label="Assembly64" onClick={() => { setSearchInitialTab(1); setShowSearch(true); }} />
|
||||
<AppCard icon={<Database className="w-7 h-7" />} label="CommoServe" onClick={() => { setSearchInitialTab(2); setShowSearch(true); }} />
|
||||
<AppCard icon={<Database className="w-7 h-7" />} label="CSDb" onClick={() => { setSearchInitialTab(3); setShowSearch(true); }} />
|
||||
<AppCard icon={<Database className="w-7 h-7" />} label="Assembly64" onClick={() => setSearchPanel('assembly64')} />
|
||||
<AppCard icon={<Database className="w-7 h-7" />} label="CommoServe" onClick={() => setSearchPanel('commoserve')} />
|
||||
<AppCard icon={<Database className="w-7 h-7" />} label="CSDb" onClick={() => setSearchPanel('csdb')} />
|
||||
<AppCard icon={<Printer className="w-7 h-7" />} label="Print Manager" onClick={() => setCurrentPage('print-manager')} />
|
||||
<AppCard icon={<Terminal className="w-7 h-7" />} label="Serial Console" onClick={() => setCurrentPage('serial-console')} />
|
||||
<AppCard icon={<Link className="w-7 h-7" />} label="Short Codes" onClick={() => setCurrentPage('serial-console')} />
|
||||
|
|
@ -262,7 +262,7 @@ function AppPage({ title, onBack }: { title: string; onBack: () => void }) {
|
|||
{isFullscreen ? <Minimize2 className="w-5 h-5 text-white" /> : <Maximize2 className="w-5 h-5 text-white" />}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setSearchInitialTab(undefined); setShowSearch(true); }}
|
||||
onClick={() => setSearchPanel('last')}
|
||||
className="p-2 hover:bg-[#5e5e5e] rounded-lg"
|
||||
>
|
||||
<Search className="w-5 h-5 text-white" />
|
||||
|
|
@ -316,20 +316,18 @@ function AppPage({ title, onBack }: { title: string; onBack: () => void }) {
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
{showSearch && (
|
||||
<SearchPane
|
||||
config={config}
|
||||
setConfig={setConfig}
|
||||
initialTab={searchInitialTab}
|
||||
onClose={() => setShowSearch(false)}
|
||||
onOpenFolder={(path: string) => {
|
||||
setFileManagerInitialPath(path);
|
||||
setFileManagerReturnPage('status');
|
||||
setShowSearch(false);
|
||||
setCurrentPage('file-manager');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<AnimatePresence>
|
||||
{searchPanel && (
|
||||
<SearchPane
|
||||
key="search"
|
||||
config={config}
|
||||
setConfig={setConfig}
|
||||
initialTab={({ local: 0, assembly64: 1, commoserve: 2, csdb: 3, last: undefined } as const)[searchPanel]}
|
||||
onClose={() => setSearchPanel(null)}
|
||||
onOpenFolder={(path: string) => { setSearchPanel(null); setFileManagerInitialPath(path); setFileManagerReturnPage('status'); setCurrentPage('file-manager'); }}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</WsProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Search, Loader2, HardDrive, Download, ChevronRight, ChevronDown, Trophy, Calendar, Users, RefreshCw, HelpCircle } from 'lucide-react';
|
||||
import { Search, Loader2, HardDrive, Download, ChevronRight, ChevronDown, Trophy, Calendar, Users, RefreshCw, HelpCircle, X } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { humanFileSize, basename, joinPath, putFileContents } from '../webdav';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog';
|
||||
|
|
@ -153,7 +153,7 @@ const AQL_TERMS = [
|
|||
|
||||
// ─── Component ────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function SearchAssembly64({ config, setConfig, onClose: _onClose }: SearchAssembly64Props) {
|
||||
export default function SearchAssembly64({ config, setConfig, onClose }: SearchAssembly64Props) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [showAqlHelp, setShowAqlHelp] = useState(false);
|
||||
|
|
@ -322,6 +322,16 @@ export default function SearchAssembly64({ config, setConfig, onClose: _onClose
|
|||
return (
|
||||
<>
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
{/* Panel header */}
|
||||
<div className="flex-shrink-0 flex items-center border-b border-neutral-200/70 px-4">
|
||||
<div className="flex items-center gap-2 flex-1 py-3 min-w-0">
|
||||
<img src={`${import.meta.env.BASE_URL}assets/favicon.a64.png`} className="w-5 h-5 flex-shrink-0 object-contain" alt="" aria-hidden="true" />
|
||||
<span className="text-sm font-semibold text-neutral-700">Assembly64</span>
|
||||
</div>
|
||||
<button onClick={onClose} className="p-1.5 my-1.5 rounded-lg hover:bg-neutral-100 text-neutral-500 transition-colors" aria-label="Close search">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
{/* Header */}
|
||||
<div className="flex-shrink-0 px-4 pt-3 pb-3 border-b border-neutral-200/70">
|
||||
{/* Search input */}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Search, Loader2, HardDrive, Download, ChevronRight } from 'lucide-react';
|
||||
import { Search, Loader2, HardDrive, Download, ChevronRight, X, Archive } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { basename, joinPath, putFileContents } from '../webdav';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog';
|
||||
|
|
@ -68,7 +68,7 @@ function typeLabel(tags: string): string {
|
|||
|
||||
// ─── Component ────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function SearchCSDbNG({ config, setConfig, onClose: _onClose }: SearchCSDbNGProps) {
|
||||
export default function SearchCSDbNG({ config, setConfig, onClose }: SearchCSDbNGProps) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [query, setQuery] = useState(() => _store.query);
|
||||
|
|
@ -201,6 +201,16 @@ export default function SearchCSDbNG({ config, setConfig, onClose: _onClose }: S
|
|||
return (
|
||||
<>
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
{/* Panel header */}
|
||||
<div className="flex-shrink-0 flex items-center border-b border-neutral-200/70 px-4">
|
||||
<div className="flex items-center gap-2 flex-1 py-3 min-w-0">
|
||||
<Archive className="w-5 h-5 flex-shrink-0 text-neutral-500" aria-hidden="true" />
|
||||
<span className="text-sm font-semibold text-neutral-700">CSDb-ng</span>
|
||||
</div>
|
||||
<button onClick={onClose} className="p-1.5 my-1.5 rounded-lg hover:bg-neutral-100 text-neutral-500 transition-colors" aria-label="Close search">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
{/* Header */}
|
||||
<div className="flex-shrink-0 px-4 pt-3 pb-3 border-b border-neutral-200/70">
|
||||
<div className="flex gap-2 mb-2">
|
||||
|
|
@ -211,7 +221,7 @@ export default function SearchCSDbNG({ config, setConfig, onClose: _onClose }: S
|
|||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && !isSearching && doSearch(query)}
|
||||
placeholder="Search CSDb…"
|
||||
placeholder="Search CSDb-ng…"
|
||||
className="w-full pl-9 pr-3 py-2.5 bg-neutral-100 border-0 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white transition-colors"
|
||||
disabled={isSearching}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Search, Loader2, HardDrive, Download, ChevronRight, ChevronDown, Trophy, Calendar, Users, RefreshCw, HelpCircle } from 'lucide-react';
|
||||
import { Search, Loader2, HardDrive, Download, ChevronRight, ChevronDown, Trophy, Calendar, Users, RefreshCw, HelpCircle, X } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { humanFileSize, basename, joinPath, putFileContents } from '../webdav';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog';
|
||||
|
|
@ -145,7 +145,7 @@ const AQL_TERMS = [
|
|||
|
||||
// ─── Component ────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function SearchCommoServe({ config, setConfig, onClose: _onClose }: SearchCommoServeProps) {
|
||||
export default function SearchCommoServe({ config, setConfig, onClose }: SearchCommoServeProps) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [showAqlHelp, setShowAqlHelp] = useState(false);
|
||||
|
|
@ -307,6 +307,16 @@ export default function SearchCommoServe({ config, setConfig, onClose: _onClose
|
|||
return (
|
||||
<>
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
{/* Panel header */}
|
||||
<div className="flex-shrink-0 flex items-center border-b border-neutral-200/70 px-4">
|
||||
<div className="flex items-center gap-2 flex-1 py-3 min-w-0">
|
||||
<img src={`${import.meta.env.BASE_URL}assets/favicon.cbm.png`} className="w-5 h-5 flex-shrink-0 object-contain" alt="" aria-hidden="true" />
|
||||
<span className="text-sm font-semibold text-neutral-700">CommoServe</span>
|
||||
</div>
|
||||
<button onClick={onClose} className="p-1.5 my-1.5 rounded-lg hover:bg-neutral-100 text-neutral-500 transition-colors" aria-label="Close search">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
{/* Header */}
|
||||
<div className="flex-shrink-0 px-4 pt-3 pb-3 border-b border-neutral-200/70">
|
||||
<div className="flex gap-2 mb-2">
|
||||
|
|
|
|||
|
|
@ -339,32 +339,23 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder }
|
|||
return (
|
||||
<>
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="flex-shrink-0 px-4 pt-3 pb-3 border-b border-neutral-100">
|
||||
<div className="flex items-center justify-end mb-3 gap-1">
|
||||
{/* Panel header */}
|
||||
<div className="flex-shrink-0 flex items-center border-b border-neutral-200/70 px-4">
|
||||
<div className="flex items-center gap-2 flex-1 py-3 min-w-0">
|
||||
<img src={`${import.meta.env.BASE_URL}favicon.ico`} className="w-5 h-5 flex-shrink-0 object-contain" alt="" aria-hidden="true" />
|
||||
<span className="text-sm font-semibold text-neutral-700">Local</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5">
|
||||
{dbPhase === 'ready' && !busy && (
|
||||
<button
|
||||
onClick={handleRefreshDb}
|
||||
className="p-1.5 rounded-lg hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600 transition-colors"
|
||||
title="Reload database"
|
||||
>
|
||||
<button onClick={handleRefreshDb} className="p-1.5 rounded-lg hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600 transition-colors" title="Reload database">
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setShowScanConfirm(true)}
|
||||
disabled={busy}
|
||||
className="p-1.5 rounded-lg hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600 disabled:opacity-40 transition-colors"
|
||||
title="Scan /sd and rebuild database"
|
||||
>
|
||||
<button onClick={() => setShowScanConfirm(true)} disabled={busy} className="p-1.5 rounded-lg hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600 disabled:opacity-40 transition-colors" title="Scan /sd and rebuild database">
|
||||
<FolderSearch className="w-4 h-4" />
|
||||
</button>
|
||||
{hasSearched && (
|
||||
<button
|
||||
onClick={() => setShowFilter(v => !v)}
|
||||
className={`relative p-1.5 rounded-lg transition-colors ${showFilter ? 'bg-blue-100 text-blue-600' : 'hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600'}`}
|
||||
title="Filter & sort"
|
||||
>
|
||||
<button onClick={() => setShowFilter(v => !v)} className={`relative p-1.5 rounded-lg transition-colors ${showFilter ? 'bg-blue-100 text-blue-600' : 'hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600'}`} title="Filter & sort">
|
||||
<SlidersHorizontal className="w-4 h-4" />
|
||||
{activeFilters > 0 && (
|
||||
<span className="absolute -top-0.5 -right-0.5 w-3.5 h-3.5 rounded-full bg-blue-600 text-white text-[9px] flex items-center justify-center font-bold">
|
||||
|
|
@ -373,9 +364,14 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder }
|
|||
)}
|
||||
</button>
|
||||
)}
|
||||
<button onClick={onClose} className="p-1.5 ml-1 rounded-lg hover:bg-neutral-100 text-neutral-500 transition-colors" aria-label="Close search">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search input */}
|
||||
{/* Search input */}
|
||||
<div className="flex-shrink-0 px-4 pt-3 pb-3 border-b border-neutral-100">
|
||||
<div className="flex gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-neutral-400 pointer-events-none" />
|
||||
|
|
@ -439,7 +435,7 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder }
|
|||
{/* Body */}
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="flex-1 overflow-y-auto"
|
||||
className="flex-1 overflow-y-auto overflow-x-hidden"
|
||||
onScroll={e => { _store.scrollTop = (e.currentTarget as HTMLDivElement).scrollTop; }}
|
||||
>
|
||||
{isScanning && scanPhase && (
|
||||
|
|
@ -482,7 +478,7 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder }
|
|||
selected={mountedPaths.has(result.path)}
|
||||
nameSlot={
|
||||
<>
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
<div className="flex items-center gap-1.5 flex-wrap min-w-0">
|
||||
<span className="text-neutral-900 text-sm">{result.name}</span>
|
||||
<TypeBadge type={result.type} isDir={result.isDir} />
|
||||
{result.system && <span className="text-xs px-1.5 py-0.5 rounded bg-indigo-100 text-indigo-700 font-mono">{result.system}</span>}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { X, Loader2, Database } from 'lucide-react';
|
||||
import { Loader2, Database } from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
import SearchLocal from './SearchLocal';
|
||||
import SearchAssembly64 from './SearchAssembly64';
|
||||
|
|
@ -21,15 +21,15 @@ interface SearchPaneProps {
|
|||
onOpenFolder: (path: string) => void;
|
||||
}
|
||||
|
||||
const TABS = ['Local', 'Assembly64', 'CommoServe', 'CSDb'] as const;
|
||||
|
||||
let _lastTab: 0 | 1 | 2 | 3 = (() => {
|
||||
const v = parseInt(localStorage.getItem('search.tab') ?? '0', 10);
|
||||
return (v >= 0 && v <= 3 ? v : 0) as 0 | 1 | 2 | 3;
|
||||
})();
|
||||
|
||||
const PANEL_LABELS = ['Local', 'Assembly64', 'CommoServe', 'CSDb-ng'] as const;
|
||||
|
||||
export default function SearchPane({ config, setConfig, initialTab, onClose, onOpenFolder }: SearchPaneProps) {
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
const [activeTab, setActiveTab] = useState<0 | 1 | 2 | 3>(initialTab ?? _lastTab);
|
||||
|
||||
// Subscribe to the locate-database load pipeline so the user can see the
|
||||
|
|
@ -61,9 +61,7 @@ export default function SearchPane({ config, setConfig, initialTab, onClose, onO
|
|||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const scrollToTab = (idx: 0 | 1 | 2 | 3) => {
|
||||
const el = panelRef.current;
|
||||
if (!el) return;
|
||||
el.scrollTo({ left: idx * el.clientWidth, behavior: 'smooth' });
|
||||
panelRef.current?.scrollTo({ left: idx * (panelRef.current.clientWidth), behavior: 'smooth' });
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
|
|
@ -93,31 +91,6 @@ export default function SearchPane({ config, setConfig, initialTab, onClose, onO
|
|||
transition={{ type: 'spring', damping: 28, stiffness: 280 }}
|
||||
className="fixed inset-0 bg-white/80 backdrop-blur-md flex flex-col overflow-hidden"
|
||||
>
|
||||
{/* Tab bar */}
|
||||
<div className="flex-shrink-0 flex items-center border-b border-neutral-200/70 px-1">
|
||||
{TABS.map((label, i) => (
|
||||
<button
|
||||
key={label}
|
||||
onClick={() => scrollToTab(i as 0 | 1 | 2 | 3)}
|
||||
className={`px-4 py-3 text-sm font-medium transition-colors border-b-2 -mb-px ${
|
||||
activeTab === i
|
||||
? 'text-blue-600 border-blue-600'
|
||||
: 'text-neutral-500 border-transparent hover:text-neutral-700'
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex-1" />
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1.5 mr-2 rounded-lg hover:bg-neutral-100 text-neutral-500 transition-colors"
|
||||
aria-label="Close search"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Locate-database load bar. Visible only while a transfer is in
|
||||
flight; collapses to zero height otherwise so it doesn't take
|
||||
up space when there's nothing to show. */}
|
||||
|
|
@ -200,6 +173,22 @@ export default function SearchPane({ config, setConfig, initialTab, onClose, onO
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Swipe indicator dots */}
|
||||
<div className="flex-shrink-0 flex items-center justify-center gap-2.5 py-2 border-t border-neutral-200/50">
|
||||
{([0, 1, 2, 3] as const).map(i => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => scrollToTab(i)}
|
||||
aria-label={PANEL_LABELS[i]}
|
||||
className={`rounded-full transition-all duration-200 ${
|
||||
activeTab === i
|
||||
? 'w-4 h-2 bg-blue-500'
|
||||
: 'w-2 h-2 bg-neutral-300 hover:bg-neutral-400'
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
||||
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "react-jsx",
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"allowImportingTsExtensions": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user