js: Initial UI implementation
I've implemented the UI using TypeScript and Shoelace. I originally started with Pico CSS, but I didn't really like its visuals. Since capturing photos using the camera requires JavaScript, and that's basically the entire point of this application, Shoelace's JavaScript dependency (for WebComponents), is acceptable. The photo capture uses the Media Capture Web API, which exposes the camera directly as a video stream. We capture a frame from this stream and save it in a canvas, which we then pass to Cropper.js to let the user select only the relevant portion of the picture containing the receipt itself.bugfix/ci-buildah
parent
9a7b083f82
commit
837caecc3a
|
@ -0,0 +1,2 @@
|
|||
dist/
|
||||
node_modules/
|
|
@ -0,0 +1,28 @@
|
|||
import SlAlert from "@shoelace-style/shoelace/dist/components/alert/alert.js";
|
||||
import SlIcon from "@shoelace-style/shoelace/dist/components/icon/icon.js";
|
||||
|
||||
type AlertVariant = "primary" | "success" | "neutral" | "warning" | "danger";
|
||||
|
||||
export function notify(
|
||||
message: string,
|
||||
variant: AlertVariant = "primary",
|
||||
iconName = "info-circle",
|
||||
duration: number | null = 3000,
|
||||
) {
|
||||
const alert = new SlAlert();
|
||||
const icon = new SlIcon();
|
||||
icon.slot = "icon";
|
||||
icon.name = iconName;
|
||||
alert.variant = variant;
|
||||
alert.open = true;
|
||||
alert.style.position = "relative";
|
||||
alert.closable = true;
|
||||
if (duration) {
|
||||
alert.duration = duration;
|
||||
}
|
||||
alert.appendChild(icon);
|
||||
alert.appendChild(document.createTextNode(message));
|
||||
document.body.append(alert);
|
||||
alert.toast();
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import * as esbuild from "esbuild";
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import * as url from "url";
|
||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
||||
|
||||
const context = await esbuild.context({
|
||||
entryPoints: [
|
||||
"*.ts",
|
||||
"icons/**/*",
|
||||
],
|
||||
outdir: "./dist",
|
||||
bundle: true,
|
||||
platform: "node",
|
||||
target: "esnext",
|
||||
format: "esm",
|
||||
loader: {
|
||||
".png": "copy",
|
||||
".ico": "copy",
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: "copy-shoelace-assets",
|
||||
setup(build) {
|
||||
let hasCopied = false;
|
||||
build.onStart(() => {
|
||||
if (!hasCopied) {
|
||||
// We only copy one time, these files shouldn't be changing often.
|
||||
const shoelaceAssets = path.resolve(
|
||||
__dirname,
|
||||
"node_modules/@shoelace-style/shoelace/dist/assets",
|
||||
);
|
||||
const outputDir = path.resolve(
|
||||
__dirname,
|
||||
"dist/shoelace/assets",
|
||||
);
|
||||
|
||||
fs.rmSync(outputDir, { force: true, recursive: true });
|
||||
fs.cpSync(shoelaceAssets, outputDir, {
|
||||
recursive: true,
|
||||
});
|
||||
hasCopied = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await context.rebuild();
|
||||
|
||||
if (process.argv.includes("--watch")) {
|
||||
await context.watch();
|
||||
console.log("watching for file changes...");
|
||||
} else {
|
||||
await context.dispose();
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
:host, body {
|
||||
font-family: var(--sl-font-sans);
|
||||
}
|
||||
|
||||
body:has(div#page-loading) main {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
transition: 1s opacity;
|
||||
}
|
||||
|
||||
#page-loading {
|
||||
animation: 2s linear infinite pulse;
|
||||
align-items: center;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
font-size: 24pt;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
body {
|
||||
margin: auto;
|
||||
max-width: 1000px;
|
||||
}
|
||||
}
|
||||
|
||||
form footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
form footer sl-button {
|
||||
margin: 0.5em 0.25em;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid var(--sl-color-neutral-200);
|
||||
}
|
||||
|
||||
table td, table th {
|
||||
padding: 1rem;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import "@shoelace-style/shoelace/dist/themes/light.css";
|
||||
import "@shoelace-style/shoelace/dist/themes/dark.css";
|
||||
|
||||
import "./common.css";
|
||||
|
||||
const mql = window.matchMedia("(prefers-color-scheme: dark");
|
||||
const setDarkMode = () => {
|
||||
if (mql.matches) {
|
||||
document.documentElement.classList.add("sl-theme-dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("sl-theme-dark");
|
||||
}
|
||||
};
|
||||
mql.addEventListener("change", setDarkMode);
|
||||
setDarkMode();
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const elm = document.getElementById("page-loading");
|
||||
if (elm) elm.remove();
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
|
@ -0,0 +1,779 @@
|
|||
{
|
||||
"name": "receipts",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "receipts",
|
||||
"dependencies": {
|
||||
"@shoelace-style/shoelace": "^2.20.0",
|
||||
"cropperjs": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element/-/element-2.0.0.tgz",
|
||||
"integrity": "sha512-lsthn0nQq73GExUE7Mg/ss6Q3RXADGDv055hxoLFwvl/wGHgy6ZkYlfLZ/VmgBHC6jDK5IgPBFnqrPqlXWSGBA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-canvas": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-canvas/-/element-canvas-2.0.0.tgz",
|
||||
"integrity": "sha512-GPtGJgSm92crJhhhwUsaMw3rz2KfJWWSz7kRAlufFEV/EHTP5+6r6/Z1BCGRna830i+Avqbm435XLOtA7PVJwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.0",
|
||||
"@cropper/utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-crosshair": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-crosshair/-/element-crosshair-2.0.0.tgz",
|
||||
"integrity": "sha512-KfPfyrdeFvUC31Ws7ATtcalWWSaMtrC6bMoCipZhqbUOE7wZoL4ecDSL6BUOZxPa74awZUqfzirCDjHvheBfyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.0",
|
||||
"@cropper/utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-grid": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-grid/-/element-grid-2.0.0.tgz",
|
||||
"integrity": "sha512-i78SQ0IJTLFveKX6P7svkfMYVdgHrQ8ZmmEw8keFy9n1ZVbK+SK0UHK5FNMRNI/gtVhKJOGEnK/zeyjUdj4Iyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.0",
|
||||
"@cropper/utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-handle": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-handle/-/element-handle-2.0.0.tgz",
|
||||
"integrity": "sha512-ZJvW+0MkK9E8xYymGdoruaQn2kwjSHFpNSWinjyq6csuVQiCPxlX5ovAEDldmZ9MWePPtWEi3vLKQOo2Yb0T8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.0",
|
||||
"@cropper/utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-image": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-image/-/element-image-2.0.0.tgz",
|
||||
"integrity": "sha512-9BxiTS/aHRmrjopaFQb9mQQXmx4ruhYHGkDZMVz24AXpMFjUY6OpqrWse/WjzD9tfhMFvEdu17b3VAekcAgpeg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.0",
|
||||
"@cropper/element-canvas": "^2.0.0",
|
||||
"@cropper/utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-selection": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-selection/-/element-selection-2.0.0.tgz",
|
||||
"integrity": "sha512-ensNnbIfJsJ8bhbJTH/RXtk2URFvTOO4TvfRk461n2FPEC588D7rwBmUJxQg74IiTi4y1JbCI+6j+4LyzYBLCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.0",
|
||||
"@cropper/element-canvas": "^2.0.0",
|
||||
"@cropper/element-image": "^2.0.0",
|
||||
"@cropper/utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-shade": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-shade/-/element-shade-2.0.0.tgz",
|
||||
"integrity": "sha512-jv/2bbNZnhU4W+T4G0c8ADocLIZvQFTXgCf2RFDNhI5UVxurzWBnDdb8Mx8LnVplnkTqO+xUmHZYve0CwgWo+Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.0",
|
||||
"@cropper/element-canvas": "^2.0.0",
|
||||
"@cropper/element-selection": "^2.0.0",
|
||||
"@cropper/utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-viewer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-viewer/-/element-viewer-2.0.0.tgz",
|
||||
"integrity": "sha512-zY+3VRN5TvpM8twlphYtXw0tzJL2VgzeK7ufhL1BixVqOdRxwP13TprYIhqwGt9EW/SyJZUiaIu396T89kRX8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.0",
|
||||
"@cropper/element-canvas": "^2.0.0",
|
||||
"@cropper/element-image": "^2.0.0",
|
||||
"@cropper/element-selection": "^2.0.0",
|
||||
"@cropper/utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/elements": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/elements/-/elements-2.0.0.tgz",
|
||||
"integrity": "sha512-PQkPo1nUjxLFUQuHYu+6atfHxpX9B41Xribao6wpvmvmNIFML6LQdNqqWYb6LyM7ujsu71CZdBiMT5oetjJVoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.0",
|
||||
"@cropper/element-canvas": "^2.0.0",
|
||||
"@cropper/element-crosshair": "^2.0.0",
|
||||
"@cropper/element-grid": "^2.0.0",
|
||||
"@cropper/element-handle": "^2.0.0",
|
||||
"@cropper/element-image": "^2.0.0",
|
||||
"@cropper/element-selection": "^2.0.0",
|
||||
"@cropper/element-shade": "^2.0.0",
|
||||
"@cropper/element-viewer": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/utils/-/utils-2.0.0.tgz",
|
||||
"integrity": "sha512-cprLYr+7kK3faGgoOsTW9gIn5sefDr2KwOmgyjzIXk+8PLpW8FgFKEg5FoWfRD5zMAmkCBuX6rGKDK3VdUEGrg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ctrl/tinycolor": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz",
|
||||
"integrity": "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
|
||||
"integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
|
||||
"integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
|
||||
"integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
|
||||
"integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
|
||||
"integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
|
||||
"integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
|
||||
"integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
|
||||
"integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
|
||||
"integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
|
||||
"integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.6.9",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
|
||||
"integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.6.13",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz",
|
||||
"integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.6.0",
|
||||
"@floating-ui/utils": "^0.2.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
||||
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lit-labs/ssr-dom-shim": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz",
|
||||
"integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@lit/react": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@lit/react/-/react-1.0.7.tgz",
|
||||
"integrity": "sha512-cencnwwLXQKiKxjfFzSgZRngcWJzUDZi/04E0fSaF86wZgchMdvTyu+lE36DrUfvuus3bH8+xLPrhM1cTjwpzw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"@types/react": "17 || 18 || 19"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit/reactive-element": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
|
||||
"integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-dom-shim": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@shoelace-style/animations": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shoelace-style/animations/-/animations-1.2.0.tgz",
|
||||
"integrity": "sha512-avvo1xxkLbv2dgtabdewBbqcJfV0e0zCwFqkPMnHFGbJbBHorRFfMAHh1NG9ymmXn0jW95ibUVH03E1NYXD6Gw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/claviska"
|
||||
}
|
||||
},
|
||||
"node_modules/@shoelace-style/localize": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@shoelace-style/localize/-/localize-3.2.1.tgz",
|
||||
"integrity": "sha512-r4C9C/5kSfMBIr0D9imvpRdCNXtUNgyYThc4YlS6K5Hchv1UyxNQ9mxwj+BTRH2i1Neits260sR3OjKMnplsFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@shoelace-style/shoelace": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@shoelace-style/shoelace/-/shoelace-2.20.0.tgz",
|
||||
"integrity": "sha512-Qq/kPtWC//HVyHX6EZ/i5y9zTMORjqV4lrxU2sbHLh+qdc9DlroYVSSqa2eqkmSzeLO+gHPZrjYmxDTja85iAA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
"@floating-ui/dom": "^1.6.12",
|
||||
"@lit/react": "^1.0.6",
|
||||
"@shoelace-style/animations": "^1.2.0",
|
||||
"@shoelace-style/localize": "^3.2.1",
|
||||
"composed-offset-position": "^0.0.6",
|
||||
"lit": "^3.2.1",
|
||||
"qr-creator": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/claviska"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
|
||||
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/composed-offset-position": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/composed-offset-position/-/composed-offset-position-0.0.6.tgz",
|
||||
"integrity": "sha512-Q7dLompI6lUwd7LWyIcP66r4WcS9u7AL2h8HaeipiRfCRPLMWqRx8fYsjb4OHi6UQFifO7XtNC2IlEJ1ozIFxw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@floating-ui/utils": "^0.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/cropperjs": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-2.0.0.tgz",
|
||||
"integrity": "sha512-TO2j0Qre01kPHbow4FuTrbdEB4jTmGRySxW49jyEIqlJZuEBfrvCTT0vC3eRB2WBXudDfKi1Onako6DKWKxeAQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/elements": "^2.0.0",
|
||||
"@cropper/utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
|
||||
"integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.0",
|
||||
"@esbuild/android-arm": "0.25.0",
|
||||
"@esbuild/android-arm64": "0.25.0",
|
||||
"@esbuild/android-x64": "0.25.0",
|
||||
"@esbuild/darwin-arm64": "0.25.0",
|
||||
"@esbuild/darwin-x64": "0.25.0",
|
||||
"@esbuild/freebsd-arm64": "0.25.0",
|
||||
"@esbuild/freebsd-x64": "0.25.0",
|
||||
"@esbuild/linux-arm": "0.25.0",
|
||||
"@esbuild/linux-arm64": "0.25.0",
|
||||
"@esbuild/linux-ia32": "0.25.0",
|
||||
"@esbuild/linux-loong64": "0.25.0",
|
||||
"@esbuild/linux-mips64el": "0.25.0",
|
||||
"@esbuild/linux-ppc64": "0.25.0",
|
||||
"@esbuild/linux-riscv64": "0.25.0",
|
||||
"@esbuild/linux-s390x": "0.25.0",
|
||||
"@esbuild/linux-x64": "0.25.0",
|
||||
"@esbuild/netbsd-arm64": "0.25.0",
|
||||
"@esbuild/netbsd-x64": "0.25.0",
|
||||
"@esbuild/openbsd-arm64": "0.25.0",
|
||||
"@esbuild/openbsd-x64": "0.25.0",
|
||||
"@esbuild/sunos-x64": "0.25.0",
|
||||
"@esbuild/win32-arm64": "0.25.0",
|
||||
"@esbuild/win32-ia32": "0.25.0",
|
||||
"@esbuild/win32-x64": "0.25.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lit": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz",
|
||||
"integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"lit-element": "^4.1.0",
|
||||
"lit-html": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lit-element": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz",
|
||||
"integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-dom-shim": "^1.2.0",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"lit-html": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lit-html": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz",
|
||||
"integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@types/trusted-types": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/qr-creator": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/qr-creator/-/qr-creator-1.0.0.tgz",
|
||||
"integrity": "sha512-C0cqfbS1P5hfqN4NhsYsUXePlk9BO+a45bAQ3xLYjBL3bOIFzoVEjs79Fado9u9BPBD3buHi3+vY+C8tHh4qMQ==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "receipts",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@shoelace-style/shoelace": "^2.20.0",
|
||||
"cropperjs": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node build.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
.invisible {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#photo-view {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#photo-view .workspace {
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#photo-view sl-icon-button {
|
||||
font-size: 3em;
|
||||
margin: 0 0.5em;
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
import "./transaction.css";
|
||||
import "@shoelace-style/shoelace/dist/components/button/button.js";
|
||||
import "@shoelace-style/shoelace/dist/components/breadcrumb/breadcrumb.js";
|
||||
import "@shoelace-style/shoelace/dist/components/breadcrumb-item/breadcrumb-item.js";
|
||||
import "@shoelace-style/shoelace/dist/components/details/details.js";
|
||||
import "@shoelace-style/shoelace/dist/components/icon-button/icon-button.js";
|
||||
import "@shoelace-style/shoelace/dist/components/input/input.js";
|
||||
import "@shoelace-style/shoelace/dist/components/textarea/textarea.js";
|
||||
import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js";
|
||||
|
||||
import SlButton from "@shoelace-style/shoelace/dist/components/button/button.js";
|
||||
import SlDetails from "@shoelace-style/shoelace/dist/components/details/details.js";
|
||||
import SlIconButton from "@shoelace-style/shoelace/dist/components/icon-button/icon-button.js";
|
||||
import { setBasePath } from "@shoelace-style/shoelace/dist/utilities/base-path.js";
|
||||
import Cropper from "cropperjs";
|
||||
|
||||
import { notify } from "./alert";
|
||||
|
||||
setBasePath("/static/shoelace/");
|
||||
|
||||
const form = document.forms[0];
|
||||
const photobox = document.getElementById("photo-box")!;
|
||||
const photoview = document.getElementById("photo-view")!;
|
||||
const video = photoview.querySelector("video")!;
|
||||
const btnshutter = photoview.querySelector(
|
||||
" sl-icon-button[label='Take Photo']",
|
||||
) as SlIconButton;
|
||||
const btncrop = photoview.querySelector(
|
||||
" sl-icon-button[label='Crop']",
|
||||
) as SlIconButton;
|
||||
const btnreset = photoview.querySelector(
|
||||
" sl-icon-button[label='Start Over']",
|
||||
) as SlIconButton;
|
||||
|
||||
let cropper: Cropper | null = null;
|
||||
let initialized = false;
|
||||
|
||||
async function clearCamera() {
|
||||
if (cropper) {
|
||||
cropper.getCropperCanvas()?.remove();
|
||||
cropper = null;
|
||||
}
|
||||
video.pause();
|
||||
video.srcObject = null;
|
||||
video.classList.add("invisible");
|
||||
video.parentNode?.querySelectorAll("canvas").forEach((e) => e.remove());
|
||||
btnshutter.disabled = true;
|
||||
btnshutter.classList.add("invisible");
|
||||
btncrop.disabled = true;
|
||||
btncrop.classList.add("invisible");
|
||||
btnreset.disabled = true;
|
||||
btnreset.classList.add("invisible");
|
||||
photoview.classList.remove("invisible");
|
||||
}
|
||||
|
||||
async function startCamera() {
|
||||
let stream: MediaStream;
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: true,
|
||||
audio: false,
|
||||
});
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
notify(`${ex}`, "danger", "exclamation-octagon", null);
|
||||
return;
|
||||
}
|
||||
photobox.querySelectorAll(".fallback").forEach((e) => e.remove());
|
||||
btnshutter.classList.remove("invisible");
|
||||
video.classList.remove("invisible");
|
||||
video.srcObject = stream;
|
||||
video.play();
|
||||
}
|
||||
|
||||
async function submitForm(data: FormData) {
|
||||
const btn = form.querySelector("sl-button[type='submit']") as SlButton;
|
||||
btn.loading = true;
|
||||
let r: Response | null = null;
|
||||
try {
|
||||
r = await fetch("", {
|
||||
method: "POST",
|
||||
body: data,
|
||||
});
|
||||
} catch (e) {
|
||||
notify(
|
||||
`Failed to submit form: ${e}`,
|
||||
"danger",
|
||||
"exclamation-octagon",
|
||||
null,
|
||||
);
|
||||
}
|
||||
btn.loading = false;
|
||||
if (r) {
|
||||
if (r.ok) {
|
||||
notify("Successfully updated transaction");
|
||||
} else {
|
||||
const html = await r.text();
|
||||
if (html) {
|
||||
const doc = new DOMParser().parseFromString(html, "text/html");
|
||||
notify(
|
||||
doc.body.textContent ?? "",
|
||||
"danger",
|
||||
"exclamation-octagon",
|
||||
null,
|
||||
);
|
||||
} else {
|
||||
notify(r.statusText, "danger", "exclamation-octagon", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function takePhoto() {
|
||||
btnshutter.disabled = true;
|
||||
btnshutter.classList.add("invisible");
|
||||
video.pause();
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) {
|
||||
notify(
|
||||
"Failed to get canvas 2D rendering context",
|
||||
"danger",
|
||||
"exclamation-octagon",
|
||||
null,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const width = video.videoWidth;
|
||||
const height = video.videoHeight;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
context.drawImage(video, 0, 0, width, height);
|
||||
video.srcObject = null;
|
||||
video.classList.add("invisible");
|
||||
video.parentNode!.appendChild(canvas);
|
||||
cropper = new Cropper(canvas);
|
||||
cropper.getCropperCanvas()!.style.height = `${height}px`;
|
||||
btncrop.disabled = false;
|
||||
btncrop.classList.remove("invisible");
|
||||
btnreset.disabled = false;
|
||||
btnreset.classList.remove("invisible");
|
||||
}
|
||||
|
||||
photobox.addEventListener("sl-show", () => {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
clearCamera();
|
||||
startCamera();
|
||||
}
|
||||
});
|
||||
|
||||
video.addEventListener("canplay", () => {
|
||||
btnshutter.disabled = false;
|
||||
});
|
||||
|
||||
btnshutter.addEventListener("click", async () => {
|
||||
takePhoto();
|
||||
});
|
||||
|
||||
btncrop.addEventListener("click", async () => {
|
||||
if (cropper) {
|
||||
const canvas = await cropper.getCropperSelection()?.$toCanvas();
|
||||
if (canvas) {
|
||||
canvas.setAttribute("id", "camera-photo");
|
||||
video.parentNode!.appendChild(canvas);
|
||||
cropper.getCropperCanvas()?.remove();
|
||||
btncrop.disabled = true;
|
||||
btncrop.classList.add("invisible");
|
||||
cropper = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
btnreset.addEventListener("click", () => {
|
||||
clearCamera();
|
||||
startCamera();
|
||||
});
|
||||
|
||||
form.addEventListener("submit", async (evt) => {
|
||||
evt.preventDefault();
|
||||
let data = new FormData(form);
|
||||
const canvas = document.getElementById("camera-photo") as HTMLCanvasElement;
|
||||
if (canvas) {
|
||||
canvas.toBlob((blob: Blob | null) => {
|
||||
if (blob) {
|
||||
data.append("photo", blob, "photo.jpg");
|
||||
}
|
||||
submitForm(data);
|
||||
}, "image/jpeg");
|
||||
} else {
|
||||
submitForm(data);
|
||||
}
|
||||
});
|
||||
|
||||
form.addEventListener("reset", () => {
|
||||
document.querySelectorAll("sl-details").forEach((e: SlDetails) => e.hide());
|
||||
clearCamera();
|
||||
initialized = false;
|
||||
});
|
Loading…
Reference in New Issue