react-os-shell 0.1.42 → 0.1.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Browser-KMVL3AJ7.js +245 -0
- package/dist/Browser-KMVL3AJ7.js.map +1 -0
- package/dist/Files-PPKWOJWV.js +486 -0
- package/dist/Files-PPKWOJWV.js.map +1 -0
- package/dist/apps/index.d.ts +8 -1
- package/dist/apps/index.js +10 -3
- package/dist/apps/index.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { WindowTitle } from './chunk-44AH2OXU.js';
|
|
2
|
+
import './chunk-RFTLYCSF.js';
|
|
3
|
+
import { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var BOOKMARKS_KEY = "react-os-shell:browser-bookmarks";
|
|
7
|
+
var HOMEPAGE_KEY = "react-os-shell:browser-homepage";
|
|
8
|
+
var DEFAULT_HOMEPAGE = "https://en.wikipedia.org/wiki/Main_Page";
|
|
9
|
+
var DEFAULT_BOOKMARKS = [
|
|
10
|
+
{ label: "Wikipedia", url: "https://en.wikipedia.org/wiki/Main_Page" },
|
|
11
|
+
{ label: "MDN", url: "https://developer.mozilla.org" },
|
|
12
|
+
{ label: "Example", url: "https://example.com" }
|
|
13
|
+
];
|
|
14
|
+
function normalizeUrl(input) {
|
|
15
|
+
let s = input.trim();
|
|
16
|
+
if (!s) return "";
|
|
17
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return s;
|
|
18
|
+
if (/^[\w.-]+\.[a-z]{2,}(\/.*)?$/i.test(s)) return "https://" + s;
|
|
19
|
+
return "https://duckduckgo.com/?q=" + encodeURIComponent(s);
|
|
20
|
+
}
|
|
21
|
+
function loadBookmarks() {
|
|
22
|
+
if (typeof window === "undefined") return DEFAULT_BOOKMARKS;
|
|
23
|
+
try {
|
|
24
|
+
const raw = localStorage.getItem(BOOKMARKS_KEY);
|
|
25
|
+
if (!raw) return DEFAULT_BOOKMARKS;
|
|
26
|
+
const parsed = JSON.parse(raw);
|
|
27
|
+
if (Array.isArray(parsed) && parsed.every((b) => typeof b?.url === "string")) return parsed;
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
return DEFAULT_BOOKMARKS;
|
|
31
|
+
}
|
|
32
|
+
function loadHomepage() {
|
|
33
|
+
if (typeof window === "undefined") return DEFAULT_HOMEPAGE;
|
|
34
|
+
return localStorage.getItem(HOMEPAGE_KEY) || DEFAULT_HOMEPAGE;
|
|
35
|
+
}
|
|
36
|
+
function Browser() {
|
|
37
|
+
const [homepage, setHomepage] = useState(loadHomepage);
|
|
38
|
+
const [url, setUrl] = useState(homepage);
|
|
39
|
+
const [inputUrl, setInputUrl] = useState(url);
|
|
40
|
+
const [history, setHistory] = useState([url]);
|
|
41
|
+
const [historyIdx, setHistoryIdx] = useState(0);
|
|
42
|
+
const [iframeKey, setIframeKey] = useState(0);
|
|
43
|
+
const [bookmarks, setBookmarks] = useState(loadBookmarks);
|
|
44
|
+
const [showHelp, setShowHelp] = useState(false);
|
|
45
|
+
const iframeRef = useRef(null);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
try {
|
|
48
|
+
localStorage.setItem(BOOKMARKS_KEY, JSON.stringify(bookmarks));
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}, [bookmarks]);
|
|
52
|
+
const navigate = (next) => {
|
|
53
|
+
const n = normalizeUrl(next);
|
|
54
|
+
if (!n) return;
|
|
55
|
+
setUrl(n);
|
|
56
|
+
setInputUrl(n);
|
|
57
|
+
setHistory((h) => {
|
|
58
|
+
const trimmed = h.slice(0, historyIdx + 1);
|
|
59
|
+
trimmed.push(n);
|
|
60
|
+
return trimmed;
|
|
61
|
+
});
|
|
62
|
+
setHistoryIdx((i) => i + 1);
|
|
63
|
+
setShowHelp(false);
|
|
64
|
+
};
|
|
65
|
+
const back = () => {
|
|
66
|
+
if (historyIdx > 0) {
|
|
67
|
+
const next = historyIdx - 1;
|
|
68
|
+
setHistoryIdx(next);
|
|
69
|
+
setUrl(history[next]);
|
|
70
|
+
setInputUrl(history[next]);
|
|
71
|
+
setShowHelp(false);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const forward = () => {
|
|
75
|
+
if (historyIdx < history.length - 1) {
|
|
76
|
+
const next = historyIdx + 1;
|
|
77
|
+
setHistoryIdx(next);
|
|
78
|
+
setUrl(history[next]);
|
|
79
|
+
setInputUrl(history[next]);
|
|
80
|
+
setShowHelp(false);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const refresh = () => setIframeKey((k) => k + 1);
|
|
84
|
+
const goHome = () => navigate(homepage);
|
|
85
|
+
const openExternal = () => {
|
|
86
|
+
if (typeof window !== "undefined") window.open(url, "_blank", "noopener,noreferrer");
|
|
87
|
+
};
|
|
88
|
+
const addBookmark = () => {
|
|
89
|
+
const label = window.prompt("Bookmark name", titleFromUrl(url));
|
|
90
|
+
if (!label) return;
|
|
91
|
+
setBookmarks((b) => [...b, { label, url }]);
|
|
92
|
+
};
|
|
93
|
+
const removeBookmark = (i) => {
|
|
94
|
+
setBookmarks((b) => b.filter((_, idx) => idx !== i));
|
|
95
|
+
};
|
|
96
|
+
const setAsHomepage = () => {
|
|
97
|
+
setHomepage(url);
|
|
98
|
+
try {
|
|
99
|
+
localStorage.setItem(HOMEPAGE_KEY, url);
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const onSubmitUrl = (e) => {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
navigate(inputUrl);
|
|
106
|
+
};
|
|
107
|
+
const canBack = historyIdx > 0;
|
|
108
|
+
const canForward = historyIdx < history.length - 1;
|
|
109
|
+
const isBookmarked = bookmarks.some((b) => b.url === url);
|
|
110
|
+
const btn = "p-1.5 rounded hover:bg-gray-200 transition-colors text-gray-600 disabled:opacity-30";
|
|
111
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full bg-white", children: [
|
|
112
|
+
/* @__PURE__ */ jsx(WindowTitle, { title: `Browser - ${titleFromUrl(url)}` }),
|
|
113
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 px-2 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0", children: [
|
|
114
|
+
/* @__PURE__ */ jsx("button", { onClick: back, disabled: !canBack, className: btn, title: "Back", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15.75 19.5L8.25 12l7.5-7.5" }) }) }),
|
|
115
|
+
/* @__PURE__ */ jsx("button", { onClick: forward, disabled: !canForward, className: btn, title: "Forward", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) }) }),
|
|
116
|
+
/* @__PURE__ */ jsx("button", { onClick: refresh, className: btn, title: "Refresh", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.023 9.348h4.992V4.356M2.985 19.644v-4.992h4.992M3.05 9.348a9 9 0 0114.85-3.36L21.015 9.348m0 5.304a9 9 0 01-14.85 3.36l-3.115-3.36" }) }) }),
|
|
117
|
+
/* @__PURE__ */ jsx("button", { onClick: goHome, className: btn, title: "Home", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M2.25 12l8.954-8.955a1.5 1.5 0 012.122 0l8.954 8.955M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" }) }) }),
|
|
118
|
+
/* @__PURE__ */ jsx("form", { onSubmit: onSubmitUrl, className: "flex-1 flex items-center mx-1", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center w-full bg-white border border-gray-300 rounded focus-within:border-blue-400 focus-within:ring-1 focus-within:ring-blue-200", children: [
|
|
119
|
+
/* @__PURE__ */ jsx("span", { className: "px-2 text-gray-400 text-xs", children: url.startsWith("https://") ? "\u{1F512}" : "\u26A0\uFE0F" }),
|
|
120
|
+
/* @__PURE__ */ jsx(
|
|
121
|
+
"input",
|
|
122
|
+
{
|
|
123
|
+
type: "text",
|
|
124
|
+
value: inputUrl,
|
|
125
|
+
onChange: (e) => setInputUrl(e.target.value),
|
|
126
|
+
onFocus: (e) => e.target.select(),
|
|
127
|
+
className: "flex-1 px-1 py-1 text-sm bg-transparent outline-none font-mono",
|
|
128
|
+
spellCheck: false,
|
|
129
|
+
placeholder: "Enter URL or search\u2026"
|
|
130
|
+
}
|
|
131
|
+
),
|
|
132
|
+
url !== inputUrl && /* @__PURE__ */ jsx(
|
|
133
|
+
"button",
|
|
134
|
+
{
|
|
135
|
+
type: "button",
|
|
136
|
+
onClick: () => setInputUrl(url),
|
|
137
|
+
className: "px-1.5 text-[10px] text-gray-400 hover:text-gray-700",
|
|
138
|
+
title: "Reset to current URL",
|
|
139
|
+
children: "\xD7"
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
] }) }),
|
|
143
|
+
/* @__PURE__ */ jsx(
|
|
144
|
+
"button",
|
|
145
|
+
{
|
|
146
|
+
onClick: () => isBookmarked ? removeBookmark(bookmarks.findIndex((b) => b.url === url)) : addBookmark(),
|
|
147
|
+
className: btn + " " + (isBookmarked ? "text-yellow-500" : ""),
|
|
148
|
+
title: isBookmarked ? "Remove bookmark" : "Add bookmark",
|
|
149
|
+
children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: isBookmarked ? "currentColor" : "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" }) })
|
|
150
|
+
}
|
|
151
|
+
),
|
|
152
|
+
/* @__PURE__ */ jsx("button", { onClick: openExternal, className: btn, title: "Open in new tab", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-7.5-9L21 3m0 0v6m0-6L9.75 14.25" }) }) }),
|
|
153
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setShowHelp((s) => !s), className: btn, title: "Embedding help", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" }) }) })
|
|
154
|
+
] }),
|
|
155
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 px-2 py-1 border-b border-gray-200 bg-white shrink-0 overflow-x-auto", children: [
|
|
156
|
+
bookmarks.length === 0 && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-gray-400 italic px-2", children: "No bookmarks yet \u2014 star the address bar to add one." }),
|
|
157
|
+
bookmarks.map((b, i) => /* @__PURE__ */ jsxs(
|
|
158
|
+
"button",
|
|
159
|
+
{
|
|
160
|
+
onClick: () => navigate(b.url),
|
|
161
|
+
onContextMenu: (e) => {
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
if (window.confirm(`Remove bookmark "${b.label}"?`)) removeBookmark(i);
|
|
164
|
+
},
|
|
165
|
+
className: "flex items-center gap-1.5 px-2 py-0.5 rounded text-[12px] text-gray-700 hover:bg-gray-100 whitespace-nowrap",
|
|
166
|
+
title: `${b.url}
|
|
167
|
+
(right-click to remove)`,
|
|
168
|
+
children: [
|
|
169
|
+
/* @__PURE__ */ jsx(
|
|
170
|
+
"img",
|
|
171
|
+
{
|
|
172
|
+
src: `https://www.google.com/s2/favicons?domain=${new URL(b.url).hostname}&sz=16`,
|
|
173
|
+
alt: "",
|
|
174
|
+
className: "h-3.5 w-3.5",
|
|
175
|
+
onError: (e) => {
|
|
176
|
+
e.target.style.visibility = "hidden";
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
),
|
|
180
|
+
b.label
|
|
181
|
+
]
|
|
182
|
+
},
|
|
183
|
+
i
|
|
184
|
+
)),
|
|
185
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
186
|
+
/* @__PURE__ */ jsx(
|
|
187
|
+
"button",
|
|
188
|
+
{
|
|
189
|
+
onClick: setAsHomepage,
|
|
190
|
+
className: "px-2 py-0.5 text-[11px] text-gray-500 hover:text-gray-800",
|
|
191
|
+
title: "Set current page as homepage",
|
|
192
|
+
children: "Set as home"
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
] }),
|
|
196
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 relative min-h-0 bg-gray-50", children: [
|
|
197
|
+
/* @__PURE__ */ jsx(
|
|
198
|
+
"iframe",
|
|
199
|
+
{
|
|
200
|
+
ref: iframeRef,
|
|
201
|
+
src: url,
|
|
202
|
+
className: "absolute inset-0 w-full h-full bg-white",
|
|
203
|
+
sandbox: "allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals",
|
|
204
|
+
referrerPolicy: "no-referrer-when-downgrade"
|
|
205
|
+
},
|
|
206
|
+
iframeKey + url
|
|
207
|
+
),
|
|
208
|
+
showHelp && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 right-2 max-w-sm bg-white border border-gray-200 rounded-md shadow-lg p-3 text-xs text-gray-700 z-10", children: [
|
|
209
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-gray-900 mb-1", children: "Why is this page blank?" }),
|
|
210
|
+
/* @__PURE__ */ jsxs("p", { className: "mb-2", children: [
|
|
211
|
+
"Most major sites (Google, GitHub, banks, news) refuse to be embedded in an iframe via ",
|
|
212
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "X-Frame-Options" }),
|
|
213
|
+
" or Content Security Policy. There's no workaround \u2014 the browser blocks the load before our app can do anything."
|
|
214
|
+
] }),
|
|
215
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
216
|
+
"Hit the ",
|
|
217
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "\u2197" }),
|
|
218
|
+
" button to open the page in a real new tab. Sites that ",
|
|
219
|
+
/* @__PURE__ */ jsx("em", { children: "do" }),
|
|
220
|
+
" allow embedding (Wikipedia, MDN, docs sites, your own apps) work fine in here."
|
|
221
|
+
] }),
|
|
222
|
+
/* @__PURE__ */ jsx(
|
|
223
|
+
"button",
|
|
224
|
+
{
|
|
225
|
+
onClick: () => setShowHelp(false),
|
|
226
|
+
className: "mt-2 text-[11px] text-blue-600 hover:underline",
|
|
227
|
+
children: "Got it"
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
] })
|
|
231
|
+
] })
|
|
232
|
+
] });
|
|
233
|
+
}
|
|
234
|
+
function titleFromUrl(u) {
|
|
235
|
+
try {
|
|
236
|
+
const url = new URL(u);
|
|
237
|
+
return url.hostname.replace(/^www\./, "");
|
|
238
|
+
} catch {
|
|
239
|
+
return u;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export { Browser as default };
|
|
244
|
+
//# sourceMappingURL=Browser-KMVL3AJ7.js.map
|
|
245
|
+
//# sourceMappingURL=Browser-KMVL3AJ7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/apps/Browser.tsx"],"names":[],"mappings":"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","file":"Browser-KMVL3AJ7.js","sourcesContent":["/**\n * Browser — minimal iframe-backed web browser app.\n *\n * URL bar with back / forward / refresh, navigable bookmark bar\n * (persisted to localStorage), and a graceful \"open in new tab\"\n * escape hatch since most major sites refuse iframe embedding via\n * X-Frame-Options or Content-Security-Policy.\n */\nimport { useEffect, useRef, useState } from 'react';\nimport { WindowTitle } from '../shell/Modal';\n\ninterface Bookmark {\n label: string;\n url: string;\n}\n\nconst BOOKMARKS_KEY = 'react-os-shell:browser-bookmarks';\nconst HOMEPAGE_KEY = 'react-os-shell:browser-homepage';\nconst DEFAULT_HOMEPAGE = 'https://en.wikipedia.org/wiki/Main_Page';\nconst DEFAULT_BOOKMARKS: Bookmark[] = [\n { label: 'Wikipedia', url: 'https://en.wikipedia.org/wiki/Main_Page' },\n { label: 'MDN', url: 'https://developer.mozilla.org' },\n { label: 'Example', url: 'https://example.com' },\n];\n\nfunction normalizeUrl(input: string): string {\n let s = input.trim();\n if (!s) return '';\n // Already a URL? Otherwise treat as a search query (DuckDuckGo, no tracking).\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(s)) return s;\n if (/^[\\w.-]+\\.[a-z]{2,}(\\/.*)?$/i.test(s)) return 'https://' + s;\n return 'https://duckduckgo.com/?q=' + encodeURIComponent(s);\n}\n\nfunction loadBookmarks(): Bookmark[] {\n if (typeof window === 'undefined') return DEFAULT_BOOKMARKS;\n try {\n const raw = localStorage.getItem(BOOKMARKS_KEY);\n if (!raw) return DEFAULT_BOOKMARKS;\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed) && parsed.every(b => typeof b?.url === 'string')) return parsed;\n } catch {}\n return DEFAULT_BOOKMARKS;\n}\n\nfunction loadHomepage(): string {\n if (typeof window === 'undefined') return DEFAULT_HOMEPAGE;\n return localStorage.getItem(HOMEPAGE_KEY) || DEFAULT_HOMEPAGE;\n}\n\nexport default function Browser() {\n const [homepage, setHomepage] = useState(loadHomepage);\n const [url, setUrl] = useState(homepage);\n const [inputUrl, setInputUrl] = useState(url);\n const [history, setHistory] = useState<string[]>([url]);\n const [historyIdx, setHistoryIdx] = useState(0);\n const [iframeKey, setIframeKey] = useState(0);\n const [bookmarks, setBookmarks] = useState<Bookmark[]>(loadBookmarks);\n const [showHelp, setShowHelp] = useState(false);\n const iframeRef = useRef<HTMLIFrameElement>(null);\n\n // Persist bookmarks.\n useEffect(() => {\n try { localStorage.setItem(BOOKMARKS_KEY, JSON.stringify(bookmarks)); } catch {}\n }, [bookmarks]);\n\n const navigate = (next: string) => {\n const n = normalizeUrl(next);\n if (!n) return;\n setUrl(n);\n setInputUrl(n);\n // Truncate forward history when navigating from a back state.\n setHistory(h => {\n const trimmed = h.slice(0, historyIdx + 1);\n trimmed.push(n);\n return trimmed;\n });\n setHistoryIdx(i => i + 1);\n setShowHelp(false);\n };\n\n const back = () => {\n if (historyIdx > 0) {\n const next = historyIdx - 1;\n setHistoryIdx(next);\n setUrl(history[next]);\n setInputUrl(history[next]);\n setShowHelp(false);\n }\n };\n\n const forward = () => {\n if (historyIdx < history.length - 1) {\n const next = historyIdx + 1;\n setHistoryIdx(next);\n setUrl(history[next]);\n setInputUrl(history[next]);\n setShowHelp(false);\n }\n };\n\n const refresh = () => setIframeKey(k => k + 1);\n\n const goHome = () => navigate(homepage);\n\n const openExternal = () => {\n if (typeof window !== 'undefined') window.open(url, '_blank', 'noopener,noreferrer');\n };\n\n const addBookmark = () => {\n const label = window.prompt('Bookmark name', titleFromUrl(url));\n if (!label) return;\n setBookmarks(b => [...b, { label, url }]);\n };\n\n const removeBookmark = (i: number) => {\n setBookmarks(b => b.filter((_, idx) => idx !== i));\n };\n\n const setAsHomepage = () => {\n setHomepage(url);\n try { localStorage.setItem(HOMEPAGE_KEY, url); } catch {}\n };\n\n const onSubmitUrl = (e: React.FormEvent) => {\n e.preventDefault();\n navigate(inputUrl);\n };\n\n const canBack = historyIdx > 0;\n const canForward = historyIdx < history.length - 1;\n const isBookmarked = bookmarks.some(b => b.url === url);\n const btn = 'p-1.5 rounded hover:bg-gray-200 transition-colors text-gray-600 disabled:opacity-30';\n\n return (\n <div className=\"flex flex-col h-full bg-white\">\n <WindowTitle title={`Browser - ${titleFromUrl(url)}`} />\n\n {/* Top toolbar */}\n <div className=\"flex items-center gap-1 px-2 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0\">\n <button onClick={back} disabled={!canBack} className={btn} title=\"Back\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" /></svg>\n </button>\n <button onClick={forward} disabled={!canForward} className={btn} title=\"Forward\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" /></svg>\n </button>\n <button onClick={refresh} className={btn} title=\"Refresh\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.8}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M16.023 9.348h4.992V4.356M2.985 19.644v-4.992h4.992M3.05 9.348a9 9 0 0114.85-3.36L21.015 9.348m0 5.304a9 9 0 01-14.85 3.36l-3.115-3.36\" /></svg>\n </button>\n <button onClick={goHome} className={btn} title=\"Home\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.8}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M2.25 12l8.954-8.955a1.5 1.5 0 012.122 0l8.954 8.955M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25\" /></svg>\n </button>\n\n <form onSubmit={onSubmitUrl} className=\"flex-1 flex items-center mx-1\">\n <div className=\"flex items-center w-full bg-white border border-gray-300 rounded focus-within:border-blue-400 focus-within:ring-1 focus-within:ring-blue-200\">\n <span className=\"px-2 text-gray-400 text-xs\">{url.startsWith('https://') ? '🔒' : '⚠️'}</span>\n <input\n type=\"text\"\n value={inputUrl}\n onChange={(e) => setInputUrl(e.target.value)}\n onFocus={(e) => e.target.select()}\n className=\"flex-1 px-1 py-1 text-sm bg-transparent outline-none font-mono\"\n spellCheck={false}\n placeholder=\"Enter URL or search…\"\n />\n {url !== inputUrl && (\n <button\n type=\"button\"\n onClick={() => setInputUrl(url)}\n className=\"px-1.5 text-[10px] text-gray-400 hover:text-gray-700\"\n title=\"Reset to current URL\"\n >×</button>\n )}\n </div>\n </form>\n\n <button\n onClick={() => isBookmarked ? removeBookmark(bookmarks.findIndex(b => b.url === url)) : addBookmark()}\n className={btn + ' ' + (isBookmarked ? 'text-yellow-500' : '')}\n title={isBookmarked ? 'Remove bookmark' : 'Add bookmark'}\n >\n <svg className=\"h-4 w-4\" fill={isBookmarked ? 'currentColor' : 'none'} viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z\" />\n </svg>\n </button>\n <button onClick={openExternal} className={btn} title=\"Open in new tab\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-7.5-9L21 3m0 0v6m0-6L9.75 14.25\" /></svg>\n </button>\n <button onClick={() => setShowHelp(s => !s)} className={btn} title=\"Embedding help\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z\" /></svg>\n </button>\n </div>\n\n {/* Bookmarks bar */}\n <div className=\"flex items-center gap-0.5 px-2 py-1 border-b border-gray-200 bg-white shrink-0 overflow-x-auto\">\n {bookmarks.length === 0 && (\n <span className=\"text-[11px] text-gray-400 italic px-2\">No bookmarks yet — star the address bar to add one.</span>\n )}\n {bookmarks.map((b, i) => (\n <button\n key={i}\n onClick={() => navigate(b.url)}\n onContextMenu={(e) => {\n e.preventDefault();\n if (window.confirm(`Remove bookmark \"${b.label}\"?`)) removeBookmark(i);\n }}\n className=\"flex items-center gap-1.5 px-2 py-0.5 rounded text-[12px] text-gray-700 hover:bg-gray-100 whitespace-nowrap\"\n title={`${b.url}\\n(right-click to remove)`}\n >\n <img\n src={`https://www.google.com/s2/favicons?domain=${new URL(b.url).hostname}&sz=16`}\n alt=\"\"\n className=\"h-3.5 w-3.5\"\n onError={(e) => { (e.target as HTMLImageElement).style.visibility = 'hidden'; }}\n />\n {b.label}\n </button>\n ))}\n <div className=\"flex-1\" />\n <button\n onClick={setAsHomepage}\n className=\"px-2 py-0.5 text-[11px] text-gray-500 hover:text-gray-800\"\n title=\"Set current page as homepage\"\n >Set as home</button>\n </div>\n\n {/* Iframe area */}\n <div className=\"flex-1 relative min-h-0 bg-gray-50\">\n <iframe\n key={iframeKey + url}\n ref={iframeRef}\n src={url}\n className=\"absolute inset-0 w-full h-full bg-white\"\n // Sandboxing keeps embedded pages from messing with the parent\n // window state. allow-same-origin lets sites that *do* allow\n // embedding actually behave normally; allow-scripts is needed\n // for any modern site.\n sandbox=\"allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals\"\n referrerPolicy=\"no-referrer-when-downgrade\"\n />\n\n {showHelp && (\n <div className=\"absolute top-2 right-2 max-w-sm bg-white border border-gray-200 rounded-md shadow-lg p-3 text-xs text-gray-700 z-10\">\n <div className=\"font-medium text-gray-900 mb-1\">Why is this page blank?</div>\n <p className=\"mb-2\">\n Most major sites (Google, GitHub, banks, news) refuse to be embedded in\n an iframe via <span className=\"font-mono\">X-Frame-Options</span> or\n Content Security Policy. There's no workaround — the browser blocks the\n load before our app can do anything.\n </p>\n <p>\n Hit the <span className=\"font-medium\">↗</span> button to open the page in\n a real new tab. Sites that <em>do</em> allow embedding (Wikipedia, MDN,\n docs sites, your own apps) work fine in here.\n </p>\n <button\n onClick={() => setShowHelp(false)}\n className=\"mt-2 text-[11px] text-blue-600 hover:underline\"\n >Got it</button>\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction titleFromUrl(u: string): string {\n try {\n const url = new URL(u);\n return url.hostname.replace(/^www\\./, '');\n } catch {\n return u;\n }\n}\n"]}
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import { setPdfPreview } from './chunk-IM3T7WV2.js';
|
|
2
|
+
import { toast_default } from './chunk-WIJ45SYD.js';
|
|
3
|
+
import { useWindowManager, WindowTitle } from './chunk-44AH2OXU.js';
|
|
4
|
+
import './chunk-RFTLYCSF.js';
|
|
5
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
6
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
var DEFAULT_SERVER = typeof window !== "undefined" && window.__REACT_OS_SHELL_FILE_SERVER__ || "http://localhost:4000";
|
|
9
|
+
var URL_KEY = "react-os-shell:file-server-url";
|
|
10
|
+
var TOKEN_KEY = "react-os-shell:file-server-token";
|
|
11
|
+
var PREVIEW_EXTS = {
|
|
12
|
+
pdf: "pdf",
|
|
13
|
+
dxf: "dxf",
|
|
14
|
+
jpg: "image",
|
|
15
|
+
jpeg: "image",
|
|
16
|
+
png: "image",
|
|
17
|
+
gif: "image",
|
|
18
|
+
webp: "image",
|
|
19
|
+
svg: "image",
|
|
20
|
+
avif: "image",
|
|
21
|
+
bmp: "image",
|
|
22
|
+
stp: "3d",
|
|
23
|
+
step: "3d",
|
|
24
|
+
stl: "3d",
|
|
25
|
+
obj: "3d",
|
|
26
|
+
gltf: "3d",
|
|
27
|
+
glb: "3d",
|
|
28
|
+
"3mf": "3d",
|
|
29
|
+
iges: "3d",
|
|
30
|
+
igs: "3d",
|
|
31
|
+
ply: "3d",
|
|
32
|
+
fbx: "3d"
|
|
33
|
+
};
|
|
34
|
+
function joinPath(parent, name) {
|
|
35
|
+
if (parent === "/" || parent === "") return "/" + name;
|
|
36
|
+
return parent.replace(/\/$/, "") + "/" + name;
|
|
37
|
+
}
|
|
38
|
+
function parentOf(p) {
|
|
39
|
+
if (p === "/" || p === "") return "/";
|
|
40
|
+
const trimmed = p.replace(/\/$/, "");
|
|
41
|
+
const idx = trimmed.lastIndexOf("/");
|
|
42
|
+
return idx <= 0 ? "/" : trimmed.slice(0, idx);
|
|
43
|
+
}
|
|
44
|
+
function formatSize(bytes) {
|
|
45
|
+
if (!bytes) return "\u2014";
|
|
46
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
47
|
+
let v = bytes, i = 0;
|
|
48
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
49
|
+
v /= 1024;
|
|
50
|
+
i++;
|
|
51
|
+
}
|
|
52
|
+
return `${v < 10 ? v.toFixed(1) : Math.round(v)} ${units[i]}`;
|
|
53
|
+
}
|
|
54
|
+
function formatTime(iso) {
|
|
55
|
+
try {
|
|
56
|
+
const d = new Date(iso);
|
|
57
|
+
return d.toLocaleString(void 0, { dateStyle: "short", timeStyle: "short" });
|
|
58
|
+
} catch {
|
|
59
|
+
return iso;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function Files() {
|
|
63
|
+
const [server, setServer] = useState(() => {
|
|
64
|
+
if (typeof window === "undefined") return DEFAULT_SERVER;
|
|
65
|
+
return localStorage.getItem(URL_KEY) || DEFAULT_SERVER;
|
|
66
|
+
});
|
|
67
|
+
const [token, setToken] = useState(() => {
|
|
68
|
+
if (typeof window === "undefined") return "";
|
|
69
|
+
return localStorage.getItem(TOKEN_KEY) || "";
|
|
70
|
+
});
|
|
71
|
+
const [user, setUser] = useState(null);
|
|
72
|
+
const [path, setPath] = useState("/");
|
|
73
|
+
const [entries, setEntries] = useState([]);
|
|
74
|
+
const [selected, setSelected] = useState(null);
|
|
75
|
+
const [loading, setLoading] = useState(false);
|
|
76
|
+
const [authError, setAuthError] = useState(null);
|
|
77
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
78
|
+
const dragDepthRef = useRef(0);
|
|
79
|
+
const fileRef = useRef(null);
|
|
80
|
+
const { openPage } = useWindowManager();
|
|
81
|
+
const authedFetch = useCallback(
|
|
82
|
+
(url, init = {}) => {
|
|
83
|
+
const headers = {
|
|
84
|
+
Authorization: `Bearer ${token}`,
|
|
85
|
+
...init.headers || {}
|
|
86
|
+
};
|
|
87
|
+
return fetch(url, { ...init, headers });
|
|
88
|
+
},
|
|
89
|
+
[token]
|
|
90
|
+
);
|
|
91
|
+
const signIn = async () => {
|
|
92
|
+
setAuthError(null);
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch(`${server.replace(/\/$/, "")}/api/me`, {
|
|
95
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
96
|
+
});
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
setAuthError(res.status === 401 ? "Invalid token" : `Server error ${res.status}`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const data = await res.json();
|
|
102
|
+
setUser(data.user);
|
|
103
|
+
localStorage.setItem(URL_KEY, server);
|
|
104
|
+
localStorage.setItem(TOKEN_KEY, token);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
setAuthError(e?.message || "Could not reach server");
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const signOut = () => {
|
|
110
|
+
setUser(null);
|
|
111
|
+
setEntries([]);
|
|
112
|
+
setPath("/");
|
|
113
|
+
setSelected(null);
|
|
114
|
+
localStorage.removeItem(TOKEN_KEY);
|
|
115
|
+
};
|
|
116
|
+
const loadDir = useCallback(async (dir) => {
|
|
117
|
+
setLoading(true);
|
|
118
|
+
setSelected(null);
|
|
119
|
+
try {
|
|
120
|
+
const res = await authedFetch(
|
|
121
|
+
`${server.replace(/\/$/, "")}/api/files?path=${encodeURIComponent(dir)}`
|
|
122
|
+
);
|
|
123
|
+
if (!res.ok) {
|
|
124
|
+
if (res.status === 401) {
|
|
125
|
+
setUser(null);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const msg = await res.json().catch(() => ({}));
|
|
129
|
+
toast_default.error(msg.error || `Failed to list (${res.status})`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const data = await res.json();
|
|
133
|
+
setEntries(data.entries || []);
|
|
134
|
+
setPath(data.path || dir);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
toast_default.error(e?.message || "Could not reach server");
|
|
137
|
+
} finally {
|
|
138
|
+
setLoading(false);
|
|
139
|
+
}
|
|
140
|
+
}, [authedFetch, server]);
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (token && !user && !authError) signIn();
|
|
143
|
+
}, []);
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
if (user) loadDir(path);
|
|
146
|
+
}, [user, path]);
|
|
147
|
+
const openFile = async (entry) => {
|
|
148
|
+
const fullPath = joinPath(path, entry.name);
|
|
149
|
+
const ext = (entry.name.split(".").pop() || "").toLowerCase();
|
|
150
|
+
const kind = PREVIEW_EXTS[ext];
|
|
151
|
+
if (!kind) {
|
|
152
|
+
downloadFile(entry);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const res = await authedFetch(
|
|
157
|
+
`${server.replace(/\/$/, "")}/api/file?path=${encodeURIComponent(fullPath)}`
|
|
158
|
+
);
|
|
159
|
+
if (!res.ok) {
|
|
160
|
+
toast_default.error(`Download failed (${res.status})`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const blob = await res.blob();
|
|
164
|
+
const url = URL.createObjectURL(blob);
|
|
165
|
+
setPdfPreview({ url, filename: entry.name, kind });
|
|
166
|
+
openPage("/preview");
|
|
167
|
+
} catch (e) {
|
|
168
|
+
toast_default.error(e?.message || "Open failed");
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const downloadFile = async (entry) => {
|
|
172
|
+
const fullPath = joinPath(path, entry.name);
|
|
173
|
+
try {
|
|
174
|
+
const res = await authedFetch(
|
|
175
|
+
`${server.replace(/\/$/, "")}/api/file?path=${encodeURIComponent(fullPath)}`
|
|
176
|
+
);
|
|
177
|
+
if (!res.ok) {
|
|
178
|
+
toast_default.error(`Download failed (${res.status})`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const blob = await res.blob();
|
|
182
|
+
const url = URL.createObjectURL(blob);
|
|
183
|
+
const a = document.createElement("a");
|
|
184
|
+
a.href = url;
|
|
185
|
+
a.download = entry.name;
|
|
186
|
+
a.click();
|
|
187
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
188
|
+
} catch (e) {
|
|
189
|
+
toast_default.error(e?.message || "Download failed");
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const handlePick = () => fileRef.current?.click();
|
|
193
|
+
const uploadFiles = async (files) => {
|
|
194
|
+
const arr = Array.from(files);
|
|
195
|
+
for (const file of arr) {
|
|
196
|
+
const form = new FormData();
|
|
197
|
+
form.append("file", file);
|
|
198
|
+
try {
|
|
199
|
+
const res = await authedFetch(
|
|
200
|
+
`${server.replace(/\/$/, "")}/api/upload?path=${encodeURIComponent(path)}`,
|
|
201
|
+
{ method: "POST", body: form }
|
|
202
|
+
);
|
|
203
|
+
if (!res.ok) {
|
|
204
|
+
const msg = await res.json().catch(() => ({}));
|
|
205
|
+
toast_default.error(`Upload ${file.name}: ${msg.error || res.status}`);
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
toast_default.error(`Upload ${file.name}: ${e?.message || "failed"}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
loadDir(path);
|
|
212
|
+
};
|
|
213
|
+
const handleNewFolder = async () => {
|
|
214
|
+
const name = window.prompt("Folder name");
|
|
215
|
+
if (!name) return;
|
|
216
|
+
if (/[\\/]/.test(name)) {
|
|
217
|
+
toast_default.error("Folder names cannot contain slashes");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const target = joinPath(path, name);
|
|
221
|
+
const res = await authedFetch(`${server.replace(/\/$/, "")}/api/folder`, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: { "Content-Type": "application/json" },
|
|
224
|
+
body: JSON.stringify({ path: target })
|
|
225
|
+
});
|
|
226
|
+
if (!res.ok) {
|
|
227
|
+
const msg = await res.json().catch(() => ({}));
|
|
228
|
+
toast_default.error(msg.error || `Create folder failed (${res.status})`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
loadDir(path);
|
|
232
|
+
};
|
|
233
|
+
const handleRename = async (entry) => {
|
|
234
|
+
const next = window.prompt("New name", entry.name);
|
|
235
|
+
if (!next || next === entry.name) return;
|
|
236
|
+
if (/[\\/]/.test(next)) {
|
|
237
|
+
toast_default.error("Names cannot contain slashes");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const from = joinPath(path, entry.name);
|
|
241
|
+
const to = joinPath(path, next);
|
|
242
|
+
const res = await authedFetch(`${server.replace(/\/$/, "")}/api/rename`, {
|
|
243
|
+
method: "POST",
|
|
244
|
+
headers: { "Content-Type": "application/json" },
|
|
245
|
+
body: JSON.stringify({ from, to })
|
|
246
|
+
});
|
|
247
|
+
if (!res.ok) {
|
|
248
|
+
const msg = await res.json().catch(() => ({}));
|
|
249
|
+
toast_default.error(msg.error || `Rename failed (${res.status})`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
loadDir(path);
|
|
253
|
+
};
|
|
254
|
+
const handleDelete = async (entry) => {
|
|
255
|
+
if (!window.confirm(`Delete "${entry.name}"${entry.kind === "folder" ? " and everything inside" : ""}?`)) return;
|
|
256
|
+
const target = joinPath(path, entry.name);
|
|
257
|
+
const res = await authedFetch(
|
|
258
|
+
`${server.replace(/\/$/, "")}/api/files?path=${encodeURIComponent(target)}`,
|
|
259
|
+
{ method: "DELETE" }
|
|
260
|
+
);
|
|
261
|
+
if (!res.ok) {
|
|
262
|
+
const msg = await res.json().catch(() => ({}));
|
|
263
|
+
toast_default.error(msg.error || `Delete failed (${res.status})`);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
loadDir(path);
|
|
267
|
+
};
|
|
268
|
+
const resetDrag = () => {
|
|
269
|
+
dragDepthRef.current = 0;
|
|
270
|
+
setIsDragging(false);
|
|
271
|
+
};
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
const reset = () => resetDrag();
|
|
274
|
+
window.addEventListener("dragend", reset);
|
|
275
|
+
window.addEventListener("drop", reset);
|
|
276
|
+
return () => {
|
|
277
|
+
window.removeEventListener("dragend", reset);
|
|
278
|
+
window.removeEventListener("drop", reset);
|
|
279
|
+
};
|
|
280
|
+
}, []);
|
|
281
|
+
if (!user) {
|
|
282
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full bg-white", children: [
|
|
283
|
+
/* @__PURE__ */ jsx(WindowTitle, { title: "Files - Sign in" }),
|
|
284
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center p-6", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm space-y-3", children: [
|
|
285
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-gray-800", children: "Connect to file server" }),
|
|
286
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500", children: [
|
|
287
|
+
"Paste your bearer token from ",
|
|
288
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "users.json" }),
|
|
289
|
+
". See ",
|
|
290
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "examples/file-server" }),
|
|
291
|
+
" for setup."
|
|
292
|
+
] }),
|
|
293
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
294
|
+
/* @__PURE__ */ jsx("span", { className: "block text-[11px] uppercase tracking-wide text-gray-500 mb-1", children: "Server URL" }),
|
|
295
|
+
/* @__PURE__ */ jsx(
|
|
296
|
+
"input",
|
|
297
|
+
{
|
|
298
|
+
type: "text",
|
|
299
|
+
value: server,
|
|
300
|
+
onChange: (e) => setServer(e.target.value),
|
|
301
|
+
className: "w-full text-sm px-2 py-1.5 border border-gray-300 rounded font-mono"
|
|
302
|
+
}
|
|
303
|
+
)
|
|
304
|
+
] }),
|
|
305
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
306
|
+
/* @__PURE__ */ jsx("span", { className: "block text-[11px] uppercase tracking-wide text-gray-500 mb-1", children: "Bearer token" }),
|
|
307
|
+
/* @__PURE__ */ jsx(
|
|
308
|
+
"input",
|
|
309
|
+
{
|
|
310
|
+
type: "password",
|
|
311
|
+
value: token,
|
|
312
|
+
onChange: (e) => setToken(e.target.value),
|
|
313
|
+
onKeyDown: (e) => {
|
|
314
|
+
if (e.key === "Enter") signIn();
|
|
315
|
+
},
|
|
316
|
+
className: "w-full text-sm px-2 py-1.5 border border-gray-300 rounded font-mono",
|
|
317
|
+
placeholder: "paste token\u2026"
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
] }),
|
|
321
|
+
authError && /* @__PURE__ */ jsx("div", { className: "text-xs text-red-600", children: authError }),
|
|
322
|
+
/* @__PURE__ */ jsx(
|
|
323
|
+
"button",
|
|
324
|
+
{
|
|
325
|
+
onClick: signIn,
|
|
326
|
+
disabled: !token,
|
|
327
|
+
className: "w-full px-3 py-1.5 text-sm bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-40",
|
|
328
|
+
children: "Connect"
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
] }) })
|
|
332
|
+
] });
|
|
333
|
+
}
|
|
334
|
+
const segments = path === "/" ? [] : path.split("/").filter(Boolean);
|
|
335
|
+
return /* @__PURE__ */ jsxs(
|
|
336
|
+
"div",
|
|
337
|
+
{
|
|
338
|
+
className: "relative flex flex-col h-full bg-white",
|
|
339
|
+
onDragEnter: (e) => {
|
|
340
|
+
if (!e.dataTransfer?.types?.includes?.("Files")) return;
|
|
341
|
+
e.preventDefault();
|
|
342
|
+
dragDepthRef.current++;
|
|
343
|
+
if (!isDragging) setIsDragging(true);
|
|
344
|
+
},
|
|
345
|
+
onDragOver: (e) => {
|
|
346
|
+
if (!e.dataTransfer?.types?.includes?.("Files")) return;
|
|
347
|
+
e.preventDefault();
|
|
348
|
+
},
|
|
349
|
+
onDragLeave: () => {
|
|
350
|
+
if (dragDepthRef.current > 0) dragDepthRef.current--;
|
|
351
|
+
if (dragDepthRef.current === 0) setIsDragging(false);
|
|
352
|
+
},
|
|
353
|
+
onDrop: (e) => {
|
|
354
|
+
e.preventDefault();
|
|
355
|
+
resetDrag();
|
|
356
|
+
if (e.dataTransfer.files?.length) uploadFiles(e.dataTransfer.files);
|
|
357
|
+
},
|
|
358
|
+
children: [
|
|
359
|
+
/* @__PURE__ */ jsx(WindowTitle, { title: `Files - ${user}${path}` }),
|
|
360
|
+
/* @__PURE__ */ jsx(
|
|
361
|
+
"input",
|
|
362
|
+
{
|
|
363
|
+
ref: fileRef,
|
|
364
|
+
type: "file",
|
|
365
|
+
multiple: true,
|
|
366
|
+
className: "hidden",
|
|
367
|
+
onChange: (e) => {
|
|
368
|
+
if (e.target.files?.length) uploadFiles(e.target.files);
|
|
369
|
+
if (fileRef.current) fileRef.current.value = "";
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
),
|
|
373
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 px-2 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0 text-xs", children: [
|
|
374
|
+
/* @__PURE__ */ jsx(
|
|
375
|
+
"button",
|
|
376
|
+
{
|
|
377
|
+
onClick: () => setPath(parentOf(path)),
|
|
378
|
+
disabled: path === "/",
|
|
379
|
+
className: "px-2 py-1 rounded hover:bg-gray-200 disabled:opacity-30 text-gray-600",
|
|
380
|
+
title: "Parent folder",
|
|
381
|
+
children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" }) })
|
|
382
|
+
}
|
|
383
|
+
),
|
|
384
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex items-center gap-0.5 text-gray-700 truncate min-w-0", children: [
|
|
385
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setPath("/"), className: "px-1.5 py-0.5 rounded hover:bg-gray-200 font-medium", children: user }),
|
|
386
|
+
segments.map((seg, i) => /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-0.5", children: [
|
|
387
|
+
/* @__PURE__ */ jsx("span", { className: "text-gray-400", children: "/" }),
|
|
388
|
+
/* @__PURE__ */ jsx(
|
|
389
|
+
"button",
|
|
390
|
+
{
|
|
391
|
+
onClick: () => setPath("/" + segments.slice(0, i + 1).join("/")),
|
|
392
|
+
className: "px-1.5 py-0.5 rounded hover:bg-gray-200",
|
|
393
|
+
children: seg
|
|
394
|
+
}
|
|
395
|
+
)
|
|
396
|
+
] }, i))
|
|
397
|
+
] }),
|
|
398
|
+
/* @__PURE__ */ jsx("button", { onClick: () => loadDir(path), className: "px-2 py-1 rounded hover:bg-gray-200 text-gray-600", title: "Refresh", children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.023 9.348h4.992V4.356M2.985 19.644v-4.992h4.992M3.05 9.348a9 9 0 0114.85-3.36L21.015 9.348m0 5.304a9 9 0 01-14.85 3.36l-3.115-3.36" }) }) }),
|
|
399
|
+
/* @__PURE__ */ jsxs("button", { onClick: handleNewFolder, className: "px-2 py-1 rounded hover:bg-gray-200 text-gray-600 flex items-center gap-1", children: [
|
|
400
|
+
/* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 10.5v6m3-3H9m4.06-7.19l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" }) }),
|
|
401
|
+
"New Folder"
|
|
402
|
+
] }),
|
|
403
|
+
/* @__PURE__ */ jsxs("button", { onClick: handlePick, className: "px-2 py-1 rounded bg-blue-500 text-white hover:bg-blue-600 flex items-center gap-1", children: [
|
|
404
|
+
/* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M7.5 7.5L12 3m0 0l4.5 4.5M12 3v13.5" }) }),
|
|
405
|
+
"Upload"
|
|
406
|
+
] }),
|
|
407
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-gray-300 mx-1" }),
|
|
408
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-400", children: user }),
|
|
409
|
+
/* @__PURE__ */ jsx("button", { onClick: signOut, className: "px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-500 text-[10px]", children: "sign out" })
|
|
410
|
+
] }),
|
|
411
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto", children: loading ? /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-400", children: "Loading\u2026" }) : entries.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "p-10 text-center text-sm text-gray-400", children: [
|
|
412
|
+
"Empty folder. Drop files here or click ",
|
|
413
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Upload" }),
|
|
414
|
+
"."
|
|
415
|
+
] }) : /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
|
|
416
|
+
/* @__PURE__ */ jsx("thead", { className: "bg-gray-50 text-[11px] uppercase tracking-wide text-gray-500", children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
417
|
+
/* @__PURE__ */ jsx("th", { className: "text-left font-medium px-3 py-1.5", children: "Name" }),
|
|
418
|
+
/* @__PURE__ */ jsx("th", { className: "text-right font-medium px-3 py-1.5 w-24", children: "Size" }),
|
|
419
|
+
/* @__PURE__ */ jsx("th", { className: "text-right font-medium px-3 py-1.5 w-40", children: "Modified" }),
|
|
420
|
+
/* @__PURE__ */ jsx("th", { className: "w-32" })
|
|
421
|
+
] }) }),
|
|
422
|
+
/* @__PURE__ */ jsx("tbody", { children: entries.map((e) => /* @__PURE__ */ jsxs(
|
|
423
|
+
"tr",
|
|
424
|
+
{
|
|
425
|
+
onClick: () => setSelected(e.name),
|
|
426
|
+
onDoubleClick: () => {
|
|
427
|
+
if (e.kind === "folder") setPath(joinPath(path, e.name));
|
|
428
|
+
else openFile(e);
|
|
429
|
+
},
|
|
430
|
+
className: `cursor-default border-b border-gray-100 ${selected === e.name ? "bg-blue-50" : "hover:bg-gray-50"}`,
|
|
431
|
+
children: [
|
|
432
|
+
/* @__PURE__ */ jsxs("td", { className: "px-3 py-1.5 flex items-center gap-2", children: [
|
|
433
|
+
e.kind === "folder" ? /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-amber-500 shrink-0", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M2.25 7.125A2.25 2.25 0 014.5 4.875h4.504c.61 0 1.193.243 1.624.673l1.494 1.494a.75.75 0 00.53.22h7.098A2.25 2.25 0 0122 9.51v8.366A2.25 2.25 0 0119.75 20.125H4.25A2.25 2.25 0 012 17.875V7.125z" }) }) : /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" }) }),
|
|
434
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", title: e.name, children: e.name })
|
|
435
|
+
] }),
|
|
436
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 text-right tabular-nums text-gray-500", children: e.kind === "folder" ? "\u2014" : formatSize(e.size) }),
|
|
437
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 text-right text-gray-500 tabular-nums", children: formatTime(e.modifiedAt) }),
|
|
438
|
+
/* @__PURE__ */ jsxs("td", { className: "px-3 py-1.5 text-right whitespace-nowrap", children: [
|
|
439
|
+
e.kind === "file" && /* @__PURE__ */ jsx(
|
|
440
|
+
"button",
|
|
441
|
+
{
|
|
442
|
+
onClick: (ev) => {
|
|
443
|
+
ev.stopPropagation();
|
|
444
|
+
downloadFile(e);
|
|
445
|
+
},
|
|
446
|
+
className: "px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-500 text-[11px]",
|
|
447
|
+
children: "Download"
|
|
448
|
+
}
|
|
449
|
+
),
|
|
450
|
+
/* @__PURE__ */ jsx(
|
|
451
|
+
"button",
|
|
452
|
+
{
|
|
453
|
+
onClick: (ev) => {
|
|
454
|
+
ev.stopPropagation();
|
|
455
|
+
handleRename(e);
|
|
456
|
+
},
|
|
457
|
+
className: "px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-500 text-[11px] ml-1",
|
|
458
|
+
children: "Rename"
|
|
459
|
+
}
|
|
460
|
+
),
|
|
461
|
+
/* @__PURE__ */ jsx(
|
|
462
|
+
"button",
|
|
463
|
+
{
|
|
464
|
+
onClick: (ev) => {
|
|
465
|
+
ev.stopPropagation();
|
|
466
|
+
handleDelete(e);
|
|
467
|
+
},
|
|
468
|
+
className: "px-1.5 py-0.5 rounded hover:bg-red-100 text-red-600 text-[11px] ml-1",
|
|
469
|
+
children: "Delete"
|
|
470
|
+
}
|
|
471
|
+
)
|
|
472
|
+
] })
|
|
473
|
+
]
|
|
474
|
+
},
|
|
475
|
+
e.name
|
|
476
|
+
)) })
|
|
477
|
+
] }) }),
|
|
478
|
+
isDragging && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-blue-500/15 border-4 border-dashed border-blue-500 pointer-events-none flex items-center justify-center z-20", children: /* @__PURE__ */ jsx("div", { className: "px-4 py-2 rounded-md bg-blue-600 text-white text-sm font-medium shadow-lg", children: "Drop to upload" }) })
|
|
479
|
+
]
|
|
480
|
+
}
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export { Files as default };
|
|
485
|
+
//# sourceMappingURL=Files-PPKWOJWV.js.map
|
|
486
|
+
//# sourceMappingURL=Files-PPKWOJWV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/apps/Files.tsx"],"names":[],"mappings":"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","file":"Files-PPKWOJWV.js","sourcesContent":["/**\n * Files — browser for the per-user file-server\n * (see examples/file-server). Lists the user's folder, navigates,\n * uploads (button + drag-from-OS), creates folders, renames, deletes,\n * and opens supported files via the Preview window.\n *\n * Server URL is configurable per-instance through an in-app field, then\n * persisted to localStorage. Bearer token same — paste it once, stays.\n */\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport { WindowTitle } from '../shell/Modal';\nimport { useWindowManager } from '../shell/WindowManager';\nimport toast from '../shell/toast';\nimport { setPdfPreview } from './Preview';\n\nconst DEFAULT_SERVER =\n (typeof window !== 'undefined' && (window as any).__REACT_OS_SHELL_FILE_SERVER__) ||\n 'http://localhost:4000';\nconst URL_KEY = 'react-os-shell:file-server-url';\nconst TOKEN_KEY = 'react-os-shell:file-server-token';\n\nconst PREVIEW_EXTS: Record<string, 'pdf' | 'image' | 'dxf' | '3d'> = {\n pdf: 'pdf',\n dxf: 'dxf',\n jpg: 'image', jpeg: 'image', png: 'image', gif: 'image',\n webp: 'image', svg: 'image', avif: 'image', bmp: 'image',\n stp: '3d', step: '3d', stl: '3d', obj: '3d',\n gltf: '3d', glb: '3d', '3mf': '3d', iges: '3d', igs: '3d', ply: '3d', fbx: '3d',\n};\n\ninterface FileEntry {\n name: string;\n kind: 'file' | 'folder';\n size: number;\n modifiedAt: string;\n}\n\nfunction joinPath(parent: string, name: string) {\n if (parent === '/' || parent === '') return '/' + name;\n return parent.replace(/\\/$/, '') + '/' + name;\n}\n\nfunction parentOf(p: string) {\n if (p === '/' || p === '') return '/';\n const trimmed = p.replace(/\\/$/, '');\n const idx = trimmed.lastIndexOf('/');\n return idx <= 0 ? '/' : trimmed.slice(0, idx);\n}\n\nfunction formatSize(bytes: number) {\n if (!bytes) return '—';\n const units = ['B', 'KB', 'MB', 'GB'];\n let v = bytes, i = 0;\n while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; }\n return `${v < 10 ? v.toFixed(1) : Math.round(v)} ${units[i]}`;\n}\n\nfunction formatTime(iso: string) {\n try {\n const d = new Date(iso);\n return d.toLocaleString(undefined, { dateStyle: 'short', timeStyle: 'short' });\n } catch { return iso; }\n}\n\nexport default function Files() {\n const [server, setServer] = useState<string>(() => {\n if (typeof window === 'undefined') return DEFAULT_SERVER;\n return localStorage.getItem(URL_KEY) || DEFAULT_SERVER;\n });\n const [token, setToken] = useState<string>(() => {\n if (typeof window === 'undefined') return '';\n return localStorage.getItem(TOKEN_KEY) || '';\n });\n const [user, setUser] = useState<string | null>(null);\n const [path, setPath] = useState('/');\n const [entries, setEntries] = useState<FileEntry[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n const [loading, setLoading] = useState(false);\n const [authError, setAuthError] = useState<string | null>(null);\n const [isDragging, setIsDragging] = useState(false);\n const dragDepthRef = useRef(0);\n const fileRef = useRef<HTMLInputElement>(null);\n\n const { openPage } = useWindowManager();\n\n const authedFetch = useCallback(\n (url: string, init: RequestInit = {}) => {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n ...((init.headers as Record<string, string>) || {}),\n };\n return fetch(url, { ...init, headers });\n },\n [token],\n );\n\n const signIn = async () => {\n setAuthError(null);\n try {\n const res = await fetch(`${server.replace(/\\/$/, '')}/api/me`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) {\n setAuthError(res.status === 401 ? 'Invalid token' : `Server error ${res.status}`);\n return;\n }\n const data = await res.json();\n setUser(data.user);\n localStorage.setItem(URL_KEY, server);\n localStorage.setItem(TOKEN_KEY, token);\n } catch (e: any) {\n setAuthError(e?.message || 'Could not reach server');\n }\n };\n\n const signOut = () => {\n setUser(null);\n setEntries([]);\n setPath('/');\n setSelected(null);\n localStorage.removeItem(TOKEN_KEY);\n };\n\n const loadDir = useCallback(async (dir: string) => {\n setLoading(true);\n setSelected(null);\n try {\n const res = await authedFetch(\n `${server.replace(/\\/$/, '')}/api/files?path=${encodeURIComponent(dir)}`,\n );\n if (!res.ok) {\n if (res.status === 401) { setUser(null); return; }\n const msg = await res.json().catch(() => ({} as any));\n toast.error(msg.error || `Failed to list (${res.status})`);\n return;\n }\n const data = await res.json();\n setEntries(data.entries || []);\n setPath(data.path || dir);\n } catch (e: any) {\n toast.error(e?.message || 'Could not reach server');\n } finally {\n setLoading(false);\n }\n }, [authedFetch, server]);\n\n // Auto-sign-in if token already in localStorage (validate by hitting /api/me).\n useEffect(() => {\n if (token && !user && !authError) signIn();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // (Re)load on path change once authenticated.\n useEffect(() => {\n if (user) loadDir(path);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [user, path]);\n\n // Open a file: fetch as Blob, route to Preview if extension is supported.\n const openFile = async (entry: FileEntry) => {\n const fullPath = joinPath(path, entry.name);\n const ext = (entry.name.split('.').pop() || '').toLowerCase();\n const kind = PREVIEW_EXTS[ext];\n if (!kind) {\n // Just download.\n downloadFile(entry);\n return;\n }\n try {\n const res = await authedFetch(\n `${server.replace(/\\/$/, '')}/api/file?path=${encodeURIComponent(fullPath)}`,\n );\n if (!res.ok) {\n toast.error(`Download failed (${res.status})`);\n return;\n }\n const blob = await res.blob();\n const url = URL.createObjectURL(blob);\n setPdfPreview({ url, filename: entry.name, kind });\n openPage('/preview');\n } catch (e: any) {\n toast.error(e?.message || 'Open failed');\n }\n };\n\n const downloadFile = async (entry: FileEntry) => {\n const fullPath = joinPath(path, entry.name);\n try {\n const res = await authedFetch(\n `${server.replace(/\\/$/, '')}/api/file?path=${encodeURIComponent(fullPath)}`,\n );\n if (!res.ok) { toast.error(`Download failed (${res.status})`); return; }\n const blob = await res.blob();\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = entry.name;\n a.click();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n } catch (e: any) {\n toast.error(e?.message || 'Download failed');\n }\n };\n\n const handlePick = () => fileRef.current?.click();\n\n const uploadFiles = async (files: FileList | File[]) => {\n const arr = Array.from(files);\n for (const file of arr) {\n const form = new FormData();\n form.append('file', file);\n try {\n const res = await authedFetch(\n `${server.replace(/\\/$/, '')}/api/upload?path=${encodeURIComponent(path)}`,\n { method: 'POST', body: form },\n );\n if (!res.ok) {\n const msg = await res.json().catch(() => ({} as any));\n toast.error(`Upload ${file.name}: ${msg.error || res.status}`);\n }\n } catch (e: any) {\n toast.error(`Upload ${file.name}: ${e?.message || 'failed'}`);\n }\n }\n loadDir(path);\n };\n\n const handleNewFolder = async () => {\n const name = window.prompt('Folder name');\n if (!name) return;\n if (/[\\\\/]/.test(name)) { toast.error('Folder names cannot contain slashes'); return; }\n const target = joinPath(path, name);\n const res = await authedFetch(`${server.replace(/\\/$/, '')}/api/folder`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ path: target }),\n });\n if (!res.ok) {\n const msg = await res.json().catch(() => ({} as any));\n toast.error(msg.error || `Create folder failed (${res.status})`);\n return;\n }\n loadDir(path);\n };\n\n const handleRename = async (entry: FileEntry) => {\n const next = window.prompt('New name', entry.name);\n if (!next || next === entry.name) return;\n if (/[\\\\/]/.test(next)) { toast.error('Names cannot contain slashes'); return; }\n const from = joinPath(path, entry.name);\n const to = joinPath(path, next);\n const res = await authedFetch(`${server.replace(/\\/$/, '')}/api/rename`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ from, to }),\n });\n if (!res.ok) {\n const msg = await res.json().catch(() => ({} as any));\n toast.error(msg.error || `Rename failed (${res.status})`);\n return;\n }\n loadDir(path);\n };\n\n const handleDelete = async (entry: FileEntry) => {\n if (!window.confirm(`Delete \"${entry.name}\"${entry.kind === 'folder' ? ' and everything inside' : ''}?`)) return;\n const target = joinPath(path, entry.name);\n const res = await authedFetch(\n `${server.replace(/\\/$/, '')}/api/files?path=${encodeURIComponent(target)}`,\n { method: 'DELETE' },\n );\n if (!res.ok) {\n const msg = await res.json().catch(() => ({} as any));\n toast.error(msg.error || `Delete failed (${res.status})`);\n return;\n }\n loadDir(path);\n };\n\n // Drag overlay reset helpers (mirror Preview).\n const resetDrag = () => { dragDepthRef.current = 0; setIsDragging(false); };\n useEffect(() => {\n const reset = () => resetDrag();\n window.addEventListener('dragend', reset);\n window.addEventListener('drop', reset);\n return () => {\n window.removeEventListener('dragend', reset);\n window.removeEventListener('drop', reset);\n };\n }, []);\n\n // ── render ─────────────────────────────────────────────────────────────\n if (!user) {\n return (\n <div className=\"flex flex-col h-full bg-white\">\n <WindowTitle title=\"Files - Sign in\" />\n <div className=\"flex flex-1 items-center justify-center p-6\">\n <div className=\"w-full max-w-sm space-y-3\">\n <h2 className=\"text-base font-semibold text-gray-800\">Connect to file server</h2>\n <p className=\"text-xs text-gray-500\">\n Paste your bearer token from <span className=\"font-mono\">users.json</span>.\n See <span className=\"font-mono\">examples/file-server</span> for setup.\n </p>\n <label className=\"block\">\n <span className=\"block text-[11px] uppercase tracking-wide text-gray-500 mb-1\">Server URL</span>\n <input\n type=\"text\"\n value={server}\n onChange={(e) => setServer(e.target.value)}\n className=\"w-full text-sm px-2 py-1.5 border border-gray-300 rounded font-mono\"\n />\n </label>\n <label className=\"block\">\n <span className=\"block text-[11px] uppercase tracking-wide text-gray-500 mb-1\">Bearer token</span>\n <input\n type=\"password\"\n value={token}\n onChange={(e) => setToken(e.target.value)}\n onKeyDown={(e) => { if (e.key === 'Enter') signIn(); }}\n className=\"w-full text-sm px-2 py-1.5 border border-gray-300 rounded font-mono\"\n placeholder=\"paste token…\"\n />\n </label>\n {authError && <div className=\"text-xs text-red-600\">{authError}</div>}\n <button\n onClick={signIn}\n disabled={!token}\n className=\"w-full px-3 py-1.5 text-sm bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-40\"\n >\n Connect\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n // Breadcrumb segments\n const segments = path === '/' ? [] : path.split('/').filter(Boolean);\n\n return (\n <div\n className=\"relative flex flex-col h-full bg-white\"\n onDragEnter={(e) => {\n if (!e.dataTransfer?.types?.includes?.('Files')) return;\n e.preventDefault();\n dragDepthRef.current++;\n if (!isDragging) setIsDragging(true);\n }}\n onDragOver={(e) => {\n if (!e.dataTransfer?.types?.includes?.('Files')) return;\n e.preventDefault();\n }}\n onDragLeave={() => {\n if (dragDepthRef.current > 0) dragDepthRef.current--;\n if (dragDepthRef.current === 0) setIsDragging(false);\n }}\n onDrop={(e) => {\n e.preventDefault();\n resetDrag();\n if (e.dataTransfer.files?.length) uploadFiles(e.dataTransfer.files);\n }}\n >\n <WindowTitle title={`Files - ${user}${path}`} />\n <input\n ref={fileRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n onChange={(e) => {\n if (e.target.files?.length) uploadFiles(e.target.files);\n if (fileRef.current) fileRef.current.value = '';\n }}\n />\n\n {/* Toolbar */}\n <div className=\"flex items-center gap-1 px-2 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0 text-xs\">\n <button\n onClick={() => setPath(parentOf(path))}\n disabled={path === '/'}\n className=\"px-2 py-1 rounded hover:bg-gray-200 disabled:opacity-30 text-gray-600\"\n title=\"Parent folder\"\n >\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18\" />\n </svg>\n </button>\n\n {/* Breadcrumbs */}\n <div className=\"flex-1 flex items-center gap-0.5 text-gray-700 truncate min-w-0\">\n <button onClick={() => setPath('/')} className=\"px-1.5 py-0.5 rounded hover:bg-gray-200 font-medium\">{user}</button>\n {segments.map((seg, i) => (\n <span key={i} className=\"flex items-center gap-0.5\">\n <span className=\"text-gray-400\">/</span>\n <button\n onClick={() => setPath('/' + segments.slice(0, i + 1).join('/'))}\n className=\"px-1.5 py-0.5 rounded hover:bg-gray-200\"\n >\n {seg}\n </button>\n </span>\n ))}\n </div>\n\n <button onClick={() => loadDir(path)} className=\"px-2 py-1 rounded hover:bg-gray-200 text-gray-600\" title=\"Refresh\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.8}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M16.023 9.348h4.992V4.356M2.985 19.644v-4.992h4.992M3.05 9.348a9 9 0 0114.85-3.36L21.015 9.348m0 5.304a9 9 0 01-14.85 3.36l-3.115-3.36\" />\n </svg>\n </button>\n <button onClick={handleNewFolder} className=\"px-2 py-1 rounded hover:bg-gray-200 text-gray-600 flex items-center gap-1\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 10.5v6m3-3H9m4.06-7.19l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z\" />\n </svg>\n New Folder\n </button>\n <button onClick={handlePick} className=\"px-2 py-1 rounded bg-blue-500 text-white hover:bg-blue-600 flex items-center gap-1\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.8}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M7.5 7.5L12 3m0 0l4.5 4.5M12 3v13.5\" />\n </svg>\n Upload\n </button>\n\n <div className=\"h-4 w-px bg-gray-300 mx-1\" />\n <span className=\"text-[10px] text-gray-400\">{user}</span>\n <button onClick={signOut} className=\"px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-500 text-[10px]\">sign out</button>\n </div>\n\n {/* List */}\n <div className=\"flex-1 overflow-auto\">\n {loading ? (\n <div className=\"p-6 text-center text-sm text-gray-400\">Loading…</div>\n ) : entries.length === 0 ? (\n <div className=\"p-10 text-center text-sm text-gray-400\">\n Empty folder. Drop files here or click <span className=\"font-medium\">Upload</span>.\n </div>\n ) : (\n <table className=\"w-full text-sm\">\n <thead className=\"bg-gray-50 text-[11px] uppercase tracking-wide text-gray-500\">\n <tr>\n <th className=\"text-left font-medium px-3 py-1.5\">Name</th>\n <th className=\"text-right font-medium px-3 py-1.5 w-24\">Size</th>\n <th className=\"text-right font-medium px-3 py-1.5 w-40\">Modified</th>\n <th className=\"w-32\" />\n </tr>\n </thead>\n <tbody>\n {entries.map((e) => (\n <tr\n key={e.name}\n onClick={() => setSelected(e.name)}\n onDoubleClick={() => {\n if (e.kind === 'folder') setPath(joinPath(path, e.name));\n else openFile(e);\n }}\n className={`cursor-default border-b border-gray-100 ${selected === e.name ? 'bg-blue-50' : 'hover:bg-gray-50'}`}\n >\n <td className=\"px-3 py-1.5 flex items-center gap-2\">\n {e.kind === 'folder' ? (\n <svg className=\"h-4 w-4 text-amber-500 shrink-0\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M2.25 7.125A2.25 2.25 0 014.5 4.875h4.504c.61 0 1.193.243 1.624.673l1.494 1.494a.75.75 0 00.53.22h7.098A2.25 2.25 0 0122 9.51v8.366A2.25 2.25 0 0119.75 20.125H4.25A2.25 2.25 0 012 17.875V7.125z\" /></svg>\n ) : (\n <svg className=\"h-4 w-4 text-gray-400 shrink-0\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z\" /></svg>\n )}\n <span className=\"truncate\" title={e.name}>{e.name}</span>\n </td>\n <td className=\"px-3 py-1.5 text-right tabular-nums text-gray-500\">\n {e.kind === 'folder' ? '—' : formatSize(e.size)}\n </td>\n <td className=\"px-3 py-1.5 text-right text-gray-500 tabular-nums\">\n {formatTime(e.modifiedAt)}\n </td>\n <td className=\"px-3 py-1.5 text-right whitespace-nowrap\">\n {e.kind === 'file' && (\n <button\n onClick={(ev) => { ev.stopPropagation(); downloadFile(e); }}\n className=\"px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-500 text-[11px]\"\n >Download</button>\n )}\n <button\n onClick={(ev) => { ev.stopPropagation(); handleRename(e); }}\n className=\"px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-500 text-[11px] ml-1\"\n >Rename</button>\n <button\n onClick={(ev) => { ev.stopPropagation(); handleDelete(e); }}\n className=\"px-1.5 py-0.5 rounded hover:bg-red-100 text-red-600 text-[11px] ml-1\"\n >Delete</button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </div>\n\n {isDragging && (\n <div className=\"absolute inset-0 bg-blue-500/15 border-4 border-dashed border-blue-500 pointer-events-none flex items-center justify-center z-20\">\n <div className=\"px-4 py-2 rounded-md bg-blue-600 text-white text-sm font-medium shadow-lg\">\n Drop to upload\n </div>\n </div>\n )}\n </div>\n );\n}\n"]}
|
package/dist/apps/index.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { W as WindowRegistry } from '../types-BKoa7nhP.js';
|
|
4
4
|
|
|
5
|
+
declare function Browser$1(): react_jsx_runtime.JSX.Element;
|
|
6
|
+
|
|
7
|
+
declare function Files$1(): react_jsx_runtime.JSX.Element;
|
|
8
|
+
|
|
5
9
|
declare function Documents$1(): react_jsx_runtime.JSX.Element;
|
|
6
10
|
|
|
7
11
|
interface PdfPreviewData {
|
|
@@ -77,10 +81,13 @@ declare const GeminiChat: react.LazyExoticComponent<typeof GeminiChat$1>;
|
|
|
77
81
|
declare const Calendar: react.LazyExoticComponent<typeof Calendar$1>;
|
|
78
82
|
declare const Preview: react.LazyExoticComponent<typeof Preview$1>;
|
|
79
83
|
declare const Documents: react.LazyExoticComponent<typeof Documents$1>;
|
|
84
|
+
declare const Files: react.LazyExoticComponent<typeof Files$1>;
|
|
85
|
+
declare const Browser: react.LazyExoticComponent<typeof Browser$1>;
|
|
80
86
|
declare const utilityApps: WindowRegistry;
|
|
81
87
|
declare const gameApps: WindowRegistry;
|
|
82
88
|
declare const googleApps: WindowRegistry;
|
|
83
89
|
declare const documentApps: WindowRegistry;
|
|
90
|
+
declare const webApps: WindowRegistry;
|
|
84
91
|
declare const bundledApps: WindowRegistry;
|
|
85
92
|
|
|
86
|
-
export { Calculator, Calendar, Checkers, Chess, CurrencyConverter, Documents, Email, Game2048, GeminiChat, Minesweeper, Notepad, type PdfPreviewData, PomodoroTimer, Preview, Spreadsheet, Sudoku, Tetris, Weather, bundledApps, documentApps, gameApps, googleApps, setPdfPreview, utilityApps };
|
|
93
|
+
export { Browser, Calculator, Calendar, Checkers, Chess, CurrencyConverter, Documents, Email, Files, Game2048, GeminiChat, Minesweeper, Notepad, type PdfPreviewData, PomodoroTimer, Preview, Spreadsheet, Sudoku, Tetris, Weather, bundledApps, documentApps, gameApps, googleApps, setPdfPreview, utilityApps, webApps };
|
package/dist/apps/index.js
CHANGED
|
@@ -21,6 +21,8 @@ var GeminiChat = lazy(() => import('../GeminiChat-BXLBJFT4.js'));
|
|
|
21
21
|
var Calendar = lazy(() => import('../Calendar-7DNNMOKO.js'));
|
|
22
22
|
var Preview = lazy(() => import('../Preview-LKFMKAIR.js'));
|
|
23
23
|
var Documents = lazy(() => import('../Documents-PK7QFWGR.js'));
|
|
24
|
+
var Files = lazy(() => import('../Files-PPKWOJWV.js'));
|
|
25
|
+
var Browser = lazy(() => import('../Browser-KMVL3AJ7.js'));
|
|
24
26
|
var utilityApps = {
|
|
25
27
|
"/calculator": { component: Calculator, label: "Calculator", size: "sm", allowPinOnTop: true, utility: true, widget: true, autoHeight: true, dimensions: [280, 420] },
|
|
26
28
|
"/spreadsheet": { component: Spreadsheet, label: "Spreadsheets", size: "2xl", compact: true, multiInstance: true },
|
|
@@ -44,15 +46,20 @@ var googleApps = {
|
|
|
44
46
|
};
|
|
45
47
|
var documentApps = {
|
|
46
48
|
"/preview": { component: Preview, label: "Preview", size: "2xl", multiInstance: true },
|
|
47
|
-
"/documents": { component: Documents, label: "Documents", size: "xl", multiInstance: true }
|
|
49
|
+
"/documents": { component: Documents, label: "Documents", size: "xl", multiInstance: true },
|
|
50
|
+
"/files": { component: Files, label: "Files", size: "xl" }
|
|
51
|
+
};
|
|
52
|
+
var webApps = {
|
|
53
|
+
"/browser": { component: Browser, label: "Browser", size: "2xl", multiInstance: true }
|
|
48
54
|
};
|
|
49
55
|
var bundledApps = {
|
|
50
56
|
...utilityApps,
|
|
51
57
|
...gameApps,
|
|
52
58
|
...googleApps,
|
|
53
|
-
...documentApps
|
|
59
|
+
...documentApps,
|
|
60
|
+
...webApps
|
|
54
61
|
};
|
|
55
62
|
|
|
56
|
-
export { Calculator, Calendar, Checkers, Chess, CurrencyConverter, Documents, Email, Game2048, GeminiChat, Minesweeper, Notepad, PomodoroTimer, Preview, Spreadsheet, Sudoku, Tetris, Weather, bundledApps, documentApps, gameApps, googleApps, utilityApps };
|
|
63
|
+
export { Browser, Calculator, Calendar, Checkers, Chess, CurrencyConverter, Documents, Email, Files, Game2048, GeminiChat, Minesweeper, Notepad, PomodoroTimer, Preview, Spreadsheet, Sudoku, Tetris, Weather, bundledApps, documentApps, gameApps, googleApps, utilityApps, webApps };
|
|
57
64
|
//# sourceMappingURL=index.js.map
|
|
58
65
|
//# sourceMappingURL=index.js.map
|
package/dist/apps/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/apps/index.ts"],"names":[],"mappings":";;;;;;AAuBA,IAAM,UAAA,GAAa,IAAA,CAAK,MAAM,OAAO,2BAAc,CAAC;AACpD,IAAM,WAAA,GAAc,IAAA,CAAK,MAAM,OAAO,4BAAe,CAAC;AACtD,IAAM,OAAA,GAAU,IAAA,CAAK,MAAM,OAAO,wBAAW,CAAC;AAC9C,IAAM,iBAAA,GAAoB,IAAA,CAAK,MAAM,OAAO,kCAAqB,CAAC;AAClE,IAAM,aAAA,GAAgB,IAAA,CAAK,MAAM,OAAO,8BAAiB,CAAC;AAC1D,IAAM,OAAA,GAAU,IAAA,CAAK,MAAM,OAAO,wBAAW,CAAC;AAG9C,IAAM,KAAA,GAAQ,IAAA,CAAK,MAAM,OAAO,sBAAS,CAAC;AAC1C,IAAM,QAAA,GAAW,IAAA,CAAK,MAAM,OAAO,yBAAY,CAAC;AAChD,IAAM,MAAA,GAAS,IAAA,CAAK,MAAM,OAAO,uBAAU,CAAC;AAC5C,IAAM,MAAA,GAAS,IAAA,CAAK,MAAM,OAAO,uBAAU,CAAC;AAC5C,IAAM,QAAA,GAAW,IAAA,CAAK,MAAM,OAAO,yBAAY,CAAC;AAChD,IAAM,WAAA,GAAc,IAAA,CAAK,MAAM,OAAO,4BAAe,CAAC;AAGtD,IAAM,KAAA,GAAQ,IAAA,CAAK,MAAM,OAAO,sBAAS,CAAC;AAC1C,IAAM,UAAA,GAAa,IAAA,CAAK,MAAM,OAAO,2BAAc,CAAC;AACpD,IAAM,QAAA,GAAW,IAAA,CAAK,MAAM,OAAO,yBAAY,CAAC;AAGhD,IAAM,OAAA,GAAU,IAAA,CAAK,MAAM,OAAO,wBAAW,CAAC;AAC9C,IAAM,SAAA,GAAY,IAAA,CAAK,MAAM,OAAO,0BAAa,CAAC;
|
|
1
|
+
{"version":3,"sources":["../../src/apps/index.ts"],"names":[],"mappings":";;;;;;AAuBA,IAAM,UAAA,GAAa,IAAA,CAAK,MAAM,OAAO,2BAAc,CAAC;AACpD,IAAM,WAAA,GAAc,IAAA,CAAK,MAAM,OAAO,4BAAe,CAAC;AACtD,IAAM,OAAA,GAAU,IAAA,CAAK,MAAM,OAAO,wBAAW,CAAC;AAC9C,IAAM,iBAAA,GAAoB,IAAA,CAAK,MAAM,OAAO,kCAAqB,CAAC;AAClE,IAAM,aAAA,GAAgB,IAAA,CAAK,MAAM,OAAO,8BAAiB,CAAC;AAC1D,IAAM,OAAA,GAAU,IAAA,CAAK,MAAM,OAAO,wBAAW,CAAC;AAG9C,IAAM,KAAA,GAAQ,IAAA,CAAK,MAAM,OAAO,sBAAS,CAAC;AAC1C,IAAM,QAAA,GAAW,IAAA,CAAK,MAAM,OAAO,yBAAY,CAAC;AAChD,IAAM,MAAA,GAAS,IAAA,CAAK,MAAM,OAAO,uBAAU,CAAC;AAC5C,IAAM,MAAA,GAAS,IAAA,CAAK,MAAM,OAAO,uBAAU,CAAC;AAC5C,IAAM,QAAA,GAAW,IAAA,CAAK,MAAM,OAAO,yBAAY,CAAC;AAChD,IAAM,WAAA,GAAc,IAAA,CAAK,MAAM,OAAO,4BAAe,CAAC;AAGtD,IAAM,KAAA,GAAQ,IAAA,CAAK,MAAM,OAAO,sBAAS,CAAC;AAC1C,IAAM,UAAA,GAAa,IAAA,CAAK,MAAM,OAAO,2BAAc,CAAC;AACpD,IAAM,QAAA,GAAW,IAAA,CAAK,MAAM,OAAO,yBAAY,CAAC;AAGhD,IAAM,OAAA,GAAU,IAAA,CAAK,MAAM,OAAO,wBAAW,CAAC;AAC9C,IAAM,SAAA,GAAY,IAAA,CAAK,MAAM,OAAO,0BAAa,CAAC;AAClD,IAAM,KAAA,GAAQ,IAAA,CAAK,MAAM,OAAO,sBAAS,CAAC;AAG1C,IAAM,OAAA,GAAU,IAAA,CAAK,MAAM,OAAO,wBAAW,CAAC;AAEvC,IAAM,WAAA,GAA8B;AAAA,EACzC,aAAA,EAAe,EAAE,SAAA,EAAW,UAAA,EAAY,OAAO,YAAA,EAAc,IAAA,EAAM,MAAM,aAAA,EAAe,IAAA,EAAM,SAAS,IAAA,EAAM,MAAA,EAAQ,MAAM,UAAA,EAAY,IAAA,EAAM,YAAY,CAAC,GAAA,EAAK,GAAG,CAAA,EAAE;AAAA,EACpK,cAAA,EAAgB,EAAE,SAAA,EAAW,WAAA,EAAa,KAAA,EAAO,cAAA,EAAgB,IAAA,EAAM,KAAA,EAAO,OAAA,EAAS,IAAA,EAAM,aAAA,EAAe,IAAA,EAAK;AAAA,EACjH,UAAA,EAAY,EAAE,SAAA,EAAW,OAAA,EAAS,OAAO,SAAA,EAAW,IAAA,EAAM,IAAA,EAAM,aAAA,EAAe,IAAA,EAAK;AAAA,EACpF,YAAY,EAAE,SAAA,EAAW,SAAS,KAAA,EAAO,SAAA,EAAW,MAAM,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,MAAA,EAAQ,MAAM,UAAA,EAAY,IAAA,EAAM,YAAY,CAAC,GAAA,EAAK,GAAG,CAAA,EAAE;AAAA,EACtI,aAAa,EAAE,SAAA,EAAW,mBAAmB,KAAA,EAAO,oBAAA,EAAsB,MAAM,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,MAAA,EAAQ,MAAM,UAAA,EAAY,IAAA,EAAM,YAAY,CAAC,GAAA,EAAK,GAAG,CAAA,EAAE;AAAA,EAC5J,aAAa,EAAE,SAAA,EAAW,eAAe,KAAA,EAAO,gBAAA,EAAkB,MAAM,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,MAAA,EAAQ,MAAM,UAAA,EAAY,IAAA,EAAM,YAAY,CAAC,GAAA,EAAK,GAAG,CAAA;AACpJ;AAEO,IAAM,QAAA,GAA2B;AAAA,EACtC,QAAA,EAAU,EAAE,SAAA,EAAW,KAAA,EAAO,OAAO,OAAA,EAAS,IAAA,EAAM,IAAA,EAAM,OAAA,EAAS,IAAA,EAAK;AAAA,EACxE,WAAA,EAAa,EAAE,SAAA,EAAW,QAAA,EAAU,OAAO,UAAA,EAAY,IAAA,EAAM,IAAA,EAAM,OAAA,EAAS,IAAA,EAAK;AAAA,EACjF,SAAA,EAAW,EAAE,SAAA,EAAW,MAAA,EAAQ,OAAO,QAAA,EAAU,IAAA,EAAM,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,UAAA,EAAY,CAAC,GAAA,EAAK,GAAG,CAAA,EAAE;AAAA,EACnG,SAAA,EAAW,EAAE,SAAA,EAAW,MAAA,EAAQ,OAAO,QAAA,EAAU,IAAA,EAAM,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,UAAA,EAAY,CAAC,GAAA,EAAK,GAAG,CAAA,EAAE;AAAA,EACnG,OAAA,EAAS,EAAE,SAAA,EAAW,QAAA,EAAU,OAAO,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAM,OAAA,EAAS,IAAA,EAAK;AAAA,EACzE,cAAA,EAAgB,EAAE,SAAA,EAAW,WAAA,EAAa,OAAO,aAAA,EAAe,IAAA,EAAM,IAAA,EAAM,OAAA,EAAS,IAAA;AACvF;AAEO,IAAM,UAAA,GAA6B;AAAA,EACxC,UAAU,EAAE,SAAA,EAAW,OAAO,KAAA,EAAO,OAAA,EAAS,MAAM,KAAA,EAAM;AAAA,EAC1D,WAAW,EAAE,SAAA,EAAW,YAAY,KAAA,EAAO,WAAA,EAAa,MAAM,IAAA,EAAK;AAAA,EACnE,aAAa,EAAE,SAAA,EAAW,UAAU,KAAA,EAAO,UAAA,EAAY,MAAM,IAAA;AAC/D;AAEO,IAAM,YAAA,GAA+B;AAAA,EAC1C,UAAA,EAAY,EAAE,SAAA,EAAW,OAAA,EAAS,OAAO,SAAA,EAAW,IAAA,EAAM,KAAA,EAAO,aAAA,EAAe,IAAA,EAAK;AAAA,EACrF,YAAA,EAAc,EAAE,SAAA,EAAW,SAAA,EAAW,OAAO,WAAA,EAAa,IAAA,EAAM,IAAA,EAAM,aAAA,EAAe,IAAA,EAAK;AAAA,EAC1F,UAAU,EAAE,SAAA,EAAW,OAAO,KAAA,EAAO,OAAA,EAAS,MAAM,IAAA;AACtD;AAEO,IAAM,OAAA,GAA0B;AAAA,EACrC,UAAA,EAAY,EAAE,SAAA,EAAW,OAAA,EAAS,OAAO,SAAA,EAAW,IAAA,EAAM,KAAA,EAAO,aAAA,EAAe,IAAA;AAClF;AAEO,IAAM,WAAA,GAA8B;AAAA,EACzC,GAAG,WAAA;AAAA,EACH,GAAG,QAAA;AAAA,EACH,GAAG,UAAA;AAAA,EACH,GAAG,YAAA;AAAA,EACH,GAAG;AACL","file":"index.js","sourcesContent":["/**\n * Bundled apps — pre-built window registry entries for the 16 apps that ship\n * with `react-os-shell`. Consumers compose them into their own registry via\n * `createWindowRegistry`:\n *\n * import { createWindowRegistry } from 'react-os-shell';\n * import { bundledApps } from 'react-os-shell/apps';\n * import { erpEntities } from './shell-config';\n *\n * const windows = createWindowRegistry(bundledApps, erpEntities);\n *\n * Subsets are also exported (`utilityApps`, `gameApps`, `googleApps`) so a\n * consumer can pick-and-choose without importing every component.\n *\n * NOTE: 4 apps require consumer-supplied persistence (Calendar / Notepad /\n * WorldClock for stored preferences, Minesweeper for leaderboard). They're\n * exported individually but absent from `bundledApps` — wire the prefs\n * provider to opt them in.\n */\nimport { lazy } from 'react';\nimport type { WindowRegistry } from '../windowRegistry/types';\n\n// ── Utility apps ──\nconst Calculator = lazy(() => import('./Calculator'));\nconst Spreadsheet = lazy(() => import('./Spreadsheet'));\nconst Weather = lazy(() => import('./Weather'));\nconst CurrencyConverter = lazy(() => import('./CurrencyConverter'));\nconst PomodoroTimer = lazy(() => import('./PomodoroTimer'));\nconst Notepad = lazy(() => import('./Notepad'));\n\n// ── Games ──\nconst Chess = lazy(() => import('./Chess'));\nconst Checkers = lazy(() => import('./Checkers'));\nconst Sudoku = lazy(() => import('./Sudoku'));\nconst Tetris = lazy(() => import('./Tetris'));\nconst Game2048 = lazy(() => import('./Game2048'));\nconst Minesweeper = lazy(() => import('./Minesweeper'));\n\n// ── Google apps ──\nconst Email = lazy(() => import('./Email'));\nconst GeminiChat = lazy(() => import('./GeminiChat'));\nconst Calendar = lazy(() => import('./Calendar'));\n\n// ── Document apps ──\nconst Preview = lazy(() => import('./Preview'));\nconst Documents = lazy(() => import('./Documents'));\nconst Files = lazy(() => import('./Files'));\n\n// ── Web ──\nconst Browser = lazy(() => import('./Browser'));\n\nexport const utilityApps: WindowRegistry = {\n '/calculator': { component: Calculator, label: 'Calculator', size: 'sm', allowPinOnTop: true, utility: true, widget: true, autoHeight: true, dimensions: [280, 420] },\n '/spreadsheet': { component: Spreadsheet, label: 'Spreadsheets', size: '2xl', compact: true, multiInstance: true },\n '/notepad': { component: Notepad, label: 'Notepad', size: 'lg', allowPinOnTop: true },\n '/weather': { component: Weather, label: 'Weather', size: 'sm', utility: true, widget: true, autoHeight: true, dimensions: [320, 400] },\n '/currency': { component: CurrencyConverter, label: 'Currency Converter', size: 'sm', utility: true, widget: true, autoHeight: true, dimensions: [320, 480] },\n '/pomodoro': { component: PomodoroTimer, label: 'Pomodoro Timer', size: 'sm', utility: true, widget: true, autoHeight: true, dimensions: [320, 420] },\n};\n\nexport const gameApps: WindowRegistry = {\n '/chess': { component: Chess, label: 'Chess', size: 'lg', compact: true },\n '/checkers': { component: Checkers, label: 'Checkers', size: 'lg', compact: true },\n '/sudoku': { component: Sudoku, label: 'Sudoku', size: 'sm', compact: true, dimensions: [360, 535] },\n '/tetris': { component: Tetris, label: 'Tetris', size: 'md', compact: true, dimensions: [452, 618] },\n '/2048': { component: Game2048, label: '2048', size: 'sm', compact: true },\n '/minesweeper': { component: Minesweeper, label: 'Minesweeper', size: 'sm', compact: true },\n};\n\nexport const googleApps: WindowRegistry = {\n '/email': { component: Email, label: 'Email', size: '2xl' },\n '/gemini': { component: GeminiChat, label: 'Gemini AI', size: 'lg' },\n '/calendar': { component: Calendar, label: 'Calendar', size: 'xl' },\n};\n\nexport const documentApps: WindowRegistry = {\n '/preview': { component: Preview, label: 'Preview', size: '2xl', multiInstance: true },\n '/documents': { component: Documents, label: 'Documents', size: 'xl', multiInstance: true },\n '/files': { component: Files, label: 'Files', size: 'xl' },\n};\n\nexport const webApps: WindowRegistry = {\n '/browser': { component: Browser, label: 'Browser', size: '2xl', multiInstance: true },\n};\n\nexport const bundledApps: WindowRegistry = {\n ...utilityApps,\n ...gameApps,\n ...googleApps,\n ...documentApps,\n ...webApps,\n};\n\nexport {\n Calculator,\n Spreadsheet,\n Notepad,\n Weather,\n CurrencyConverter,\n PomodoroTimer,\n Chess,\n Checkers,\n Sudoku,\n Tetris,\n Game2048,\n Minesweeper,\n Email,\n GeminiChat,\n Calendar,\n Preview,\n Documents,\n Files,\n Browser,\n};\n\nexport { setPdfPreview } from './Preview';\nexport type { PdfPreviewData } from './Preview';\n"]}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-os-shell",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.44",
|
|
4
4
|
"description": "Desktop-style React UI shell — windows, taskbar, start menu, sticky notes, frosted glass theming, and 17 bundled apps including a PDF Preview viewer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Victor Y. Mau",
|