scenri 0.9.4 → 0.10.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/CHANGELOG.md +61 -0
- package/dist/{chunk-3ZRMKVBB.js → chunk-WORM6GB3.js} +128 -3
- package/dist/{cli-MQO66C5O.js → cli-EXASA5BK.js} +3 -3
- package/dist/index.js +2 -2
- package/dist/serve.js +2156 -336
- package/dist/{src-3ITTV2UE.js → src-4N4TS3HO.js} +3 -3
- package/package.json +1 -1
- package/studio-dist/assets/facial-2-BmVnDzjJ.webp +0 -0
- package/studio-dist/assets/facial-3-DZtKCJBc.webp +0 -0
- package/studio-dist/assets/facial-4-D3bBk4Y-.webp +0 -0
- package/studio-dist/assets/facial-5-BZUOhBlt.webp +0 -0
- package/studio-dist/assets/facial-6-BjNlybGd.webp +0 -0
- package/studio-dist/assets/freckles-1-EZ0x3-vK.webp +0 -0
- package/studio-dist/assets/freckles-2-DuHEq26I.webp +0 -0
- package/studio-dist/assets/freckles-3-WGP6r0Fh.webp +0 -0
- package/studio-dist/assets/glasses-1-DPlo67j6.webp +0 -0
- package/studio-dist/assets/glasses-2-mRgaHYWV.webp +0 -0
- package/studio-dist/assets/glasses-3-DuTs0m8y.webp +0 -0
- package/studio-dist/assets/glasses-4-DGjoOjFS.webp +0 -0
- package/studio-dist/assets/glasses-5-DoEVdOaY.webp +0 -0
- package/studio-dist/assets/glasses-6-CcUVUrfz.webp +0 -0
- package/studio-dist/assets/{index-BGts89lc.css → index-CVbs0aDq.css} +1 -1
- package/studio-dist/assets/index-CyQ6cSKd.js +107 -0
- package/studio-dist/assets/length-1-androgynous-DiFL-HbJ.webp +0 -0
- package/studio-dist/assets/length-1-man-OI1u_7yA.webp +0 -0
- package/studio-dist/assets/length-1-woman-BbYkzjnG.webp +0 -0
- package/studio-dist/assets/length-2-androgynous-zBgi1XzH.webp +0 -0
- package/studio-dist/assets/length-2-man-Df3fVDUO.webp +0 -0
- package/studio-dist/assets/length-2-woman-BP1NSRcI.webp +0 -0
- package/studio-dist/assets/length-3-androgynous-BN_SLD6d.webp +0 -0
- package/studio-dist/assets/length-3-man-Dk_3OkKz.webp +0 -0
- package/studio-dist/assets/length-3-woman-BFV6b4If.webp +0 -0
- package/studio-dist/assets/length-4-androgynous-BblLWRi-.webp +0 -0
- package/studio-dist/assets/length-4-man-Cszq08t7.webp +0 -0
- package/studio-dist/assets/length-4-woman-B8gzWrbX.webp +0 -0
- package/studio-dist/assets/length-5-androgynous-DwxP_mgy.webp +0 -0
- package/studio-dist/assets/length-5-man-Yt6YRiWE.webp +0 -0
- package/studio-dist/assets/length-5-woman-BTz0YJMm.webp +0 -0
- package/studio-dist/assets/length-6-androgynous-BD7OoH6P.webp +0 -0
- package/studio-dist/assets/length-6-man-oceHSSPF.webp +0 -0
- package/studio-dist/assets/length-6-woman-BvX37wCz.webp +0 -0
- package/studio-dist/assets/makeup-1-CvGOSJO_.webp +0 -0
- package/studio-dist/assets/makeup-3-yoncgA8G.webp +0 -0
- package/studio-dist/assets/makeup-4-Buulx0wj.webp +0 -0
- package/studio-dist/assets/makeup-5-5Y_Qpn53.webp +0 -0
- package/studio-dist/assets/makeup-6-CSQm_m8e.webp +0 -0
- package/studio-dist/assets/prosthetic-1-C-bbKoKE.webp +0 -0
- package/studio-dist/assets/prosthetic-2-DCGXnKzb.webp +0 -0
- package/studio-dist/assets/prosthetic-3-BW_gdjlG.webp +0 -0
- package/studio-dist/assets/prosthetic-4-ec_5-7Tb.webp +0 -0
- package/studio-dist/assets/prosthetic-5-res5u7gj.webp +0 -0
- package/studio-dist/assets/prosthetic-6-0AvaEGMf.webp +0 -0
- package/studio-dist/assets/tattoo-3-33-3gd4C.webp +0 -0
- package/studio-dist/assets/tattoo-4-BW22hxdb.webp +0 -0
- package/studio-dist/assets/tattoo-5-BWJwjBfm.webp +0 -0
- package/studio-dist/index.html +2 -2
- package/studio-dist/assets/index-uiX1mW43.js +0 -104
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.0](https://github.com/tonygorb/Scenri/compare/v0.9.4...v0.10.0) (2026-09-16)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* presenter creation as a conversation, with a view and an editor ([39e1a36](https://github.com/tonygorb/Scenri/commit/39e1a3633ab27712a628508fd5c1db51c3bcfb51))
|
|
9
|
+
* **presenter:** the studio arrives the way everything else you make here does ([7fee9b7](https://github.com/tonygorb/Scenri/commit/7fee9b77160475490c5e4dc39dd19725f7e2ba53))
|
|
10
|
+
* **studio:** show an import where you are, and fill the wall as it lands ([667d8ac](https://github.com/tonygorb/Scenri/commit/667d8acd913818400b5ea7fee0502439e06fc0e9))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **brand:** teach the brand scraper the manners the crawler already had ([b62f4da](https://github.com/tonygorb/Scenri/commit/b62f4da7d929dc9a3fdbdba5c4840e5bba3c9c84))
|
|
16
|
+
* **catalog:** a card asks for a thumbnail, not the picture it will import ([2a91200](https://github.com/tonygorb/Scenri/commit/2a91200a96cc240e7b0e144ece6670747efccee3))
|
|
17
|
+
* **catalog:** capture the site's own icon, and open the import from its dock ([cb76556](https://github.com/tonygorb/Scenri/commit/cb76556c1b85bd001deffbc9e1f3f35595bca679))
|
|
18
|
+
* **catalog:** count a catalogue the budget can actually reach ([8ba5d85](https://github.com/tonygorb/Scenri/commit/8ba5d85111e46c6c4a0afb7c48c9b972c067a168))
|
|
19
|
+
* **catalog:** count the pictures a run owes, not the ones it has looked at ([7913f21](https://github.com/tonygorb/Scenri/commit/7913f21949ea58823f0eab1a19bd175a84ae376c))
|
|
20
|
+
* **catalog:** count what a person is missing, in the unit they asked in ([e763b93](https://github.com/tonygorb/Scenri/commit/e763b93cdf0cfce12a84fe6a5e703e55bc7db973))
|
|
21
|
+
* **catalog:** read a guarded endpoint as guarded, not as a busy one ([1643170](https://github.com/tonygorb/Scenri/commit/1643170dcb93c80cc06d5be565e85d6aa7dfdc9a))
|
|
22
|
+
* **catalog:** reliable website import — accurate counts, guarded endpoints, live progress ([37658f5](https://github.com/tonygorb/Scenri/commit/37658f53a846d42bc87b7fc522c68c589aef20a6))
|
|
23
|
+
* **cli:** a revision does not carry a picture of the face it replaced ([072a353](https://github.com/tonygorb/Scenri/commit/072a353f8c43328df3e8b066a1f78637398c5394))
|
|
24
|
+
* **cli:** putting a picture back is a swap, not a decision to build the next view ([12d1787](https://github.com/tonygorb/Scenri/commit/12d17879e711a8275e34f27879030f5082226439))
|
|
25
|
+
* **composer:** a chip for a presenter who is gone says so ([58de149](https://github.com/tonygorb/Scenri/commit/58de1497623ab4b512eca3a3ac7e77d73a067f24))
|
|
26
|
+
* **core:** a draft becoming a saved presenter is one write ([970c0dd](https://github.com/tonygorb/Scenri/commit/970c0dd2c1a571ba524a719bf05c4b06d653f454))
|
|
27
|
+
* **presenter:** "Describe someone instead" describes someone ([b115d2e](https://github.com/tonygorb/Scenri/commit/b115d2e70a1e0642b4350d74c1149ba3804539b8))
|
|
28
|
+
* **presenter:** a record from before the studio opens, reads right, and can be drawn ([24f6ae1](https://github.com/tonygorb/Scenri/commit/24f6ae1dfaa3a92148c9caad2c915965eb702755))
|
|
29
|
+
* **presenter:** a reload is not a way of holding state ([3f7231d](https://github.com/tonygorb/Scenri/commit/3f7231dd6d5e51ae71ac46dbf58dcd2dc6df77fa))
|
|
30
|
+
* **presenter:** a view drawn from a face that moved lands stale, not current ([ce7d5fe](https://github.com/tonygorb/Scenri/commit/ce7d5fe6b40c93173a937095902089803352fe4b))
|
|
31
|
+
* **presenter:** an answer is installed only for the draft it was asked about ([c62ae1a](https://github.com/tonygorb/Scenri/commit/c62ae1ac9a261d319295f30e76f113bec1a6f845))
|
|
32
|
+
* **presenter:** arriving at a draft that is drawing reads its conversation ([edec9e3](https://github.com/tonygorb/Scenri/commit/edec9e35d213a0e6db30f78792cd1cffd098daff))
|
|
33
|
+
* **presenter:** Create presenter is a new presenter, every time ([4188c42](https://github.com/tonygorb/Scenri/commit/4188c4230865981366fb3ccccfe4c55ea40788b3))
|
|
34
|
+
* **presenter:** deleting a presenter reaches every surface, and takes its session with it ([73ceea2](https://github.com/tonygorb/Scenri/commit/73ceea292f3c124aeb85903dc0b40677e1cdf4c7))
|
|
35
|
+
* **presenter:** opening a draft does not ask a question it is about to withdraw ([4e4c6a4](https://github.com/tonygorb/Scenri/commit/4e4c6a427797f721cdb936474f3d7778a4de3a6b))
|
|
36
|
+
* **presenter:** the close on a phone head sits on the screen, and in the middle of the bar ([54634bd](https://github.com/tonygorb/Scenri/commit/54634bdcd5a050ad3cabc032bef67a68d22e001f))
|
|
37
|
+
* **presenter:** the editor gets the latch the creation flow already has ([56119c7](https://github.com/tonygorb/Scenri/commit/56119c7731ab71d69806a99944531b33235eedf3))
|
|
38
|
+
* **presenter:** the picture is simply there, and a picture put back brings its view up ([8665646](https://github.com/tonygorb/Scenri/commit/866564696cab338829c77dee9851bb65a69f7cef))
|
|
39
|
+
* **presenter:** the question over a picture put back says what happened to it ([d678a96](https://github.com/tonygorb/Scenri/commit/d678a969aa92260d0d02f6dcb615b78eb477dd80))
|
|
40
|
+
* **presenter:** the stage is the shape of the picture, so a bad draw is not a crop ([52f6bde](https://github.com/tonygorb/Scenri/commit/52f6bde7b6dfbade987b1e24324315c62a950a48))
|
|
41
|
+
* **presenter:** throwing an unfinished person away asks first ([9226470](https://github.com/tonygorb/Scenri/commit/9226470fe7cb0001695699f446d5e4c2f16ad579))
|
|
42
|
+
* **presenter:** without an engine, the photo is the face rather than a dead end ([8ddae49](https://github.com/tonygorb/Scenri/commit/8ddae49a74a191e126024d65340ee89ab78d84c9))
|
|
43
|
+
* **studio:** a discarded draft hands its place on, instead of dropping focus ([5180237](https://github.com/tonygorb/Scenri/commit/5180237bcbf18d2d76eff0cbe64f6644084ad071))
|
|
44
|
+
* **studio:** a kit you walk away from is not a brand you made ([03b3613](https://github.com/tonygorb/Scenri/commit/03b36139699e665669e75d7722c170930c8ec87b))
|
|
45
|
+
* **studio:** close the silent paths through onboarding and import ([441d310](https://github.com/tonygorb/Scenri/commit/441d310ecdd6e30ab63f117c19507036bff06513))
|
|
46
|
+
* **studio:** count one thing in the task row, and say what a stop kept ([0aba7e5](https://github.com/tonygorb/Scenri/commit/0aba7e5ee42b1a05b9a4635a2557c3a0bca2a5c3))
|
|
47
|
+
* **studio:** offer the products a listing found, not only the ones read ([4ea36ef](https://github.com/tonygorb/Scenri/commit/4ea36ef0890a854e19375a865e2c8190e97b78c7))
|
|
48
|
+
* **studio:** one press is one act, for Enter and for a confirm ([d43a8c7](https://github.com/tonygorb/Scenri/commit/d43a8c7f99a083f2d8d559d02475f89e03952464))
|
|
49
|
+
* **studio:** say what was kept, not only what went wrong ([70f1855](https://github.com/tonygorb/Scenri/commit/70f1855ce4945d72f56bf43ece8080b5050cafa5))
|
|
50
|
+
* **studio:** say when the look for a shop did not finish ([be0e2f6](https://github.com/tonygorb/Scenri/commit/be0e2f600673f3f1c8f8aef36d80b7cba0430212))
|
|
51
|
+
* **studio:** Start over keeps drawn work, and lets go of a draft with none ([f5d1328](https://github.com/tonygorb/Scenri/commit/f5d132824d030ab604cf52a97f71c5a73118464f))
|
|
52
|
+
* **studio:** the import pill wears the brand's mark and a clock ([4b78b1a](https://github.com/tonygorb/Scenri/commit/4b78b1a6e93780e5389ce2990af929c3dc515638))
|
|
53
|
+
* **studio:** the presenter studio says what it will spend, like the other two flows ([3bb039c](https://github.com/tonygorb/Scenri/commit/3bb039cf22c5950fd5226f22b93405477a216a4c))
|
|
54
|
+
* **test:** keep the private repo out of the battery drivers' defaults ([cd0f674](https://github.com/tonygorb/Scenri/commit/cd0f674d4f7b98490bf70c840ef8c9292c5f8f53))
|
|
55
|
+
* **test:** read the reference image's basename, not its POSIX split ([442cc07](https://github.com/tonygorb/Scenri/commit/442cc07bdedb02c62a695b9c22ac090caee2d890))
|
|
56
|
+
* **test:** the analyzer's imagePaths assertion had the same split('/') bug ([7877088](https://github.com/tonygorb/Scenri/commit/787708802c94f46be158c4f3e93c147e0973ee97))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
### Performance Improvements
|
|
60
|
+
|
|
61
|
+
* **catalog:** ask the listing for a chosen set, not a page each ([cfe9a3c](https://github.com/tonygorb/Scenri/commit/cfe9a3c3aa7411ae1e8fe6d5002a2f1b4397de53))
|
|
62
|
+
* **catalog:** keep the catalogue the store already handed over ([9630378](https://github.com/tonygorb/Scenri/commit/9630378ed2e076f6cb9b7ce50dde28c68e615548))
|
|
63
|
+
|
|
3
64
|
## [0.9.4](https://github.com/tonygorb/Scenri/compare/v0.9.3...v0.9.4) (2026-09-14)
|
|
4
65
|
|
|
5
66
|
|
|
@@ -2,7 +2,7 @@ import { homedir } from 'os';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import Database from 'better-sqlite3';
|
|
4
4
|
import { randomUUID, createHash } from 'crypto';
|
|
5
|
-
import { mkdirSync, existsSync, chmodSync, readFileSync, writeFileSync, readdirSync
|
|
5
|
+
import { mkdirSync, existsSync, chmodSync, rmSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
6
6
|
|
|
7
7
|
// ../core/src/index.ts
|
|
8
8
|
|
|
@@ -173,6 +173,14 @@ CREATE TABLE IF NOT EXISTS import_jobs (
|
|
|
173
173
|
finished_at TEXT
|
|
174
174
|
);
|
|
175
175
|
CREATE INDEX IF NOT EXISTS idx_import_jobs_brand ON import_jobs(brand_id);
|
|
176
|
+
CREATE TABLE IF NOT EXISTS presenter_drafts (
|
|
177
|
+
id TEXT PRIMARY KEY,
|
|
178
|
+
brand_id TEXT NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
|
|
179
|
+
json TEXT NOT NULL,
|
|
180
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
181
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
182
|
+
);
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_presenter_drafts_brand ON presenter_drafts(brand_id);
|
|
176
184
|
`;
|
|
177
185
|
function widenNodeStatusCheck(db) {
|
|
178
186
|
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='nodes'").get();
|
|
@@ -577,6 +585,12 @@ function createImageStore(homeDir) {
|
|
|
577
585
|
},
|
|
578
586
|
has(hash) {
|
|
579
587
|
return /^[a-f0-9]{32}$/.test(hash) && EXTS.some((ext) => existsSync(fileFor(hash, ext)));
|
|
588
|
+
},
|
|
589
|
+
remove(hash) {
|
|
590
|
+
const file = this.pathFor(hash);
|
|
591
|
+
if (!existsSync(file)) return false;
|
|
592
|
+
rmSync(file, { force: true });
|
|
593
|
+
return true;
|
|
580
594
|
}
|
|
581
595
|
};
|
|
582
596
|
}
|
|
@@ -674,6 +688,9 @@ function uniqueSetSlug(db, brandId, name, id) {
|
|
|
674
688
|
return firstFree(slugifyWithId(name, id, "set"), (c) => !!stmt.get(brandId, c, id));
|
|
675
689
|
}
|
|
676
690
|
var SET_NAME_SEP = String.fromCharCode(31);
|
|
691
|
+
function rowToPresenterDraft(r) {
|
|
692
|
+
return { id: r.id, brandId: r.brand_id, json: JSON.parse(r.json), createdAt: r.created_at, updatedAt: r.updated_at };
|
|
693
|
+
}
|
|
677
694
|
function rowToSet(r) {
|
|
678
695
|
return {
|
|
679
696
|
id: r.id,
|
|
@@ -850,6 +867,21 @@ function createStore(db) {
|
|
|
850
867
|
);
|
|
851
868
|
return this.getBrand(id);
|
|
852
869
|
},
|
|
870
|
+
/**
|
|
871
|
+
* A draft becoming a saved asset: the document and the row move together.
|
|
872
|
+
*
|
|
873
|
+
* These were two statements with nothing holding them. A crash between
|
|
874
|
+
* them left the presenter in the brand AND its draft row alive, so the
|
|
875
|
+
* wall showed a finished person and an unfinished one, and saving that
|
|
876
|
+
* draft again appended a second copy of the same person.
|
|
877
|
+
*/
|
|
878
|
+
updateBrandAndDropPresenterDraft(id, json, draftId) {
|
|
879
|
+
return db.transaction(() => {
|
|
880
|
+
const row = this.updateBrand(id, json);
|
|
881
|
+
this.deletePresenterDraft(draftId);
|
|
882
|
+
return row;
|
|
883
|
+
})();
|
|
884
|
+
},
|
|
853
885
|
deleteBrand(id) {
|
|
854
886
|
db.prepare("DELETE FROM brands WHERE id=?").run(id);
|
|
855
887
|
},
|
|
@@ -972,6 +1004,42 @@ function createStore(db) {
|
|
|
972
1004
|
membersOf(setId) {
|
|
973
1005
|
return db.prepare("SELECT node_id FROM set_nodes WHERE set_id=? ORDER BY added_at, node_id").all(setId).map((r) => r.node_id);
|
|
974
1006
|
},
|
|
1007
|
+
// presenter drafts
|
|
1008
|
+
/** Upsert: the same id replaces the json and moves updated_at forward; created_at stays. */
|
|
1009
|
+
putPresenterDraft(row) {
|
|
1010
|
+
db.prepare(
|
|
1011
|
+
// Millisecond stamps, the nodes table's own format: a draft is touched
|
|
1012
|
+
// on every step, and two touches inside one second must still order.
|
|
1013
|
+
`INSERT INTO presenter_drafts (id, brand_id, json, created_at, updated_at)
|
|
1014
|
+
VALUES (?,?,?, strftime('%Y-%m-%d %H:%M:%f','now'), strftime('%Y-%m-%d %H:%M:%f','now'))
|
|
1015
|
+
ON CONFLICT(id) DO UPDATE SET json=excluded.json, updated_at=strftime('%Y-%m-%d %H:%M:%f','now')`
|
|
1016
|
+
).run(row.id, row.brandId, JSON.stringify(row.json));
|
|
1017
|
+
return this.getPresenterDraft(row.id);
|
|
1018
|
+
},
|
|
1019
|
+
getPresenterDraft(id) {
|
|
1020
|
+
const r = db.prepare("SELECT * FROM presenter_drafts WHERE id=?").get(id);
|
|
1021
|
+
return r ? rowToPresenterDraft(r) : null;
|
|
1022
|
+
},
|
|
1023
|
+
/** Most recently touched first, the same order sets keep. */
|
|
1024
|
+
listPresenterDrafts(brandId) {
|
|
1025
|
+
return db.prepare("SELECT * FROM presenter_drafts WHERE brand_id=? ORDER BY updated_at DESC, created_at DESC, id").all(brandId).map(rowToPresenterDraft);
|
|
1026
|
+
},
|
|
1027
|
+
deletePresenterDraft(id) {
|
|
1028
|
+
db.prepare("DELETE FROM presenter_drafts WHERE id=?").run(id);
|
|
1029
|
+
},
|
|
1030
|
+
/**
|
|
1031
|
+
* Whether any row still points at a stored image: a shot's images, a
|
|
1032
|
+
* shot's brief (source image, attachments), an imported catalog image, or
|
|
1033
|
+
* another presenter draft. The store is content-addressed, so the same
|
|
1034
|
+
* photo uploaded twice is one file; whoever removes an image asks this
|
|
1035
|
+
* first, and the brand documents are the caller's half of the answer.
|
|
1036
|
+
*/
|
|
1037
|
+
imageReferenced(hash) {
|
|
1038
|
+
if (!/^[a-f0-9]{32}$/.test(hash)) return false;
|
|
1039
|
+
const like = `%${hash}%`;
|
|
1040
|
+
const hit = (sql, ...args) => !!db.prepare(sql).get(...args);
|
|
1041
|
+
return hit("SELECT 1 FROM nodes WHERE images LIKE ? LIMIT 1", `%"${hash}"%`) || hit("SELECT 1 FROM nodes WHERE brief LIKE ? LIMIT 1", like) || hit("SELECT 1 FROM catalog_images WHERE asset_ref LIKE ? LIMIT 1", like) || hit("SELECT 1 FROM presenter_drafts WHERE json LIKE ? LIMIT 1", like);
|
|
1042
|
+
},
|
|
975
1043
|
// nodes / version tree
|
|
976
1044
|
addNode(input) {
|
|
977
1045
|
if (input.parentId) {
|
|
@@ -1711,6 +1779,24 @@ function productImportMethods(db) {
|
|
|
1711
1779
|
WHERE product_id=? AND source_url=?`
|
|
1712
1780
|
).run(assetRef, meta?.width ?? null, meta?.height ?? null, productId, sourceUrl);
|
|
1713
1781
|
},
|
|
1782
|
+
/**
|
|
1783
|
+
* How many pictures are still owed, for a progress that can be trusted.
|
|
1784
|
+
*
|
|
1785
|
+
* The importer used to add up the rounds it had fetched and call that the
|
|
1786
|
+
* total, so the total was really "pictures discovered so far": it grew by
|
|
1787
|
+
* sixty every round, and the fraction fell back every time it did. A run
|
|
1788
|
+
* went 60/60, then 64/120, then 122/172 - a bar sliding backwards twice
|
|
1789
|
+
* while nothing had gone wrong. Counting what is left is one query and it
|
|
1790
|
+
* is the actual answer.
|
|
1791
|
+
*/
|
|
1792
|
+
countImagesNeedingAssets(brandId, imagesPerProduct) {
|
|
1793
|
+
const row = db.prepare(
|
|
1794
|
+
`SELECT COUNT(*) AS n FROM catalog_images i
|
|
1795
|
+
JOIN catalog_products p ON p.id = i.product_id
|
|
1796
|
+
WHERE p.brand_id=? AND (i.asset_ref IS NULL OR i.asset_ref='') AND i.position < ?`
|
|
1797
|
+
).get(brandId, imagesPerProduct);
|
|
1798
|
+
return row?.n ?? 0;
|
|
1799
|
+
},
|
|
1714
1800
|
listImagesNeedingAssets(brandId, limit = 500) {
|
|
1715
1801
|
return db.prepare(
|
|
1716
1802
|
`SELECT i.* FROM catalog_images i
|
|
@@ -1730,6 +1816,38 @@ function productImportMethods(db) {
|
|
|
1730
1816
|
excluded: !!r.excluded
|
|
1731
1817
|
}));
|
|
1732
1818
|
},
|
|
1819
|
+
/**
|
|
1820
|
+
* Hide pictures that turn up on product after product.
|
|
1821
|
+
*
|
|
1822
|
+
* A photograph of a thing belongs to that thing. A picture the crawl found
|
|
1823
|
+
* on eighteen different product pages is the shop talking, not the
|
|
1824
|
+
* product: a promotion flash, a collection sticker, a delivery badge.
|
|
1825
|
+
* Measured on a real storefront, three addresses out of 265 were shared
|
|
1826
|
+
* past this threshold and all three were merchandising furniture, while
|
|
1827
|
+
* every genuine packshot appeared once.
|
|
1828
|
+
*
|
|
1829
|
+
* Names nothing and knows nothing about any platform, which is the point:
|
|
1830
|
+
* the badges that caused this carry no word a filter could match, and the
|
|
1831
|
+
* next store's will be different words.
|
|
1832
|
+
*
|
|
1833
|
+
* Excluded rather than deleted, the same state the picture chooser uses,
|
|
1834
|
+
* so anything wrongly caught is still there to be put back.
|
|
1835
|
+
*/
|
|
1836
|
+
excludeSharedImages(sourceId, minProducts = 3) {
|
|
1837
|
+
const r = db.prepare(
|
|
1838
|
+
`UPDATE catalog_images SET excluded=1
|
|
1839
|
+
WHERE excluded=0
|
|
1840
|
+
AND product_id IN (SELECT id FROM catalog_products WHERE source_id=?)
|
|
1841
|
+
AND source_url IN (
|
|
1842
|
+
SELECT ci.source_url FROM catalog_images ci
|
|
1843
|
+
JOIN catalog_products cp ON cp.id = ci.product_id
|
|
1844
|
+
WHERE cp.source_id=?
|
|
1845
|
+
GROUP BY ci.source_url
|
|
1846
|
+
HAVING COUNT(DISTINCT ci.product_id) >= ?
|
|
1847
|
+
)`
|
|
1848
|
+
).run(sourceId, sourceId, minProducts);
|
|
1849
|
+
return r.changes;
|
|
1850
|
+
},
|
|
1733
1851
|
markMissingUnavailable(sourceId, seenExternalKeys) {
|
|
1734
1852
|
if (!seenExternalKeys.length) {
|
|
1735
1853
|
const r2 = db.prepare(
|
|
@@ -1952,6 +2070,12 @@ function createCatalogStore(db) {
|
|
|
1952
2070
|
var REFERENCE_ROLE_DIRECTIVE = {
|
|
1953
2071
|
product: "the exact product \u2014 preserve its label, shape, colors and design faithfully; do not redesign it",
|
|
1954
2072
|
character: "the exact person \u2014 match their face, facial structure, skin, hair and build exactly; their clothing, pose and background are capture context, not styling to reproduce",
|
|
2073
|
+
// One thing in the picture, and nothing else in it. A person uploads a pair
|
|
2074
|
+
// of glasses they like, or the exact tattoo, and every other role would have
|
|
2075
|
+
// handed the model a second face to blend with the first: `character` says
|
|
2076
|
+
// "this is who they are", `style` takes the mood of the whole frame. This
|
|
2077
|
+
// says take the object and leave the rest of the photograph behind.
|
|
2078
|
+
detail: "a reference for one detail only \u2014 match the design, shape, proportions, colours and finish of the thing itself, exactly as drawn or made. Take nothing else from this image: not the identity of anyone in it, not their face, skin, hair, body or build, not their clothing, not the background, not the lighting. The person in the picture being drawn is somebody else entirely",
|
|
1955
2079
|
brand: "the brand's own mark \u2014 if the direction calls for the mark to appear, reproduce it exactly as drawn, same colours, letterforms and proportions, every character down to the smallest secondary lettering, in its original script and reading direction, never translated, transliterated or re-spelled; otherwise take only its colour and treatment, and never its subject, geometry or composition",
|
|
1956
2080
|
// Only a figure-led scene attaches one of these now, so this says what that
|
|
1957
2081
|
// case actually needs. It used to read "environment and light only - take no
|
|
@@ -1976,6 +2100,7 @@ var REFERENCE_ROLE_DIRECTIVE = {
|
|
|
1976
2100
|
var EDIT_REFERENCE_ROLE_DIRECTIVE = {
|
|
1977
2101
|
product: "the exact product: keep or restore its label, shape and design faithfully",
|
|
1978
2102
|
character: "the exact person: keep their face, facial structure, skin, hair and build faithfully; take no clothing, pose or background from this reference, and keep the source image's existing outfit unless the instruction changes it",
|
|
2103
|
+
detail: "one detail to match: its design, shape, colours and finish only. Take no identity, skin, hair, body, clothing or background from it",
|
|
1979
2104
|
brand: "the brand's own mark: reproduce it exactly as drawn wherever it appears \u2014 every character down to the smallest secondary lettering, in its original script and reading direction \u2014 never redrawn, re-lettered, translated or transliterated",
|
|
1980
2105
|
scene: "a reference for environment, light and treatment only \u2014 take no identity from any person in it",
|
|
1981
2106
|
composition: "a reference for framing and pose only",
|
|
@@ -2032,5 +2157,5 @@ function createCore(homeDir = defaultHome()) {
|
|
|
2032
2157
|
}
|
|
2033
2158
|
|
|
2034
2159
|
export { ASPECT_TOLERANCE, BUDGET_EXHAUSTED, EDIT_REFERENCE_ROLE_DIRECTIVE, REFERENCE_ROLE_DIRECTIVE, SCHEMA_VERSION, STEM_MIN, SchemaTooNewError, SpendCapError, TRIGRAM_MIN, budgetSize, createCatalogStore, createCore, createStore, defaultHome, fold, ftsMatch, matchesQuery, ratioLabel, searchTerms, termMatches, uniqueProjectSlug, uniqueSetSlug, uniqueSlug };
|
|
2035
|
-
//# sourceMappingURL=chunk-
|
|
2036
|
-
//# sourceMappingURL=chunk-
|
|
2160
|
+
//# sourceMappingURL=chunk-WORM6GB3.js.map
|
|
2161
|
+
//# sourceMappingURL=chunk-WORM6GB3.js.map
|
|
@@ -463,7 +463,7 @@ async function addOrRepair(ownEntry, say, over) {
|
|
|
463
463
|
}
|
|
464
464
|
async function rememberDecline(home) {
|
|
465
465
|
try {
|
|
466
|
-
const { createCore } = await import('./src-
|
|
466
|
+
const { createCore } = await import('./src-4N4TS3HO.js');
|
|
467
467
|
const core = createCore(home);
|
|
468
468
|
try {
|
|
469
469
|
core.store.setSetting("desktop.prompt", "declined");
|
|
@@ -530,5 +530,5 @@ function tail(path, bytes = 4096) {
|
|
|
530
530
|
}
|
|
531
531
|
|
|
532
532
|
export { addToDesktop, assetsDirFor, installDeps, runDesktopCommand, runExecFile, runOpenCommand };
|
|
533
|
-
//# sourceMappingURL=cli-
|
|
534
|
-
//# sourceMappingURL=cli-
|
|
533
|
+
//# sourceMappingURL=cli-EXASA5BK.js.map
|
|
534
|
+
//# sourceMappingURL=cli-EXASA5BK.js.map
|
package/dist/index.js
CHANGED
|
@@ -86,12 +86,12 @@ try {
|
|
|
86
86
|
break;
|
|
87
87
|
}
|
|
88
88
|
case "desktop": {
|
|
89
|
-
const { runDesktopCommand } = await import('./cli-
|
|
89
|
+
const { runDesktopCommand } = await import('./cli-EXASA5BK.js');
|
|
90
90
|
process.exit(await runDesktopCommand(command, fileURLToPath(import.meta.url)));
|
|
91
91
|
break;
|
|
92
92
|
}
|
|
93
93
|
case "open": {
|
|
94
|
-
const { runOpenCommand } = await import('./cli-
|
|
94
|
+
const { runOpenCommand } = await import('./cli-EXASA5BK.js');
|
|
95
95
|
process.exit(await runOpenCommand(fileURLToPath(import.meta.url)));
|
|
96
96
|
break;
|
|
97
97
|
}
|