Enforce consistent formatting in webapp source

This commit is contained in:
daylily
2026-01-14 09:30:12 -04:00
parent 130cd5298a
commit 60b436b52b
39 changed files with 719 additions and 496 deletions
+10 -2
View File
@@ -6,7 +6,15 @@
"singleQuote": true,
"printWidth": 100,
"plugins": [
"prettier-plugin-svelte"
"prettier-plugin-svelte",
"prettier-plugin-tailwindcss",
"@ianvs/prettier-plugin-sort-imports"
],
"svelteIndentScriptAndStyle": false
"svelteIndentScriptAndStyle": false,
"tailwindStylesheet": "./src/app.css",
"tailwindFunctions": ["clsx", "twMerge", "cn"],
"importOrder": ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "^~icons/", "^$lib/", "^[./]"],
"importOrderParserPlugins": ["typescript"],
"importOrderTypeScriptVersion": "5.9.2",
"importOrderCaseSensitive": true
}
+9 -3
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@@ -7,12 +7,18 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="author" content="daylily" />
<meta name="generator" content="Vite & Svelte 5" />
<meta name="description" content="Utility for writing images onto the “Inkclip” e-paper accessory." />
<meta
name="description"
content="Utility for writing images onto the “Inkclip” e-paper accessory."
/>
<meta property="og:title" content="Write to Inkclip" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://inkclip.dayli.ly/" />
<meta property="og:description" content="Utility for writing images onto the “Inkclip” e-paper accessory." />
<meta
property="og:description"
content="Utility for writing images onto the “Inkclip” e-paper accessory."
/>
<meta property="og:site_name" content="dayli.ly" />
<title>Write to Inkclip</title>
+8 -6
View File
@@ -10,13 +10,14 @@
},
"devDependencies": {
"@fontsource-variable/ibm-plex-sans": "^5.2.8",
"@iconify/json": "^2.2.427",
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
"@iconify/json": "^2.2.428",
"@internationalized/date": "^3.10.1",
"@lucide/svelte": "^0.561.0",
"@lucide/svelte": "^0.562.0",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.1.18",
"@tsconfig/svelte": "^5.0.6",
"@types/node": "^22.19.5",
"@types/node": "^24.10.8",
"@types/w3c-web-hid": "^1.0.6",
"@variegated-coffee/serde-postcard-ts": "^0.1.4",
"autoprefixer": "^10.4.23",
@@ -25,16 +26,17 @@
"mode-watcher": "^1.1.0",
"prettier": "^3.7.4",
"prettier-plugin-svelte": "^3.4.1",
"svelte": "^5.46.1",
"prettier-plugin-tailwindcss": "^0.7.2",
"svelte": "^5.46.3",
"svelte-sonner": "^1.0.7",
"tailwind-merge": "^3.4.0",
"tailwind-variants": "^3.2.2",
"tailwindcss": "^4.1.18",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"unplugin-icons": "^22.5.0",
"unplugin-icons": "^23.0.1",
"vite": "^7.3.1",
"wrangler": "^4.58.0"
"wrangler": "^4.59.1"
},
"packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
}
+462 -261
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -1,3 +1,4 @@
onlyBuiltDependencies:
- esbuild
- sharp
- workerd
+3 -4
View File
@@ -1,15 +1,14 @@
<script lang="ts">
import { mode, ModeWatcher } from 'mode-watcher'
import { Toaster } from 'svelte-sonner'
import Footer from '$lib/layouts/Footer.svelte'
import Main from '$lib/layouts/Main.svelte'
import { ModeWatcher, mode } from 'mode-watcher'
import { Toaster } from 'svelte-sonner'
</script>
<ModeWatcher />
<Toaster position="bottom-center" duration={2000} theme={mode.current} invert />
<div class="max-w-7xl min-h-dvh m-auto p-6 lg:py-8 stack gap-4">
<div class="m-auto stack min-h-dvh max-w-7xl gap-4 p-6 lg:py-8">
<Main />
<Footer />
</div>
+1 -1
View File
@@ -124,7 +124,7 @@
}
::selection {
@apply text-muted bg-muted-foreground;
@apply bg-muted-foreground text-muted;
}
a {
@@ -2,7 +2,10 @@
import { cn, showNumber } from '$lib/utils'
import type { HTMLAttributes } from 'svelte/elements'
interface Props extends Omit<HTMLAttributes<HTMLInputElement>, 'type' | 'inputmode' | 'onchange' | 'value'> {
interface Props extends Omit<
HTMLAttributes<HTMLInputElement>,
'type' | 'inputmode' | 'onchange' | 'value'
> {
min: number
max: number
value: number
@@ -18,7 +21,7 @@
type="text"
inputmode="numeric"
class={cn(
'bg-muted rounded-md focus-visible:outline-none focus-visible:ring-ring focus-visible:ring-1 focus-visible:ring-offset-2 px-1 mx-0.5',
'mx-0.5 rounded-md bg-muted px-1 focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none',
classNames,
)}
{size}
+2 -3
View File
@@ -1,9 +1,8 @@
<script lang="ts">
import * as Popover from '$lib/components/ui/popover'
import type { PopoverTriggerProps } from 'bits-ui'
import type { Snippet } from 'svelte'
import IconInfo from '~icons/material-symbols/info'
import * as Popover from '$lib/components/ui/popover'
interface Props extends PopoverTriggerProps {
icon?: Snippet
@@ -15,7 +14,7 @@
<Popover.Root>
<Popover.Trigger aria-label="More info" {...restProps}>
{#if !icon}
<IconInfo class="text-muted-foreground text-sm" aria-hidden />
<IconInfo class="text-sm text-muted-foreground" aria-hidden />
{:else}
{@render icon()}
{/if}
+1 -1
View File
@@ -1,8 +1,8 @@
import { Device } from '$lib/image/device'
import { effect } from '$lib/utils.svelte'
import { getContext, setContext } from 'svelte'
import { toast } from 'svelte-sonner'
import type { MidiContext } from './midi.svelte'
import { effect } from '$lib/utils.svelte'
function isInkclip(port: MIDIPort): boolean {
return !!(
+3 -3
View File
@@ -1,7 +1,7 @@
import { getContext, setContext } from 'svelte'
import type { FilesContext } from './files.svelte'
import { toast } from 'svelte-sonner'
import { DEVICE_HEIGHT, DEVICE_WIDTH } from '$lib/constants'
import { getContext, setContext } from 'svelte'
import { toast } from 'svelte-sonner'
import type { FilesContext } from './files.svelte'
export interface ImageContext {
image: ImageBitmap | null
+6 -6
View File
@@ -1,10 +1,10 @@
import { getContext, setContext } from 'svelte'
import type { ImageContext } from './image.svelte'
import { withTransform } from '$lib/image/transform'
import type { ConversionConfig } from './config.svelte'
import { Scaler } from '$lib/image/scaler'
import { Quantizer } from '$lib/image/quantizer'
import { DEVICE_HEIGHT, DEVICE_WIDTH } from '$lib/constants'
import { Quantizer } from '$lib/image/quantizer'
import { Scaler } from '$lib/image/scaler'
import { withTransform } from '$lib/image/transform'
import { getContext, setContext } from 'svelte'
import type { ConversionConfig } from './config.svelte'
import type { ImageContext } from './image.svelte'
export class RenderedContext {
rendered: Uint8Array | null = $state(null)
+1 -2
View File
@@ -1,5 +1,4 @@
import RgbQuant from '$lib/vendor/rgbquant'
import type { DitheringKernel, RgbQuantImage } from '$lib/vendor/rgbquant'
import RgbQuant, { type DitheringKernel, type RgbQuantImage } from '$lib/vendor/rgbquant'
export type { DitheringKernel, RgbQuantImage } from '$lib/vendor/rgbquant'
+4 -1
View File
@@ -10,7 +10,10 @@ export type ScaleMode = 'fit' | 'crop' | 'distort'
export class Scaler {
private readonly canvasAspectRatio: number
constructor(private readonly canvasWidth: number, private readonly canvasHeight: number) {
constructor(
private readonly canvasWidth: number,
private readonly canvasHeight: number,
) {
this.canvasAspectRatio = canvasWidth / canvasHeight
}
+5 -2
View File
@@ -11,7 +11,10 @@ export type Operation = 'cw' | 'ccw' | 'h' | 'v'
* Algebraically, this is the dihedral group of order 8 (D₄).
*/
export class Transform {
constructor(public readonly side: Side = 'obverse', public readonly rotation: Rotation = 0) {}
constructor(
public readonly side: Side = 'obverse',
public readonly rotation: Rotation = 0,
) {}
cw(): Transform {
const rotationAngle = this.side === 'obverse' ? 90 : 270
@@ -67,6 +70,6 @@ export function withTransform<T>(ctx: Context2D, transform: Transform, action: (
ctx.rotate((transform.rotation / 180) * Math.PI)
ctx.translate(-centerX, -centerY)
},
action
action,
)
}
+2 -3
View File
@@ -1,9 +1,8 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button'
import IconLightMode from '~icons/material-symbols/light-mode'
import IconDarkMode from '~icons/material-symbols/dark-mode'
import { mode, toggleMode } from 'mode-watcher'
import IconDarkMode from '~icons/material-symbols/dark-mode'
import IconLightMode from '~icons/material-symbols/light-mode'
</script>
<footer class="row text-sm text-muted-foreground">
+8 -9
View File
@@ -1,18 +1,17 @@
<script lang="ts">
import { Separator } from '$lib/components/ui/separator'
import { createConversionConfig } from '$lib/contexts/config.svelte'
import { createDeviceContext } from '$lib/contexts/device.svelte'
import { createFilesContext } from '$lib/contexts/files.svelte'
import { createImageContext } from '$lib/contexts/image.svelte'
import { createMidiContext } from '$lib/contexts/midi.svelte'
import { createRenderedContext } from '$lib/contexts/rendered.svelte'
import ConnectSection from '$lib/layouts/connect/ConnectSection.svelte'
import EditSection from '$lib/layouts/edit/EditSection.svelte'
import WriteSection from '$lib/layouts/write/WriteSection.svelte'
import { createDeviceContext } from '$lib/contexts/device.svelte'
import { createConversionConfig } from '$lib/contexts/config.svelte'
import { createFilesContext } from '$lib/contexts/files.svelte'
import { createImageContext } from '$lib/contexts/image.svelte'
import { createRenderedContext } from '$lib/contexts/rendered.svelte'
import { createMidiContext } from '$lib/contexts/midi.svelte'
import { onMount } from 'svelte'
import PermissionRequestDialog from './dialog/PermissionRequestDialog.svelte'
import PermissionDeniedDialog from './dialog/PermissionDeniedDialog.svelte'
import PermissionRequestDialog from './dialog/PermissionRequestDialog.svelte'
import UnsupportedDialog from './dialog/UnsupportedDialog.svelte'
const midiCtx = createMidiContext()
@@ -27,7 +26,7 @@ onMount(async () => {
})
</script>
<main class="grow w-full stack gap-4">
<main class="stack w-full grow gap-4">
<h1 class="sr-only">Write to Inkclip</h1>
<ConnectSection />
@@ -1,11 +1,10 @@
<script lang="ts">
import IconPending from '~icons/material-symbols/pending'
import IconCheckCircle from '~icons/material-symbols/check-circle'
import ConnectButton from './ConnectButton.svelte'
import { getDeviceContext } from '$lib/contexts/device.svelte'
import MoreInfo from '$lib/components/MoreInfo.svelte'
import { getDeviceContext } from '$lib/contexts/device.svelte'
import { assert } from '$lib/utils'
import IconCheckCircle from '~icons/material-symbols/check-circle'
import IconPending from '~icons/material-symbols/pending'
import ConnectButton from './ConnectButton.svelte'
const deviceCtx = getDeviceContext()
@@ -35,11 +34,11 @@ $effect(() => {
</script>
<section
class="flex flex-col lg:flex-row lg:items-center gap-2"
class="flex flex-col gap-2 lg:flex-row lg:items-center"
aria-labelledby="connect-section-label"
>
<div class="grow">
<h2 class="font-semibold text-xl/8" id="connect-section-label">Connect to a device</h2>
<h2 class="text-xl/8 font-semibold" id="connect-section-label">Connect to a device</h2>
<div class="stack-h gap-1 text-sm" aria-live="polite">
{#if deviceCtx.device === null}
@@ -1,15 +1,14 @@
<script lang="ts">
import { Separator } from '$lib/components/ui/separator'
import PreviewSection from './preview/PreviewSection.svelte'
import ControlsSection from './controls/ControlsSection.svelte'
import { MediaQuery } from 'svelte/reactivity'
import ControlsSection from './controls/ControlsSection.svelte'
import PreviewSection from './preview/PreviewSection.svelte'
const mediaLg = new MediaQuery('min-width: 1024px')
const separatorOrientation = $derived(mediaLg.current ? 'vertical' : 'horizontal')
</script>
<div class="grow flex flex-col lg:flex-row gap-4">
<div class="flex grow flex-col gap-4 lg:flex-row">
<PreviewSection />
<Separator decorative orientation={separatorOrientation} />
@@ -1,9 +1,8 @@
<script lang="ts">
import ImplicitNumericInput from '$lib/components/ImplicitNumericInput.svelte'
import MoreInfo from '$lib/components/MoreInfo.svelte'
import { Label } from '$lib/components/ui/label'
import { Slider } from '$lib/components/ui/slider'
import MoreInfo from '$lib/components/MoreInfo.svelte'
import ImplicitNumericInput from '$lib/components/ImplicitNumericInput.svelte'
import { getConversionConfig } from '$lib/contexts/config.svelte'
const config = getConversionConfig()
@@ -1,20 +1,18 @@
<script lang="ts">
import { Transform } from '$lib/image/transform'
import { Button } from '$lib/components/ui/button'
import Separator from '$lib/components/ui/separator/separator.svelte'
import IconEditOff from '~icons/material-symbols/edit-off'
import AspectRatioAlert from './dimensions/AspectRatioAlert.svelte'
import ScaleModeToggleGroup from './dimensions/ScaleModeToggleGroup.svelte'
import TransformControls from './dimensions/TransformControls.svelte'
import BackgroundColorSlider from './BackgroundColorSlider.svelte'
import DitherControls from './conversion/dither/DitherControls.svelte'
import ContrastSlider from './conversion/ContrastSlider.svelte'
import BrightnessSlider from './conversion/BrightnessSlider.svelte'
import { getConversionConfig } from '$lib/contexts/config.svelte'
import { getImageContext, imageIsCorrectRatio } from '$lib/contexts/image.svelte'
import { DEFAULT_DITHERING_KERNEL } from '$lib/image/quantizer'
import { Transform } from '$lib/image/transform'
import IconEditOff from '~icons/material-symbols/edit-off'
import BackgroundColorSlider from './BackgroundColorSlider.svelte'
import BrightnessSlider from './conversion/BrightnessSlider.svelte'
import ContrastSlider from './conversion/ContrastSlider.svelte'
import DitherControls from './conversion/dither/DitherControls.svelte'
import AspectRatioAlert from './dimensions/AspectRatioAlert.svelte'
import ScaleModeToggleGroup from './dimensions/ScaleModeToggleGroup.svelte'
import TransformControls from './dimensions/TransformControls.svelte'
const imageCtx = getImageContext()
const config = getConversionConfig()
@@ -32,14 +30,14 @@ function restoreDefaultImageSettings() {
}
</script>
<section class="grow stack gap-4" aria-labelledby="controls-section-label">
<h2 class="font-semibold text-xl/6" id="controls-section-label">Edit image</h2>
<section class="stack grow gap-4" aria-labelledby="controls-section-label">
<h2 class="text-xl/6 font-semibold" id="controls-section-label">Edit image</h2>
{#if imageNonSquare}
<AspectRatioAlert />
{/if}
<div class="stack 2xs:row gap-4">
<div class="stack gap-4 2xs:row">
{#if imageNonSquare}
<ScaleModeToggleGroup />
{/if}
@@ -1,9 +1,8 @@
<script lang="ts">
import ImplicitNumericInput from '$lib/components/ImplicitNumericInput.svelte'
import MoreInfo from '$lib/components/MoreInfo.svelte'
import { Label } from '$lib/components/ui/label'
import { Slider } from '$lib/components/ui/slider'
import MoreInfo from '$lib/components/MoreInfo.svelte'
import ImplicitNumericInput from '$lib/components/ImplicitNumericInput.svelte'
import { getConversionConfig } from '$lib/contexts/config.svelte'
const config = getConversionConfig()
@@ -24,7 +23,8 @@
/>%
</span>
<MoreInfo>
Whether the conversion algorithm should bias the entire image towards white (positive) or black (negative).
Whether the conversion algorithm should bias the entire image towards white (positive) or
black (negative).
</MoreInfo>
</div>
@@ -1,9 +1,8 @@
<script lang="ts">
import ImplicitNumericInput from '$lib/components/ImplicitNumericInput.svelte'
import MoreInfo from '$lib/components/MoreInfo.svelte'
import { Label } from '$lib/components/ui/label'
import { Slider } from '$lib/components/ui/slider'
import MoreInfo from '$lib/components/MoreInfo.svelte'
import ImplicitNumericInput from '$lib/components/ImplicitNumericInput.svelte'
import { getConversionConfig } from '$lib/contexts/config.svelte'
const config = getConversionConfig()
@@ -24,7 +23,8 @@
/>%
</span>
<MoreInfo>
How eager the conversion algorithm should push colors towards the two ends (black and white) of the grayscale.
How eager the conversion algorithm should push colors towards the two ends (black and white)
of the grayscale.
</MoreInfo>
</div>
@@ -1,10 +1,8 @@
<script lang="ts">
import { DEFAULT_DITHERING_KERNEL, type DitheringKernel } from '$lib/image/quantizer'
import DitheringKernelDropdown from './DitheringKernelDropdown.svelte'
import DitherSwitch from './DitherSwitch.svelte'
import { getConversionConfig } from '$lib/contexts/config.svelte'
import { DEFAULT_DITHERING_KERNEL, type DitheringKernel } from '$lib/image/quantizer'
import DitherSwitch from './DitherSwitch.svelte'
import DitheringKernelDropdown from './DitheringKernelDropdown.svelte'
const config = getConversionConfig()
@@ -1,7 +1,7 @@
<script lang="ts">
import { Switch } from '$lib/components/ui/switch'
import { Label } from '$lib/components/ui/label'
import MoreInfo from '$lib/components/MoreInfo.svelte'
import { Label } from '$lib/components/ui/label'
import { Switch } from '$lib/components/ui/switch'
interface Props {
checked: boolean
@@ -17,7 +17,7 @@
<MoreInfo>Dithering uses different dot densities to simulate shades of gray.</MoreInfo>
</div>
<div class="grow row items-center">
<div class="row grow items-center">
<Switch {checked} onCheckedChange={onchange} aria-labelledby="dither-switch-label" />
</div>
</div>
@@ -1,10 +1,8 @@
<script lang="ts">
import { type DitheringKernel } from '$lib/image/quantizer'
import MoreInfo from '$lib/components/MoreInfo.svelte'
import { Label } from '$lib/components/ui/label'
import * as Select from '$lib/components/ui/select'
import MoreInfo from '$lib/components/MoreInfo.svelte'
import { type DitheringKernel } from '$lib/image/quantizer'
import { cn } from '$lib/utils'
interface Props {
@@ -29,7 +27,7 @@ const ditheringKernels: Record<DitheringKernel, string> = {
</script>
<div
class={cn('grow stack gap-2', hidden ? ['invisible'] : [])}
class={cn('stack grow gap-2', hidden ? ['invisible'] : [])}
role="group"
aria-labelledby="dithering-kernel-select-label"
>
@@ -1,12 +1,13 @@
<script lang="ts">
import * as Alert from '$lib/components/ui/alert'
import IconAspectRatio from '~icons/material-symbols/aspect-ratio-outline'
import { DEVICE_ASPECT_RATIO } from '$lib/constants'
import IconAspectRatio from '~icons/material-symbols/aspect-ratio-outline'
</script>
<Alert.Root>
<IconAspectRatio aria-hidden />
<Alert.Title>Image is not {String(Math.round(DEVICE_ASPECT_RATIO * 100) / 100)}:1 ratio</Alert.Title>
<Alert.Title
>Image is not {String(Math.round(DEVICE_ASPECT_RATIO * 100) / 100)}:1 ratio</Alert.Title
>
<Alert.Description>You need to choose how to scale your image.</Alert.Description>
</Alert.Root>
@@ -1,11 +1,9 @@
<script lang="ts">
import type { ScaleMode } from '$lib/image/scaler'
import { Label } from '$lib/components/ui/label'
import * as ToggleGroup from '$lib/components/ui/toggle-group'
import * as Tooltip from '$lib/components/ui/tooltip'
import { getConversionConfig } from '$lib/contexts/config.svelte'
import type { ScaleMode } from '$lib/image/scaler'
const config = getConversionConfig()
@@ -1,15 +1,13 @@
<script lang="ts">
import type { Operation } from '$lib/image/transform'
import IconRotate90DegreesCw from '~icons/material-symbols/rotate-90-degrees-cw'
import IconRotate90DegreesCcw from '~icons/material-symbols/rotate-90-degrees-ccw'
import IconFlipHorizontal from '~icons/mdi/flip-horizontal'
import IconFlipVertical from '~icons/mdi/flip-Vertical'
import { Button } from '$lib/components/ui/button'
import { Label } from '$lib/components/ui/label'
import * as Tooltip from '$lib/components/ui/tooltip'
import { getConversionConfig } from '$lib/contexts/config.svelte'
import type { Operation } from '$lib/image/transform'
import IconRotate90DegreesCcw from '~icons/material-symbols/rotate-90-degrees-ccw'
import IconRotate90DegreesCw from '~icons/material-symbols/rotate-90-degrees-cw'
import IconFlipVertical from '~icons/mdi/flip-Vertical'
import IconFlipHorizontal from '~icons/mdi/flip-horizontal'
interface Props {
disabled?: boolean
@@ -1,14 +1,13 @@
<script lang="ts">
import { Label } from '$lib/components/ui/label'
import IconHideImage from '~icons/material-symbols/hide-image'
import { cn } from '$lib/utils'
import { drawQuantizedData, freshContext, makeAltText } from './common.svelte'
import { getRenderedContext } from '$lib/contexts/rendered.svelte'
import { DEVICE_HEIGHT, DEVICE_WIDTH } from '$lib/constants'
import { getFilesContext } from '$lib/contexts/files.svelte'
import { getConversionConfig } from '$lib/contexts/config.svelte'
import { getFilesContext } from '$lib/contexts/files.svelte'
import { getImageContext } from '$lib/contexts/image.svelte'
import { getRenderedContext } from '$lib/contexts/rendered.svelte'
import { cn } from '$lib/utils'
import IconHideImage from '~icons/material-symbols/hide-image'
import { drawQuantizedData, freshContext, makeAltText } from './common.svelte'
const filesCtx = getFilesContext()
const imageCtx = getImageContext()
@@ -26,9 +25,9 @@ $effect(() => {
})
</script>
<div class="bg-[#ccc] shadow-md rounded-lg p-2 w-full aspect-square relative" role="group">
<div class="relative aspect-square w-full rounded-lg bg-[#ccc] p-2 shadow-md" role="group">
<div
class="inset-shadow-sm inset-shadow-stone-400 p-1 size-full"
class="size-full p-1 inset-shadow-sm inset-shadow-stone-400"
role="img"
aria-label={makeAltText(filesCtx, imageCtx, config)}
>
@@ -41,7 +40,7 @@ $effect(() => {
width={DEVICE_WIDTH}
></canvas>
{:else}
<div class="col justify-center text-[#333] size-full">
<div class="col size-full justify-center text-[#333]">
<IconHideImage class="text-5xl" aria-label="No image" />
</div>
{/if}
@@ -1,6 +1,5 @@
<script lang="ts">
import { Input } from '$lib/components/ui/input'
import { getFilesContext } from '$lib/contexts/files.svelte'
interface Props {
@@ -17,7 +16,7 @@ const filesCtx = getFilesContext()
type="file"
accept="image/*"
bind:files={filesCtx.files}
class="font-normal file-input"
class="file-input font-normal"
aria-labelledby="preview-section-label"
/>
@@ -1,14 +1,13 @@
<script lang="ts">
import { Label } from '$lib/components/ui/label'
import IconHideImage from '~icons/material-symbols/hide-image'
import { cn } from '$lib/utils'
import { drawQuantizedData, freshContext, makeAltText } from './common.svelte'
import { getRenderedContext } from '$lib/contexts/rendered.svelte'
import { DEVICE_HEIGHT, DEVICE_WIDTH } from '$lib/constants'
import { getFilesContext } from '$lib/contexts/files.svelte'
import { getConversionConfig } from '$lib/contexts/config.svelte'
import { getFilesContext } from '$lib/contexts/files.svelte'
import { getImageContext } from '$lib/contexts/image.svelte'
import { getRenderedContext } from '$lib/contexts/rendered.svelte'
import { cn } from '$lib/utils'
import IconHideImage from '~icons/material-symbols/hide-image'
import { drawQuantizedData, freshContext, makeAltText } from './common.svelte'
const filesCtx = getFilesContext()
const imageCtx = getImageContext()
@@ -34,7 +33,7 @@ $effect(() => {
<div>
<div
class="bg-[#ccc] shadow-md rounded-lg p-2 w-fit relative mb-1"
class="relative mb-1 w-fit rounded-lg bg-[#ccc] p-2 shadow-md"
role="group"
aria-labelledby="preview-{scale}x-label"
>
@@ -54,7 +53,7 @@ $effect(() => {
width={DEVICE_WIDTH}
></canvas>
{:else}
<div class="col justify-center text-[#333] size-full">
<div class="col size-full justify-center text-[#333]">
<IconHideImage style="font-size: {30 * scale}px" aria-label="No image" />
</div>
{/if}
@@ -1,14 +1,14 @@
<script lang="ts">
import { MediaQuery } from 'svelte/reactivity'
import AdaptivePreviewCanvas from './AdaptivePreviewCanvas.svelte'
import FileSelect from './FileSelect.svelte'
import PreviewCanvas from './PreviewCanvas.svelte'
import AdaptivePreviewCanvas from './AdaptivePreviewCanvas.svelte'
const mediaSm = new MediaQuery('min-width: 640px', false)
</script>
<section class="stack gap-4" aria-labelledby="preview-section-label">
<h2 class="font-semibold text-xl/6" id="preview-section-label">Choose an image</h2>
<h2 class="text-xl/6 font-semibold" id="preview-section-label">Choose an image</h2>
<FileSelect />
@@ -1,10 +1,9 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button'
import { toast } from 'svelte-sonner'
import { BYTES_IN_A_ROW, DEVICE_HEIGHT, DEVICE_WIDTH, WRITE_TIME } from '$lib/constants'
import { getDeviceContext } from '$lib/contexts/device.svelte'
import { getRenderedContext } from '$lib/contexts/rendered.svelte'
import { BYTES_IN_A_ROW, DEVICE_HEIGHT, DEVICE_WIDTH, WRITE_TIME } from '$lib/constants'
import { toast } from 'svelte-sonner'
interface Props {
onprogress: (inPropgress: boolean) => void
@@ -53,7 +52,14 @@ async function connectAndWrite() {
const written = Date.now()
await deviceCtx.device.request({ type: 'UpdateDisplay' })
const finish = Date.now()
console.log('Time (ms): write', written - start, 'update', finish - written, 'total', finish - start)
console.log(
'Time (ms): write',
written - start,
'update',
finish - written,
'total',
finish - start,
)
} catch (e) {
toast.error(`Error writing to device: ${e}`)
return
@@ -63,7 +69,6 @@ async function connectAndWrite() {
inProgress = false
}, WRITE_TIME)
}
}
$effect(() => {
@@ -1,21 +1,20 @@
<script lang="ts">
import IconPending from '~icons/material-symbols/pending'
import { getImageContext } from '$lib/contexts/image.svelte'
import IconArrowUploadProgress from '~icons/material-symbols/arrow-upload-progress'
import IconPending from '~icons/material-symbols/pending'
import IconWarning from '~icons/material-symbols/warning'
import WriteButton from './WriteButton.svelte'
import { getImageContext } from '$lib/contexts/image.svelte'
const imageCtx = getImageContext()
let inProgress = $state(false)
</script>
<section class="flex flex-col lg:flex-row gap-2" aria-labelledby="write-section-label">
<section class="flex flex-col gap-2 lg:flex-row" aria-labelledby="write-section-label">
<div class="grow">
<h2 class="font-semibold text-xl/8" id="write-section-label">Write pattern to device</h2>
<h2 class="text-xl/8 font-semibold" id="write-section-label">Write pattern to device</h2>
<div class="text-sm stack-h gap-1" aria-live="polite">
<div class="stack-h gap-1 text-sm" aria-live="polite">
{#if imageCtx.image === null}
<IconPending class="mt-0.5 shrink-0" aria-hidden /> Select an image file in order to write it
onto your device.
+3 -1
View File
@@ -1,7 +1,9 @@
import { untrack } from 'svelte'
/**
* Like $effect(), but requires you to declare dependencies explicitly. Conceptually, `track` should be a pure function that returns the dependencies, and `action` is executed whenever these dependencies change.
* Like $effect(), but requires you to declare dependencies explicitly. Conceptually, `track`
* should be a pure function that returns the dependencies, and `action` is executed whenever these
* dependencies change.
*/
export function effect(track: () => unknown, action: () => unknown) {
$effect(() => {
+12 -2
View File
@@ -45,6 +45,16 @@ export default class RgbQuant {
palette(tuples: true, noSort?: boolean = false): Rgb[]
palette(tuples: false, noSort?: boolean = false): Uint8Array
palette(): Uint8Array
reduce(image: RgbQuantImage, retType: 1, dithKern?: DitheringKernel | null, dithSerp?: boolean): Uint8Array
reduce(image: RgbQuantImage, retType: 2, dithKern?: DitheringKernel | null, dithSerp?: boolean): number[]
reduce(
image: RgbQuantImage,
retType: 1,
dithKern?: DitheringKernel | null,
dithSerp?: boolean,
): Uint8Array
reduce(
image: RgbQuantImage,
retType: 2,
dithKern?: DitheringKernel | null,
dithSerp?: boolean,
): number[]
}
+3 -3
View File
@@ -1,9 +1,9 @@
/// <reference types="node" />
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import { resolve } from 'path'
import icons from 'unplugin-icons/vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import tailwindcss from '@tailwindcss/vite'
import icons from 'unplugin-icons/vite'
import { defineConfig } from 'vite'
// https://vite.dev/config/
export default defineConfig({