load, save, and new buttons

This commit is contained in:
Emerald 2023-10-25 17:46:55 -04:00
parent 1f54e4e8b0
commit 2228cfc713
Signed by: emerald
GPG Key ID: 13F7EFB915A0F623
24 changed files with 2453 additions and 92 deletions

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ node_modules
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
target
# Local Netlify folder
.netlify

View File

@ -26,10 +26,12 @@
"@types/omggif": "^1.0.3",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vite-pwa/sveltekit": "^0.2.7",
"autoprefixer": "10.4.16",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0",
"kolorist": "^1.8.0",
"postcss": "8.4.31",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",
@ -40,9 +42,12 @@
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.4.2",
"vite-plugin-pwa": "^0.16.5",
"vite-plugin-tailwind-purgecss": "^0.1.3",
"vite-plugin-top-level-await": "^1.3.1",
"vite-plugin-wasm": "^3.2.2"
"vite-plugin-wasm": "^3.2.2",
"workbox-build": "^7.0.0",
"workbox-window": "^7.0.0"
},
"type": "module",
"dependencies": {

View File

@ -25,8 +25,6 @@ gtk3 = ">= 3"
webkit2gtk3 = ">= 2"
[workspace]
[profile.release]
strip = true
opt-level = "s"

View File

@ -2,7 +2,7 @@
<html lang="en" class="dark">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>

View File

@ -0,0 +1,68 @@
<script lang="ts">
import { Gif } from '$lib/gifler';
import { loadFrame, loadImage, loadRay } from '$lib/io';
import { debug } from '$lib/logging';
import { keepFocused } from '$lib/state';
import { frames, mode } from '$lib/store';
import { base64ToArrayBuffer } from '$lib/utils';
import Image from 'image-js';
let fileLoader: HTMLInputElement;
let files: FileList;
$: loading = false;
$: $keepFocused = loading;
const handleLoad = () => {
if (mode === 'web') {
fileLoader.click();
loading = true;
}
};
const handleWebLoad = () => {
const file = files.item(0);
if (file) finishLoad(file);
fileLoader.value = '';
};
const finishLoad = async (data: File | Blob) => {
const ray = await loadRay(data);
const expr = ray?.get_expr(0);
for (let i = 0; i < 4; i++) {
const raw = expr?.get_frame(i);
if (!raw) {
$frames[i] = null;
continue;
}
const image = await loadFrame(raw);
if (image instanceof Image) {
$frames[i] = {
kind: 'still',
value: image
};
} else if (image instanceof Gif) {
$frames[i] = {
kind: 'GIF',
value: image,
data: new Uint8Array(base64ToArrayBuffer(raw))
};
} else {
$frames[i] = null;
}
}
ray?.free();
loading = false
};
</script>
{#if mode === 'web'}
<input type="file" class="hidden" bind:this={fileLoader} bind:files on:change={handleWebLoad} />
{/if}
<button class="btn btn-md variant-ringed-surface" on:click={handleLoad}>Load</button>

View File

@ -12,6 +12,8 @@
});
const saveCurrent = async () => {
// this feels inefficient but we're basically just mapping the current expression to
// wasm memory
const ray = new UnpackedRay();
const expr = new Expression();
for (let i = 0; i < $frames.length; i++) {
@ -36,6 +38,9 @@
}
// don't forget to free
ray.free();
};
</script>

View File

@ -1,6 +1,21 @@
import { Image } from 'image-js';
import { mode } from '$lib/store';
import gifler, { Gif } from './gifler';
import { load_ray, type UnpackedRay } from '@cathode/cathode-ray';
import { base64ToArrayBuffer } from '$lib/utils';
export const loadFrame = async (raw: string): Promise<Gif | Image | null> => {
const blob = new Blob([base64ToArrayBuffer(raw)]);
try {
return await loadImage(blob);
} catch {
try {
return await loadGif(blob);
} catch {}
}
return null;
};
export const loadImage = async (data: File | Blob): Promise<Image | null> => {
if (mode === 'web') {
@ -10,21 +25,17 @@ export const loadImage = async (data: File | Blob): Promise<Image | null> => {
const raw = reader.result as ArrayBuffer | null;
if (!raw) return null;
let img = await Image.load(raw);
if (img.width > 600 || img.height > 400){
img = img.resize({
if (img.width > 600 || img.height > 400) {
img = img.resize({
width: 600
})
}
resolve(
img
);
});
}
resolve(img);
};
reader.readAsArrayBuffer(data);
});
}
return null;
};
@ -36,14 +47,25 @@ export const loadGif = async (data: File | Blob): Promise<Gif | null> => {
const raw = reader.result as string | null;
if (!raw) return resolve(null);
let img = gifler(raw);
resolve(
img
);
resolve(img);
};
reader.readAsDataURL(data);
});
}
return null;
}
};
export const loadRay = async (data: File | Blob): Promise<UnpackedRay | null> => {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = async () => {
const raw = reader.result as ArrayBuffer | null;
if (!raw) return resolve(null);
const ray = load_ray(new Uint8Array(raw));
resolve(ray ?? null);
};
reader.readAsArrayBuffer(data);
});
};

