feat(library-manager): read-only libraries.json file + drop Uninstall for shared index libs
(1) The explorer's per-board manifest entry is renamed velxio.json -> libraries.json
and clicking it now opens a READ-ONLY JSON view of that board's declared libraries
(board.libraries) in the editor, instead of the modal. New editor state
manifestViewBoardId: when set, CodeEditor renders a read-only Monaco showing
{libraries:[...]} live; opening/activating any real file clears it. No file is
added to the workspace, so nothing touches compile or save. Library actions are
done in the Library Manager modal (toolbar button).
(2) Drop the 'Uninstall' button for shared index/cache libraries — you can't
uninstall a copy everyone shares (content-addressed cache). Only your own custom
.zip uploads keep a 'Remove' (per-user store). Index libs: just Add to / In project.
This commit is contained in:
parent
02c8ad756d
commit
c580a7e418
|
|
@ -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 (
|
||||
<div style={{ height: '100%', width: '100%' }}>
|
||||
<Editor
|
||||
key="__libraries_json__"
|
||||
height="100%"
|
||||
language="json"
|
||||
theme={theme}
|
||||
value={content}
|
||||
options={{
|
||||
readOnly: true,
|
||||
domReadOnly: true,
|
||||
minimap: { enabled: false },
|
||||
fontSize,
|
||||
automaticLayout: true,
|
||||
scrollBeyondLastLine: false,
|
||||
wordWrap: 'on',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', width: '100%' }}>
|
||||
<Editor
|
||||
|
|
|
|||
|
|
@ -298,6 +298,8 @@ export const FileExplorer: React.FC<FileExplorerProps> = ({ 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<FileExplorerProps> = ({ onSaveClick, onNewCl
|
|||
Clicking switches to the board and opens the Library
|
||||
Manager on its list. */}
|
||||
<div
|
||||
className="file-explorer-item fe-file-item"
|
||||
className={`file-explorer-item fe-file-item${
|
||||
manifestViewBoardId === board.id ? ' file-explorer-item-active' : ''
|
||||
}`}
|
||||
onClick={() => {
|
||||
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)`}
|
||||
>
|
||||
<span className="file-explorer-icon" style={{ color: '#ffd60a' }}>
|
||||
<FileIcon name="velxio.json" />
|
||||
<FileIcon name="libraries.json" />
|
||||
</span>
|
||||
<span className="file-explorer-name">velxio.json</span>
|
||||
<span className="file-explorer-name">libraries.json</span>
|
||||
<span
|
||||
style={{
|
||||
marginLeft: 'auto',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
getInstalledLibraries,
|
||||
getCustomLibraries,
|
||||
deleteCustomLibrary,
|
||||
uninstallLibrary,
|
||||
} from '../../services/libraryService';
|
||||
import type { ArduinoLibrary, InstalledLibrary } from '../../services/libraryService';
|
||||
import { trackInstallLibrary } from '../../utils/analytics';
|
||||
|
|
@ -218,27 +217,6 @@ export const LibraryManagerModal: React.FC<LibraryManagerModalProps> = ({ 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<LibraryManagerModalProps> = ({ isOpen
|
|||
{busy ? '…' : '+ Add to project'}
|
||||
</button>
|
||||
)}
|
||||
{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 && (
|
||||
<button
|
||||
className="lib-uninstall-btn"
|
||||
onClick={() => (row.custom ? removeCustom(row.name) : uninstall(row.name))}
|
||||
onClick={() => removeCustom(row.name)}
|
||||
disabled={busy}
|
||||
title={row.custom ? 'Remove your custom upload' : 'Uninstall (free the cache)'}
|
||||
title="Remove your custom upload"
|
||||
>
|
||||
{busy ? '…' : row.custom ? 'Remove' : 'Uninstall'}
|
||||
{busy ? '…' : 'Remove'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<EditorState>((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<EditorState>((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<EditorState>((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 },
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue