@momentumcms/admin 0.5.1 → 0.5.2
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/fesm2022/{momentumcms-admin-array-field.component-pqA3_nC8.mjs → momentumcms-admin-array-field.component-DH6vaHO-.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-array-field.component-pqA3_nC8.mjs.map → momentumcms-admin-array-field.component-DH6vaHO-.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-blocks-field.component-88TEhVm4.mjs → momentumcms-admin-blocks-field.component-BxJRfiV3.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-blocks-field.component-88TEhVm4.mjs.map → momentumcms-admin-blocks-field.component-BxJRfiV3.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-collapsible-field.component-D5Jc8h2Q.mjs → momentumcms-admin-collapsible-field.component-CsjYCkGw.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-collapsible-field.component-D5Jc8h2Q.mjs.map → momentumcms-admin-collapsible-field.component-CsjYCkGw.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-global-edit.page-D4QNE5AM.mjs → momentumcms-admin-global-edit.page-CmLAM17O.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-global-edit.page-D4QNE5AM.mjs.map → momentumcms-admin-global-edit.page-CmLAM17O.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-group-field.component-Cenc5zMW.mjs → momentumcms-admin-group-field.component-CMKcqfjy.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-group-field.component-Cenc5zMW.mjs.map → momentumcms-admin-group-field.component-CMKcqfjy.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-momentumcms-admin-5WigESOC.mjs → momentumcms-admin-momentumcms-admin-BTZEdMNj.mjs} +782 -23
- package/fesm2022/momentumcms-admin-momentumcms-admin-BTZEdMNj.mjs.map +1 -0
- package/fesm2022/{momentumcms-admin-relationship-field.component-DlCdpcRy.mjs → momentumcms-admin-relationship-field.component-DNZUCENa.mjs} +8 -3
- package/fesm2022/{momentumcms-admin-relationship-field.component-DlCdpcRy.mjs.map → momentumcms-admin-relationship-field.component-DNZUCENa.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-rich-text-field.component-BUziCgyn.mjs → momentumcms-admin-rich-text-field.component-BVAQkX3O.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-rich-text-field.component-BUziCgyn.mjs.map → momentumcms-admin-rich-text-field.component-BVAQkX3O.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-row-field.component-fFTcYU-P.mjs → momentumcms-admin-row-field.component-0F6cnUK_.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-row-field.component-fFTcYU-P.mjs.map → momentumcms-admin-row-field.component-0F6cnUK_.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-tabs-field.component-D_T_JZej.mjs → momentumcms-admin-tabs-field.component-qYlbl8Ud.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-tabs-field.component-D_T_JZej.mjs.map → momentumcms-admin-tabs-field.component-qYlbl8Ud.mjs.map} +1 -1
- package/fesm2022/momentumcms-admin.mjs +1 -1
- package/package.json +1 -1
- package/types/momentumcms-admin.d.ts +121 -58
- package/fesm2022/momentumcms-admin-momentumcms-admin-5WigESOC.mjs.map +0 -1
|
@@ -1134,7 +1134,7 @@ function momentumAdminRoutes(configOrOptions) {
|
|
|
1134
1134
|
// Global edit
|
|
1135
1135
|
{
|
|
1136
1136
|
path: 'globals/:slug',
|
|
1137
|
-
loadComponent: () => import('./momentumcms-admin-global-edit.page-
|
|
1137
|
+
loadComponent: () => import('./momentumcms-admin-global-edit.page-CmLAM17O.mjs').then((m) => m.GlobalEditPage),
|
|
1138
1138
|
canDeactivate: [unsavedChangesGuard],
|
|
1139
1139
|
},
|
|
1140
1140
|
// Plugin-registered routes
|
|
@@ -4837,6 +4837,427 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
4837
4837
|
}]
|
|
4838
4838
|
}], ctorParameters: () => [], propDecorators: { fileInputRef: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }], uploadConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadConfig", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], pendingFile: [{ type: i0.Input, args: [{ isSignal: true, alias: "pendingFile", required: false }] }], isUploading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isUploading", required: false }] }], uploadProgress: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadProgress", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], existingMedia: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingMedia", required: false }] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }], fileRemoved: [{ type: i0.Output, args: ["fileRemoved"] }] } });
|
|
4839
4839
|
|
|
4840
|
+
/**
|
|
4841
|
+
* Normalize a click position within a container to a 0-1 focal point.
|
|
4842
|
+
*/
|
|
4843
|
+
function normalizeFocalPoint(click, container) {
|
|
4844
|
+
if (container.width <= 0 || container.height <= 0) {
|
|
4845
|
+
return { x: 0.5, y: 0.5 };
|
|
4846
|
+
}
|
|
4847
|
+
return clampFocalPoint({
|
|
4848
|
+
x: click.x / container.width,
|
|
4849
|
+
y: click.y / container.height,
|
|
4850
|
+
});
|
|
4851
|
+
}
|
|
4852
|
+
/**
|
|
4853
|
+
* Clamp a focal point to the valid 0-1 range on both axes.
|
|
4854
|
+
*/
|
|
4855
|
+
function clampFocalPoint(point) {
|
|
4856
|
+
return {
|
|
4857
|
+
x: Math.max(0, Math.min(1, point.x)),
|
|
4858
|
+
y: Math.max(0, Math.min(1, point.y)),
|
|
4859
|
+
};
|
|
4860
|
+
}
|
|
4861
|
+
/**
|
|
4862
|
+
* Convert a normalized focal point to a CSS `object-position` string.
|
|
4863
|
+
*
|
|
4864
|
+
* @example focalPointToCssPosition({ x: 0.25, y: 0.75 }) → '25% 75%'
|
|
4865
|
+
*/
|
|
4866
|
+
function focalPointToCssPosition(point) {
|
|
4867
|
+
const fp = point ?? { x: 0.5, y: 0.5 };
|
|
4868
|
+
return `${Math.round(fp.x * 100)}% ${Math.round(fp.y * 100)}%`;
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4871
|
+
/**
|
|
4872
|
+
* Calculate the crop region for a cover-fit preview.
|
|
4873
|
+
* Same math as the server-side crop-calculator, but for browser-side preview.
|
|
4874
|
+
*/
|
|
4875
|
+
function calculatePreviewCrop(source, target, focalPoint) {
|
|
4876
|
+
const fp = focalPoint ?? { x: 0.5, y: 0.5 };
|
|
4877
|
+
const scale = Math.max(target.width / source.width, target.height / source.height);
|
|
4878
|
+
const cropW = Math.round(target.width / scale);
|
|
4879
|
+
const cropH = Math.round(target.height / scale);
|
|
4880
|
+
const focalX = fp.x * source.width;
|
|
4881
|
+
const focalY = fp.y * source.height;
|
|
4882
|
+
let x = Math.round(focalX - cropW / 2);
|
|
4883
|
+
let y = Math.round(focalY - cropH / 2);
|
|
4884
|
+
x = Math.max(0, Math.min(x, source.width - cropW));
|
|
4885
|
+
y = Math.max(0, Math.min(y, source.height - cropH));
|
|
4886
|
+
return { x, y, width: cropW, height: cropH };
|
|
4887
|
+
}
|
|
4888
|
+
|
|
4889
|
+
/**
|
|
4890
|
+
* Focal Point Picker Component
|
|
4891
|
+
*
|
|
4892
|
+
* Displays an image with an interactive crosshair overlay.
|
|
4893
|
+
* Click anywhere on the image to set the focal point.
|
|
4894
|
+
* Optionally shows crop preview outlines for configured image sizes.
|
|
4895
|
+
*
|
|
4896
|
+
* @example
|
|
4897
|
+
* ```html
|
|
4898
|
+
* <mcms-focal-point-picker
|
|
4899
|
+
* [imageUrl]="mediaUrl"
|
|
4900
|
+
* [focalPoint]="{ x: 0.5, y: 0.5 }"
|
|
4901
|
+
* [imageSizes]="configuredSizes"
|
|
4902
|
+
* [naturalWidth]="800"
|
|
4903
|
+
* [naturalHeight]="600"
|
|
4904
|
+
* (focalPointChange)="onFocalPointChange($event)"
|
|
4905
|
+
* />
|
|
4906
|
+
* ```
|
|
4907
|
+
*/
|
|
4908
|
+
class FocalPointPickerComponent {
|
|
4909
|
+
imageElRef = viewChild('imageEl', ...(ngDevMode ? [{ debugName: "imageElRef" }] : []));
|
|
4910
|
+
/** URL of the image to display */
|
|
4911
|
+
imageUrl = input.required(...(ngDevMode ? [{ debugName: "imageUrl" }] : []));
|
|
4912
|
+
/** Current focal point (0-1 normalized) */
|
|
4913
|
+
focalPoint = input({ x: 0.5, y: 0.5 }, ...(ngDevMode ? [{ debugName: "focalPoint" }] : []));
|
|
4914
|
+
/** Alt text for the image */
|
|
4915
|
+
alt = input('', ...(ngDevMode ? [{ debugName: "alt" }] : []));
|
|
4916
|
+
/** Natural width of the source image (for crop preview calculations) */
|
|
4917
|
+
naturalWidth = input(0, ...(ngDevMode ? [{ debugName: "naturalWidth" }] : []));
|
|
4918
|
+
/** Natural height of the source image (for crop preview calculations) */
|
|
4919
|
+
naturalHeight = input(0, ...(ngDevMode ? [{ debugName: "naturalHeight" }] : []));
|
|
4920
|
+
/** Configured image sizes for crop preview outlines */
|
|
4921
|
+
imageSizes = input([], ...(ngDevMode ? [{ debugName: "imageSizes" }] : []));
|
|
4922
|
+
/** Emitted when the focal point changes */
|
|
4923
|
+
focalPointChange = output();
|
|
4924
|
+
/** Focal point X as percentage */
|
|
4925
|
+
focalPointX = computed(() => Math.round(this.focalPoint().x * 100), ...(ngDevMode ? [{ debugName: "focalPointX" }] : []));
|
|
4926
|
+
/** Focal point Y as percentage */
|
|
4927
|
+
focalPointY = computed(() => Math.round(this.focalPoint().y * 100), ...(ngDevMode ? [{ debugName: "focalPointY" }] : []));
|
|
4928
|
+
/** CSS object-position string */
|
|
4929
|
+
cssPosition = computed(() => focalPointToCssPosition(this.focalPoint()), ...(ngDevMode ? [{ debugName: "cssPosition" }] : []));
|
|
4930
|
+
/** Human-readable position label */
|
|
4931
|
+
positionLabel = computed(() => {
|
|
4932
|
+
const fp = this.focalPoint();
|
|
4933
|
+
return `${Math.round(fp.x * 100)}% x ${Math.round(fp.y * 100)}%`;
|
|
4934
|
+
}, ...(ngDevMode ? [{ debugName: "positionLabel" }] : []));
|
|
4935
|
+
/** Crop preview data for each configured size */
|
|
4936
|
+
cropPreviews = computed(() => {
|
|
4937
|
+
const w = this.naturalWidth();
|
|
4938
|
+
const h = this.naturalHeight();
|
|
4939
|
+
const fp = this.focalPoint();
|
|
4940
|
+
const sizes = this.imageSizes();
|
|
4941
|
+
if (!w || !h || sizes.length === 0)
|
|
4942
|
+
return [];
|
|
4943
|
+
return sizes
|
|
4944
|
+
.filter((s) => s.width && s.height && s.fit === 'cover')
|
|
4945
|
+
.map((s) => {
|
|
4946
|
+
const crop = calculatePreviewCrop({ width: w, height: h }, { width: s.width ?? 0, height: s.height ?? 0 }, fp);
|
|
4947
|
+
return {
|
|
4948
|
+
name: s.name,
|
|
4949
|
+
leftPct: (crop.x / w) * 100,
|
|
4950
|
+
topPct: (crop.y / h) * 100,
|
|
4951
|
+
widthPct: (crop.width / w) * 100,
|
|
4952
|
+
heightPct: (crop.height / h) * 100,
|
|
4953
|
+
};
|
|
4954
|
+
});
|
|
4955
|
+
}, ...(ngDevMode ? [{ debugName: "cropPreviews" }] : []));
|
|
4956
|
+
/**
|
|
4957
|
+
* Handle click on the image overlay.
|
|
4958
|
+
*/
|
|
4959
|
+
onClick(event) {
|
|
4960
|
+
const el = this.imageElRef();
|
|
4961
|
+
if (!el)
|
|
4962
|
+
return;
|
|
4963
|
+
const rect = el.nativeElement.getBoundingClientRect();
|
|
4964
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
4965
|
+
return;
|
|
4966
|
+
const x = event.clientX - rect.left;
|
|
4967
|
+
const y = event.clientY - rect.top;
|
|
4968
|
+
const fp = normalizeFocalPoint({ x, y }, { width: rect.width, height: rect.height });
|
|
4969
|
+
this.focalPointChange.emit(fp);
|
|
4970
|
+
}
|
|
4971
|
+
/**
|
|
4972
|
+
* Reset focal point to center.
|
|
4973
|
+
*/
|
|
4974
|
+
onResetCenter() {
|
|
4975
|
+
this.focalPointChange.emit({ x: 0.5, y: 0.5 });
|
|
4976
|
+
}
|
|
4977
|
+
/**
|
|
4978
|
+
* Nudge focal point by a small amount (keyboard navigation).
|
|
4979
|
+
*/
|
|
4980
|
+
onNudge(dx, dy, event) {
|
|
4981
|
+
event.preventDefault();
|
|
4982
|
+
const fp = this.focalPoint();
|
|
4983
|
+
const newFp = {
|
|
4984
|
+
x: Math.max(0, Math.min(1, fp.x + dx)),
|
|
4985
|
+
y: Math.max(0, Math.min(1, fp.y + dy)),
|
|
4986
|
+
};
|
|
4987
|
+
this.focalPointChange.emit(newFp);
|
|
4988
|
+
}
|
|
4989
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: FocalPointPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4990
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: FocalPointPickerComponent, isStandalone: true, selector: "mcms-focal-point-picker", inputs: { imageUrl: { classPropertyName: "imageUrl", publicName: "imageUrl", isSignal: true, isRequired: true, transformFunction: null }, focalPoint: { classPropertyName: "focalPoint", publicName: "focalPoint", isSignal: true, isRequired: false, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, naturalWidth: { classPropertyName: "naturalWidth", publicName: "naturalWidth", isSignal: true, isRequired: false, transformFunction: null }, naturalHeight: { classPropertyName: "naturalHeight", publicName: "naturalHeight", isSignal: true, isRequired: false, transformFunction: null }, imageSizes: { classPropertyName: "imageSizes", publicName: "imageSizes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { focalPointChange: "focalPointChange" }, host: { classAttribute: "block" }, viewQueries: [{ propertyName: "imageElRef", first: true, predicate: ["imageEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
4991
|
+
<div class="space-y-3">
|
|
4992
|
+
<div class="relative inline-block overflow-hidden rounded-lg border border-mcms-border">
|
|
4993
|
+
<!-- Image -->
|
|
4994
|
+
<img
|
|
4995
|
+
#imageEl
|
|
4996
|
+
[src]="imageUrl()"
|
|
4997
|
+
[alt]="alt() || 'Image with focal point selector'"
|
|
4998
|
+
class="block max-h-96 max-w-full"
|
|
4999
|
+
[style.object-position]="cssPosition()"
|
|
5000
|
+
/>
|
|
5001
|
+
|
|
5002
|
+
<!-- Clickable overlay -->
|
|
5003
|
+
<div
|
|
5004
|
+
class="absolute inset-0 cursor-crosshair"
|
|
5005
|
+
role="button"
|
|
5006
|
+
tabindex="0"
|
|
5007
|
+
[attr.aria-label]="'Set focal point. Current position: ' + positionLabel()"
|
|
5008
|
+
(click)="onClick($event)"
|
|
5009
|
+
(keydown.enter)="onResetCenter()"
|
|
5010
|
+
(keydown.space)="onResetCenter()"
|
|
5011
|
+
(keydown.ArrowLeft)="onNudge(-0.05, 0, $event)"
|
|
5012
|
+
(keydown.ArrowRight)="onNudge(0.05, 0, $event)"
|
|
5013
|
+
(keydown.ArrowUp)="onNudge(0, -0.05, $event)"
|
|
5014
|
+
(keydown.ArrowDown)="onNudge(0, 0.05, $event)"
|
|
5015
|
+
>
|
|
5016
|
+
<!-- Crosshair lines -->
|
|
5017
|
+
<div
|
|
5018
|
+
class="pointer-events-none absolute h-px w-full bg-white/70"
|
|
5019
|
+
[style.top.%]="focalPointY()"
|
|
5020
|
+
></div>
|
|
5021
|
+
<div
|
|
5022
|
+
class="pointer-events-none absolute w-px h-full bg-white/70"
|
|
5023
|
+
[style.left.%]="focalPointX()"
|
|
5024
|
+
></div>
|
|
5025
|
+
|
|
5026
|
+
<!-- Focal point dot -->
|
|
5027
|
+
<div
|
|
5028
|
+
class="pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white bg-mcms-primary shadow-md"
|
|
5029
|
+
[style.left.%]="focalPointX()"
|
|
5030
|
+
[style.top.%]="focalPointY()"
|
|
5031
|
+
aria-hidden="true"
|
|
5032
|
+
></div>
|
|
5033
|
+
|
|
5034
|
+
<!-- Crop preview outlines -->
|
|
5035
|
+
@for (preview of cropPreviews(); track preview.name) {
|
|
5036
|
+
<div
|
|
5037
|
+
class="pointer-events-none absolute border border-dashed border-yellow-400/60"
|
|
5038
|
+
[style.left.%]="preview.leftPct"
|
|
5039
|
+
[style.top.%]="preview.topPct"
|
|
5040
|
+
[style.width.%]="preview.widthPct"
|
|
5041
|
+
[style.height.%]="preview.heightPct"
|
|
5042
|
+
[attr.aria-label]="'Crop preview for ' + preview.name"
|
|
5043
|
+
>
|
|
5044
|
+
<span class="absolute -top-5 left-0 text-xs text-yellow-400 drop-shadow-sm">
|
|
5045
|
+
{{ preview.name }}
|
|
5046
|
+
</span>
|
|
5047
|
+
</div>
|
|
5048
|
+
}
|
|
5049
|
+
</div>
|
|
5050
|
+
</div>
|
|
5051
|
+
|
|
5052
|
+
<!-- Coordinates display -->
|
|
5053
|
+
<p class="text-xs text-mcms-muted-foreground" aria-live="polite">
|
|
5054
|
+
Focal point: {{ positionLabel() }}
|
|
5055
|
+
<button
|
|
5056
|
+
class="ml-2 underline hover:text-mcms-foreground"
|
|
5057
|
+
type="button"
|
|
5058
|
+
(click)="onResetCenter()"
|
|
5059
|
+
aria-label="Reset focal point to center"
|
|
5060
|
+
>
|
|
5061
|
+
Reset to center
|
|
5062
|
+
</button>
|
|
5063
|
+
</p>
|
|
5064
|
+
</div>
|
|
5065
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5066
|
+
}
|
|
5067
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: FocalPointPickerComponent, decorators: [{
|
|
5068
|
+
type: Component,
|
|
5069
|
+
args: [{
|
|
5070
|
+
selector: 'mcms-focal-point-picker',
|
|
5071
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
5072
|
+
host: { class: 'block' },
|
|
5073
|
+
template: `
|
|
5074
|
+
<div class="space-y-3">
|
|
5075
|
+
<div class="relative inline-block overflow-hidden rounded-lg border border-mcms-border">
|
|
5076
|
+
<!-- Image -->
|
|
5077
|
+
<img
|
|
5078
|
+
#imageEl
|
|
5079
|
+
[src]="imageUrl()"
|
|
5080
|
+
[alt]="alt() || 'Image with focal point selector'"
|
|
5081
|
+
class="block max-h-96 max-w-full"
|
|
5082
|
+
[style.object-position]="cssPosition()"
|
|
5083
|
+
/>
|
|
5084
|
+
|
|
5085
|
+
<!-- Clickable overlay -->
|
|
5086
|
+
<div
|
|
5087
|
+
class="absolute inset-0 cursor-crosshair"
|
|
5088
|
+
role="button"
|
|
5089
|
+
tabindex="0"
|
|
5090
|
+
[attr.aria-label]="'Set focal point. Current position: ' + positionLabel()"
|
|
5091
|
+
(click)="onClick($event)"
|
|
5092
|
+
(keydown.enter)="onResetCenter()"
|
|
5093
|
+
(keydown.space)="onResetCenter()"
|
|
5094
|
+
(keydown.ArrowLeft)="onNudge(-0.05, 0, $event)"
|
|
5095
|
+
(keydown.ArrowRight)="onNudge(0.05, 0, $event)"
|
|
5096
|
+
(keydown.ArrowUp)="onNudge(0, -0.05, $event)"
|
|
5097
|
+
(keydown.ArrowDown)="onNudge(0, 0.05, $event)"
|
|
5098
|
+
>
|
|
5099
|
+
<!-- Crosshair lines -->
|
|
5100
|
+
<div
|
|
5101
|
+
class="pointer-events-none absolute h-px w-full bg-white/70"
|
|
5102
|
+
[style.top.%]="focalPointY()"
|
|
5103
|
+
></div>
|
|
5104
|
+
<div
|
|
5105
|
+
class="pointer-events-none absolute w-px h-full bg-white/70"
|
|
5106
|
+
[style.left.%]="focalPointX()"
|
|
5107
|
+
></div>
|
|
5108
|
+
|
|
5109
|
+
<!-- Focal point dot -->
|
|
5110
|
+
<div
|
|
5111
|
+
class="pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white bg-mcms-primary shadow-md"
|
|
5112
|
+
[style.left.%]="focalPointX()"
|
|
5113
|
+
[style.top.%]="focalPointY()"
|
|
5114
|
+
aria-hidden="true"
|
|
5115
|
+
></div>
|
|
5116
|
+
|
|
5117
|
+
<!-- Crop preview outlines -->
|
|
5118
|
+
@for (preview of cropPreviews(); track preview.name) {
|
|
5119
|
+
<div
|
|
5120
|
+
class="pointer-events-none absolute border border-dashed border-yellow-400/60"
|
|
5121
|
+
[style.left.%]="preview.leftPct"
|
|
5122
|
+
[style.top.%]="preview.topPct"
|
|
5123
|
+
[style.width.%]="preview.widthPct"
|
|
5124
|
+
[style.height.%]="preview.heightPct"
|
|
5125
|
+
[attr.aria-label]="'Crop preview for ' + preview.name"
|
|
5126
|
+
>
|
|
5127
|
+
<span class="absolute -top-5 left-0 text-xs text-yellow-400 drop-shadow-sm">
|
|
5128
|
+
{{ preview.name }}
|
|
5129
|
+
</span>
|
|
5130
|
+
</div>
|
|
5131
|
+
}
|
|
5132
|
+
</div>
|
|
5133
|
+
</div>
|
|
5134
|
+
|
|
5135
|
+
<!-- Coordinates display -->
|
|
5136
|
+
<p class="text-xs text-mcms-muted-foreground" aria-live="polite">
|
|
5137
|
+
Focal point: {{ positionLabel() }}
|
|
5138
|
+
<button
|
|
5139
|
+
class="ml-2 underline hover:text-mcms-foreground"
|
|
5140
|
+
type="button"
|
|
5141
|
+
(click)="onResetCenter()"
|
|
5142
|
+
aria-label="Reset focal point to center"
|
|
5143
|
+
>
|
|
5144
|
+
Reset to center
|
|
5145
|
+
</button>
|
|
5146
|
+
</p>
|
|
5147
|
+
</div>
|
|
5148
|
+
`,
|
|
5149
|
+
}]
|
|
5150
|
+
}], propDecorators: { imageElRef: [{ type: i0.ViewChild, args: ['imageEl', { isSignal: true }] }], imageUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUrl", required: true }] }], focalPoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "focalPoint", required: false }] }], alt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: false }] }], naturalWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "naturalWidth", required: false }] }], naturalHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "naturalHeight", required: false }] }], imageSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageSizes", required: false }] }], focalPointChange: [{ type: i0.Output, args: ["focalPointChange"] }] } });
|
|
5151
|
+
|
|
5152
|
+
/**
|
|
5153
|
+
* Displays generated image size variants as a grid of thumbnail cards.
|
|
5154
|
+
*
|
|
5155
|
+
* @example
|
|
5156
|
+
* ```html
|
|
5157
|
+
* <mcms-image-variants-display [sizes]="entity.sizes" />
|
|
5158
|
+
* ```
|
|
5159
|
+
*/
|
|
5160
|
+
class ImageVariantsDisplay {
|
|
5161
|
+
/** The sizes record from the media document (e.g., entity['sizes']) */
|
|
5162
|
+
sizes = input(null, ...(ngDevMode ? [{ debugName: "sizes" }] : []));
|
|
5163
|
+
/** Parsed variant entries */
|
|
5164
|
+
variants = computed(() => {
|
|
5165
|
+
const sizes = this.sizes();
|
|
5166
|
+
if (!sizes || typeof sizes !== 'object')
|
|
5167
|
+
return [];
|
|
5168
|
+
return Object.entries(sizes)
|
|
5169
|
+
.filter((entry) => {
|
|
5170
|
+
const v = entry[1];
|
|
5171
|
+
return v != null && typeof v === 'object' && 'width' in v && 'path' in v;
|
|
5172
|
+
})
|
|
5173
|
+
.map(([name, data]) => ({
|
|
5174
|
+
name,
|
|
5175
|
+
...data,
|
|
5176
|
+
}));
|
|
5177
|
+
}, ...(ngDevMode ? [{ debugName: "variants" }] : []));
|
|
5178
|
+
/** Build URL for a variant image */
|
|
5179
|
+
variantUrl(variant) {
|
|
5180
|
+
if (variant.url)
|
|
5181
|
+
return variant.url;
|
|
5182
|
+
if (variant.path)
|
|
5183
|
+
return `/api/media/file/${variant.path}`;
|
|
5184
|
+
return '';
|
|
5185
|
+
}
|
|
5186
|
+
/** Format file size for display */
|
|
5187
|
+
formatFileSize(bytes) {
|
|
5188
|
+
if (!bytes)
|
|
5189
|
+
return '';
|
|
5190
|
+
if (bytes >= 1024 * 1024)
|
|
5191
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
5192
|
+
if (bytes >= 1024)
|
|
5193
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
5194
|
+
return `${bytes} bytes`;
|
|
5195
|
+
}
|
|
5196
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ImageVariantsDisplay, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5197
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ImageVariantsDisplay, isStandalone: true, selector: "mcms-image-variants-display", inputs: { sizes: { classPropertyName: "sizes", publicName: "sizes", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
5198
|
+
@if (variants().length > 0) {
|
|
5199
|
+
<div class="space-y-3">
|
|
5200
|
+
<p class="text-sm font-medium">Generated Sizes</p>
|
|
5201
|
+
<div class="grid gap-3 grid-cols-2 sm:grid-cols-3">
|
|
5202
|
+
@for (variant of variants(); track variant.name) {
|
|
5203
|
+
<div class="rounded-lg border border-mcms-border bg-mcms-card overflow-hidden">
|
|
5204
|
+
<img
|
|
5205
|
+
[src]="variantUrl(variant)"
|
|
5206
|
+
[alt]="variant.name + ' variant'"
|
|
5207
|
+
class="h-24 w-full object-cover bg-mcms-muted"
|
|
5208
|
+
/>
|
|
5209
|
+
<div class="p-2">
|
|
5210
|
+
<p class="text-xs font-medium">{{ variant.name }}</p>
|
|
5211
|
+
<p class="text-xs text-mcms-muted-foreground">
|
|
5212
|
+
{{ variant.width }} × {{ variant.height }}
|
|
5213
|
+
</p>
|
|
5214
|
+
<p class="text-xs text-mcms-muted-foreground">
|
|
5215
|
+
{{ formatFileSize(variant.filesize) }}
|
|
5216
|
+
</p>
|
|
5217
|
+
</div>
|
|
5218
|
+
</div>
|
|
5219
|
+
}
|
|
5220
|
+
</div>
|
|
5221
|
+
</div>
|
|
5222
|
+
}
|
|
5223
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5224
|
+
}
|
|
5225
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ImageVariantsDisplay, decorators: [{
|
|
5226
|
+
type: Component,
|
|
5227
|
+
args: [{
|
|
5228
|
+
selector: 'mcms-image-variants-display',
|
|
5229
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
5230
|
+
host: { class: 'block' },
|
|
5231
|
+
template: `
|
|
5232
|
+
@if (variants().length > 0) {
|
|
5233
|
+
<div class="space-y-3">
|
|
5234
|
+
<p class="text-sm font-medium">Generated Sizes</p>
|
|
5235
|
+
<div class="grid gap-3 grid-cols-2 sm:grid-cols-3">
|
|
5236
|
+
@for (variant of variants(); track variant.name) {
|
|
5237
|
+
<div class="rounded-lg border border-mcms-border bg-mcms-card overflow-hidden">
|
|
5238
|
+
<img
|
|
5239
|
+
[src]="variantUrl(variant)"
|
|
5240
|
+
[alt]="variant.name + ' variant'"
|
|
5241
|
+
class="h-24 w-full object-cover bg-mcms-muted"
|
|
5242
|
+
/>
|
|
5243
|
+
<div class="p-2">
|
|
5244
|
+
<p class="text-xs font-medium">{{ variant.name }}</p>
|
|
5245
|
+
<p class="text-xs text-mcms-muted-foreground">
|
|
5246
|
+
{{ variant.width }} × {{ variant.height }}
|
|
5247
|
+
</p>
|
|
5248
|
+
<p class="text-xs text-mcms-muted-foreground">
|
|
5249
|
+
{{ formatFileSize(variant.filesize) }}
|
|
5250
|
+
</p>
|
|
5251
|
+
</div>
|
|
5252
|
+
</div>
|
|
5253
|
+
}
|
|
5254
|
+
</div>
|
|
5255
|
+
</div>
|
|
5256
|
+
}
|
|
5257
|
+
`,
|
|
5258
|
+
}]
|
|
5259
|
+
}], propDecorators: { sizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizes", required: false }] }] } });
|
|
5260
|
+
|
|
4840
5261
|
/**
|
|
4841
5262
|
* Entity Form Widget
|
|
4842
5263
|
*
|
|
@@ -4902,8 +5323,87 @@ class EntityFormWidget {
|
|
|
4902
5323
|
isUploadingFile = signal(false, ...(ngDevMode ? [{ debugName: "isUploadingFile" }] : []));
|
|
4903
5324
|
uploadFileProgress = signal(0, ...(ngDevMode ? [{ debugName: "uploadFileProgress" }] : []));
|
|
4904
5325
|
uploadFileError = signal(null, ...(ngDevMode ? [{ debugName: "uploadFileError" }] : []));
|
|
5326
|
+
/** Preview URL for the pending image file (focal point picker) */
|
|
5327
|
+
pendingFileUrl = signal(null, ...(ngDevMode ? [{ debugName: "pendingFileUrl" }] : []));
|
|
5328
|
+
/** Detected dimensions of the pending image */
|
|
5329
|
+
pendingImageDimensions = signal({
|
|
5330
|
+
width: 0,
|
|
5331
|
+
height: 0,
|
|
5332
|
+
}, ...(ngDevMode ? [{ debugName: "pendingImageDimensions" }] : []));
|
|
4905
5333
|
/** Whether the collection is an upload collection */
|
|
4906
5334
|
isUploadCol = computed(() => isUploadCollection(this.collection()), ...(ngDevMode ? [{ debugName: "isUploadCol" }] : []));
|
|
5335
|
+
/** Whether the current file (pending or existing) is an image */
|
|
5336
|
+
isImageFile = computed(() => {
|
|
5337
|
+
const file = this.pendingFile();
|
|
5338
|
+
if (file)
|
|
5339
|
+
return file.type.startsWith('image/');
|
|
5340
|
+
if (this.isUploadCol() && this.mode() !== 'create') {
|
|
5341
|
+
const mimeType = this.formModel()['mimeType'];
|
|
5342
|
+
return typeof mimeType === 'string' && mimeType.startsWith('image/');
|
|
5343
|
+
}
|
|
5344
|
+
return false;
|
|
5345
|
+
}, ...(ngDevMode ? [{ debugName: "isImageFile" }] : []));
|
|
5346
|
+
/** Image sizes from collection upload config */
|
|
5347
|
+
uploadImageSizes = computed(() => {
|
|
5348
|
+
return this.collection().upload?.imageSizes ?? [];
|
|
5349
|
+
}, ...(ngDevMode ? [{ debugName: "uploadImageSizes" }] : []));
|
|
5350
|
+
/** Image URL for the focal point picker */
|
|
5351
|
+
focalPointImageUrl = computed(() => {
|
|
5352
|
+
const fileUrl = this.pendingFileUrl();
|
|
5353
|
+
if (fileUrl)
|
|
5354
|
+
return fileUrl;
|
|
5355
|
+
const model = this.formModel();
|
|
5356
|
+
if (typeof model['url'] === 'string' && model['url'])
|
|
5357
|
+
return model['url'];
|
|
5358
|
+
if (typeof model['path'] === 'string' && model['path'])
|
|
5359
|
+
return `/api/media/file/${model['path']}`;
|
|
5360
|
+
return '';
|
|
5361
|
+
}, ...(ngDevMode ? [{ debugName: "focalPointImageUrl" }] : []));
|
|
5362
|
+
/** Current focal point value */
|
|
5363
|
+
currentFocalPoint = computed(() => {
|
|
5364
|
+
const fp = this.formModel()['focalPoint'];
|
|
5365
|
+
if (fp != null && typeof fp === 'object' && !Array.isArray(fp)) {
|
|
5366
|
+
const obj = fp; // eslint-disable-line @typescript-eslint/consistent-type-assertions
|
|
5367
|
+
const x = obj['x'];
|
|
5368
|
+
const y = obj['y'];
|
|
5369
|
+
if (typeof x === 'number' && typeof y === 'number') {
|
|
5370
|
+
return { x, y };
|
|
5371
|
+
}
|
|
5372
|
+
}
|
|
5373
|
+
return { x: 0.5, y: 0.5 };
|
|
5374
|
+
}, ...(ngDevMode ? [{ debugName: "currentFocalPoint" }] : []));
|
|
5375
|
+
/** Natural image dimensions (pending file detection or existing media) */
|
|
5376
|
+
imageNaturalDimensions = computed(() => {
|
|
5377
|
+
if (this.pendingFile())
|
|
5378
|
+
return this.pendingImageDimensions();
|
|
5379
|
+
const model = this.formModel();
|
|
5380
|
+
return {
|
|
5381
|
+
width: typeof model['width'] === 'number' ? model['width'] : 0,
|
|
5382
|
+
height: typeof model['height'] === 'number' ? model['height'] : 0,
|
|
5383
|
+
};
|
|
5384
|
+
}, ...(ngDevMode ? [{ debugName: "imageNaturalDimensions" }] : []));
|
|
5385
|
+
/** Alt text for the focal point picker */
|
|
5386
|
+
focalPointAlt = computed(() => {
|
|
5387
|
+
const model = this.formModel();
|
|
5388
|
+
const alt = model['alt'];
|
|
5389
|
+
if (typeof alt === 'string' && alt)
|
|
5390
|
+
return alt;
|
|
5391
|
+
const fn = model['filename'];
|
|
5392
|
+
if (typeof fn === 'string')
|
|
5393
|
+
return fn;
|
|
5394
|
+
return '';
|
|
5395
|
+
}, ...(ngDevMode ? [{ debugName: "focalPointAlt" }] : []));
|
|
5396
|
+
/** Generated image sizes from the form model */
|
|
5397
|
+
formModelSizes = computed(() => {
|
|
5398
|
+
const sizes = this.formModel()['sizes'];
|
|
5399
|
+
if (sizes != null &&
|
|
5400
|
+
typeof sizes === 'object' &&
|
|
5401
|
+
!Array.isArray(sizes) &&
|
|
5402
|
+
Object.keys(sizes).length > 0) {
|
|
5403
|
+
return sizes; // eslint-disable-line @typescript-eslint/consistent-type-assertions
|
|
5404
|
+
}
|
|
5405
|
+
return null;
|
|
5406
|
+
}, ...(ngDevMode ? [{ debugName: "formModelSizes" }] : []));
|
|
4907
5407
|
/** Whether the form has been set up */
|
|
4908
5408
|
formCreated = false;
|
|
4909
5409
|
/** Whether the form has unsaved changes (from signal forms dirty tracking) */
|
|
@@ -5002,16 +5502,46 @@ class EntityFormWidget {
|
|
|
5002
5502
|
this.loadGlobal(gSlug);
|
|
5003
5503
|
}
|
|
5004
5504
|
else if (currentMode === 'create' || !id) {
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5505
|
+
// Guard: don't reset formModel if the user has already selected a file.
|
|
5506
|
+
// On Analog (SSR), reactive route signals may re-emit after hydration,
|
|
5507
|
+
// causing this effect to re-run and wipe user input.
|
|
5508
|
+
const hasPendingFile = untracked(() => this.pendingFile());
|
|
5509
|
+
if (!hasPendingFile) {
|
|
5510
|
+
this.formModel.set(createInitialFormData(col));
|
|
5511
|
+
const ef = this.entityForm();
|
|
5512
|
+
if (ef)
|
|
5513
|
+
ef().reset();
|
|
5514
|
+
}
|
|
5009
5515
|
}
|
|
5010
5516
|
else {
|
|
5011
5517
|
this.loadEntity(col.slug, id);
|
|
5012
5518
|
}
|
|
5013
5519
|
}
|
|
5014
5520
|
});
|
|
5521
|
+
// Manage preview URL for focal point picker and detect image dimensions
|
|
5522
|
+
effect((onCleanup) => {
|
|
5523
|
+
const file = this.pendingFile();
|
|
5524
|
+
if (file && file.type.startsWith('image/')) {
|
|
5525
|
+
const url = URL.createObjectURL(file);
|
|
5526
|
+
this.pendingFileUrl.set(url);
|
|
5527
|
+
let cancelled = false;
|
|
5528
|
+
onCleanup(() => {
|
|
5529
|
+
cancelled = true;
|
|
5530
|
+
URL.revokeObjectURL(url);
|
|
5531
|
+
});
|
|
5532
|
+
const img = new Image();
|
|
5533
|
+
img.onload = () => {
|
|
5534
|
+
if (!cancelled) {
|
|
5535
|
+
this.pendingImageDimensions.set({ width: img.naturalWidth, height: img.naturalHeight });
|
|
5536
|
+
}
|
|
5537
|
+
};
|
|
5538
|
+
img.src = url;
|
|
5539
|
+
}
|
|
5540
|
+
else {
|
|
5541
|
+
this.pendingFileUrl.set(null);
|
|
5542
|
+
this.pendingImageDimensions.set({ width: 0, height: 0 });
|
|
5543
|
+
}
|
|
5544
|
+
});
|
|
5015
5545
|
}
|
|
5016
5546
|
/**
|
|
5017
5547
|
* Get a FieldTree node for a top-level field by name.
|
|
@@ -5120,6 +5650,14 @@ class EntityFormWidget {
|
|
|
5120
5650
|
data['filesize'] = file.size;
|
|
5121
5651
|
this.formModel.set(data);
|
|
5122
5652
|
}
|
|
5653
|
+
/**
|
|
5654
|
+
* Handle focal point change from the picker.
|
|
5655
|
+
*/
|
|
5656
|
+
onFocalPointChange(fp) {
|
|
5657
|
+
const data = { ...this.formModel() };
|
|
5658
|
+
data['focalPoint'] = fp;
|
|
5659
|
+
this.formModel.set(data);
|
|
5660
|
+
}
|
|
5123
5661
|
/**
|
|
5124
5662
|
* Handle file removed from the upload zone.
|
|
5125
5663
|
*/
|
|
@@ -5395,7 +5933,7 @@ class EntityFormWidget {
|
|
|
5395
5933
|
<mcms-collection-upload-zone
|
|
5396
5934
|
[uploadConfig]="collection().upload"
|
|
5397
5935
|
[pendingFile]="pendingFile()"
|
|
5398
|
-
[existingMedia]="mode()
|
|
5936
|
+
[existingMedia]="mode() !== 'create' && !pendingFile() ? formModel() : null"
|
|
5399
5937
|
[disabled]="mode() === 'view'"
|
|
5400
5938
|
[isUploading]="isUploadingFile()"
|
|
5401
5939
|
[uploadProgress]="uploadFileProgress()"
|
|
@@ -5405,6 +5943,27 @@ class EntityFormWidget {
|
|
|
5405
5943
|
/>
|
|
5406
5944
|
}
|
|
5407
5945
|
|
|
5946
|
+
@if (isUploadCol() && isImageFile() && focalPointImageUrl()) {
|
|
5947
|
+
<div class="mb-6" [class.pointer-events-none]="mode() === 'view'">
|
|
5948
|
+
<p class="mb-2 text-sm font-medium">Focal Point</p>
|
|
5949
|
+
<mcms-focal-point-picker
|
|
5950
|
+
[imageUrl]="focalPointImageUrl()"
|
|
5951
|
+
[focalPoint]="currentFocalPoint()"
|
|
5952
|
+
[naturalWidth]="imageNaturalDimensions().width"
|
|
5953
|
+
[naturalHeight]="imageNaturalDimensions().height"
|
|
5954
|
+
[alt]="focalPointAlt()"
|
|
5955
|
+
[imageSizes]="uploadImageSizes()"
|
|
5956
|
+
(focalPointChange)="onFocalPointChange($event)"
|
|
5957
|
+
/>
|
|
5958
|
+
</div>
|
|
5959
|
+
}
|
|
5960
|
+
|
|
5961
|
+
@if (isUploadCol() && formModelSizes()) {
|
|
5962
|
+
<div class="mb-6">
|
|
5963
|
+
<mcms-image-variants-display [sizes]="formModelSizes()" />
|
|
5964
|
+
</div>
|
|
5965
|
+
}
|
|
5966
|
+
|
|
5408
5967
|
<div class="space-y-6">
|
|
5409
5968
|
@for (field of visibleFields(); track field.name) {
|
|
5410
5969
|
<mcms-field-renderer
|
|
@@ -5473,7 +6032,7 @@ class EntityFormWidget {
|
|
|
5473
6032
|
</div>
|
|
5474
6033
|
}
|
|
5475
6034
|
</div>
|
|
5476
|
-
`, isInline: true, dependencies: [{ kind: "component", type: Card, selector: "mcms-card" }, { kind: "component", type: CardContent, selector: "mcms-card-content" }, { kind: "component", type: CardFooter, selector: "mcms-card-footer" }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: Spinner, selector: "mcms-spinner", inputs: ["size", "label", "class"] }, { kind: "component", type: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: FieldRenderer, selector: "mcms-field-renderer", inputs: ["field", "formNode", "formTree", "formModel", "mode", "path"] }, { kind: "component", type: Breadcrumbs, selector: "mcms-breadcrumbs", inputs: ["class"] }, { kind: "component", type: BreadcrumbItem, selector: "mcms-breadcrumb-item", inputs: ["href", "current", "class"] }, { kind: "component", type: BreadcrumbSeparator, selector: "mcms-breadcrumb-separator", inputs: ["class"] }, { kind: "component", type: VersionHistoryWidget, selector: "mcms-version-history", inputs: ["collection", "documentId", "documentLabel"], outputs: ["restored"] }, { kind: "component", type: CollectionUploadZoneComponent, selector: "mcms-collection-upload-zone", inputs: ["uploadConfig", "disabled", "pendingFile", "isUploading", "uploadProgress", "error", "existingMedia"], outputs: ["fileSelected", "fileRemoved"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6035
|
+
`, isInline: true, dependencies: [{ kind: "component", type: Card, selector: "mcms-card" }, { kind: "component", type: CardContent, selector: "mcms-card-content" }, { kind: "component", type: CardFooter, selector: "mcms-card-footer" }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: Spinner, selector: "mcms-spinner", inputs: ["size", "label", "class"] }, { kind: "component", type: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: FieldRenderer, selector: "mcms-field-renderer", inputs: ["field", "formNode", "formTree", "formModel", "mode", "path"] }, { kind: "component", type: Breadcrumbs, selector: "mcms-breadcrumbs", inputs: ["class"] }, { kind: "component", type: BreadcrumbItem, selector: "mcms-breadcrumb-item", inputs: ["href", "current", "class"] }, { kind: "component", type: BreadcrumbSeparator, selector: "mcms-breadcrumb-separator", inputs: ["class"] }, { kind: "component", type: VersionHistoryWidget, selector: "mcms-version-history", inputs: ["collection", "documentId", "documentLabel"], outputs: ["restored"] }, { kind: "component", type: CollectionUploadZoneComponent, selector: "mcms-collection-upload-zone", inputs: ["uploadConfig", "disabled", "pendingFile", "isUploading", "uploadProgress", "error", "existingMedia"], outputs: ["fileSelected", "fileRemoved"] }, { kind: "component", type: FocalPointPickerComponent, selector: "mcms-focal-point-picker", inputs: ["imageUrl", "focalPoint", "alt", "naturalWidth", "naturalHeight", "imageSizes"], outputs: ["focalPointChange"] }, { kind: "component", type: ImageVariantsDisplay, selector: "mcms-image-variants-display", inputs: ["sizes"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5477
6036
|
}
|
|
5478
6037
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EntityFormWidget, decorators: [{
|
|
5479
6038
|
type: Component,
|
|
@@ -5492,6 +6051,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
5492
6051
|
BreadcrumbSeparator,
|
|
5493
6052
|
VersionHistoryWidget,
|
|
5494
6053
|
CollectionUploadZoneComponent,
|
|
6054
|
+
FocalPointPickerComponent,
|
|
6055
|
+
ImageVariantsDisplay,
|
|
5495
6056
|
],
|
|
5496
6057
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
5497
6058
|
host: { class: 'block' },
|
|
@@ -5557,7 +6118,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
5557
6118
|
<mcms-collection-upload-zone
|
|
5558
6119
|
[uploadConfig]="collection().upload"
|
|
5559
6120
|
[pendingFile]="pendingFile()"
|
|
5560
|
-
[existingMedia]="mode()
|
|
6121
|
+
[existingMedia]="mode() !== 'create' && !pendingFile() ? formModel() : null"
|
|
5561
6122
|
[disabled]="mode() === 'view'"
|
|
5562
6123
|
[isUploading]="isUploadingFile()"
|
|
5563
6124
|
[uploadProgress]="uploadFileProgress()"
|
|
@@ -5567,6 +6128,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
5567
6128
|
/>
|
|
5568
6129
|
}
|
|
5569
6130
|
|
|
6131
|
+
@if (isUploadCol() && isImageFile() && focalPointImageUrl()) {
|
|
6132
|
+
<div class="mb-6" [class.pointer-events-none]="mode() === 'view'">
|
|
6133
|
+
<p class="mb-2 text-sm font-medium">Focal Point</p>
|
|
6134
|
+
<mcms-focal-point-picker
|
|
6135
|
+
[imageUrl]="focalPointImageUrl()"
|
|
6136
|
+
[focalPoint]="currentFocalPoint()"
|
|
6137
|
+
[naturalWidth]="imageNaturalDimensions().width"
|
|
6138
|
+
[naturalHeight]="imageNaturalDimensions().height"
|
|
6139
|
+
[alt]="focalPointAlt()"
|
|
6140
|
+
[imageSizes]="uploadImageSizes()"
|
|
6141
|
+
(focalPointChange)="onFocalPointChange($event)"
|
|
6142
|
+
/>
|
|
6143
|
+
</div>
|
|
6144
|
+
}
|
|
6145
|
+
|
|
6146
|
+
@if (isUploadCol() && formModelSizes()) {
|
|
6147
|
+
<div class="mb-6">
|
|
6148
|
+
<mcms-image-variants-display [sizes]="formModelSizes()" />
|
|
6149
|
+
</div>
|
|
6150
|
+
}
|
|
6151
|
+
|
|
5570
6152
|
<div class="space-y-6">
|
|
5571
6153
|
@for (field of visibleFields(); track field.name) {
|
|
5572
6154
|
<mcms-field-renderer
|
|
@@ -5945,6 +6527,81 @@ class EntityViewWidget {
|
|
|
5945
6527
|
canDelete = computed(() => {
|
|
5946
6528
|
return this.collectionAccess.canDelete(this.collection().slug);
|
|
5947
6529
|
}, ...(ngDevMode ? [{ debugName: "canDelete" }] : []));
|
|
6530
|
+
/** Whether this collection is an upload collection */
|
|
6531
|
+
isUploadCol = computed(() => isUploadCollection(this.collection()), ...(ngDevMode ? [{ debugName: "isUploadCol" }] : []));
|
|
6532
|
+
/** Whether the entity is an image */
|
|
6533
|
+
isEntityImage = computed(() => {
|
|
6534
|
+
const e = this.entity();
|
|
6535
|
+
if (!e)
|
|
6536
|
+
return false;
|
|
6537
|
+
const mimeType = e['mimeType'];
|
|
6538
|
+
return typeof mimeType === 'string' && mimeType.startsWith('image/');
|
|
6539
|
+
}, ...(ngDevMode ? [{ debugName: "isEntityImage" }] : []));
|
|
6540
|
+
/** Media URL for preview */
|
|
6541
|
+
entityMediaUrl = computed(() => {
|
|
6542
|
+
const e = this.entity();
|
|
6543
|
+
if (!e)
|
|
6544
|
+
return '';
|
|
6545
|
+
if (typeof e['url'] === 'string' && e['url'])
|
|
6546
|
+
return e['url'];
|
|
6547
|
+
if (typeof e['path'] === 'string' && e['path'])
|
|
6548
|
+
return `/api/media/file/${e['path']}`;
|
|
6549
|
+
return '';
|
|
6550
|
+
}, ...(ngDevMode ? [{ debugName: "entityMediaUrl" }] : []));
|
|
6551
|
+
/** Focal point from entity data */
|
|
6552
|
+
entityFocalPoint = computed(() => {
|
|
6553
|
+
const e = this.entity();
|
|
6554
|
+
if (!e)
|
|
6555
|
+
return { x: 0.5, y: 0.5 };
|
|
6556
|
+
const fp = e['focalPoint'];
|
|
6557
|
+
if (fp != null && typeof fp === 'object' && !Array.isArray(fp)) {
|
|
6558
|
+
const obj = fp; // eslint-disable-line @typescript-eslint/consistent-type-assertions
|
|
6559
|
+
const x = obj['x'];
|
|
6560
|
+
const y = obj['y'];
|
|
6561
|
+
if (typeof x === 'number' && typeof y === 'number')
|
|
6562
|
+
return { x, y };
|
|
6563
|
+
}
|
|
6564
|
+
return { x: 0.5, y: 0.5 };
|
|
6565
|
+
}, ...(ngDevMode ? [{ debugName: "entityFocalPoint" }] : []));
|
|
6566
|
+
/** Image dimensions from entity data */
|
|
6567
|
+
entityDimensions = computed(() => {
|
|
6568
|
+
const e = this.entity();
|
|
6569
|
+
return {
|
|
6570
|
+
width: typeof e?.['width'] === 'number' ? e['width'] : 0,
|
|
6571
|
+
height: typeof e?.['height'] === 'number' ? e['height'] : 0,
|
|
6572
|
+
};
|
|
6573
|
+
}, ...(ngDevMode ? [{ debugName: "entityDimensions" }] : []));
|
|
6574
|
+
/** Image sizes from collection upload config */
|
|
6575
|
+
viewImageSizes = computed(() => {
|
|
6576
|
+
return this.collection().upload?.imageSizes ?? [];
|
|
6577
|
+
}, ...(ngDevMode ? [{ debugName: "viewImageSizes" }] : []));
|
|
6578
|
+
/** Generated image sizes from entity data */
|
|
6579
|
+
entitySizes = computed(() => {
|
|
6580
|
+
const e = this.entity();
|
|
6581
|
+
if (!e)
|
|
6582
|
+
return null;
|
|
6583
|
+
const sizes = e['sizes'];
|
|
6584
|
+
if (sizes != null &&
|
|
6585
|
+
typeof sizes === 'object' &&
|
|
6586
|
+
!Array.isArray(sizes) &&
|
|
6587
|
+
Object.keys(sizes).length > 0) {
|
|
6588
|
+
return sizes; // eslint-disable-line @typescript-eslint/consistent-type-assertions
|
|
6589
|
+
}
|
|
6590
|
+
return null;
|
|
6591
|
+
}, ...(ngDevMode ? [{ debugName: "entitySizes" }] : []));
|
|
6592
|
+
/** Media preview data for non-image files */
|
|
6593
|
+
entityMediaPreview = computed(() => {
|
|
6594
|
+
const e = this.entity();
|
|
6595
|
+
if (!e)
|
|
6596
|
+
return null;
|
|
6597
|
+
return {
|
|
6598
|
+
url: typeof e['url'] === 'string' ? e['url'] : undefined,
|
|
6599
|
+
path: typeof e['path'] === 'string' ? e['path'] : undefined,
|
|
6600
|
+
mimeType: typeof e['mimeType'] === 'string' ? e['mimeType'] : undefined,
|
|
6601
|
+
filename: typeof e['filename'] === 'string' ? e['filename'] : undefined,
|
|
6602
|
+
alt: typeof e['alt'] === 'string' ? e['alt'] : undefined,
|
|
6603
|
+
};
|
|
6604
|
+
}, ...(ngDevMode ? [{ debugName: "entityMediaPreview" }] : []));
|
|
5948
6605
|
/** Whether collection has soft delete enabled */
|
|
5949
6606
|
hasSoftDelete = computed(() => !!this.collection().softDelete, ...(ngDevMode ? [{ debugName: "hasSoftDelete" }] : []));
|
|
5950
6607
|
/** Whether the current entity is soft-deleted */
|
|
@@ -6400,6 +7057,28 @@ class EntityViewWidget {
|
|
|
6400
7057
|
{{ loadError() }}
|
|
6401
7058
|
</mcms-alert>
|
|
6402
7059
|
} @else if (entity()) {
|
|
7060
|
+
@if (isUploadCol() && entityMediaUrl()) {
|
|
7061
|
+
<div class="mb-6">
|
|
7062
|
+
@if (isEntityImage()) {
|
|
7063
|
+
<div class="pointer-events-none">
|
|
7064
|
+
<mcms-focal-point-picker
|
|
7065
|
+
[imageUrl]="entityMediaUrl()"
|
|
7066
|
+
[focalPoint]="entityFocalPoint()"
|
|
7067
|
+
[naturalWidth]="entityDimensions().width"
|
|
7068
|
+
[naturalHeight]="entityDimensions().height"
|
|
7069
|
+
[imageSizes]="viewImageSizes()"
|
|
7070
|
+
/>
|
|
7071
|
+
</div>
|
|
7072
|
+
} @else {
|
|
7073
|
+
<mcms-media-preview [media]="entityMediaPreview()" size="xl" />
|
|
7074
|
+
}
|
|
7075
|
+
</div>
|
|
7076
|
+
}
|
|
7077
|
+
@if (isUploadCol() && entitySizes()) {
|
|
7078
|
+
<div class="mb-6">
|
|
7079
|
+
<mcms-image-variants-display [sizes]="entitySizes()" />
|
|
7080
|
+
</div>
|
|
7081
|
+
}
|
|
6403
7082
|
<div class="grid gap-6 md:grid-cols-2">
|
|
6404
7083
|
@for (field of visibleFields(); track field.name) {
|
|
6405
7084
|
<mcms-field-display
|
|
@@ -6448,7 +7127,7 @@ class EntityViewWidget {
|
|
|
6448
7127
|
</div>
|
|
6449
7128
|
}
|
|
6450
7129
|
</div>
|
|
6451
|
-
`, isInline: true, dependencies: [{ kind: "component", type: Card, selector: "mcms-card" }, { kind: "component", type: CardContent, selector: "mcms-card-content" }, { kind: "component", type: CardFooter, selector: "mcms-card-footer" }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: Skeleton, selector: "mcms-skeleton", inputs: ["class"] }, { kind: "component", type: FieldDisplay, selector: "mcms-field-display", inputs: ["value", "type", "label", "format", "emptyText", "badgeConfig", "openInNewTab", "maxItems", "class", "fieldMeta", "numberFormat", "dateFormat"] }, { kind: "component", type: Breadcrumbs, selector: "mcms-breadcrumbs", inputs: ["class"] }, { kind: "component", type: BreadcrumbItem, selector: "mcms-breadcrumb-item", inputs: ["href", "current", "class"] }, { kind: "component", type: BreadcrumbSeparator, selector: "mcms-breadcrumb-separator", inputs: ["class"] }, { kind: "component", type: VersionHistoryWidget, selector: "mcms-version-history", inputs: ["collection", "documentId", "documentLabel"], outputs: ["restored"] }, { kind: "component", type: PublishControlsWidget, selector: "mcms-publish-controls", inputs: ["collection", "documentId", "documentLabel", "initialStatus"], outputs: ["statusChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7130
|
+
`, isInline: true, dependencies: [{ kind: "component", type: Card, selector: "mcms-card" }, { kind: "component", type: CardContent, selector: "mcms-card-content" }, { kind: "component", type: CardFooter, selector: "mcms-card-footer" }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: Skeleton, selector: "mcms-skeleton", inputs: ["class"] }, { kind: "component", type: FieldDisplay, selector: "mcms-field-display", inputs: ["value", "type", "label", "format", "emptyText", "badgeConfig", "openInNewTab", "maxItems", "class", "fieldMeta", "numberFormat", "dateFormat"] }, { kind: "component", type: Breadcrumbs, selector: "mcms-breadcrumbs", inputs: ["class"] }, { kind: "component", type: BreadcrumbItem, selector: "mcms-breadcrumb-item", inputs: ["href", "current", "class"] }, { kind: "component", type: BreadcrumbSeparator, selector: "mcms-breadcrumb-separator", inputs: ["class"] }, { kind: "component", type: VersionHistoryWidget, selector: "mcms-version-history", inputs: ["collection", "documentId", "documentLabel"], outputs: ["restored"] }, { kind: "component", type: PublishControlsWidget, selector: "mcms-publish-controls", inputs: ["collection", "documentId", "documentLabel", "initialStatus"], outputs: ["statusChanged"] }, { kind: "component", type: MediaPreviewComponent, selector: "mcms-media-preview", inputs: ["media", "size", "class", "rounded"] }, { kind: "component", type: FocalPointPickerComponent, selector: "mcms-focal-point-picker", inputs: ["imageUrl", "focalPoint", "alt", "naturalWidth", "naturalHeight", "imageSizes"], outputs: ["focalPointChange"] }, { kind: "component", type: ImageVariantsDisplay, selector: "mcms-image-variants-display", inputs: ["sizes"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6452
7131
|
}
|
|
6453
7132
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EntityViewWidget, decorators: [{
|
|
6454
7133
|
type: Component,
|
|
@@ -6467,6 +7146,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
6467
7146
|
BreadcrumbSeparator,
|
|
6468
7147
|
VersionHistoryWidget,
|
|
6469
7148
|
PublishControlsWidget,
|
|
7149
|
+
MediaPreviewComponent,
|
|
7150
|
+
FocalPointPickerComponent,
|
|
7151
|
+
ImageVariantsDisplay,
|
|
6470
7152
|
],
|
|
6471
7153
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
6472
7154
|
host: { class: 'block' },
|
|
@@ -6570,6 +7252,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
6570
7252
|
{{ loadError() }}
|
|
6571
7253
|
</mcms-alert>
|
|
6572
7254
|
} @else if (entity()) {
|
|
7255
|
+
@if (isUploadCol() && entityMediaUrl()) {
|
|
7256
|
+
<div class="mb-6">
|
|
7257
|
+
@if (isEntityImage()) {
|
|
7258
|
+
<div class="pointer-events-none">
|
|
7259
|
+
<mcms-focal-point-picker
|
|
7260
|
+
[imageUrl]="entityMediaUrl()"
|
|
7261
|
+
[focalPoint]="entityFocalPoint()"
|
|
7262
|
+
[naturalWidth]="entityDimensions().width"
|
|
7263
|
+
[naturalHeight]="entityDimensions().height"
|
|
7264
|
+
[imageSizes]="viewImageSizes()"
|
|
7265
|
+
/>
|
|
7266
|
+
</div>
|
|
7267
|
+
} @else {
|
|
7268
|
+
<mcms-media-preview [media]="entityMediaPreview()" size="xl" />
|
|
7269
|
+
}
|
|
7270
|
+
</div>
|
|
7271
|
+
}
|
|
7272
|
+
@if (isUploadCol() && entitySizes()) {
|
|
7273
|
+
<div class="mb-6">
|
|
7274
|
+
<mcms-image-variants-display [sizes]="entitySizes()" />
|
|
7275
|
+
</div>
|
|
7276
|
+
}
|
|
6573
7277
|
<div class="grid gap-6 md:grid-cols-2">
|
|
6574
7278
|
@for (field of visibleFields(); track field.name) {
|
|
6575
7279
|
<mcms-field-display
|
|
@@ -11452,14 +12156,27 @@ class MediaEditDialog {
|
|
|
11452
12156
|
data = inject(DIALOG_DATA);
|
|
11453
12157
|
api = injectMomentumAPI();
|
|
11454
12158
|
media = this.data.media;
|
|
12159
|
+
imageSizes = this.data.imageSizes ?? [];
|
|
11455
12160
|
filename = signal(this.data.media.filename, ...(ngDevMode ? [{ debugName: "filename" }] : []));
|
|
11456
12161
|
altText = signal(this.data.media.alt ?? '', ...(ngDevMode ? [{ debugName: "altText" }] : []));
|
|
12162
|
+
focalPointValue = signal(this.data.media.focalPoint ?? { x: 0.5, y: 0.5 }, ...(ngDevMode ? [{ debugName: "focalPointValue" }] : []));
|
|
11457
12163
|
isSaving = signal(false, ...(ngDevMode ? [{ debugName: "isSaving" }] : []));
|
|
11458
12164
|
saveError = signal(null, ...(ngDevMode ? [{ debugName: "saveError" }] : []));
|
|
11459
12165
|
formattedSize = formatFileSize(this.data.media.filesize);
|
|
12166
|
+
isImage = computed(() => this.media.mimeType.startsWith('image/'), ...(ngDevMode ? [{ debugName: "isImage" }] : []));
|
|
12167
|
+
imageUrl = computed(() => {
|
|
12168
|
+
return this.media.url ?? `/api/media/file/${this.media.path}`;
|
|
12169
|
+
}, ...(ngDevMode ? [{ debugName: "imageUrl" }] : []));
|
|
11460
12170
|
hasChanges = computed(() => {
|
|
11461
|
-
|
|
12171
|
+
const fpChanged = this.focalPointValue().x !== (this.media.focalPoint?.x ?? 0.5) ||
|
|
12172
|
+
this.focalPointValue().y !== (this.media.focalPoint?.y ?? 0.5);
|
|
12173
|
+
return (this.filename() !== this.media.filename ||
|
|
12174
|
+
this.altText() !== (this.media.alt ?? '') ||
|
|
12175
|
+
fpChanged);
|
|
11462
12176
|
}, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
|
|
12177
|
+
onFocalPointChange(fp) {
|
|
12178
|
+
this.focalPointValue.set(fp);
|
|
12179
|
+
}
|
|
11463
12180
|
/**
|
|
11464
12181
|
* Save media metadata changes via API.
|
|
11465
12182
|
*/
|
|
@@ -11467,10 +12184,14 @@ class MediaEditDialog {
|
|
|
11467
12184
|
this.isSaving.set(true);
|
|
11468
12185
|
this.saveError.set(null);
|
|
11469
12186
|
try {
|
|
11470
|
-
const
|
|
12187
|
+
const updateData = {
|
|
11471
12188
|
filename: this.filename(),
|
|
11472
12189
|
alt: this.altText(),
|
|
11473
|
-
}
|
|
12190
|
+
};
|
|
12191
|
+
if (this.isImage()) {
|
|
12192
|
+
updateData['focalPoint'] = this.focalPointValue();
|
|
12193
|
+
}
|
|
12194
|
+
const result = await this.api.collection('media').update(this.media.id, updateData);
|
|
11474
12195
|
if (isMediaEditItem(result)) {
|
|
11475
12196
|
this.dialogRef.close({ updated: true, media: result });
|
|
11476
12197
|
}
|
|
@@ -11527,6 +12248,21 @@ class MediaEditDialog {
|
|
|
11527
12248
|
</div>
|
|
11528
12249
|
</div>
|
|
11529
12250
|
|
|
12251
|
+
@if (isImage()) {
|
|
12252
|
+
<div class="mt-4">
|
|
12253
|
+
<p class="mb-2 text-sm font-medium">Focal Point</p>
|
|
12254
|
+
<mcms-focal-point-picker
|
|
12255
|
+
[imageUrl]="imageUrl()"
|
|
12256
|
+
[focalPoint]="focalPointValue()"
|
|
12257
|
+
[naturalWidth]="media.width ?? 0"
|
|
12258
|
+
[naturalHeight]="media.height ?? 0"
|
|
12259
|
+
[alt]="media.alt ?? media.filename"
|
|
12260
|
+
[imageSizes]="imageSizes"
|
|
12261
|
+
(focalPointChange)="onFocalPointChange($event)"
|
|
12262
|
+
/>
|
|
12263
|
+
</div>
|
|
12264
|
+
}
|
|
12265
|
+
|
|
11530
12266
|
@if (saveError()) {
|
|
11531
12267
|
<mcms-alert variant="destructive" class="mt-4">
|
|
11532
12268
|
{{ saveError() }}
|
|
@@ -11544,7 +12280,7 @@ class MediaEditDialog {
|
|
|
11544
12280
|
</button>
|
|
11545
12281
|
</mcms-dialog-footer>
|
|
11546
12282
|
</mcms-dialog>
|
|
11547
|
-
`, isInline: true, dependencies: [{ kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: Dialog, selector: "mcms-dialog", inputs: ["class"] }, { kind: "component", type: DialogHeader, selector: "mcms-dialog-header" }, { kind: "component", type: DialogTitle, selector: "mcms-dialog-title", inputs: ["id"] }, { kind: "component", type: DialogContent, selector: "mcms-dialog-content" }, { kind: "component", type: DialogFooter, selector: "mcms-dialog-footer" }, { kind: "directive", type: DialogClose, selector: "[mcmsDialogClose]", inputs: ["mcmsDialogClose"] }, { kind: "component", type: Input, selector: "mcms-input", inputs: ["value", "disabled", "errors", "touched", "invalid", "readonly", "required", "type", "id", "name", "placeholder", "autocomplete", "ariaLabel", "describedBy", "min", "max", "step"], outputs: ["valueChange", "blurred"] }, { kind: "component", type: Textarea, selector: "mcms-textarea", inputs: ["value", "disabled", "errors", "touched", "invalid", "readonly", "required", "id", "name", "placeholder", "rows", "ariaLabel", "describedBy"], outputs: ["valueChange", "blurred"] }, { kind: "component", type: Label, selector: "mcms-label", inputs: ["for", "required", "disabled", "class"] }, { kind: "component", type: Spinner, selector: "mcms-spinner", inputs: ["size", "label", "class"] }, { kind: "component", type: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: MediaPreviewComponent, selector: "mcms-media-preview", inputs: ["media", "size", "class", "rounded"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
12283
|
+
`, isInline: true, dependencies: [{ kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: Dialog, selector: "mcms-dialog", inputs: ["class"] }, { kind: "component", type: DialogHeader, selector: "mcms-dialog-header" }, { kind: "component", type: DialogTitle, selector: "mcms-dialog-title", inputs: ["id"] }, { kind: "component", type: DialogContent, selector: "mcms-dialog-content" }, { kind: "component", type: DialogFooter, selector: "mcms-dialog-footer" }, { kind: "directive", type: DialogClose, selector: "[mcmsDialogClose]", inputs: ["mcmsDialogClose"] }, { kind: "component", type: Input, selector: "mcms-input", inputs: ["value", "disabled", "errors", "touched", "invalid", "readonly", "required", "type", "id", "name", "placeholder", "autocomplete", "ariaLabel", "describedBy", "min", "max", "step"], outputs: ["valueChange", "blurred"] }, { kind: "component", type: Textarea, selector: "mcms-textarea", inputs: ["value", "disabled", "errors", "touched", "invalid", "readonly", "required", "id", "name", "placeholder", "rows", "ariaLabel", "describedBy"], outputs: ["valueChange", "blurred"] }, { kind: "component", type: Label, selector: "mcms-label", inputs: ["for", "required", "disabled", "class"] }, { kind: "component", type: Spinner, selector: "mcms-spinner", inputs: ["size", "label", "class"] }, { kind: "component", type: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: MediaPreviewComponent, selector: "mcms-media-preview", inputs: ["media", "size", "class", "rounded"] }, { kind: "component", type: FocalPointPickerComponent, selector: "mcms-focal-point-picker", inputs: ["imageUrl", "focalPoint", "alt", "naturalWidth", "naturalHeight", "imageSizes"], outputs: ["focalPointChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11548
12284
|
}
|
|
11549
12285
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: MediaEditDialog, decorators: [{
|
|
11550
12286
|
type: Component,
|
|
@@ -11564,6 +12300,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
11564
12300
|
Spinner,
|
|
11565
12301
|
Alert,
|
|
11566
12302
|
MediaPreviewComponent,
|
|
12303
|
+
FocalPointPickerComponent,
|
|
11567
12304
|
],
|
|
11568
12305
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
11569
12306
|
host: { style: 'display: block; width: 100%' },
|
|
@@ -11608,6 +12345,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
11608
12345
|
</div>
|
|
11609
12346
|
</div>
|
|
11610
12347
|
|
|
12348
|
+
@if (isImage()) {
|
|
12349
|
+
<div class="mt-4">
|
|
12350
|
+
<p class="mb-2 text-sm font-medium">Focal Point</p>
|
|
12351
|
+
<mcms-focal-point-picker
|
|
12352
|
+
[imageUrl]="imageUrl()"
|
|
12353
|
+
[focalPoint]="focalPointValue()"
|
|
12354
|
+
[naturalWidth]="media.width ?? 0"
|
|
12355
|
+
[naturalHeight]="media.height ?? 0"
|
|
12356
|
+
[alt]="media.alt ?? media.filename"
|
|
12357
|
+
[imageSizes]="imageSizes"
|
|
12358
|
+
(focalPointChange)="onFocalPointChange($event)"
|
|
12359
|
+
/>
|
|
12360
|
+
</div>
|
|
12361
|
+
}
|
|
12362
|
+
|
|
11611
12363
|
@if (saveError()) {
|
|
11612
12364
|
<mcms-alert variant="destructive" class="mt-4">
|
|
11613
12365
|
{{ saveError() }}
|
|
@@ -11668,6 +12420,7 @@ function getInputElement(event) {
|
|
|
11668
12420
|
*/
|
|
11669
12421
|
class MediaLibraryPage {
|
|
11670
12422
|
document = inject(DOCUMENT);
|
|
12423
|
+
route = inject(ActivatedRoute);
|
|
11671
12424
|
api = injectMomentumAPI();
|
|
11672
12425
|
uploadService = inject(UploadService);
|
|
11673
12426
|
feedback = inject(FeedbackService);
|
|
@@ -11675,6 +12428,12 @@ class MediaLibraryPage {
|
|
|
11675
12428
|
dialog = inject(DialogService);
|
|
11676
12429
|
destroyRef = inject(DestroyRef);
|
|
11677
12430
|
uploadSubscriptions = [];
|
|
12431
|
+
/** Image sizes config from the media collection (for crop previews in edit dialog) */
|
|
12432
|
+
mediaImageSizes = (() => {
|
|
12433
|
+
const collections = getCollectionsFromRouteData(this.route.parent?.snapshot.data);
|
|
12434
|
+
const mediaColl = collections.find((c) => c.slug === 'media');
|
|
12435
|
+
return mediaColl?.upload?.imageSizes ?? [];
|
|
12436
|
+
})();
|
|
11678
12437
|
/** Internal state */
|
|
11679
12438
|
isLoading = signal(true, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
11680
12439
|
mediaItems = signal([], ...(ngDevMode ? [{ debugName: "mediaItems" }] : []));
|
|
@@ -11826,7 +12585,7 @@ class MediaLibraryPage {
|
|
|
11826
12585
|
*/
|
|
11827
12586
|
editMedia(media) {
|
|
11828
12587
|
const dialogRef = this.dialog.open(MediaEditDialog, {
|
|
11829
|
-
data: { media },
|
|
12588
|
+
data: { media, imageSizes: this.mediaImageSizes },
|
|
11830
12589
|
width: '36rem',
|
|
11831
12590
|
});
|
|
11832
12591
|
dialogRef.afterClosed.subscribe((result) => {
|
|
@@ -13454,18 +14213,18 @@ function provideMomentumFieldRenderers() {
|
|
|
13454
14213
|
registry.register('checkbox', () => Promise.resolve().then(function () { return checkboxField_component; }).then((m) => m.CheckboxFieldRenderer));
|
|
13455
14214
|
registry.register('date', () => Promise.resolve().then(function () { return dateField_component; }).then((m) => m.DateFieldRenderer));
|
|
13456
14215
|
registry.register('upload', () => Promise.resolve().then(function () { return uploadField_component; }).then((m) => m.UploadFieldRenderer));
|
|
13457
|
-
registry.register('richText', () => import('./momentumcms-admin-rich-text-field.component-
|
|
14216
|
+
registry.register('richText', () => import('./momentumcms-admin-rich-text-field.component-BVAQkX3O.mjs').then((m) => m.RichTextFieldRenderer));
|
|
13458
14217
|
// Layout field renderers (support nested field rendering)
|
|
13459
|
-
registry.register('group', () => import('./momentumcms-admin-group-field.component-
|
|
13460
|
-
registry.register('array', () => import('./momentumcms-admin-array-field.component-
|
|
13461
|
-
registry.register('blocks', () => import('./momentumcms-admin-blocks-field.component-
|
|
14218
|
+
registry.register('group', () => import('./momentumcms-admin-group-field.component-CMKcqfjy.mjs').then((m) => m.GroupFieldRenderer));
|
|
14219
|
+
registry.register('array', () => import('./momentumcms-admin-array-field.component-DH6vaHO-.mjs').then((m) => m.ArrayFieldRenderer));
|
|
14220
|
+
registry.register('blocks', () => import('./momentumcms-admin-blocks-field.component-BxJRfiV3.mjs').then((m) => m.BlocksFieldRenderer));
|
|
13462
14221
|
// Visual block editor variant (blocks field with admin.editor === 'visual')
|
|
13463
14222
|
registry.register('blocks-visual', () => Promise.resolve().then(function () { return visualBlockEditor_component; }).then((m) => m.VisualBlockEditorComponent));
|
|
13464
|
-
registry.register('relationship', () => import('./momentumcms-admin-relationship-field.component-
|
|
14223
|
+
registry.register('relationship', () => import('./momentumcms-admin-relationship-field.component-DNZUCENa.mjs').then((m) => m.RelationshipFieldRenderer));
|
|
13465
14224
|
// Layout-only renderers (tabs, collapsible, row)
|
|
13466
|
-
registry.register('tabs', () => import('./momentumcms-admin-tabs-field.component-
|
|
13467
|
-
registry.register('collapsible', () => import('./momentumcms-admin-collapsible-field.component-
|
|
13468
|
-
registry.register('row', () => import('./momentumcms-admin-row-field.component-
|
|
14225
|
+
registry.register('tabs', () => import('./momentumcms-admin-tabs-field.component-qYlbl8Ud.mjs').then((m) => m.TabsFieldRenderer));
|
|
14226
|
+
registry.register('collapsible', () => import('./momentumcms-admin-collapsible-field.component-CsjYCkGw.mjs').then((m) => m.CollapsibleFieldRenderer));
|
|
14227
|
+
registry.register('row', () => import('./momentumcms-admin-row-field.component-0F6cnUK_.mjs').then((m) => m.RowFieldRenderer));
|
|
13469
14228
|
};
|
|
13470
14229
|
},
|
|
13471
14230
|
},
|
|
@@ -15245,4 +16004,4 @@ var uploadField_component = /*#__PURE__*/Object.freeze({
|
|
|
15245
16004
|
*/
|
|
15246
16005
|
|
|
15247
16006
|
export { adminGuard as $, AdminShellComponent as A, BlockEditDialog as B, CheckboxFieldRenderer as C, DashboardPage as D, EntityFormWidget as E, FieldRenderer as F, MediaLibraryPage as G, MediaPickerDialog as H, MediaPreviewComponent as I, MomentumApiService as J, MomentumAuthService as K, LivePreviewComponent as L, MOMENTUM_API as M, NumberFieldRenderer as N, ResetPasswordPage as O, PublishControlsWidget as P, SKIP_AUTO_TOAST as Q, ResetPasswordFormComponent as R, SHEET_QUERY_PARAMS as S, SelectFieldRenderer as T, SetupPage as U, TextFieldRenderer as V, UploadFieldRenderer as W, UploadService as X, VersionHistoryWidget as Y, VersionService as Z, VisualBlockEditorComponent as _, getFieldNodeState as a, authGuard as a0, collectionAccessGuard as a1, crudToastInterceptor as a2, guestGuard as a3, injectHasAnyRole as a4, injectHasRole as a5, injectIsAdmin as a6, injectIsAuthenticated as a7, injectMomentumAPI as a8, injectTypedMomentumAPI as a9, injectUser as aa, injectUserRole as ab, injectVersionService as ac, momentumAdminRoutes as ad, provideFieldRenderer as ae, provideMomentumAPI as af, provideMomentumFieldRenderers as ag, setupGuard as ah, unsavedChangesGuard as ai, getSubNode as b, getFieldDefaultValue as c, EntitySheetService as d, getTitleField as e, AdminSidebarWidget as f, getGlobalsFromRouteData as g, BlockInserterComponent as h, isRecord as i, BlockWrapperComponent as j, CollectionAccessService as k, CollectionCardWidget as l, CollectionEditPage as m, normalizeBlockDefaults as n, CollectionListPage as o, CollectionViewPage as p, DateFieldRenderer as q, EntityListWidget as r, EntityViewWidget as s, FeedbackService as t, FieldRendererRegistry as u, ForgotPasswordFormComponent as v, ForgotPasswordPage as w, LoginPage as x, MOMENTUM_API_CONTEXT as y, McmsThemeService as z };
|
|
15248
|
-
//# sourceMappingURL=momentumcms-admin-momentumcms-admin-
|
|
16007
|
+
//# sourceMappingURL=momentumcms-admin-momentumcms-admin-BTZEdMNj.mjs.map
|