@kustomizer/visual-editor 0.1.0 → 0.2.1
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, Injectable, makeEnvironmentProviders, ViewContainerRef, DestroyRef, input, effect, reflectComponentType, ChangeDetectionStrategy, Component, computed, NgZone,
|
|
2
|
+
import { InjectionToken, inject, Injectable, makeEnvironmentProviders, provideEnvironmentInitializer, signal, ViewContainerRef, DestroyRef, input, effect, reflectComponentType, ChangeDetectionStrategy, Component, computed, NgZone, viewChild, output, isDevMode } from '@angular/core';
|
|
3
3
|
import { Router, ActivatedRoute } from '@angular/router';
|
|
4
|
-
import { of, startWith, map, delay, throwError, isObservable, Observable, BehaviorSubject, tap, Subject, filter, debounceTime, switchMap as switchMap$1, takeUntil } from 'rxjs';
|
|
4
|
+
import { of, startWith, map, delay, throwError, isObservable, Observable, BehaviorSubject, tap, Subject, filter, firstValueFrom, debounceTime, switchMap as switchMap$1, takeUntil } from 'rxjs';
|
|
5
5
|
import { switchMap, map as map$1, catchError } from 'rxjs/operators';
|
|
6
6
|
import { HttpClient } from '@angular/common/http';
|
|
7
7
|
import { createActionGroup, emptyProps, props, createReducer, on, createFeatureSelector, createSelector, provideState, Store } from '@ngrx/store';
|
|
@@ -2104,6 +2104,17 @@ function provideEditorComponents(definitions) {
|
|
|
2104
2104
|
useValue: definitions,
|
|
2105
2105
|
multi: true,
|
|
2106
2106
|
},
|
|
2107
|
+
// Fallback: directly register definitions via environment initializer.
|
|
2108
|
+
// This ensures registration works even when the InjectionToken doesn't
|
|
2109
|
+
// match between pre-bundled library code and app code (e.g. ng serve with Vite).
|
|
2110
|
+
provideEnvironmentInitializer(() => {
|
|
2111
|
+
const registry = inject(ComponentRegistryService);
|
|
2112
|
+
for (const def of definitions) {
|
|
2113
|
+
if (!registry.has(def.type)) {
|
|
2114
|
+
registry.register(def);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
}),
|
|
2107
2118
|
]);
|
|
2108
2119
|
}
|
|
2109
2120
|
|
|
@@ -2112,10 +2123,28 @@ function provideEditorComponents(definitions) {
|
|
|
2112
2123
|
*/
|
|
2113
2124
|
class ComponentRegistryService {
|
|
2114
2125
|
definitions = new Map();
|
|
2126
|
+
_version = signal(0, ...(ngDevMode ? [{ debugName: "_version" }] : []));
|
|
2115
2127
|
injectedDefinitions = inject(EDITOR_COMPONENT_DEFINITIONS, { optional: true }) ?? [];
|
|
2116
2128
|
constructor() {
|
|
2117
2129
|
this.injectedDefinitions.flat().forEach((def) => this.register(def));
|
|
2118
2130
|
}
|
|
2131
|
+
/**
|
|
2132
|
+
* Clear all registered definitions
|
|
2133
|
+
*/
|
|
2134
|
+
clear() {
|
|
2135
|
+
this.definitions.clear();
|
|
2136
|
+
this._version.update(v => v + 1);
|
|
2137
|
+
}
|
|
2138
|
+
/**
|
|
2139
|
+
* Unregister a single component definition by type
|
|
2140
|
+
*/
|
|
2141
|
+
unregister(type) {
|
|
2142
|
+
const deleted = this.definitions.delete(type);
|
|
2143
|
+
if (deleted) {
|
|
2144
|
+
this._version.update(v => v + 1);
|
|
2145
|
+
}
|
|
2146
|
+
return deleted;
|
|
2147
|
+
}
|
|
2119
2148
|
/**
|
|
2120
2149
|
* Register a component definition
|
|
2121
2150
|
*/
|
|
@@ -2124,6 +2153,7 @@ class ComponentRegistryService {
|
|
|
2124
2153
|
console.warn(`ComponentRegistry: Component type "${definition.type}" is already registered. Overwriting.`);
|
|
2125
2154
|
}
|
|
2126
2155
|
this.definitions.set(definition.type, definition);
|
|
2156
|
+
this._version.update(v => v + 1);
|
|
2127
2157
|
}
|
|
2128
2158
|
/**
|
|
2129
2159
|
* Register multiple definitions
|
|
@@ -2172,6 +2202,7 @@ class ComponentRegistryService {
|
|
|
2172
2202
|
* Get all definitions
|
|
2173
2203
|
*/
|
|
2174
2204
|
getAll() {
|
|
2205
|
+
this._version(); // reactive dependency for signal consumers
|
|
2175
2206
|
return Array.from(this.definitions.values());
|
|
2176
2207
|
}
|
|
2177
2208
|
/**
|
|
@@ -2365,8 +2396,7 @@ class DynamicRendererComponent {
|
|
|
2365
2396
|
/** Additional context (sectionId, index, etc.) */
|
|
2366
2397
|
context = input({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
2367
2398
|
/** Error state signal */
|
|
2368
|
-
|
|
2369
|
-
error = () => this._error;
|
|
2399
|
+
error = signal(false, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
2370
2400
|
componentRef = null;
|
|
2371
2401
|
currentType = null;
|
|
2372
2402
|
currentAvailableInputs = null;
|
|
@@ -2392,13 +2422,17 @@ class DynamicRendererComponent {
|
|
|
2392
2422
|
this.cleanup();
|
|
2393
2423
|
const componentType = this.registry.getComponent(element.type);
|
|
2394
2424
|
if (!componentType) {
|
|
2395
|
-
this.
|
|
2425
|
+
this.error.set(true);
|
|
2396
2426
|
this.currentType = null;
|
|
2397
2427
|
this.currentAvailableInputs = null;
|
|
2398
|
-
|
|
2428
|
+
const all = this.registry.getAll();
|
|
2429
|
+
const hasType = this.registry.has(element.type);
|
|
2430
|
+
console.warn(`DynamicRenderer: No component registered for type "${element.type}".`, hasType
|
|
2431
|
+
? `Definition exists but has no "component" class (manifest-only).`
|
|
2432
|
+
: `Registry has ${all.length} definition(s): [${all.map(d => d.type).join(', ')}]`);
|
|
2399
2433
|
return;
|
|
2400
2434
|
}
|
|
2401
|
-
this.
|
|
2435
|
+
this.error.set(false);
|
|
2402
2436
|
this.currentType = element.type;
|
|
2403
2437
|
this.previousInputs.clear();
|
|
2404
2438
|
this.componentRef = this.viewContainerRef.createComponent(componentType);
|
|
@@ -3260,6 +3294,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
3260
3294
|
type: Injectable
|
|
3261
3295
|
}], ctorParameters: () => [] });
|
|
3262
3296
|
|
|
3297
|
+
/**
|
|
3298
|
+
* Service that holds and dynamically loads the per-merchant storefront URL.
|
|
3299
|
+
*
|
|
3300
|
+
* Resolution order:
|
|
3301
|
+
* 1. Metafield value loaded from GET /api/storefront-url (per-shop)
|
|
3302
|
+
* 2. Static STOREFRONT_URL injection token (env var / window global fallback)
|
|
3303
|
+
* 3. Empty string (editor works with local components only)
|
|
3304
|
+
*/
|
|
3305
|
+
class StorefrontUrlService {
|
|
3306
|
+
http = inject(HttpClient);
|
|
3307
|
+
staticUrl = inject(STOREFRONT_URL);
|
|
3308
|
+
/** Current storefront URL — reactive signal used by the editor and iframe. */
|
|
3309
|
+
url = signal('', ...(ngDevMode ? [{ debugName: "url" }] : []));
|
|
3310
|
+
/**
|
|
3311
|
+
* Load the storefront URL from the backend metafield.
|
|
3312
|
+
* Falls back to the static STOREFRONT_URL token if the endpoint
|
|
3313
|
+
* returns no value or fails.
|
|
3314
|
+
*
|
|
3315
|
+
* Called during APP_INITIALIZER.
|
|
3316
|
+
*/
|
|
3317
|
+
async load() {
|
|
3318
|
+
try {
|
|
3319
|
+
const res = await firstValueFrom(this.http.get('/api/storefront-url'));
|
|
3320
|
+
if (res.url) {
|
|
3321
|
+
this.url.set(res.url);
|
|
3322
|
+
return;
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
catch {
|
|
3326
|
+
// Non-fatal — fall back to static token
|
|
3327
|
+
}
|
|
3328
|
+
// Fallback: use the static injection token (env var / window global)
|
|
3329
|
+
if (this.staticUrl) {
|
|
3330
|
+
this.url.set(this.staticUrl);
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontUrlService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3334
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontUrlService, providedIn: 'root' });
|
|
3335
|
+
}
|
|
3336
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontUrlService, decorators: [{
|
|
3337
|
+
type: Injectable,
|
|
3338
|
+
args: [{ providedIn: 'root' }]
|
|
3339
|
+
}] });
|
|
3340
|
+
|
|
3263
3341
|
const MAX_DEPTH = 12;
|
|
3264
3342
|
class DragDropService {
|
|
3265
3343
|
store = inject(Store);
|
|
@@ -4391,7 +4469,7 @@ class VisualEditorComponent {
|
|
|
4391
4469
|
config = inject(VISUAL_EDITOR_CONFIG);
|
|
4392
4470
|
dndService = inject(DragDropService);
|
|
4393
4471
|
iframeBridge = inject(IframeBridgeService);
|
|
4394
|
-
|
|
4472
|
+
storefrontUrlService = inject(StorefrontUrlService);
|
|
4395
4473
|
sanitizer = inject(DomSanitizer);
|
|
4396
4474
|
destroy$ = new Subject();
|
|
4397
4475
|
// Configuration-driven UI options
|
|
@@ -4404,9 +4482,10 @@ class VisualEditorComponent {
|
|
|
4404
4482
|
// Iframe preview
|
|
4405
4483
|
previewFrame = viewChild('previewFrame', ...(ngDevMode ? [{ debugName: "previewFrame" }] : []));
|
|
4406
4484
|
previewUrl = computed(() => {
|
|
4407
|
-
|
|
4485
|
+
const url = this.storefrontUrlService.url();
|
|
4486
|
+
if (!url)
|
|
4408
4487
|
return null;
|
|
4409
|
-
return this.sanitizer.bypassSecurityTrustResourceUrl(`${
|
|
4488
|
+
return this.sanitizer.bypassSecurityTrustResourceUrl(`${url}/kustomizer/editor`);
|
|
4410
4489
|
}, ...(ngDevMode ? [{ debugName: "previewUrl" }] : []));
|
|
4411
4490
|
iframeReady = false;
|
|
4412
4491
|
propertiesTab = signal('props', ...(ngDevMode ? [{ debugName: "propertiesTab" }] : []));
|
|
@@ -4673,7 +4752,8 @@ class VisualEditorComponent {
|
|
|
4673
4752
|
onIframeLoad() {
|
|
4674
4753
|
const iframe = this.previewFrame()?.nativeElement;
|
|
4675
4754
|
if (iframe) {
|
|
4676
|
-
const
|
|
4755
|
+
const sfUrl = this.storefrontUrlService.url();
|
|
4756
|
+
const origin = sfUrl ? new URL(sfUrl).origin : '*';
|
|
4677
4757
|
this.iframeBridge.connect(iframe, origin);
|
|
4678
4758
|
}
|
|
4679
4759
|
}
|
|
@@ -7768,22 +7848,64 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
7768
7848
|
class ManifestLoaderService {
|
|
7769
7849
|
http = inject(HttpClient);
|
|
7770
7850
|
registry = inject(ComponentRegistryService);
|
|
7851
|
+
watchInterval = null;
|
|
7852
|
+
lastManifestHash = '';
|
|
7853
|
+
manifestTypes = new Set();
|
|
7771
7854
|
/**
|
|
7772
7855
|
* Fetch the manifest from the given URL and register all components.
|
|
7773
7856
|
*/
|
|
7774
7857
|
loadManifest(url) {
|
|
7775
|
-
return this.http.get(url).pipe(tap((manifest) =>
|
|
7858
|
+
return this.http.get(url).pipe(tap((manifest) => {
|
|
7859
|
+
this.registerManifestComponents(manifest);
|
|
7860
|
+
this.lastManifestHash = this.hashManifest(manifest);
|
|
7861
|
+
}));
|
|
7862
|
+
}
|
|
7863
|
+
/**
|
|
7864
|
+
* Poll the manifest URL for changes and re-register components when the
|
|
7865
|
+
* content hash differs. Only active in dev mode. No-op in production.
|
|
7866
|
+
*/
|
|
7867
|
+
startWatching(manifestUrl, intervalMs = 2000) {
|
|
7868
|
+
if (!isDevMode() || this.watchInterval)
|
|
7869
|
+
return;
|
|
7870
|
+
this.watchInterval = setInterval(() => {
|
|
7871
|
+
this.http.get(manifestUrl).subscribe({
|
|
7872
|
+
next: (manifest) => {
|
|
7873
|
+
const hash = this.hashManifest(manifest);
|
|
7874
|
+
if (hash !== this.lastManifestHash) {
|
|
7875
|
+
console.log('[Kustomizer] Manifest changed, reloading components…');
|
|
7876
|
+
this.lastManifestHash = hash;
|
|
7877
|
+
this.registerManifestComponents(manifest);
|
|
7878
|
+
}
|
|
7879
|
+
},
|
|
7880
|
+
error: () => { }, // silently ignore polling errors
|
|
7881
|
+
});
|
|
7882
|
+
}, intervalMs);
|
|
7883
|
+
}
|
|
7884
|
+
ngOnDestroy() {
|
|
7885
|
+
if (this.watchInterval) {
|
|
7886
|
+
clearInterval(this.watchInterval);
|
|
7887
|
+
this.watchInterval = null;
|
|
7888
|
+
}
|
|
7776
7889
|
}
|
|
7777
7890
|
/**
|
|
7778
7891
|
* Register manifest entries in the ComponentRegistryService.
|
|
7779
7892
|
* Each entry becomes a ComponentDefinition without a `component` field.
|
|
7780
7893
|
*/
|
|
7781
7894
|
registerManifestComponents(manifest) {
|
|
7895
|
+
// Remove only previously manifest-loaded definitions, preserving local components
|
|
7896
|
+
for (const type of this.manifestTypes) {
|
|
7897
|
+
this.registry.unregister(type);
|
|
7898
|
+
}
|
|
7899
|
+
this.manifestTypes.clear();
|
|
7782
7900
|
for (const entry of manifest.components) {
|
|
7783
7901
|
const definition = this.manifestEntryToDefinition(entry);
|
|
7784
7902
|
this.registry.register(definition);
|
|
7903
|
+
this.manifestTypes.add(definition.type);
|
|
7785
7904
|
}
|
|
7786
7905
|
}
|
|
7906
|
+
hashManifest(manifest) {
|
|
7907
|
+
return JSON.stringify(manifest.components.map(c => c.type + c.name + JSON.stringify(c.props ?? {}) + JSON.stringify(c.slots ?? [])));
|
|
7908
|
+
}
|
|
7787
7909
|
manifestEntryToDefinition(entry) {
|
|
7788
7910
|
return {
|
|
7789
7911
|
type: entry.type,
|
|
@@ -7839,5 +7961,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
7839
7961
|
* Generated bundle index. Do not edit.
|
|
7840
7962
|
*/
|
|
7841
7963
|
|
|
7842
|
-
export { BlockTreeItemComponent, CREATE_METAFIELD_DEFINITION_MUTATION, ComponentRegistryService, DEFAULT_ROUTER_NAVIGATION_CONFIG, DEFAULT_VISUAL_EDITOR_CONFIG, DELETE_METAFIELDS_MUTATION, DELETE_METAFIELD_DEFINITION_MUTATION, DefaultRouterNavigationService, DragDropService, DynamicRendererComponent, EDITOR_COMPONENT_DEFINITIONS, FILES_QUERY, GET_METAFIELD_DEFINITION_QUERY, GET_SHOP_ID_QUERY, GET_SHOP_METAFIELD_QUERY$1 as GET_SHOP_METAFIELD_QUERY, INDEX_KEY$1 as INDEX_KEY, IframeBridgeService, InputPageLoadingStrategy, MAX_METAFIELD_SIZE, ManifestLoaderService, NAMESPACE$1 as NAMESPACE, PageLoadingStrategy, PageManagerComponent, PageService, PageShopifyRepository, PageStorefrontRepository, ROUTER_NAVIGATION_CONFIG, RoutePageLoadingStrategy, SET_METAFIELD_MUTATION, SHOPIFY_CONFIG, STOREFRONT_CONFIG, STOREFRONT_URL, ShopifyFilePickerComponent, ShopifyFilesService, ShopifyGraphQLClient, ShopifyMetafieldRepository, SlotRendererComponent, StorefrontGraphQLClient, StorefrontMetafieldRepository, USE_IN_MEMORY_PAGES, VISUAL_EDITOR_CONFIG, VISUAL_EDITOR_FEATURE_KEY, VisualEditor, VisualEditorActions, VisualEditorComponent, VisualEditorFacade, VisualEditorNavigation, initialVisualEditorState, provideEditorComponents, provideVisualEditor, provideVisualEditorStore, selectBlocksForSection, selectBlocksForSlot, selectCanRedo, selectCanUndo, selectCurrentPage, selectCurrentPageId, selectCurrentPageSlug, selectCurrentPageStatus, selectCurrentPageTitle, selectCurrentPageVersion, selectDraggedElementId, selectElementById, selectHistory, selectHistoryIndex, selectHistoryLength, selectIsDirty, selectIsDragging, selectIsPageLoaded, selectLastAction, selectSectionById, selectSections, selectSelectedBlock, selectSelectedBlockSlotName, selectSelectedElement, selectSelectedElementId, selectSelectedElementType, selectSelectedSection, selectSelectedSectionId, selectSelectedSectionType, selectVisualEditorState, visualEditorReducer };
|
|
7964
|
+
export { BlockTreeItemComponent, CREATE_METAFIELD_DEFINITION_MUTATION, ComponentRegistryService, DEFAULT_ROUTER_NAVIGATION_CONFIG, DEFAULT_VISUAL_EDITOR_CONFIG, DELETE_METAFIELDS_MUTATION, DELETE_METAFIELD_DEFINITION_MUTATION, DefaultRouterNavigationService, DragDropService, DynamicRendererComponent, EDITOR_COMPONENT_DEFINITIONS, FILES_QUERY, GET_METAFIELD_DEFINITION_QUERY, GET_SHOP_ID_QUERY, GET_SHOP_METAFIELD_QUERY$1 as GET_SHOP_METAFIELD_QUERY, INDEX_KEY$1 as INDEX_KEY, IframeBridgeService, InputPageLoadingStrategy, MAX_METAFIELD_SIZE, ManifestLoaderService, NAMESPACE$1 as NAMESPACE, PageLoadingStrategy, PageManagerComponent, PageService, PageShopifyRepository, PageStorefrontRepository, ROUTER_NAVIGATION_CONFIG, RoutePageLoadingStrategy, SET_METAFIELD_MUTATION, SHOPIFY_CONFIG, STOREFRONT_CONFIG, STOREFRONT_URL, ShopifyFilePickerComponent, ShopifyFilesService, ShopifyGraphQLClient, ShopifyMetafieldRepository, SlotRendererComponent, StorefrontGraphQLClient, StorefrontMetafieldRepository, StorefrontUrlService, USE_IN_MEMORY_PAGES, VISUAL_EDITOR_CONFIG, VISUAL_EDITOR_FEATURE_KEY, VisualEditor, VisualEditorActions, VisualEditorComponent, VisualEditorFacade, VisualEditorNavigation, initialVisualEditorState, provideEditorComponents, provideVisualEditor, provideVisualEditorStore, selectBlocksForSection, selectBlocksForSlot, selectCanRedo, selectCanUndo, selectCurrentPage, selectCurrentPageId, selectCurrentPageSlug, selectCurrentPageStatus, selectCurrentPageTitle, selectCurrentPageVersion, selectDraggedElementId, selectElementById, selectHistory, selectHistoryIndex, selectHistoryLength, selectIsDirty, selectIsDragging, selectIsPageLoaded, selectLastAction, selectSectionById, selectSections, selectSelectedBlock, selectSelectedBlockSlotName, selectSelectedElement, selectSelectedElementId, selectSelectedElementType, selectSelectedSection, selectSelectedSectionId, selectSelectedSectionType, selectVisualEditorState, visualEditorReducer };
|
|
7843
7965
|
//# sourceMappingURL=kustomizer-visual-editor.mjs.map
|