Table of Contents

File Explorer Page

The File Explorer page is a server-side filesystem browser covering three named roots:

  • AdminDirectory — server-wide admin area.
  • ProjectDirectory — the currently-loaded project directory (omitted from the root list when no project is loaded).
  • ResourceDir — the shared resource tree under AdminDirectory + "Resource/".

wwwroot-src/src/components/FileExplorer.vue holds the whole browser — toolbar, tree, editor panel and preview. wwwroot-src/src/pages/FileExplorerPage.vue is a thin route wrapper around it, and wwwroot-src/src/components/widgets/FileExplorerDialog.vue mounts the same component in a modal as the app's file picker (see Picker Mode).

Note

Absolute filesystem paths never leave the server through the File Explorer's own endpoints. The /roots endpoint returns only { name, displayName }, and Common/FileExplorerController.cs routes every exception message through a scrub that replaces root prefixes with the tokens <Admin> / <Project> / <Resource> before returning it. That scrub is private to that controller, so it does not cover the STL preview endpoints the editor column also calls — see STL Preview Pane.

Layout

  • File Explorer Page
    • Toolbar
      • Root Selector Dropdown — display-name only, sourced from GET /roots; a caller that locks the root gets the name as plain text instead.
      • Up Button
      • Path Input — editable; Enter navigates.
      • Refresh / New Folder / New File / Upload Buttons.
      • Sort Dropdown — sort key (Name / Size / Modified / Type), Ascending / Descending, and a “Folders first” checkbox.
      • Editor Panel Toggle (edit_note pencil) — shows or hides the editor slave panel.
    • Breadcrumb Row — single-click navigation up the tree, plus a file-type filter select whenever the host supplies filters. The page supplies none, so the select belongs to picker mode.
    • Explorer | Editor Splitter — the divider collapses to zero width while the editor panel is hidden.
      • Listing Tree (<q-tree>) — lazy: a folder lists its children the first time it is expanded. Each row is icon + name + a fixed-width size / modified tail, with the hover action strip laid over that tail so nothing reflows when the pointer enters a row. Ordering follows the toolbar's sort control.
        • Row select:
          • Directory → expand it and make it the current directory (the toolbar ops and the URL follow).
          • Text file → load it into the editor panel; .stl → open the STL preview in that same slot. Both only while the panel is shown.
        • Row double-click on a file opens it in the editor panel — or the preview, for an .stl — even when the panel is hidden. This is the gesture that opens a file for editing; the action strip deliberately carries no Edit button, since a second pencil beside Rename's drive_file_rename_outline would be two near-identical icons meaning different things. The empty editor's own hint has not followed that decision — it reads "Click a file (or its Edit action) to load it here.", naming an action no row offers.
        • Actions: Download (files), Download ZIP (folders), Extract ZIP (.zip only), Rename, Duplicate, Delete (double-click to confirm).
        • Pick Column (picker mode only) — a radio or a checkbox on every pickable row, rendered as an empty cell elsewhere so unpickable rows keep the same indent.
      • Editor Slave Panel (right)
        • Bar — Root / relative/path label with a red * while the buffer is dirty, language select, Auto Save checkbox, Save button (disabled while Auto Save is on), close icon.
        • Body — <TextEditor> wrapping CodeMirror 6, filling the panel.
        • STL Preview — an .stl row hands this slot to <StlPreviewPane>, a server-rendered 3D view that replaces the editor's bar and body and stays up until it is closed; see STL Preview Pane. The text buffer and any unsaved edits survive underneath and return when the preview closes.

