Repository navigation
Expand file tree
/
Copy pathfiles-app.ts
More file actions
153 lines (146 loc) · 4.88 KB
/
Copy pathfiles-app.ts
File metadata and controls
153 lines (146 loc) · 4.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { readTextFile, type DirectoryEntry } from "../../native/file-access";
import { isDecodableImageFile } from "../../native/image-files";
import { FileBrowserLayer } from "../apps/file-browser";
import { FileInfoDialogLayer, type FileInfoAction } from "../apps/file-info-dialog";
import { ImageViewerLayer } from "../apps/image-viewer";
import { TextViewerLayer } from "../apps/text-viewer";
import {
createInProcessWindow,
YieldAtRootLayer,
type InProcessAppOptions,
type InProcessWindow,
} from "./in-process-window";
import { shell } from "./shell";
export const FILES_WINDOW_ID = "files";
export const FILES_SURFACE_ID = "window:files";
const TEXT_FILE = /\.(txt|md|log)$/i;
export type FilesAppOptions = InProcessAppOptions & {
/** Open a text document as its own shell window (also used by the share intent). */
openDocumentWindow: (title: string, text: string) => void;
/** Open an image file as its own shell window. */
openImageWindow: (title: string, path: string) => void;
};
/**
* The Files app's launcher-opened window: a file browser over Places
* (bookmarks and storage roots). Picking any file opens an info dialog with
* metadata plus, for viewable types (text, images), the open actions.
*/
export function createFilesAppWindow(options: FilesAppOptions): InProcessWindow {
let created: InProcessWindow | null = null;
const browser = new FileBrowserLayer({
isSupportedFile: (name) => TEXT_FILE.test(name) || isDecodableImageFile(name),
// The browser handles double-click itself (up a level), so it is not
// wrapped in YieldAtRootLayer; it yields explicitly from the top level.
onLeave: () => shell.yieldFocusToSidebar(),
onFilePicked: (entry, ctx) => {
ctx.stack.push(new FileInfoDialogLayer(entry, fileOpenActions(entry, options)));
},
});
created = createInProcessWindow({
appId: "files",
windowId: FILES_WINDOW_ID,
title: "Files",
iconLetter: "F",
icon: "folder",
closeable: true,
// Browser actions (view switch, bookmarks) only apply while the browser
// itself is on top; a pushed viewer or dialog gets just the defaults.
menuItems: () => (created && !created.stack.isAtBase() ? [] : browser.buildMenuItems()),
actions: options.actions,
baseLayer: browser,
submitFrame: options.submitFrame,
setSurfaceVisible: options.setSurfaceVisible,
removeSurface: options.removeSurface,
onClosed: options.onClosed,
});
return created;
}
/** One opened text document as its own window (from the browser or a share intent). */
export function createTextDocumentWindow(
windowId: string,
title: string,
text: string,
options: InProcessAppOptions,
): InProcessWindow {
return createInProcessWindow({
appId: "files",
windowId,
title,
iconLetter: "F",
icon: "file-text",
closeable: true,
actions: options.actions,
baseLayer: new YieldAtRootLayer(new TextViewerLayer(text, title)),
submitFrame: options.submitFrame,
setSurfaceVisible: options.setSurfaceVisible,
removeSurface: options.removeSurface,
onClosed: options.onClosed,
});
}
/** One opened image file as its own window. */
export function createImageDocumentWindow(
windowId: string,
title: string,
path: string,
options: InProcessAppOptions,
): InProcessWindow {
return createInProcessWindow({
appId: "files",
windowId,
title,
iconLetter: "F",
icon: "image",
closeable: true,
actions: options.actions,
baseLayer: new YieldAtRootLayer(new ImageViewerLayer(path, title)),
submitFrame: options.submitFrame,
setSurfaceVisible: options.setSurfaceVisible,
removeSurface: options.removeSurface,
onClosed: options.onClosed,
});
}
/**
* The open actions for the picked-file dialog: View here / Open in new
* window for viewable types, empty for everything else (metadata only).
*/
function fileOpenActions(entry: DirectoryEntry, options: FilesAppOptions): FileInfoAction[] {
if (TEXT_FILE.test(entry.name)) {
return [
{
label: "View here",
onSelect: (ctx) => {
const text = readTextFile(entry.path);
ctx.stack.pop();
ctx.stack.push(new TextViewerLayer(text ?? "(could not read file)", entry.name));
},
},
{
label: "Open in new window",
onSelect: (ctx) => {
const text = readTextFile(entry.path);
ctx.stack.pop();
options.openDocumentWindow(entry.name, text ?? "(could not read file)");
},
},
];
}
if (isDecodableImageFile(entry.name)) {
return [
{
label: "View here",
onSelect: (ctx) => {
ctx.stack.pop();
ctx.stack.push(new ImageViewerLayer(entry.path, entry.name));
},
},
{
label: "Open in new window",
onSelect: (ctx) => {
ctx.stack.pop();
options.openImageWindow(entry.name, entry.path);
},
},
];
}
return [];
}