diff --git a/frontend/src/components/editor/CodeEditor.tsx b/frontend/src/components/editor/CodeEditor.tsx index b9536757..6efaa17b 100644 --- a/frontend/src/components/editor/CodeEditor.tsx +++ b/frontend/src/components/editor/CodeEditor.tsx @@ -1,5 +1,6 @@ import Editor from '@monaco-editor/react'; import { useEditorStore } from '../../store/useEditorStore'; +import { useSimulatorStore } from '../../store/useSimulatorStore'; import { registerRetroAsm, LANGUAGE_ID as RETRO_ASM_ID } from './retroAsmLanguage'; function getLanguage(filename: string): string { @@ -14,9 +15,40 @@ function getLanguage(filename: string): string { } export const CodeEditor = () => { - const { files, activeFileId, setFileContent, theme, fontSize } = useEditorStore(); + const { files, activeFileId, setFileContent, theme, fontSize, manifestViewBoardId } = + useEditorStore(); + const boards = useSimulatorStore((s) => s.boards); const activeFile = files.find((f) => f.id === activeFileId); + // READ-ONLY libraries.json view (the file explorer's libraries.json entry). + // Shows the active board's declared library manifest as plain-text JSON, live. + // It is read-only on purpose: adding/removing libraries is done from the + // Library Manager modal, which edits board.libraries (this just reflects it). + if (manifestViewBoardId) { + const b = boards.find((x) => x.id === manifestViewBoardId); + const content = JSON.stringify({ libraries: b?.libraries ?? [] }, null, 2); + return ( +
+ +
+ ); + } + return (
= ({ onSaveClick, onNewCl deleteFile, renameFile, setActiveGroup, + manifestViewBoardId, + setManifestView, } = useEditorStore(); const boards = useSimulatorStore((s) => s.boards); const activeBoardId = useSimulatorStore((s) => s.activeBoardId); @@ -741,17 +743,21 @@ export const FileExplorer: React.FC = ({ onSaveClick, onNewCl Clicking switches to the board and opens the Library Manager on its list. */}
{ switchToBoard(board.id, groupId); - window.dispatchEvent(new CustomEvent('velxio-open-library-manager')); + // Open the READ-ONLY libraries.json view (not the modal). + // Library actions happen in the Library Manager modal. + setManifestView(board.id); }} - title={`Libraries for ${boardDisplayName(board)} — click to manage (compile scope)`} + title={`libraries.json — ${boardDisplayName(board)}'s declared libraries (read-only; manage from the Library Manager)`} > - + - velxio.json + libraries.json = ({ isOpen [install, addToManifest, activeBoard], ); - const uninstall = useCallback( - async (name: string) => { - setBusyLib(name); - setStatusMsg(null); - try { - const result = await uninstallLibrary(name); - if (result.success) { - setStatusMsg({ type: 'success', text: `"${name}" uninstalled.` }); - fetchInstalled(); - } else { - setStatusMsg({ type: 'error', text: result.error || `Failed to uninstall "${name}"` }); - } - } catch (e: unknown) { - setStatusMsg({ type: 'error', text: e instanceof Error ? e.message : 'Uninstall failed' }); - } finally { - setBusyLib(null); - } - }, - [fetchInstalled], - ); - // A CUSTOM lib lives in the user's per-user store, so removing it hits the // per-user delete endpoint and also drops it from the manifest. const removeCustom = useCallback( @@ -446,14 +424,18 @@ export const LibraryManagerModal: React.FC = ({ isOpen {busy ? '…' : '+ Add to project'} )} - {row.installed && ( + {/* Only CUSTOM uploads can be removed (per-user store). Index + libraries live in the shared content-addressed cache — you + add/remove them from THIS project, but never "uninstall" a + copy everyone shares, so no Uninstall button for them. */} + {row.custom && ( )}
diff --git a/frontend/src/store/useEditorStore.ts b/frontend/src/store/useEditorStore.ts index 4871b5b8..94df5221 100644 --- a/frontend/src/store/useEditorStore.ts +++ b/frontend/src/store/useEditorStore.ts @@ -104,6 +104,12 @@ interface EditorState { files: WorkspaceFile[]; activeFileId: string; openFileIds: string[]; + /** When set, the editor shows a READ-ONLY `libraries.json` view of this + * board's library manifest (board.libraries) instead of the active file. + * Cleared whenever a real file is opened/activated. Managed by the explorer's + * libraries.json entry; the Library Manager modal is what edits the manifest. */ + manifestViewBoardId: string | null; + setManifestView: (boardId: string | null) => void; theme: 'vs-dark' | 'light'; fontSize: number; viewMode: EditorViewMode; @@ -159,6 +165,8 @@ export const useEditorStore = create((set, get) => ({ files: [DEFAULT_FILE], activeFileId: MAIN_ID, openFileIds: [MAIN_ID], + manifestViewBoardId: null, + setManifestView: (boardId: string | null) => set({ manifestViewBoardId: boardId }), theme: 'vs-dark', fontSize: 14, viewMode: 'both', @@ -266,6 +274,7 @@ export const useEditorStore = create((set, get) => ({ return { openFileIds: s.openFileIds.includes(id) ? s.openFileIds : [...s.openFileIds, id], activeFileId: id, + manifestViewBoardId: null, // opening a real file exits the libraries.json view openGroupFileIds: { ...s.openGroupFileIds, [groupId]: groupOpenIds.includes(id) ? groupOpenIds : [...groupOpenIds, id], @@ -299,6 +308,7 @@ export const useEditorStore = create((set, get) => ({ const groupId = s.activeGroupId; return { activeFileId: id, + manifestViewBoardId: null, // activating a real file exits the libraries.json view activeGroupFileId: { ...s.activeGroupFileId, [groupId]: id }, }; });