View File

@ -2,6 +2,7 @@ import {writable} from "svelte/store";
import type { Image } from "image-js";
import type { Gif } from "$lib/gifler";
import { info } from "$lib/logging";
import { TAURI_MODE } from "$env/static/public";
export type BGColor ="transparent" | "blue" | "green" | "pink" | {custom: string} ;
export type Meta = {
@ -50,6 +51,6 @@ export class Config {
export let frames = writable<Array<FrameData | null>>([null, null, null, null]);
export let config = writable(new Config());
export const mode = window?.__TAURI__ ? "tauri" : "web";
export const mode = TAURI_MODE === "tauri" ? "tauri" : "web";
info(`Running app in ${mode} mode`);

View File

@ -5,6 +5,15 @@ export function bytesToBase64(bytes: Uint8Array): string {
return btoa(binString);
}
export function base64ToArrayBuffer(base64: string) {
var binaryString = atob(base64);
var bytes = new Uint8Array(binaryString.length);
for (var i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
export const getBase64 = async (obj: FrameData) => {
if (obj.kind === "still") {
return await obj.value.toBase64();

View File

@ -12,8 +12,14 @@
import { onDestroy, onMount } from 'svelte';
import { active, initState, closeThreshold, activationLevel, transparent } from '$lib/state';
import SaveButton from '$lib/components/SaveButton.svelte';
import LoadButton from '$lib/components/LoadButton.svelte';
import { frames } from "$lib/store"
storePopup.set({ computePosition, autoUpdate, flip, shift, offset, arrow });
// import { pwaInfo } from 'virtual:pwa-info';
// $: webManifestLink = pwaInfo ? pwaInfo.webManifest.linkTag : '' ;
let deinitAudio: NodeJS.Timeout | undefined = undefined;
onMount(async () => {
deinitAudio = await initAudio();
@ -24,9 +30,17 @@
if (deinitAudio) clearInterval(deinitAudio);
});
const clearFrames = () => {
frames.update(() => [null, null, null, null]);
}
$: bgColor = $transparent ? 'bg-green-800' : 'bg-surface-900';
</script>
<!-- <svelte:head>
{@html webManifestLink}
</svelte:head> -->
<div class="{bgColor} w-screen h-screen" id="container">
<slot />
{#if !$transparent}
@ -39,9 +53,9 @@
}}
class="flex w-full justify-center absolute top-4 gap-4"
>
<button class="btn btn-md variant-ringed-surface">New</button>
<button class="btn btn-md variant-ringed-surface" on:click={clearFrames}>New</button>
<SaveButton />
<button class="btn btn-md variant-ringed-surface">Load</button>
<LoadButton />
</div>
<div
transition:fly={{

64
src/service-worker.js Normal file
View File

@ -0,0 +1,64 @@
/// <reference types="@sveltejs/kit" />
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
import {build, files, version} from "$service-worker";
const CACHE = `cache-${version}`;
const ASSETS = [
...build,
...files,
];
self.addEventListener('install', (event) => {
async function cacheFiles() {
const c = await caches.open(CACHE);
await c.addAll(ASSETS);
}
event.waitUntil(cacheFiles());
});
self.addEventListener('activate', (event) => {
// Remove previous cached data from disk
async function deleteOldCaches() {
for (const key of await caches.keys()) {
if (key !== CACHE) await caches.delete(key);
}
}
event.waitUntil(deleteOldCaches());
});
self.addEventListener('fetch', (event) => {
// ignore POST requests etc
if (event.request.method !== 'GET') return;
async function respond() {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
// `build`/`files` can always be served from the cache
if (ASSETS.includes(url.pathname)) {
return cache.match(url.pathname);
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const response = await fetch(event.request);
if (response.status === 200) {
cache.put(event.request, response.clone());
}
return response;
} catch {
return cache.match(event.request);
}
}
event.respondWith(respond());
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
static/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
static/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 B

BIN
static/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

BIN
static/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 204.481 204.481" style="enable-background:new 0 0 204.481 204.481;" xml:space="preserve">
<g>
<path d="M162.116,38.31c0.163-0.215,0.315-0.438,0.454-0.67c0.033-0.055,0.068-0.109,0.1-0.164
c0.156-0.276,0.297-0.561,0.419-0.857c0.014-0.034,0.024-0.069,0.038-0.104c0.102-0.26,0.188-0.528,0.261-0.801
c0.019-0.069,0.037-0.137,0.053-0.207c0.068-0.288,0.124-0.581,0.157-0.881c0.002-0.017,0.006-0.034,0.008-0.052
c0.028-0.262,0.043-0.527,0.043-0.796V7.5c0-4.142-3.358-7.5-7.5-7.5H48.332c-4.142,0-7.5,3.358-7.5,7.5v26.279
c0,0.269,0.016,0.534,0.043,0.796c0.002,0.017,0.006,0.034,0.008,0.052c0.034,0.3,0.089,0.593,0.157,0.881
c0.016,0.069,0.035,0.138,0.053,0.207c0.073,0.273,0.159,0.541,0.261,0.801c0.013,0.034,0.024,0.069,0.038,0.104
c0.121,0.296,0.262,0.581,0.419,0.857c0.032,0.056,0.067,0.109,0.1,0.164c0.14,0.232,0.291,0.455,0.454,0.67
c0.027,0.035,0.047,0.074,0.074,0.109l50.255,63.821l-50.255,63.821c-0.028,0.035-0.047,0.074-0.074,0.109
c-0.163,0.215-0.315,0.438-0.454,0.67c-0.033,0.055-0.068,0.109-0.1,0.164c-0.156,0.276-0.297,0.561-0.419,0.857
c-0.014,0.034-0.024,0.069-0.038,0.104c-0.102,0.26-0.188,0.528-0.261,0.801c-0.019,0.069-0.037,0.137-0.053,0.207
c-0.068,0.288-0.124,0.581-0.157,0.881c-0.002,0.017-0.006,0.034-0.008,0.052c-0.028,0.262-0.043,0.527-0.043,0.796v26.279
c0,4.142,3.358,7.5,7.5,7.5h107.817c4.142,0,7.5-3.358,7.5-7.5v-26.279c0-0.269-0.016-0.534-0.043-0.796
c-0.002-0.017-0.006-0.034-0.008-0.052c-0.034-0.3-0.089-0.593-0.157-0.881c-0.016-0.069-0.035-0.138-0.053-0.207
c-0.073-0.273-0.159-0.541-0.261-0.801c-0.013-0.034-0.024-0.069-0.038-0.104c-0.121-0.296-0.262-0.581-0.419-0.857
c-0.032-0.056-0.067-0.109-0.1-0.164c-0.14-0.232-0.291-0.455-0.454-0.67c-0.027-0.035-0.047-0.074-0.074-0.109l-50.255-63.821
l50.255-63.821C162.07,38.385,162.089,38.346,162.116,38.31z M148.649,15v11.279H55.832V15H148.649z M55.832,189.481v-11.279
h92.817v11.279H55.832z M140.698,163.202H63.784l38.457-48.838L140.698,163.202z M102.241,90.118L63.784,41.279h76.914
L102.241,90.118z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -19,7 +19,7 @@ const config = {
env: {
publicPrefix: 'TAURI_'
},
}
};
export default config;

View File

@ -8,7 +8,11 @@
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
"strict": true,
"types": [
"vite-plugin-pwa/client",
"vite-plugin-pwa/info"
]
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//

View File

@ -2,6 +2,7 @@ import { purgeCss } from 'vite-plugin-tailwind-purgecss';
import { sveltekit } from '@sveltejs/kit/vite';
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
import { SvelteKitPWA } from "@vite-pwa/sveltekit";
import { defineConfig } from 'vite';
export default defineConfig({

2253
yarn.lock

File diff suppressed because it is too large Load Diff