getobsrv 0.9.0 → 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/README.md CHANGED
@@ -73,11 +73,16 @@ the advanced sliders. Enter your own monitor's diagonal in Settings once so the
73
73
  pane renders at true physical size. Drop a 2x/3x PNG or JPG export to check a design
74
74
  before it's built.
75
75
 
76
- The target pane opens at 1:1, which usually overflows the pane: pan with a
77
- middle-button drag, Option+drag or Option+wheel, or switch the toolbar's
78
- `1:1 / Fit` control to a fit-to-pane overview (smoothly minified, so not
79
- pixel-exact — the footer says so) and click anywhere in it to jump back to 1:1
80
- at that spot.
76
+ The target pane opens in **Fit**, a fit-to-pane view of the whole render. Fit
77
+ never enlarges past 1:1, so a render that already fits is shown at true size;
78
+ a larger one is smoothly minified to fit, which is not pixel-exact — the footer
79
+ says so, and names the magnification. Fit is fully interactive: clicks, drags,
80
+ the wheel and the keyboard all reach the page.
81
+
82
+ Switch the toolbar's `1:1 / Fit` control to **1:1** for true magnification,
83
+ which usually overflows the pane: pan with a middle-button drag, Option+drag or
84
+ Option+wheel. From fit, **Option+click** jumps straight to 1:1 with the clicked
85
+ spot centred (plain clicks belong to the page).
81
86
 
82
87
  The `Both / Target` control beside it hides the native pane so the target render
83
88
  takes the whole window — useful for a small mobile preset that would otherwise
package/out/main/index.js CHANGED
@@ -792,7 +792,7 @@ function registerIpc(ctx) {
792
792
  settings = s;
793
793
  if (s.agentControl !== wasEnabled) applyAgentControl(s.agentControl);
794
794
  });
795
- const uiState = { presetId: "1080p-24", profileId: "reference", viewMode: "1:1", panes: "both", mode: "url" };
795
+ const uiState = { presetId: "1080p-24", profileId: "reference", viewMode: "fit", panes: "both", mode: "url" };
796
796
  let targetBounds = null;
797
797
  let canvasBounds = null;
798
798
  const MAX_PENDING_APPLIES = 32;
