kempo-inventory 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +429 -0
- package/admin/categories/index.page.html +229 -0
- package/admin/edit/index.page.html +51 -0
- package/admin/fields/index.page.html +300 -0
- package/admin/index.page.html +203 -0
- package/admin/nav.global.html +3 -0
- package/admin/new/index.page.html +43 -0
- package/install.js +19 -0
- package/kempo-config.json +103 -0
- package/package.json +59 -0
- package/public/api/categories/DELETE.js +12 -0
- package/public/api/categories/GET.js +12 -0
- package/public/api/categories/PUT.js +20 -0
- package/public/api/export/GET.js +31 -0
- package/public/api/fields/GET.js +23 -0
- package/public/api/fields/POST.js +12 -0
- package/public/api/fields/[key]/DELETE.js +11 -0
- package/public/api/fields/[key]/PATCH.js +16 -0
- package/public/api/fields/[key]/suggestions/GET.js +12 -0
- package/public/api/import/apply/POST.js +25 -0
- package/public/api/import/preview/POST.js +30 -0
- package/public/api/items/GET.js +35 -0
- package/public/api/items/POST.js +12 -0
- package/public/api/items/[id]/DELETE.js +11 -0
- package/public/api/items/[id]/GET.js +15 -0
- package/public/api/items/[id]/PATCH.js +12 -0
- package/public/api/items/[id]/adjust/POST.js +12 -0
- package/public/api/movements/GET.js +17 -0
- package/public/api/tags/GET.js +12 -0
- package/public/components/BarcodeScanner.js +198 -0
- package/public/components/ImageCropDialog.js +94 -0
- package/public/components/ImportExport.js +194 -0
- package/public/components/ItemDialog.js +135 -0
- package/public/components/ItemFields.js +441 -0
- package/public/components/ItemForm.js +186 -0
- package/public/components/NewItemPage.js +92 -0
- package/public/components/RowAction.js +34 -0
- package/public/components/flash.js +20 -0
- package/public/sdk.js +96 -0
- package/public/vendor/LICENSES.md +12 -0
- package/public/vendor/ts-custom-error.js +8 -0
- package/public/vendor/zxing-browser.js +8 -0
- package/public/vendor/zxing-library.js +27 -0
- package/sdk.js +65 -0
- package/server/db/schema.js +74 -0
- package/server/utils/access.js +43 -0
- package/server/utils/authorize.js +13 -0
- package/server/utils/categories.js +267 -0
- package/server/utils/categoryLogic.js +54 -0
- package/server/utils/events.js +31 -0
- package/server/utils/fieldTypes.js +227 -0
- package/server/utils/fields.js +238 -0
- package/server/utils/hooks.js +30 -0
- package/server/utils/importExport.js +239 -0
- package/server/utils/importFile.js +148 -0
- package/server/utils/importStage.js +76 -0
- package/server/utils/items.js +412 -0
- package/server/utils/media.js +245 -0
- package/server/utils/mediaSafety.js +51 -0
- package/server/utils/ownership.js +44 -0
- package/server/utils/rank.js +31 -0
- package/server/utils/ratio.js +31 -0
- package/server/utils/suggestions.js +36 -0
- package/server/utils/tagList.js +25 -0
- package/server/utils/tags.js +47 -0
- package/server/utils/zip.js +88 -0
- package/server/utils/zipRead.js +99 -0
- package/update.js +126 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { html, render, nothing } from '/kempo-ui/lit-all.min.js';
|
|
2
|
+
import Toast from '/kempo-ui/components/Toast.js';
|
|
3
|
+
import { getItems, getCategories, createItem, updateItem } from '/inventory/sdk.js';
|
|
4
|
+
import { fieldTemplate, readField, applicableFields, inputId, tagsValue, suggestTags, readTags, lowerTagInput, lowerTagChips } from '/inventory/components/ItemFields.js';
|
|
5
|
+
import { scanBarcode } from '/inventory/components/BarcodeScanner.js';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
The item form, on its own: a container element holding every box, with no dialog around it, so the same
|
|
9
|
+
form can sit in a dialog (editing an item) or fill a page (adding one, see NewItemPage.js).
|
|
10
|
+
|
|
11
|
+
const form = createItemForm({ item, fields, defaults, askQuantity });
|
|
12
|
+
parent.append(form.$root);
|
|
13
|
+
const saved = await form.save(); // the saved item, or null (a problem was already shown to the person)
|
|
14
|
+
form.isDirty(); // has anything been changed since it opened? (for "unsaved work" warnings)
|
|
15
|
+
|
|
16
|
+
`fields` is the full field list from getFields(): the form shows the default fields plus those of the
|
|
17
|
+
item's category, and swaps them as the Category box changes. `defaults` pre-fills a new item
|
|
18
|
+
({ sku, name, quantity, category, tags, fields }); `askQuantity: false` leaves out the starting
|
|
19
|
+
quantity box (the item just exists, with defaults.quantity).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
Fills the Category box with the categories already in use the first time it is focused. The box
|
|
24
|
+
is also scrolled to the middle of the form so the list is not hidden behind the buttons.
|
|
25
|
+
*/
|
|
26
|
+
const offerCategories = async $combobox => {
|
|
27
|
+
setTimeout(() => $combobox.scrollIntoView({ block: 'center', behavior: 'smooth' }), 120);
|
|
28
|
+
if($combobox.dataset.loaded) return;
|
|
29
|
+
$combobox.dataset.loaded = 'true';
|
|
30
|
+
const [error, data] = await getCategories({ includeEmpty: true }); // categories made in the admin count even before an item uses them
|
|
31
|
+
if(!error) $combobox.setOptions(data.categories.map(c => c.name));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const createItemForm = ({ item = null, fields = [], defaults = {}, askQuantity = true, context = { linked: {}, media: {} }, inDialog = false } = {}) => {
|
|
35
|
+
const editing = Boolean(item);
|
|
36
|
+
/* An item an extension owns keeps its SKU, name and category: the server refuses changes to them, so the boxes are not offered. */
|
|
37
|
+
const locked = editing && Boolean(item.owner);
|
|
38
|
+
const $root = document.createElement('div');
|
|
39
|
+
if(inDialog){
|
|
40
|
+
$root.className = 'p';
|
|
41
|
+
$root.style.cssText = 'max-height: calc(100vh - 14rem); overflow-y: auto;';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
A hardware barcode scanner types the code and then presses Enter, which a dialog would take as
|
|
46
|
+
"submit the form". In the SKU box Enter just moves on to the name instead.
|
|
47
|
+
*/
|
|
48
|
+
const skuKeydown = event => {
|
|
49
|
+
if(event.key !== 'Enter') return;
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
event.stopPropagation();
|
|
52
|
+
$root.querySelector('#dlg-name')?.focus();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const scan = async () => {
|
|
56
|
+
const code = (await scanBarcode())?.trim();
|
|
57
|
+
if(!code) return;
|
|
58
|
+
$root.querySelector('#dlg-sku').value = code;
|
|
59
|
+
if(code === item?.sku) return;
|
|
60
|
+
const [, found] = await getItems({ q: code, limit: 5 });
|
|
61
|
+
const clash = found?.items.find(i => i.sku.toLowerCase() === code.toLowerCase() && i.id !== item?.id);
|
|
62
|
+
if(clash) Toast.warning(`"${code}" is already used by ${clash.name}`);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
The custom fields shown depend on the category typed in the box. What was typed in a field is kept
|
|
67
|
+
when the category changes, so a field both categories share does not lose it.
|
|
68
|
+
*/
|
|
69
|
+
const kept = {};
|
|
70
|
+
let shown = [];
|
|
71
|
+
const showFields = () => {
|
|
72
|
+
const $box = $root.querySelector('#dlg-fields');
|
|
73
|
+
if(!$box) return;
|
|
74
|
+
const next = applicableFields(fields, $root.querySelector('#dlg-category')?.value ?? '');
|
|
75
|
+
if(next.length === shown.length && next.every((f, i) => f === shown[i])) return;
|
|
76
|
+
for(const field of shown){
|
|
77
|
+
try { kept[field.key] = readField($root, field); } catch { /* an incomplete value is not worth keeping */ }
|
|
78
|
+
}
|
|
79
|
+
/* Files uploaded in this form are not in the item's media list yet, so carry their details across too. */
|
|
80
|
+
for(const field of shown){
|
|
81
|
+
if(field.type !== 'media') continue;
|
|
82
|
+
const $media = $root.querySelector(`#${inputId(field)}`);
|
|
83
|
+
if($media?.assets) context.media = { ...context.media, ...$media.assets };
|
|
84
|
+
}
|
|
85
|
+
shown = next;
|
|
86
|
+
/*
|
|
87
|
+
Cleared first, so every field is built fresh. Re-rendering over the old fields reuses their parts
|
|
88
|
+
by position, which left a second, stale copy of a field (a photo picker the save then ignored)
|
|
89
|
+
whenever the category's fields changed the order.
|
|
90
|
+
*/
|
|
91
|
+
render(nothing, $box);
|
|
92
|
+
render(html`${shown.map(f => fieldTemplate(f, f.key in kept ? kept[f.key] : (item?.fields[f.key] ?? defaults.fields?.[f.key]), context))}`, $box);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
render(html`
|
|
96
|
+
${locked ? html`<p class="mb tc-muted">Managed by the ${item.owner} extension. Its SKU, name and category can only be changed there; everything else, including stock, you can edit here.</p>` : ''}
|
|
97
|
+
<div class="d-f mb" style="gap: var(--spacer);">
|
|
98
|
+
<div class="flex">
|
|
99
|
+
<label class="d-b mb-sm" for="dlg-sku">SKU *</label>
|
|
100
|
+
<div class="d-f" style="gap: var(--spacer_h);">
|
|
101
|
+
<input type="text" id="dlg-sku" class="flex" style="width: auto; min-width: 0;" autocapitalize="off" autocomplete="off"
|
|
102
|
+
.value=${item?.sku ?? defaults.sku ?? ''} ?disabled=${locked} @keydown=${skuKeydown}>
|
|
103
|
+
<button type="button" class="btn" id="dlg-scan" ?disabled=${locked} @click=${scan}>Scan</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
${editing || !askQuantity ? '' : html`<div style="width: 9rem;"><label class="d-b mb-sm" for="dlg-qty">Starting quantity</label><input type="number" id="dlg-qty" class="full" min="0" step="1" .value=${String(defaults.quantity ?? 0)}></div>`}
|
|
107
|
+
</div>
|
|
108
|
+
<div class="mb"><label class="d-b mb-sm" for="dlg-name">Name *</label><input type="text" id="dlg-name" class="full" ?disabled=${locked} .value=${item?.name ?? defaults.name ?? ''}></div>
|
|
109
|
+
<div class="mb">
|
|
110
|
+
<label class="d-b mb-sm" for="dlg-category">Category</label>
|
|
111
|
+
<k-combobox id="dlg-category" class="full" debounce-ms="150" ?disabled=${locked} .value=${item?.category ?? defaults.category ?? ''}
|
|
112
|
+
empty-message="No categories yet. Type to add one." no-results-message="New category"
|
|
113
|
+
@focusin=${e => offerCategories(e.currentTarget)}
|
|
114
|
+
@search=${showFields} @select=${() => setTimeout(showFields)} @change=${() => setTimeout(showFields)} @focusout=${() => setTimeout(showFields, 150)}></k-combobox>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="mb"><label class="d-b mb-sm" for="dlg-tags">Tags</label><k-tags id="dlg-tags" .value=${tagsValue(item?.tags ?? defaults.tags)} .getSuggestions=${suggestTags} @input=${lowerTagInput} @change=${lowerTagChips}></k-tags></div>
|
|
117
|
+
<div class="mb"><label class="d-b mb-sm" for="dlg-description">Description</label><textarea id="dlg-description" class="full" rows="2" .value=${item?.description ?? ''}></textarea></div>
|
|
118
|
+
<div id="dlg-fields"></div>
|
|
119
|
+
`, $root);
|
|
120
|
+
showFields();
|
|
121
|
+
|
|
122
|
+
/* Everything the person has entered, as one string, for telling whether anything has changed. */
|
|
123
|
+
const snapshot = () => {
|
|
124
|
+
const values = {};
|
|
125
|
+
for(const field of shown){
|
|
126
|
+
try { values[field.key] = readField($root, field); } catch { values[field.key] = '?'; }
|
|
127
|
+
}
|
|
128
|
+
return JSON.stringify({
|
|
129
|
+
sku: $root.querySelector('#dlg-sku')?.value ?? '',
|
|
130
|
+
name: $root.querySelector('#dlg-name')?.value ?? '',
|
|
131
|
+
description: $root.querySelector('#dlg-description')?.value ?? '',
|
|
132
|
+
category: $root.querySelector('#dlg-category')?.value ?? '',
|
|
133
|
+
quantity: $root.querySelector('#dlg-qty')?.value ?? '',
|
|
134
|
+
tags: readTags($root.querySelector('#dlg-tags')),
|
|
135
|
+
values,
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
/* Taken once the boxes have settled (the tags box and the category list fill themselves in just after they appear). */
|
|
139
|
+
let opened = null;
|
|
140
|
+
setTimeout(() => { opened = snapshot(); }, 800);
|
|
141
|
+
const isDirty = () => opened !== null && snapshot() !== opened;
|
|
142
|
+
const markSaved = () => { opened = snapshot(); };
|
|
143
|
+
|
|
144
|
+
const save = async () => {
|
|
145
|
+
const values = {};
|
|
146
|
+
try {
|
|
147
|
+
for(const field of shown) values[field.key] = readField($root, field);
|
|
148
|
+
} catch(error) {
|
|
149
|
+
Toast.error(error.message);
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const data = {
|
|
153
|
+
sku: $root.querySelector('#dlg-sku').value.trim(),
|
|
154
|
+
name: $root.querySelector('#dlg-name').value.trim(),
|
|
155
|
+
description: $root.querySelector('#dlg-description').value,
|
|
156
|
+
category: ($root.querySelector('#dlg-category').value || '').trim(),
|
|
157
|
+
tags: readTags($root.querySelector('#dlg-tags')),
|
|
158
|
+
fields: values,
|
|
159
|
+
};
|
|
160
|
+
if(!data.sku || !data.name){
|
|
161
|
+
Toast.error('SKU and name are required');
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
if(locked) for(const property of ['sku', 'name', 'category']) delete data[property];
|
|
165
|
+
let error;
|
|
166
|
+
let saved;
|
|
167
|
+
if(editing){
|
|
168
|
+
[error, saved] = await updateItem(item.id, data);
|
|
169
|
+
} else {
|
|
170
|
+
/* Blank fields are left out on create so hooks can tell "not provided" from a value. */
|
|
171
|
+
data.fields = Object.fromEntries(Object.entries(values).filter(([, v]) => v !== '' && !(Array.isArray(v) && !v.length)));
|
|
172
|
+
if(!data.category) delete data.category;
|
|
173
|
+
if(!data.tags.length) delete data.tags;
|
|
174
|
+
data.quantity = askQuantity ? Number($root.querySelector('#dlg-qty').value || 0) : Number(defaults.quantity ?? 0);
|
|
175
|
+
[error, saved] = await createItem(data);
|
|
176
|
+
}
|
|
177
|
+
if(error){
|
|
178
|
+
Toast.error(error.msg || `Failed to ${editing ? 'save' : 'create'} item`);
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
markSaved();
|
|
182
|
+
return saved.item;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return { $root, save, isDirty, markSaved, editing };
|
|
186
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { html, render } from '/kempo-ui/lit-all.min.js';
|
|
2
|
+
import Dialog from '/kempo-ui/components/Dialog.js';
|
|
3
|
+
import { createItemForm } from '/inventory/components/ItemForm.js';
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Adding and editing an item are pages, not dialogs. A dialog closes with a stray tap outside it and takes
|
|
7
|
+
everything entered (photos taken with the camera included) with it. These pages keep the work, and warn
|
|
8
|
+
before the browser leaves it (a link, the back button, a refresh, closing the tab) while anything entered
|
|
9
|
+
has not been saved.
|
|
10
|
+
|
|
11
|
+
import { mountNewItemPage, mountEditItemPage, safeNext } from '/inventory/components/NewItemPage.js';
|
|
12
|
+
mountNewItemPage({ $container, fields, defaults: { category: 'Paint' }, next: '/category?name=Paint' });
|
|
13
|
+
mountEditItemPage({ $container, item, fields, context, next: '/items/' + item.id }); // item and context from getItem()
|
|
14
|
+
|
|
15
|
+
After a save it goes to `next` (a path on this site) and that page shows "Added <name>" or "Saved <name>"
|
|
16
|
+
(see flash.js). "Create and add another" starts a completely empty form instead (a pre-filled SKU, name or
|
|
17
|
+
category is not carried over). `askQuantity` leaves out the starting-quantity box when adding.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/* A path on this site, or the fallback: never another site's address (an open redirect). */
|
|
21
|
+
export const safeNext = (value, fallback) => (typeof value === 'string' && /^\/(?!\/|\\)/.test(value) ? value : fallback);
|
|
22
|
+
|
|
23
|
+
const FLASH_KEY = 'inventory.flash';
|
|
24
|
+
|
|
25
|
+
/* The message a page shows once, on the next page (see flash.js). */
|
|
26
|
+
const flash = message => {
|
|
27
|
+
try { sessionStorage.setItem(FLASH_KEY, JSON.stringify({ message })); } catch { /* the message is just not shown */ }
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const mountItemPage = ({ $container, item = null, fields = [], defaults = {}, askQuantity = true, context, next = '/', cancelLabel = 'Cancel' } = {}) => {
|
|
31
|
+
const editing = Boolean(item);
|
|
32
|
+
const form = createItemForm({ item, fields, defaults, askQuantity, context });
|
|
33
|
+
let leaving = false;
|
|
34
|
+
let saving = false;
|
|
35
|
+
|
|
36
|
+
/* The browser's own "Leave site? Changes you made may not be saved" prompt. */
|
|
37
|
+
const warn = event => {
|
|
38
|
+
if(leaving || !form.isDirty()) return;
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
event.returnValue = '';
|
|
41
|
+
};
|
|
42
|
+
window.addEventListener('beforeunload', warn);
|
|
43
|
+
/* Anything else that wants to move the page (the offline redirect) asks first. */
|
|
44
|
+
window.hasUnsavedInventoryWork = () => !leaving && form.isDirty();
|
|
45
|
+
|
|
46
|
+
const go = href => {
|
|
47
|
+
leaving = true;
|
|
48
|
+
location.assign(href);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const submit = async again => {
|
|
52
|
+
if(saving) return;
|
|
53
|
+
saving = true;
|
|
54
|
+
draw();
|
|
55
|
+
const saved = await form.save();
|
|
56
|
+
saving = false;
|
|
57
|
+
if(!saved){ draw(); return; }
|
|
58
|
+
flash(`${editing ? 'Saved' : 'Added'} ${saved.name}`);
|
|
59
|
+
if(again){
|
|
60
|
+
/*
|
|
61
|
+
A fresh, empty form. The address is what pre-filled the last one (a scanned SKU, a name, the
|
|
62
|
+
category), so those come out of it; where to go when finished (next) stays.
|
|
63
|
+
*/
|
|
64
|
+
const params = new URLSearchParams(location.search);
|
|
65
|
+
for(const key of ['sku', 'name', 'category', 'quantity']) params.delete(key);
|
|
66
|
+
go(location.pathname + (params.size ? '?' + params : ''));
|
|
67
|
+
} else {
|
|
68
|
+
go(next);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const cancel = () => {
|
|
73
|
+
if(!form.isDirty()){ go(next); return; }
|
|
74
|
+
Dialog.confirm('Discard what you have entered? It will be lost.', confirmed => { if(confirmed) go(next); });
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const $actions = document.createElement('div');
|
|
78
|
+
$actions.className = 'd-f mt-lg';
|
|
79
|
+
$actions.style.cssText = 'gap: var(--spacer_h); flex-wrap: wrap;';
|
|
80
|
+
const draw = () => render(html`
|
|
81
|
+
<button type="button" class="btn success" id="saveItem" ?disabled=${saving} @click=${() => submit(false)}>${saving ? 'Saving…' : editing ? 'Save' : 'Create item'}</button>
|
|
82
|
+
${editing ? '' : html`<button type="button" class="btn" id="saveAnother" ?disabled=${saving} @click=${() => submit(true)}>Create and add another</button>`}
|
|
83
|
+
<button type="button" class="btn" id="cancelItem" ?disabled=${saving} @click=${cancel}>${cancelLabel}</button>`, $actions);
|
|
84
|
+
draw();
|
|
85
|
+
|
|
86
|
+
$container.replaceChildren(form.$root, $actions);
|
|
87
|
+
return { form, go };
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const mountNewItemPage = options => mountItemPage({ ...options, item: null });
|
|
91
|
+
|
|
92
|
+
export const mountEditItemPage = ({ item, ...options } = {}) => mountItemPage({ ...options, item });
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import AdminTableControl from '/admin/components/AdminTableControl.js';
|
|
2
|
+
import { html } from '/kempo-ui/lit-all.min.js';
|
|
3
|
+
import '/kempo-ui/components/Icon.js';
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
A k-table row control that tells the page which action was chosen for its row's record.
|
|
7
|
+
The page owns the dialog and the request; this only reports the choice, as an `itemAction`
|
|
8
|
+
event on the table: { action, record }.
|
|
9
|
+
|
|
10
|
+
<inventory-row-action action="edit" icon="edit" title="Edit"></inventory-row-action>
|
|
11
|
+
*/
|
|
12
|
+
export default class RowAction extends AdminTableControl {
|
|
13
|
+
static properties = {
|
|
14
|
+
...AdminTableControl.properties,
|
|
15
|
+
action: { type: String },
|
|
16
|
+
icon: { type: String },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
constructor(){
|
|
20
|
+
super();
|
|
21
|
+
this.action = '';
|
|
22
|
+
this.icon = 'edit';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
handleAction(){
|
|
26
|
+
const record = this.record;
|
|
27
|
+
if(!record) return;
|
|
28
|
+
this.table.dispatchEvent(new CustomEvent('itemAction', { detail: { action: this.action, record } }));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
render(){ return html`<slot><k-icon name="${this.icon}"></k-icon></slot>`; }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
customElements.define('inventory-row-action', RowAction);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Toast from '/kempo-ui/components/Toast.js';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
A message left by the page you just came from ("Added Vallejo Red"), shown once. A page that follows a
|
|
5
|
+
save calls this when it loads:
|
|
6
|
+
|
|
7
|
+
import { showFlash } from '/inventory/components/flash.js';
|
|
8
|
+
showFlash();
|
|
9
|
+
*/
|
|
10
|
+
const FLASH_KEY = 'inventory.flash';
|
|
11
|
+
|
|
12
|
+
export const showFlash = () => {
|
|
13
|
+
try {
|
|
14
|
+
const raw = sessionStorage.getItem(FLASH_KEY);
|
|
15
|
+
if(!raw) return;
|
|
16
|
+
sessionStorage.removeItem(FLASH_KEY);
|
|
17
|
+
const { message } = JSON.parse(raw);
|
|
18
|
+
if(message) Toast.success(message);
|
|
19
|
+
} catch { /* nothing to show */ }
|
|
20
|
+
};
|
package/public/sdk.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const BASE = '/inventory/api';
|
|
2
|
+
|
|
3
|
+
const req = async (method, path, data) => {
|
|
4
|
+
const opts = { method, headers: {} };
|
|
5
|
+
if(data !== undefined && method !== 'GET'){
|
|
6
|
+
opts.headers['Content-Type'] = 'application/json';
|
|
7
|
+
opts.body = JSON.stringify(data);
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
const res = await fetch(path, opts);
|
|
11
|
+
const json = await res.json().catch(() => ({}));
|
|
12
|
+
if(!res.ok) return [{ code: res.status, msg: json.error || 'An error occurred' }, null];
|
|
13
|
+
return [null, json];
|
|
14
|
+
} catch {
|
|
15
|
+
return [{ code: 503, msg: 'Network error' }, null];
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const buildQuery = params => {
|
|
20
|
+
const entries = Object.entries(params).filter(([, v]) => v !== undefined && v !== null && v !== '' && v !== false);
|
|
21
|
+
if(!entries.length) return '';
|
|
22
|
+
return '?' + entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(typeof v === 'object' ? JSON.stringify(v) : v)}`).join('&');
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const enc = encodeURIComponent;
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
Items. `filters` is an object of field key -> exact value, e.g. { manufacturer: 'Acme' }.
|
|
29
|
+
*/
|
|
30
|
+
export const getItems = (params = {}) => req('GET', `${BASE}/items${buildQuery(params)}`);
|
|
31
|
+
export const getItem = id => req('GET', `${BASE}/items/${enc(id)}`);
|
|
32
|
+
export const createItem = data => req('POST', `${BASE}/items`, data);
|
|
33
|
+
export const updateItem = (id, data) => req('PATCH', `${BASE}/items/${enc(id)}`, data);
|
|
34
|
+
export const deleteItem = id => req('DELETE', `${BASE}/items/${enc(id)}`);
|
|
35
|
+
export const adjustStock = (id, data) => req('POST', `${BASE}/items/${enc(id)}/adjust`, data);
|
|
36
|
+
export const getMovements = (params = {}) => req('GET', `${BASE}/movements${buildQuery(params)}`);
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
Fields
|
|
40
|
+
*/
|
|
41
|
+
/*
|
|
42
|
+
A field belongs to one category or, with no category, to every item. getFields({ category: 'Paint' })
|
|
43
|
+
returns what an item in that category shows (the default fields plus Paint's); with no argument it
|
|
44
|
+
returns every field, each with its `category` key and `categoryName`. A field is identified by its
|
|
45
|
+
key and category, so changing or deleting a category's field passes { category }.
|
|
46
|
+
*/
|
|
47
|
+
export const getFields = (params = {}) => req('GET', `${BASE}/fields${buildQuery(params)}`);
|
|
48
|
+
export const createField = data => req('POST', `${BASE}/fields`, data);
|
|
49
|
+
export const updateField = (key, data, { category = '' } = {}) => req('PATCH', `${BASE}/fields/${enc(key)}${buildQuery({ category })}`, data);
|
|
50
|
+
export const deleteField = (key, { category = '' } = {}) => req('DELETE', `${BASE}/fields/${enc(key)}${buildQuery({ category })}`);
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
Values already used for a text field that match what has been typed, most relevant first:
|
|
54
|
+
[{ value, uses }]. With no `q` it returns the most used values.
|
|
55
|
+
*/
|
|
56
|
+
export const getSuggestions = (key, q = '', limit = 8, category = '') => req('GET', `${BASE}/fields/${enc(key)}/suggestions${buildQuery({ q, limit, category })}`);
|
|
57
|
+
|
|
58
|
+
/*
|
|
59
|
+
Import and export (top tier only). exportUrl('json' | 'zip') is a download link (the zip carries the
|
|
60
|
+
photos too). Importing is two steps with the file as the raw request body: previewImport(file) checks it
|
|
61
|
+
and reports what would happen (and keeps the upload, returning its importId); applyImport(importId,
|
|
62
|
+
'skip' | 'update') then does it. The server opens a zip, never the browser.
|
|
63
|
+
*/
|
|
64
|
+
export const exportUrl = format => `${BASE}/export?format=${format === 'zip' ? 'zip' : 'json'}`;
|
|
65
|
+
export const previewImport = async file => {
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(`${BASE}/import/preview?name=${enc(file.name)}`, { method: 'POST', headers: { 'Content-Type': 'application/octet-stream' }, body: file, credentials: 'same-origin' });
|
|
68
|
+
const json = await res.json().catch(() => ({}));
|
|
69
|
+
return res.ok ? [null, json] : [{ code: res.status, msg: json.error || 'An error occurred' }, null];
|
|
70
|
+
} catch {
|
|
71
|
+
return [{ code: 503, msg: 'Network error' }, null];
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
export const applyImport = (importId, onMatch) => req('POST', `${BASE}/import/apply`, { importId, onMatch });
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
Every tag in use on items and categories, most used first: { tags: [{ tag, count }] }. `q` narrows it to
|
|
78
|
+
tags containing that text.
|
|
79
|
+
*/
|
|
80
|
+
export const getTags = (params = {}) => req('GET', `${BASE}/tags${buildQuery(params)}`);
|
|
81
|
+
|
|
82
|
+
/*
|
|
83
|
+
Categories: { categories: [{ name, key, count, image, description }], uncategorised }. `image` is
|
|
84
|
+
{ id, kind, name, path, thumbnail } or null. Set one with a stored media id (null clears it).
|
|
85
|
+
*/
|
|
86
|
+
export const getCategories = (params = {}) => req('GET', `${BASE}/categories${buildQuery(params)}`);
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
Create or update a category; only what is passed changes: { description }, { image } (null clears
|
|
90
|
+
it) or { newName } to rename it and update its items. Resolves to { category: { name, key, image,
|
|
91
|
+
description, moved } }.
|
|
92
|
+
*/
|
|
93
|
+
export const saveCategory = (name, changes = {}) => req('PUT', `${BASE}/categories`, { name, ...changes });
|
|
94
|
+
export const setCategoryImage = (name, image = null) => saveCategory(name, { image });
|
|
95
|
+
/* Refused while any item is still in the category. */
|
|
96
|
+
export const deleteCategory = name => req('DELETE', `${BASE}/categories`, { name });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Vendored libraries
|
|
2
|
+
|
|
3
|
+
Bundled copies, so the barcode scanner works without loading anything from a CDN at runtime.
|
|
4
|
+
|
|
5
|
+
| File | Package | Licence |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| zxing-browser.js | `@zxing/browser` 0.1.5 | MIT |
|
|
8
|
+
| zxing-library.js | `@zxing/library` 0.21.0 | MIT |
|
|
9
|
+
| ts-custom-error.js | `ts-custom-error` 3.3.1 | MIT |
|
|
10
|
+
|
|
11
|
+
They are the jsDelivr `+esm` builds of those packages, with their imports rewritten to point at each
|
|
12
|
+
other. To update, download the new `+esm` builds and repeat that rewrite.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled by jsDelivr using Rollup v4.62.2 and esbuild v0.28.1.
|
|
3
|
+
* Original file: /npm/ts-custom-error@3.3.1/dist/custom-error.mjs
|
|
4
|
+
*
|
|
5
|
+
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
|
6
|
+
*/
|
|
7
|
+
function i(o,t){var r=Object.setPrototypeOf;r?r(o,t):o.__proto__=t}function u(o,t){t===void 0&&(t=o.constructor);var r=Error.captureStackTrace;r&&r(o,t)}var s=(function(){var o=function(r,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(c,n){c.__proto__=n}||function(c,n){for(var a in n)Object.prototype.hasOwnProperty.call(n,a)&&(c[a]=n[a])},o(r,e)};return function(t,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");o(t,r);function e(){this.constructor=t}t.prototype=r===null?Object.create(r):(e.prototype=r.prototype,new e)}})(),l=(function(o){s(t,o);function t(r,e){var c=this.constructor,n=o.call(this,r,e)||this;return Object.defineProperty(n,"name",{value:c.name,enumerable:!1,configurable:!0}),i(n,c.prototype),u(n),n}return t})(Error),f=function(o,t,r){if(r||arguments.length===2)for(var e=0,c=t.length,n;e<c;e++)(n||!(e in t))&&(n||(n=Array.prototype.slice.call(t,0,e)),n[e]=t[e]);return o.concat(n||Array.prototype.slice.call(t))};function p(o,t){t===void 0&&(t=Error);function r(){for(var e=[],c=0;c<arguments.length;c++)e[c]=arguments[c];if(!(this instanceof r))return new(r.bind.apply(r,f([void 0],e,!1)));t.apply(this,e),Object.defineProperty(this,"name",{value:o.name||t.name,enumerable:!1,configurable:!0}),o.apply(this,e),u(this,r)}return Object.defineProperties(r,{prototype:{value:Object.create(t.prototype,{constructor:{value:r,writable:!0,configurable:!0}})}})}export{l as CustomError,p as customErrorFactory};
|
|
8
|
+
//# sourceMappingURL=/sm/efe5755f0c050cd3a05883fc332f7bd98637e7feb45f3e212b497aa9a6ad3886.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled by jsDelivr using Rollup v4.62.2 and esbuild v0.28.1.
|
|
3
|
+
* Original file: /npm/@zxing/browser@0.1.5/esm/index.js
|
|
4
|
+
*
|
|
5
|
+
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
|
6
|
+
*/
|
|
7
|
+
import{IllegalArgumentException as M,InvertedLuminanceSource as U,LuminanceSource as q,ArgumentException as E,NotFoundException as L,ChecksumException as P,FormatException as D,DecodeHintType as N,HybridBinarizer as Q,BinaryBitmap as Z,AztecCodeReader as $,MultiFormatOneDReader as z,DataMatrixReader as J,MultiFormatReader as K,PDF417Reader as Y,QRCodeReader as X,EncodeHintType as O,QRCodeDecoderErrorCorrectionLevel as A,QRCodeEncoder as k,IllegalStateException as H}from"./zxing-library.js";import{BarcodeFormat as Se}from"./zxing-library.js";var ee=(function(){var n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,o){r.__proto__=o}||function(r,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(r[i]=o[i])},n(t,e)};return function(t,e){if(typeof e!="function"&&e!==null)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");n(t,e);function r(){this.constructor=t}t.prototype=e===null?Object.create(e):(r.prototype=e.prototype,new r)}})(),j=(function(n){ee(t,n);function t(e){var r=n.call(this,e.width,e.height)||this;return r.canvas=e,r.tempCanvasElement=null,r.buffer=t.makeBufferFromCanvasImageData(e),r}return t.makeBufferFromCanvasImageData=function(e){var r;try{r=e.getContext("2d",{willReadFrequently:!0})}catch{r=e.getContext("2d")}if(!r)throw new Error("Couldn't get canvas context.");var o=r.getImageData(0,0,e.width,e.height);return t.toGrayscaleBuffer(o.data,e.width,e.height)},t.toGrayscaleBuffer=function(e,r,o){for(var i=new Uint8ClampedArray(r*o),a=0,s=0,u=e.length;a<u;a+=4,s++){var c=void 0,d=e[a+3];if(d===0)c=255;else{var l=e[a],f=e[a+1],w=e[a+2];c=306*l+601*f+117*w+512>>10}i[s]=c}return i},t.prototype.getRow=function(e,r){if(e<0||e>=this.getHeight())throw new M("Requested row is outside the image: "+e);var o=this.getWidth(),i=e*o;return r===null?r=this.buffer.slice(i,i+o):(r.length<o&&(r=new Uint8ClampedArray(o)),r.set(this.buffer.slice(i,i+o))),r},t.prototype.getMatrix=function(){return this.buffer},t.prototype.isCropSupported=function(){return!0},t.prototype.crop=function(e,r,o,i){return n.prototype.crop.call(this,e,r,o,i),this},t.prototype.isRotateSupported=function(){return!0},t.prototype.rotateCounterClockwise=function(){return this.rotate(-90),this},t.prototype.rotateCounterClockwise45=function(){return this.rotate(-45),this},t.prototype.invert=function(){return new U(this)},t.prototype.getTempCanvasElement=function(){if(this.tempCanvasElement===null){var e=this.canvas.ownerDocument.createElement("canvas");e.width=this.canvas.width,e.height=this.canvas.height,this.tempCanvasElement=e}return this.tempCanvasElement},t.prototype.rotate=function(e){var r=this.getTempCanvasElement();if(!r)throw new Error("Could not create a Canvas element.");var o=e*t.DEGREE_TO_RADIANS,i=this.canvas.width,a=this.canvas.height,s=Math.ceil(Math.abs(Math.cos(o))*i+Math.abs(Math.sin(o))*a),u=Math.ceil(Math.abs(Math.sin(o))*i+Math.abs(Math.cos(o))*a);r.width=s,r.height=u;var c=r.getContext("2d");if(!c)throw new Error("Could not create a Canvas Context element.");return c.translate(s/2,u/2),c.rotate(o),c.drawImage(this.canvas,i/-2,a/-2),this.buffer=t.makeBufferFromCanvasImageData(r),this},t.DEGREE_TO_RADIANS=Math.PI/180,t})(q);function G(){return typeof navigator<"u"}function te(){return G()&&!!navigator.mediaDevices}function re(){return!!(te()&&navigator.mediaDevices.enumerateDevices)}var R=function(){return R=Object.assign||function(n){for(var t,e=1,r=arguments.length;e<r;e++){t=arguments[e];for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&(n[o]=t[o])}return n},R.apply(this,arguments)},v=function(n,t,e,r){function o(i){return i instanceof e?i:new e(function(a){a(i)})}return new(e||(e=Promise))(function(i,a){function s(d){try{c(r.next(d))}catch(l){a(l)}}function u(d){try{c(r.throw(d))}catch(l){a(l)}}function c(d){d.done?i(d.value):o(d.value).then(s,u)}c((r=r.apply(n,t||[])).next())})},p=function(n,t){var e={label:0,sent:function(){if(i[0]&1)throw i[1];return i[1]},trys:[],ops:[]},r,o,i,a;return a={next:s(0),throw:s(1),return:s(2)},typeof Symbol=="function"&&(a[Symbol.iterator]=function(){return this}),a;function s(c){return function(d){return u([c,d])}}function u(c){if(r)throw new TypeError("Generator is already executing.");for(;e;)try{if(r=1,o&&(i=c[0]&2?o.return:c[0]?o.throw||((i=o.return)&&i.call(o),0):o.next)&&!(i=i.call(o,c[1])).done)return i;switch(o=0,i&&(c=[c[0]&2,i.value]),c[0]){case 0:case 1:i=c;break;case 4:return e.label++,{value:c[1],done:!1};case 5:e.label++,o=c[1],c=[0];continue;case 7:c=e.ops.pop(),e.trys.pop();continue;default:if(i=e.trys,!(i=i.length>0&&i[i.length-1])&&(c[0]===6||c[0]===2)){e=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]<i[3])){e.label=c[1];break}if(c[0]===6&&e.label<i[1]){e.label=i[1],i=c;break}if(i&&e.label<i[2]){e.label=i[2],e.ops.push(c);break}i[2]&&e.ops.pop(),e.trys.pop();continue}c=t.call(n,e)}catch(d){c=[6,d],o=0}finally{r=i=0}if(c[0]&5)throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}},F=function(n){var t=typeof Symbol=="function"&&Symbol.iterator,e=t&&n[t],r=0;if(e)return e.call(n);if(n&&typeof n.length=="number")return{next:function(){return n&&r>=n.length&&(n=void 0),{value:n&&n[r++],done:!n}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},ne={delayBetweenScanAttempts:500,delayBetweenScanSuccess:500,tryPlayVideoTimeout:5e3},T=(function(){function n(t,e,r){e===void 0&&(e=new Map),r===void 0&&(r={}),this.reader=t,this.hints=e,this.options=R(R({},ne),r)}return Object.defineProperty(n.prototype,"possibleFormats",{set:function(t){this.hints.set(N.POSSIBLE_FORMATS,t)},enumerable:!1,configurable:!0}),n.addVideoSource=function(t,e){try{t.srcObject=e}catch{console.error("got interrupted by new loading request")}},n.mediaStreamSetTorch=function(t,e){return v(this,void 0,void 0,function(){return p(this,function(r){switch(r.label){case 0:return[4,t.applyConstraints({advanced:[{fillLightMode:e?"flash":"off",torch:!!e}]})];case 1:return r.sent(),[2]}})})},n.mediaStreamIsTorchCompatible=function(t){var e,r,o=t.getVideoTracks();try{for(var i=F(o),a=i.next();!a.done;a=i.next()){var s=a.value;if(n.mediaStreamIsTorchCompatibleTrack(s))return!0}}catch(u){e={error:u}}finally{try{a&&!a.done&&(r=i.return)&&r.call(i)}finally{if(e)throw e.error}}return!1},n.mediaStreamIsTorchCompatibleTrack=function(t){try{var e=t.getCapabilities();return"torch"in e}catch(r){return console.error(r),console.warn("Your browser may be not fully compatible with WebRTC and/or ImageCapture specs. Torch will not be available."),!1}},n.isVideoPlaying=function(t){return t.currentTime>0&&!t.paused&&t.readyState>2},n.getMediaElement=function(t,e){var r=document.getElementById(t);if(!r)throw new E("element with id '".concat(t,"' not found"));if(r.nodeName.toLowerCase()!==e.toLowerCase())throw new E("element with id '".concat(t,"' must be an ").concat(e," element"));return r},n.createVideoElement=function(t){if(t instanceof HTMLVideoElement)return t;if(typeof t=="string")return n.getMediaElement(t,"video");if(!t&&typeof document<"u"){var e=document.createElement("video");return e.width=200,e.height=200,e}throw new Error("Couldn't get videoElement from videoSource!")},n.prepareImageElement=function(t){if(t instanceof HTMLImageElement)return t;if(typeof t=="string")return n.getMediaElement(t,"img");if(typeof t>"u"){var e=document.createElement("img");return e.width=200,e.height=200,e}throw new Error("Couldn't get imageElement from imageSource!")},n.prepareVideoElement=function(t){var e=n.createVideoElement(t);return e.setAttribute("autoplay","true"),e.setAttribute("muted","true"),e.setAttribute("playsinline","true"),e},n.isImageLoaded=function(t){return!(!t.complete||t.naturalWidth===0)},n.createBinaryBitmapFromCanvas=function(t){var e=new j(t),r=new Q(e);return new Z(r)},n.drawImageOnCanvas=function(t,e){t.drawImage(e,0,0)},n.getMediaElementDimensions=function(t){if(t instanceof HTMLVideoElement)return{height:t.videoHeight,width:t.videoWidth};if(t instanceof HTMLImageElement)return{height:t.naturalHeight||t.height,width:t.naturalWidth||t.width};throw new Error("Couldn't find the Source's dimensions!")},n.createCaptureCanvas=function(t){if(!t)throw new E("Cannot create a capture canvas without a media element.");if(typeof document>"u")throw new Error(`The page "Document" is undefined, make sure you're running in a browser.`);var e=document.createElement("canvas"),r=n.getMediaElementDimensions(t),o=r.width,i=r.height;return e.style.width=o+"px",e.style.height=i+"px",e.width=o,e.height=i,e},n.tryPlayVideo=function(t){return v(this,void 0,void 0,function(){var e;return p(this,function(r){switch(r.label){case 0:if(t?.ended)return console.error("Trying to play video that has ended."),[2,!1];if(n.isVideoPlaying(t))return console.warn("Trying to play video that is already playing."),[2,!0];r.label=1;case 1:return r.trys.push([1,3,,4]),[4,t.play()];case 2:return r.sent(),[2,!0];case 3:return e=r.sent(),console.warn("It was not possible to play the video.",e),[2,!1];case 4:return[2]}})})},n.createCanvasFromMediaElement=function(t){var e=n.createCaptureCanvas(t),r=e.getContext("2d");if(!r)throw new Error("Couldn't find Canvas 2D Context.");return n.drawImageOnCanvas(r,t),e},n.createBinaryBitmapFromMediaElem=function(t){var e=n.createCanvasFromMediaElement(t);return n.createBinaryBitmapFromCanvas(e)},n.destroyImageElement=function(t){t.src="",t.removeAttribute("src"),t=void 0},n.listVideoInputDevices=function(){return v(this,void 0,void 0,function(){var t,e,r,o,i,a,s,u,c,d,l,f;return p(this,function(w){switch(w.label){case 0:if(!G())throw new Error("Can't enumerate devices, navigator is not present.");if(!re())throw new Error("Can't enumerate devices, method not supported.");return[4,navigator.mediaDevices.enumerateDevices()];case 1:t=w.sent(),e=[];try{for(r=F(t),o=r.next();!o.done;o=r.next())i=o.value,a=i.kind==="video"?"videoinput":i.kind,a==="videoinput"&&(s=i.deviceId||i.id,u=i.label||"Video device ".concat(e.length+1),c=i.groupId,d={deviceId:s,label:u,kind:a,groupId:c},e.push(d))}catch(S){l={error:S}}finally{try{o&&!o.done&&(f=r.return)&&f.call(r)}finally{if(l)throw l.error}}return[2,e]}})})},n.findDeviceById=function(t){return v(this,void 0,void 0,function(){var e;return p(this,function(r){switch(r.label){case 0:return[4,n.listVideoInputDevices()];case 1:return e=r.sent(),e?[2,e.find(function(o){return o.deviceId===t})]:[2]}})})},n.cleanVideoSource=function(t){if(t){try{t.srcObject=null}catch{t.src=""}t&&t.removeAttribute("src")}},n.releaseAllStreams=function(){n.streamTracker.length!==0&&n.streamTracker.forEach(function(t){t.getTracks().forEach(function(e){return e.stop()})}),n.streamTracker=[]},n.playVideoOnLoadAsync=function(t,e){return v(this,void 0,void 0,function(){var r;return p(this,function(o){switch(o.label){case 0:return[4,n.tryPlayVideo(t)];case 1:return r=o.sent(),r?[2,!0]:[2,new Promise(function(i,a){var s=setTimeout(function(){n.isVideoPlaying(t)||(a(!1),t.removeEventListener("canplay",u))},e),u=function(){n.tryPlayVideo(t).then(function(c){clearTimeout(s),t.removeEventListener("canplay",u),i(c)})};t.addEventListener("canplay",u)})]}})})},n.attachStreamToVideo=function(t,e,r){return r===void 0&&(r=5e3),v(this,void 0,void 0,function(){var o;return p(this,function(i){switch(i.label){case 0:return o=n.prepareVideoElement(e),n.addVideoSource(o,t),[4,n.playVideoOnLoadAsync(o,r)];case 1:return i.sent(),[2,o]}})})},n._waitImageLoad=function(t){return new Promise(function(e,r){var o=1e4,i=setTimeout(function(){n.isImageLoaded(t)||(t.removeEventListener("load",a),r())},o),a=function(){clearTimeout(i),t.removeEventListener("load",a),e()};t.addEventListener("load",a)})},n.checkCallbackFnOrThrow=function(t){if(!t)throw new E("`callbackFn` is a required parameter, you cannot capture results without it.")},n.disposeMediaStream=function(t){t.getVideoTracks().forEach(function(e){return e.stop()}),t=void 0},n.prototype.decode=function(t){var e=n.createCanvasFromMediaElement(t);return this.decodeFromCanvas(e)},n.prototype.decodeBitmap=function(t){return this.reader.decode(t,this.hints)},n.prototype.decodeFromCanvas=function(t){var e=n.createBinaryBitmapFromCanvas(t);return this.decodeBitmap(e)},n.prototype.decodeFromImageElement=function(t){return v(this,void 0,void 0,function(){var e;return p(this,function(r){switch(r.label){case 0:if(!t)throw new E("An image element must be provided.");return e=n.prepareImageElement(t),[4,this._decodeOnLoadImage(e)];case 1:return[2,r.sent()]}})})},n.prototype.decodeFromImageUrl=function(t){return v(this,void 0,void 0,function(){var e;return p(this,function(r){switch(r.label){case 0:if(!t)throw new E("An URL must be provided.");e=n.prepareImageElement(),e.src=t,r.label=1;case 1:return r.trys.push([1,,3,4]),[4,this.decodeFromImageElement(e)];case 2:return[2,r.sent()];case 3:return n.destroyImageElement(e),[7];case 4:return[2]}})})},n.prototype.decodeFromConstraints=function(t,e,r){return v(this,void 0,void 0,function(){var o,i;return p(this,function(a){switch(a.label){case 0:return n.checkCallbackFnOrThrow(r),[4,this.getUserMedia(t)];case 1:o=a.sent(),a.label=2;case 2:return a.trys.push([2,4,,5]),[4,this.decodeFromStream(o,e,r)];case 3:return[2,a.sent()];case 4:throw i=a.sent(),n.disposeMediaStream(o),i;case 5:return[2]}})})},n.prototype.decodeFromStream=function(t,e,r){return v(this,void 0,void 0,function(){var o,i,a,s,u,c,d,l,f,w=this;return p(this,function(S){switch(S.label){case 0:return n.checkCallbackFnOrThrow(r),o=this.options.tryPlayVideoTimeout,[4,n.attachStreamToVideo(t,e,o)];case 1:return i=S.sent(),a=function(){n.disposeMediaStream(t),n.cleanVideoSource(i)},s=this.scan(i,r,a),u=t.getVideoTracks(),c=R(R({},s),{stop:function(){s.stop()},streamVideoConstraintsApply:function(h,y){return v(this,void 0,void 0,function(){var m,C,g,b,I,V,B;return p(this,function(x){switch(x.label){case 0:m=y?u.filter(y):u,x.label=1;case 1:x.trys.push([1,6,7,8]),C=F(m),g=C.next(),x.label=2;case 2:return g.done?[3,5]:(b=g.value,[4,b.applyConstraints(h)]);case 3:x.sent(),x.label=4;case 4:return g=C.next(),[3,2];case 5:return[3,8];case 6:return I=x.sent(),V={error:I},[3,8];case 7:try{g&&!g.done&&(B=C.return)&&B.call(C)}finally{if(V)throw V.error}return[7];case 8:return[2]}})})},streamVideoConstraintsGet:function(h){return u.find(h).getConstraints()},streamVideoSettingsGet:function(h){return u.find(h).getSettings()},streamVideoCapabilitiesGet:function(h){return u.find(h).getCapabilities()}}),d=n.mediaStreamIsTorchCompatible(t),d&&(l=u?.find(function(h){return n.mediaStreamIsTorchCompatibleTrack(h)}),f=function(h){return v(w,void 0,void 0,function(){return p(this,function(y){switch(y.label){case 0:return[4,n.mediaStreamSetTorch(l,h)];case 1:return y.sent(),[2]}})})},c.switchTorch=f,c.stop=function(){return v(w,void 0,void 0,function(){return p(this,function(h){switch(h.label){case 0:return s.stop(),[4,f(!1)];case 1:return h.sent(),[2]}})})}),[2,c]}})})},n.prototype.decodeFromVideoDevice=function(t,e,r){return v(this,void 0,void 0,function(){var o,i;return p(this,function(a){switch(a.label){case 0:return n.checkCallbackFnOrThrow(r),t?o={deviceId:{exact:t}}:o={facingMode:"environment"},i={video:o},[4,this.decodeFromConstraints(i,e,r)];case 1:return[2,a.sent()]}})})},n.prototype.decodeFromVideoElement=function(t,e){return v(this,void 0,void 0,function(){var r,o;return p(this,function(i){switch(i.label){case 0:if(n.checkCallbackFnOrThrow(e),!t)throw new E("A video element must be provided.");return r=n.prepareVideoElement(t),o=this.options.tryPlayVideoTimeout,[4,n.playVideoOnLoadAsync(r,o)];case 1:return i.sent(),[2,this.scan(r,e)]}})})},n.prototype.decodeFromVideoUrl=function(t,e){return v(this,void 0,void 0,function(){var r,o,i,a;return p(this,function(s){switch(s.label){case 0:if(n.checkCallbackFnOrThrow(e),!t)throw new E("An URL must be provided.");return r=n.prepareVideoElement(),r.src=t,o=function(){n.cleanVideoSource(r)},i=this.options.tryPlayVideoTimeout,[4,n.playVideoOnLoadAsync(r,i)];case 1:return s.sent(),a=this.scan(r,e,o),[2,a]}})})},n.prototype.decodeOnceFromConstraints=function(t,e){return v(this,void 0,void 0,function(){var r;return p(this,function(o){switch(o.label){case 0:return[4,this.getUserMedia(t)];case 1:return r=o.sent(),[4,this.decodeOnceFromStream(r,e)];case 2:return[2,o.sent()]}})})},n.prototype.decodeOnceFromStream=function(t,e){return v(this,void 0,void 0,function(){var r,o,i;return p(this,function(a){switch(a.label){case 0:return r=!!e,[4,n.attachStreamToVideo(t,e)];case 1:o=a.sent(),a.label=2;case 2:return a.trys.push([2,,4,5]),[4,this.scanOneResult(o)];case 3:return i=a.sent(),[2,i];case 4:return r||n.cleanVideoSource(o),[7];case 5:return[2]}})})},n.prototype.decodeOnceFromVideoDevice=function(t,e){return v(this,void 0,void 0,function(){var r,o;return p(this,function(i){switch(i.label){case 0:return t?r={deviceId:{exact:t}}:r={facingMode:"environment"},o={video:r},[4,this.decodeOnceFromConstraints(o,e)];case 1:return[2,i.sent()]}})})},n.prototype.decodeOnceFromVideoElement=function(t){return v(this,void 0,void 0,function(){var e,r;return p(this,function(o){switch(o.label){case 0:if(!t)throw new E("A video element must be provided.");return e=n.prepareVideoElement(t),r=this.options.tryPlayVideoTimeout,[4,n.playVideoOnLoadAsync(e,r)];case 1:return o.sent(),[4,this.scanOneResult(e)];case 2:return[2,o.sent()]}})})},n.prototype.decodeOnceFromVideoUrl=function(t){return v(this,void 0,void 0,function(){var e,r;return p(this,function(o){switch(o.label){case 0:if(!t)throw new E("An URL must be provided.");e=n.prepareVideoElement(),e.src=t,r=this.decodeOnceFromVideoElement(e),o.label=1;case 1:return o.trys.push([1,,3,4]),[4,r];case 2:return[2,o.sent()];case 3:return n.cleanVideoSource(e),[7];case 4:return[2]}})})},n.prototype.scanOneResult=function(t,e,r,o){var i=this;return e===void 0&&(e=!0),r===void 0&&(r=!0),o===void 0&&(o=!0),new Promise(function(a,s){i.scan(t,function(u,c,d){if(u){a(u),d.stop();return}if(c){if(c instanceof L&&e||c instanceof P&&r||c instanceof D&&o)return;d.stop(),s(c)}})})},n.prototype.scan=function(t,e,r){var o=this;n.checkCallbackFnOrThrow(e);var i=n.createCaptureCanvas(t),a;try{a=i.getContext("2d",{willReadFrequently:!0})}catch{a=i.getContext("2d")}if(!a)throw new Error("Couldn't create canvas for visual element scan.");var s=function(){a=void 0,i=void 0},u=!1,c,d=function(){u=!0,clearTimeout(c),s(),r&&r()},l={stop:d},f=function(){if(!u)try{n.drawImageOnCanvas(a,t);var w=o.decodeFromCanvas(i);e(w,void 0,l),c=setTimeout(f,o.options.delayBetweenScanSuccess)}catch(m){e(void 0,m,l);var S=m instanceof P,h=m instanceof D,y=m instanceof L;if(S||h||y){c=setTimeout(f,o.options.delayBetweenScanAttempts);return}s(),r&&r(m)}};return f(),l},n.prototype._decodeOnLoadImage=function(t){return v(this,void 0,void 0,function(){var e;return p(this,function(r){switch(r.label){case 0:return e=n.isImageLoaded(t),e?[3,2]:[4,n._waitImageLoad(t)];case 1:r.sent(),r.label=2;case 2:return[2,this.decode(t)]}})})},n.prototype.getUserMedia=function(t){return v(this,void 0,void 0,function(){var e;return p(this,function(r){switch(r.label){case 0:return[4,navigator.mediaDevices.getUserMedia(t)];case 1:return e=r.sent(),n.streamTracker.push(e),[2,e]}})})},n.streamTracker=[],n})(),oe=(function(){var n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,o){r.__proto__=o}||function(r,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(r[i]=o[i])},n(t,e)};return function(t,e){if(typeof e!="function"&&e!==null)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");n(t,e);function r(){this.constructor=t}t.prototype=e===null?Object.create(e):(r.prototype=e.prototype,new r)}})(),ie=(function(n){oe(t,n);function t(e,r){return n.call(this,new $,e,r)||this}return t})(T),ae=(function(){var n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,o){r.__proto__=o}||function(r,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(r[i]=o[i])},n(t,e)};return function(t,e){if(typeof e!="function"&&e!==null)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");n(t,e);function r(){this.constructor=t}t.prototype=e===null?Object.create(e):(r.prototype=e.prototype,new r)}})(),ce=(function(n){ae(t,n);function t(e,r){return n.call(this,new z(e),e,r)||this}return t})(T),se=(function(){var n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,o){r.__proto__=o}||function(r,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(r[i]=o[i])},n(t,e)};return function(t,e){if(typeof e!="function"&&e!==null)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");n(t,e);function r(){this.constructor=t}t.prototype=e===null?Object.create(e):(r.prototype=e.prototype,new r)}})(),ue=(function(n){se(t,n);function t(e,r){return n.call(this,new J,e,r)||this}return t})(T),de=(function(){var n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,o){r.__proto__=o}||function(r,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(r[i]=o[i])},n(t,e)};return function(t,e){if(typeof e!="function"&&e!==null)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");n(t,e);function r(){this.constructor=t}t.prototype=e===null?Object.create(e):(r.prototype=e.prototype,new r)}})(),fe=(function(n){de(t,n);function t(e,r){var o=this,i=new K;return i.setHints(e),o=n.call(this,i,e,r)||this,o.reader=i,o}return Object.defineProperty(t.prototype,"possibleFormats",{set:function(e){this.hints.set(N.POSSIBLE_FORMATS,e),this.reader.setHints(this.hints)},enumerable:!1,configurable:!0}),t.prototype.decodeBitmap=function(e){return this.reader.decodeWithState(e)},t.prototype.setHints=function(e){this.hints=e,this.reader.setHints(this.hints)},t})(T),le=(function(){var n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,o){r.__proto__=o}||function(r,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(r[i]=o[i])},n(t,e)};return function(t,e){if(typeof e!="function"&&e!==null)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");n(t,e);function r(){this.constructor=t}t.prototype=e===null?Object.create(e):(r.prototype=e.prototype,new r)}})(),he=(function(n){le(t,n);function t(e,r){return n.call(this,new Y,e,r)||this}return t})(T),ve=(function(){var n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,o){r.__proto__=o}||function(r,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(r[i]=o[i])},n(t,e)};return function(t,e){if(typeof e!="function"&&e!==null)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");n(t,e);function r(){this.constructor=t}t.prototype=e===null?Object.create(e):(r.prototype=e.prototype,new r)}})(),pe=(function(n){ve(t,n);function t(e,r){return n.call(this,new X,e,r)||this}return t})(T),_="http://www.w3.org/2000/svg",me=(function(){function n(t){if(typeof t=="string"){var e=document.getElementById(t);if(!e)throw new Error("Could not find a Container element with '".concat(t,"'."));this.containerElement=e}else this.containerElement=t}return n.prototype.write=function(t,e,r,o){if(t.length===0)throw new M("Found empty contents");if(e<0||r<0)throw new M("Requested dimensions are too small: "+e+"x"+r);var i=o&&o.get(O.MARGIN)!==void 0?Number.parseInt(o.get(O.MARGIN).toString(),10):n.QUIET_ZONE_SIZE,a=this.encode(o,t);return this.renderResult(a,e,r,i)},n.prototype.createSVGElement=function(t,e){var r=document.createElementNS(n.SVG_NS,"svg");return r.setAttributeNS(_,"width",e.toString()),r.setAttributeNS(_,"height",t.toString()),r},n.prototype.createSvgPathPlaceholderElement=function(t,e){var r=document.createElementNS(n.SVG_NS,"path");return r.setAttributeNS(_,"d","M0 0h".concat(t,"v").concat(e,"H0z")),r.setAttributeNS(_,"fill","none"),r},n.prototype.createSvgRectElement=function(t,e,r,o){var i=document.createElementNS(n.SVG_NS,"rect");return i.setAttributeNS(_,"x",t.toString()),i.setAttributeNS(_,"y",e.toString()),i.setAttributeNS(_,"height",r.toString()),i.setAttributeNS(_,"width",o.toString()),i.setAttributeNS(_,"fill","#000000"),i},n.prototype.encode=function(t,e){var r=A.L;if(t&&t.get(O.ERROR_CORRECTION)!==void 0){var o=t.get(O.ERROR_CORRECTION).toString();r=A.fromString(o)}var i=k.encode(e,r,t);return i},n.prototype.renderResult=function(t,e,r,o){var i=t.getMatrix();if(i===null)throw new H;var a=i.getWidth(),s=i.getHeight(),u=a+o*2,c=s+o*2,d=Math.max(e,u),l=Math.max(r,c),f=Math.min(Math.floor(d/u),Math.floor(l/c)),w=Math.floor((d-a*f)/2),S=Math.floor((l-s*f)/2),h=this.createSVGElement(d,l),y=this.createSvgPathPlaceholderElement(e,r);h.appendChild(y),this.containerElement.appendChild(h);for(var m=0,C=S;m<s;m++,C+=f)for(var g=0,b=w;g<a;g++,b+=f)if(i.get(g,m)===1){var I=this.createSvgRectElement(b,C,f,f);h.appendChild(I)}return h},n.QUIET_ZONE_SIZE=4,n.SVG_NS="http://www.w3.org/2000/svg",n})(),W="http://www.w3.org/2000/svg",we=(function(){function n(){}return n.prototype.write=function(t,e,r,o){if(t.length===0)throw new M("Found empty contents");if(e<0||r<0)throw new M("Requested dimensions are too small: "+e+"x"+r);var i=A.L,a=n.QUIET_ZONE_SIZE;if(o){if(o.get(O.ERROR_CORRECTION)!==void 0){var s=o.get(O.ERROR_CORRECTION).toString();i=A.fromString(s)}o.get(O.MARGIN)!==void 0&&(a=Number.parseInt(o.get(O.MARGIN).toString(),10))}var u=k.encode(t,i,o);return this.renderResult(u,e,r,a)},n.prototype.writeToDom=function(t,e,r,o,i){if(typeof t=="string"){var a=document.querySelector(t);if(!a)throw new Error("Could no find the target HTML element.");t=a}var s=this.write(e,r,o,i);t instanceof HTMLElement&&t.appendChild(s)},n.prototype.renderResult=function(t,e,r,o){var i=t.getMatrix();if(i===null)throw new H;for(var a=i.getWidth(),s=i.getHeight(),u=a+o*2,c=s+o*2,d=Math.max(e,u),l=Math.max(r,c),f=Math.min(Math.floor(d/u),Math.floor(l/c)),w=Math.floor((d-a*f)/2),S=Math.floor((l-s*f)/2),h=this.createSVGElement(d,l),y=0,m=S;y<s;y++,m+=f)for(var C=0,g=w;C<a;C++,g+=f)if(i.get(C,y)===1){var b=this.createSvgRectElement(g,m,f,f);h.appendChild(b)}return h},n.prototype.createSVGElement=function(t,e){var r=document.createElementNS(W,"svg"),o=t.toString(),i=e.toString();return r.setAttribute("height",i),r.setAttribute("width",o),r.setAttribute("viewBox","0 0 "+o+" "+i),r},n.prototype.createSvgRectElement=function(t,e,r,o){var i=document.createElementNS(W,"rect");return i.setAttribute("x",t.toString()),i.setAttribute("y",e.toString()),i.setAttribute("height",r.toString()),i.setAttribute("width",o.toString()),i.setAttribute("fill","#000000"),i},n.QUIET_ZONE_SIZE=4,n})();export{Se as BarcodeFormat,ie as BrowserAztecCodeReader,T as BrowserCodeReader,me as BrowserCodeSvgWriter,ue as BrowserDatamatrixCodeReader,ce as BrowserMultiFormatOneDReader,fe as BrowserMultiFormatReader,he as BrowserPDF417Reader,pe as BrowserQRCodeReader,we as BrowserQRCodeSvgWriter,j as HTMLCanvasElementLuminanceSource};
|
|
8
|
+
//# sourceMappingURL=/sm/defa0442a75c447087d00395007e00f60e47c7c9e901e153c3f46c8b2561de79.map
|