Skip to content

Commit 508e2e0

Browse files
committed
First pass at manage...
1 parent 4ffc5c5 commit 508e2e0

File tree

3 files changed

+428
-24
lines changed

3 files changed

+428
-24
lines changed
Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Porpoiseful LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/**
19+
* @author [email protected] (Alan Smith)
20+
*/
21+
import { TabType, TabTypeUtils } from "./Tabs";
22+
import * as Antd from "antd";
23+
import * as I18Next from "react-i18next";
24+
import * as React from "react";
25+
import * as commonStorage from "../storage/common_storage";
26+
import { EditOutlined, DeleteOutlined, CopyOutlined } from '@ant-design/icons';
27+
import ModuleNameComponent from "./ModuleNameComponent";
28+
29+
type Module = {
30+
path: string;
31+
title: string;
32+
type: TabType;
33+
}
34+
35+
type FileManageModalProps = {
36+
isOpen: boolean;
37+
onCancel: () => void;
38+
project: commonStorage.Project | null;
39+
setProject: (project: commonStorage.Project | null) => void;
40+
setAlertErrorMessage: (message: string) => void;
41+
storage: commonStorage.Storage | null;
42+
moduleType: TabType;
43+
}
44+
45+
export default function FileManageModal(props: FileManageModalProps) {
46+
const { t } = I18Next.useTranslation();
47+
const [modules, setModules] = React.useState<Module[]>([]);
48+
const [loading, setLoading] = React.useState(false);
49+
const [newItemName, setNewItemName] = React.useState('');
50+
const [currentRecord, setCurrentRecord] = React.useState<Module | null>(null);
51+
const [renameModalOpen, setRenameModalOpen] = React.useState(false);
52+
const [name, setName] = React.useState('');
53+
const [copyModalOpen, setCopyModalOpen] = React.useState(false);
54+
55+
React.useEffect(() => {
56+
if (props.project && props.moduleType !== null) {
57+
let moduleList: Module[] = [];
58+
59+
if (props.moduleType === TabType.MECHANISM) {
60+
moduleList = props.project.mechanisms.map(m => ({
61+
path: m.modulePath,
62+
title: m.className,
63+
type: TabType.MECHANISM
64+
}));
65+
} else if (props.moduleType === TabType.OPMODE) {
66+
moduleList = props.project.opModes.map(o => ({
67+
path: o.modulePath,
68+
title: o.className,
69+
type: TabType.OPMODE
70+
}));
71+
}
72+
73+
// Sort modules alphabetically by title
74+
moduleList.sort((a, b) => a.title.localeCompare(b.title));
75+
76+
setModules(moduleList);
77+
} else {
78+
setModules([]);
79+
}
80+
}, [props.project, props.moduleType]);
81+
82+
const handleDelete = async (module: Module) => {
83+
if (props.storage && props.project) {
84+
setLoading(true);
85+
try {
86+
await commonStorage.removeModuleFromProject(props.storage, props.project, module.path);
87+
// Remove from local state
88+
setModules(modules.filter(m => m.path !== module.path));
89+
} catch (error) {
90+
console.error('Error deleting module:', error);
91+
Antd.message.error('Failed to delete module');
92+
} finally {
93+
setLoading(false);
94+
}
95+
}
96+
};
97+
98+
const handleRename = async (origModule: Module, newName: string) => {
99+
if (props.storage && props.project) {
100+
try {
101+
let newPath = await commonStorage.renameModuleInProject(
102+
props.storage,
103+
props.project,
104+
newName,
105+
origModule.path
106+
);
107+
const newModules = modules.map((module) => {
108+
if (module.path === origModule.path) {
109+
return { ...module, title: newName, path: newPath };
110+
}
111+
return module;
112+
});
113+
setModules(newModules);
114+
props.setProject({ ...props.project });
115+
} catch (error) {
116+
console.error('Error renaming module:', error);
117+
props.setAlertErrorMessage('Failed to rename module');
118+
}
119+
}
120+
setRenameModalOpen(false);
121+
};
122+
const handleCopy = async (origModule: Module, newName: string) => {
123+
if (props.storage && props.project) {
124+
try {
125+
let newPath = await commonStorage.copyModuleInProject(
126+
props.storage,
127+
props.project,
128+
newName,
129+
origModule.path
130+
);
131+
const newModules = [...modules];
132+
133+
// find the original module to copy its type
134+
const originalModule = modules.find(module => module.path === origModule.path);
135+
if (!originalModule) {
136+
console.error('Original module not found for copying:', origModule.path);
137+
props.setAlertErrorMessage('Original module not found for copying');
138+
return;
139+
}
140+
// Add the new module with the copied name and type
141+
newModules.push({ path: newPath, title: newName, type: originalModule.type });
142+
143+
setModules(newModules);
144+
props.setProject({ ...props.project, });
145+
} catch (error) {
146+
console.error('Error copying module:', error);
147+
props.setAlertErrorMessage('Failed to copy module');
148+
}
149+
}
150+
setCopyModalOpen(false);
151+
};
152+
153+
const handleAddNewItem = async () => {
154+
let trimmedName = newItemName.trim();
155+
if (trimmedName) {
156+
/*
157+
if (props.storage && props.project) {
158+
let storage_type = tabType == TabType.MECHANISM ? commonStorage.MODULE_TYPE_MECHANISM : commonStorage.MODULE_TYPE_OPMODE;
159+
await commonStorage.addModuleToProject(props.storage,
160+
props.project, storage_type, trimmedName);
161+
let m = commonStorage.getClassInProject(props.project, trimmedName);
162+
// add the new item to selected items
163+
if (m) {
164+
const module: Module = {
165+
path: m.modulePath,
166+
title: m.className,
167+
type: tabType
168+
};
169+
setSelectedItems([...selectedItems, module]);
170+
}
171+
}
172+
*/
173+
setNewItemName("");
174+
// setProject(null); // Reset project to null to trigger re-fetch
175+
}
176+
};
177+
const columns: Antd.TableProps<Module>['columns'] = [
178+
{
179+
title: 'Name',
180+
dataIndex: 'title',
181+
key: 'title',
182+
ellipsis: {
183+
showTitle: false,
184+
},
185+
render: (title: string) => (
186+
<Antd.Tooltip title={title}>
187+
{title}
188+
</Antd.Tooltip>
189+
),
190+
},
191+
{
192+
title: 'Actions',
193+
key: 'actions',
194+
width: 120,
195+
render: (_, record: Module) => (
196+
<Antd.Space size="small">
197+
<Antd.Tooltip title={t("Rename")}>
198+
<Antd.Button
199+
type="text"
200+
size="small"
201+
icon={<EditOutlined />}
202+
onClick={() => {
203+
setCurrentRecord(record);
204+
setName(record.title);
205+
setRenameModalOpen(true);
206+
}}
207+
/>
208+
</Antd.Tooltip>
209+
<Antd.Tooltip title={t("Copy")}>
210+
<Antd.Button
211+
type="text"
212+
size="small"
213+
icon={<CopyOutlined />}
214+
onClick={() => {
215+
setCurrentRecord(record);
216+
setName(record.title + 'Copy');
217+
setCopyModalOpen(true);
218+
}}
219+
/>
220+
</Antd.Tooltip>
221+
<Antd.Tooltip title={t("Delete")}>
222+
<Antd.Popconfirm
223+
title={`Delete ${record.title}?`}
224+
description="This action cannot be undone."
225+
onConfirm={async () => {
226+
const newModules = modules.filter(m => m.path !== record.path);
227+
setModules(newModules);
228+
229+
if (props.storage && props.project) {
230+
await commonStorage.removeModuleFromProject(props.storage, props.project, record.path);
231+
props.setProject({ ...props.project });
232+
}
233+
}}
234+
235+
okText={t("Delete")}
236+
cancelText={t("Cancel")}
237+
okType="danger"
238+
239+
>
240+
<Antd.Button
241+
type="text"
242+
size="small"
243+
icon={<DeleteOutlined />}
244+
danger
245+
/>
246+
</Antd.Popconfirm>
247+
</Antd.Tooltip>
248+
</Antd.Space>
249+
),
250+
},
251+
];
252+
253+
const getModalTitle = () => {
254+
if (props.moduleType === null) {
255+
return 'Project Management';
256+
}
257+
return `${TabTypeUtils.toString(props.moduleType)} Management`;
258+
};
259+
260+
return (
261+
<>
262+
<Antd.Modal
263+
title={`Rename ${currentRecord ? TabTypeUtils.toString(currentRecord.type) : ''}: ${currentRecord ? currentRecord.title : ''}`}
264+
open={renameModalOpen}
265+
onCancel={() => setRenameModalOpen(false)}
266+
onOk={() => {
267+
if (currentRecord) {
268+
handleRename(currentRecord, name);
269+
}
270+
}}
271+
okText={t("Rename")}
272+
cancelText={t("Cancel")}
273+
>
274+
{currentRecord && (
275+
<ModuleNameComponent
276+
tabType={currentRecord.type}
277+
newItemName={name}
278+
setNewItemName={setName}
279+
onAddNewItem={() => {
280+
if (currentRecord) {
281+
handleRename(currentRecord, name);
282+
}
283+
}}
284+
project={props.project}
285+
storage={props.storage}
286+
buttonLabel=""
287+
/>
288+
)}
289+
</Antd.Modal>
290+
<Antd.Modal
291+
title={`Copy ${currentRecord ? TabTypeUtils.toString(currentRecord.type) : ''}: ${currentRecord ? currentRecord.title : ''}`}
292+
open={copyModalOpen}
293+
onCancel={() => setCopyModalOpen(false)}
294+
onOk={() => {
295+
if (currentRecord) {
296+
handleCopy(currentRecord, name);
297+
}
298+
}}
299+
okText={t("Copy")}
300+
cancelText={t("Cancel")}
301+
>
302+
{currentRecord && (
303+
<ModuleNameComponent
304+
tabType={currentRecord.type}
305+
newItemName={name}
306+
setNewItemName={setName}
307+
onAddNewItem={() => {
308+
if (currentRecord) {
309+
handleCopy(currentRecord, name);
310+
}
311+
}}
312+
project={props.project}
313+
storage={props.storage}
314+
buttonLabel=""
315+
/>
316+
)}
317+
</Antd.Modal>
318+
319+
<Antd.Modal
320+
title={getModalTitle()}
321+
open={props.isOpen}
322+
onCancel={props.onCancel}
323+
footer={[
324+
<Antd.Button key="close" onClick={props.onCancel}>
325+
{t("Close")}
326+
</Antd.Button>
327+
]}
328+
width={800}
329+
>
330+
<div style={{
331+
marginBottom: 16,
332+
border: '1px solid #d9d9d9',
333+
borderRadius: '6px',
334+
padding: '12px'
335+
}}>
336+
<ModuleNameComponent
337+
tabType={props.moduleType}
338+
newItemName={newItemName}
339+
setNewItemName={setNewItemName}
340+
onAddNewItem={handleAddNewItem}
341+
project={props.project}
342+
storage={props.storage}
343+
buttonLabel={t("New")}
344+
/>
345+
</div>
346+
<Antd.Table<Module>
347+
columns={columns}
348+
dataSource={modules}
349+
rowKey="path"
350+
loading={loading}
351+
size="small"
352+
pagination={modules.length > 5 ? {
353+
pageSize: 5,
354+
showSizeChanger: false,
355+
showQuickJumper: false,
356+
showTotal: (total, range) =>
357+
`${range[0]}-${range[1]} of ${total} items`,
358+
} : false}
359+
bordered
360+
locale={{
361+
emptyText: `No ${TabTypeUtils.toString(props.moduleType || TabType.OPMODE).toLowerCase()} files found`
362+
}}
363+
/>
364+
</Antd.Modal>
365+
</>
366+
);
367+
}

0 commit comments

Comments
 (0)