Enforce consistent formatting in webapp source
This commit is contained in:
+10
-2
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
|
||||
Generated
+462
-261
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
- sharp
|
||||
- workerd
|
||||
|
||||
@@ -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
@@ -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}
|
||||
|
||||
@@ -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,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 !!(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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'
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
+3
-5
@@ -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.
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
Vendored
+12
-2
@@ -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[]
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user