@memberjunction/ng-shared 5.27.0 → 5.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/home-pin.service.d.ts.map +1 -1
- package/dist/lib/home-pin.service.js +15 -0
- package/dist/lib/home-pin.service.js.map +1 -1
- package/dist/lib/home-pin.types.d.ts +18 -0
- package/dist/lib/home-pin.types.d.ts.map +1 -1
- package/dist/lib/home-pin.types.js.map +1 -1
- package/package.json +11 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home-pin.service.d.ts","sourceRoot":"","sources":["../../src/lib/home-pin.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAGvC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;;AAKxF;;;;;GAKG;AACH,qBACa,iBAAiB;IAC5B,mDAAmD;IACnD,SAAgB,KAAK,uCAAgD;IAErE,OAAO,CAAC,MAAM,CAAS;IAMvB;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB/B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IASjC;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO;IAkBvC;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM9B;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAQzD;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAS5C;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAIxE;;OAEG;IACH,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,GAAG,SAAS;IAQ7F;;OAEG;IACH,gBAAgB,IAAI,iBAAiB,EAAE;IAIvC;;OAEG;IACH,SAAS,IAAI,MAAM,EAAE;IAcrB;;OAEG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,iBAAiB,EAAE;IAUtD;;;;;OAKG;IACG,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAmBzE,OAAO,CAAC,eAAe;
|
|
1
|
+
{"version":3,"file":"home-pin.service.d.ts","sourceRoot":"","sources":["../../src/lib/home-pin.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAGvC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;;AAKxF;;;;;GAKG;AACH,qBACa,iBAAiB;IAC5B,mDAAmD;IACnD,SAAgB,KAAK,uCAAgD;IAErE,OAAO,CAAC,MAAM,CAAS;IAMvB;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB/B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IASjC;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO;IAkBvC;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM9B;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAQzD;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAS5C;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAIxE;;OAEG;IACH,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,GAAG,SAAS;IAQ7F;;OAEG;IACH,gBAAgB,IAAI,iBAAiB,EAAE;IAIvC;;OAEG;IACH,SAAS,IAAI,MAAM,EAAE;IAcrB;;OAEG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,iBAAiB,EAAE;IAUtD;;;;;OAKG;IACG,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAmBzE,OAAO,CAAC,eAAe;IA+CvB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,UAAU;yCA9OP,iBAAiB;6CAAjB,iBAAiB;CA0P7B"}
|
|
@@ -196,6 +196,21 @@ export class HomeAppPinService {
|
|
|
196
196
|
const sameQP = pinQP === configQP;
|
|
197
197
|
return sameApp && sameNav && sameRoute && sameQP;
|
|
198
198
|
}
|
|
199
|
+
case 'Actions': {
|
|
200
|
+
// A pinned Action is unique per (actionId + preset-params + runtime-params + title).
|
|
201
|
+
// Two pins targeting the same action with different preset configs are allowed —
|
|
202
|
+
// e.g. "Weekly Report — Sales" vs. "Weekly Report — Ops" both pin "Run Report".
|
|
203
|
+
if (pin.Configuration['actionId'] !== config['actionId'])
|
|
204
|
+
return false;
|
|
205
|
+
const pinPreset = JSON.stringify(pin.Configuration['presetParams'] ?? {});
|
|
206
|
+
const configPreset = JSON.stringify(config['presetParams'] ?? {});
|
|
207
|
+
const pinRuntime = JSON.stringify(pin.Configuration['runtimeParamNames'] ?? []);
|
|
208
|
+
const configRuntime = JSON.stringify(config['runtimeParamNames'] ?? []);
|
|
209
|
+
const samePreset = pinPreset === configPreset;
|
|
210
|
+
const sameRuntime = pinRuntime === configRuntime;
|
|
211
|
+
const sameTitle = pin.DisplayName === (config['displayName'] ?? pin.DisplayName);
|
|
212
|
+
return samePreset && sameRuntime && sameTitle;
|
|
213
|
+
}
|
|
199
214
|
default:
|
|
200
215
|
return false;
|
|
201
216
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home-pin.service.js","sourceRoot":"","sources":["../../src/lib/home-pin.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { BehaviorSubject } from 'rxjs';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\nimport { toJpeg } from 'html-to-image';\nimport { HomeAppPinnedItem, HomeAppPinInput, HomeAppPinUpdate } from './home-pin.types';\n\n/** UserSettings key for persisting pins */\nconst PINS_SETTING_KEY = 'HomeApp.PinnedItems';\n\n/**\n * Service for managing pinned items on the Home dashboard.\n *\n * All pin state is stored as a JSON blob in UserSettings via UserInfoEngine.\n * Uses debounced saving to avoid excessive DB writes during edit mode operations.\n */\n@Injectable({ providedIn: 'root' })\nexport class HomeAppPinService {\n /** Observable of current pins (for reactive UI) */\n public readonly Pins$ = new BehaviorSubject<HomeAppPinnedItem[]>([]);\n\n private loaded = false;\n\n // =============================================\n // LOADING\n // =============================================\n\n /**\n * Load pins from UserSettings. Safe to call multiple times (loads only once).\n */\n async LoadPins(): Promise<void> {\n if (this.loaded) return;\n\n const raw = UserInfoEngine.Instance.GetSetting(PINS_SETTING_KEY);\n if (raw) {\n try {\n const pins = JSON.parse(raw) as HomeAppPinnedItem[];\n this.Pins$.next(this.sortPins(pins));\n } catch (err) {\n console.warn('HomeAppPinService: Failed to parse saved pins, starting fresh', err);\n this.Pins$.next([]);\n }\n }\n this.loaded = true;\n }\n\n /**\n * Force reload from UserSettings (e.g. after external changes)\n */\n async ReloadPins(): Promise<void> {\n this.loaded = false;\n await this.LoadPins();\n }\n\n // =============================================\n // CRUD OPERATIONS\n // =============================================\n\n /**\n * Add a new pin. Returns false if a duplicate already exists.\n */\n AddPin(input: HomeAppPinInput): boolean {\n if (this.IsPinned(input.ResourceType, input.Configuration)) {\n return false;\n }\n\n const pins = [...this.Pins$.value];\n const newPin: HomeAppPinnedItem = {\n ...input,\n Id: this.generateId(),\n Sequence: pins.length,\n PinnedAt: new Date().toISOString()\n };\n\n pins.push(newPin);\n this.updateAndSave(pins);\n return true;\n }\n\n /**\n * Remove a pin by ID\n */\n RemovePin(pinId: string): void {\n const pins = this.Pins$.value.filter(p => p.Id !== pinId);\n this.resequence(pins);\n this.updateAndSave(pins);\n }\n\n /**\n * Update pin properties (name, description, icon, group, thumbnail)\n */\n UpdatePin(pinId: string, updates: HomeAppPinUpdate): void {\n const pins = this.Pins$.value.map(p => {\n if (p.Id !== pinId) return p;\n return { ...p, ...updates };\n });\n this.updateAndSave(pins);\n }\n\n /**\n * Reorder pins. Accepts full reordered array with updated Sequence values.\n */\n ReorderPins(pins: HomeAppPinnedItem[]): void {\n this.resequence(pins);\n this.updateAndSave(pins);\n }\n\n // =============================================\n // DUPLICATE DETECTION\n // =============================================\n\n /**\n * Check if a resource is already pinned\n */\n IsPinned(resourceType: string, config: Record<string, unknown>): boolean {\n return this.Pins$.value.some(pin => this.matchesResource(pin, resourceType, config));\n }\n\n /**\n * Find the pin for a given resource, if it exists\n */\n FindPin(resourceType: string, config: Record<string, unknown>): HomeAppPinnedItem | undefined {\n return this.Pins$.value.find(pin => this.matchesResource(pin, resourceType, config));\n }\n\n // =============================================\n // HELPERS\n // =============================================\n\n /**\n * Get ungrouped pins (Group is null/undefined/empty)\n */\n GetUngroupedPins(): HomeAppPinnedItem[] {\n return this.Pins$.value.filter(p => !p.Group);\n }\n\n /**\n * Get unique group names in display order (ordered by lowest Sequence in group)\n */\n GetGroups(): string[] {\n const groupMinSeq = new Map<string, number>();\n for (const pin of this.Pins$.value) {\n if (!pin.Group) continue;\n const current = groupMinSeq.get(pin.Group);\n if (current === undefined || pin.Sequence < current) {\n groupMinSeq.set(pin.Group, pin.Sequence);\n }\n }\n return [...groupMinSeq.entries()]\n .sort((a, b) => a[1] - b[1])\n .map(([name]) => name);\n }\n\n /**\n * Get pins in a specific group\n */\n GetPinsInGroup(groupName: string): HomeAppPinnedItem[] {\n return this.Pins$.value\n .filter(p => p.Group === groupName)\n .sort((a, b) => a.Sequence - b.Sequence);\n }\n\n // =============================================\n // THUMBNAIL CAPTURE\n // =============================================\n\n /**\n * Capture a thumbnail of a DOM element using html-to-image.\n * Uses SVG foreignObject so the browser's own CSS engine handles\n * modern features like color-mix() that html2canvas can't parse.\n * Returns a base64 JPEG data URL (~5-15KB) or undefined on failure.\n */\n async CaptureThumbnail(element: HTMLElement): Promise<string | undefined> {\n try {\n if (element.clientWidth === 0 || element.clientHeight === 0) {\n return undefined;\n }\n return await toJpeg(element, {\n quality: 0.6,\n pixelRatio: 0.2,\n cacheBust: true,\n });\n } catch {\n return undefined;\n }\n }\n\n // =============================================\n // PRIVATE\n // =============================================\n\n private matchesResource(pin: HomeAppPinnedItem, resourceType: string, config: Record<string, unknown>): boolean {\n if (pin.ResourceType !== resourceType) return false;\n switch (resourceType) {\n case 'Dashboards':\n return pin.Configuration['dashboardId'] === config['dashboardId'];\n case 'User Views':\n return pin.Configuration['viewId'] === config['viewId'];\n case 'Queries':\n return pin.Configuration['queryId'] === config['queryId'];\n case 'Reports':\n return pin.Configuration['reportId'] === config['reportId'];\n case 'Records':\n return pin.Configuration['Entity'] === config['Entity']\n && pin.Configuration['recordId'] === config['recordId'];\n case 'Custom': {\n // Match by app name + nav item name + route — never use appId or driverClass\n // Include route to differentiate same nav item with different URL paths\n const sameApp = pin.Configuration['appName'] === config['appName'];\n const sameNav = pin.Configuration['navItemName'] === config['navItemName'];\n const pinRoute = pin.Configuration['route'] as string || '';\n const configRoute = config['route'] as string || '';\n const sameRoute = pinRoute === configRoute;\n // Also compare queryParams if present\n const pinQP = JSON.stringify(pin.Configuration['queryParams'] || {});\n const configQP = JSON.stringify(config['queryParams'] || {});\n const sameQP = pinQP === configQP;\n return sameApp && sameNav && sameRoute && sameQP;\n }\n default:\n return false;\n }\n }\n\n private updateAndSave(pins: HomeAppPinnedItem[]): void {\n const sorted = this.sortPins(pins);\n this.Pins$.next(sorted);\n UserInfoEngine.Instance.SetSettingDebounced(\n PINS_SETTING_KEY,\n JSON.stringify(sorted)\n );\n }\n\n private sortPins(pins: HomeAppPinnedItem[]): HomeAppPinnedItem[] {\n return [...pins].sort((a, b) => a.Sequence - b.Sequence);\n }\n\n private resequence(pins: HomeAppPinnedItem[]): void {\n pins.forEach((pin, i) => { pin.Sequence = i; });\n }\n\n private generateId(): string {\n // Crypto.randomUUID is available in modern browsers\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"home-pin.service.js","sourceRoot":"","sources":["../../src/lib/home-pin.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { BehaviorSubject } from 'rxjs';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\nimport { toJpeg } from 'html-to-image';\nimport { HomeAppPinnedItem, HomeAppPinInput, HomeAppPinUpdate } from './home-pin.types';\n\n/** UserSettings key for persisting pins */\nconst PINS_SETTING_KEY = 'HomeApp.PinnedItems';\n\n/**\n * Service for managing pinned items on the Home dashboard.\n *\n * All pin state is stored as a JSON blob in UserSettings via UserInfoEngine.\n * Uses debounced saving to avoid excessive DB writes during edit mode operations.\n */\n@Injectable({ providedIn: 'root' })\nexport class HomeAppPinService {\n /** Observable of current pins (for reactive UI) */\n public readonly Pins$ = new BehaviorSubject<HomeAppPinnedItem[]>([]);\n\n private loaded = false;\n\n // =============================================\n // LOADING\n // =============================================\n\n /**\n * Load pins from UserSettings. Safe to call multiple times (loads only once).\n */\n async LoadPins(): Promise<void> {\n if (this.loaded) return;\n\n const raw = UserInfoEngine.Instance.GetSetting(PINS_SETTING_KEY);\n if (raw) {\n try {\n const pins = JSON.parse(raw) as HomeAppPinnedItem[];\n this.Pins$.next(this.sortPins(pins));\n } catch (err) {\n console.warn('HomeAppPinService: Failed to parse saved pins, starting fresh', err);\n this.Pins$.next([]);\n }\n }\n this.loaded = true;\n }\n\n /**\n * Force reload from UserSettings (e.g. after external changes)\n */\n async ReloadPins(): Promise<void> {\n this.loaded = false;\n await this.LoadPins();\n }\n\n // =============================================\n // CRUD OPERATIONS\n // =============================================\n\n /**\n * Add a new pin. Returns false if a duplicate already exists.\n */\n AddPin(input: HomeAppPinInput): boolean {\n if (this.IsPinned(input.ResourceType, input.Configuration)) {\n return false;\n }\n\n const pins = [...this.Pins$.value];\n const newPin: HomeAppPinnedItem = {\n ...input,\n Id: this.generateId(),\n Sequence: pins.length,\n PinnedAt: new Date().toISOString()\n };\n\n pins.push(newPin);\n this.updateAndSave(pins);\n return true;\n }\n\n /**\n * Remove a pin by ID\n */\n RemovePin(pinId: string): void {\n const pins = this.Pins$.value.filter(p => p.Id !== pinId);\n this.resequence(pins);\n this.updateAndSave(pins);\n }\n\n /**\n * Update pin properties (name, description, icon, group, thumbnail)\n */\n UpdatePin(pinId: string, updates: HomeAppPinUpdate): void {\n const pins = this.Pins$.value.map(p => {\n if (p.Id !== pinId) return p;\n return { ...p, ...updates };\n });\n this.updateAndSave(pins);\n }\n\n /**\n * Reorder pins. Accepts full reordered array with updated Sequence values.\n */\n ReorderPins(pins: HomeAppPinnedItem[]): void {\n this.resequence(pins);\n this.updateAndSave(pins);\n }\n\n // =============================================\n // DUPLICATE DETECTION\n // =============================================\n\n /**\n * Check if a resource is already pinned\n */\n IsPinned(resourceType: string, config: Record<string, unknown>): boolean {\n return this.Pins$.value.some(pin => this.matchesResource(pin, resourceType, config));\n }\n\n /**\n * Find the pin for a given resource, if it exists\n */\n FindPin(resourceType: string, config: Record<string, unknown>): HomeAppPinnedItem | undefined {\n return this.Pins$.value.find(pin => this.matchesResource(pin, resourceType, config));\n }\n\n // =============================================\n // HELPERS\n // =============================================\n\n /**\n * Get ungrouped pins (Group is null/undefined/empty)\n */\n GetUngroupedPins(): HomeAppPinnedItem[] {\n return this.Pins$.value.filter(p => !p.Group);\n }\n\n /**\n * Get unique group names in display order (ordered by lowest Sequence in group)\n */\n GetGroups(): string[] {\n const groupMinSeq = new Map<string, number>();\n for (const pin of this.Pins$.value) {\n if (!pin.Group) continue;\n const current = groupMinSeq.get(pin.Group);\n if (current === undefined || pin.Sequence < current) {\n groupMinSeq.set(pin.Group, pin.Sequence);\n }\n }\n return [...groupMinSeq.entries()]\n .sort((a, b) => a[1] - b[1])\n .map(([name]) => name);\n }\n\n /**\n * Get pins in a specific group\n */\n GetPinsInGroup(groupName: string): HomeAppPinnedItem[] {\n return this.Pins$.value\n .filter(p => p.Group === groupName)\n .sort((a, b) => a.Sequence - b.Sequence);\n }\n\n // =============================================\n // THUMBNAIL CAPTURE\n // =============================================\n\n /**\n * Capture a thumbnail of a DOM element using html-to-image.\n * Uses SVG foreignObject so the browser's own CSS engine handles\n * modern features like color-mix() that html2canvas can't parse.\n * Returns a base64 JPEG data URL (~5-15KB) or undefined on failure.\n */\n async CaptureThumbnail(element: HTMLElement): Promise<string | undefined> {\n try {\n if (element.clientWidth === 0 || element.clientHeight === 0) {\n return undefined;\n }\n return await toJpeg(element, {\n quality: 0.6,\n pixelRatio: 0.2,\n cacheBust: true,\n });\n } catch {\n return undefined;\n }\n }\n\n // =============================================\n // PRIVATE\n // =============================================\n\n private matchesResource(pin: HomeAppPinnedItem, resourceType: string, config: Record<string, unknown>): boolean {\n if (pin.ResourceType !== resourceType) return false;\n switch (resourceType) {\n case 'Dashboards':\n return pin.Configuration['dashboardId'] === config['dashboardId'];\n case 'User Views':\n return pin.Configuration['viewId'] === config['viewId'];\n case 'Queries':\n return pin.Configuration['queryId'] === config['queryId'];\n case 'Reports':\n return pin.Configuration['reportId'] === config['reportId'];\n case 'Records':\n return pin.Configuration['Entity'] === config['Entity']\n && pin.Configuration['recordId'] === config['recordId'];\n case 'Custom': {\n // Match by app name + nav item name + route — never use appId or driverClass\n // Include route to differentiate same nav item with different URL paths\n const sameApp = pin.Configuration['appName'] === config['appName'];\n const sameNav = pin.Configuration['navItemName'] === config['navItemName'];\n const pinRoute = pin.Configuration['route'] as string || '';\n const configRoute = config['route'] as string || '';\n const sameRoute = pinRoute === configRoute;\n // Also compare queryParams if present\n const pinQP = JSON.stringify(pin.Configuration['queryParams'] || {});\n const configQP = JSON.stringify(config['queryParams'] || {});\n const sameQP = pinQP === configQP;\n return sameApp && sameNav && sameRoute && sameQP;\n }\n case 'Actions': {\n // A pinned Action is unique per (actionId + preset-params + runtime-params + title).\n // Two pins targeting the same action with different preset configs are allowed —\n // e.g. \"Weekly Report — Sales\" vs. \"Weekly Report — Ops\" both pin \"Run Report\".\n if (pin.Configuration['actionId'] !== config['actionId']) return false;\n const pinPreset = JSON.stringify(pin.Configuration['presetParams'] ?? {});\n const configPreset = JSON.stringify(config['presetParams'] ?? {});\n const pinRuntime = JSON.stringify(pin.Configuration['runtimeParamNames'] ?? []);\n const configRuntime = JSON.stringify(config['runtimeParamNames'] ?? []);\n const samePreset = pinPreset === configPreset;\n const sameRuntime = pinRuntime === configRuntime;\n const sameTitle = pin.DisplayName === (config['displayName'] ?? pin.DisplayName);\n return samePreset && sameRuntime && sameTitle;\n }\n default:\n return false;\n }\n }\n\n private updateAndSave(pins: HomeAppPinnedItem[]): void {\n const sorted = this.sortPins(pins);\n this.Pins$.next(sorted);\n UserInfoEngine.Instance.SetSettingDebounced(\n PINS_SETTING_KEY,\n JSON.stringify(sorted)\n );\n }\n\n private sortPins(pins: HomeAppPinnedItem[]): HomeAppPinnedItem[] {\n return [...pins].sort((a, b) => a.Sequence - b.Sequence);\n }\n\n private resequence(pins: HomeAppPinnedItem[]): void {\n pins.forEach((pin, i) => { pin.Sequence = i; });\n }\n\n private generateId(): string {\n // Crypto.randomUUID is available in modern browsers\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n}\n"]}
|
|
@@ -30,6 +30,24 @@ export interface HomeAppPinnedItem {
|
|
|
30
30
|
/** ISO timestamp of when this was pinned */
|
|
31
31
|
PinnedAt: string;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Configuration shape for pins with ResourceType === 'Actions'.
|
|
35
|
+
* Stored inside HomeAppPinnedItem.Configuration as a plain object.
|
|
36
|
+
*/
|
|
37
|
+
export interface ActionPinConfiguration {
|
|
38
|
+
/** ID of the MJ Action to execute */
|
|
39
|
+
actionId: string;
|
|
40
|
+
/** Action name at pin time — kept for display in case the Action is renamed */
|
|
41
|
+
actionName: string;
|
|
42
|
+
/** Parameter values baked into the pin — passed every time the pin is invoked */
|
|
43
|
+
presetParams: Record<string, string>;
|
|
44
|
+
/** Names of ActionParams to prompt the user for when the pin is clicked */
|
|
45
|
+
runtimeParamNames: string[];
|
|
46
|
+
/** Hex accent color (e.g. '#4F46E5') used for the pin card background gradient */
|
|
47
|
+
accentColor?: string;
|
|
48
|
+
/** User's custom title (also stored on HomeAppPinnedItem.DisplayName for consistency) */
|
|
49
|
+
displayName?: string;
|
|
50
|
+
}
|
|
33
51
|
/**
|
|
34
52
|
* Input for creating a new pin (ID, Sequence, and PinnedAt are auto-generated)
|
|
35
53
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home-pin.types.d.ts","sourceRoot":"","sources":["../../src/lib/home-pin.types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,4BAA4B;IAC5B,EAAE,EAAE,MAAM,CAAC;IAEX,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IAEpB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,8FAA8F;IAC9F,YAAY,EAAE,MAAM,CAAC;IAErB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,4DAA4D;IAC5D,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEvC,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IAEjB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG,UAAU,GAAG,UAAU,CAAC,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,aAAa,GAAG,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"home-pin.types.d.ts","sourceRoot":"","sources":["../../src/lib/home-pin.types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,4BAA4B;IAC5B,EAAE,EAAE,MAAM,CAAC;IAEX,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IAEpB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,8FAA8F;IAC9F,YAAY,EAAE,MAAM,CAAC;IAErB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,4DAA4D;IAC5D,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEvC,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IAEjB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,2EAA2E;IAC3E,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG,UAAU,GAAG,UAAU,CAAC,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,aAAa,GAAG,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home-pin.types.js","sourceRoot":"","sources":["../../src/lib/home-pin.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * A pinned item on the Home dashboard.\n * Stored as JSON array in UserSettings key 'HomeApp.PinnedItems'.\n */\nexport interface HomeAppPinnedItem {\n /** Client-generated UUID */\n Id: string;\n\n /** User-editable display name (defaults to tab title at pin time) */\n DisplayName: string;\n\n /** Optional user-editable subtitle */\n Description?: string;\n\n /** FA icon class override. Null = auto-resolve from entity/app metadata */\n Icon?: string;\n\n /** Hex color override. Null = derive from source app's Color */\n Color?: string;\n\n /** MJ resource type: \"Dashboards\", \"User Views\", \"Reports\", \"Queries\", \"Records\", \"Custom\" */\n ResourceType: string;\n\n /** Source app ID (null = orphan / Home context) */\n ApplicationID?: string;\n\n /** Source app name for display badge (\"AI\", \"CRM\", etc.) */\n ApplicationName?: string;\n\n /** Full TabConfiguration blob - everything needed to re-open the resource */\n Configuration: Record<string, unknown>;\n\n /** Base64 JPEG thumbnail screenshot (~5-15KB at 0.2x scale). Null = use icon fallback */\n Thumbnail?: string;\n\n /** Display order within group (0-based) */\n Sequence: number;\n\n /** Optional group name for organizing pins. Null = ungrouped (shown first) */\n Group?: string;\n\n /** ISO timestamp of when this was pinned */\n PinnedAt: string;\n}\n\n/**\n * Input for creating a new pin (ID, Sequence, and PinnedAt are auto-generated)\n */\nexport type HomeAppPinInput = Omit<HomeAppPinnedItem, 'Id' | 'Sequence' | 'PinnedAt'>;\n\n/**\n * Fields that can be updated on an existing pin\n */\nexport type HomeAppPinUpdate = Partial<Pick<HomeAppPinnedItem, 'DisplayName' | 'Description' | 'Icon' | 'Color' | 'Group' | 'Thumbnail'>>;\n"]}
|
|
1
|
+
{"version":3,"file":"home-pin.types.js","sourceRoot":"","sources":["../../src/lib/home-pin.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * A pinned item on the Home dashboard.\n * Stored as JSON array in UserSettings key 'HomeApp.PinnedItems'.\n */\nexport interface HomeAppPinnedItem {\n /** Client-generated UUID */\n Id: string;\n\n /** User-editable display name (defaults to tab title at pin time) */\n DisplayName: string;\n\n /** Optional user-editable subtitle */\n Description?: string;\n\n /** FA icon class override. Null = auto-resolve from entity/app metadata */\n Icon?: string;\n\n /** Hex color override. Null = derive from source app's Color */\n Color?: string;\n\n /** MJ resource type: \"Dashboards\", \"User Views\", \"Reports\", \"Queries\", \"Records\", \"Custom\" */\n ResourceType: string;\n\n /** Source app ID (null = orphan / Home context) */\n ApplicationID?: string;\n\n /** Source app name for display badge (\"AI\", \"CRM\", etc.) */\n ApplicationName?: string;\n\n /** Full TabConfiguration blob - everything needed to re-open the resource */\n Configuration: Record<string, unknown>;\n\n /** Base64 JPEG thumbnail screenshot (~5-15KB at 0.2x scale). Null = use icon fallback */\n Thumbnail?: string;\n\n /** Display order within group (0-based) */\n Sequence: number;\n\n /** Optional group name for organizing pins. Null = ungrouped (shown first) */\n Group?: string;\n\n /** ISO timestamp of when this was pinned */\n PinnedAt: string;\n}\n\n/**\n * Configuration shape for pins with ResourceType === 'Actions'.\n * Stored inside HomeAppPinnedItem.Configuration as a plain object.\n */\nexport interface ActionPinConfiguration {\n /** ID of the MJ Action to execute */\n actionId: string;\n /** Action name at pin time — kept for display in case the Action is renamed */\n actionName: string;\n /** Parameter values baked into the pin — passed every time the pin is invoked */\n presetParams: Record<string, string>;\n /** Names of ActionParams to prompt the user for when the pin is clicked */\n runtimeParamNames: string[];\n /** Hex accent color (e.g. '#4F46E5') used for the pin card background gradient */\n accentColor?: string;\n /** User's custom title (also stored on HomeAppPinnedItem.DisplayName for consistency) */\n displayName?: string;\n}\n\n/**\n * Input for creating a new pin (ID, Sequence, and PinnedAt are auto-generated)\n */\nexport type HomeAppPinInput = Omit<HomeAppPinnedItem, 'Id' | 'Sequence' | 'PinnedAt'>;\n\n/**\n * Fields that can be updated on an existing pin\n */\nexport type HomeAppPinUpdate = Partial<Pick<HomeAppPinnedItem, 'DisplayName' | 'Description' | 'Icon' | 'Color' | 'Group' | 'Thumbnail'>>;\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-shared",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.28.0",
|
|
4
4
|
"description": "MemberJunction: MJ Explorer Angular Shared Package - utility functions and other reusable elements used across other MJ Angular packages within the MJ Explorer App - do not use outside of MJ Explorer.",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -29,16 +29,16 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@angular/platform-browser": "21.1.3",
|
|
32
|
-
"@memberjunction/ai-engine-base": "5.
|
|
33
|
-
"@memberjunction/core": "5.
|
|
34
|
-
"@memberjunction/core-entities": "5.
|
|
35
|
-
"@memberjunction/entity-communications-base": "5.
|
|
36
|
-
"@memberjunction/global": "5.
|
|
37
|
-
"@memberjunction/graphql-dataprovider": "5.
|
|
38
|
-
"@memberjunction/ng-base-application": "5.
|
|
39
|
-
"@memberjunction/ng-base-types": "5.
|
|
40
|
-
"@memberjunction/ng-notifications": "5.
|
|
41
|
-
"@memberjunction/ng-shared-generic": "5.
|
|
32
|
+
"@memberjunction/ai-engine-base": "5.28.0",
|
|
33
|
+
"@memberjunction/core": "5.28.0",
|
|
34
|
+
"@memberjunction/core-entities": "5.28.0",
|
|
35
|
+
"@memberjunction/entity-communications-base": "5.28.0",
|
|
36
|
+
"@memberjunction/global": "5.28.0",
|
|
37
|
+
"@memberjunction/graphql-dataprovider": "5.28.0",
|
|
38
|
+
"@memberjunction/ng-base-application": "5.28.0",
|
|
39
|
+
"@memberjunction/ng-base-types": "5.28.0",
|
|
40
|
+
"@memberjunction/ng-notifications": "5.28.0",
|
|
41
|
+
"@memberjunction/ng-shared-generic": "5.28.0",
|
|
42
42
|
"html-to-image": "^1.11.11",
|
|
43
43
|
"tslib": "^2.8.1"
|
|
44
44
|
},
|