@morphika/webframe 0.1.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/LICENSE +21 -0
- package/README.md +46 -0
- package/admin/assets.ts +4 -0
- package/admin/database.ts +4 -0
- package/admin/index.ts +6 -0
- package/admin/login.ts +4 -0
- package/admin/navigation.ts +4 -0
- package/admin/pages-editor.ts +4 -0
- package/admin/pages.ts +4 -0
- package/admin/projects-editor.ts +4 -0
- package/admin/projects.ts +4 -0
- package/admin/settings.ts +4 -0
- package/admin/setup.ts +4 -0
- package/admin/storage.ts +4 -0
- package/admin/styles.ts +4 -0
- package/app/(site)/[slug]/loading.tsx +20 -0
- package/app/(site)/[slug]/page.tsx +83 -0
- package/app/(site)/error.tsx +32 -0
- package/app/(site)/layout.tsx +53 -0
- package/app/(site)/loading.tsx +20 -0
- package/app/(site)/not-found.tsx +41 -0
- package/app/(site)/page.tsx +43 -0
- package/app/(site)/preview/page.tsx +99 -0
- package/app/(site)/work/[slug]/loading.tsx +23 -0
- package/app/(site)/work/[slug]/page.tsx +84 -0
- package/app/admin/assets/page.tsx +573 -0
- package/app/admin/database/page.tsx +302 -0
- package/app/admin/error.tsx +53 -0
- package/app/admin/layout.tsx +273 -0
- package/app/admin/login/page.tsx +88 -0
- package/app/admin/navigation/page.tsx +157 -0
- package/app/admin/page.tsx +17 -0
- package/app/admin/pages/[slug]/page.tsx +849 -0
- package/app/admin/pages/page.tsx +588 -0
- package/app/admin/projects/[slug]/page.tsx +3 -0
- package/app/admin/projects/page.tsx +669 -0
- package/app/admin/settings/page.tsx +132 -0
- package/app/admin/setup/page.tsx +64 -0
- package/app/admin/storage/page.tsx +518 -0
- package/app/admin/styles/page.tsx +243 -0
- package/app/api/admin/assets/file/route.ts +81 -0
- package/app/api/admin/assets/health/route.ts +170 -0
- package/app/api/admin/assets/register/route.ts +163 -0
- package/app/api/admin/assets/registry/route.ts +98 -0
- package/app/api/admin/assets/relink/confirm/route.ts +242 -0
- package/app/api/admin/assets/relink/route.ts +202 -0
- package/app/api/admin/assets/scan/route.ts +271 -0
- package/app/api/admin/auth/route.ts +160 -0
- package/app/api/admin/custom-sections/[slug]/route.ts +159 -0
- package/app/api/admin/custom-sections/route.ts +127 -0
- package/app/api/admin/database/route.ts +53 -0
- package/app/api/admin/pages/[slug]/duplicate/route.ts +91 -0
- package/app/api/admin/pages/[slug]/route.ts +617 -0
- package/app/api/admin/pages/[slug]/set-home/route.ts +76 -0
- package/app/api/admin/pages/route.ts +129 -0
- package/app/api/admin/preview/route.ts +53 -0
- package/app/api/admin/r2/connect/route.ts +181 -0
- package/app/api/admin/r2/delete/route.ts +198 -0
- package/app/api/admin/r2/disconnect/route.ts +42 -0
- package/app/api/admin/r2/rename/route.ts +265 -0
- package/app/api/admin/r2/status/route.ts +106 -0
- package/app/api/admin/r2/upload-url/route.ts +148 -0
- package/app/api/admin/revalidate/route.ts +55 -0
- package/app/api/admin/settings/route.ts +279 -0
- package/app/api/admin/setup/complete/route.ts +51 -0
- package/app/api/admin/setup/route.ts +118 -0
- package/app/api/admin/storage/switch/route.ts +117 -0
- package/app/api/admin/styles/fonts/route.ts +97 -0
- package/app/api/admin/styles/route.ts +304 -0
- package/app/api/assets/[...path]/route.ts +98 -0
- package/app/api/custom-sections/[id]/route.ts +43 -0
- package/app/api/draft-mode/disable/route.ts +10 -0
- package/app/api/draft-mode/enable/route.ts +26 -0
- package/app/api/projects/route.ts +42 -0
- package/app/api/styles/route.ts +88 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +7 -0
- package/app/layout.tsx +53 -0
- package/app/robots.ts +17 -0
- package/app/sitemap.ts +48 -0
- package/app/studio/[[...index]]/page.tsx +8 -0
- package/components/admin/MetadataEditor.tsx +173 -0
- package/components/admin/PublishToggle.tsx +130 -0
- package/components/admin/icons.tsx +40 -0
- package/components/admin/nav-builder/NavBuilder.tsx +182 -0
- package/components/admin/nav-builder/NavBuilderGrid.tsx +326 -0
- package/components/admin/nav-builder/NavGeneralSettings.tsx +275 -0
- package/components/admin/nav-builder/NavGridCell.tsx +48 -0
- package/components/admin/nav-builder/NavGridItem.tsx +189 -0
- package/components/admin/nav-builder/NavItemSettings.tsx +288 -0
- package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -0
- package/components/admin/nav-builder/NavLivePreview.tsx +125 -0
- package/components/admin/nav-builder/NavSettingsFields.tsx +248 -0
- package/components/admin/nav-builder/NavSettingsPanel.tsx +127 -0
- package/components/admin/nav-builder/index.ts +10 -0
- package/components/admin/nav-builder/nav-builder-utils.ts +238 -0
- package/components/admin/setup-wizard/BrandingStep.tsx +218 -0
- package/components/admin/setup-wizard/DatabaseStep.tsx +331 -0
- package/components/admin/setup-wizard/DoneStep.tsx +187 -0
- package/components/admin/setup-wizard/SetupWizard.tsx +166 -0
- package/components/admin/setup-wizard/StorageStep.tsx +308 -0
- package/components/admin/setup-wizard/WelcomeStep.tsx +96 -0
- package/components/admin/setup-wizard/index.ts +9 -0
- package/components/admin/styles/ColorsEditor.tsx +214 -0
- package/components/admin/styles/FontsEditor.tsx +258 -0
- package/components/admin/styles/GridLayoutEditor.tsx +292 -0
- package/components/admin/styles/LinksButtonsEditor.tsx +120 -0
- package/components/admin/styles/TypographyEditor.tsx +266 -0
- package/components/admin/styles/index.ts +9 -0
- package/components/admin/styles/shared.tsx +68 -0
- package/components/blocks/BlockRenderer.tsx +404 -0
- package/components/blocks/ButtonBlockRenderer.tsx +52 -0
- package/components/blocks/CoverBlockRenderer.tsx +239 -0
- package/components/blocks/CustomSectionInstanceRenderer.tsx +82 -0
- package/components/blocks/EnterAnimationWrapper.tsx +140 -0
- package/components/blocks/HoverAnimationWrapper.tsx +308 -0
- package/components/blocks/ImageBlockRenderer.tsx +61 -0
- package/components/blocks/ImageGridBlockRenderer.tsx +545 -0
- package/components/blocks/PageBackground.tsx +28 -0
- package/components/blocks/PageNavAnimation.tsx +35 -0
- package/components/blocks/PageNavColor.tsx +24 -0
- package/components/blocks/PageRenderer.tsx +142 -0
- package/components/blocks/ParallaxGroupRenderer.tsx +448 -0
- package/components/blocks/ParallaxSlideRenderer.tsx +175 -0
- package/components/blocks/ProjectGridBlockRenderer.tsx +556 -0
- package/components/blocks/SectionRenderer.tsx +170 -0
- package/components/blocks/SectionV2Renderer.tsx +330 -0
- package/components/blocks/ShaderCanvas.tsx +392 -0
- package/components/blocks/SpacerBlockRenderer.tsx +17 -0
- package/components/blocks/TextBlockRenderer.tsx +87 -0
- package/components/blocks/TypewriterRichText.tsx +464 -0
- package/components/blocks/TypewriterWrapper.tsx +149 -0
- package/components/blocks/VideoBlockRenderer.tsx +304 -0
- package/components/blocks/index.ts +2 -0
- package/components/builder/AssetBrowser.tsx +2 -0
- package/components/builder/BlockLivePreview.tsx +101 -0
- package/components/builder/BlockTypePicker.tsx +178 -0
- package/components/builder/BuilderCanvas.tsx +354 -0
- package/components/builder/CanvasMinimap.tsx +200 -0
- package/components/builder/CanvasToolbar.tsx +202 -0
- package/components/builder/ColorPicker.tsx +243 -0
- package/components/builder/ColorSwatchPicker.tsx +274 -0
- package/components/builder/ColumnDragContext.tsx +51 -0
- package/components/builder/ColumnDragOverlay.tsx +110 -0
- package/components/builder/CustomSectionInstanceCard.tsx +97 -0
- package/components/builder/DeviceFrame.tsx +123 -0
- package/components/builder/DndWrapper.tsx +337 -0
- package/components/builder/InsertionLines.tsx +186 -0
- package/components/builder/ParallaxGroupCanvas.tsx +228 -0
- package/components/builder/ParallaxSlideHeader.tsx +113 -0
- package/components/builder/ReadOnlyFrame.tsx +417 -0
- package/components/builder/SectionEditorBar.tsx +288 -0
- package/components/builder/SectionTypePicker.tsx +422 -0
- package/components/builder/SectionV2Canvas.tsx +297 -0
- package/components/builder/SectionV2Column.tsx +488 -0
- package/components/builder/SettingsPanel.tsx +911 -0
- package/components/builder/SortableBlock.tsx +230 -0
- package/components/builder/SortableRow.tsx +362 -0
- package/components/builder/VirtualAssetGrid.tsx +397 -0
- package/components/builder/asset-browser/AssetBrowser.tsx +178 -0
- package/components/builder/asset-browser/FileLightbox.tsx +116 -0
- package/components/builder/asset-browser/FolderTreeItem.tsx +55 -0
- package/components/builder/asset-browser/R2BrowserContent.tsx +436 -0
- package/components/builder/asset-browser/R2ContextMenu.tsx +98 -0
- package/components/builder/asset-browser/VideoThumbnail.tsx +63 -0
- package/components/builder/asset-browser/helpers.ts +88 -0
- package/components/builder/asset-browser/index.ts +1 -0
- package/components/builder/asset-browser/types.ts +49 -0
- package/components/builder/asset-browser/useAssetBrowser.ts +344 -0
- package/components/builder/asset-browser/useR2DragDrop.ts +116 -0
- package/components/builder/asset-browser/useR2Operations.ts +189 -0
- package/components/builder/blockStyles.tsx +295 -0
- package/components/builder/editors/ButtonBlockEditor.tsx +184 -0
- package/components/builder/editors/CoverBlockEditor.tsx +488 -0
- package/components/builder/editors/EnterAnimationPicker.tsx +297 -0
- package/components/builder/editors/HoverEffectPicker.tsx +209 -0
- package/components/builder/editors/ImageBlockEditor.tsx +206 -0
- package/components/builder/editors/ImageGridBlockEditor.tsx +386 -0
- package/components/builder/editors/ProjectGridEditor.tsx +648 -0
- package/components/builder/editors/SpacerBlockEditor.tsx +167 -0
- package/components/builder/editors/StaggerSettings.tsx +108 -0
- package/components/builder/editors/TextAlignmentIcons.tsx +39 -0
- package/components/builder/editors/TextBlockEditor.tsx +462 -0
- package/components/builder/editors/TextStylePicker.tsx +183 -0
- package/components/builder/editors/VideoBlockEditor.tsx +278 -0
- package/components/builder/editors/index.ts +10 -0
- package/components/builder/editors/shared.tsx +345 -0
- package/components/builder/hooks/useColumnDrag.ts +472 -0
- package/components/builder/hooks/useColumnResize.ts +221 -0
- package/components/builder/index.ts +12 -0
- package/components/builder/live-preview/LiveButtonPreview.tsx +38 -0
- package/components/builder/live-preview/LiveCoverPreview.tsx +146 -0
- package/components/builder/live-preview/LiveImageGridPreview.tsx +123 -0
- package/components/builder/live-preview/LiveImagePreview.tsx +107 -0
- package/components/builder/live-preview/LiveProjectGridPreview.tsx +1010 -0
- package/components/builder/live-preview/LiveSpacerPreview.tsx +9 -0
- package/components/builder/live-preview/LiveTextEditor.tsx +198 -0
- package/components/builder/live-preview/LiveVideoPreview.tsx +98 -0
- package/components/builder/live-preview/index.ts +10 -0
- package/components/builder/live-preview/shared.tsx +153 -0
- package/components/builder/settings-panel/BlockLayoutTab.tsx +532 -0
- package/components/builder/settings-panel/BlockSettings.tsx +94 -0
- package/components/builder/settings-panel/ColumnV2Settings.tsx +160 -0
- package/components/builder/settings-panel/LayoutTab.tsx +310 -0
- package/components/builder/settings-panel/PageSettings.tsx +200 -0
- package/components/builder/settings-panel/ParallaxGroupSettings.tsx +118 -0
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +178 -0
- package/components/builder/settings-panel/SectionV2AnimationTab.tsx +103 -0
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +312 -0
- package/components/builder/settings-panel/SectionV2Settings.tsx +323 -0
- package/components/builder/settings-panel/TRBLInputs.tsx +51 -0
- package/components/builder/settings-panel/index.ts +19 -0
- package/components/builder/settings-panel/responsive-helpers.ts +524 -0
- package/components/ui/CustomCursor.tsx +118 -0
- package/components/ui/NavContentLightbox.tsx +152 -0
- package/components/ui/Navbar.tsx +582 -0
- package/components/ui/PortfolioTracker.tsx +87 -0
- package/components/ui/ScrollToTop.tsx +47 -0
- package/lib/animation/enter-presets.ts +147 -0
- package/lib/animation/enter-resolve.ts +90 -0
- package/lib/animation/enter-types.ts +128 -0
- package/lib/animation/hover-effect-presets.ts +210 -0
- package/lib/animation/hover-effect-types.ts +126 -0
- package/lib/asset-retry.ts +111 -0
- package/lib/assets.ts +92 -0
- package/lib/audit.ts +35 -0
- package/lib/auth-token.ts +94 -0
- package/lib/auth.ts +13 -0
- package/lib/builder/cascade-helpers.ts +51 -0
- package/lib/builder/cascade.ts +533 -0
- package/lib/builder/constants.ts +103 -0
- package/lib/builder/defaults.ts +182 -0
- package/lib/builder/history.ts +48 -0
- package/lib/builder/index.ts +21 -0
- package/lib/builder/layout-styles.ts +344 -0
- package/lib/builder/masonry.ts +166 -0
- package/lib/builder/responsive.ts +156 -0
- package/lib/builder/serializer.ts +845 -0
- package/lib/builder/store-blocks.ts +193 -0
- package/lib/builder/store-canvas.ts +319 -0
- package/lib/builder/store-helpers.ts +490 -0
- package/lib/builder/store-sections.ts +709 -0
- package/lib/builder/store.ts +333 -0
- package/lib/builder/templates.ts +297 -0
- package/lib/builder/types.ts +374 -0
- package/lib/builder/utils.ts +37 -0
- package/lib/color-utils.ts +116 -0
- package/lib/config/index.ts +57 -0
- package/lib/config/types.ts +122 -0
- package/lib/contexts/AssetContext.tsx +79 -0
- package/lib/contexts/NavAnimationContext.tsx +44 -0
- package/lib/contexts/NavColorContext.tsx +38 -0
- package/lib/contexts/PageExitContext.tsx +194 -0
- package/lib/contexts/ThumbStatusContext.tsx +83 -0
- package/lib/csrf-client.ts +34 -0
- package/lib/csrf.ts +68 -0
- package/lib/format-utils.ts +24 -0
- package/lib/hooks/useViewport.ts +42 -0
- package/lib/logger.ts +81 -0
- package/lib/revalidate.ts +23 -0
- package/lib/sanitize.ts +91 -0
- package/lib/sanity/client.ts +8 -0
- package/lib/sanity/queries.ts +486 -0
- package/lib/sanity/types.ts +869 -0
- package/lib/sanity/writeClient.ts +24 -0
- package/lib/security.ts +402 -0
- package/lib/setup/detect.ts +156 -0
- package/lib/shader/glsl/index.ts +27 -0
- package/lib/shader/glsl/pixelate.ts +51 -0
- package/lib/shader/glsl/rgb-shift.ts +45 -0
- package/lib/shader/glsl/ripple.ts +46 -0
- package/lib/shader/glsl/vertex.ts +14 -0
- package/lib/storage/index.ts +211 -0
- package/lib/storage/r2-adapter.ts +286 -0
- package/lib/storage/types.ts +125 -0
- package/lib/styles/provider.tsx +267 -0
- package/lib/thumbnails/generate.ts +151 -0
- package/lib/utils.ts +6 -0
- package/package.json +212 -0
- package/sanity/compose.ts +65 -0
- package/sanity/sanity.config.ts +126 -0
- package/sanity/schemas/assetRegistry.ts +301 -0
- package/sanity/schemas/blocks/blockLayout.ts +90 -0
- package/sanity/schemas/blocks/buttonBlock.ts +82 -0
- package/sanity/schemas/blocks/coverBlock.ts +229 -0
- package/sanity/schemas/blocks/imageBlock.ts +58 -0
- package/sanity/schemas/blocks/imageGridBlock.ts +112 -0
- package/sanity/schemas/blocks/index.ts +9 -0
- package/sanity/schemas/blocks/projectGridBlock.ts +251 -0
- package/sanity/schemas/blocks/spacerBlock.ts +41 -0
- package/sanity/schemas/blocks/textBlock.ts +139 -0
- package/sanity/schemas/blocks/videoBlock.ts +80 -0
- package/sanity/schemas/customSection.ts +69 -0
- package/sanity/schemas/customSectionInstance.ts +163 -0
- package/sanity/schemas/index.ts +111 -0
- package/sanity/schemas/objects/enterAnimationConfig.ts +72 -0
- package/sanity/schemas/objects/hoverEffectConfig.ts +90 -0
- package/sanity/schemas/objects/parallaxGroup.ts +66 -0
- package/sanity/schemas/objects/parallaxSlide.ts +217 -0
- package/sanity/schemas/objects/typewriterConfig.ts +38 -0
- package/sanity/schemas/page.ts +162 -0
- package/sanity/schemas/pageSection.ts +157 -0
- package/sanity/schemas/pageSectionV2.ts +269 -0
- package/sanity/schemas/siteSettings.ts +256 -0
- package/sanity/schemas/siteStyles.ts +210 -0
- package/site/error.ts +4 -0
- package/site/index.ts +8 -0
- package/site/not-found.ts +4 -0
- package/site/page.ts +4 -0
- package/site/preview.ts +4 -0
- package/site/robots.ts +4 -0
- package/site/sitemap.ts +4 -0
- package/site/work.ts +4 -0
- package/studio/index.ts +4 -0
- package/styles/admin.css +85 -0
- package/styles/animations.css +237 -0
- package/styles/base.css +148 -0
- package/styles/globals.css +10 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// Masonry Engine — Pure JS, zero React deps
|
|
3
|
+
// ============================================
|
|
4
|
+
// Computes absolute card positions for the Project Grid v2.
|
|
5
|
+
// Used by both the builder preview and the public renderer
|
|
6
|
+
// to guarantee pixel-perfect parity.
|
|
7
|
+
|
|
8
|
+
// ─── Interfaces ───
|
|
9
|
+
|
|
10
|
+
export interface MasonryItem {
|
|
11
|
+
key: string;
|
|
12
|
+
aspectRatio: number; // width / height, e.g. 16/9 ≈ 1.778, 1/1 = 1, 9/16 ≈ 0.5625
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MasonryConfig {
|
|
16
|
+
columns: number; // 1–6
|
|
17
|
+
gapH: number; // horizontal gap in px
|
|
18
|
+
gapV: number; // vertical gap in px
|
|
19
|
+
containerWidth: number; // px (measured at runtime)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MasonryResult {
|
|
23
|
+
key: string;
|
|
24
|
+
x: number; // px from left
|
|
25
|
+
y: number; // px from top
|
|
26
|
+
width: number; // px
|
|
27
|
+
height: number; // px
|
|
28
|
+
column: number; // 0-based column index
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MasonryOutput {
|
|
32
|
+
items: MasonryResult[];
|
|
33
|
+
totalHeight: number; // px — height of tallest column (for container sizing)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface RatioResolverConfig {
|
|
37
|
+
gridRatios: string[]; // e.g. ["16/9", "1/1"]
|
|
38
|
+
seed?: number; // optional deterministic seed
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── Helpers ───
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse a ratio string like "16/9" into a numeric value (width / height).
|
|
45
|
+
* Returns 1 if the string is malformed.
|
|
46
|
+
*/
|
|
47
|
+
export function parseRatio(ratio: string): number {
|
|
48
|
+
const parts = ratio.split("/");
|
|
49
|
+
if (parts.length !== 2) return 1;
|
|
50
|
+
const w = Number(parts[0]);
|
|
51
|
+
const h = Number(parts[1]);
|
|
52
|
+
if (!w || !h || !isFinite(w) || !isFinite(h) || h === 0) return 1;
|
|
53
|
+
return w / h;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns the index of the shortest column. Tie-break: leftmost (lowest index).
|
|
58
|
+
*/
|
|
59
|
+
export function getShortestColumn(columnHeights: number[]): number {
|
|
60
|
+
if (columnHeights.length === 0) return 0;
|
|
61
|
+
let minIdx = 0;
|
|
62
|
+
let minVal = columnHeights[0];
|
|
63
|
+
for (let i = 1; i < columnHeights.length; i++) {
|
|
64
|
+
if (columnHeights[i] < minVal) {
|
|
65
|
+
minVal = columnHeights[i];
|
|
66
|
+
minIdx = i;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return minIdx;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve the aspect ratio for a single item.
|
|
74
|
+
*
|
|
75
|
+
* Priority: override > grid single ratio > deterministic random pick.
|
|
76
|
+
*
|
|
77
|
+
* The deterministic pick uses a simple hash:
|
|
78
|
+
* gridRatios[(index * 7 + (seed || 0)) % gridRatios.length]
|
|
79
|
+
* The * 7 prime multiplier avoids adjacent repeats.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveItemRatio(
|
|
82
|
+
index: number,
|
|
83
|
+
override: string | null | undefined,
|
|
84
|
+
config: RatioResolverConfig,
|
|
85
|
+
): number {
|
|
86
|
+
// Per-card override wins
|
|
87
|
+
if (override) {
|
|
88
|
+
return parseRatio(override);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const { gridRatios, seed } = config;
|
|
92
|
+
|
|
93
|
+
// Fallback if empty array
|
|
94
|
+
if (!gridRatios || gridRatios.length === 0) return 1;
|
|
95
|
+
|
|
96
|
+
// Single ratio — no randomness needed
|
|
97
|
+
if (gridRatios.length === 1) {
|
|
98
|
+
return parseRatio(gridRatios[0]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Deterministic pseudo-random pick
|
|
102
|
+
const pick = Math.abs((index * 7 + (seed || 0)) % gridRatios.length);
|
|
103
|
+
return parseRatio(gridRatios[pick]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── Core Algorithm ───
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Shortest-column masonry layout.
|
|
110
|
+
*
|
|
111
|
+
* 1. colWidth = (containerWidth - gapH * (columns - 1)) / columns
|
|
112
|
+
* 2. For each item → place in shortest column → compute x, y, height
|
|
113
|
+
* 3. totalHeight = max(columnHeights) - trailing gapV
|
|
114
|
+
*
|
|
115
|
+
* Edge cases:
|
|
116
|
+
* - containerWidth <= 0 → returns empty output
|
|
117
|
+
* - columns <= 0 → clamped to 1
|
|
118
|
+
* - empty items → returns { items: [], totalHeight: 0 }
|
|
119
|
+
*/
|
|
120
|
+
export function computeMasonry(
|
|
121
|
+
items: MasonryItem[],
|
|
122
|
+
config: MasonryConfig,
|
|
123
|
+
): MasonryOutput {
|
|
124
|
+
const { gapH, gapV, containerWidth } = config;
|
|
125
|
+
const columns = Math.max(1, Math.round(config.columns));
|
|
126
|
+
|
|
127
|
+
// Graceful handling of invalid container
|
|
128
|
+
if (containerWidth <= 0 || items.length === 0) {
|
|
129
|
+
return { items: [], totalHeight: 0 };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const colWidth =
|
|
133
|
+
(containerWidth - gapH * (columns - 1)) / columns;
|
|
134
|
+
|
|
135
|
+
// If column width is non-positive (e.g. gaps exceed container), bail
|
|
136
|
+
if (colWidth <= 0) {
|
|
137
|
+
return { items: [], totalHeight: 0 };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const columnHeights = new Array<number>(columns).fill(0);
|
|
141
|
+
const results: MasonryResult[] = [];
|
|
142
|
+
|
|
143
|
+
for (const item of items) {
|
|
144
|
+
const col = getShortestColumn(columnHeights);
|
|
145
|
+
const x = col * (colWidth + gapH);
|
|
146
|
+
const y = columnHeights[col];
|
|
147
|
+
const height = colWidth / (item.aspectRatio || 1); // guard div-by-zero
|
|
148
|
+
|
|
149
|
+
results.push({
|
|
150
|
+
key: item.key,
|
|
151
|
+
x,
|
|
152
|
+
y,
|
|
153
|
+
width: colWidth,
|
|
154
|
+
height,
|
|
155
|
+
column: col,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
columnHeights[col] += height + gapV;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// totalHeight = tallest column minus trailing gap
|
|
162
|
+
const maxHeight = Math.max(...columnHeights);
|
|
163
|
+
const totalHeight = Math.max(0, maxHeight - gapV);
|
|
164
|
+
|
|
165
|
+
return { items: results, totalHeight };
|
|
166
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Responsive block resolution.
|
|
3
|
+
*
|
|
4
|
+
* The responsive system works with a simple override model:
|
|
5
|
+
*
|
|
6
|
+
* 1. Each block stores its BASE properties (= desktop defaults).
|
|
7
|
+
* 2. Each block has an optional `responsive` field:
|
|
8
|
+
* { tablet?: Partial<Props>, phone?: Partial<Props> }
|
|
9
|
+
* 3. At render time, `resolveBlock(block, viewport)` returns a merged
|
|
10
|
+
* copy: { ...base, ...overrides[viewport] }.
|
|
11
|
+
*
|
|
12
|
+
* Desktop always returns the block as-is (no merge needed).
|
|
13
|
+
* Tablet/phone only store what differs from desktop — everything else
|
|
14
|
+
* inherits automatically.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ContentBlock } from "../../lib/sanity/types";
|
|
19
|
+
import type { DeviceViewport } from "./types";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Type-safe accessor for the `responsive` field on any ContentBlock.
|
|
23
|
+
* All block types in the union have `responsive?: ResponsiveOverrides<T>`,
|
|
24
|
+
* but TypeScript can't see the common property without narrowing.
|
|
25
|
+
*/
|
|
26
|
+
type ResponsiveData = Record<string, Record<string, unknown>> | undefined;
|
|
27
|
+
|
|
28
|
+
function getResponsive(block: ContentBlock): ResponsiveData {
|
|
29
|
+
// All ContentBlock variants have a `responsive` field — safe to access via index
|
|
30
|
+
return (block as { responsive?: ResponsiveData }).responsive;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============================================
|
|
34
|
+
// Block resolution
|
|
35
|
+
// ============================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a block for a given viewport by merging responsive overrides.
|
|
39
|
+
*
|
|
40
|
+
* - `desktop` → returns the block unchanged.
|
|
41
|
+
* - `tablet` / `phone` → shallow-merges the viewport overrides on top of
|
|
42
|
+
* the base block properties. Nested objects (like `style` on TextBlock)
|
|
43
|
+
* are deep-merged one level.
|
|
44
|
+
*
|
|
45
|
+
* The returned block always keeps the original `_type`, `_key`, and
|
|
46
|
+
* `responsive` fields intact.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveBlock<T extends ContentBlock>(
|
|
49
|
+
block: T,
|
|
50
|
+
viewport: DeviceViewport
|
|
51
|
+
): T {
|
|
52
|
+
if (viewport === "desktop") return block;
|
|
53
|
+
|
|
54
|
+
const responsive = getResponsive(block);
|
|
55
|
+
if (!responsive) return block;
|
|
56
|
+
|
|
57
|
+
const overrides = responsive[viewport];
|
|
58
|
+
if (!overrides || Object.keys(overrides).length === 0) return block;
|
|
59
|
+
|
|
60
|
+
// Shallow merge with special handling for nested `style` object
|
|
61
|
+
const merged = { ...block } as T;
|
|
62
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
63
|
+
if (key === "responsive" || key === "_type" || key === "_key") continue;
|
|
64
|
+
|
|
65
|
+
// Deep-merge one-level nested objects (e.g. TextBlock.style, CoverBlock.cta_button)
|
|
66
|
+
const blockRecord = block as unknown as Record<string, unknown>;
|
|
67
|
+
const mergedRecord = merged as unknown as Record<string, unknown>;
|
|
68
|
+
if (
|
|
69
|
+
value !== null &&
|
|
70
|
+
typeof value === "object" &&
|
|
71
|
+
!Array.isArray(value) &&
|
|
72
|
+
key in block &&
|
|
73
|
+
typeof blockRecord[key] === "object" &&
|
|
74
|
+
blockRecord[key] !== null
|
|
75
|
+
) {
|
|
76
|
+
mergedRecord[key] = {
|
|
77
|
+
...(blockRecord[key] as Record<string, unknown>),
|
|
78
|
+
...(value as Record<string, unknown>),
|
|
79
|
+
};
|
|
80
|
+
} else {
|
|
81
|
+
mergedRecord[key] = value;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return merged;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============================================
|
|
89
|
+
// Helpers for the settings panel
|
|
90
|
+
// ============================================
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Check if a block property has a responsive override for the given viewport.
|
|
94
|
+
* Used by the editor to show "inherited" vs "overridden" state.
|
|
95
|
+
*/
|
|
96
|
+
export function hasOverride(
|
|
97
|
+
block: ContentBlock,
|
|
98
|
+
viewport: DeviceViewport,
|
|
99
|
+
property: string
|
|
100
|
+
): boolean {
|
|
101
|
+
if (viewport === "desktop") return true; // desktop is always "the value"
|
|
102
|
+
const responsive = getResponsive(block);
|
|
103
|
+
if (!responsive?.[viewport]) return false;
|
|
104
|
+
return property in responsive[viewport];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get the effective value of a block property for a viewport.
|
|
109
|
+
* If the viewport has an override, returns it. Otherwise returns the base value.
|
|
110
|
+
*/
|
|
111
|
+
export function getEffectiveValue<T>(
|
|
112
|
+
block: ContentBlock,
|
|
113
|
+
viewport: DeviceViewport,
|
|
114
|
+
property: string,
|
|
115
|
+
baseValue: T
|
|
116
|
+
): T {
|
|
117
|
+
if (viewport === "desktop") return baseValue;
|
|
118
|
+
const responsive = getResponsive(block);
|
|
119
|
+
if (!responsive?.[viewport] || !(property in responsive[viewport])) {
|
|
120
|
+
return baseValue;
|
|
121
|
+
}
|
|
122
|
+
return responsive[viewport][property] as T;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Set or clear a responsive override for a specific property.
|
|
127
|
+
* Returns the updated `responsive` object to pass to `updateBlock()`.
|
|
128
|
+
*
|
|
129
|
+
* If `value` matches the base value, the override is removed (inherit).
|
|
130
|
+
* If `value` is `undefined`, the override is explicitly removed.
|
|
131
|
+
*/
|
|
132
|
+
export function setResponsiveOverride(
|
|
133
|
+
block: ContentBlock,
|
|
134
|
+
viewport: DeviceViewport,
|
|
135
|
+
property: string,
|
|
136
|
+
value: unknown
|
|
137
|
+
): { responsive: Record<string, Record<string, unknown>> } {
|
|
138
|
+
const existing = getResponsive(block) || {};
|
|
139
|
+
const viewportOverrides = { ...(existing[viewport] || {}) };
|
|
140
|
+
|
|
141
|
+
if (value === undefined) {
|
|
142
|
+
delete viewportOverrides[property];
|
|
143
|
+
} else {
|
|
144
|
+
viewportOverrides[property] = value;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Clean up empty viewport objects
|
|
148
|
+
const responsive = { ...existing } as Record<string, Record<string, unknown>>;
|
|
149
|
+
if (Object.keys(viewportOverrides).length === 0) {
|
|
150
|
+
delete responsive[viewport];
|
|
151
|
+
} else {
|
|
152
|
+
responsive[viewport] = viewportOverrides;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { responsive };
|
|
156
|
+
}
|