screengraft 0.62.1 → 0.62.3
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/package.json +1 -1
- package/scripts/recents.py +13 -0
- package/scripts/ui.py +14 -2
- package/ui/index.html +1 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "screengraft",
|
|
3
|
-
"version": "0.62.
|
|
3
|
+
"version": "0.62.3",
|
|
4
4
|
"description": "Put a UI screenshot or screen recording onto a photographed device screen with the perspective exactly right \u2014 a homography you confirm by hand, not a generative guess.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mockup",
|
package/scripts/recents.py
CHANGED
|
@@ -39,7 +39,16 @@ def _load() -> dict:
|
|
|
39
39
|
if not isinstance(d, dict):
|
|
40
40
|
raise ValueError
|
|
41
41
|
except (OSError, ValueError):
|
|
42
|
+
# No store (or an unreadable one): build it from the sessions on disk
|
|
43
|
+
# and WRITE it, or every read until the first record() walks them again.
|
|
42
44
|
d = _seed()
|
|
45
|
+
for r in ROLES:
|
|
46
|
+
d.setdefault(r, [])
|
|
47
|
+
try:
|
|
48
|
+
_save(d)
|
|
49
|
+
except OSError:
|
|
50
|
+
pass
|
|
51
|
+
return d
|
|
43
52
|
for r in ROLES:
|
|
44
53
|
d.setdefault(r, [])
|
|
45
54
|
return d
|
|
@@ -52,6 +61,10 @@ def _save(d: dict):
|
|
|
52
61
|
with open(tmp, "w") as f:
|
|
53
62
|
json.dump(d, f, indent=1)
|
|
54
63
|
os.replace(tmp, p)
|
|
64
|
+
try:
|
|
65
|
+
os.chmod(p, 0o600) # paths under the user's home: owner-only
|
|
66
|
+
except OSError:
|
|
67
|
+
pass
|
|
55
68
|
|
|
56
69
|
|
|
57
70
|
def _seed() -> dict:
|
package/scripts/ui.py
CHANGED
|
@@ -28,6 +28,7 @@ stdlib only on the server side; OpenCV via detect/warp.
|
|
|
28
28
|
"""
|
|
29
29
|
import argparse
|
|
30
30
|
import atexit
|
|
31
|
+
import hashlib
|
|
31
32
|
import json
|
|
32
33
|
import mimetypes
|
|
33
34
|
import os
|
|
@@ -733,7 +734,10 @@ def _thumb_for(item):
|
|
|
733
734
|
out_dir = os.path.join(SESSION.dir, "thumbs")
|
|
734
735
|
if item.get("ext") in VIDEO_EXT:
|
|
735
736
|
os.makedirs(out_dir, exist_ok=True)
|
|
736
|
-
|
|
737
|
+
# A stable name: hash() is salted per process, so a name built on it
|
|
738
|
+
# misses the cache on every launch.
|
|
739
|
+
key = hashlib.sha1(item["path"].encode()).hexdigest()[:16]
|
|
740
|
+
out = os.path.join(out_dir, f"{key}_{int(item.get('mtime') or 0)}_clip.jpg")
|
|
737
741
|
if not os.path.exists(out):
|
|
738
742
|
try:
|
|
739
743
|
fr = W.read_frame_at(item["path"], 0)
|
|
@@ -981,7 +985,10 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
981
985
|
# The folder scan (scan.py) still exists for the CLI and for a
|
|
982
986
|
# first run with nothing used yet; ?role= selects the list.
|
|
983
987
|
role = (q.get("role") or ["photo"])[0]
|
|
984
|
-
|
|
988
|
+
try:
|
|
989
|
+
limit = max(1, min(200, int(q.get("limit", ["40"])[0])))
|
|
990
|
+
except ValueError:
|
|
991
|
+
limit = 40
|
|
985
992
|
items = RECENT.items(role, limit)
|
|
986
993
|
for it in items:
|
|
987
994
|
it["thumb"] = _thumb_for(it)
|
|
@@ -1546,6 +1553,11 @@ def _publish_current(payload):
|
|
|
1546
1553
|
"""
|
|
1547
1554
|
os.makedirs(os.path.dirname(CURRENT), exist_ok=True)
|
|
1548
1555
|
_write_json_atomic(CURRENT, payload)
|
|
1556
|
+
# The pointer carries the session token: owner-only, like a credential.
|
|
1557
|
+
try:
|
|
1558
|
+
os.chmod(CURRENT, 0o600)
|
|
1559
|
+
except OSError:
|
|
1560
|
+
pass
|
|
1549
1561
|
|
|
1550
1562
|
def clear():
|
|
1551
1563
|
cur = None
|
package/ui/index.html
CHANGED
|
@@ -574,7 +574,6 @@
|
|
|
574
574
|
.segbtn[disabled],.segbtn[disabled]:hover{background:var(--raise-low);color:var(--mute)}
|
|
575
575
|
.segbtn:focus-visible{outline-offset:-2px}
|
|
576
576
|
.segbtn svg{width:16px;height:16px;display:block;flex:none}
|
|
577
|
-
.segbtn svg text{font:600 8px/1 -apple-system,BlinkMacSystemFont,"SF Pro",system-ui,sans-serif;fill:var(--ink)}
|
|
578
577
|
.lrail{position:absolute;left:24px;top:48px;z-index:3;display:flex;flex-direction:column;gap:var(--s2)}
|
|
579
578
|
.lrail .corners{margin-top:var(--s6)} /* 8 + 24 = the design's 32px between rails */
|
|
580
579
|
.brail{position:absolute;left:0;right:0;bottom:8px;z-index:3;display:flex;justify-content:center;
|
|
@@ -1137,7 +1136,7 @@
|
|
|
1137
1136
|
<div class="lrail" id="lrail">
|
|
1138
1137
|
<div class="bgroup" role="group" aria-label="Zoom">
|
|
1139
1138
|
<button class="segbtn" id="czIn" aria-label="Zoom in" title="Zoom in (⌘ + scroll zooms to the pointer)"><svg viewBox="0 0 16 16" aria-hidden="true"><path fill="var(--icon)" d="M8.5 7.5H12.5V8.5H8.5V12.5H7.5V8.5H3.5V7.5H7.5V3.5H8.5V7.5Z"/></svg></button>
|
|
1140
|
-
<button class="segbtn" id="cz100" aria-label="Actual size, 100%" title="Actual size — one photo pixel per screen pixel"><svg viewBox="0 0 16 16" aria-hidden="true"><path fill="var(--mute)" d="M14.1025 1.00488C14.6067 1.05621 15 1.48232 15 2V14L14.9951 14.1025C14.9472 14.573 14.573 14.9472 14.1025 14.9951L14 15H2C1.48232 15 1.05621 14.6067 1.00488 14.1025L1 14V2C1 1.44772 1.44772 1 2 1H14L14.1025 1.00488ZM2 14H14V2H2V14Z"/><
|
|
1139
|
+
<button class="segbtn" id="cz100" aria-label="Actual size, 100%" title="Actual size — one photo pixel per screen pixel"><svg viewBox="0 0 16 16" aria-hidden="true"><path fill="var(--mute)" d="M14.1025 1.00488C14.6067 1.05621 15 1.48232 15 2V14L14.9951 14.1025C14.9472 14.573 14.573 14.9472 14.1025 14.9951L14 15H2C1.48232 15 1.05621 14.6067 1.00488 14.1025L1 14V2C1 1.44772 1.44772 1 2 1H14L14.1025 1.00488ZM2 14H14V2H2V14Z"/><g transform="translate(2.77 4.245)" fill="var(--ink)"><path d="M4.80713 7.75049L6.78906 4.85547L4.78564 1.96045H5.88672L7.26172 4.11426H7.34766L8.70654 1.96045H9.75391L7.78809 4.81787L9.78076 7.75049H8.68506L7.31543 5.56445H7.22949L5.85449 7.75049H4.80713Z"/><path d="M2.05713 7.75049V1.07422H1.97119L0 2.50293V1.48242L2.0625 0H3.02393V7.75049H2.05713Z"/></g></svg></button>
|
|
1141
1140
|
<button class="segbtn" id="czOut" aria-label="Zoom out" title="Zoom out"><svg viewBox="0 0 16 16" aria-hidden="true"><path fill="var(--icon)" d="M12.5 8.5H3.5V7.5H12.5V8.5Z"/></svg></button>
|
|
1142
1141
|
</div>
|
|
1143
1142
|
<div class="bgroup">
|