Replace old codebase with a sveltekit scaffold
217
.svelte-kit/ambient.d.ts
vendored
Normal file
|
@ -0,0 +1,217 @@
|
|||
|
||||
// this file is generated — do not edit it
|
||||
|
||||
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
|
||||
/**
|
||||
* Environment variables [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env`. Like [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), this module cannot be imported into client-side code. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured).
|
||||
*
|
||||
* _Unlike_ [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), the values exported from this module are statically injected into your bundle at build time, enabling optimisations like dead code elimination.
|
||||
*
|
||||
* ```ts
|
||||
* import { API_KEY } from '$env/static/private';
|
||||
* ```
|
||||
*
|
||||
* Note that all environment variables referenced in your code should be declared (for example in an `.env` file), even if they don't have a value until the app is deployed:
|
||||
*
|
||||
* ```
|
||||
* MY_FEATURE_FLAG=""
|
||||
* ```
|
||||
*
|
||||
* You can override `.env` values from the command line like so:
|
||||
*
|
||||
* ```bash
|
||||
* MY_FEATURE_FLAG="enabled" npm run dev
|
||||
* ```
|
||||
*/
|
||||
declare module '$env/static/private' {
|
||||
export const NVM_INC: string;
|
||||
export const TERM_PROGRAM: string;
|
||||
export const NODE: string;
|
||||
export const INIT_CWD: string;
|
||||
export const NVM_CD_FLAGS: string;
|
||||
export const TERM: string;
|
||||
export const SHELL: string;
|
||||
export const HOMEBREW_REPOSITORY: string;
|
||||
export const TMPDIR: string;
|
||||
export const npm_config_global_prefix: string;
|
||||
export const TERM_PROGRAM_VERSION: string;
|
||||
export const ZDOTDIR: string;
|
||||
export const ORIGINAL_XDG_CURRENT_DESKTOP: string;
|
||||
export const MallocNanoZone: string;
|
||||
export const COLOR: string;
|
||||
export const npm_config_noproxy: string;
|
||||
export const npm_config_local_prefix: string;
|
||||
export const ZSH: string;
|
||||
export const NVM_DIR: string;
|
||||
export const USER: string;
|
||||
export const LS_COLORS: string;
|
||||
export const COMMAND_MODE: string;
|
||||
export const npm_config_globalconfig: string;
|
||||
export const SSH_AUTH_SOCK: string;
|
||||
export const VSCODE_PROFILE_INITIALIZED: string;
|
||||
export const __CF_USER_TEXT_ENCODING: string;
|
||||
export const npm_execpath: string;
|
||||
export const PAGER: string;
|
||||
export const LSCOLORS: string;
|
||||
export const PATH: string;
|
||||
export const npm_package_json: string;
|
||||
export const _: string;
|
||||
export const npm_config_userconfig: string;
|
||||
export const npm_config_init_module: string;
|
||||
export const USER_ZDOTDIR: string;
|
||||
export const __CFBundleIdentifier: string;
|
||||
export const npm_command: string;
|
||||
export const PWD: string;
|
||||
export const npm_lifecycle_event: string;
|
||||
export const EDITOR: string;
|
||||
export const npm_package_name: string;
|
||||
export const LANG: string;
|
||||
export const npm_config_npm_version: string;
|
||||
export const VSCODE_GIT_ASKPASS_EXTRA_ARGS: string;
|
||||
export const XPC_FLAGS: string;
|
||||
export const npm_config_node_gyp: string;
|
||||
export const npm_package_version: string;
|
||||
export const XPC_SERVICE_NAME: string;
|
||||
export const VSCODE_INJECTION: string;
|
||||
export const SHLVL: string;
|
||||
export const HOME: string;
|
||||
export const VSCODE_GIT_ASKPASS_MAIN: string;
|
||||
export const HOMEBREW_PREFIX: string;
|
||||
export const npm_config_cache: string;
|
||||
export const LESS: string;
|
||||
export const LOGNAME: string;
|
||||
export const npm_lifecycle_script: string;
|
||||
export const VSCODE_GIT_IPC_HANDLE: string;
|
||||
export const NVM_BIN: string;
|
||||
export const npm_config_user_agent: string;
|
||||
export const VSCODE_GIT_ASKPASS_NODE: string;
|
||||
export const GIT_ASKPASS: string;
|
||||
export const INFOPATH: string;
|
||||
export const HOMEBREW_CELLAR: string;
|
||||
export const npm_node_execpath: string;
|
||||
export const npm_config_prefix: string;
|
||||
export const COLORTERM: string;
|
||||
export const NODE_ENV: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private), except that it only includes environment variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
|
||||
*
|
||||
* Values are replaced statically at build time.
|
||||
*
|
||||
* ```ts
|
||||
* import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||
* ```
|
||||
*/
|
||||
declare module '$env/static/public' {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/main/packages/adapter-node) (or running [`vite preview`](https://svelte.dev/docs/kit/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured).
|
||||
*
|
||||
* This module cannot be imported into client-side code.
|
||||
*
|
||||
* Dynamic environment variables cannot be used during prerendering.
|
||||
*
|
||||
* ```ts
|
||||
* import { env } from '$env/dynamic/private';
|
||||
* console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE);
|
||||
* ```
|
||||
*
|
||||
* > In `dev`, `$env/dynamic` always includes environment variables from `.env`. In `prod`, this behavior will depend on your adapter.
|
||||
*/
|
||||
declare module '$env/dynamic/private' {
|
||||
export const env: {
|
||||
NVM_INC: string;
|
||||
TERM_PROGRAM: string;
|
||||
NODE: string;
|
||||
INIT_CWD: string;
|
||||
NVM_CD_FLAGS: string;
|
||||
TERM: string;
|
||||
SHELL: string;
|
||||
HOMEBREW_REPOSITORY: string;
|
||||
TMPDIR: string;
|
||||
npm_config_global_prefix: string;
|
||||
TERM_PROGRAM_VERSION: string;
|
||||
ZDOTDIR: string;
|
||||
ORIGINAL_XDG_CURRENT_DESKTOP: string;
|
||||
MallocNanoZone: string;
|
||||
COLOR: string;
|
||||
npm_config_noproxy: string;
|
||||
npm_config_local_prefix: string;
|
||||
ZSH: string;
|
||||
NVM_DIR: string;
|
||||
USER: string;
|
||||
LS_COLORS: string;
|
||||
COMMAND_MODE: string;
|
||||
npm_config_globalconfig: string;
|
||||
SSH_AUTH_SOCK: string;
|
||||
VSCODE_PROFILE_INITIALIZED: string;
|
||||
__CF_USER_TEXT_ENCODING: string;
|
||||
npm_execpath: string;
|
||||
PAGER: string;
|
||||
LSCOLORS: string;
|
||||
PATH: string;
|
||||
npm_package_json: string;
|
||||
_: string;
|
||||
npm_config_userconfig: string;
|
||||
npm_config_init_module: string;
|
||||
USER_ZDOTDIR: string;
|
||||
__CFBundleIdentifier: string;
|
||||
npm_command: string;
|
||||
PWD: string;
|
||||
npm_lifecycle_event: string;
|
||||
EDITOR: string;
|
||||
npm_package_name: string;
|
||||
LANG: string;
|
||||
npm_config_npm_version: string;
|
||||
VSCODE_GIT_ASKPASS_EXTRA_ARGS: string;
|
||||
XPC_FLAGS: string;
|
||||
npm_config_node_gyp: string;
|
||||
npm_package_version: string;
|
||||
XPC_SERVICE_NAME: string;
|
||||
VSCODE_INJECTION: string;
|
||||
SHLVL: string;
|
||||
HOME: string;
|
||||
VSCODE_GIT_ASKPASS_MAIN: string;
|
||||
HOMEBREW_PREFIX: string;
|
||||
npm_config_cache: string;
|
||||
LESS: string;
|
||||
LOGNAME: string;
|
||||
npm_lifecycle_script: string;
|
||||
VSCODE_GIT_IPC_HANDLE: string;
|
||||
NVM_BIN: string;
|
||||
npm_config_user_agent: string;
|
||||
VSCODE_GIT_ASKPASS_NODE: string;
|
||||
GIT_ASKPASS: string;
|
||||
INFOPATH: string;
|
||||
HOMEBREW_CELLAR: string;
|
||||
npm_node_execpath: string;
|
||||
npm_config_prefix: string;
|
||||
COLORTERM: string;
|
||||
NODE_ENV: string;
|
||||
[key: `PUBLIC_${string}`]: undefined;
|
||||
[key: `${string}`]: string | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), but only includes variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
|
||||
*
|
||||
* Note that public dynamic environment variables must all be sent from the server to the client, causing larger network requests — when possible, use `$env/static/public` instead.
|
||||
*
|
||||
* Dynamic environment variables cannot be used during prerendering.
|
||||
*
|
||||
* ```ts
|
||||
* import { env } from '$env/dynamic/public';
|
||||
* console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);
|
||||
* ```
|
||||
*/
|
||||
declare module '$env/dynamic/public' {
|
||||
export const env: {
|
||||
[key: `PUBLIC_${string}`]: string | undefined;
|
||||
}
|
||||
}
|
28
.svelte-kit/generated/client/app.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
export { matchers } from './matchers.js';
|
||||
|
||||
export const nodes = [
|
||||
() => import('./nodes/0'),
|
||||
() => import('./nodes/1'),
|
||||
() => import('./nodes/2')
|
||||
];
|
||||
|
||||
export const server_loads = [];
|
||||
|
||||
export const dictionary = {
|
||||
"/": [2]
|
||||
};
|
||||
|
||||
export const hooks = {
|
||||
handleError: (({ error }) => { console.error(error) }),
|
||||
|
||||
reroute: (() => {}),
|
||||
transport: {}
|
||||
};
|
||||
|
||||
export const decoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.decode]));
|
||||
|
||||
export const hash = false;
|
||||
|
||||
export const decode = (type, value) => decoders[type](value);
|
||||
|
||||
export { default as root } from '../root.js';
|
1
.svelte-kit/generated/client/matchers.js
Normal file
|
@ -0,0 +1 @@
|
|||
export const matchers = {};
|
1
.svelte-kit/generated/client/nodes/0.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as component } from "../../../../node_modules/@sveltejs/kit/src/runtime/components/svelte-5/layout.svelte";
|
1
.svelte-kit/generated/client/nodes/1.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as component } from "../../../../node_modules/@sveltejs/kit/src/runtime/components/svelte-5/error.svelte";
|
1
.svelte-kit/generated/client/nodes/2.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as component } from "../../../../src/routes/+page.svelte";
|
3
.svelte-kit/generated/root.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { asClassComponent } from 'svelte/legacy';
|
||||
import Root from './root.svelte';
|
||||
export default asClassComponent(Root);
|
66
.svelte-kit/generated/root.svelte
Normal file
|
@ -0,0 +1,66 @@
|
|||
<!-- This file is generated by @sveltejs/kit — do not edit it! -->
|
||||
<svelte:options runes={true} />
|
||||
<script>
|
||||
import { setContext, onMount, tick } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// stores
|
||||
let { stores, page, constructors, components = [], form, data_0 = null, data_1 = null } = $props();
|
||||
|
||||
if (!browser) {
|
||||
setContext('__svelte__', stores);
|
||||
}
|
||||
|
||||
if (browser) {
|
||||
$effect.pre(() => stores.page.set(page));
|
||||
} else {
|
||||
stores.page.set(page);
|
||||
}
|
||||
$effect(() => {
|
||||
stores;page;constructors;components;form;data_0;data_1;
|
||||
stores.page.notify();
|
||||
});
|
||||
|
||||
let mounted = $state(false);
|
||||
let navigated = $state(false);
|
||||
let title = $state(null);
|
||||
|
||||
onMount(() => {
|
||||
const unsubscribe = stores.page.subscribe(() => {
|
||||
if (mounted) {
|
||||
navigated = true;
|
||||
tick().then(() => {
|
||||
title = document.title || 'untitled page';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
mounted = true;
|
||||
return unsubscribe;
|
||||
});
|
||||
|
||||
const Pyramid_1=$derived(constructors[1])
|
||||
</script>
|
||||
|
||||
{#if constructors[1]}
|
||||
{@const Pyramid_0 = constructors[0]}
|
||||
<!-- svelte-ignore binding_property_non_reactive -->
|
||||
<Pyramid_0 bind:this={components[0]} data={data_0} {form}>
|
||||
<!-- svelte-ignore binding_property_non_reactive -->
|
||||
<Pyramid_1 bind:this={components[1]} data={data_1} {form} />
|
||||
</Pyramid_0>
|
||||
|
||||
{:else}
|
||||
{@const Pyramid_0 = constructors[0]}
|
||||
<!-- svelte-ignore binding_property_non_reactive -->
|
||||
<Pyramid_0 bind:this={components[0]} data={data_0} {form} />
|
||||
|
||||
{/if}
|
||||
|
||||
{#if mounted}
|
||||
<div id="svelte-announcer" aria-live="assertive" aria-atomic="true" style="position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px">
|
||||
{#if navigated}
|
||||
{title}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
48
.svelte-kit/generated/server/internal.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
|
||||
import root from '../root.js';
|
||||
import { set_building, set_prerendering } from '__sveltekit/environment';
|
||||
import { set_assets } from '__sveltekit/paths';
|
||||
import { set_manifest, set_read_implementation } from '__sveltekit/server';
|
||||
import { set_private_env, set_public_env, set_safe_public_env } from '../../../node_modules/@sveltejs/kit/src/runtime/shared-server.js';
|
||||
|
||||
export const options = {
|
||||
app_template_contains_nonce: false,
|
||||
csp: {"mode":"auto","directives":{"upgrade-insecure-requests":false,"block-all-mixed-content":false},"reportOnly":{"upgrade-insecure-requests":false,"block-all-mixed-content":false}},
|
||||
csrf_check_origin: true,
|
||||
embedded: false,
|
||||
env_public_prefix: 'PUBLIC_',
|
||||
env_private_prefix: '',
|
||||
hash_routing: false,
|
||||
hooks: null, // added lazily, via `get_hooks`
|
||||
preload_strategy: "modulepreload",
|
||||
root,
|
||||
service_worker: false,
|
||||
templates: {
|
||||
app: ({ head, body, assets, nonce, env }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n",
|
||||
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
|
||||
},
|
||||
version_hash: "1ic5av3"
|
||||
};
|
||||
|
||||
export async function get_hooks() {
|
||||
let handle;
|
||||
let handleFetch;
|
||||
let handleError;
|
||||
let init;
|
||||
|
||||
|
||||
let reroute;
|
||||
let transport;
|
||||
|
||||
|
||||
return {
|
||||
handle,
|
||||
handleFetch,
|
||||
handleError,
|
||||
init,
|
||||
reroute,
|
||||
transport
|
||||
};
|
||||
}
|
||||
|
||||
export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation, set_safe_public_env };
|
25
.svelte-kit/non-ambient.d.ts
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
|
||||
// this file is generated — do not edit it
|
||||
|
||||
|
||||
declare module "svelte/elements" {
|
||||
export interface HTMLAttributes<T> {
|
||||
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
|
||||
'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null;
|
||||
'data-sveltekit-preload-code'?:
|
||||
| true
|
||||
| ''
|
||||
| 'eager'
|
||||
| 'viewport'
|
||||
| 'hover'
|
||||
| 'tap'
|
||||
| 'off'
|
||||
| undefined
|
||||
| null;
|
||||
'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
|
||||
'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
|
||||
'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
49
.svelte-kit/tsconfig.json
Normal file
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"$lib": [
|
||||
"../src/lib"
|
||||
],
|
||||
"$lib/*": [
|
||||
"../src/lib/*"
|
||||
]
|
||||
},
|
||||
"rootDirs": [
|
||||
"..",
|
||||
"./types"
|
||||
],
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"lib": [
|
||||
"esnext",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"moduleResolution": "bundler",
|
||||
"module": "esnext",
|
||||
"noEmit": true,
|
||||
"target": "esnext"
|
||||
},
|
||||
"include": [
|
||||
"ambient.d.ts",
|
||||
"non-ambient.d.ts",
|
||||
"./types/**/$types.d.ts",
|
||||
"../vite.config.js",
|
||||
"../vite.config.ts",
|
||||
"../src/**/*.js",
|
||||
"../src/**/*.ts",
|
||||
"../src/**/*.svelte",
|
||||
"../tests/**/*.js",
|
||||
"../tests/**/*.ts",
|
||||
"../tests/**/*.svelte"
|
||||
],
|
||||
"exclude": [
|
||||
"../node_modules/**",
|
||||
"../src/service-worker.js",
|
||||
"../src/service-worker/**/*.js",
|
||||
"../src/service-worker.ts",
|
||||
"../src/service-worker/**/*.ts",
|
||||
"../src/service-worker.d.ts",
|
||||
"../src/service-worker/**/*.d.ts"
|
||||
]
|
||||
}
|
3
.svelte-kit/types/route_meta_data.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"/": []
|
||||
}
|
24
.svelte-kit/types/src/routes/$types.d.ts
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
import type * as Kit from '@sveltejs/kit';
|
||||
|
||||
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
||||
// @ts-ignore
|
||||
type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;
|
||||
type RouteParams = { };
|
||||
type RouteId = '/';
|
||||
type MaybeWithVoid<T> = {} extends T ? T | void : T;
|
||||
export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];
|
||||
type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>
|
||||
type EnsureDefined<T> = T extends null | undefined ? {} : T;
|
||||
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
|
||||
export type Snapshot<T = any> = Kit.Snapshot<T>;
|
||||
type PageParentData = EnsureDefined<LayoutData>;
|
||||
type LayoutRouteId = RouteId | "/" | null
|
||||
type LayoutParams = RouteParams & { }
|
||||
type LayoutParentData = EnsureDefined<{}>;
|
||||
|
||||
export type PageServerData = null;
|
||||
export type PageData = Expand<PageParentData>;
|
||||
export type PageProps = { data: PageData }
|
||||
export type LayoutServerData = null;
|
||||
export type LayoutData = Expand<LayoutParentData>;
|
||||
export type LayoutProps = { data: LayoutData; children: import("svelte").Snippet }
|
21
LICENSE
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Leonardo Murça
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
40
README.md
|
@ -1,10 +1,38 @@
|
|||
# Embroidery Viewer
|
||||
# sv
|
||||
|
||||
A free online tool to view embroidery files.
|
||||
Available at https://embroideryviewer.xyz.
|
||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||
|
||||

|
||||
## Creating a project
|
||||
|
||||
Current supported formats: **.pes, .dst, .pec, .jef and .exp**.
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
Inspired by https://github.com/redteam316/html5-embroidery.git.
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npx sv create
|
||||
|
||||
# create a new project in my-app
|
||||
npx sv create my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||
|
|
BIN
demo.gif
Before Width: | Height: | Size: 4.9 MiB |
26
eslint.config.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import prettier from 'eslint-config-prettier';
|
||||
import js from '@eslint/js';
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import globals from 'globals';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import svelteConfig from './svelte.config.js';
|
||||
|
||||
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||
|
||||
export default [
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
...svelte.configs.recommended,
|
||||
prettier,
|
||||
...svelte.configs.prettier,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser, ...globals.node }
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte', '**/*.svelte.js'],
|
||||
languageOptions: { parserOptions: { svelteConfig } }
|
||||
}
|
||||
];
|
27
index.html
|
@ -1,27 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-us">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="author" content="Leonardo Murça" />
|
||||
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="apple-mobile-web-app-title" content="Embroidery Viewer" />
|
||||
|
||||
<script defer src="https://umami.leomurca.xyz/script.js" data-website-id="bd4c0533-36e6-402d-ac04-577993aaf43a"></script>
|
||||
|
||||
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="canonical" href="https://embroideryviewer.xyz/">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,33 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "Node",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
/**
|
||||
* svelte-preprocess cannot figure out whether you have
|
||||
* a value or a type, so tell TypeScript to enforce using
|
||||
* `import type` instead of `import` for Types.
|
||||
*/
|
||||
"importsNotUsedAsValues": "error",
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* To have warnings / errors of the Svelte compiler at the
|
||||
* correct position, enable source maps by default.
|
||||
*/
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable this if you'd like to use dynamic types.
|
||||
*/
|
||||
"checkJs": true
|
||||
},
|
||||
/**
|
||||
* Use global.d.ts instead of compilerOptions.types
|
||||
* to avoid limiting type declarations.
|
||||
*/
|
||||
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
|
|
4124
package-lock.json
generated
46
package.json
|
@ -1,17 +1,33 @@
|
|||
{
|
||||
"name": "embroidery-viewer",
|
||||
"private": true,
|
||||
"version": "2.0.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"postbuild": "npx svelte-sitemap --domain https://embroideryviewer.xyz -o dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"svelte": "^5.23.3",
|
||||
"vite": "^6.2.3"
|
||||
}
|
||||
"name": "embroidery-viewer",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.2.5",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-svelte": "^3.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^6.2.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
google.com, pub-5761689301112420, DIRECT, f08c47fec0942fa0
|
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 61 KiB |
|
@ -1,2 +0,0 @@
|
|||
User-agent: *
|
||||
Sitemap: https://www.embroideryviewer.xyz/sitemap.xml
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"name": "Embroidery Viewer",
|
||||
"short_name": "EmbViewer",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/web-app-manifest-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/web-app-manifest-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"theme_color": "#06345f",
|
||||
"background_color": "#06345f",
|
||||
"display": "standalone"
|
||||
}
|
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 71 KiB |
|
@ -1,11 +0,0 @@
|
|||
<script>
|
||||
import Head from "./lib/sections/Head.svelte";
|
||||
import Header from "./lib/sections/Header.svelte";
|
||||
import Footer from "./lib/sections/Footer.svelte";
|
||||
import Main from "./lib/sections/Main.svelte";
|
||||
</script>
|
||||
|
||||
<Head/>
|
||||
<Header />
|
||||
<Main />
|
||||
<Footer />
|
74
src/app.css
|
@ -1,74 +0,0 @@
|
|||
:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background-color: #F2F6F5;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
margin-top: 20px;
|
||||
background-color: #05345f;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
input[type="submit"]:hover {
|
||||
cursor: pointer;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body a {
|
||||
text-decoration: none;
|
||||
color: #06345F;
|
||||
border-bottom: 3px solid #06345F;
|
||||
}
|
||||
|
||||
body a:hover {
|
||||
background-color: #06345F;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
:is(h1, h2, h3, h4, h5, h6) {
|
||||
color: #06345F;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #06345F;
|
||||
}
|
||||
|
||||
ul li::marker {
|
||||
color: #06345F;
|
||||
}
|
13
src/app.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
12
src/app.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 155 KiB |
Before Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 124 KiB |
|
@ -1,18 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="68.965652mm"
|
||||
height="68.948975mm"
|
||||
viewBox="0 0 68.965652 68.948975"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1" /><g
|
||||
id="layer1"
|
||||
transform="translate(-78.581248,-114.14203)"><path
|
||||
d="m 101.71922,133.815 7.89657,-7.93103 v 33.06897 c 0,4.59771 6.89657,4.59771 6.89657,0 v -33.06897 l 7.89657,7.93103 c 1.34889,1.35999 3.54767,1.35999 4.89656,0 1.36001,-1.34889 1.36001,-3.5477 0,-4.89659 l -13.79313,-13.79308 c -0.32789,-0.31411 -0.71459,-0.56036 -1.1379,-0.72463 -0.83953,-0.34489 -1.78117,-0.34489 -2.6207,0 -0.42331,0.16427 -0.81001,0.41052 -1.1379,0.72463 l -13.793128,13.79308 c -3.265094,3.26437 1.631464,8.16096 4.896558,4.89659 z m 42.3794,14.7931 c -1.90447,0 -3.44833,1.5439 -3.44828,3.44837 v 20.68969 c -2e-5,1.90442 -1.54386,3.44824 -3.44828,3.44824 H 88.926098 c -1.904415,0 -3.448254,-1.54382 -3.448278,-3.44824 v -20.68969 c 0,-4.59771 -6.89657,-4.59771 -6.89657,0 v 20.68969 c -10e-7,5.7133 4.631545,10.34485 10.344848,10.34485 h 48.275962 c 5.7133,0 10.34485,-4.63155 10.34485,-10.34485 v -20.68969 c 5e-5,-1.90447 -1.54382,-3.44837 -3.44829,-3.44837 z"
|
||||
id="path1"
|
||||
style="opacity:1;mix-blend-mode:normal;fill:#06345f;fill-opacity:1;stroke-width:3.448;stroke-dasharray:none" /></g></svg>
|
Before Width: | Height: | Size: 1.4 KiB |
|
@ -1,75 +0,0 @@
|
|||
import { jDataView } from "./jdataview";
|
||||
import { supportedFormats } from "../format-readers";
|
||||
import { Pattern } from "./pattern";
|
||||
|
||||
function renderFile(filename, evt, canvas, colorView, stitchesView, sizeView, localizedStrings) {
|
||||
const fileExtension = filename.toLowerCase().split(".").pop();
|
||||
const view = jDataView(evt.target.result, 0, evt.size);
|
||||
const pattern = new Pattern();
|
||||
|
||||
supportedFormats[fileExtension].read(view, pattern);
|
||||
|
||||
pattern.moveToPositive();
|
||||
pattern.drawShapeTo(canvas);
|
||||
pattern.drawColorsTo(colorView);
|
||||
pattern.drawStitchesCountTo(stitchesView, localizedStrings.stitches);
|
||||
pattern.drawSizeValuesTo(stitchesView, localizedStrings.dimensions);
|
||||
}
|
||||
|
||||
function renderAbortMessage(errorMessageRef) {
|
||||
errorMessageRef.innerHTML = "Render aborted!";
|
||||
}
|
||||
|
||||
function renderErrorMessage(errorName, errorMessageRef) {
|
||||
let message;
|
||||
switch (errorName) {
|
||||
case "NotFoundError":
|
||||
message =
|
||||
"The file could not be found at the time the read was processed.";
|
||||
break;
|
||||
case "SecurityError":
|
||||
message = "<p>A file security error occured. This can be due to:</p>";
|
||||
message +=
|
||||
"<ul><li>Accessing certain files deemed unsafe for Web applications.</li>";
|
||||
message += "<li>Performing too many read calls on file resources.</li>";
|
||||
message +=
|
||||
"<li>The file has changed on disk since the user selected it.</li></ul>";
|
||||
break;
|
||||
case "NotReadableError":
|
||||
message =
|
||||
"The file cannot be read. This can occur if the file is open in another application.";
|
||||
break;
|
||||
case "EncodingError":
|
||||
message = "The length of the data URL for the file is too long.";
|
||||
break;
|
||||
default:
|
||||
message = "Something wrong happened!";
|
||||
break;
|
||||
}
|
||||
|
||||
errorMessageRef.innerHTML = message;
|
||||
}
|
||||
|
||||
export default function renderFileToCanvas(
|
||||
fileObject,
|
||||
canvas,
|
||||
errorMessageRef,
|
||||
colorView,
|
||||
stitchesView,
|
||||
sizeView,
|
||||
localizedStrings
|
||||
) {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onloadend = (evt) =>
|
||||
renderFile(fileObject.name, evt, canvas, colorView, stitchesView, sizeView, localizedStrings);
|
||||
reader.abort = (/** @type {any} */ _) => renderAbortMessage(errorMessageRef);
|
||||
reader.onerror = (evt) =>
|
||||
renderErrorMessage(evt.target.error.name, errorMessageRef);
|
||||
|
||||
if (fileObject) {
|
||||
reader.readAsArrayBuffer(fileObject);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
|
@ -1,848 +0,0 @@
|
|||
// @ts-nocheck
|
||||
//
|
||||
// jDataView by Vjeux <vjeuxx@gmail.com> - Jan 2010
|
||||
// Continued by RReverser <me@rreverser.com> - Feb 2013
|
||||
//
|
||||
// A unique way to work with a binary file in the browser
|
||||
// http://github.com/jDataView/jDataView
|
||||
// http://jDataView.github.io/
|
||||
|
||||
var compatibility = {
|
||||
// NodeJS Buffer in v0.5.5 and newer
|
||||
NodeBuffer: "Buffer" in globalThis && "readInt16LE" in Buffer.prototype,
|
||||
DataView:
|
||||
"DataView" in globalThis &&
|
||||
("getFloat64" in DataView.prototype || // Chrome
|
||||
"getFloat64" in new DataView(new ArrayBuffer(1))), // Node
|
||||
ArrayBuffer: "ArrayBuffer" in globalThis,
|
||||
PixelData:
|
||||
"CanvasPixelArray" in globalThis &&
|
||||
"ImageData" in globalThis &&
|
||||
"document" in globalThis,
|
||||
};
|
||||
|
||||
var createPixelData = function (byteLength, buffer) {
|
||||
var data = createPixelData.context2d.createImageData(
|
||||
(byteLength + 3) / 4,
|
||||
1
|
||||
).data;
|
||||
data.byteLength = byteLength;
|
||||
if (buffer !== undefined) {
|
||||
for (var i = 0; i < byteLength; i++) {
|
||||
data[i] = buffer[i];
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
||||
createPixelData.context2d = document.createElement("canvas").getContext("2d");
|
||||
|
||||
var dataTypes = {
|
||||
Int8: 1,
|
||||
Int16: 2,
|
||||
Int32: 4,
|
||||
Uint8: 1,
|
||||
Uint16: 2,
|
||||
Uint32: 4,
|
||||
Float32: 4,
|
||||
Float64: 8,
|
||||
};
|
||||
|
||||
var nodeNaming = {
|
||||
Int8: "Int8",
|
||||
Int16: "Int16",
|
||||
Int32: "Int32",
|
||||
Uint8: "UInt8",
|
||||
Uint16: "UInt16",
|
||||
Uint32: "UInt32",
|
||||
Float32: "Float",
|
||||
Float64: "Double",
|
||||
};
|
||||
|
||||
function arrayFrom(arrayLike, forceCopy) {
|
||||
return !forceCopy && arrayLike instanceof Array
|
||||
? arrayLike
|
||||
: Array.prototype.slice.call(arrayLike);
|
||||
}
|
||||
|
||||
function defined(value, defaultValue) {
|
||||
return value !== undefined ? value : defaultValue;
|
||||
}
|
||||
|
||||
export function jDataView(buffer, byteOffset, byteLength, littleEndian) {
|
||||
/* jshint validthis:true */
|
||||
|
||||
if (buffer instanceof jDataView) {
|
||||
var result = buffer.slice(byteOffset, byteOffset + byteLength);
|
||||
result._littleEndian = defined(littleEndian, result._littleEndian);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!(this instanceof jDataView)) {
|
||||
return new jDataView(buffer, byteOffset, byteLength, littleEndian);
|
||||
}
|
||||
|
||||
this.buffer = buffer = jDataView.wrapBuffer(buffer);
|
||||
|
||||
// Check parameters and existing functionnalities
|
||||
this._isArrayBuffer =
|
||||
compatibility.ArrayBuffer && buffer instanceof ArrayBuffer;
|
||||
this._isPixelData =
|
||||
compatibility.PixelData && buffer instanceof CanvasPixelArray;
|
||||
this._isDataView = compatibility.DataView && this._isArrayBuffer;
|
||||
this._isNodeBuffer = compatibility.NodeBuffer && buffer instanceof Buffer;
|
||||
|
||||
// Handle Type Errors
|
||||
if (
|
||||
!this._isNodeBuffer &&
|
||||
!this._isArrayBuffer &&
|
||||
!this._isPixelData &&
|
||||
!(buffer instanceof Array)
|
||||
) {
|
||||
throw new TypeError("jDataView buffer has an incompatible type");
|
||||
}
|
||||
|
||||
// Default Values
|
||||
this._littleEndian = !!littleEndian;
|
||||
|
||||
var bufferLength = "byteLength" in buffer ? buffer.byteLength : buffer.length;
|
||||
this.byteOffset = byteOffset = defined(byteOffset, 0);
|
||||
this.byteLength = byteLength = defined(byteLength, bufferLength - byteOffset);
|
||||
|
||||
if (!this._isDataView) {
|
||||
this._checkBounds(byteOffset, byteLength, bufferLength);
|
||||
} else {
|
||||
this._view = new DataView(buffer, byteOffset, byteLength);
|
||||
}
|
||||
|
||||
// Create uniform methods (action wrappers) for the following data types
|
||||
|
||||
this._engineAction = this._isDataView
|
||||
? this._dataViewAction
|
||||
: this._isNodeBuffer
|
||||
? this._nodeBufferAction
|
||||
: this._isArrayBuffer
|
||||
? this._arrayBufferAction
|
||||
: this._arrayAction;
|
||||
}
|
||||
|
||||
function getCharCodes(string) {
|
||||
if (compatibility.NodeBuffer) {
|
||||
return new Buffer(string, "binary");
|
||||
}
|
||||
|
||||
var Type = compatibility.ArrayBuffer ? Uint8Array : Array,
|
||||
codes = new Type(string.length);
|
||||
|
||||
for (var i = 0, length = string.length; i < length; i++) {
|
||||
codes[i] = string.charCodeAt(i) & 0xff;
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
|
||||
// mostly internal function for wrapping any supported input (String or Array-like) to best suitable buffer format
|
||||
jDataView.wrapBuffer = function (buffer) {
|
||||
switch (typeof buffer) {
|
||||
case "number":
|
||||
if (compatibility.NodeBuffer) {
|
||||
buffer = new Buffer(buffer);
|
||||
buffer.fill(0);
|
||||
} else if (compatibility.ArrayBuffer) {
|
||||
buffer = new Uint8Array(buffer).buffer;
|
||||
} else if (compatibility.PixelData) {
|
||||
buffer = createPixelData(buffer);
|
||||
} else {
|
||||
buffer = new Array(buffer);
|
||||
for (var i = 0; i < buffer.length; i++) {
|
||||
buffer[i] = 0;
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
|
||||
case "string":
|
||||
buffer = getCharCodes(buffer);
|
||||
/* falls through */
|
||||
default:
|
||||
if (
|
||||
"length" in buffer &&
|
||||
!(
|
||||
(compatibility.NodeBuffer && buffer instanceof Buffer) ||
|
||||
(compatibility.ArrayBuffer && buffer instanceof ArrayBuffer) ||
|
||||
(compatibility.PixelData && buffer instanceof CanvasPixelArray)
|
||||
)
|
||||
) {
|
||||
if (compatibility.NodeBuffer) {
|
||||
buffer = new Buffer(buffer);
|
||||
} else if (compatibility.ArrayBuffer) {
|
||||
if (!(buffer instanceof ArrayBuffer)) {
|
||||
buffer = new Uint8Array(buffer).buffer;
|
||||
// bug in Node.js <= 0.8:
|
||||
if (!(buffer instanceof ArrayBuffer)) {
|
||||
buffer = new Uint8Array(arrayFrom(buffer, true)).buffer;
|
||||
}
|
||||
}
|
||||
} else if (compatibility.PixelData) {
|
||||
buffer = createPixelData(buffer.length, buffer);
|
||||
} else {
|
||||
buffer = arrayFrom(buffer);
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
|
||||
function pow2(n) {
|
||||
return n >= 0 && n < 31 ? 1 << n : pow2[n] || (pow2[n] = Math.pow(2, n));
|
||||
}
|
||||
|
||||
// left for backward compatibility
|
||||
jDataView.createBuffer = function () {
|
||||
return jDataView.wrapBuffer(arguments);
|
||||
};
|
||||
|
||||
function Uint64(lo, hi) {
|
||||
this.lo = lo;
|
||||
this.hi = hi;
|
||||
}
|
||||
|
||||
jDataView.Uint64 = Uint64;
|
||||
|
||||
Uint64.prototype = {
|
||||
valueOf: function () {
|
||||
return this.lo + pow2(32) * this.hi;
|
||||
},
|
||||
|
||||
toString: function () {
|
||||
return Number.prototype.toString.apply(this.valueOf(), arguments);
|
||||
},
|
||||
};
|
||||
|
||||
Uint64.fromNumber = function (number) {
|
||||
var hi = Math.floor(number / pow2(32)),
|
||||
lo = number - hi * pow2(32);
|
||||
|
||||
return new Uint64(lo, hi);
|
||||
};
|
||||
|
||||
function Int64(lo, hi) {
|
||||
Uint64.apply(this, arguments);
|
||||
}
|
||||
|
||||
jDataView.Int64 = Int64;
|
||||
|
||||
Int64.prototype =
|
||||
"create" in Object ? Object.create(Uint64.prototype) : new Uint64();
|
||||
|
||||
Int64.prototype.valueOf = function () {
|
||||
if (this.hi < pow2(31)) {
|
||||
return Uint64.prototype.valueOf.apply(this, arguments);
|
||||
}
|
||||
return -(pow2(32) - this.lo + pow2(32) * (pow2(32) - 1 - this.hi));
|
||||
};
|
||||
|
||||
Int64.fromNumber = function (number) {
|
||||
var lo, hi;
|
||||
if (number >= 0) {
|
||||
var unsigned = Uint64.fromNumber(number);
|
||||
lo = unsigned.lo;
|
||||
hi = unsigned.hi;
|
||||
} else {
|
||||
hi = Math.floor(number / pow2(32));
|
||||
lo = number - hi * pow2(32);
|
||||
hi += pow2(32);
|
||||
}
|
||||
return new Int64(lo, hi);
|
||||
};
|
||||
|
||||
jDataView.prototype = {
|
||||
_offset: 0,
|
||||
_bitOffset: 0,
|
||||
|
||||
compatibility: compatibility,
|
||||
|
||||
_checkBounds: function (byteOffset, byteLength, maxLength) {
|
||||
// Do additional checks to simulate DataView
|
||||
if (typeof byteOffset !== "number") {
|
||||
throw new TypeError("Offset is not a number.");
|
||||
}
|
||||
if (typeof byteLength !== "number") {
|
||||
throw new TypeError("Size is not a number.");
|
||||
}
|
||||
if (byteLength < 0) {
|
||||
throw new RangeError("Length is negative.");
|
||||
}
|
||||
if (
|
||||
byteOffset < 0 ||
|
||||
byteOffset + byteLength > defined(maxLength, this.byteLength)
|
||||
) {
|
||||
throw new RangeError("Offsets are out of bounds.");
|
||||
}
|
||||
},
|
||||
|
||||
_action: function (type, isReadAction, byteOffset, littleEndian, value) {
|
||||
return this._engineAction(
|
||||
type,
|
||||
isReadAction,
|
||||
defined(byteOffset, this._offset),
|
||||
defined(littleEndian, this._littleEndian),
|
||||
value
|
||||
);
|
||||
},
|
||||
|
||||
_dataViewAction: function (
|
||||
type,
|
||||
isReadAction,
|
||||
byteOffset,
|
||||
littleEndian,
|
||||
value
|
||||
) {
|
||||
// Move the internal offset forward
|
||||
this._offset = byteOffset + dataTypes[type];
|
||||
return isReadAction
|
||||
? this._view["get" + type](byteOffset, littleEndian)
|
||||
: this._view["set" + type](byteOffset, value, littleEndian);
|
||||
},
|
||||
|
||||
_nodeBufferAction: function (
|
||||
type,
|
||||
isReadAction,
|
||||
byteOffset,
|
||||
littleEndian,
|
||||
value
|
||||
) {
|
||||
// Move the internal offset forward
|
||||
this._offset = byteOffset + dataTypes[type];
|
||||
var nodeName =
|
||||
nodeNaming[type] +
|
||||
(type === "Int8" || type === "Uint8" ? "" : littleEndian ? "LE" : "BE");
|
||||
byteOffset += this.byteOffset;
|
||||
return isReadAction
|
||||
? this.buffer["read" + nodeName](byteOffset)
|
||||
: this.buffer["write" + nodeName](value, byteOffset);
|
||||
},
|
||||
|
||||
_arrayBufferAction: function (
|
||||
type,
|
||||
isReadAction,
|
||||
byteOffset,
|
||||
littleEndian,
|
||||
value
|
||||
) {
|
||||
var size = dataTypes[type],
|
||||
TypedArray = globalThis[type + "Array"],
|
||||
typedArray;
|
||||
|
||||
littleEndian = defined(littleEndian, this._littleEndian);
|
||||
|
||||
// ArrayBuffer: we use a typed array of size 1 from original buffer if alignment is good and from slice when it's not
|
||||
if (
|
||||
size === 1 ||
|
||||
((this.byteOffset + byteOffset) % size === 0 && littleEndian)
|
||||
) {
|
||||
typedArray = new TypedArray(this.buffer, this.byteOffset + byteOffset, 1);
|
||||
this._offset = byteOffset + size;
|
||||
return isReadAction ? typedArray[0] : (typedArray[0] = value);
|
||||
} else {
|
||||
var bytes = new Uint8Array(
|
||||
isReadAction
|
||||
? this.getBytes(size, byteOffset, littleEndian, true)
|
||||
: size
|
||||
);
|
||||
typedArray = new TypedArray(bytes.buffer, 0, 1);
|
||||
|
||||
if (isReadAction) {
|
||||
return typedArray[0];
|
||||
} else {
|
||||
typedArray[0] = value;
|
||||
this._setBytes(byteOffset, bytes, littleEndian);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_arrayAction: function (type, isReadAction, byteOffset, littleEndian, value) {
|
||||
return isReadAction
|
||||
? this["_get" + type](byteOffset, littleEndian)
|
||||
: this["_set" + type](byteOffset, value, littleEndian);
|
||||
},
|
||||
|
||||
// Helpers
|
||||
|
||||
_getBytes: function (length, byteOffset, littleEndian) {
|
||||
littleEndian = defined(littleEndian, this._littleEndian);
|
||||
byteOffset = defined(byteOffset, this._offset);
|
||||
length = defined(length, this.byteLength - byteOffset);
|
||||
|
||||
this._checkBounds(byteOffset, length);
|
||||
|
||||
byteOffset += this.byteOffset;
|
||||
|
||||
this._offset = byteOffset - this.byteOffset + length;
|
||||
|
||||
var result = this._isArrayBuffer
|
||||
? new Uint8Array(this.buffer, byteOffset, length)
|
||||
: (this.buffer.slice || Array.prototype.slice).call(
|
||||
this.buffer,
|
||||
byteOffset,
|
||||
byteOffset + length
|
||||
);
|
||||
|
||||
return littleEndian || length <= 1 ? result : arrayFrom(result).reverse();
|
||||
},
|
||||
|
||||
// wrapper for external calls (do not return inner buffer directly to prevent it's modifying)
|
||||
getBytes: function (length, byteOffset, littleEndian, toArray) {
|
||||
var result = this._getBytes(
|
||||
length,
|
||||
byteOffset,
|
||||
defined(littleEndian, true)
|
||||
);
|
||||
return toArray ? arrayFrom(result) : result;
|
||||
},
|
||||
|
||||
_setBytes: function (byteOffset, bytes, littleEndian) {
|
||||
var length = bytes.length;
|
||||
|
||||
// needed for Opera
|
||||
if (length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
littleEndian = defined(littleEndian, this._littleEndian);
|
||||
byteOffset = defined(byteOffset, this._offset);
|
||||
|
||||
this._checkBounds(byteOffset, length);
|
||||
|
||||
if (!littleEndian && length > 1) {
|
||||
bytes = arrayFrom(bytes, true).reverse();
|
||||
}
|
||||
|
||||
byteOffset += this.byteOffset;
|
||||
|
||||
if (this._isArrayBuffer) {
|
||||
new Uint8Array(this.buffer, byteOffset, length).set(bytes);
|
||||
} else {
|
||||
if (this._isNodeBuffer) {
|
||||
new Buffer(bytes).copy(this.buffer, byteOffset);
|
||||
} else {
|
||||
for (var i = 0; i < length; i++) {
|
||||
this.buffer[byteOffset + i] = bytes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._offset = byteOffset - this.byteOffset + length;
|
||||
},
|
||||
|
||||
setBytes: function (byteOffset, bytes, littleEndian) {
|
||||
this._setBytes(byteOffset, bytes, defined(littleEndian, true));
|
||||
},
|
||||
|
||||
getString: function (byteLength, byteOffset, encoding) {
|
||||
if (this._isNodeBuffer) {
|
||||
byteOffset = defined(byteOffset, this._offset);
|
||||
byteLength = defined(byteLength, this.byteLength - byteOffset);
|
||||
|
||||
this._checkBounds(byteOffset, byteLength);
|
||||
|
||||
this._offset = byteOffset + byteLength;
|
||||
return this.buffer.toString(
|
||||
encoding || "binary",
|
||||
this.byteOffset + byteOffset,
|
||||
this.byteOffset + this._offset
|
||||
);
|
||||
}
|
||||
var bytes = this._getBytes(byteLength, byteOffset, true),
|
||||
string = "";
|
||||
byteLength = bytes.length;
|
||||
for (var i = 0; i < byteLength; i++) {
|
||||
string += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
if (encoding === "utf8") {
|
||||
string = decodeURIComponent(escape(string));
|
||||
}
|
||||
return string;
|
||||
},
|
||||
|
||||
setString: function (byteOffset, subString, encoding) {
|
||||
if (this._isNodeBuffer) {
|
||||
byteOffset = defined(byteOffset, this._offset);
|
||||
this._checkBounds(byteOffset, subString.length);
|
||||
this._offset =
|
||||
byteOffset +
|
||||
this.buffer.write(
|
||||
subString,
|
||||
this.byteOffset + byteOffset,
|
||||
encoding || "binary"
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (encoding === "utf8") {
|
||||
subString = unescape(encodeURIComponent(subString));
|
||||
}
|
||||
this._setBytes(byteOffset, getCharCodes(subString), true);
|
||||
},
|
||||
|
||||
getChar: function (byteOffset) {
|
||||
return this.getString(1, byteOffset);
|
||||
},
|
||||
|
||||
setChar: function (byteOffset, character) {
|
||||
this.setString(byteOffset, character);
|
||||
},
|
||||
|
||||
tell: function () {
|
||||
return this._offset;
|
||||
},
|
||||
|
||||
seek: function (byteOffset) {
|
||||
this._checkBounds(byteOffset, 0);
|
||||
/* jshint boss: true */
|
||||
return (this._offset = byteOffset);
|
||||
},
|
||||
|
||||
skip: function (byteLength) {
|
||||
return this.seek(this._offset + byteLength);
|
||||
},
|
||||
|
||||
slice: function (start, end, forceCopy) {
|
||||
function normalizeOffset(offset, byteLength) {
|
||||
return offset < 0 ? offset + byteLength : offset;
|
||||
}
|
||||
|
||||
start = normalizeOffset(start, this.byteLength);
|
||||
end = normalizeOffset(defined(end, this.byteLength), this.byteLength);
|
||||
|
||||
return forceCopy
|
||||
? new jDataView(
|
||||
this.getBytes(end - start, start, true, true),
|
||||
undefined,
|
||||
undefined,
|
||||
this._littleEndian
|
||||
)
|
||||
: new jDataView(
|
||||
this.buffer,
|
||||
this.byteOffset + start,
|
||||
end - start,
|
||||
this._littleEndian
|
||||
);
|
||||
},
|
||||
|
||||
alignBy: function (byteCount) {
|
||||
this._bitOffset = 0;
|
||||
if (defined(byteCount, 1) !== 1) {
|
||||
return this.skip(byteCount - (this._offset % byteCount || byteCount));
|
||||
} else {
|
||||
return this._offset;
|
||||
}
|
||||
},
|
||||
|
||||
// Compatibility functions
|
||||
|
||||
_getFloat64: function (byteOffset, littleEndian) {
|
||||
var b = this._getBytes(8, byteOffset, littleEndian),
|
||||
sign = 1 - 2 * (b[7] >> 7),
|
||||
exponent = ((((b[7] << 1) & 0xff) << 3) | (b[6] >> 4)) - ((1 << 10) - 1),
|
||||
// Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead
|
||||
mantissa =
|
||||
(b[6] & 0x0f) * pow2(48) +
|
||||
b[5] * pow2(40) +
|
||||
b[4] * pow2(32) +
|
||||
b[3] * pow2(24) +
|
||||
b[2] * pow2(16) +
|
||||
b[1] * pow2(8) +
|
||||
b[0];
|
||||
|
||||
if (exponent === 1024) {
|
||||
if (mantissa !== 0) {
|
||||
return NaN;
|
||||
} else {
|
||||
return sign * Infinity;
|
||||
}
|
||||
}
|
||||
|
||||
if (exponent === -1023) {
|
||||
// Denormalized
|
||||
return sign * mantissa * pow2(-1022 - 52);
|
||||
}
|
||||
|
||||
return sign * (1 + mantissa * pow2(-52)) * pow2(exponent);
|
||||
},
|
||||
|
||||
_getFloat32: function (byteOffset, littleEndian) {
|
||||
var b = this._getBytes(4, byteOffset, littleEndian),
|
||||
sign = 1 - 2 * (b[3] >> 7),
|
||||
exponent = (((b[3] << 1) & 0xff) | (b[2] >> 7)) - 127,
|
||||
mantissa = ((b[2] & 0x7f) << 16) | (b[1] << 8) | b[0];
|
||||
|
||||
if (exponent === 128) {
|
||||
if (mantissa !== 0) {
|
||||
return NaN;
|
||||
} else {
|
||||
return sign * Infinity;
|
||||
}
|
||||
}
|
||||
|
||||
if (exponent === -127) {
|
||||
// Denormalized
|
||||
return sign * mantissa * pow2(-126 - 23);
|
||||
}
|
||||
|
||||
return sign * (1 + mantissa * pow2(-23)) * pow2(exponent);
|
||||
},
|
||||
|
||||
_get64: function (Type, byteOffset, littleEndian) {
|
||||
littleEndian = defined(littleEndian, this._littleEndian);
|
||||
byteOffset = defined(byteOffset, this._offset);
|
||||
|
||||
var parts = littleEndian ? [0, 4] : [4, 0];
|
||||
|
||||
for (var i = 0; i < 2; i++) {
|
||||
parts[i] = this.getUint32(byteOffset + parts[i], littleEndian);
|
||||
}
|
||||
|
||||
this._offset = byteOffset + 8;
|
||||
|
||||
return new Type(parts[0], parts[1]);
|
||||
},
|
||||
|
||||
getInt64: function (byteOffset, littleEndian) {
|
||||
return this._get64(Int64, byteOffset, littleEndian);
|
||||
},
|
||||
|
||||
getUint64: function (byteOffset, littleEndian) {
|
||||
return this._get64(Uint64, byteOffset, littleEndian);
|
||||
},
|
||||
|
||||
_getInt32: function (byteOffset, littleEndian) {
|
||||
var b = this._getBytes(4, byteOffset, littleEndian);
|
||||
return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0];
|
||||
},
|
||||
|
||||
_getUint32: function (byteOffset, littleEndian) {
|
||||
return this._getInt32(byteOffset, littleEndian) >>> 0;
|
||||
},
|
||||
|
||||
_getInt16: function (byteOffset, littleEndian) {
|
||||
return (this._getUint16(byteOffset, littleEndian) << 16) >> 16;
|
||||
},
|
||||
|
||||
_getUint16: function (byteOffset, littleEndian) {
|
||||
var b = this._getBytes(2, byteOffset, littleEndian);
|
||||
return (b[1] << 8) | b[0];
|
||||
},
|
||||
|
||||
_getInt8: function (byteOffset) {
|
||||
return (this._getUint8(byteOffset) << 24) >> 24;
|
||||
},
|
||||
|
||||
_getUint8: function (byteOffset) {
|
||||
return this._getBytes(1, byteOffset)[0];
|
||||
},
|
||||
|
||||
_getBitRangeData: function (bitLength, byteOffset) {
|
||||
var startBit = (defined(byteOffset, this._offset) << 3) + this._bitOffset,
|
||||
endBit = startBit + bitLength,
|
||||
start = startBit >>> 3,
|
||||
end = (endBit + 7) >>> 3,
|
||||
b = this._getBytes(end - start, start, true),
|
||||
wideValue = 0;
|
||||
|
||||
/* jshint boss: true */
|
||||
if ((this._bitOffset = endBit & 7)) {
|
||||
this._bitOffset -= 8;
|
||||
}
|
||||
|
||||
for (var i = 0, length = b.length; i < length; i++) {
|
||||
wideValue = (wideValue << 8) | b[i];
|
||||
}
|
||||
|
||||
return {
|
||||
start: start,
|
||||
bytes: b,
|
||||
wideValue: wideValue,
|
||||
};
|
||||
},
|
||||
|
||||
getSigned: function (bitLength, byteOffset) {
|
||||
var shift = 32 - bitLength;
|
||||
return (this.getUnsigned(bitLength, byteOffset) << shift) >> shift;
|
||||
},
|
||||
|
||||
getUnsigned: function (bitLength, byteOffset) {
|
||||
var value =
|
||||
this._getBitRangeData(bitLength, byteOffset).wideValue >>>
|
||||
-this._bitOffset;
|
||||
return bitLength < 32 ? value & ~(-1 << bitLength) : value;
|
||||
},
|
||||
|
||||
_setBinaryFloat: function (
|
||||
byteOffset,
|
||||
value,
|
||||
mantSize,
|
||||
expSize,
|
||||
littleEndian
|
||||
) {
|
||||
var signBit = value < 0 ? 1 : 0,
|
||||
exponent,
|
||||
mantissa,
|
||||
eMax = ~(-1 << (expSize - 1)),
|
||||
eMin = 1 - eMax;
|
||||
|
||||
if (value < 0) {
|
||||
value = -value;
|
||||
}
|
||||
|
||||
if (value === 0) {
|
||||
exponent = 0;
|
||||
mantissa = 0;
|
||||
} else if (isNaN(value)) {
|
||||
exponent = 2 * eMax + 1;
|
||||
mantissa = 1;
|
||||
} else if (value === Infinity) {
|
||||
exponent = 2 * eMax + 1;
|
||||
mantissa = 0;
|
||||
} else {
|
||||
exponent = Math.floor(Math.log(value) / Math.LN2);
|
||||
if (exponent >= eMin && exponent <= eMax) {
|
||||
mantissa = Math.floor((value * pow2(-exponent) - 1) * pow2(mantSize));
|
||||
exponent += eMax;
|
||||
} else {
|
||||
mantissa = Math.floor(value / pow2(eMin - mantSize));
|
||||
exponent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var b = [];
|
||||
while (mantSize >= 8) {
|
||||
b.push(mantissa % 256);
|
||||
mantissa = Math.floor(mantissa / 256);
|
||||
mantSize -= 8;
|
||||
}
|
||||
exponent = (exponent << mantSize) | mantissa;
|
||||
expSize += mantSize;
|
||||
while (expSize >= 8) {
|
||||
b.push(exponent & 0xff);
|
||||
exponent >>>= 8;
|
||||
expSize -= 8;
|
||||
}
|
||||
b.push((signBit << expSize) | exponent);
|
||||
|
||||
this._setBytes(byteOffset, b, littleEndian);
|
||||
},
|
||||
|
||||
_setFloat32: function (byteOffset, value, littleEndian) {
|
||||
this._setBinaryFloat(byteOffset, value, 23, 8, littleEndian);
|
||||
},
|
||||
|
||||
_setFloat64: function (byteOffset, value, littleEndian) {
|
||||
this._setBinaryFloat(byteOffset, value, 52, 11, littleEndian);
|
||||
},
|
||||
|
||||
_set64: function (Type, byteOffset, value, littleEndian) {
|
||||
if (!(value instanceof Type)) {
|
||||
value = Type.fromNumber(value);
|
||||
}
|
||||
|
||||
littleEndian = defined(littleEndian, this._littleEndian);
|
||||
byteOffset = defined(byteOffset, this._offset);
|
||||
|
||||
var parts = littleEndian ? { lo: 0, hi: 4 } : { lo: 4, hi: 0 };
|
||||
|
||||
for (var partName in parts) {
|
||||
this.setUint32(
|
||||
byteOffset + parts[partName],
|
||||
value[partName],
|
||||
littleEndian
|
||||
);
|
||||
}
|
||||
|
||||
this._offset = byteOffset + 8;
|
||||
},
|
||||
|
||||
setInt64: function (byteOffset, value, littleEndian) {
|
||||
this._set64(Int64, byteOffset, value, littleEndian);
|
||||
},
|
||||
|
||||
setUint64: function (byteOffset, value, littleEndian) {
|
||||
this._set64(Uint64, byteOffset, value, littleEndian);
|
||||
},
|
||||
|
||||
_setUint32: function (byteOffset, value, littleEndian) {
|
||||
this._setBytes(
|
||||
byteOffset,
|
||||
[value & 0xff, (value >>> 8) & 0xff, (value >>> 16) & 0xff, value >>> 24],
|
||||
littleEndian
|
||||
);
|
||||
},
|
||||
|
||||
_setUint16: function (byteOffset, value, littleEndian) {
|
||||
this._setBytes(
|
||||
byteOffset,
|
||||
[value & 0xff, (value >>> 8) & 0xff],
|
||||
littleEndian
|
||||
);
|
||||
},
|
||||
|
||||
_setUint8: function (byteOffset, value) {
|
||||
this._setBytes(byteOffset, [value & 0xff]);
|
||||
},
|
||||
|
||||
setUnsigned: function (byteOffset, value, bitLength) {
|
||||
var data = this._getBitRangeData(bitLength, byteOffset),
|
||||
wideValue = data.wideValue,
|
||||
b = data.bytes;
|
||||
|
||||
wideValue &= ~(~(-1 << bitLength) << -this._bitOffset); // clearing bit range before binary "or"
|
||||
wideValue |=
|
||||
(bitLength < 32 ? value & ~(-1 << bitLength) : value) << -this._bitOffset; // setting bits
|
||||
|
||||
for (var i = b.length - 1; i >= 0; i--) {
|
||||
b[i] = wideValue & 0xff;
|
||||
wideValue >>>= 8;
|
||||
}
|
||||
|
||||
this._setBytes(data.start, b, true);
|
||||
},
|
||||
};
|
||||
|
||||
var proto = jDataView.prototype;
|
||||
|
||||
for (var type in dataTypes) {
|
||||
(function (type) {
|
||||
proto["get" + type] = function (byteOffset, littleEndian) {
|
||||
return this._action(type, true, byteOffset, littleEndian);
|
||||
};
|
||||
proto["set" + type] = function (byteOffset, value, littleEndian) {
|
||||
this._action(type, false, byteOffset, littleEndian, value);
|
||||
};
|
||||
})(type);
|
||||
}
|
||||
|
||||
proto._setInt32 = proto._setUint32;
|
||||
proto._setInt16 = proto._setUint16;
|
||||
proto._setInt8 = proto._setUint8;
|
||||
proto.setSigned = proto.setUnsigned;
|
||||
|
||||
for (var method in proto) {
|
||||
if (method.slice(0, 3) === "set") {
|
||||
(function (type) {
|
||||
proto["write" + type] = function () {
|
||||
Array.prototype.unshift.call(arguments, undefined);
|
||||
this["set" + type].apply(this, arguments);
|
||||
};
|
||||
})(method.slice(3));
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof module !== "undefined" && typeof module.exports === "object") {
|
||||
module.exports = jDataView;
|
||||
} else if (typeof define === "function" && define.amd) {
|
||||
define([], function () {
|
||||
return jDataView;
|
||||
});
|
||||
} else {
|
||||
var oldGlobalThis = globalThis.jDataView;
|
||||
(globalThis.jDataView = jDataView).noConflict = function () {
|
||||
globalThis.jDataView = oldGlobalThis;
|
||||
return this;
|
||||
};
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
import { rgbToHex } from "../utils/rgbToHex";
|
||||
import { shadeColor } from "../utils/shadeColor";
|
||||
|
||||
function Stitch(x, y, flags, color) {
|
||||
this.flags = flags;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
function Color(r, g, b, description) {
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
const stitchTypes = {
|
||||
normal: 0,
|
||||
jump: 1,
|
||||
trim: 2,
|
||||
stop: 4,
|
||||
end: 8,
|
||||
};
|
||||
|
||||
function Pattern() {
|
||||
this.colors = [];
|
||||
this.stitches = [];
|
||||
this.hoop = {};
|
||||
this.lastX = 0;
|
||||
this.lastY = 0;
|
||||
this.top = 0;
|
||||
this.bottom = 0;
|
||||
this.left = 0;
|
||||
this.right = 0;
|
||||
this.currentColorIndex = 0;
|
||||
}
|
||||
|
||||
Pattern.prototype.addColorRgb = function (r, g, b, description) {
|
||||
this.colors[this.colors.length] = new Color(r, g, b, description);
|
||||
};
|
||||
|
||||
Pattern.prototype.addColor = function (color) {
|
||||
this.colors[this.colors.length] = color;
|
||||
};
|
||||
|
||||
Pattern.prototype.addStitchAbs = function (x, y, flags, isAutoColorIndex) {
|
||||
if ((flags & stitchTypes.end) === stitchTypes.end) {
|
||||
this.calculateBoundingBox();
|
||||
this.fixColorCount();
|
||||
}
|
||||
if (
|
||||
(flags & stitchTypes.stop) === stitchTypes.stop &&
|
||||
this.stitches.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if ((flags & stitchTypes.stop) === stitchTypes.stop && isAutoColorIndex) {
|
||||
this.currentColorIndex += 1;
|
||||
}
|
||||
this.stitches[this.stitches.length] = new Stitch(
|
||||
x,
|
||||
y,
|
||||
flags,
|
||||
this.currentColorIndex
|
||||
);
|
||||
};
|
||||
|
||||
Pattern.prototype.addStitchRel = function (dx, dy, flags, isAutoColorIndex) {
|
||||
if (this.stitches.length !== 0) {
|
||||
let nx = this.lastX + dx,
|
||||
ny = this.lastY + dy;
|
||||
this.lastX = nx;
|
||||
this.lastY = ny;
|
||||
this.addStitchAbs(nx, ny, flags, isAutoColorIndex);
|
||||
} else {
|
||||
this.addStitchAbs(dx, dy, flags, isAutoColorIndex);
|
||||
}
|
||||
};
|
||||
|
||||
Pattern.prototype.calculateBoundingBox = function () {
|
||||
let i = 0,
|
||||
stitchCount = this.stitches.length,
|
||||
pt;
|
||||
if (stitchCount === 0) {
|
||||
this.bottom = 1;
|
||||
this.right = 1;
|
||||
return;
|
||||
}
|
||||
this.left = 99999;
|
||||
this.top = 99999;
|
||||
this.right = -99999;
|
||||
this.bottom = -99999;
|
||||
|
||||
for (i = 0; i < stitchCount; i += 1) {
|
||||
pt = this.stitches[i];
|
||||
if (!(pt.flags & stitchTypes.trim)) {
|
||||
this.left = this.left < pt.x ? this.left : pt.x;
|
||||
this.top = this.top < pt.y ? this.top : pt.y;
|
||||
this.right = this.right > pt.x ? this.right : pt.x;
|
||||
this.bottom = this.bottom > pt.y ? this.bottom : pt.y;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Pattern.prototype.moveToPositive = function () {
|
||||
let i = 0,
|
||||
stitchCount = this.stitches.length;
|
||||
for (i = 0; i < stitchCount; i += 1) {
|
||||
this.stitches[i].x -= this.left;
|
||||
this.stitches[i].y -= this.top;
|
||||
}
|
||||
this.right -= this.left;
|
||||
this.left = 0;
|
||||
this.bottom -= this.top;
|
||||
this.top = 0;
|
||||
};
|
||||
|
||||
Pattern.prototype.invertPatternVertical = function () {
|
||||
let i = 0,
|
||||
temp = -this.top,
|
||||
stitchCount = this.stitches.length;
|
||||
for (i = 0; i < stitchCount; i += 1) {
|
||||
this.stitches[i].y = -this.stitches[i].y;
|
||||
}
|
||||
this.top = -this.bottom;
|
||||
this.bottom = temp;
|
||||
};
|
||||
|
||||
Pattern.prototype.addColorRandom = function () {
|
||||
this.colors[this.colors.length] = new Color(
|
||||
Math.round(Math.random() * 256),
|
||||
Math.round(Math.random() * 256),
|
||||
Math.round(Math.random() * 256),
|
||||
"random"
|
||||
);
|
||||
};
|
||||
|
||||
Pattern.prototype.fixColorCount = function () {
|
||||
let maxColorIndex = 0,
|
||||
stitchCount = this.stitches.length,
|
||||
i;
|
||||
for (i = 0; i < stitchCount; i += 1) {
|
||||
maxColorIndex = Math.max(maxColorIndex, this.stitches[i].color);
|
||||
}
|
||||
while (this.colors.length <= maxColorIndex) {
|
||||
this.addColorRandom();
|
||||
}
|
||||
this.colors.splice(maxColorIndex + 1, this.colors.length - maxColorIndex - 1);
|
||||
};
|
||||
|
||||
Pattern.prototype.drawShapeTo = function (canvas) {
|
||||
canvas.width = this.right;
|
||||
canvas.height = this.bottom;
|
||||
|
||||
let gradient, tx, ty;
|
||||
let lastStitch = this.stitches[0];
|
||||
let gWidth = 100;
|
||||
if (canvas.getContext) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.lineWidth = 3;
|
||||
ctx.lineJoin = "round";
|
||||
|
||||
let color = this.colors[this.stitches[0].color];
|
||||
for (let i = 0; i < this.stitches.length; i++) {
|
||||
const currentStitch = this.stitches[i];
|
||||
if (i > 0) lastStitch = this.stitches[i - 1];
|
||||
tx = currentStitch.x - lastStitch.x;
|
||||
ty = currentStitch.y - lastStitch.y;
|
||||
|
||||
gWidth = Math.sqrt(tx * tx + ty * ty);
|
||||
gradient = ctx.createRadialGradient(
|
||||
currentStitch.x - tx,
|
||||
currentStitch.y - ty,
|
||||
0,
|
||||
currentStitch.x - tx,
|
||||
currentStitch.y - ty,
|
||||
gWidth * 1.4
|
||||
);
|
||||
|
||||
gradient.addColorStop("0", shadeColor(rgbToHex(color), -60));
|
||||
gradient.addColorStop("0.05", rgbToHex(color));
|
||||
gradient.addColorStop("0.5", shadeColor(rgbToHex(color), 60));
|
||||
gradient.addColorStop("0.9", rgbToHex(color));
|
||||
gradient.addColorStop("1.0", shadeColor(rgbToHex(color), -60));
|
||||
|
||||
ctx.strokeStyle = gradient;
|
||||
if (
|
||||
currentStitch.flags === stitchTypes.jump ||
|
||||
currentStitch.flags === stitchTypes.trim ||
|
||||
currentStitch.flags === stitchTypes.stop
|
||||
) {
|
||||
color = this.colors[currentStitch.color];
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle =
|
||||
"rgba(" + color.r + "," + color.g + "," + color.b + ",0)";
|
||||
ctx.moveTo(currentStitch.x, currentStitch.y);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(lastStitch.x, lastStitch.y);
|
||||
ctx.lineTo(currentStitch.x, currentStitch.y);
|
||||
ctx.stroke();
|
||||
lastStitch = currentStitch;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Pattern.prototype.drawColorsTo = function (colorContainer) {
|
||||
this.colors.forEach((color) => {
|
||||
colorContainer.innerHTML += `<div style='background-color: rgb(${color.r}, ${color.g}, ${color.b}); height: 25px; width: 25px; border: 1px solid #000000; border-radius: 16px;'></div>`;
|
||||
});
|
||||
};
|
||||
|
||||
Pattern.prototype.drawStitchesCountTo = function (stitchesContainer, stitchesString) {
|
||||
stitchesContainer.innerHTML += `<div><strong>${stitchesString}:</strong> ${this.stitches.length} </div>`;
|
||||
};
|
||||
|
||||
Pattern.prototype.drawSizeValuesTo = function (sizeContainer, dimensionsString) {
|
||||
sizeContainer.innerHTML += `<div><strong>${dimensionsString}:</strong> ${Math.round(
|
||||
this.right / 10
|
||||
)}mm x ${Math.round(this.bottom / 10)}mm </div>`;
|
||||
};
|
||||
|
||||
export { Pattern, Color, stitchTypes };
|
|
@ -1,107 +0,0 @@
|
|||
// @ts-nocheck
|
||||
import { stitchTypes } from "../file-renderer/pattern";
|
||||
|
||||
function decodeExp(b2) {
|
||||
let returnCode = 0;
|
||||
if (b2 === 0xf3) {
|
||||
return stitchTypes.end;
|
||||
}
|
||||
if ((b2 & 0xc3) === 0xc3) {
|
||||
return stitchTypes.trim | stitchTypes.stop;
|
||||
}
|
||||
if (b2 & 0x80) {
|
||||
returnCode |= stitchTypes.trim;
|
||||
}
|
||||
if (b2 & 0x40) {
|
||||
returnCode |= stitchTypes.stop;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
export function dstRead(file, pattern) {
|
||||
let flags,
|
||||
x,
|
||||
y,
|
||||
prevJump = false,
|
||||
thisJump = false,
|
||||
b = [],
|
||||
byteCount = file.byteLength;
|
||||
file.seek(512);
|
||||
|
||||
while (file.tell() < byteCount - 3) {
|
||||
b[0] = file.getUint8();
|
||||
b[1] = file.getUint8();
|
||||
b[2] = file.getUint8();
|
||||
x = 0;
|
||||
y = 0;
|
||||
if (b[0] & 0x01) {
|
||||
x += 1;
|
||||
}
|
||||
if (b[0] & 0x02) {
|
||||
x -= 1;
|
||||
}
|
||||
if (b[0] & 0x04) {
|
||||
x += 9;
|
||||
}
|
||||
if (b[0] & 0x08) {
|
||||
x -= 9;
|
||||
}
|
||||
if (b[0] & 0x80) {
|
||||
y += 1;
|
||||
}
|
||||
if (b[0] & 0x40) {
|
||||
y -= 1;
|
||||
}
|
||||
if (b[0] & 0x20) {
|
||||
y += 9;
|
||||
}
|
||||
if (b[0] & 0x10) {
|
||||
y -= 9;
|
||||
}
|
||||
if (b[1] & 0x01) {
|
||||
x += 3;
|
||||
}
|
||||
if (b[1] & 0x02) {
|
||||
x -= 3;
|
||||
}
|
||||
if (b[1] & 0x04) {
|
||||
x += 27;
|
||||
}
|
||||
if (b[1] & 0x08) {
|
||||
x -= 27;
|
||||
}
|
||||
if (b[1] & 0x80) {
|
||||
y += 3;
|
||||
}
|
||||
if (b[1] & 0x40) {
|
||||
y -= 3;
|
||||
}
|
||||
if (b[1] & 0x20) {
|
||||
y += 27;
|
||||
}
|
||||
if (b[1] & 0x10) {
|
||||
y -= 27;
|
||||
}
|
||||
if (b[2] & 0x04) {
|
||||
x += 81;
|
||||
}
|
||||
if (b[2] & 0x08) {
|
||||
x -= 81;
|
||||
}
|
||||
if (b[2] & 0x20) {
|
||||
y += 81;
|
||||
}
|
||||
if (b[2] & 0x10) {
|
||||
y -= 81;
|
||||
}
|
||||
flags = decodeExp(b[2]);
|
||||
thisJump = flags & stitchTypes.jump;
|
||||
if (prevJump) {
|
||||
flags |= stitchTypes.jump;
|
||||
}
|
||||
pattern.addStitchRel(x, y, flags, true);
|
||||
prevJump = thisJump;
|
||||
}
|
||||
pattern.addStitchRel(0, 0, stitchTypes.end, true);
|
||||
pattern.invertPatternVertical();
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
import { stitchTypes } from "../file-renderer/pattern";
|
||||
|
||||
function expDecode(input) {
|
||||
return input > 128 ? -(~input & 255) - 1 : input;
|
||||
}
|
||||
|
||||
export function expRead(file, pattern) {
|
||||
let b0 = 0,
|
||||
b1 = 0,
|
||||
dx = 0,
|
||||
dy = 0,
|
||||
flags = 0,
|
||||
i = 0,
|
||||
byteCount = file.byteLength;
|
||||
while (i < byteCount) {
|
||||
flags = stitchTypes.normal;
|
||||
b0 = file.getInt8(i);
|
||||
i += 1;
|
||||
b1 = file.getInt8(i);
|
||||
i += 1;
|
||||
if (b0 === -128) {
|
||||
if (b1 & 1) {
|
||||
b0 = file.getInt8(i);
|
||||
i += 1;
|
||||
b1 = file.getInt8(i);
|
||||
i += 1;
|
||||
flags = stitchTypes.stop;
|
||||
} else if (b1 === 2 || b1 === 4) {
|
||||
b0 = file.getInt8(i);
|
||||
i += 1;
|
||||
b1 = file.getInt8(i);
|
||||
i += 1;
|
||||
flags = stitchTypes.trim;
|
||||
} else if (b1 === -128) {
|
||||
b0 = file.getInt8(i);
|
||||
i += 1;
|
||||
b1 = file.getInt8(i);
|
||||
i += 1;
|
||||
b0 = 0;
|
||||
b1 = 0;
|
||||
flags = stitchTypes.trim;
|
||||
}
|
||||
}
|
||||
dx = expDecode(b0);
|
||||
dy = expDecode(b1);
|
||||
pattern.addStitchRel(dx, dy, flags, true);
|
||||
}
|
||||
pattern.addStitchRel(0, 0, stitchTypes.end);
|
||||
pattern.invertPatternVertical();
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import { dstRead } from "./dst";
|
||||
import { expRead } from "./exp";
|
||||
import { jefRead } from "./jef";
|
||||
import { pecRead } from "./pec";
|
||||
import { pesRead } from "./pes";
|
||||
|
||||
const supportedFormats = {
|
||||
pes: { ext: ".pes", read: pesRead },
|
||||
dst: { ext: ".dst", read: dstRead },
|
||||
pec: { ext: ".pec", read: pecRead },
|
||||
jef: { ext: ".jef", read: jefRead },
|
||||
exp: { ext: ".exp", read: expRead },
|
||||
};
|
||||
|
||||
export { supportedFormats };
|
|
@ -1,133 +0,0 @@
|
|||
import { Color, stitchTypes } from "../file-renderer/pattern";
|
||||
|
||||
const colors = [
|
||||
new Color(0, 0, 0, "Black"),
|
||||
new Color(0, 0, 0, "Black"),
|
||||
new Color(255, 255, 255, "White"),
|
||||
new Color(255, 255, 23, "Yellow"),
|
||||
new Color(250, 160, 96, "Orange"),
|
||||
new Color(92, 118, 73, "Olive Green"),
|
||||
new Color(64, 192, 48, "Green"),
|
||||
new Color(101, 194, 200, "Sky"),
|
||||
new Color(172, 128, 190, "Purple"),
|
||||
new Color(245, 188, 203, "Pink"),
|
||||
new Color(255, 0, 0, "Red"),
|
||||
new Color(192, 128, 0, "Brown"),
|
||||
new Color(0, 0, 240, "Blue"),
|
||||
new Color(228, 195, 93, "Gold"),
|
||||
new Color(165, 42, 42, "Dark Brown"),
|
||||
new Color(213, 176, 212, "Pale Violet"),
|
||||
new Color(252, 242, 148, "Pale Yellow"),
|
||||
new Color(240, 208, 192, "Pale Pink"),
|
||||
new Color(255, 192, 0, "Peach"),
|
||||
new Color(201, 164, 128, "Beige"),
|
||||
new Color(155, 61, 75, "Wine Red"),
|
||||
new Color(160, 184, 204, "Pale Sky"),
|
||||
new Color(127, 194, 28, "Yellow Green"),
|
||||
new Color(185, 185, 185, "Silver Grey"),
|
||||
new Color(160, 160, 160, "Grey"),
|
||||
new Color(152, 214, 189, "Pale Aqua"),
|
||||
new Color(184, 240, 240, "Baby Blue"),
|
||||
new Color(54, 139, 160, "Powder Blue"),
|
||||
new Color(79, 131, 171, "Bright Blue"),
|
||||
new Color(56, 106, 145, "Slate Blue"),
|
||||
new Color(0, 32, 107, "Nave Blue"),
|
||||
new Color(229, 197, 202, "Salmon Pink"),
|
||||
new Color(249, 103, 107, "Coral"),
|
||||
new Color(227, 49, 31, "Burnt Orange"),
|
||||
new Color(226, 161, 136, "Cinnamon"),
|
||||
new Color(181, 148, 116, "Umber"),
|
||||
new Color(228, 207, 153, "Blonde"),
|
||||
new Color(225, 203, 0, "Sunflower"),
|
||||
new Color(225, 173, 212, "Orchid Pink"),
|
||||
new Color(195, 0, 126, "Peony Purple"),
|
||||
new Color(128, 0, 75, "Burgundy"),
|
||||
new Color(160, 96, 176, "Royal Purple"),
|
||||
new Color(192, 64, 32, "Cardinal Red"),
|
||||
new Color(202, 224, 192, "Opal Green"),
|
||||
new Color(137, 152, 86, "Moss Green"),
|
||||
new Color(0, 170, 0, "Meadow Green"),
|
||||
new Color(33, 138, 33, "Dark Green"),
|
||||
new Color(93, 174, 148, "Aquamarine"),
|
||||
new Color(76, 191, 143, "Emerald Green"),
|
||||
new Color(0, 119, 114, "Peacock Green"),
|
||||
new Color(112, 112, 112, "Dark Grey"),
|
||||
new Color(242, 255, 255, "Ivory White"),
|
||||
new Color(177, 88, 24, "Hazel"),
|
||||
new Color(203, 138, 7, "Toast"),
|
||||
new Color(247, 146, 123, "Salmon"),
|
||||
new Color(152, 105, 45, "Cocoa Brown"),
|
||||
new Color(162, 113, 72, "Sienna"),
|
||||
new Color(123, 85, 74, "Sepia"),
|
||||
new Color(79, 57, 70, "Dark Sepia"),
|
||||
new Color(82, 58, 151, "Violet Blue"),
|
||||
new Color(0, 0, 160, "Blue Ink"),
|
||||
new Color(0, 150, 222, "Solar Blue"),
|
||||
new Color(178, 221, 83, "Green Dust"),
|
||||
new Color(250, 143, 187, "Crimson"),
|
||||
new Color(222, 100, 158, "Floral Pink"),
|
||||
new Color(181, 80, 102, "Wine"),
|
||||
new Color(94, 87, 71, "Olive Drab"),
|
||||
new Color(76, 136, 31, "Meadow"),
|
||||
new Color(228, 220, 121, "Mustard"),
|
||||
new Color(203, 138, 26, "Yellow Ochre"),
|
||||
new Color(198, 170, 66, "Old Gold"),
|
||||
new Color(236, 176, 44, "Honeydew"),
|
||||
new Color(248, 128, 64, "Tangerine"),
|
||||
new Color(255, 229, 5, "Canary Yellow"),
|
||||
new Color(250, 122, 122, "Vermillion"),
|
||||
new Color(107, 224, 0, "Bright Green"),
|
||||
new Color(56, 108, 174, "Ocean Blue"),
|
||||
new Color(227, 196, 180, "Beige Grey"),
|
||||
new Color(227, 172, 129, "Bamboo"),
|
||||
];
|
||||
|
||||
const jefDecode = (byte) => (byte >= 0x80 ? -(~byte & 0xff) - 1 : byte);
|
||||
const isSpecialStitch = (byte) => byte === 0x80;
|
||||
const isStopOrTrim = (byte) => (byte & 0x01) !== 0 || byte === 0x02 || byte === 0x04;
|
||||
const isEndOfPattern = (byte) => byte === 0x10;
|
||||
const isStop = (byte) => byte & 0x01;
|
||||
const readStitchData = (file) => ({ byte1: file.getUint8(), byte2: file.getUint8() });
|
||||
|
||||
const addColorsToPattern = (file, pattern, colorCount) => {
|
||||
for (let i = 0; i < colorCount; i++) {
|
||||
pattern.addColor(colors[file.getUint32(file.tell(), true) % colors.length]);
|
||||
}
|
||||
};
|
||||
|
||||
const determineStitchType = (file, byte1, byte2) => {
|
||||
if (isSpecialStitch(byte1)) {
|
||||
if (isStopOrTrim(byte2)) {
|
||||
return { type: isStop(byte2) ? stitchTypes.stop : stitchTypes.trim, byte1: file.getUint8(), byte2: file.getUint8() };
|
||||
} else if (isEndOfPattern(byte2)) {
|
||||
return { type: stitchTypes.end, byte1: 0, byte2: 0, end: true };
|
||||
}
|
||||
}
|
||||
return { type: stitchTypes.normal, byte1, byte2 };
|
||||
}
|
||||
|
||||
const processStitches = (file, pattern, stitchCount) => {
|
||||
let stitchesProcessed = 0;
|
||||
while (stitchesProcessed < stitchCount + 100) {
|
||||
let { byte1, byte2 } = readStitchData(file);
|
||||
let { type, byte1: decodedByte1, byte2: decodedByte2, end } = determineStitchType(file, byte1, byte2);
|
||||
pattern.addStitchRel(jefDecode(decodedByte1), jefDecode(decodedByte2), type, true);
|
||||
if (end) break;
|
||||
stitchesProcessed++;
|
||||
}
|
||||
}
|
||||
|
||||
export function jefRead(file, pattern) {
|
||||
file.seek(24);
|
||||
const colorCount = file.getInt32(file.tell(), true);
|
||||
const stitchCount = file.getInt32(file.tell(), true);
|
||||
file.seek(file.tell() + 84);
|
||||
|
||||
addColorsToPattern(file, pattern, colorCount);
|
||||
file.seek(file.tell() + (6 - colorCount) * 4);
|
||||
|
||||
processStitches(file, pattern, stitchCount);
|
||||
pattern.invertPatternVertical();
|
||||
}
|
||||
|
||||
export const jefColors = colors;
|
|
@ -1,13 +0,0 @@
|
|||
import { pecColors, pecReadStitches } from "./pes";
|
||||
|
||||
export function pecRead(file, pattern) {
|
||||
let colorChanges, i;
|
||||
file.seek(0x38);
|
||||
colorChanges = file.getUint8();
|
||||
for (i = 0; i <= colorChanges; i++) {
|
||||
pattern.addColor(pecColors[file.getUint8() % 65]);
|
||||
}
|
||||
file.seek(0x21c);
|
||||
pecReadStitches(file, pattern);
|
||||
return true;
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
import { Color, stitchTypes } from "../file-renderer/pattern";
|
||||
|
||||
const namedColors = [
|
||||
new Color(0, 0, 0, "Unknown"),
|
||||
new Color(14, 31, 124, "Prussian Blue"),
|
||||
new Color(10, 85, 163, "Blue"),
|
||||
new Color(0, 135, 119, "Teal Green"),
|
||||
new Color(75, 107, 175, "Cornflower Blue"),
|
||||
new Color(237, 23, 31, "Red"),
|
||||
new Color(209, 92, 0, "Reddish Brown"),
|
||||
new Color(145, 54, 151, "Magenta"),
|
||||
new Color(228, 154, 203, "Light Lilac"),
|
||||
new Color(145, 95, 172, "Lilac"),
|
||||
new Color(158, 214, 125, "Mint Green"),
|
||||
new Color(232, 169, 0, "Deep Gold"),
|
||||
new Color(254, 186, 53, "Orange"),
|
||||
new Color(255, 255, 0, "Yellow"),
|
||||
new Color(112, 188, 31, "Lime Green"),
|
||||
new Color(186, 152, 0, "Brass"),
|
||||
new Color(168, 168, 168, "Silver"),
|
||||
new Color(125, 111, 0, "Russet Brown"),
|
||||
new Color(255, 255, 179, "Cream Brown"),
|
||||
new Color(79, 85, 86, "Pewter"),
|
||||
new Color(0, 0, 0, "Black"),
|
||||
new Color(11, 61, 145, "Ultramarine"),
|
||||
new Color(119, 1, 118, "Royal Purple"),
|
||||
new Color(41, 49, 51, "Dark Gray"),
|
||||
new Color(42, 19, 1, "Dark Brown"),
|
||||
new Color(246, 74, 138, "Deep Rose"),
|
||||
new Color(178, 118, 36, "Light Brown"),
|
||||
new Color(252, 187, 197, "Salmon Pink"),
|
||||
new Color(254, 55, 15, "Vermillion"),
|
||||
new Color(240, 240, 240, "White"),
|
||||
new Color(106, 28, 138, "Violet"),
|
||||
new Color(168, 221, 196, "Seacrest"),
|
||||
new Color(37, 132, 187, "Sky Blue"),
|
||||
new Color(254, 179, 67, "Pumpkin"),
|
||||
new Color(255, 243, 107, "Cream Yellow"),
|
||||
new Color(208, 166, 96, "Khaki"),
|
||||
new Color(209, 84, 0, "Clay Brown"),
|
||||
new Color(102, 186, 73, "Leaf Green"),
|
||||
new Color(19, 74, 70, "Peacock Blue"),
|
||||
new Color(135, 135, 135, "Gray"),
|
||||
new Color(216, 204, 198, "Warm Gray"),
|
||||
new Color(67, 86, 7, "Dark Olive"),
|
||||
new Color(253, 217, 222, "Flesh Pink"),
|
||||
new Color(249, 147, 188, "Pink"),
|
||||
new Color(0, 56, 34, "Deep Green"),
|
||||
new Color(178, 175, 212, "Lavender"),
|
||||
new Color(104, 106, 176, "Wisteria Violet"),
|
||||
new Color(239, 227, 185, "Beige"),
|
||||
new Color(247, 56, 102, "Carmine"),
|
||||
new Color(181, 75, 100, "Amber Red"),
|
||||
new Color(19, 43, 26, "Olive Green"),
|
||||
new Color(199, 1, 86, "Dark Fuschia"),
|
||||
new Color(254, 158, 50, "Tangerine"),
|
||||
new Color(168, 222, 235, "Light Blue"),
|
||||
new Color(0, 103, 62, "Emerald Green"),
|
||||
new Color(78, 41, 144, "Purple"),
|
||||
new Color(47, 126, 32, "Moss Green"),
|
||||
new Color(255, 204, 204, "Flesh Pink"),
|
||||
new Color(255, 217, 17, "Harvest Gold"),
|
||||
new Color(9, 91, 166, "Electric Blue"),
|
||||
new Color(240, 249, 112, "Lemon Yellow"),
|
||||
new Color(227, 243, 91, "Fresh Green"),
|
||||
new Color(255, 153, 0, "Orange"),
|
||||
new Color(255, 240, 141, "Cream Yellow"),
|
||||
new Color(255, 200, 200, "Applique"),
|
||||
];
|
||||
|
||||
function readPecStitches(file, pattern) {
|
||||
let stitchNumber = 0;
|
||||
const byteCount = file.byteLength;
|
||||
|
||||
while (file.tell() < byteCount) {
|
||||
let [xOffset, yOffset] = [file.getUint8(), file.getUint8()];
|
||||
let stitchType = stitchTypes.normal;
|
||||
|
||||
if (isEndStitch(xOffset, yOffset)) {
|
||||
pattern.addStitchRel(0, 0, stitchTypes.end, true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (isStopStitch(xOffset, yOffset)) {
|
||||
file.getInt8(); // Skip extra byte
|
||||
pattern.addStitchRel(0, 0, stitchTypes.stop, true);
|
||||
stitchNumber++;
|
||||
continue;
|
||||
}
|
||||
|
||||
stitchType = determineStitchType(xOffset, yOffset);
|
||||
[xOffset, yOffset] = decodeCoordinates(xOffset, yOffset, file);
|
||||
|
||||
pattern.addStitchRel(xOffset, yOffset, stitchType, true);
|
||||
stitchNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
function isEndStitch(xOffset, yOffset) {
|
||||
return xOffset === 0xff && yOffset === 0x00;
|
||||
}
|
||||
|
||||
function isStopStitch(xOffset, yOffset) {
|
||||
return xOffset === 0xfe && yOffset === 0xb0;
|
||||
}
|
||||
|
||||
function determineStitchType(xOffset, yOffset) {
|
||||
if (xOffset & 0x80) {
|
||||
if (xOffset & 0x20) return stitchTypes.trim;
|
||||
if (xOffset & 0x10) return stitchTypes.jump;
|
||||
}
|
||||
if (yOffset & 0x80) {
|
||||
if (yOffset & 0x20) return stitchTypes.trim;
|
||||
if (yOffset & 0x10) return stitchTypes.jump;
|
||||
}
|
||||
return stitchTypes.normal;
|
||||
}
|
||||
|
||||
function decodeCoordinates(xOffset, yOffset, file) {
|
||||
if (xOffset & 0x80) {
|
||||
xOffset = ((xOffset & 0x0f) << 8) + yOffset;
|
||||
if (xOffset & 0x800) xOffset -= 0x1000;
|
||||
yOffset = file.getUint8();
|
||||
} else if (xOffset >= 0x40) {
|
||||
xOffset -= 0x80;
|
||||
}
|
||||
|
||||
if (yOffset & 0x80) {
|
||||
yOffset = ((yOffset & 0x0f) << 8) + file.getUint8();
|
||||
if (yOffset & 0x800) yOffset -= 0x1000;
|
||||
} else if (yOffset > 0x3f) {
|
||||
yOffset -= 0x80;
|
||||
}
|
||||
|
||||
return [xOffset, yOffset];
|
||||
}
|
||||
|
||||
export function pesRead(file, pattern) {
|
||||
const pecStart = file.getInt32(8, true);
|
||||
file.seek(pecStart + 48);
|
||||
|
||||
const numColors = file.getInt8() + 1;
|
||||
for (let i = 0; i < numColors; i++) {
|
||||
pattern.addColor(namedColors[file.getInt8()]);
|
||||
}
|
||||
|
||||
file.seek(pecStart + 532);
|
||||
readPecStitches(file, pattern);
|
||||
pattern.addStitchRel(0, 0, stitchTypes.end);
|
||||
}
|
||||
|
||||
export const pecReadStitches = readPecStitches;
|
||||
export const pecColors = namedColors;
|
|
@ -1,47 +0,0 @@
|
|||
import { derived, writable } from "svelte/store";
|
||||
import translations from "./translations";
|
||||
|
||||
const storedLocale = localStorage.getItem("locale");
|
||||
const browserLocale = navigator.language || "en";
|
||||
const [baseLang] = browserLocale.split("-");
|
||||
|
||||
export const DEFAULT_LOCALE =
|
||||
storedLocale && translations[storedLocale] ? storedLocale :
|
||||
translations[browserLocale] ? browserLocale :
|
||||
translations[baseLang] ? baseLang :
|
||||
"en";
|
||||
|
||||
export const locale = writable(DEFAULT_LOCALE);
|
||||
|
||||
locale.subscribe((value) => {
|
||||
if (value) localStorage.setItem("locale", value);
|
||||
});
|
||||
|
||||
export const locales = Object.entries(translations).map(([key, lang]) => [key, lang.name]);
|
||||
|
||||
function translate(locale, key, vars = {}) {
|
||||
if (!key) throw new Error("Translation key is required.");
|
||||
|
||||
const fallbackLocale = "en";
|
||||
const validLocale = translations[locale]
|
||||
? locale
|
||||
: translations[baseLang]
|
||||
? baseLang
|
||||
: fallbackLocale;
|
||||
|
||||
let text = translations[validLocale][key] || translations[fallbackLocale][key];
|
||||
|
||||
if (!text) {
|
||||
console.error(`Missing translation for key "${key}" in locale "${validLocale}".`);
|
||||
return key;
|
||||
}
|
||||
|
||||
return Object.entries(vars).reduce(
|
||||
(str, [varKey, value]) => str.replaceAll(`{{${varKey}}}`, value),
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
export const t = derived(locale, ($locale) => (key, vars = {}) =>
|
||||
translate($locale, key, vars)
|
||||
);
|
|
@ -1,124 +0,0 @@
|
|||
export default {
|
||||
en: {
|
||||
"head.title": "Free Online Embroidery File Viewer – Open PES, DST, EXP & More",
|
||||
"head.description": "View multiple embroidery files online for free! Open PES, DST, EXP, JEF & more without software. Upload and preview multiple files in a card list format. Try now!",
|
||||
"head.keywords": "free embroidery file viewer, open PES files online, view DST files, embroidery file preview, EXP file viewer, multiple embroidery files",
|
||||
"head.ogtitle": "Free Online Embroidery File Viewer – Open PES, DST & More",
|
||||
"head.ogdescription": "Upload and preview multiple embroidery files like PES, DST, and EXP online for free. No software needed!",
|
||||
"nav.home": "🏠 Home",
|
||||
"nav.viewer": "🧵 Viewer",
|
||||
"nav.donate": "💖 Donate",
|
||||
"nav.about": "ℹ About",
|
||||
"nav.privacy.policy": "🔐 Privacy Policy",
|
||||
"nav.terms.of.service": "📝 Terms of Service",
|
||||
"main.title": "Upload files",
|
||||
"home.main.title": "🧵 Free Online Embroidery File Viewer",
|
||||
"home.main.description": "<p>✨Upload and preview your embroidery designs instantly – no software needed.</p> <p><strong>Embroidery Viewer</strong> is a free, browser-based tool that supports multiple embroidery file formats. View your designs quickly and securely, right in your browser.</p>",
|
||||
"home.features.title": "🚀 Features",
|
||||
"home.features.list": "<ul><li>📂 <strong>Supports Multiple Formats:</strong> DST, PES, JEF, EXP, VP3, and more</li><li>⚡ <strong>Quick Previews:</strong> See your embroidery files rendered as images</li><li>🧷 <strong>Multiple Files at Once:</strong> Upload several designs and view them side-by-side</li><li>🔒 <strong>No Upload to Server:</strong> Your files stay private – all processing happens locally</li><li>⬇️ <strong>Download as Image:</strong> Save each embroidery design preview as a PNG</li><li>💸 <strong>Fast & Free:</strong> No installations, no sign-ups – just open and use</li></ul>",
|
||||
"home.howtouse.title": "📘 How to Use",
|
||||
"home.howtouse.list": "<ol><li>📁 <strong>Click</strong> the upload button <em>or</em> <strong>drag and drop</strong> your embroidery files into the drop area</li><li>🧵 Select one or more embroidery files</li><li>▶️ Click the <strong>“Render files”</strong> button to preview your designs</li><li>👀 Instantly view your designs right in your browser – it’s that simple</li></ol>",
|
||||
"home.testimonials.title": "❤️ Loved by Hobbyists and Professionals",
|
||||
"home.testimonials.description": "<p>Whether you're a hobbyist working on your next DIY project or a professional digitizer reviewing client files, <strong>Embroidery Viewer</strong> gives you a no-fuss, instant way to visualize your work.</p>",
|
||||
"home.donation.title": "💖 Help Keep It Free",
|
||||
"home.donation.description": "<p><strong>Embroidery Viewer is completely free</strong> for everyone to use.</p><p>If you find it useful and want to support ongoing development and hosting costs, please consider making a small donation.</p>",
|
||||
"home.donation.cta": "🙌 Donate Now",
|
||||
"home.donation.cta.description": "every little bit helps!",
|
||||
"home.cta.title": "🚀 Try It Now",
|
||||
"home.cta.cta": "🧵 Open Viewer",
|
||||
"home.cta.cta.description": "the fastest <strong>Free Online Embroidery File Viewer</strong>.",
|
||||
"donate.title": "💖 Donate",
|
||||
"donate.subtitle": "Help support Embroidery Viewer and its development!",
|
||||
"donate.description": "⭐️ <strong>Embroidery Viewer</strong> is free to use. If you find this tool helpful, please consider making a donation to keep it running and fund future improvements.",
|
||||
"donate.ways": "💸 Ways to Donate",
|
||||
"donate.bitcoin.description": "Scan or copy the address",
|
||||
"donate.copy": "Copy Address",
|
||||
"donate.copied": "Copied to Clipboard!",
|
||||
"donate.copy.failed": "Copy Failed!",
|
||||
"donate.monero.description": "Private and secure donation option.",
|
||||
"donate.paypal.description": "Want to show support in a friendly way?",
|
||||
"donate.paypal.link": "Open Donation link",
|
||||
"about.title": "ℹ About Embroidery Viewer",
|
||||
"about.content": "<p>Hi there! 👋</p><p><strong>⭐️ Embroidery Viewer</strong> was born out of a simple need — helping someone I care about. 💖</p><p>My girlfriend loves embroidery, but she often struggled to find an easy and free way to preview her embroidery design files before stitching them. Most tools she tried were either paid, overly complex, or required technical knowledge — and she’s not a techie.</p><p>So, to make things easier for her (and others like her), I decided to build this web application.</p><p>Over the course of a few weeks, I created <strong>Embroidery Viewer</strong> — a lightweight, fast, and free tool that lets you view embroidery files directly in your browser. No installation, no setup, and no tech hurdles. Just upload your file and see your design.</p><p>It’s not a super sophisticated tool, but it solves the problem it was meant to solve: making embroidery file previews accessible to everyone.</p><p>If this tool has helped you too, that makes me really happy! I plan to continue improving it based on feedback from users like you.</p><p>Thanks for stopping by — and happy stitching! 🧵✨</p>",
|
||||
"privacy.policy.title": "🔐 Privacy Policy",
|
||||
"privacy.policy.last.update": "Last updated: May 9, 2025",
|
||||
"privacy.policy.content": "<p>At <strong>Embroidery Viewer</strong> (<a href=\"https://embroideryviewer.xyz\">embroideryviewer.xyz</a>), we respect your privacy and are committed to protecting any information you share while using our service.</p><h2>1. Personal Information</h2><p>Embroidery Viewer does <strong>not</strong> collect or store any personal information. You do not need to create an account, and we do not ask for your name, email address, or any identifying details.</p><h2>2. File Uploads</h2><p>When you upload an embroidery file to the viewer, the file is processed in your browser or temporarily on our server (if required) for preview purposes only. <strong>No uploaded files are stored, saved, or shared.</strong></p><p>Please avoid uploading any copyrighted or sensitive material unless you have permission to use it.</p><h2>3. Analytics</h2><p>We use <strong>Umami</strong> to collect anonymous usage statistics about our website, such as the number of visitors, page views, device types, and referral sources. This data helps us understand how the site is being used and improve it over time.</p><p>Umami is a privacy-friendly, cookie-free analytics tool. It does <strong>not</strong> track users across sites, collect personal data, or use cookies. All data is aggregated and anonymized.</p><h2>4. Cookies</h2><p>Embroidery Viewer does <strong>not</strong> use cookies or other tracking mechanisms in your browser.</p><h2>5. Third-Party Services</h2><p>We do not use third-party advertising, embed external trackers, or share data with third parties.</p><h2>6. Changes to This Policy</h2><p>We may update this Privacy Policy from time to time. All updates will be posted on this page with the updated date.</p><h2>7. Contact</h2><p>If you have any questions about this Privacy Policy, you can reach us at <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a>.</p>",
|
||||
"terms.of.service.title": "📝 Terms of Service",
|
||||
"terms.of.service.update": "May 9, 2025",
|
||||
"terms.of.service.content": "<p>Welcome to <strong>Embroidery Viewer</strong> (<a href=\"https://embroideryviewer.xyz\">embroideryviewer.xyz</a>). By accessing or using this website, you agree to be bound by the following Terms of Service. If you do not agree with any part of these terms, please do not use the site.</p><h2>1. Description of Service</h2><p>Embroidery Viewer is a free, browser-based tool that allows users to preview embroidery design files online. The service is intended for personal, non-commercial use.</p><h2>2. Use of the Service</h2><p>You agree to use the service only for lawful purposes. You are solely responsible for any content (including embroidery files) you upload, and you confirm that you have the legal right to use, view, and process those files.</p><p>You agree not to upload any files that are illegal, offensive, infringe on intellectual property rights, or contain malicious code.</p><h2>3. File Processing</h2><p>Files uploaded to Embroidery Viewer are processed either directly in your browser or temporarily on our servers. Files are not stored permanently, shared, or backed up.</p><p>While we aim to keep your content secure, you acknowledge that no system is 100% secure and you use the service at your own risk.</p><h2>4. No Warranty</h2><p>This service is provided \"as is\" and \"as available\" without any warranties, express or implied. We do not guarantee that the service will be uninterrupted, secure, or error-free.</p><h2>5. Limitation of Liability</h2><p>Embroidery Viewer shall not be held liable for any damages resulting from the use or inability to use the service, including but not limited to loss of data, loss of profits, or other incidental or consequential damages.</p><h2>6. Modifications to the Service</h2><p>We reserve the right to modify, suspend, or discontinue the service at any time without notice. We may also update these Terms of Service from time to time. Continued use of the service after changes constitutes your acceptance of the new terms.</p><h2>7. Governing Law</h2><p>These Terms shall be governed by and interpreted in accordance with the laws of Brazil, without regard to its conflict of law principles.</p><h2>8. Contact</h2><p>If you have any questions about these Terms of Service, feel free to contact us at <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a>.</p>",
|
||||
"main.languageSwitch": "🇧🇷",
|
||||
"main.fileSize": "Max file size is <strong>{{fileSize}}MB</strong>.",
|
||||
"main.supportedFormats": "Accepted formats: <strong>{{supportedFormats}}</strong>.",
|
||||
"main.render": "Render files",
|
||||
"main.dropzone": "<strong>Choose files</strong><br /><span>or drag and drop them here</span>",
|
||||
"main.browse": "Browse",
|
||||
"main.selected": "Selected files",
|
||||
"main.rejected": "Rejected files",
|
||||
"main.stitches": "Stitches",
|
||||
"main.dimensions": "Dimensions (x, y)",
|
||||
"main.download": "Download image",
|
||||
"main.copyright": "Copyright © {{year}} <a href=\"{{website}}\" target=\"_blank\" rel=\"noreferrer\">Leonardo Murça</a>. <br/> All rights reserved.",
|
||||
"main.version": "🧵 Version: {{version}}"
|
||||
},
|
||||
pt: {
|
||||
"head.title": "Visualizador de arquivos de bordado online gratuito – Abra PES, DST, EXP e mais",
|
||||
"head.description": "Visualize vários arquivos de bordado online gratuitamente! Abra PES, DST, EXP, JEF e mais sem software. Carregue e visualize vários arquivos em um formato de lista de cartões. Experimente agora!",
|
||||
"head.keywords": "visualizador de arquivos de bordado grátis, abra arquivos PES online, visualize arquivos DST, pré-visualização de arquivos de bordado, visualizador de arquivos EXP, vários arquivos de bordado",
|
||||
"head.ogtitle": "Visualizador de arquivos de bordado online gratuito – Abra PES, DST e mais",
|
||||
"head.ogdescription": "Carregue e visualize vários arquivos de bordado como PES, DST e EXP online gratuitamente. Não precisa de software!",
|
||||
"nav.home": "🏠 Página Inicial",
|
||||
"nav.viewer": "🧵 Visualizador",
|
||||
"nav.donate": "💖 Doe",
|
||||
"nav.about": "ℹ Sobre",
|
||||
"nav.privacy.policy": "🔐 Política de Privacidade",
|
||||
"nav.terms.of.service": "📝 Termos de Serviço",
|
||||
"home.main.title": "🧵 Visualizador de arquivos de bordado online gratuito",
|
||||
"home.main.description": "<p>✨Carregue e visualize seus desenhos de bordado instantaneamente – sem necessidade de software</p> <p><strong>Embroidery Viewer</strong> é uma ferramenta gratuita para navegador que suporta diversos formatos de arquivo de bordado. Visualize seus designs de forma rápida e segura, diretamente no seu navegador.</p>",
|
||||
"home.features.title": "🚀 Funcionalidades",
|
||||
"home.features.list": "<ul><li>📂 <strong>Suporta vários formatos:</strong> DST, PES, JEF, EXP, VP3 e mais</li><li>⚡ <strong>Visualizações rápidas:</strong> Veja seus arquivos de bordado renderizados como imagens</li><li>🧷 <strong>Vários arquivos de uma só vez:</strong> Carregue vários designs e visualize-os lado a lado</li><li>🔒 <strong>Sem upload para o servidor:</strong> Seus arquivos permanecem privados – todo o processamento acontece localmente</li><li>⬇️ <strong>Baixar como imagem:</strong> Salve cada pré-visualização do desenho do bordado como um PNG</li><li>💸 <strong>Rápido e gratuito:</strong> Sem instalações, sem cadastros – basta abrir e usar</li></ul>",
|
||||
"home.howtouse.title": "📘 Como usar",
|
||||
"home.howtouse.list": "<ol><li>📁 <strong>Clique</strong> no botão de upload <em>ou</em> <strong>arraste e solte</strong> seus arquivos de bordado na área de soltar</li><li>🧵 Selecione um ou mais arquivos de bordado</li><li>▶️ Clique no botão <strong>“Renderizar arquivos”</strong> para visualizar seus designs</li><li>👀 Visualize seus designs instantaneamente no seu navegador – é simples assim</li></ol>",
|
||||
"home.testimonials.title": "❤️ Amado por Hobbyistas e Profissionais",
|
||||
"home.testimonials.description": "<p>Seja você um amador trabalhando em seu próximo projeto \"faça você mesmo\" ou um digitalizador profissional revisando arquivos de clientes, o <strong>Embroidery Viewer</strong> oferece uma maneira fácil e instantânea de visualizar seu trabalho.</p>",
|
||||
"home.donation.title": "💖 Ajude a mantê-lo gratuito",
|
||||
"home.donation.description": "<p><strong>O Embroidery Viewer é totalmente gratuito</strong> para todos usarem.</p><p>Se você o achar útil e quiser apoiar o desenvolvimento contínuo e os custos de hospedagem, considere fazer uma pequena doação.</p>",
|
||||
"home.donation.cta": "🙌 Doe agora",
|
||||
"home.donation.cta.description": "cada pequena ajuda é bem-vinda!",
|
||||
"home.cta.title": "🚀 Experimente agora",
|
||||
"home.cta.cta": "🧵 Abrir visualizador",
|
||||
"home.cta.cta.description": "o <strong>visualizador de arquivos de bordado online gratuito</strong> mais rápido.",
|
||||
"donate.title": "💖 Doe",
|
||||
"donate.subtitle": "Ajude a apoiar o Embroidery Viewer e seu desenvolvimento!",
|
||||
"donate.description": "⭐️ O <strong>Embroidery Viewer</strong> é gratuito. Se você achar esta ferramenta útil, considere fazer uma doação para mantê-la funcionando e financiar melhorias futuras.",
|
||||
"donate.ways": "💸 Formas de doar",
|
||||
"donate.bitcoin.description": "Escaneie ou copie o endereço",
|
||||
"donate.copy": "Copiar Endereço",
|
||||
"donate.copied": "Copiado para a área de transferência!",
|
||||
"donate.copy.failed": "Falha na Cópia!",
|
||||
"donate.monero.description": "Opção de doação privada e segura.",
|
||||
"donate.paypal.description": "Quer demonstrar apoio de uma forma amigável?",
|
||||
"donate.paypal.link": "Abrir Link de Doação",
|
||||
"about.title": "ℹ Sobre o Embroidery Viewer",
|
||||
"about.content": "<p>Oi! 👋</p><p><strong>⭐️ Embroidery Viewer</strong> nasceu de uma necessidade simples — ajudar alguém que eu amo. 💖</p><p>Minha namorada adora bordado, mas ela sempre teve dificuldades para encontrar uma maneira fácil e gratuita de visualizar os arquivos de design de bordado antes de começar a costurar. A maioria das ferramentas que ela tentou eram pagas, muito complexas ou exigiam conhecimento técnico — e ela não é da área de tecnologia.</p><p>Então, para facilitar a vida dela (e de outras pessoas como ela), decidi criar este aplicativo web.</p><p>Ao longo de algumas semanas, criei o <strong>Embroidery Viewer</strong> — uma ferramenta leve, rápida e gratuita que permite visualizar arquivos de bordado diretamente no navegador. Sem instalação, sem configuração e sem obstáculos técnicos. Basta enviar o arquivo e ver o design.</p><p>Não é uma ferramenta super sofisticada, mas resolve o problema para o qual foi criada: tornar a visualização de arquivos de bordado acessível para todos.</p><p>Se essa ferramenta também te ajudou, isso me deixa muito feliz! Pretendo continuar melhorando com base no feedback de usuários como você.</p><p>Obrigado por visitar — e bons bordados! 🧵✨</p>",
|
||||
"privacy.policy.title": "🔐 Política de Privacidade",
|
||||
"privacy.policy.last.update": "Última atualização: 9 de maio de 2025",
|
||||
"privacy.policy.content": "<p>No <strong>Embroidery Viewer</strong> (<a href=\"https://embroideryviewer.xyz\">embroideryviewer.xyz</a>), respeitamos sua privacidade e estamos comprometidos em proteger qualquer informação que você compartilhe ao usar nosso serviço.</p><h2>1. Informações Pessoais</h2><p>O Embroidery Viewer <strong>não</strong> coleta nem armazena informações pessoais. Você não precisa criar uma conta e não pedimos seu nome, e-mail ou qualquer dado identificável.</p><h2>2. Envio de Arquivos</h2><p>Quando você envia um arquivo de bordado para o visualizador, o arquivo é processado no seu navegador ou temporariamente em nosso servidor (se necessário) apenas para fins de visualização. <strong>Nenhum arquivo enviado é armazenado, salvo ou compartilhado.</strong></p><p>Evite enviar materiais sensíveis ou protegidos por direitos autorais, a menos que tenha permissão para usá-los.</p><h2>3. Análises</h2><p>Utilizamos o <strong>Umami</strong> para coletar estatísticas anônimas de uso do site, como número de visitantes, visualizações de página, tipos de dispositivo e fontes de acesso. Esses dados nos ajudam a entender como o site está sendo utilizado e melhorá-lo com o tempo.</p><p>O Umami é uma ferramenta de análise que respeita a privacidade, não usa cookies e não rastreia os usuários entre sites. Todos os dados são agregados e anonimizados.</p><h2>4. Cookies</h2><p>O Embroidery Viewer <strong>não</strong> utiliza cookies ou outros mecanismos de rastreamento em seu navegador.</p><h2>5. Serviços de Terceiros</h2><p>Não utilizamos publicidade de terceiros, nem incorporamos rastreadores externos, nem compartilhamos dados com terceiros.</p><h2>6. Alterações nesta Política</h2><p>Podemos atualizar esta Política de Privacidade ocasionalmente. Todas as atualizações serão publicadas nesta página com a data de modificação.</p><h2>7. Contato</h2><p>Se você tiver dúvidas sobre esta Política de Privacidade, entre em contato pelo e-mail <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a>.</p>",
|
||||
"terms.of.service.title": "📝 Termos de Serviço",
|
||||
"terms.of.service.update": "Última atualização: 9 de maio de 2025",
|
||||
"terms.of.service.content": "<p>Bem-vindo ao <strong>Embroidery Viewer</strong> (<a href=\"https://embroideryviewer.xyz\">embroideryviewer.xyz</a>). Ao acessar ou utilizar este site, você concorda em estar vinculado aos seguintes Termos de Serviço. Se você não concordar com qualquer parte destes termos, por favor, não utilize o site.</p><h2>1. Descrição do Serviço</h2><p>O Embroidery Viewer é uma ferramenta gratuita baseada em navegador que permite aos usuários visualizar arquivos de design de bordado online. O serviço é destinado ao uso pessoal e não comercial.</p><h2>2. Uso do Serviço</h2><p>Você concorda em usar o serviço apenas para fins legais. Você é o único responsável por qualquer conteúdo (incluindo arquivos de bordado) que enviar, e confirma que tem o direito legal de usar, visualizar e processar esses arquivos.</p><p>Você concorda em não enviar arquivos que sejam ilegais, ofensivos, infrinjam direitos de propriedade intelectual ou contenham código malicioso.</p><h2>3. Processamento de Arquivos</h2><p>Os arquivos enviados para o Embroidery Viewer são processados diretamente em seu navegador ou temporariamente em nossos servidores. Os arquivos não são armazenados permanentemente, compartilhados ou backupados.</p><p>Embora tenhamos o objetivo de manter seu conteúdo seguro, você reconhece que nenhum sistema é 100% seguro e você utiliza o serviço por sua conta e risco.</p><h2>4. Sem Garantia</h2><p>Este serviço é fornecido \"como está\" e \"como disponível\", sem quaisquer garantias, expressas ou implícitas. Não garantimos que o serviço será ininterrupto, seguro ou sem erros.</p><h2>5. Limitação de Responsabilidade</h2><p>O Embroidery Viewer não será responsabilizado por quaisquer danos resultantes do uso ou da impossibilidade de usar o serviço, incluindo, mas não se limitando a, perda de dados, perda de lucros ou outros danos incidentais ou consequenciais.</p><h2>6. Modificações no Serviço</h2><p>Reservamo-nos o direito de modificar, suspender ou descontinuar o serviço a qualquer momento, sem aviso prévio. Podemos também atualizar estes Termos de Serviço de tempos em tempos. O uso contínuo do serviço após as mudanças constitui sua aceitação dos novos termos.</p><h2>7. Lei Aplicável</h2><p>Estes Termos serão regidos e interpretados de acordo com as leis do Brasil, sem levar em consideração seus princípios de conflitos de leis.</p><h2>8. Contato</h2><p>Se você tiver qualquer dúvida sobre estes Termos de Serviço, sinta-se à vontade para entrar em contato conosco pelo e-mail <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a>.</p>",
|
||||
"main.title": "Carregar arquivos",
|
||||
"main.languageSwitch": "🇺🇸",
|
||||
"main.fileSize": "O tamanho máximo de cada arquivo é <strong>{{fileSize}}MB</strong>.",
|
||||
"main.supportedFormats": "Formatos aceitos: <strong>{{supportedFormats}}</strong>.",
|
||||
"main.render": "Renderizar arquivos",
|
||||
"main.dropzone": "<strong>Selecione arquivos</strong><br /><span>ou arraste e solte-os aqui</span>",
|
||||
"main.browse": "Selecionar arquivos",
|
||||
"main.selected": "Arquivos selecionados",
|
||||
"main.rejected": "Arquivos recusados",
|
||||
"main.stitches": "Pontos",
|
||||
"main.dimensions": "Dimensões (x, y)",
|
||||
"main.download": "Baixar imagem",
|
||||
"main.copyright": "Copyright © {{year}} <a href=\"{{website}}/pt-br\" target=\"_blank\" rel=\"noreferrer\">Leonardo Murça</a>. <br/> Todos os direitos reservados.",
|
||||
"main.version": "🧵 Versão: {{version}}"
|
||||
},
|
||||
};
|
|
@ -1,39 +0,0 @@
|
|||
<script>
|
||||
import { onMount } from "svelte";
|
||||
|
||||
export let query;
|
||||
|
||||
let mql;
|
||||
let mqlListener;
|
||||
let wasMounted = false;
|
||||
let matches = false;
|
||||
|
||||
onMount(() => {
|
||||
wasMounted = true;
|
||||
return () => {
|
||||
removeActiveListener();
|
||||
};
|
||||
});
|
||||
|
||||
$: {
|
||||
if (wasMounted) {
|
||||
removeActiveListener();
|
||||
addNewListener(query);
|
||||
}
|
||||
}
|
||||
|
||||
function addNewListener(query) {
|
||||
mql = window.matchMedia(query);
|
||||
mqlListener = (v) => (matches = v.matches);
|
||||
mql.addListener(mqlListener);
|
||||
matches = mql.matches;
|
||||
}
|
||||
|
||||
function removeActiveListener() {
|
||||
if (mql && mqlListener) {
|
||||
mql.removeListener(mqlListener);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<slot {matches} />
|
|
@ -1,143 +0,0 @@
|
|||
<script>
|
||||
import { t } from "../../i18n"
|
||||
import renderFileToCanvas from "../../file-renderer";
|
||||
|
||||
export let files = [];
|
||||
let canvasRefs = [];
|
||||
let colorRefs = [];
|
||||
let stitchesRefs = [];
|
||||
let sizeRefs = [];
|
||||
let errorMessageRef;
|
||||
let localizedStrings = {
|
||||
stitches: $t("main.stitches"),
|
||||
dimensions: $t("main.dimensions"),
|
||||
}
|
||||
|
||||
const downloadCanvasAsImage = (canvas, filename) => {
|
||||
const image = canvas
|
||||
.toDataURL("image/png")
|
||||
.replace("image/png", "image/octet-stream");
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.download = `${filename.split(".").slice(0, -1).join(".")}.png`;
|
||||
link.href = image;
|
||||
link.click();
|
||||
};
|
||||
|
||||
const onKeydown = (evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
document.getElementById("download-button").click();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if files.length !== 0}
|
||||
<div id="container" style="width: 100%; heigth: 100vh;">
|
||||
{#each Array.from(files) as file, i}
|
||||
<div class="canvas-container">
|
||||
<canvas bind:this={canvasRefs[i]} class="canvas"></canvas>
|
||||
<p><strong>{file.name}</strong></p>
|
||||
<div class="stitches-container" bind:this={stitchesRefs[i]}></div>
|
||||
<div class="size-container" bind:this={sizeRefs[i]}></div>
|
||||
<div class="colors-container" bind:this={colorRefs[i]}></div>
|
||||
<div
|
||||
id="download-button"
|
||||
role="button"
|
||||
tabindex=0
|
||||
on:keydown={onKeydown}
|
||||
on:click={() => downloadCanvasAsImage(canvasRefs[i], file.name)}
|
||||
>
|
||||
{$t("main.download")}
|
||||
</div>
|
||||
</div>
|
||||
{canvasRefs[i] &&
|
||||
renderFileToCanvas(
|
||||
file,
|
||||
canvasRefs[i],
|
||||
errorMessageRef,
|
||||
colorRefs[i],
|
||||
stitchesRefs[i],
|
||||
sizeRefs[i],
|
||||
localizedStrings
|
||||
)}
|
||||
{/each}
|
||||
<!-- svelte-ignore a11y-missing-content -->
|
||||
<h1 bind:this={errorMessageRef}></h1>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
#container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-evenly;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 550px;
|
||||
max-height: 1000px;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
/* border: 2px solid black;*/
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.canvas {
|
||||
height: 70%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.colors-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
row-gap: 5px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.stitches-container {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
div[role="button"] {
|
||||
background-color: #05345f;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div[role="button"]:hover {
|
||||
cursor: pointer;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media only screen and (max-device-width: 812px) {
|
||||
.canvas-container {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div[role="button"] {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,85 +0,0 @@
|
|||
<script>
|
||||
import { t } from "../../i18n"
|
||||
import upload from "../../assets/upload.svg"
|
||||
|
||||
export let files;
|
||||
export let supportedFormats;
|
||||
export let onKeydown;
|
||||
export let onClick;
|
||||
export let onDrop;
|
||||
export let onChange;
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div
|
||||
id="dropzone"
|
||||
tabindex={0}
|
||||
role="region"
|
||||
on:keydown={onKeydown}
|
||||
on:dragover|preventDefault|stopPropagation
|
||||
on:drop|preventDefault|stopPropagation={onDrop}
|
||||
>
|
||||
<img src={upload} width="40" height="40" alt="Upload icon" />
|
||||
<label id="file-label" for="file-input">{@html $t("main.dropzone")}</label>
|
||||
<input
|
||||
id="file-input"
|
||||
type="file"
|
||||
name="files[]"
|
||||
accept={supportedFormats.join(",")}
|
||||
multiple
|
||||
on:change={onChange}
|
||||
bind:this={files}
|
||||
/>
|
||||
<button on:click|preventDefault={onClick}>{$t("main.browse")}</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#dropzone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
border: 1px solid #d3dce6;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#file-label {
|
||||
z-index: -1;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#file-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 20px;
|
||||
padding: 12px 24px;
|
||||
background-color: #06345F;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
width: 60%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
@media only screen and (max-device-width: 812px) {
|
||||
#dropzone {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,59 +0,0 @@
|
|||
<script>
|
||||
export let title;
|
||||
export let files = [];
|
||||
export let isError = false;
|
||||
</script>
|
||||
|
||||
{#if files.length !== 0}
|
||||
<div id="selected-files-container">
|
||||
<h2>{title}:</h2>
|
||||
<div id="files-list">
|
||||
{#each Array.from(files) as file}
|
||||
<div id={isError ? "selected-file-card-error" : "selected-file-card"}>
|
||||
<span>{file.name}</span>
|
||||
<span>{Math.round(file.size / 1000)} KB</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
#files-list{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#selected-file-card {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: #06345F;
|
||||
font-weight: bolder;
|
||||
width: 500px;
|
||||
padding-left: 15px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#selected-file-card-error {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: #06345F;
|
||||
font-weight: bolder;
|
||||
width: 500px;
|
||||
padding-left: 15px;
|
||||
margin-top: 10px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
@media only screen and (max-device-width: 812px) {
|
||||
#selected-files-container,
|
||||
#selected-file-card-error {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#selected-file-card {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,112 +0,0 @@
|
|||
<script>
|
||||
import CardList from "./CardList.svelte";
|
||||
import Dropzone from "./Dropzone.svelte";
|
||||
import FileList from "./FileList.svelte";
|
||||
|
||||
import { filterFiles } from "../../utils/filterFiles";
|
||||
import { supportedFormats } from "../../format-readers";
|
||||
import { t } from "../../i18n"
|
||||
|
||||
let acceptedFiles;
|
||||
let rejectedFiles;
|
||||
let areAcceptedFilesRendered = false;
|
||||
const fileRequirements = {
|
||||
supportedFormats: Object.values(supportedFormats).map((f) => f.ext),
|
||||
maxSize: 1000000,
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
areAcceptedFilesRendered = true;
|
||||
};
|
||||
|
||||
const onDrop = (evt) => {
|
||||
onChange(evt);
|
||||
};
|
||||
|
||||
const onChange = (evt) => {
|
||||
acceptedFiles = null;
|
||||
areAcceptedFilesRendered = false;
|
||||
|
||||
const changedFiles = evt.dataTransfer
|
||||
? evt.dataTransfer.files
|
||||
: evt.target.files;
|
||||
|
||||
const results = filterFiles(changedFiles, fileRequirements);
|
||||
acceptedFiles = results.accepted;
|
||||
rejectedFiles = results.rejected;
|
||||
};
|
||||
|
||||
const onClick = () => {
|
||||
document.getElementById("file-input").click();
|
||||
};
|
||||
|
||||
const onKeydown = (evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
document.getElementById("file-input").click();
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<form
|
||||
id="form"
|
||||
enctype="multipart/form-data"
|
||||
on:submit|preventDefault|stopPropagation={onSubmit}
|
||||
>
|
||||
<div class="title-container">
|
||||
<h2>{$t("main.title")}</h2>
|
||||
</div>
|
||||
<p>
|
||||
{@html $t("main.fileSize", { fileSize: fileRequirements.maxSize / 1000000 })}
|
||||
{@html $t("main.supportedFormats", { supportedFormats: fileRequirements.supportedFormats.join(", ") })}
|
||||
</p>
|
||||
|
||||
<Dropzone
|
||||
files={acceptedFiles}
|
||||
supportedFormats={fileRequirements.supportedFormats}
|
||||
{onKeydown}
|
||||
{onClick}
|
||||
{onDrop}
|
||||
{onChange}
|
||||
/>
|
||||
|
||||
<input id="submit" type="submit" value={$t("main.render")} />
|
||||
|
||||
<p class="disclaimer"><em>Do not upload copyrighted material you do not own or have rights to.</em></p>
|
||||
</form>
|
||||
|
||||
{#if areAcceptedFilesRendered}
|
||||
<CardList files={acceptedFiles} />
|
||||
{:else}
|
||||
<FileList title={$t("main.selected")} files={acceptedFiles} />
|
||||
<FileList title={$t("main.rejected")} files={rejectedFiles} isError />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
form {
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.title-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#submit {
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 15px
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-device-width: 768px) {
|
||||
#form {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,24 +0,0 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { routes, fallback } from '../../utils/routes.js';
|
||||
import { path } from '../../utils/stores.js';
|
||||
|
||||
const navigate = (to) => {
|
||||
history.pushState({}, '', to);
|
||||
path.set(to);
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', () => {
|
||||
path.set(window.location.pathname);
|
||||
});
|
||||
|
||||
let component;
|
||||
const unsubscribe = path.subscribe(current => {
|
||||
component = routes[current] !== undefined ? routes[current].component : fallback;
|
||||
});
|
||||
|
||||
onMount(() => () => unsubscribe());
|
||||
|
||||
</script>
|
||||
|
||||
<svelte:component this={component} />
|
1
src/lib/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
|
@ -1,29 +0,0 @@
|
|||
<script>
|
||||
import { t } from "../../i18n"
|
||||
|
||||
</script>
|
||||
<section aria-labelledby="about-heading">
|
||||
<h1 id="about-heading">{$t('about.title')}</h1>
|
||||
|
||||
{@html $t("about.content")}
|
||||
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
padding: 0;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
section {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,195 +0,0 @@
|
|||
|
||||
<script>
|
||||
import { t } from "../../i18n"
|
||||
import bitcoin from "../../assets/bitcoin.svg"
|
||||
import monero from "../../assets/monero.svg"
|
||||
import paypal from "../../assets/paypal.svg"
|
||||
|
||||
let bitcoinCopyStatus = '';
|
||||
let moneroCopyStatus= '';
|
||||
|
||||
const onCopyMonero = async (text) => {
|
||||
try {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.setAttribute('readonly', '');
|
||||
textarea.style.position = 'absolute';
|
||||
textarea.style.left = '-9999px';
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
|
||||
moneroCopyStatus = 'donate.copied';
|
||||
} catch (err) {
|
||||
console.error('Copy failed:', err);
|
||||
moneroCopyStatus = 'donate.copy.failed';
|
||||
}
|
||||
|
||||
setTimeout(() => moneroCopyStatus = '', 2000);
|
||||
};
|
||||
|
||||
const onCopyBitcoin = async (text) => {
|
||||
try {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.setAttribute('readonly', '');
|
||||
textarea.style.position = 'absolute';
|
||||
textarea.style.left = '-9999px';
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
|
||||
bitcoinCopyStatus = 'donate.copied';
|
||||
} catch (err) {
|
||||
console.error('Copy failed:', err);
|
||||
bitcoinCopyStatus = 'donate.copy.failed';
|
||||
}
|
||||
|
||||
setTimeout(() => bitcoinCopyStatus = '', 2000);
|
||||
};
|
||||
|
||||
</script>
|
||||
<section aria-labelledby="donate-title">
|
||||
<h1 id="donate-title">{$t("donate.title")}</h1>
|
||||
<p class="donate-subtitle">{$t("donate.subtitle")}</p>
|
||||
<p>
|
||||
{@html $t("donate.description")}
|
||||
</p>
|
||||
</section>
|
||||
<section id="ways" aria-labelledby="ways-title">
|
||||
<h2>{$t("donate.ways")}</h2>
|
||||
<div class="donation-options">
|
||||
<article class="donation-method" aria-labelledby="btc-label">
|
||||
<img src={bitcoin} width="200" height="200" alt="Bitcoin QR code" />
|
||||
<h3 id="btc-label">Bitcoin</h3>
|
||||
<p>{$t("donate.bitcoin.description")}</p>
|
||||
<button id="copy-btc" aria-label="Copy Bitcoin address" on:click={() => onCopyBitcoin("bc1qpc4lpyr6stxrrg3u0k4clp4crlt6z4j6q845rq")}>
|
||||
{#if bitcoinCopyStatus}
|
||||
{$t(bitcoinCopyStatus)}
|
||||
{:else}
|
||||
{$t("donate.copy")}
|
||||
{/if}
|
||||
</button>
|
||||
</article>
|
||||
|
||||
|
||||
<article class="donation-method" aria-labelledby="xmr-label">
|
||||
<img src={monero} alt="Monero QR code" width="200" height="200" />
|
||||
<h3 id="xmr-label">Monero</h3>
|
||||
<p>{$t("donate.monero.description")}</p>
|
||||
<button id="copy-monero" aria-label="Copy Monero address" on:click={() => onCopyMonero("8A9iyTskiBh6f6GDUwnUJaYhAW13gNjDYaZYJBftX434D3XLrcGBko4a8kC4pLSfiuJAoSJ7e8rwP8W4StsVypftCp6FGwm")}>
|
||||
{#if moneroCopyStatus}
|
||||
{$t(moneroCopyStatus)}
|
||||
{:else}
|
||||
{$t("donate.copy")}
|
||||
{/if}
|
||||
</button>
|
||||
</article>
|
||||
|
||||
<article class="donation-method" aria-labelledby="bmc-label">
|
||||
<img src={paypal} alt="PayPal" width="200" height="200" />
|
||||
<h3 id="bmc-label">PayPal</h3>
|
||||
<p>{$t("donate.paypal.description")}</p>
|
||||
<a id="paypal-donation-link" aria-label="Paypal donation link" target="_blank" href="https://www.paypal.com/donate/?business=leo@leomurca.xyz¤cy_code=USD">{$t("donate.paypal.link")}</a>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
padding: 0;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.donate-subtitle {
|
||||
font-weight: bold;
|
||||
color: #06345F;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.donation-options {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.donation-method {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 33.33%;
|
||||
}
|
||||
|
||||
.donation-method p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 14px;
|
||||
background-color: #05345f;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
width: 200px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
cursor: pointer;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#paypal-donation-link {
|
||||
font-size: 14px;
|
||||
background-color: #05345f;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
width: 200px;
|
||||
height: 45px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#paypal-donation-link:hover {
|
||||
cursor: pointer;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
button {
|
||||
font-size: 1em;
|
||||
width: 100%;
|
||||
height: 55px;
|
||||
}
|
||||
#paypal-donation-link {
|
||||
font-size: 1em;
|
||||
width: 100%;
|
||||
height: 55px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.donation-options{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 50px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.donation-method {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,60 +0,0 @@
|
|||
<script>
|
||||
import { t } from "../../i18n"
|
||||
import { path } from '../../utils/stores.js';
|
||||
|
||||
const onNavigateTo = (e, route) => {
|
||||
e.preventDefault()
|
||||
history.pushState({}, '', route);
|
||||
path.set(route);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="home-container">
|
||||
|
||||
<section aria-labelledby="main-title">
|
||||
<h1 id="main-title">{$t("home.main.title")}</h1>
|
||||
{@html $t("home.main.description")}
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="features-title">
|
||||
<h2 id="features-title">{$t("home.features.title")}</h2>
|
||||
{@html $t("home.features.list")}
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="how-to-use-title">
|
||||
<h2 id="how-to-use-title">{$t("home.howtouse.title")}</h2>
|
||||
{@html $t("home.howtouse.list")}
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="testimonials-title">
|
||||
<h2 id="testimonials-title">{$t("home.testimonials.title")}</h2>
|
||||
{@html $t("home.testimonials.description")}
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="donation-title">
|
||||
<h2 id="donation-title">{$t("home.donation.title")}</h2>
|
||||
{@html $t("home.donation.description")}
|
||||
<p><a href="#" on:click={(e) => onNavigateTo(e, "/donate")} class="button">{$t("home.donation.cta")}</a> – {$t("home.donation.cta.description")}</p>
|
||||
</section>
|
||||
|
||||
<!--TODO: add video preview-->
|
||||
<section aria-labelledby="cta-title">
|
||||
<h2 id="cta-title">{$t("home.cta.title")}</h2>
|
||||
<p><a href="#" on:click={(e) => onNavigateTo(e, "/viewer")} class="button">{$t("home.cta.cta")}</a> – {@html $t("home.cta.cta.description")}</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.home-container {
|
||||
margin: 0 auto;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.home-container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
<h1>404 - Not Found</h1>
|
||||
<p>Oops! That route does not exist.</p>
|
|
@ -1,25 +0,0 @@
|
|||
<script>
|
||||
import { t } from "../../i18n"
|
||||
</script>
|
||||
<section aria-labelledby="privacy-policy-heading">
|
||||
<h1 id="privacy-policy-heading">{$t('privacy.policy.title')}</h1>
|
||||
<p><em>{$t('privacy.policy.last.update')}</em></p>
|
||||
|
||||
{@html $t('privacy.policy.content')}
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h2 {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
section {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,26 +0,0 @@
|
|||
<script>
|
||||
import { t } from "../../i18n"
|
||||
</script>
|
||||
|
||||
<section aria-labelledby="tos-heading">
|
||||
<h1 id="tos-heading">{$t('terms.of.service.title')}</h1>
|
||||
<p><em>{$t('terms.of.service.update')}</em></p>
|
||||
|
||||
{@html $t('terms.of.service.content')}
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h2 {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
section {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,4 +0,0 @@
|
|||
<script>
|
||||
import FileViewer from "../components/FileViewer.svelte"
|
||||
</script>
|
||||
<FileViewer/>
|
|
@ -1,87 +0,0 @@
|
|||
<script>
|
||||
import { t } from "../../i18n";
|
||||
import { appVersion } from "../../utils/env";
|
||||
import { footerRoutes } from "../../utils/routes"
|
||||
</script>
|
||||
|
||||
<footer>
|
||||
<div class="footer-content">
|
||||
<div class="footer-info">
|
||||
<p>{@html $t("main.copyright", {
|
||||
year: new Date().getFullYear(),
|
||||
website: "https://leomurca.xyz"
|
||||
})}</p>
|
||||
<p>{@html $t("main.version", { version: appVersion() })}</p>
|
||||
</div>
|
||||
|
||||
<nav class="footer-nav">
|
||||
{#each Object.entries(footerRoutes) as [route, config]}
|
||||
<a href={route} >{$t(config.nameKey)}</a>
|
||||
{/each}
|
||||
</nav>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
footer {
|
||||
background-color: #f8f9fa;
|
||||
border-top: 1px solid #ddd;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-info {
|
||||
flex: 1 1 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.footer-info p {
|
||||
margin: 4px 0;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.footer-info p:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.footer-nav {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.footer-nav a {
|
||||
margin: 0 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
.footer-content {
|
||||
flex-wrap: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.footer-info,
|
||||
.footer-nav {
|
||||
flex: 1 1 50%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.footer-info {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.footer-nav {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,16 +0,0 @@
|
|||
<script>
|
||||
import { t, locale } from "../../i18n";
|
||||
import thumbnail from "../../assets/thumbnail.webp";
|
||||
|
||||
$: document.documentElement.lang = $locale;
|
||||
</script>
|
||||
<svelte:head>
|
||||
<title>{$t("head.title")}</title>
|
||||
<meta name="description" content="{$t('head.description')}" />
|
||||
<meta name="keywords" content="{$t('head.keywords')}">
|
||||
<meta property="og:title" content="{$t('head.ogtitle')}">
|
||||
<meta property="og:description" content="{$t('head.ogdescription')}">
|
||||
<meta property="og:url" content="https://embroideryviewer.xyz/">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="{thumbnail}">
|
||||
</svelte:head>
|
|
@ -1,195 +0,0 @@
|
|||
<script>
|
||||
import MediaQuery from "../MediaQuery.svelte";
|
||||
import logo from "../../assets/logo.webp";
|
||||
import { t, locale, locales } from "../../i18n"
|
||||
import { path } from '../../utils/stores.js';
|
||||
import { routes } from '../../utils/routes.js';
|
||||
|
||||
const configsFor = (matches) => {
|
||||
return matches
|
||||
? { src: logo, width: 150, height: 70} // mobile
|
||||
: { src: logo, width: 150, height: 100 }; // desktop
|
||||
};
|
||||
|
||||
const onSwitchToOppositeLang = () => {
|
||||
const oppositeLang = locales.find(item => item[0] !== $locale);
|
||||
locale.set(oppositeLang[0]);
|
||||
}
|
||||
|
||||
const onNavigateTo = (e, route) => {
|
||||
e.preventDefault()
|
||||
history.pushState({}, '', route);
|
||||
path.set(route);
|
||||
if (isMenuOpen) {
|
||||
isMenuOpen = false
|
||||
}
|
||||
}
|
||||
|
||||
let isMenuOpen = false;
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<div class="logo">
|
||||
<MediaQuery query="(max-width: 768px)" let:matches>
|
||||
{@const configs = configsFor(matches)}
|
||||
<a href="#" on:click={(e) => onNavigateTo(e, "/")}>
|
||||
<img src={configs.src} alt="Embroidery viewer logo" width={configs.width} height={configs.height}/>
|
||||
</a>
|
||||
</MediaQuery>
|
||||
</div>
|
||||
|
||||
<div class="nav-container">
|
||||
<MediaQuery query="(max-width: 768px)" let:matches >
|
||||
<slot let-matches>
|
||||
{#if matches}
|
||||
<button class="hamburger" on:click={() => (isMenuOpen = !isMenuOpen)}>
|
||||
{#if isMenuOpen}x{:else}☰{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</slot>
|
||||
</MediaQuery>
|
||||
<nav class:is-open={isMenuOpen}>
|
||||
<ul>
|
||||
{#each Object.entries(routes).filter(r => r[1].nameKey !== undefined) as [route, config]}
|
||||
<li><a href="#" on:click={(e) => onNavigateTo(e, route)} >{$t(config.nameKey)}</a></li>
|
||||
{/each}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<a class="common-switch {$locale === 'en' ? 'portuguese-switch' : 'english-switch' }" href="#" on:click|preventDefault={onSwitchToOppositeLang}>
|
||||
<div style="display: flex; width: fit-content;">
|
||||
<span style="font-size: 20px;">{$t("main.languageSwitch")}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 100px;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #ddd;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
height: auto;
|
||||
max-height: 60px;
|
||||
}
|
||||
|
||||
.logo a {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.logo a:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 35px;
|
||||
width: 35px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.common-switch {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.portuguese-switch {
|
||||
color: #0C8F27;
|
||||
border-bottom: 3px solid #0C8F27 !important;
|
||||
fill: #0C8F27 !important;
|
||||
}
|
||||
|
||||
.portuguese-switch:hover {
|
||||
background: #0C8F27;
|
||||
color: #ffffff;
|
||||
fill: #ffffff !important;
|
||||
}
|
||||
|
||||
.english-switch{
|
||||
color: #BE0A2F;
|
||||
border-bottom: 3px solid #BE0A2F;
|
||||
width: fit-content;
|
||||
fill: #BE0A2F !important;
|
||||
}
|
||||
|
||||
.english-switch:hover {
|
||||
background: #BE0A2F;
|
||||
color: #ffffff;
|
||||
fill: #ffffff !important;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
header {
|
||||
padding: 10px 20px ;
|
||||
}
|
||||
.hamburger {
|
||||
display: block;
|
||||
width: 35px;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
background-color: #f8f9fa;
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
right: 0;
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
nav.is-open {
|
||||
display: inline-block;
|
||||
margin-top: 25px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
flex-direction: column;
|
||||
gap: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,14 +0,0 @@
|
|||
<script>
|
||||
import Router from "../components/Router.svelte";
|
||||
</script>
|
||||
<main>
|
||||
<Router />
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
flex: 1; /* This pushes footer to bottom */
|
||||
padding: 20px;
|
||||
min-height: 90vh;
|
||||
}
|
||||
</style>
|
|
@ -1,9 +0,0 @@
|
|||
import { mount } from 'svelte';
|
||||
import App from './App.svelte';
|
||||
import "./app.css";
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById('app'),
|
||||
});
|
||||
|
||||
export default app;
|
2
src/routes/+page.svelte
Normal file
|
@ -0,0 +1,2 @@
|
|||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
|
@ -1,6 +0,0 @@
|
|||
// @ts-nocheck
|
||||
function appVersion() {
|
||||
return APP_VERSION;
|
||||
}
|
||||
|
||||
export { appVersion };
|
|
@ -1,21 +0,0 @@
|
|||
const formattedFilenameExt = (name) =>
|
||||
`.${name.split(".").pop().toLowerCase()}`;
|
||||
|
||||
const areRequirementsFulfilled = (requirements, file) =>
|
||||
requirements.maxSize >= file.size &&
|
||||
requirements.supportedFormats.includes(formattedFilenameExt(file.name));
|
||||
|
||||
export function filterFiles(files, requirements) {
|
||||
let accepted = [];
|
||||
let rejected = [];
|
||||
Array.from(files).forEach((file) => {
|
||||
if (file) {
|
||||
if (areRequirementsFulfilled(requirements, file)) {
|
||||
accepted.push(file);
|
||||
} else {
|
||||
rejected.push(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
return { accepted, rejected };
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
function componentToHex(c) {
|
||||
var hex = c.toString(16);
|
||||
return hex.length == 1 ? "0" + hex : hex;
|
||||
}
|
||||
|
||||
function rgbToHex(color) {
|
||||
return (
|
||||
"#" +
|
||||
componentToHex(color.r) +
|
||||
componentToHex(color.g) +
|
||||
componentToHex(color.b)
|
||||
);
|
||||
}
|
||||
|
||||
export { rgbToHex };
|
|
@ -1,51 +0,0 @@
|
|||
import Home from '../lib/pages/Home.svelte';
|
||||
import Donate from '../lib/pages/Donate.svelte';
|
||||
import About from '../lib/pages/About.svelte';
|
||||
import PrivacyPolicy from '../lib/pages/PrivacyPolicy.svelte';
|
||||
import TermsOfService from '../lib/pages/TermsOfService.svelte';
|
||||
import Viewer from '../lib/pages/Viewer.svelte';
|
||||
import NotFound from '../lib/pages/NotFound.svelte';
|
||||
|
||||
export const routes = {
|
||||
'/': {
|
||||
component: Home,
|
||||
nameKey: "nav.home"
|
||||
},
|
||||
'/viewer': {
|
||||
component: Viewer,
|
||||
nameKey: "nav.viewer"
|
||||
},
|
||||
'/donate': {
|
||||
component: Donate,
|
||||
nameKey: "nav.donate"
|
||||
},
|
||||
'/about': {
|
||||
component: About,
|
||||
nameKey: "nav.about"
|
||||
},
|
||||
'/privacy-policy': {
|
||||
component: PrivacyPolicy,
|
||||
nameKey: undefined
|
||||
},
|
||||
'/terms-of-service': {
|
||||
component: TermsOfService,
|
||||
nameKey: undefined
|
||||
},
|
||||
};
|
||||
|
||||
export const footerRoutes = {
|
||||
'/about': {
|
||||
component: About,
|
||||
nameKey: "nav.about"
|
||||
},
|
||||
'/privacy-policy': {
|
||||
component: PrivacyPolicy,
|
||||
nameKey: "nav.privacy.policy"
|
||||
},
|
||||
'/terms-of-service': {
|
||||
component: TermsOfService,
|
||||
nameKey: "nav.terms.of.service"
|
||||
},
|
||||
}
|
||||
|
||||
export const fallback = NotFound;
|
|
@ -1,20 +0,0 @@
|
|||
function shadeColor(color, percent) {
|
||||
const num = parseInt(color.slice(1), 16),
|
||||
amt = Math.round(2.55 * percent),
|
||||
R = (num >> 16) + amt,
|
||||
G = ((num >> 8) & 0x00ff) + amt,
|
||||
B = (num & 0x0000ff) + amt;
|
||||
return (
|
||||
"#" +
|
||||
(
|
||||
0x1000000 +
|
||||
(R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
|
||||
(G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
|
||||
(B < 255 ? (B < 1 ? 0 : B) : 255)
|
||||
)
|
||||
.toString(16)
|
||||
.slice(1)
|
||||
);
|
||||
}
|
||||
|
||||
export { shadeColor };
|
|
@ -1,3 +0,0 @@
|
|||
import { writable } from 'svelte/store';
|
||||
|
||||
export const path = writable(window.location.pathname);
|
2
src/vite-env.d.ts
vendored
|
@ -1,2 +0,0 @@
|
|||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
BIN
static/favicon.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
5
svelte.config.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import adapter from '@sveltejs/adapter-auto';
|
||||
|
||||
const config = { kit: { adapter: adapter() } };
|
||||
|
||||
export default config;
|
|
@ -1,10 +1,6 @@
|
|||
import { defineConfig } from "vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
define: {
|
||||
APP_VERSION: JSON.stringify(process.env.npm_package_version),
|
||||
},
|
||||
plugins: [sveltekit()]
|
||||
});
|
||||
|
|