Skip to content

Commit b137507

Browse files
committed
core: Improved directory list state handling
1 parent 7d6e2e8 commit b137507

File tree

5 files changed

+97
-5
lines changed

5 files changed

+97
-5
lines changed

packages/core/src/components/_utils/directory-list/DirectoryList.tsx

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { VirtualFolder } from "../../../features/virtual-drive/folder";
66
import { Vector2 } from "../../../features";
77
import { Interactable } from "../interactable/Interactable";
88
import { useClassNames } from "../../../hooks/_utils/classNames";
9+
import { removeFromArray } from "@prozilla-os/shared";
10+
import { VirtualBase } from "../../../features/virtual-drive/virtualBase";
911

1012
export interface OnSelectionChangeParams {
1113
files?: string[];
@@ -33,6 +35,8 @@ interface DirectoryListProps {
3335

3436
export function DirectoryList({ directory, showHidden = false, folderClassName, fileClassName, className,
3537
onContextMenuFile, onContextMenuFolder, onOpenFile, onOpenFolder, allowMultiSelect = true, onSelectionChange, ...props }: DirectoryListProps): ReactElement | null {
38+
const [folders, setFolders] = useState<VirtualFolder[]>([]);
39+
const [files, setFiles] = useState<VirtualFile[]>([]);
3640
const [selectedFolders, setSelectedFolders] = useState<string[]>([]);
3741
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
3842

@@ -80,7 +84,29 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
8084
document.removeEventListener("mousemove", onMoveRectSelect);
8185
document.removeEventListener("mouseup", onStopRectSelect);
8286
};
83-
});
87+
}, []);
88+
89+
useEffect(() => {
90+
const onUpdate = () => {
91+
console.log("Updated");
92+
93+
setFolders([...directory.getSubFolders(showHidden)]);
94+
setFiles([...directory.getFiles(showHidden)]);
95+
96+
setSelectedFolders((folders) => folders.filter((folder) => directory.hasFolder(folder)));
97+
setSelectedFiles((files) => files.filter((file) => {
98+
const { name, extension } = VirtualFile.splitId(file);
99+
return directory.hasFile(name, extension as string | undefined);
100+
}));
101+
};
102+
103+
onUpdate();
104+
directory.on(VirtualBase.EVENT_NAMES.update, onUpdate);
105+
106+
return () => {
107+
directory.off(VirtualBase.EVENT_NAMES.update, onUpdate);
108+
};
109+
}, [directory, showHidden]);
84110

85111
if (!directory)
86112
return null;
@@ -92,6 +118,7 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
92118
if (exclusive)
93119
setSelectedFiles([]);
94120
};
121+
95122
const selectFile = (file: VirtualFile, exclusive = false) => {
96123
if (!allowMultiSelect)
97124
exclusive = true;
@@ -100,9 +127,22 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
100127
setSelectedFolders([]);
101128
};
102129

