From 7f1e44e8b18509bb4015770cd599659ba3e252f4 Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Thu, 16 Apr 2026 19:15:25 +0200 Subject: [PATCH] commiat --- src/lib/cards/_base/BaseCard/BaseCard.svelte | 55 +-- .../_base/BaseCard/BaseEditingCard.svelte | 15 +- .../cards/_base/BaseCard/GridBaseCard.svelte | 69 +++ .../_base/BaseCard/GridBaseEditingCard.svelte | 64 +++ src/lib/cards/index.ts | 4 +- .../card-command/CardCommand.svelte | 8 +- .../GridSection/EditingGridSection.svelte | 29 +- .../sections/GridSection/GridSection.svelte | 6 +- .../sections/HeroSection/Decoration.svelte | 93 ++++ .../HeroSection/EditingHeroSection.svelte | 396 ++++++++++++++++++ .../sections/HeroSection/HeroSection.svelte | 92 ++++ src/lib/sections/HeroSection/index.ts | 90 ++++ src/lib/sections/index.ts | 3 +- src/lib/sections/types.ts | 6 +- src/lib/website/EditBar.svelte | 10 +- src/lib/website/EditableWebsite.svelte | 46 +- src/lib/website/EmbeddedCard.svelte | 2 +- src/lib/website/Website.svelte | 2 +- 18 files changed, 893 insertions(+), 97 deletions(-) create mode 100644 src/lib/cards/_base/BaseCard/GridBaseCard.svelte create mode 100644 src/lib/cards/_base/BaseCard/GridBaseEditingCard.svelte create mode 100644 src/lib/sections/HeroSection/Decoration.svelte create mode 100644 src/lib/sections/HeroSection/EditingHeroSection.svelte create mode 100644 src/lib/sections/HeroSection/HeroSection.svelte create mode 100644 src/lib/sections/HeroSection/index.ts diff --git a/src/lib/cards/_base/BaseCard/BaseCard.svelte b/src/lib/cards/_base/BaseCard/BaseCard.svelte index 74aac40..bddd479 100644 --- a/src/lib/cards/_base/BaseCard/BaseCard.svelte +++ b/src/lib/cards/_base/BaseCard/BaseCard.svelte @@ -1,5 +1,4 @@ + +
+ + {@render children?.()} + +
+ + diff --git a/src/lib/cards/_base/BaseCard/GridBaseEditingCard.svelte b/src/lib/cards/_base/BaseCard/GridBaseEditingCard.svelte new file mode 100644 index 0000000..0bfceb9 --- /dev/null +++ b/src/lib/cards/_base/BaseCard/GridBaseEditingCard.svelte @@ -0,0 +1,64 @@ + + +
+ + {@render children?.()} + +
+ + diff --git a/src/lib/cards/index.ts b/src/lib/cards/index.ts index 3515b34..0e20704 100644 --- a/src/lib/cards/index.ts +++ b/src/lib/cards/index.ts @@ -62,7 +62,6 @@ import { KichRecipeCardDefinition } from './social/KichRecipeCard'; import { KichRecipeCollectionCardDefinition } from './social/KichRecipeCollectionCard'; import { KichCookingLogCardDefinition } from './social/KichCookingLogCard'; import { SecretImageCardDefinition } from './media/SecretImageCard'; -import { HeroCardDefinition } from './sections/HeroCard'; // import { Model3DCardDefinition } from './visual/Model3DCard'; export const AllCardDefinitions = [ @@ -130,8 +129,7 @@ export const AllCardDefinitions = [ KichRecipeCardDefinition, KichRecipeCollectionCardDefinition, KichCookingLogCardDefinition, - SecretImageCardDefinition, - HeroCardDefinition + SecretImageCardDefinition ] as const; export const CardDefinitionsByType = AllCardDefinitions.reduce( diff --git a/src/lib/components/card-command/CardCommand.svelte b/src/lib/components/card-command/CardCommand.svelte index 28b6984..df1b65e 100644 --- a/src/lib/components/card-command/CardCommand.svelte +++ b/src/lib/components/card-command/CardCommand.svelte @@ -8,11 +8,13 @@ let { open = $bindable(false), onselect, - onlink + onlink, + filter }: { open: boolean; onselect: (cardDef: CardDefinition) => void; onlink?: (url: string, cardDef: CardDefinition) => void; + filter?: (cardDef: CardDefinition) => boolean; } = $props(); let collections = $state([]); @@ -31,7 +33,9 @@ }); let filteredCardDefs = $derived( - AllCardDefinitions.filter((d) => !d.canAdd || d.canAdd({ collections })) + AllCardDefinitions.filter( + (d) => (!d.canAdd || d.canAdd({ collections })) && (!filter || filter(d)) + ) ); $inspect(filteredCardDefs, 'filteredCardDefs'); diff --git a/src/lib/sections/GridSection/EditingGridSection.svelte b/src/lib/sections/GridSection/EditingGridSection.svelte index d8c95fa..edd85a5 100644 --- a/src/lib/sections/GridSection/EditingGridSection.svelte +++ b/src/lib/sections/GridSection/EditingGridSection.svelte @@ -1,7 +1,8 @@ + +{#if item && isEditing} +
+
+ ondelete?.()} showGridControls={false}> + + +
+
+{:else if item} +
+
+ + + +
+
+{:else if isEditing} + +{/if} + + diff --git a/src/lib/sections/HeroSection/EditingHeroSection.svelte b/src/lib/sections/HeroSection/EditingHeroSection.svelte new file mode 100644 index 0000000..ad7cb85 --- /dev/null +++ b/src/lib/sections/HeroSection/EditingHeroSection.svelte @@ -0,0 +1,396 @@ + + +
+ {#if hovered || isActive} +
+
+ + Hero +
+
+ +
+ + {#snippet child({ props })} + + {/snippet} +
+
+ +
+ d.showBadge !== false, (val) => update('showBadge', val)} + id="hero-show-badge" + variant="secondary" + /> + +
+
+ d.showSubtitle !== false, (val) => update('showSubtitle', val)} + id="hero-show-subtitle" + variant="secondary" + /> + +
+
+ d.showButton !== false, (val) => update('showButton', val)} + id="hero-show-button" + variant="secondary" + /> + +
+
+ +
+ + update('buttonHref', (e.target as HTMLInputElement).value)} + placeholder="example.com" + class="text-sm" + onkeydown={(event) => { + if (event.code === 'Enter') { + event.preventDefault(); + confirmUrl(); + } + }} + /> +
+ +
+ + d.textAlign ?? 'center', + (value) => { + if (value) update('textAlign', value); + } + } + > + + + + + + + + + + +
+ + {#if filledSlots.length > 0} +
+ + {#each filledSlots as { slot, item: slotItem } (slot.id)} +
+ + {slot.side === 'left' ? 'L' : 'R'}{DEFAULT_DECORATION_SLOTS.filter( + (s) => s.side === slot.side + ).indexOf(slot) + 1} + + + {CardDefinitionsByType[slotItem?.cardType ?? '']?.name ?? slotItem?.cardType} + + +
+ {/each} +
+ {/if} +
+
+
+ {/if} + +
+ {#each DEFAULT_DECORATION_SLOTS as slot (slot.id)} + {@const slotItem = getSlotItem(slot, assignments, sectionItems)} + clearSlot(slot.id)} + onclick={() => openSlotPicker(slot.id)} + /> + {/each} + +
+ {#if d.showBadge !== false} + + update('badge', text)} + /> + + {/if} + + update('title', text)} + /> + + {#if d.showSubtitle !== false} + update('subtitle', text)} + /> + {/if} + + {#if d.showButton !== false} +
+ update('buttonText', text)} + /> +
+ {/if} +
+
+
diff --git a/src/lib/sections/HeroSection/HeroSection.svelte b/src/lib/sections/HeroSection/HeroSection.svelte new file mode 100644 index 0000000..1468cd6 --- /dev/null +++ b/src/lib/sections/HeroSection/HeroSection.svelte @@ -0,0 +1,92 @@ + + +
+
+ {#each DEFAULT_DECORATION_SLOTS as slot (slot.id)} + + {/each} + +
+ {#if d.showBadge !== false && d.badge} + + {d.badge} + + {/if} + +

+ {d.title || 'My cool website'} +

+ + {#if d.showSubtitle !== false && d.subtitle} +

+ {d.subtitle} +

+ {/if} + + {#if d.showButton !== false && d.buttonText} + {#if d.buttonHref === '#login'} + + {:else} + + {d.buttonText} + + {/if} + {/if} +
+
+
diff --git a/src/lib/sections/HeroSection/index.ts b/src/lib/sections/HeroSection/index.ts new file mode 100644 index 0000000..197e06d --- /dev/null +++ b/src/lib/sections/HeroSection/index.ts @@ -0,0 +1,90 @@ +import type { Item, SectionRecord } from '$lib/types'; +import type { SectionDefinition } from '../types'; +import EditingHeroSection from './EditingHeroSection.svelte'; +import HeroSection from './HeroSection.svelte'; + +export type DecorationSlot = { + id: string; + side: 'left' | 'right'; + top: number; + rotation: number; +}; + +export const DEFAULT_DECORATION_SLOTS: DecorationSlot[] = [ + { id: 'slot-l-0', side: 'left', top: 15, rotation: -10 }, + { id: 'slot-l-1', side: 'left', top: 50, rotation: -4 }, + { id: 'slot-l-2', side: 'left', top: 82, rotation: -12 }, + { id: 'slot-r-0', side: 'right', top: 18, rotation: 8 }, + { id: 'slot-r-1', side: 'right', top: 52, rotation: -12 }, + { id: 'slot-r-2', side: 'right', top: 80, rotation: 6 } +]; + +export function getSlotAssignments(sectionData: Record): Record { + return (sectionData.slotAssignments as Record) ?? {}; +} + +export function getSlotItem( + slot: DecorationSlot, + assignments: Record, + items: Item[] +): Item | undefined { + const itemId = assignments[slot.id]; + if (!itemId) return undefined; + return items.find((i) => i.id === itemId); +} + +export function canFitInSlot(cardDef: { minW?: number; minH?: number }): boolean { + return (cardDef.minW ?? 2) <= 2 && (cardDef.minH ?? 2) <= 2; +} + +export function defaultHeroSectionData(): Record { + return { + title: 'My cool website', + subtitle: 'A little something about me, what I do, and why you should stick around.', + badge: 'Welcome', + buttonText: 'Say hi', + buttonHref: '', + showBadge: true, + showSubtitle: true, + showButton: true, + textAlign: 'center', + verticalAlign: 'center', + slotAssignments: {} + }; +} + +export const heroAlignClasses: Record = { + left: 'text-left items-start', + center: 'text-center items-center', + right: 'text-right items-end' +}; + +export const heroVerticalAlignClasses: Record = { + top: 'justify-start', + center: 'justify-center', + bottom: 'justify-end' +}; + +export const HeroSectionDefinition: SectionDefinition = { + type: 'hero', + contentComponent: HeroSection, + editingContentComponent: EditingHeroSection, + defaultSectionData: defaultHeroSectionData, + cardFilter: canFitInSlot, + addItem: (item: Item, allItems: Item[], options) => { + item.w = 2; + item.h = 2; + item.mobileW = 2; + item.mobileH = 2; + if (options.extraData?.slotId) { + item.cardData._slotId = options.extraData.slotId; + } + return [...allItems, item]; + }, + deleteItem: (itemId: string, allItems: Item[]) => { + return allItems.filter((i) => i.id !== itemId); + }, + resizeItem: () => {}, + name: 'Hero', + icon: `` +}; diff --git a/src/lib/sections/index.ts b/src/lib/sections/index.ts index 0821e19..e2641d4 100644 --- a/src/lib/sections/index.ts +++ b/src/lib/sections/index.ts @@ -1,7 +1,8 @@ import type { SectionDefinition } from './types'; import { GridSectionDefinition } from './GridSection'; +import { HeroSectionDefinition } from './HeroSection'; -export const AllSectionDefinitions = [GridSectionDefinition] as const; +export const AllSectionDefinitions = [GridSectionDefinition, HeroSectionDefinition] as const; export const SectionDefinitionsByType = AllSectionDefinitions.reduce( (acc, def) => { diff --git a/src/lib/sections/types.ts b/src/lib/sections/types.ts index c4ee5bf..848b97f 100644 --- a/src/lib/sections/types.ts +++ b/src/lib/sections/types.ts @@ -1,5 +1,6 @@ import type { Component } from 'svelte'; import type { Item, SectionRecord } from '$lib/types'; +import type { CardDefinition } from '$lib/cards/types'; export type SectionContentProps = { section: SectionRecord; @@ -13,7 +14,7 @@ export type EditingSectionContentProps = SectionContentProps & { isActive: boolean; onlayoutchange: () => void; ondeselect: () => void; - onadditem: (item: Item) => void; + onrequestaddcard: (extraData?: Record) => void; oncreatefilecards: (files: File[]) => Promise; onactivate: () => void; onrefchange: (el: HTMLDivElement | undefined) => void; @@ -22,12 +23,15 @@ export type EditingSectionContentProps = SectionContentProps & { export type AddItemOptions = { gridRef?: HTMLDivElement; isMobile: boolean; + extraData?: Record; }; export type SectionDefinition = { type: string; contentComponent: Component; editingContentComponent: Component; + defaultSectionData?: () => Record; + cardFilter?: (cardDef: CardDefinition) => boolean; addItem: (item: Item, allItems: Item[], options: AddItemOptions) => Item[]; deleteItem: (itemId: string, allItems: Item[], sectionId: string) => Item[]; resizeItem: (item: Item, allItems: Item[], w: number, h: number, isMobile: boolean) => void; diff --git a/src/lib/website/EditBar.svelte b/src/lib/website/EditBar.svelte index d7161ed..df002a4 100644 --- a/src/lib/website/EditBar.svelte +++ b/src/lib/website/EditBar.svelte @@ -58,7 +58,7 @@ } = $props(); let linkPopoverOpen = $state(false); - let imageInputRef: HTMLInputElement | undefined = $state(); + let fileInputRef: HTMLInputElement | undefined = $state(); function getShareUrl() { const base = typeof window !== 'undefined' ? window.location.origin : ''; @@ -130,7 +130,7 @@ return w >= minW && w <= maxW && h >= minH && h <= maxH; } - const showMobileEditControls = $derived(isCoarse && selectedCard); + const showEditControls = $derived(!!selectedCard); {#if dev || (user.isLoggedIn && user.profile?.did === data.did)} - {#if showMobileEditControls} + {#if showEditControls}
{#if cardDef?.allowSetColor !== false} @@ -342,7 +342,7 @@
{/if} -
+