Behavior

  • Path-traversal defence. Every request is resolved via Path.GetFullPath and validated with Hi.Common.PathUtils.PathUtil.IsDescendant(root, absolute) before any IO. Attempts like relativePath=../outside are rejected with HTTP 400.
  • Duplicate. POST /copy tries {name}-Copy-00 through {name}-Copy-19 and returns the first free slot; 400 if all 20 are taken.
  • UTF-8 without BOM. WriteText uses a cached new UTF8Encoding(encoderShouldEmitUTF8Identifier: false) so round-tripped files do not accumulate a 3-byte BOM on every save.
  • Binary gating. /read-text reports a binary file rather than refusing it: it answers content=null, isBinary=true, and the panel toasts. The client decides before the round-trip — an extension set (.stl, .zip, .dll, .exe, images, .pdf, Office documents, .sqlite / .db, …) keeps a click on a binary row from opening the editor at all, and .stl is routed to the STL preview instead.
  • Line endings. The buffer is LF-normalized on load, because CodeMirror 6 joins lines with LF and would otherwise echo a normalize-only change that falsely dirties a freshly opened CRLF file. Save converts back to the style the file was read with.
  • Auto Save. Checked, the panel writes 800 ms after typing stops and the Save button disables. Switching files, closing the panel and leaving the page each flush a pending write first; with Auto Save off, dirty edits raise a discard confirm instead, and settleEditorBeforeClose() keeps the user on the page when that confirm is cancelled.
  • Per-device view prefs. Editor shown, splitter position, Auto Save and the sort spec persist in localStorage under hinc.fileExplorer.viewPrefs.v1. Nothing is written until one of them is changed, and the page's own default is editor shown — so a first visit, a private window or a cleared browser all open with the editor panel up and single-click file opening already live. Dialog instances seed the splitter, Auto Save and the sort from that blob and write those three back, but they neither read nor write the panel-visibility part: a picker always opens with the editor panel hidden, and a one-off edit inside one does not rearrange the page.

Picker Mode