@@ -12782,7 +12782,12 @@ const useStore = create()((set) => ({
12782
12782
  update: null,
12783
12783
  image: null,
12784
12784
  surround: "graphite",
12785
- viewMode: "1:1",
12785
+ // Fit, not 1:1: fit never enlarges past 1:1, so a render that already fits
12786
+ // its pane opens at true magnification anyway, while one that does not is
12787
+ // shown whole instead of as its top-left corner. Fit is interactive, so
12788
+ // this costs nothing — and the footer names the actual magnification
12789
+ // whenever fit is minifying.
12790
+ viewMode: "fit",
12786
12791
  panes: "both",
12787
12792
  fitScale: null,
12788
12793
  agentPan: null,
@@ -14025,7 +14030,6 @@ function TargetCanvas({ onFatal, imageFrame }) {
14025
14030
  canvas.addEventListener("wheel", onWheel, { passive: false });
14026
14031
  const onWindowUp = (e) => {
14027
14032
  if (useStore.getState().mode !== "url") return;
14028
- if (useStore.getState().viewMode !== "1:1") return;
14029
14033
  if (panRef.current || e.button === 1) return;
14030
14034
  if (e.altKey && !forwardDrag.current) return;
14031
14035
  if (e.target === canvas) return;
@@ -14099,7 +14103,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
14099
14103
  return body instanceof HTMLElement ? body : null;
14100
14104
  };
14101
14105
  const startPan = (e) => {
14102
- if (viewMode !== "1:1") return;
14106
+ if (fit) return;
14103
14107
  if (!(e.button === 1 || e.button === 0 && e.altKey)) return;
14104
14108
  const body = paneBody();
14105
14109
  if (!body) return;
@@ -14148,7 +14152,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
14148
14152
  pendingJump.current = null;
14149
14153
  }, [viewMode]);
14150
14154
  const jumpTo1x = (e) => {
14151
- if (viewMode !== "fit") return;
14155
+ if (!fit || !e.altKey) return;
14152
14156
  const r = e.currentTarget.getBoundingClientRect();
14153
14157
  pendingJump.current = jumpScroll(
14154
14158
  e.clientX - r.left,
@@ -14189,7 +14193,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
14189
14193
  }, [agentHighlight, clearAgentHighlight]);
14190
14194
  const send = (type) => (e) => {
14191
14195
  if (mode !== "url") return;
14192
- if (viewMode !== "1:1" || panRef.current || e.button === 1) return;
14196
+ if (panRef.current || e.button === 1) return;
14193
14197
  if (e.altKey && !forwardDrag.current) return;
14194
14198
  const out = mouseEvent(type, e, e.currentTarget.getBoundingClientRect(), scale * dsf / dpr);
14195
14199
  if (out) {
@@ -14220,7 +14224,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
14220
14224
  "canvas",
14221
14225
  {
14222
14226
  ref: canvasRef,
14223
- className: `target-canvas${fit ? " fit" : panning ? " panning" : altHeld ? " pan-ready" : ""}`,
14227
+ className: `target-canvas${panning ? " panning" : altHeld ? fit ? " fit" : " pan-ready" : ""}`,
14224
14228
  tabIndex: 0,
14225
14229
  style: { width: `${cssW}px`, height: `${cssH}px` },
14226
14230
  onClick: jumpTo1x,
@@ -14232,12 +14236,12 @@ function TargetCanvas({ onFatal, imageFrame }) {
14232
14236
  onMouseUp: send("mouseUp"),
14233
14237
  onMouseMove: send("mouseMove"),
14234
14238
  onKeyDown: (e) => {
14235
- if (mode !== "url" || viewMode !== "1:1") return;
14239
+ if (mode !== "url") return;
14236
14240
  if (!e.metaKey && !e.ctrlKey) e.preventDefault();
14237
14241
  for (const ev of keyDownEvents(e)) window.obsrv.sendInput(ev);
14238
14242
  },
14239
14243
  onKeyUp: (e) => {
14240
- if (mode !== "url" || viewMode !== "1:1") return;
14244
+ if (mode !== "url") return;
14241
14245
  const ev = keyUpEvent(e);
14242
14246
  if (ev) window.obsrv.sendInput(ev);
14243
14247
  }
@@ -551,6 +551,27 @@ html, body, #root { margin: 0; height: 100%; background: var(--chrome-0); color:
551
551
  `left: 0` holds while a wide canvas is scrolled horizontally. The fill is a
552
552
  neutral grey: only the rule under it is red, so the one chromatic thing on
553
553
  screen is the error itself. */
554
+ /* At 1:1 the render usually overflows and the pane is a scroll box, so it
555
+ starts at the top-left and you pan around it. When it is *smaller* than the
556
+ pane — a small preset, fit mode, or solo target on a wide window — hugging
557
+ the corner leaves it stranded against the chrome with dead field to the
558
+ right. Centre it instead.
559
+
560
+ `safe` is what lets one rule serve both cases: it falls back to start
561
+ alignment the moment the content overflows, so the top-left of an oversized
562
+ render stays reachable by scrolling. Plain `center` would push it out of
563
+ reach. The image pane already centres this way. */
564
+ .target-pane .pane-body {
565
+ display: flex;
566
+ flex-direction: column;
567
+ align-items: safe center;
568
+ justify-content: safe center;
569
+ }
570
+ /* The stall notice spans the pane, not the canvas: it is sticky at `left: 0`
571
+ and must stay pane-width while a wide canvas scrolls under it, so it opts
572
+ out of the centring its sibling wants. */
573
+ .target-pane .pane-body > .stall { align-self: stretch; }
574
+
554
575
  /* position: relative anchors the agent highlight to the canvas origin. */
555
576
  .target-wrap { min-width: min-content; position: relative; }
556
577
 
@@ -598,8 +619,10 @@ html, body, #root { margin: 0; height: 100%; background: var(--chrome-0); color:
598
619
  and being (0,1,1) and later in the file they beat `.segmented button` and
599
620
  split the two groups apart. Nothing about 1:1/Fit is control-specific. */
600
621
 
601
- /* The overview invites the jump-to-1:1 click; the pan chords (middle drag,
602
- Option+drag) show the grab affordances. Cursors only no overlay may sit
622
+ /* Option is what a view gesture needs, so Option is what the cursor answers:
623
+ the jump-to-1:1 zoom in fit, the grab affordances for the pan chords
624
+ (middle drag, Option+drag) at 1:1. Unmodified, both views are interactive
625
+ and the page's own cursors show through. Cursors only — no overlay may sit
603
626
  on the pixels under inspection. */
604
627
  .target-canvas.fit { cursor: zoom-in; }
605
628
  .target-canvas.pan-ready { cursor: grab; }
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:" />
6
6
  <title>Obsrv</title>
7
- <script type="module" crossorigin src="./assets/index-CpeTWcl6.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-CasW01OP.css">
7
+ <script type="module" crossorigin src="./assets/index-CQVE5hF3.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-CZUlXhEb.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getobsrv",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "See your site the way 1x screens see it",
5
5
  "main": "./out/main/index.js",
6
6
  "bin": {