u
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
|
||||
import { Automat } from 'react-automat';
|
||||
import { vfs as vfsApi } from '../api';
|
||||
import { isAppletFile, parseAppletSchemas } from '../components/vfs/vfsUtils';
|
||||
import authAutomat from './authAutomat';
|
||||
import sseAutomat from './sseAutomat';
|
||||
|
||||
@@ -25,6 +26,7 @@ import sseAutomat from './sseAutomat';
|
||||
* @property {boolean} isText Whether the file can be edited in the text editor
|
||||
* @property {boolean} isImage Whether the file can be rendered as an image
|
||||
* @property {boolean} isPdf Whether the file is a PDF
|
||||
* @property {string} [description] Applet description declared in the @applet-description header
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -71,6 +73,7 @@ const initialState = {
|
||||
};
|
||||
|
||||
let vfsAutomat;
|
||||
const appletDescriptionCache = new Map();
|
||||
|
||||
const actions = {
|
||||
/**
|
||||
@@ -120,7 +123,17 @@ const actions = {
|
||||
}
|
||||
try {
|
||||
const res = await vfsApi.list();
|
||||
const files = Array.isArray(res?.files) ? res.files : [];
|
||||
const rawFiles = Array.isArray(res?.files) ? res.files : [];
|
||||
const files = rawFiles.map((f) => {
|
||||
if (f.description) {
|
||||
appletDescriptionCache.set(f.name, f.description);
|
||||
return f;
|
||||
}
|
||||
if (appletDescriptionCache.has(f.name)) {
|
||||
return { ...f, description: appletDescriptionCache.get(f.name) };
|
||||
}
|
||||
return f;
|
||||
});
|
||||
const currentSelected = vfsAutomat.state.selectedFileName;
|
||||
const updatedSelectedFile = currentSelected
|
||||
? files.find((f) => f.name === currentSelected) || null
|
||||
@@ -132,6 +145,10 @@ const actions = {
|
||||
loadingFiles: false,
|
||||
loadError: null,
|
||||
});
|
||||
|
||||
// Asynchronously fetch descriptions for any applet files that don't have one yet
|
||||
void actions.loadAppletDescriptions(files);
|
||||
|
||||
return files;
|
||||
} catch (err) {
|
||||
console.error('[vfsAutomat] loadFiles failed:', err);
|
||||
@@ -143,6 +160,68 @@ const actions = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Asynchronously load description headers for applets missing cached descriptions.
|
||||
* @param {VfsFile[]} [files]
|
||||
*/
|
||||
async loadAppletDescriptions(files) {
|
||||
const list = files || vfsAutomat.state.files;
|
||||
const missing = list.filter((f) => isAppletFile(f.name) && !f.description);
|
||||
if (missing.length === 0) return;
|
||||
|
||||
let changed = false;
|
||||
await Promise.all(
|
||||
missing.map(async (file) => {
|
||||
try {
|
||||
const text = await vfsApi.getText(file.name);
|
||||
const { description } = parseAppletSchemas(text);
|
||||
if (description) {
|
||||
appletDescriptionCache.set(file.name, description);
|
||||
changed = true;
|
||||
}
|
||||
} catch {
|
||||
// Ignore failures reading unreadable files
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (changed) {
|
||||
const nextFiles = vfsAutomat.state.files.map((f) => {
|
||||
const desc = appletDescriptionCache.get(f.name);
|
||||
return desc && f.description !== desc ? { ...f, description: desc } : f;
|
||||
});
|
||||
const currentSelected = vfsAutomat.state.selectedFileName;
|
||||
const updatedSelectedFile = currentSelected
|
||||
? nextFiles.find((f) => f.name === currentSelected) || null
|
||||
: null;
|
||||
vfsAutomat.setState({
|
||||
files: nextFiles,
|
||||
selectedFile: updatedSelectedFile,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Manually record or update an applet file's description in the state & cache.
|
||||
* @param {string} fileName
|
||||
* @param {string} description
|
||||
*/
|
||||
setAppletDescription(fileName, description) {
|
||||
if (!fileName || !description) return;
|
||||
appletDescriptionCache.set(fileName, description);
|
||||
const nextFiles = vfsAutomat.state.files.map((f) =>
|
||||
f.name === fileName ? { ...f, description } : f
|
||||
);
|
||||
const updatedSelectedFile =
|
||||
vfsAutomat.state.selectedFileName === fileName
|
||||
? { ...(vfsAutomat.state.selectedFile || {}), description }
|
||||
: vfsAutomat.state.selectedFile;
|
||||
vfsAutomat.setState({
|
||||
files: nextFiles,
|
||||
selectedFile: updatedSelectedFile,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Select a file for viewing or editing.
|
||||
* @param {string|null} fileName
|
||||
@@ -179,9 +258,18 @@ const actions = {
|
||||
const text = await vfsApi.getText(fileName);
|
||||
// Only apply if still selected
|
||||
if (vfsAutomat.state.selectedFileName === fileName) {
|
||||
let updatedFile = file;
|
||||
if (isAppletFile(fileName)) {
|
||||
const { description } = parseAppletSchemas(text);
|
||||
if (description) {
|
||||
appletDescriptionCache.set(fileName, description);
|
||||
updatedFile = { ...file, description };
|
||||
}
|
||||
}
|
||||
vfsAutomat.setState({
|
||||
fileContent: text,
|
||||
originalContent: text,
|
||||
selectedFile: updatedFile,
|
||||
isDirty: false,
|
||||
loadingContent: false,
|
||||
});
|
||||
@@ -224,6 +312,14 @@ const actions = {
|
||||
vfsAutomat.setState({ isSaving: true, saveError: null });
|
||||
try {
|
||||
await vfsApi.save(selectedFileName, fileContent, selectedFile?.mime);
|
||||
if (isAppletFile(selectedFileName)) {
|
||||
const { description } = parseAppletSchemas(fileContent);
|
||||
if (description) {
|
||||
appletDescriptionCache.set(selectedFileName, description);
|
||||
} else {
|
||||
appletDescriptionCache.delete(selectedFileName);
|
||||
}
|
||||
}
|
||||
vfsAutomat.setState({
|
||||
originalContent: fileContent,
|
||||
isDirty: false,
|
||||
@@ -274,6 +370,7 @@ const actions = {
|
||||
if (!fileName) return;
|
||||
try {
|
||||
await vfsApi.delete(fileName);
|
||||
appletDescriptionCache.delete(fileName);
|
||||
if (vfsAutomat.state.selectedFileName === fileName) {
|
||||
actions.selectFile(null);
|
||||
}
|
||||
@@ -320,6 +417,11 @@ const actions = {
|
||||
if (!cleanOld || !cleanNew) throw new Error('Alter und neuer Pfad dürfen nicht leer sein');
|
||||
try {
|
||||
const res = await vfsApi.rename(cleanOld, cleanNew);
|
||||
if (appletDescriptionCache.has(cleanOld)) {
|
||||
const desc = appletDescriptionCache.get(cleanOld);
|
||||
appletDescriptionCache.delete(cleanOld);
|
||||
appletDescriptionCache.set(cleanNew, desc);
|
||||
}
|
||||
const isCurrentSelected = vfsAutomat.state.selectedFileName === cleanOld;
|
||||
await actions.loadFiles(true);
|
||||
if (isCurrentSelected) {
|
||||
@@ -379,6 +481,7 @@ const actions = {
|
||||
* Reset state (on logout or company switch).
|
||||
*/
|
||||
reset() {
|
||||
appletDescriptionCache.clear();
|
||||
vfsAutomat.setState({
|
||||
files: [],
|
||||
loadingFiles: false,
|
||||
|
||||
Reference in New Issue
Block a user