130+
const deselectFolder = (folder: VirtualFolder) => {
131+
const newFolders = [...selectedFolders];
132+
removeFromArray(folder.id, newFolders);
133+
setSelectedFolders(newFolders);
134+
};
135+
136+
const deselectFile = (file: VirtualFile) => {
137+
const newFiles = [...selectedFiles];
138+
removeFromArray(file.id, newFiles);
139+
setSelectedFiles(newFiles);
140+
};
141+
103142
const onStartRectSelect = (event: MouseEvent) => {
104143
setRectSelectStart({ x: event.clientX, y: event.clientY } as Vector2);
105144
};
145+
106146
const getRectSelectStyle = () => {
107147
let x: number, y: number, width: number, height: number = 0;
108148

@@ -160,7 +200,7 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
160200
? <div className={styles.SelectionRect} style={getRectSelectStyle()}/>
161201
: null
162202
}
163-
{directory?.getSubFolders(showHidden)?.map((folder) =>
203+
{folders.map((folder) =>
164204
<Interactable
165205
key={folder.id}
166206
tabIndex={0}
@@ -174,6 +214,7 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
174214
}}
175215
onDoubleClick={(event: MouseEvent) => {
176216
onOpenFolder?.(event, folder);
217+
deselectFolder(folder);
177218
}}
178219
>
179220
<div className={styles.FolderIcon}>
@@ -182,7 +223,7 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
182223
<p>{folder.name}</p>
183224
</Interactable>
184225
)}
185-
{directory?.getFiles(showHidden)?.map((file) =>
226+
{files.map((file) =>
186227
<Interactable
187228
key={file.id}
188229
tabIndex={0}
@@ -196,6 +237,7 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
196237
}}
197238
onDoubleClick={(event: MouseEvent) => {
198239
onOpenFile?.(event, file);
240+
deselectFile(file);
199241
}}
200242
>
201243
<div className={styles.FileIcon}>

packages/core/src/features/virtual-drive/file/virtualFile.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class VirtualFile extends VirtualBase {
2424

2525
static EVENT_NAMES = {
2626
contentChange: "contentchange",
27+
...super.EVENT_NAMES,
2728
};
2829

2930
constructor(name: string, extension?: string ) {
@@ -95,6 +96,7 @@ export class VirtualFile extends VirtualBase {
9596
}
9697

9798
async read(): Promise<OptionalStringProperty | undefined> {
99+
if (this.isDeleted) return null;
98100
if (this.content != null) return this.content;
99101
if (this.source == null) return null;
100102

@@ -115,6 +117,9 @@ export class VirtualFile extends VirtualBase {
115117
}
116118

117119
getIconUrl(): string {
120+
if (this.isDeleted)
121+
return super.getIconUrl();
122+
118123
if (this.iconUrl != null)
119124
return this.iconUrl;
120125

packages/core/src/features/virtual-drive/folder/virtualFolder.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ export class VirtualFolder extends VirtualBase {
5353
* Finds and returns a file inside this folder matching a name and extension
5454
*/
5555
findFile(name: string, extension?: string | null): VirtualFile | VirtualFileLink | null {
56+
if (this.isDeleted)
57+
return null;
58+
5659
let resultFile: VirtualFile | VirtualFileLink | null = null;
5760

5861
this.files.forEach((file) => {
@@ -70,6 +73,9 @@ export class VirtualFolder extends VirtualBase {
7073
* Finds and returns a folder inside this folder matching a name
7174
*/
7275
findSubFolder(name: string): VirtualFolder | VirtualFolderLink | null {
76+
if (this.isDeleted)
77+
return null;
78+
7379
let resultFolder: VirtualFolder | VirtualFolderLink | null = null;
7480

7581
this.subFolders.forEach((folder) => {
@@ -232,7 +238,8 @@ export class VirtualFolder extends VirtualBase {
232238
removeFromArray(child, this.subFolders);
233239
}
234240

235-
child.confirmChanges();
241+
child.confirmChanges(this.getRoot());
242+
this.emit(VirtualBase.EVENT_NAMES.update);
236243
return this;
237244
}
238245

@@ -288,6 +295,9 @@ export class VirtualFolder extends VirtualBase {
288295
* Opens this folder in file explorer
289296
*/
290297
open(windowsManager: WindowsManager) {
298+
if (this.isDeleted)
299+
return;
300+
291301
const { appsConfig } = this.getRoot().systemManager;
292302
const fileExplorer = appsConfig.getAppByRole(AppsConfig.APP_ROLES.fileExplorer);
293303
if (fileExplorer != null)
@@ -320,6 +330,7 @@ export class VirtualFolder extends VirtualBase {
320330
* @param showHidden Whether to include hidden files
321331
*/
322332
getFiles(showHidden = false): VirtualFile[] {
333+
if (this.isDeleted) return [];
323334
if (showHidden) return this.files as VirtualFile[];
324335

325336
return this.files.filter(({ name }) =>
@@ -332,6 +343,7 @@ export class VirtualFolder extends VirtualBase {
332343
* @param showHidden Whether to include hidden folders
333344
*/
334345
getSubFolders(showHidden = false): VirtualFolder[] {
346+
if (this.isDeleted) return [];
335347
if (showHidden) return this.subFolders as VirtualFolder[];
336348

337349
return this.subFolders.filter(({ name }) =>
@@ -355,6 +367,9 @@ export class VirtualFolder extends VirtualBase {
355367
}
356368

357369
getIconUrl(): string {
370+
if (this.isDeleted)
371+
return super.getIconUrl();
372+
358373
if (this.iconUrl != null)
359374
return this.iconUrl;
360375

packages/core/src/features/virtual-drive/root/virtualRoot.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export class VirtualRoot extends VirtualFolder {
2323

2424
static EVENT_NAMES = {
2525
error: "error",
26+
...super.EVENT_NAMES,
2627
};
2728

2829
constructor(systemManager: SystemManager) {

packages/core/src/features/virtual-drive/virtualBase.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
1919
editedByUser: boolean | undefined | null;
2020
isRoot: boolean | undefined | null;
2121
root: VirtualRoot | undefined | null;
22+
isDeleted: boolean;
23+
24+
static EVENT_NAMES = {
25+
update: "update",
26+
};
2227

2328
constructor(name: string) {
2429
super();
2530
this.name = name;
31+
this.isDeleted = false;
2632
}
2733

2834
get id() {
@@ -79,11 +85,21 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
7985
}
8086

8187
getIconUrl(): string {
88+
if (this.isDeleted) {
89+
const systemManager = this.parent?.getRoot().systemManager;
90+
91+
if (!systemManager)
92+
return "";
93+
94+
return systemManager.skin.fileIcons.generic;
95+
}
96+
8297
if (this.iconUrl != null) return this.iconUrl;
8398
if (this.linkedFile?.iconUrl != null) return this.linkedFile.iconUrl;
8499
if (this.linkedFolder?.iconUrl != null) return this.linkedFolder.iconUrl;
85100

86101
const { skin } = this.getRoot().systemManager;
102+
87103
return skin.fileIcons.generic;
88104
}
89105

@@ -101,17 +117,24 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
101117
return;
102118

103119
parent.remove?.(this as never);
120+
this.isDeleted = true;
121+
104122
this.confirmChanges(parent.getRoot());
105123
}
106124

107125
confirmChanges(root?: VirtualRoot) {
108-
if (root == null)
126+
if (root == null) {
127+
if (this.isDeleted)
128+
return;
129+
109130
root = this.getRoot();
131+
}
110132

111133
if (root?.loadedDefaultData)
112134
this.editedByUser = true;
113135

114136
root?.saveData();
137+
this.emit(VirtualBase.EVENT_NAMES.update);
115138
}
116139

117140
open(..._args: unknown[]): unknown {
@@ -144,6 +167,9 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
144167
* Returns whether this can be edited in its current state
145168
*/
146169
get canBeEdited(): boolean {
170+
if (this.isDeleted)
171+
return false;
172+
147173
const isProtected = this.isProtected && this.getRoot().loadedDefaultData;
148174

149175
if (!isProtected && this.parent != null) {
@@ -172,6 +198,9 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
172198
}
173199

174200
toJSON(): VirtualBaseJson | null {
201+
if (this.isDeleted)
202+
return null;
203+
175204
const object = {
176205
nam: this.name,
177206
ico: this.iconUrl,

0 commit comments

Comments
 (0)