Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@
},
"dependencies": {
"@lit/context": "^1.1.6",
"data-explorer-core": "github:mathworks/data-explorer-core#v1.39.0",
"data-explorer-core": "github:mathworks/data-explorer-core#v1.40.0",
"fast-xml-parser": "^5.8.0",
"fflate": "^0.8.3",
"highlight.js": "^11.11.1",
Expand Down
29 changes: 20 additions & 9 deletions src/host/SlddModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,16 +232,27 @@ export function findNode(uriString: string, nodeId: string): any | null {
// model in DataModel under a prefixed srcId but NOT in this module's `cache`,
// so gating on `cache.get(uriString)` here would wrongly drop their selections
// (the Property Inspector would never render). Try the registry unconditionally.
// PRECONDITION (untested) for the `: null` arm: the pinned data-explorer-core
// always exports findNodeById on DataModel. The feature-detect exists because
// core is a git-pinned dependency bumped independently of this repo, so an older
// pin must degrade to the cache fallback below rather than throw on every
// selection; the `try` covers the same risk for a throwing implementation.
// The pinned data-explorer-core always exports findNodeById on DataModel. The
// feature-detect exists because core is a git-pinned dependency bumped
// independently of this repo, so an older pin must degrade to the cache fallback
// below rather than throw on every selection (test/findNodeSharedName.test.ts);
// the `try` covers the same risk for a throwing implementation.
//
// A null from the registry is FINAL when the registry holds this uri's source. Core
// answers null for an id that names two nodes (F1-50: two same-named entries in one
// section, and every row inside them), because it cannot tell which one a row meant.
// Every adder here registers under the uri and caches the very tree it registered, so
// flattening the cache would find the FIRST twin, the wrong-entry pick core refuses,
// and a cell edit, a child delete, a copy, a drag and the Property Inspector all act
// on what this returns. So the cache answers only what the registry cannot: an old
// pin without findNodeById, a lookup that threw, or a source it no longer holds.
try {
const viaRegistry = (DataModel as any).findNodeById
? (DataModel as any).findNodeById(nodeId)
: null;
if (viaRegistry) return viaRegistry;
const session = DataModel as any;
if (typeof session.findNodeById === 'function') {
const viaRegistry = session.findNodeById(nodeId);
if (viaRegistry) return viaRegistry;
if (session.hasDataSource?.(uriString)) return null;
}
} catch {
/* fall through */
}
Expand Down
26 changes: 21 additions & 5 deletions src/host/parseWarnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ import type { ParseWarning } from 'data-explorer-core';
/** The `code`s that describe the whole source rather than one piece of it. */
const SOURCE_LEVEL = new Set(['source-empty', 'source-unreadable']);

/**
* The `code` core's session adds at open for siblings that share a name, and so one node
* id (F1-50). Nothing was lost: both are read and both are shown, but neither can be edited
* or deleted by id until the file names them apart. So it is never counted as a part that
* could not be read, and never a reason to refuse the file.
*/
const SHARED_NAME = 'duplicate-id';

/** The headline when the only thing to say is that some names are shared. */
const SHARED_NAME_HEADLINE = 'Some items in this file share a name, so they cannot be edited or deleted here.';

/**
* How many individual messages the banner spells out. A `.mat` with a truncated
* record tail can report a warning per variable, and a banner tall enough to hold
Expand Down Expand Up @@ -91,8 +102,10 @@ export function sourceWarnings(node: unknown): ParseWarning[] {
*
* The headline is a source-level warning's own message when there is one, because
* core writes those as a whole sentence about the whole file ("…so this project
* reads as empty") and that is the larger fact; otherwise it counts the parts. The
* details are core's messages verbatim — each already names what it lost, so the
* reads as empty") and that is the larger fact; otherwise it counts the parts that
* could not be read. A shared name (`duplicate-id`) is not one of those: it gets a
* headline of its own when it is all there is, and otherwise only a line below. The
* details are core's messages verbatim — each already names what it is about, so the
* `part` field is used to tell two warnings apart here and never rendered.
*/
export function warningBanner(warnings: ParseWarning[] | undefined): WarningBanner | undefined {
Expand All @@ -115,11 +128,14 @@ export function warningBanner(warnings: ParseWarning[] | undefined): WarningBann

const sourceLevel = unique.find((w) => SOURCE_LEVEL.has(w.code));
const rest = unique.filter((w) => w !== sourceLevel);
const lost = rest.filter((w) => w.code !== SHARED_NAME).length;
const headline = sourceLevel
? sourceLevel.message
: rest.length === 1
? 'One part of this file could not be read, so what you see below is incomplete.'
: `${rest.length} parts of this file could not be read, so what you see below is incomplete.`;
: lost === 0
? SHARED_NAME_HEADLINE
: lost === 1
? 'One part of this file could not be read, so what you see below is incomplete.'
: `${lost} parts of this file could not be read, so what you see below is incomplete.`;

const details = rest.slice(0, MAX_BANNER_DETAILS).map((w) => w.message);
const hidden = rest.length - details.length;
Expand Down
11 changes: 7 additions & 4 deletions src/host/rowBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ export const COLUMN_LABELS: Record<string, string> = {
// columns (dimensions/complexity/dimensionsMode → Data Object;
// storageClass/headerFile/alignment → Code Generation) all get their picker
// header here. Read-only for most classes, but not by definition: on a
// Simulink.BusElement, complexity and dimensionsMode emit an editable select over
// MATLAB's own enum, which the generic cell path renders and edits like any other
// object cell. Labels still come from the schema (schemaColumnLabels), which
// remains the single source of truth for a column's display name.
// Simulink.BusElement, a Simulink.ValueType and a Simulink.Signal, complexity and
// dimensionsMode emit an editable select over MATLAB's own enum for that class,
// and on a Simulink.Parameter or Simulink.Signal whose StorageClass is not 'Auto',
// alignment emits an editable text cell. The generic cell path renders and edits
// each like any other object cell.
// Labels still come from the schema (schemaColumnLabels), which remains the
// single source of truth for a column's display name.
export const COLUMN_GROUPS: Record<string, string> = {
Min: 'Data Object', Max: 'Data Object', Unit: 'Data Object',
dimensions: 'Data Object', complexity: 'Data Object', dimensionsMode: 'Data Object',
Expand Down
21 changes: 17 additions & 4 deletions src/host/sectionRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@
// needs to PREDICT a drop locally (dropDecision) without a host round-trip on
// every dragover: the section name, its human label, whether it is derived
// (architectural), and the classes it accepts. It is posted to each webview
// alongside its rows, and mirrors SectionNode's allow-list + the section→
// isderived mapping so the webview's prediction matches what the host paste
// would actually do.
// alongside its rows, and mirrors the section→isderived mapping and the classes
// SectionNode.allowsType admits, so the webview's prediction matches what the
// host paste would actually do.
//
// `allowedTypes` feeds only that prediction: the drop cursor and the Paste item.
// It is what a paste ADMITS (getAdmittedTypes), not what may be CREATED
// (getAllowedTypes). The two differ by the registered subclasses: a paste or drop
// of an mpt.Parameter or mpt.Signal the dictionary already holds lands in Design
// Data, but none can be created there, so the Add gallery keeps reading
// getAllowedTypes. A core without getAdmittedTypes admits exactly its creation
// list, which is the fallback.
import { getSectionMetadata } from 'data-explorer-core';

export interface SectionRule {
Expand All @@ -22,6 +30,11 @@ export function sectionRules(model: any): SectionRule[] {
sectionName: s.name,
sectionLabel: s.displayName ?? s.name,
isDerived: getSectionMetadata(s.name).isderived === '1',
allowedTypes: typeof s.getAllowedTypes === 'function' ? s.getAllowedTypes() : [],
allowedTypes:
typeof s.getAdmittedTypes === 'function'
? s.getAdmittedTypes()
: typeof s.getAllowedTypes === 'function'
? s.getAllowedTypes()
: [],
}));
}
Loading
Loading