your personal website on atproto - mirror blento.app

allow changing cards

Florian 10068f47 604ac7af

+140 -40
+103 -38
src/lib/cards/BaseCard/BaseEditingCard.svelte
··· 3 import type { HTMLAttributes } from 'svelte/elements'; 4 import BaseCard from './BaseCard.svelte'; 5 import type { Item } from '$lib/types'; 6 - import { Button, Popover } from '@foxui/core'; 7 import { ColorSelect } from '@foxui/colors'; 8 - import { CardDefinitionsByType, getColor } from '..'; 9 import { COLUMNS } from '$lib'; 10 import { getCanEdit, getIsMobile } from '$lib/website/context'; 11 ··· 151 } 152 153 let settingsPopoverOpen = $state(false); 154 </script> 155 156 <BaseCard ··· 166 {#snippet controls()} 167 <!-- class="bg-base-100 border-base-200 dark:bg-base-800 dark:border-base-700 absolute -top-3 -left-3 hidden cursor-pointer items-center justify-center rounded-full border p-2 shadow-lg group-focus-within:inline-flex group-hover:inline-flex" --> 168 {#if canEdit()} 169 <Button 170 size="icon" 171 variant="rose" ··· 200 <div 201 class="bg-base-100 border-base-200 dark:bg-base-800 dark:border-base-700 z-[100] inline-flex items-center gap-0.5 rounded-2xl border p-1 px-2 shadow-lg" 202 > 203 - <Popover bind:open={colorPopoverOpen}> 204 - {#snippet child({ props })} 205 - <button 206 - {...props} 207 - class={[ 208 - 'm-2 size-4 cursor-pointer rounded-full', 209 - !item.color || item.color === 'base' || item.color === 'transparent' 210 - ? 'text-base-800 dark:text-base-200' 211 - : 'text-accent-500' 212 - ]} 213 - > 214 - <svg 215 - xmlns="http://www.w3.org/2000/svg" 216 - viewBox="0 0 24 24" 217 - fill="currentColor" 218 - class="size-4" 219 > 220 - <path 221 - fill-rule="evenodd" 222 - d="M20.599 1.5c-.376 0-.743.111-1.055.32l-5.08 3.385a18.747 18.747 0 0 0-3.471 2.987 10.04 10.04 0 0 1 4.815 4.815 18.748 18.748 0 0 0 2.987-3.472l3.386-5.079A1.902 1.902 0 0 0 20.599 1.5Zm-8.3 14.025a18.76 18.76 0 0 0 1.896-1.207 8.026 8.026 0 0 0-4.513-4.513A18.75 18.75 0 0 0 8.475 11.7l-.278.5a5.26 5.26 0 0 1 3.601 3.602l.502-.278ZM6.75 13.5A3.75 3.75 0 0 0 3 17.25a1.5 1.5 0 0 1-1.601 1.497.75.75 0 0 0-.7 1.123 5.25 5.25 0 0 0 9.8-2.62 3.75 3.75 0 0 0-3.75-3.75Z" 223 - clip-rule="evenodd" 224 - /> 225 - </svg> 226 - </button> 227 - {/snippet} 228 - <ColorSelect 229 - selected={selectedColor} 230 - colors={colorsChoices} 231 - onselected={(color, previous) => { 232 - if (typeof previous === 'string' || typeof color === 'string') { 233 - return; 234 - } 235 236 - item.color = color.label; 237 - }} 238 - class="w-64" 239 - /> 240 - </Popover> 241 242 {#if canSetSize(2, 2)} 243 <button
··· 3 import type { HTMLAttributes } from 'svelte/elements'; 4 import BaseCard from './BaseCard.svelte'; 5 import type { Item } from '$lib/types'; 6 + import { Button, Label, Popover } from '@foxui/core'; 7 import { ColorSelect } from '@foxui/colors'; 8 + import { AllCardDefinitions, CardDefinitionsByType, getColor } from '..'; 9 import { COLUMNS } from '$lib'; 10 import { getCanEdit, getIsMobile } from '$lib/website/context'; 11 ··· 151 } 152 153 let settingsPopoverOpen = $state(false); 154 + let changePopoverOpen = $state(false); 155 + 156 + const changeOptions = $derived( 157 + AllCardDefinitions.filter((def) => def.type !== item.cardType && def.canChange?.(item)) 158 + ); 159 + 160 + function applyChange(def: (typeof AllCardDefinitions)[number]) { 161 + const updated = def.change ? def.change(item) : item; 162 + if (updated !== item) { 163 + item = updated; 164 + } 165 + item.cardType = def.type; 166 + changePopoverOpen = false; 167 + } 168 + 169 + function getChangeLabel(def: (typeof AllCardDefinitions)[number]) { 170 + return def.name; 171 + } 172 </script> 173 174 <BaseCard ··· 184 {#snippet controls()} 185 <!-- class="bg-base-100 border-base-200 dark:bg-base-800 dark:border-base-700 absolute -top-3 -left-3 hidden cursor-pointer items-center justify-center rounded-full border p-2 shadow-lg group-focus-within:inline-flex group-hover:inline-flex" --> 186 {#if canEdit()} 187 + {#if changeOptions.length > 0} 188 + <div 189 + class={[ 190 + 'absolute -top-3 -right-3 hidden group-focus-within:inline-flex group-hover:inline-flex', 191 + changePopoverOpen ? 'inline-flex' : '' 192 + ]} 193 + > 194 + <Popover bind:open={changePopoverOpen} class="bg-base-50 dark:bg-base-900"> 195 + {#snippet child({ props })} 196 + <Button size="icon" variant="secondary" {...props}> 197 + <svg 198 + xmlns="http://www.w3.org/2000/svg" 199 + fill="none" 200 + viewBox="0 0 24 24" 201 + stroke-width="1.5" 202 + stroke="currentColor" 203 + class="size-6" 204 + > 205 + <path 206 + stroke-linecap="round" 207 + stroke-linejoin="round" 208 + d="M7.5 21 3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" 209 + /> 210 + </svg> 211 + 212 + <span class="sr-only">Change card type</span> 213 + </Button> 214 + {/snippet} 215 + 216 + <div class="flex min-w-36 flex-col gap-1"> 217 + <Label class="mb-2">Change card to</Label> 218 + {#each changeOptions as changeDef} 219 + <Button 220 + class="justify-start" 221 + variant="ghost" 222 + onclick={() => applyChange(changeDef)} 223 + > 224 + {getChangeLabel(changeDef)} 225 + </Button> 226 + {/each} 227 + </div> 228 + </Popover> 229 + </div> 230 + {/if} 231 + 232 <Button 233 size="icon" 234 variant="rose" ··· 263 <div 264 class="bg-base-100 border-base-200 dark:bg-base-800 dark:border-base-700 z-[100] inline-flex items-center gap-0.5 rounded-2xl border p-1 px-2 shadow-lg" 265 > 266 + {#if cardDef.allowSetColor !== false} 267 + <Popover bind:open={colorPopoverOpen}> 268 + {#snippet child({ props })} 269 + <button 270 + {...props} 271 + class={[ 272 + 'm-2 size-4 cursor-pointer rounded-full', 273 + !item.color || item.color === 'base' || item.color === 'transparent' 274 + ? 'text-base-800 dark:text-base-200' 275 + : 'text-accent-500' 276 + ]} 277 > 278 + <svg 279 + xmlns="http://www.w3.org/2000/svg" 280 + viewBox="0 0 24 24" 281 + fill="currentColor" 282 + class="size-4" 283 + > 284 + <path 285 + fill-rule="evenodd" 286 + d="M20.599 1.5c-.376 0-.743.111-1.055.32l-5.08 3.385a18.747 18.747 0 0 0-3.471 2.987 10.04 10.04 0 0 1 4.815 4.815 18.748 18.748 0 0 0 2.987-3.472l3.386-5.079A1.902 1.902 0 0 0 20.599 1.5Zm-8.3 14.025a18.76 18.76 0 0 0 1.896-1.207 8.026 8.026 0 0 0-4.513-4.513A18.75 18.75 0 0 0 8.475 11.7l-.278.5a5.26 5.26 0 0 1 3.601 3.602l.502-.278ZM6.75 13.5A3.75 3.75 0 0 0 3 17.25a1.5 1.5 0 0 1-1.601 1.497.75.75 0 0 0-.7 1.123 5.25 5.25 0 0 0 9.8-2.62 3.75 3.75 0 0 0-3.75-3.75Z" 287 + clip-rule="evenodd" 288 + /> 289 + </svg> 290 + </button> 291 + {/snippet} 292 + <ColorSelect 293 + selected={selectedColor} 294 + colors={colorsChoices} 295 + onselected={(color, previous) => { 296 + if (typeof previous === 'string' || typeof color === 'string') { 297 + return; 298 + } 299 300 + item.color = color.label; 301 + }} 302 + class="w-64" 303 + /> 304 + </Popover> 305 + {/if} 306 307 {#if canSetSize(2, 2)} 308 <button
+17
src/lib/cards/BigSocialCard/index.ts
··· 19 card.mobileW = 4; 20 card.mobileH = 4; 21 }, 22 allowSetColor: false, 23 defaultColor: 'transparent', 24 minW: 2,
··· 19 card.mobileW = 4; 20 card.mobileH = 4; 21 }, 22 + canChange: (item) => { 23 + const href = item.cardData?.href; 24 + if (!href) return false; 25 + return Boolean(detectPlatform(href)); 26 + }, 27 + change: (item) => { 28 + const href = item.cardData?.href; 29 + const platform = href ? detectPlatform(href) : null; 30 + if (!href || !platform) return item; 31 + item.cardData = { 32 + href, 33 + platform, 34 + color: platformsData[platform].hex 35 + }; 36 + return item; 37 + }, 38 + name: 'Social Icon', 39 allowSetColor: false, 40 defaultColor: 'transparent', 41 minW: 2,
+14 -1
src/lib/cards/LinkCard/index.ts
··· 1 import type { CardDefinition } from '../types'; 2 import EditingLinkCard from './EditingLinkCard.svelte'; 3 import LinkCard from './LinkCard.svelte'; ··· 11 card.cardType = 'link'; 12 }, 13 settingsComponent: LinkCardSettings, 14 onUrlHandler: (url, item) => { 15 item.cardData.href = url; 16 item.cardData.domain = new URL(url).hostname; 17 - item.cardData.hasFetched = false; 18 return item; 19 }, 20 urlHandlerPriority: 0
··· 1 + import { validateLink } from '$lib/helper'; 2 import type { CardDefinition } from '../types'; 3 import EditingLinkCard from './EditingLinkCard.svelte'; 4 import LinkCard from './LinkCard.svelte'; ··· 12 card.cardType = 'link'; 13 }, 14 settingsComponent: LinkCardSettings, 15 + 16 + name: 'Link Card', 17 + canChange: (item) => Boolean(validateLink(item.cardData?.href)), 18 + change: (item) => { 19 + const href = validateLink(item.cardData?.href); 20 + if (!href) return item; 21 + 22 + item.cardData = { 23 + href, 24 + hasFetched: false 25 + }; 26 + return item; 27 + }, 28 onUrlHandler: (url, item) => { 29 item.cardData.href = url; 30 item.cardData.domain = new URL(url).hostname; 31 return item; 32 }, 33 urlHandlerPriority: 0
+6 -1
src/lib/cards/types.ts
··· 62 63 onUrlHandler?: (url: string, item: Item) => Item | null; 64 urlHandlerPriority?: number; 65 - };
··· 62 63 onUrlHandler?: (url: string, item: Item) => Item | null; 64 urlHandlerPriority?: number; 65 + 66 + canChange?: (item: Item) => boolean; 67 + change?: (item: Item) => Item; 68 + 69 + name?: string; 70 + };