The same component runs inside wwwroot-src/src/components/widgets/FileExplorerDialog.vue, which adds an apply bar and emits each pick as "{rootName}:{relativePath}". Props steer it: pickable (file / folder / any / none) turns the pick column on, multi swaps radios for checkboxes, filters supplies the file-type dropdown (“All Files” is appended when the caller's list omits it), lockRoot and allowedRoots narrow the root switcher, and mode: 'save' turns the path input into a save target with a forcedExtension appended on confirm.

That dialog is how the app opens and saves files: the menu bar's Project ▾ entries, the Object Management Menu Button, FilePathInput, the Mission command panels, the Machine Tool page, and the Mechanism Builder's Load and both Save As actions.

Syntax Highlighting

wwwroot-src/src/components/widgets/TextEditor.vue wraps CodeMirror 6 so the editor gets real syntax highlighting for the common project-file formats listed below.

Stack:

  • codemirror + @codemirror/state + @codemirror/view (core).
  • @codemirror/lang-xml + @codemirror/lang-json + @codemirror/lang-markdown + @replit/codemirror-lang-csharp + the in-repo mission-script mode from wwwroot-src/src/components/widgets/missionScriptLanguage.ts (5 language modes).
  • @codemirror/autocomplete, wired only when a completion source is supplied.

Props: modelValue (two-way), language: 'xml' | 'json' | 'markdown' | 'csharp' | 'mission-script' | 'text' (default text), readonly, and completionSource — an optional CodeMirror completion source that overrides the language-provided completions, read once at mount. Language and readonly are swapped via Compartment.reconfigure, so the view never has to tear down on mode change.

Extension → language mapping lives in wwwroot-src/src/components/widgets/editorLanguage.ts (single source of truth imported by both TextEditor.vue and FileExplorer.vue):

Language Extensions
xml xml, hincproj, CoatingMaterial, CutterMaterial, Holder, WorkpieceMaterial, mp, MillingPara, SpindleCapability, StickMachiningTool, GeneralMechanism, general-mech, MachineTool, mt, Controller, SoftNcRunner, Cutter, CoolantHeatCondition, MachiningToolHouse, Fixture
markdown md, markdown
json json
csharp cs, csx
mission-script none — the mode is picked from the editor's language select, and wwwroot-src/src/components/mission/ScriptCommandPanel.vue sets it directly on its own editor
text (fallback) everything else, including .nc / .ptp / .mpf / .h / .csv

The language select in the editor bar offers all six modes, so an auto-detected mode can be overridden per file.

Source Code Path

See HiNC App Anatomy for git repository links.

HiNC-2025-webservice (Quasar CLI SPA):

  • wwwroot-src/src/components/FileExplorer.vue — the browser itself: toolbar, sort control, breadcrumb + filter, the lazy tree with its hover actions, the editor slave panel with Auto Save, the STL preview slot, and the view prefs.
  • wwwroot-src/src/pages/FileExplorerPage.vue — routed page at /util/file-explorer; parses and mirrors the location in the URL and guards the route leave.
  • wwwroot-src/src/components/widgets/FileExplorerDialog.vue — modal wrapper: apply bar, save mode, "{rootName}:{relativePath}" picks.
  • wwwroot-src/src/components/explorerSort.ts — sort keys, comparators, DEFAULT_SORT and coerceSortSpec behind the Sort dropdown.
  • wwwroot-src/src/components/widgets/fileFilter.ts — the FileFilter shape driving the file-type select.
  • wwwroot-src/src/utils/collapsibleSplit.ts — collapses the editor divider while the panel is hidden.
  • wwwroot-src/src/components/StlPreviewPane.vue — the 3D preview that takes over the panel for .stl files, over Disp/StlPreviewController.cs.
  • wwwroot-src/src/components/widgets/TextEditor.vue — CodeMirror 6 wrapper.
  • wwwroot-src/src/components/widgets/editorLanguage.ts — extension → EditorLanguage mapping.
  • wwwroot-src/src/components/widgets/missionScriptLanguage.ts — the in-repo mission-script mode.
  • wwwroot-src/src/api/fileExplorer.ts — typed wrapper over /api/file-explorer/*.
  • wwwroot-src/src/router/routes.ts/util/file-explorer entry (route name util-file-explorer).
  • wwwroot-src/src/components/AppMenuBar.vuePage → File Explorer entry, below the separator that follows the three workflow pages.
  • Common/NamedRootResolver.cs — resolves AdminDirectory / ProjectDirectory / ResourceDir; shared with every other controller that reads or writes under a named root.
  • Common/FileExplorerController.cs — REST endpoints under /api/file-explorer:
Method Path Notes
GET /roots List available roots; Project is omitted when no project is loaded. Returns display names only.
GET /list?rootName=&relativePath= Directories-first, name-ascending listing; the client re-sorts it per the toolbar's sort control.
GET /read-text?… UTF-8 text; when the file is detected as binary returns content=null, isBinary=true.
POST /write-text Body {rootName, relativePath, content}; creates parents as needed.
POST /create-file Fails if target exists.
POST /create-directory Recursive create; no-op if already a directory.
POST /rename Body {rootName, relativePath, newRelativePath}; refuses cross-root moves.
POST /copy {name}-Copy-00..-Copy-19; uses Hi.Common.FileLines.FileUtil.CopyDirectory for directories.
DELETE /delete?… Files → delete, directories → recursive; refuses to delete the root itself.
GET /download?… Streams a file as application/octet-stream.
GET /download-zip?… Zips a directory in-memory and streams application/zip.
POST /upload?… Multipart single-file upload; overwrites target.
POST /extract-zip Body {rootName, relativePath}; extracts next to the archive into <name>/.

Addressing

The browsed location is mirrored into the URL by an optional catch-all: /util/file-explorer/{RootTitle}/{relative/path}. The bare /util/file-explorer still resolves through the named route the menu uses, so both a deep link and a plain menu click land correctly. Browsing rewrites the URL with router.replace, and an external URL change — a paste, a bookmark, browser back — drives the explorer the other way.

The named roots (Admin / Project / Resource) are the only addressing the client sees, and the absolute path is formed and kept on the server — subject to the error-message caveat noted at the head of this page.

See Also

  • File Explorer (manual) — the end-user task: the roots, and renaming a file to move it

  • Object Management Menu Button — shares the XML editor dialog pattern and targets the same project / resource folders through a different endpoint family.

  • Mechanism Builder Page — drives its Load and both Save As operations through this browser's dialog wrapper.

  • STL Preview Pane — the 3D preview this page's editor column hands over to for an .stl row.