your personal website on atproto - mirror
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

move to 8 column layout

Florian a61a1edd 0eb04ab3

+278 -124
+38 -14
src/lib/EditableWebsite.svelte
··· 4 4 import { Navbar, Button, toast, Toaster, Toggle, Sidebar } from '@foxui/core'; 5 5 import { BlueskyLogin } from '@foxui/social'; 6 6 7 - import { margin, mobileMargin } from '$lib'; 7 + import { COLUMNS, margin, mobileMargin } from '$lib'; 8 8 import { 9 9 cardsEqual, 10 10 clamp, ··· 92 92 id: TID.nextStr(), 93 93 x: 0, 94 94 y: 0, 95 - w: 1, 96 - h: 1, 97 - mobileH: 2, 98 - mobileW: 2, 95 + w: 2, 96 + h: 2, 97 + mobileH: 4, 98 + mobileW: 4, 99 99 mobileX: 0, 100 100 mobileY: 0, 101 101 cardType: type, ··· 133 133 const currentY = isMobile ? item.mobileY : item.y; 134 134 const bodyRect = document.body.getBoundingClientRect(); 135 135 const offset = containerRect.top - bodyRect.top; 136 - const cellSize = (containerRect.width - currentMargin * 2) / 4; 136 + const cellSize = (containerRect.width - currentMargin * 2) / COLUMNS; 137 137 window.scrollTo({ top: offset + cellSize * (currentY - 1), behavior: 'smooth' }); 138 138 } 139 139 ··· 209 209 console.log(rect.top); 210 210 211 211 let gridX = clamp( 212 - Math.floor(((x - rect.left) / rect.width) * 4), 212 + Math.floor(((x - rect.left) / rect.width) * 8), 213 213 0, 214 - 4 - (activeDragElement.w ?? 0) 214 + COLUMNS - (activeDragElement.w ?? 0) 215 + ); 216 + gridX = Math.floor(gridX / 2) * 2; 217 + let gridY = Math.max( 218 + Math.round(((y - rect.top + margin) / (rect.width - margin)) * COLUMNS), 219 + 0 215 220 ); 216 - let gridY = Math.max(Math.round(((y - rect.top + margin) / (rect.width - margin)) * 4), 0); 217 221 if (isMobile) { 218 222 gridX = Math.floor(gridX / 2) * 2; 219 223 gridY = Math.floor(gridY / 2) * 2; ··· 274 278 activeDragElement.x = cell.x; 275 279 activeDragElement.y = cell.y; 276 280 281 + if (activeDragElement.item) { 282 + if (isMobile) { 283 + activeDragElement.item.mobileX = cell.x; 284 + activeDragElement.item.mobileY = cell.y; 285 + } else { 286 + activeDragElement.item.x = cell.x; 287 + activeDragElement.item.y = cell.y; 288 + } 289 + 290 + fixCollisions(items, activeDragElement.item, isMobile); 291 + } 292 + 277 293 // Auto-scroll when dragging near top or bottom of viewport 278 294 const scrollZone = 150; 279 295 const scrollSpeed = 15; ··· 313 329 class="@container/grid relative col-span-3 px-2 py-8 @5xl/wrapper:px-8 @7xl/wrapper:col-span-2" 314 330 > 315 331 {#each items as item, i (item.id)} 332 + <!-- {#if item !== activeDragElement.item} --> 316 333 <BaseEditingCard 317 334 bind:item={items[i]} 318 335 ondelete={() => { ··· 346 363 > 347 364 <EditingCard bind:item={items[i]} /> 348 365 </BaseEditingCard> 366 + <!-- {/if} --> 349 367 {/each} 350 368 351 369 {#if activeDragElement.element && activeDragElement.x >= 0 && activeDragElement.item} 352 - {@const finalPos = simulateFinalPosition(items, activeDragElement.item, activeDragElement.x, activeDragElement.y, isMobile)} 370 + {@const finalPos = simulateFinalPosition( 371 + items, 372 + activeDragElement.item, 373 + activeDragElement.x, 374 + activeDragElement.y, 375 + isMobile 376 + )} 353 377 <div 354 378 class={[ 355 379 'bg-base-500/10 absolute aspect-square rounded-2xl transition-transform duration-100' 356 380 ]} 357 - style={`translate: calc(${(finalPos.x / 4) * 100}cqw + ${margin}px) calc(${(finalPos.y / 4) * 100}cqw + ${margin}px); 381 + style={`translate: calc(${(finalPos.x / COLUMNS) * 100}cqw + ${margin}px) calc(${(finalPos.y / COLUMNS) * 100}cqw + ${margin}px); 358 382 359 - width: calc(${(getW(activeDragElement.item) / 4) * 100}cqw - ${margin * 2}px); 360 - height: calc(${(getH(activeDragElement.item) / 4) * 100}cqw - ${margin * 2}px);`} 383 + width: calc(${(getW(activeDragElement.item) / COLUMNS) * 100}cqw - ${margin * 2}px); 384 + height: calc(${(getH(activeDragElement.item) / COLUMNS) * 100}cqw - ${margin * 2}px);`} 361 385 ></div> 362 386 {/if} 363 387 ··· 367 391 style={`translate: ${debugPoint.x}px ${debugPoint.y}px;`} 368 392 ></div> 369 393 {/if} 370 - <div style="height: {((maxHeight + 1) / 4) * 100}cqw;"></div> 394 + <div style="height: {((maxHeight + 2) / 8) * 100}cqw;"></div> 371 395 </div> 372 396 </div> 373 397 </div>
+1 -1
src/lib/Website.svelte
··· 50 50 <Card {item} /> 51 51 </BaseCard> 52 52 {/each} 53 - <div style="height: {(maxHeight / 4) * 100}cqw;"></div> 53 + <div style="height: {(maxHeight / 8) * 100}cqw;"></div> 54 54 </div> 55 55 </div> 56 56
+2 -2
src/lib/cards/ATProtoCollectionsCard/index.ts
··· 17 17 return Array.from(collections); 18 18 }, 19 19 createNew: (item) => { 20 - item.w = 2; 21 - item.mobileW = 4; 20 + item.w = 4; 21 + item.mobileW = 8; 22 22 }, 23 23 sidebarComponent: SidebarItemATProtoCollectionsCard 24 24 } as CardDefinition & { type: 'atprotocollections' };
+28 -23
src/lib/cards/BaseCard/BaseCard.svelte
··· 1 1 <script lang="ts"> 2 - import { margin, mobileMargin } from '$lib'; 2 + import { COLUMNS, margin, mobileMargin } from '$lib'; 3 3 import type { Item } from '$lib/types'; 4 4 import type { WithElementRef } from 'bits-ui'; 5 5 import type { Snippet } from 'svelte'; 6 6 import type { HTMLAttributes } from 'svelte/elements'; 7 + import { getColor } from '..'; 7 8 8 9 const colors = { 9 10 base: 'border-base-200 shadow-lg dark:shadow-none inset-shadow-sm inset-shadow-base-500/10 shadow-base-900/5 bg-base-50 dark:border-base-800 dark:bg-base-900 border', 10 - accent: 'border-accent-200 shadow-lg inset-shadow-sm inset-shadow-accent-500/10 shadow-accent-900/10 bg-accent-50 dark:border-accent-900/50 dark:bg-accent-950/20 border', 11 + accent: 12 + 'border-accent-200 shadow-lg inset-shadow-sm inset-shadow-accent-500/10 shadow-accent-900/10 bg-accent-50 dark:border-accent-900/50 dark:bg-accent-950/20 border', 11 13 transparent: '' 12 14 } as Record<string, string>; 13 15 ··· 25 27 controls, 26 28 ...rest 27 29 }: BaseCardProps = $props(); 30 + 31 + let color = $derived(getColor(item)); 28 32 </script> 29 33 30 34 <div ··· 33 37 bind:this={ref} 34 38 draggable={isEditing} 35 39 class={[ 36 - 'card group transition-transform duration-200 focus-within:outline-accent-500 @container/card absolute z-0 rounded-2xl outline-offset-2 focus-within:outline-2', 37 - item.color ? (colors[item.color] ?? colors.accent) : colors.base, 38 - item.color !== 'accent' && item.color !== 'base' && item.color !== 'transparent' 39 - ? item.color 40 - : '' 40 + 'card group focus-within:outline-accent-500 @container/card absolute z-0 rounded-2xl outline-offset-2 transition-transform duration-200 focus-within:outline-2', 41 + color ? (colors[color] ?? colors.accent) : colors.base, 42 + color !== 'accent' && item.color !== 'base' && item.color !== 'transparent' ? color : '' 41 43 ]} 42 44 style={` 43 - --mx: ${item.mobileX * 2}; 44 - --my: ${item.mobileY * 2}; 45 - --mw: ${item.mobileW * 2}; 46 - --mh: ${item.mobileH * 2}; 45 + --mx: ${item.mobileX}; 46 + --my: ${item.mobileY}; 47 + --mw: ${item.mobileW}; 48 + --mh: ${item.mobileH}; 47 49 --mm: ${mobileMargin}px; 48 50 49 - --dx: ${item.x * 2}; 50 - --dy: ${item.y * 2}; 51 - --dw: ${item.w * 2}; 52 - --dh: ${item.h * 2}; 53 - --dm: ${margin}px;`} 51 + --dx: ${item.x}; 52 + --dy: ${item.y}; 53 + --dw: ${item.w}; 54 + --dh: ${item.h}; 55 + --dm: ${margin}px; 56 + 57 + --columns: ${COLUMNS}`} 54 58 {...rest} 55 59 > 56 60 <div class="relative h-full w-full overflow-hidden rounded-[15px]"> ··· 61 65 62 66 <style> 63 67 .card { 64 - translate: calc((var(--mx) / 8) * 100cqw + var(--mm)) calc((var(--my) / 8) * 100cqw + var(--mm)); 65 - width: calc((var(--mw) / 8) * 100cqw - (var(--mm) * 2)); 66 - height: calc((var(--mh) / 8) * 100cqw - (var(--mm) * 2)); 68 + translate: calc((var(--mx) / var(--columns)) * 100cqw + var(--mm)) 69 + calc((var(--my) / var(--columns)) * 100cqw + var(--mm)); 70 + width: calc((var(--mw) / var(--columns)) * 100cqw - (var(--mm) * 2)); 71 + height: calc((var(--mh) / var(--columns)) * 100cqw - (var(--mm) * 2)); 67 72 } 68 73 69 74 @container grid (width >= 42rem) { 70 75 .card { 71 - translate: calc((var(--dx) / 8) * 100cqw + var(--dm)) 72 - calc((var(--dy) / 8) * 100cqw + var(--dm)); 73 - width: calc((var(--dw) / 8) * 100cqw - (var(--dm) * 2)); 74 - height: calc((var(--dh) / 8) * 100cqw - (var(--dm) * 2)); 76 + translate: calc((var(--dx) / var(--columns)) * 100cqw + var(--dm)) 77 + calc((var(--dy) / var(--columns)) * 100cqw + var(--dm)); 78 + width: calc((var(--dw) / var(--columns)) * 100cqw - (var(--dm) * 2)); 79 + height: calc((var(--dh) / var(--columns)) * 100cqw - (var(--dm) * 2)); 75 80 } 76 81 } 77 82 </style>
+62 -40
src/lib/cards/BaseCard/BaseEditingCard.svelte
··· 4 4 import BaseCard from './BaseCard.svelte'; 5 5 import type { Item } from '$lib/types'; 6 6 import { Button, Popover } from '@foxui/core'; 7 - import { getCanEdit } from '$lib/helper'; 7 + import { getCanEdit, getIsMobile } from '$lib/helper'; 8 8 import { ColorSelect } from '@foxui/colors'; 9 + import { CardDefinitionsByType, getColor } from '..'; 10 + import { COLUMNS } from '$lib'; 9 11 10 12 let colorsChoices = [ 11 13 { class: 'text-base-500', label: 'base' }, ··· 47 49 ...rest 48 50 }: BaseEditingCardProps = $props(); 49 51 50 - let selectedColor = $derived( 51 - !item.color ? colorsChoices[0] : colorsChoices.find((c) => item.color === c.label) 52 - ); 52 + let selectedColor = $derived(colorsChoices.find((c) => getColor(item) === c.label)); 53 53 54 54 let canEdit = getCanEdit(); 55 55 56 56 let colorPopoverOpen = $state(false); 57 + 58 + const cardDef = $derived(CardDefinitionsByType[item.cardType]); 59 + 60 + const minW = $derived(cardDef.minW ?? 0); 61 + const minH = $derived(cardDef.minH ?? 0); 62 + 63 + const maxW = $derived(cardDef.maxW ?? COLUMNS); 64 + const maxH = $derived(cardDef.maxH ?? COLUMNS); 65 + 66 + function canSetSize(w: number, h: number) { 67 + if (!cardDef) return false; 68 + 69 + return w >= minW && w <= maxW && h >= minH && h <= maxH; 70 + } 57 71 </script> 58 72 59 73 <BaseCard {item} {...rest} isEditing={true} bind:ref> ··· 135 149 /> 136 150 </Popover> 137 151 138 - <button 139 - onclick={() => { 140 - onsetsize?.(1, 1); 141 - }} 142 - class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2" 143 - > 144 - <div class="border-base-900 dark:border-base-50 size-3 rounded-sm border-2"></div> 152 + {#if canSetSize(2, 2)} 153 + <button 154 + onclick={() => { 155 + onsetsize?.(2, 2); 156 + }} 157 + class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2" 158 + > 159 + <div class="border-base-900 dark:border-base-50 size-3 rounded-sm border-2"></div> 145 160 146 - <span class="sr-only">set size to 1x1</span> 147 - </button> 161 + <span class="sr-only">set size to 1x1</span> 162 + </button> 163 + {/if} 148 164 149 - <button 150 - onclick={() => { 151 - onsetsize?.(2, 1); 152 - }} 153 - class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2" 154 - > 155 - <div class="border-base-900 dark:border-base-50 h-3 w-5 rounded-sm border-2"></div> 156 - <span class="sr-only">set size to 2x1</span> 157 - </button> 158 - <button 159 - onclick={() => { 160 - onsetsize?.(1, 2); 161 - }} 162 - class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2" 163 - > 164 - <div class="border-base-900 dark:border-base-50 h-5 w-3 rounded-sm border-2"></div> 165 + {#if canSetSize(4, 2)} 166 + <button 167 + onclick={() => { 168 + onsetsize?.(4, 2); 169 + }} 170 + class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2" 171 + > 172 + <div class="border-base-900 dark:border-base-50 h-3 w-5 rounded-sm border-2"></div> 173 + <span class="sr-only">set size to 2x1</span> 174 + </button> 175 + {/if} 176 + {#if canSetSize(2, 4)} 177 + <button 178 + onclick={() => { 179 + onsetsize?.(2, 4); 180 + }} 181 + class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2" 182 + > 183 + <div class="border-base-900 dark:border-base-50 h-5 w-3 rounded-sm border-2"></div> 165 184 166 - <span class="sr-only">set size to 1x2</span> 167 - </button> 168 - <button 169 - onclick={() => { 170 - onsetsize?.(2, 2); 171 - }} 172 - class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2" 173 - > 174 - <div class="border-base-900 dark:border-base-50 h-5 w-5 rounded-sm border-2"></div> 185 + <span class="sr-only">set size to 1x2</span> 186 + </button> 187 + {/if} 188 + {#if canSetSize(4, 4)} 189 + <button 190 + onclick={() => { 191 + onsetsize?.(4, 4); 192 + }} 193 + class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2" 194 + > 195 + <div class="border-base-900 dark:border-base-50 h-5 w-5 rounded-sm border-2"></div> 175 196 176 - <span class="sr-only">set size to 2x2</span> 177 - </button> 197 + <span class="sr-only">set size to 2x2</span> 198 + </button> 199 + {/if} 178 200 179 201 {#if onshowsettings} 180 202 <button
+6 -5
src/lib/cards/BlueskyPostCard/index.ts
··· 8 8 contentComponent: BlueskyPostCard, 9 9 createNew: (card) => { 10 10 card.cardType = 'latestPost'; 11 - card.w = 2; 12 - card.mobileW = 4; 13 - card.h = 2; 14 - card.mobileH = 4; 11 + card.w = 4; 12 + card.mobileW = 8; 13 + card.h = 4; 14 + card.mobileH = 8; 15 15 }, 16 16 sidebarComponent: SidebarItemBlueskyPostCard, 17 17 loadData: async (items, { did }) => { ··· 22 22 limit: 2 23 23 }); 24 24 return JSON.parse(JSON.stringify(authorFeed.data)); 25 - } 25 + }, 26 + minW: 4 26 27 } as CardDefinition & { type: 'latestPost' };
+4 -4
src/lib/cards/EmbedCard/index.ts
··· 9 9 creationModalComponent: CreateEmbedCardModal, 10 10 sidebarComponent: SidebarItemEmbedCard, 11 11 createNew: (card) => { 12 - card.w = 2; 13 - card.h = 2; 14 - card.mobileH = 4; 15 - card.mobileW = 4; 12 + card.w = 4; 13 + card.h = 4; 14 + card.mobileH = 8; 15 + card.mobileW = 8; 16 16 } 17 17 } as CardDefinition & { type: 'embed' };
+1 -1
src/lib/cards/LinkCard/index.ts
··· 10 10 createNew: (card) => { 11 11 card.cardType = 'link'; 12 12 card.cardData = { 13 - href: 'https://flo-bit.dev' 13 + href: 'https://' 14 14 }; 15 15 }, 16 16 creationModalComponent: CreateLinkCardModal
+8 -8
src/lib/cards/LivestreamCard/index.ts
··· 13 13 contentComponent: LivestreamCard, 14 14 sidebarComponent: SidebarItemLivestreamCard, 15 15 createNew: (card) => { 16 - card.w = 2; 17 - card.h = 1; 18 - card.mobileH = 2; 19 - card.mobileW = 4; 16 + card.w = 4; 17 + card.h = 4; 18 + card.mobileH = 8; 19 + card.mobileW = 8; 20 20 }, 21 21 loadData: async (items, { did }) => { 22 22 const records = await listRecords({ did, collection: 'place.stream.livestream', limit: 3 }); ··· 72 72 contentComponent: LivestreamEmbedCard, 73 73 sidebarComponent: SidebarItemEmbedLivestreamCard, 74 74 createNew: (card) => { 75 - card.w = 2; 76 - card.h = 1; 77 - card.mobileH = 2; 78 - card.mobileW = 4; 75 + card.w = 4; 76 + card.h = 2; 77 + card.mobileW = 8; 78 + card.mobileH = 4; 79 79 80 80 card.cardData = { 81 81 href: 'https://stream.place/embed/' + client.profile?.handle
+4 -4
src/lib/cards/MapCard/index.ts
··· 8 8 contentComponent: MapCard, 9 9 sidebarButtonText: 'map', 10 10 createNew: (item) => { 11 - item.w = 2; 12 - item.h = 2; 13 - item.mobileH = 4; 14 - item.mobileW = 4; 11 + item.w = 4; 12 + item.h = 4; 13 + item.mobileH = 8; 14 + item.mobileW = 8; 15 15 }, 16 16 17 17 sidebarComponent: SidebarItemMapCard,
+13
src/lib/cards/SectionCard/EditingSectionCard.svelte
··· 1 + <script lang="ts"> 2 + import type { Item } from '$lib/types'; 3 + import type { ContentComponentProps } from '../types'; 4 + import PlainTextEditor from '../utils/PlainTextEditor.svelte'; 5 + 6 + let { item = $bindable<Item>() }: ContentComponentProps = $props(); 7 + </script> 8 + 9 + <div 10 + class="line-clamp-1 inline-flex h-full w-full items-center rounded-md p-1 px-2 text-2xl font-semibold" 11 + > 12 + <PlainTextEditor bind:item key="text" class="line-clamp-1 w-full" /> 13 + </div>
+16
src/lib/cards/SectionCard/SectionCard.svelte
··· 1 + <script lang="ts"> 2 + import { marked } from 'marked'; 3 + import type { ContentComponentProps } from '../types'; 4 + 5 + let { item }: ContentComponentProps = $props(); 6 + 7 + const renderer = new marked.Renderer(); 8 + renderer.link = ({ href, title, text }) => 9 + `<a target="_blank" href="${href}" title="${title}">${text}</a>`; 10 + </script> 11 + 12 + <div 13 + class="line-clamp-1 inline-flex h-full w-full items-center rounded-md p-1 px-2 text-2xl font-semibold" 14 + > 15 + {item.cardData.text} 16 + </div>
+26
src/lib/cards/SectionCard/index.ts
··· 1 + import { COLUMNS } from '$lib'; 2 + import type { CardDefinition } from '../types'; 3 + import EditingSectionCard from './EditingSectionCard.svelte'; 4 + import SectionCard from './SectionCard.svelte'; 5 + 6 + export const SectionCardDefinition = { 7 + type: 'section', 8 + contentComponent: SectionCard, 9 + editingContentComponent: EditingSectionCard, 10 + createNew: (card) => { 11 + card.cardType = 'section'; 12 + card.cardData = { 13 + text: 'hello world' 14 + }; 15 + 16 + card.h = 1; 17 + card.mobileH = 2; 18 + 19 + card.w = COLUMNS; 20 + card.mobileW = COLUMNS; 21 + }, 22 + 23 + sidebarButtonText: 'section', 24 + defaultColor: 'transparent', 25 + maxH: 1 26 + } as CardDefinition & { type: 'section' };
+2 -2
src/lib/cards/YoutubeVideo/index.ts
··· 10 10 createNew: (card) => { 11 11 card.cardType = 'youtubeVideo'; 12 12 card.cardData = {}; 13 - card.w = 2; 14 - card.mobileW = 4; 13 + card.w = 4; 14 + card.mobileW = 8; 15 15 }, 16 16 sidebarComponent: SidebarItemYoutubeCard 17 17 } as CardDefinition & { type: 'youtubeVideo' };
+12 -1
src/lib/cards/index.ts
··· 1 + import type { Item } from '$lib/types'; 1 2 import { ATProtoCollectionsCardDefinition } from './ATProtoCollectionsCard'; 2 3 import { BlueskyPostCardDefinition } from './BlueskyPostCard'; 3 4 import { EmbedCardDefinition } from './EmbedCard'; ··· 5 6 import { LinkCardDefinition } from './LinkCard'; 6 7 import { LivestreamCardDefitition, LivestreamEmbedCardDefitition } from './LivestreamCard'; 7 8 import { MapCardDefinition } from './MapCard'; 9 + import { SectionCardDefinition } from './SectionCard'; 8 10 import { UpdatedBlentosCardDefitition } from './SpecialCards/UpdatedBlentos'; 9 11 import { TextCardDefinition } from './TextCard'; 10 12 import type { CardDefinition } from './types'; ··· 21 23 LivestreamEmbedCardDefitition, 22 24 EmbedCardDefinition, 23 25 MapCardDefinition, 24 - ATProtoCollectionsCardDefinition 26 + ATProtoCollectionsCardDefinition, 27 + SectionCardDefinition 25 28 ] as const; 26 29 27 30 export const CardDefinitionsByType = AllCardDefinitions.reduce( ··· 31 34 }, 32 35 {} as Record<string, CardDefinition> 33 36 ); 37 + 38 + export function getColor(item: Item): string { 39 + if (item.color) return item.color; 40 + 41 + const cardDefColor = CardDefinitionsByType[item.cardType]?.defaultColor; 42 + 43 + return cardDefColor || 'base'; 44 + }
+10
src/lib/cards/types.ts
··· 42 42 { did, handle, platform }: { did: string; handle: string; platform?: App.Platform } 43 43 ) => Promise<unknown>; 44 44 dataKey?: string; 45 + 46 + allowSetColor?: boolean; 47 + 48 + defaultColor?: string; 49 + 50 + minW?: number; 51 + maxW?: number; 52 + 53 + minH?: number; 54 + maxH?: number; 45 55 };
+9 -10
src/lib/helper.ts
··· 1 1 import { createContext } from 'svelte'; 2 2 import type { Item } from './types'; 3 + import { COLUMNS } from '$lib'; 3 4 4 5 export function clamp(value: number, min: number, max: number): number { 5 6 return Math.min(Math.max(value, min), max); ··· 39 40 }; 40 41 41 42 export function fixCollisions(items: Item[], movedItem: Item, mobile: boolean = false) { 42 - const COLS = 4; 43 - 44 43 const clampX = (item: Item) => { 45 - if (mobile) item.mobileX = clamp(item.mobileX, 0, COLS - item.mobileW); 46 - else item.x = clamp(item.x, 0, COLS - item.w); 44 + if (mobile) item.mobileX = clamp(item.mobileX, 0, COLUMNS - item.mobileW); 45 + else item.x = clamp(item.x, 0, COLUMNS - item.w); 47 46 }; 48 47 49 48 // Push `target` down until it no longer overlaps with any item (including movedItem), ··· 91 90 pushDownCascade(it, movedItem); 92 91 93 92 // enforce "x stays the same" during pushing (clamp already applied) 94 - if (mobile) it.mobileX = clamp(it.mobileX, 0, COLS - it.mobileW); 95 - else it.x = clamp(it.x, 0, COLS - it.w); 93 + if (mobile) it.mobileX = clamp(it.mobileX, 0, COLUMNS - it.mobileW); 94 + else it.x = clamp(it.x, 0, COLUMNS - it.w); 96 95 } 97 96 98 97 compactItems(items, mobile); ··· 168 167 } 169 168 170 169 export function sortItems(a: Item, b: Item) { 171 - return a.y * 4 + a.x - b.y * 4 - b.x; 170 + return a.y * COLUMNS + a.x - b.y * COLUMNS - b.x; 172 171 } 173 172 174 173 export function cardsEqual(a: Item, b: Item) { ··· 191 190 export function setPositionOfNewItem(newItem: Item, items: Item[]) { 192 191 let foundPosition = false; 193 192 while (!foundPosition) { 194 - for (newItem.x = 0; newItem.x <= 4 - newItem.w; newItem.x++) { 193 + for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x++) { 195 194 const collision = items.find((item) => overlaps(newItem, item)); 196 195 if (!collision) { 197 196 foundPosition = true; ··· 203 202 204 203 let foundMobilePosition = false; 205 204 while (!foundMobilePosition) { 206 - for (newItem.mobileX = 0; newItem.mobileX <= 4 - newItem.mobileW; newItem.mobileX += 1) { 205 + for (newItem.mobileX = 0; newItem.mobileX <= COLUMNS - newItem.mobileW; newItem.mobileX += 1) { 207 206 const collision = items.find((item) => overlaps(newItem, item, true)); 208 207 209 208 if (!collision) { ··· 211 210 break; 212 211 } 213 212 } 214 - if (!foundMobilePosition) newItem.mobileY! += 2; 213 + if (!foundMobilePosition) newItem.mobileY! += 1; 215 214 } 216 215 } 217 216
+3 -1
src/lib/index.ts
··· 1 1 // place files you want to import through the `$lib` alias in this folder. 2 2 export const margin = 20; 3 - export const mobileMargin = 12; 3 + export const mobileMargin = 12; 4 + 5 + export const COLUMNS = 8;
+2
src/lib/types.ts
··· 19 19 cardData: any; 20 20 21 21 updatedAt?: string; 22 + 23 + version?: number; 22 24 };
+31 -8
src/lib/website/load.ts
··· 10 10 import { CardDefinitionsByType } from '$lib/cards'; 11 11 import type { Item } from '$lib/types'; 12 12 13 - export async function loadData( 14 - handle: string, 15 - platform?: App.Platform, 16 - forceUpdate: boolean = false 17 - ): Promise<{ 13 + type LoadedData = { 18 14 did: string; 19 15 data: DownloadedData; 20 16 additionalData: Record<string, unknown>; 21 17 updatedAt: number; 22 - }> { 18 + }; 19 + 20 + export async function loadData( 21 + handle: string, 22 + platform?: App.Platform, 23 + forceUpdate: boolean = false 24 + ): Promise<LoadedData> { 23 25 console.log(handle); 24 26 if (!forceUpdate) { 25 27 try { ··· 36 38 timePassed, 37 39 'seconds ago' 38 40 ); 39 - return JSON.parse(cachedResult); 41 + return migrateData(JSON.parse(cachedResult)); 40 42 } 41 43 } catch (error) { 42 44 console.log('getting cached result failed', error); ··· 144 146 145 147 await platform?.env?.USER_DATA_CACHE?.put(handle, JSON.stringify(result)); 146 148 147 - return result; 149 + return migrateData(result); 150 + } 151 + 152 + function migrateFromV0ToV1(data: LoadedData): LoadedData { 153 + for (const card of Object.values(data.data['app.blento.card']).map((i) => i.value) as Item[]) { 154 + if (card.version) continue; 155 + card.x *= 2; 156 + card.y *= 2; 157 + card.h *= 2; 158 + card.w *= 2; 159 + card.mobileX *= 2; 160 + card.mobileY *= 2; 161 + card.mobileH *= 2; 162 + card.mobileW *= 2; 163 + card.version = 1; 164 + } 165 + 166 + return data; 167 + } 168 + 169 + function migrateData(data: LoadedData): LoadedData { 170 + return migrateFromV0ToV1(data); 148 171 }