Skip to content

Commit c4dab4b

Browse files
authored
Merge pull request #245 from mi-examples/feat/variables-editor-import-values
Variables Editor: import values from another page
2 parents 1983d3d + 6db8edf commit c4dab4b

14 files changed

Lines changed: 10097 additions & 6924 deletions

package-lock.json

Lines changed: 2335 additions & 1603 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api/page.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BaseAPI } from './base.js';
33
import { unavailablePageDataError } from './unavailable-json-api.js';
44

55
export interface Page {
6+
id: number;
67
name: string;
78
enabled: 'Y' | 'N';
89
visible_in_homepage: 'Y' | 'N';
@@ -62,7 +63,7 @@ export class PageAPI extends BaseAPI {
6263
).data;
6364
}
6465

65-
async create(page: Page, headers?: Headers) {
66+
async create(page: Omit<Page, 'id'>, headers?: Headers) {
6667
return (
6768
await this.axios.post<{ page: Page }>('/api/page', page, {
6869
withCredentials: true,

src/lib/page-variables-diff.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,96 @@ export function buildPageVariablesExport(
304304
}));
305305
}
306306

307+
export interface ImportCandidate {
308+
name: string;
309+
sourceValue: string;
310+
tagType: string;
311+
}
312+
313+
export interface ImportSkip {
314+
name: string;
315+
sourceValue: string;
316+
reason: string;
317+
}
318+
319+
export interface PageVariablesImportPlan {
320+
importable: ImportCandidate[];
321+
skipped: ImportSkip[];
322+
}
323+
324+
/**
325+
* Whether `value` can structurally apply to `tag`'s type — a hard yes/no, unlike
326+
* `validateValueAgainstTag()`'s always-a-warning checks. Deliberately skips select/multiselect's
327+
* declared-option check: two pages can legitimately point a same-named select at different
328+
* datasets/sources, so an option-list mismatch doesn't mean the value can't apply.
329+
*/
330+
function typeCompatibilityIssue(tag: TemplateVariableTag, value: string): string | null {
331+
switch (tag.tag_type) {
332+
case 'boolean':
333+
return BOOLEAN_ALLOWED_VALUES.includes(value)
334+
? null
335+
: `Not a recognized boolean value ("${value}").`;
336+
337+
case 'color':
338+
return value === '' || COLOR_PATTERN.test(value) ? null : `Not a recognized color value ("${value}").`;
339+
340+
case 'list': {
341+
if (value === '') {
342+
return null;
343+
}
344+
345+
try {
346+
const parsed = JSON.parse(value);
347+
348+
return parsed === null || Array.isArray(parsed) ? null : 'Value is valid JSON but not an array.';
349+
} catch {
350+
return 'Value is not valid JSON.';
351+
}
352+
}
353+
354+
default:
355+
return null;
356+
}
357+
}
358+
359+
/**
360+
* Plans an import of `sourceValues` (another page's live variable values) into the current
361+
* page's schema: matches by variable name first (anything unmatched is skipped outright), then
362+
* checks the matched value against the target tag's type. Nothing here writes anything — the
363+
* caller decides what to actually apply.
364+
*/
365+
export function planPageVariablesImport(
366+
schema: TemplateVariablesSchema | null,
367+
sourceValues: PageVariableEntry[],
368+
): PageVariablesImportPlan {
369+
const schemaMap = new Map<string, TemplateVariableTag>((schema?.tags ?? []).map((tag) => [tag.name, tag]));
370+
const importable: ImportCandidate[] = [];
371+
const skipped: ImportSkip[] = [];
372+
373+
for (const entry of sourceValues) {
374+
const tag = schemaMap.get(entry.name);
375+
376+
if (!tag) {
377+
skipped.push({
378+
name: entry.name,
379+
sourceValue: entry.value,
380+
reason: "Not in the current page's schema.",
381+
});
382+
383+
continue;
384+
}
385+
386+
const issue = typeCompatibilityIssue(tag, entry.value);
387+
388+
if (issue) {
389+
skipped.push({ name: entry.name, sourceValue: entry.value, reason: issue });
390+
391+
continue;
392+
}
393+
394+
importable.push({ name: entry.name, sourceValue: entry.value, tagType: tag.tag_type || 'text' });
395+
}
396+
397+
return { importable, skipped };
398+
}
399+

src/lib/pp.middleware.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import axios, { Axios } from 'axios';
22
import http from 'node:http';
33
import https from 'node:https';
44
import { JSDOM } from 'jsdom';
5-
import { AssetsAPI, PageAPI, AssetsV7API, PageTemplateAPI, PageVariableAPI, PageVariableTagEntry } from '../api/index.js';
5+
import { AssetsAPI, Page, PageAPI, AssetsV7API, PageTemplateAPI, PageVariableAPI, PageVariableTagEntry } from '../api/index.js';
66
import { isUnavailableJsonApiError } from '../api/unavailable-json-api.js';
77
import { createLogger } from './logger.js';
88
import { colors, getTokenErrorInfo, logTokenError } from './helpers/index.js';
@@ -444,6 +444,28 @@ export class MiAPI {
444444
);
445445
}
446446

447+
/**
448+
* List every portal page (`/api/page`) — used by the Variables Editor's "import values from
449+
* another page" picker to offer candidate source pages.
450+
*
451+
* @param headers
452+
*/
453+
async listPages(headers: Headers = this.#headers): Promise<Page[]> {
454+
return this.pageApi.getAll(this.#clearHeaders(headers));
455+
}
456+
457+
/**
458+
* Get another page's live variable values via `/api/page_variable`, by numeric page id.
459+
* Unlike `getLivePageVariables()`, this deliberately does not touch `this.appId` — it's for
460+
* reading a *different* page's values (e.g. to import from), not the current page's.
461+
*
462+
* @param pageId
463+
* @param headers
464+
*/
465+
async getPageVariablesFor(pageId: number, headers: Headers = this.#headers): Promise<PageVariableTagEntry[]> {
466+
return this.pageVariableApi.getById(pageId, this.#clearHeaders(headers));
467+
}
468+
447469
/** Whether this page has no associated template — no `__template_variables.json`, no page variables. */
448470
get isTemplateLess(): boolean {
449471
return !!this.templateLess;

0 commit comments

Comments
 (0)