getobsrv 0.10.0 → 0.12.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 +34 -0
- package/out/main/cli.js +1 -1
- package/out/main/index.js +842 -192
- package/out/main/{targetSource-CbJwfugi.js → targetSource-DlsHc-Ri.js} +50 -1
- package/out/mcp/server.js +21 -1
- package/out/preload/app.js +25 -16
- package/out/renderer/assets/{index-CZUlXhEb.css → index-DvwDBeho.css} +284 -14
- package/out/renderer/assets/{index-CQVE5hF3.js → index-gYDbIgqJ.js} +655 -157
- package/out/renderer/index.html +2 -2
- package/out/shared/control.js +13 -1
- package/out/shared/history.js +90 -0
- package/out/shared/ipcPayloads.js +41 -2
- package/out/shared/presets.js +12 -1
- package/out/shared/tabList.js +46 -0
- package/package.json +1 -1
- package/skills/obsrv-screens/SKILL.md +7 -0
|
@@ -12688,12 +12688,19 @@ const createImpl = (createState) => {
|
|
|
12688
12688
|
};
|
|
12689
12689
|
const create = ((createState) => createImpl);
|
|
12690
12690
|
const MAX_VIEWPORT = 4096;
|
|
12691
|
+
const SPLIT_MIN = 0.1;
|
|
12692
|
+
const SPLIT_MAX = 0.9;
|
|
12693
|
+
const MAX_TABS_MIN = 2;
|
|
12694
|
+
const MAX_TABS_MAX = 32;
|
|
12691
12695
|
const DEFAULT_SETTINGS = {
|
|
12692
12696
|
hostDiagonalInches: 27,
|
|
12693
12697
|
hostNits: 500,
|
|
12694
12698
|
agentControl: false,
|
|
12695
12699
|
updateCheck: true,
|
|
12696
|
-
lastUpdateCheck: 0
|
|
12700
|
+
lastUpdateCheck: 0,
|
|
12701
|
+
recordHistory: true,
|
|
12702
|
+
split: 0.5,
|
|
12703
|
+
maxTabs: 12
|
|
12697
12704
|
};
|
|
12698
12705
|
const SCREEN_PRESETS = [
|
|
12699
12706
|
// Laptops — ordered largest to smallest panel, then the denser 1080p outlier.
|
|
@@ -12757,6 +12764,27 @@ function profileToParams(p, hostNits) {
|
|
|
12757
12764
|
dither: p.frc
|
|
12758
12765
|
};
|
|
12759
12766
|
}
|
|
12767
|
+
function closeTab(tabs, closeId, activeId) {
|
|
12768
|
+
const index = tabs.findIndex((t) => t.id === closeId);
|
|
12769
|
+
if (index === -1) return { tabs, activeId };
|
|
12770
|
+
const next = tabs.filter((t) => t.id !== closeId);
|
|
12771
|
+
if (next.length === 0) return { tabs: next, activeId: null };
|
|
12772
|
+
if (closeId !== activeId) return { tabs: next, activeId };
|
|
12773
|
+
const neighbour = next[Math.min(index, next.length - 1)];
|
|
12774
|
+
return { tabs: next, activeId: neighbour.id };
|
|
12775
|
+
}
|
|
12776
|
+
function canAddTab(count, max) {
|
|
12777
|
+
return count < max;
|
|
12778
|
+
}
|
|
12779
|
+
function tabTitle(url, pageTitle) {
|
|
12780
|
+
if (pageTitle.trim() !== "") return pageTitle;
|
|
12781
|
+
if (url.trim() === "" || url.trim() === "about:blank") return "New tab";
|
|
12782
|
+
try {
|
|
12783
|
+
return new URL(url).host || url;
|
|
12784
|
+
} catch {
|
|
12785
|
+
return url;
|
|
12786
|
+
}
|
|
12787
|
+
}
|
|
12760
12788
|
const CUSTOM_PRESET_ID = "custom";
|
|
12761
12789
|
const FALLBACK_SCALE = 2;
|
|
12762
12790
|
function sameError(a, b) {
|
|
@@ -12764,81 +12792,175 @@ function sameError(a, b) {
|
|
|
12764
12792
|
if (a === null || b === null) return false;
|
|
12765
12793
|
return a.code === b.code && a.url === b.url && a.description === b.description;
|
|
12766
12794
|
}
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12795
|
+
function blankTab() {
|
|
12796
|
+
return {
|
|
12797
|
+
mode: "url",
|
|
12798
|
+
url: "",
|
|
12799
|
+
title: "",
|
|
12800
|
+
lastUrl: "",
|
|
12801
|
+
presetId: "1080p-24",
|
|
12802
|
+
custom: { width: 1920, height: 1080, diagonalInches: 24 },
|
|
12803
|
+
pixelExact: false,
|
|
12804
|
+
profileId: PANEL_PROFILES[0].id,
|
|
12805
|
+
profileOverride: null,
|
|
12806
|
+
targetLoading: false,
|
|
12807
|
+
error: null,
|
|
12808
|
+
image: null,
|
|
12809
|
+
// Fit, not 1:1: fit never enlarges past 1:1, so a render that already fits
|
|
12810
|
+
// its pane opens at true magnification anyway, while one that does not is
|
|
12811
|
+
// shown whole instead of as its top-left corner. Fit is interactive, so
|
|
12812
|
+
// this costs nothing — and the footer names the actual magnification
|
|
12813
|
+
// whenever fit is minifying.
|
|
12814
|
+
viewMode: "fit",
|
|
12815
|
+
fitScale: null,
|
|
12816
|
+
agentPan: null,
|
|
12817
|
+
agentHighlight: null
|
|
12818
|
+
};
|
|
12819
|
+
}
|
|
12820
|
+
let nextTabId = 0;
|
|
12821
|
+
function newTabId() {
|
|
12822
|
+
nextTabId += 1;
|
|
12823
|
+
return `local-${nextTabId}`;
|
|
12824
|
+
}
|
|
12825
|
+
const patchActiveWith = (f) => (s) => {
|
|
12826
|
+
const active = s.tabs[s.activeId];
|
|
12827
|
+
const patch = f(active);
|
|
12828
|
+
return patch === null ? {} : { tabs: { ...s.tabs, [s.activeId]: { ...active, ...patch } } };
|
|
12829
|
+
};
|
|
12830
|
+
const patchActive = (patch) => patchActiveWith(() => patch);
|
|
12831
|
+
const patchTabWith = (id, f) => (s) => {
|
|
12832
|
+
const t = s.tabs[id];
|
|
12833
|
+
if (!t) return {};
|
|
12834
|
+
const patch = f(t);
|
|
12835
|
+
return patch === null ? {} : { tabs: { ...s.tabs, [id]: { ...t, ...patch } } };
|
|
12836
|
+
};
|
|
12837
|
+
const FIRST_TAB = newTabId();
|
|
12838
|
+
const useStore = create()((set, get) => ({
|
|
12839
|
+
tabs: { [FIRST_TAB]: blankTab() },
|
|
12840
|
+
tabOrder: [FIRST_TAB],
|
|
12841
|
+
activeId: FIRST_TAB,
|
|
12776
12842
|
settings: { ...DEFAULT_SETTINGS },
|
|
12777
12843
|
// Zeroes until the first `getHostInfo`; `selectScale` falls back meanwhile.
|
|
12778
12844
|
host: { physicalWidth: 0, physicalHeight: 0, scaleFactor: 0 },
|
|
12779
|
-
targetLoading: false,
|
|
12780
|
-
error: null,
|
|
12781
12845
|
toast: null,
|
|
12782
12846
|
update: null,
|
|
12783
|
-
|
|
12847
|
+
history: [],
|
|
12784
12848
|
surround: "graphite",
|
|
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",
|
|
12791
12849
|
panes: "both",
|
|
12792
|
-
fitScale: null,
|
|
12793
|
-
agentPan: null,
|
|
12794
|
-
agentHighlight: null,
|
|
12795
12850
|
// Does not clear `error`: a failed load navigates to Chromium's error page,
|
|
12796
12851
|
// so clearing here would wipe the toolbar badge the moment it appeared.
|
|
12797
12852
|
// Does clear the agent highlight: it marked pixels of the page that was
|
|
12798
12853
|
// showing, and a committed navigation (a reload included) replaces them.
|
|
12799
|
-
setUrl: (url) => set({ url, agentHighlight: null }),
|
|
12854
|
+
setUrl: (url) => set((s) => patchTabWith(s.activeId, () => ({ url, agentHighlight: null }))(s)),
|
|
12855
|
+
setTabUrl: (id, url) => set(patchTabWith(id, () => ({ url, agentHighlight: null }))),
|
|
12856
|
+
setTabTitle: (id, title) => set(patchTabWith(id, (t) => t.title === title ? null : { title })),
|
|
12857
|
+
// Both panes report the same failure; see `setError`.
|
|
12858
|
+
setTabError: (id, error) => set(patchTabWith(id, (t) => sameError(t.error, error) ? null : { error })),
|
|
12859
|
+
setTabLoading: (id, targetLoading) => set(patchTabWith(id, () => ({ targetLoading }))),
|
|
12800
12860
|
// A screen change re-rasters the target, so a highlight's target-pixel rect
|
|
12801
12861
|
// no longer marks what it marked; the same for the custom fields below.
|
|
12802
|
-
setPreset: (presetId) => set({ presetId, agentHighlight: null }),
|
|
12803
|
-
setCustom: (c) => set((
|
|
12804
|
-
setPixelExact: (pixelExact) => set({ pixelExact }),
|
|
12862
|
+
setPreset: (presetId) => set(patchActive({ presetId, agentHighlight: null })),
|
|
12863
|
+
setCustom: (c) => set(patchActiveWith((t) => ({ custom: { ...t.custom, ...c }, presetId: CUSTOM_PRESET_ID, agentHighlight: null }))),
|
|
12864
|
+
setPixelExact: (pixelExact) => set(patchActive({ pixelExact })),
|
|
12805
12865
|
// Picking a profile drops any hand-tuned slider values.
|
|
12806
|
-
setProfile: (profileId) => set({ profileId, profileOverride: null }),
|
|
12807
|
-
setProfileOverride: (profileOverride) => set({ profileOverride }),
|
|
12866
|
+
setProfile: (profileId) => set(patchActive({ profileId, profileOverride: null })),
|
|
12867
|
+
setProfileOverride: (profileOverride) => set(patchActive({ profileOverride })),
|
|
12808
12868
|
setSettings: (settings) => set({ settings }),
|
|
12809
12869
|
setHost: (host) => set({ host }),
|
|
12810
|
-
setTargetLoading: (targetLoading) => set({ targetLoading }),
|
|
12870
|
+
setTargetLoading: (targetLoading) => set(patchActive({ targetLoading })),
|
|
12811
12871
|
// Both panes report the same `loadError` for one failed navigation; the
|
|
12812
12872
|
// duplicate must not replace the object and re-render everything twice.
|
|
12813
|
-
setError: (error) => set((
|
|
12873
|
+
setError: (error) => set(patchActiveWith((t) => sameError(t.error, error) ? null : { error })),
|
|
12814
12874
|
setUpdate: (update) => set({ update }),
|
|
12875
|
+
setHistory: (history) => set({ history }),
|
|
12815
12876
|
setToast: (toast) => set({ toast }),
|
|
12816
|
-
setImage: (image) => set({ image }),
|
|
12877
|
+
setImage: (image) => set(patchActive({ image })),
|
|
12817
12878
|
setSurround: (surround) => set({ surround }),
|
|
12818
|
-
setViewMode: (viewMode) => set({ viewMode }),
|
|
12879
|
+
setViewMode: (viewMode) => set(patchActive({ viewMode })),
|
|
12819
12880
|
// No `agentHighlight: null` here, unlike setPreset: hiding a pane does not
|
|
12820
12881
|
// re-raster the target, so the highlight still marks the pixels it marked.
|
|
12821
12882
|
setPanes: (panes) => set({ panes }),
|
|
12822
|
-
setFitScale: (fitScale2) => set({ fitScale: fitScale2 }),
|
|
12823
|
-
requestAgentPan: (p) => set((
|
|
12824
|
-
clearAgentPan: () => set({ agentPan: null }),
|
|
12825
|
-
showAgentHighlight: (h) => set((
|
|
12826
|
-
clearAgentHighlight: (seq) => set((
|
|
12883
|
+
setFitScale: (fitScale2) => set(patchActive({ fitScale: fitScale2 })),
|
|
12884
|
+
requestAgentPan: (p) => set(patchActiveWith((t) => ({ agentPan: { ...p, seq: (t.agentPan?.seq ?? 0) + 1 } }))),
|
|
12885
|
+
clearAgentPan: () => set(patchActive({ agentPan: null })),
|
|
12886
|
+
showAgentHighlight: (h) => set(patchActiveWith((t) => ({ agentHighlight: { ...h, seq: (t.agentHighlight?.seq ?? 0) + 1 } }))),
|
|
12887
|
+
clearAgentHighlight: (seq) => set(patchActiveWith((t) => seq === void 0 || t.agentHighlight?.seq === seq ? { agentHighlight: null } : null)),
|
|
12827
12888
|
// Spec §7: leaving image mode restores the URL that was showing before.
|
|
12828
12889
|
// Either direction swaps what the target pane shows, so a highlight over
|
|
12829
12890
|
// the old content is dropped with it.
|
|
12830
12891
|
setMode: (mode) => set(
|
|
12831
|
-
(
|
|
12832
|
-
|
|
12892
|
+
patchActiveWith(
|
|
12893
|
+
(t) => mode === t.mode ? null : mode === "image" ? { mode, lastUrl: t.url, agentHighlight: null } : { mode, url: t.lastUrl, image: null, agentHighlight: null }
|
|
12894
|
+
)
|
|
12895
|
+
),
|
|
12896
|
+
syncTabs: (snap) => set((s) => {
|
|
12897
|
+
if (snap.tabs.length === 0) return {};
|
|
12898
|
+
const tabs = {};
|
|
12899
|
+
for (const info of snap.tabs) {
|
|
12900
|
+
const existing = s.tabs[info.id];
|
|
12901
|
+
tabs[info.id] = existing ? (
|
|
12902
|
+
// url and title are main's to know — it is what every tab's panes
|
|
12903
|
+
// report to — so the snapshot is authoritative for those two and
|
|
12904
|
+
// for nothing else.
|
|
12905
|
+
existing.url === info.url && existing.title === info.title ? existing : { ...existing, url: info.url, title: info.title }
|
|
12906
|
+
) : (
|
|
12907
|
+
// A tab the renderer has never seen. Its screen comes from the
|
|
12908
|
+
// snapshot rather than from `blankTab`'s defaults, because main
|
|
12909
|
+
// may have restored it from disk with a preset chosen in a
|
|
12910
|
+
// previous launch — and for a tab genuinely opened just now, the
|
|
12911
|
+
// session's own defaults are those same defaults.
|
|
12912
|
+
{
|
|
12913
|
+
...blankTab(),
|
|
12914
|
+
url: info.url,
|
|
12915
|
+
title: info.title,
|
|
12916
|
+
presetId: info.presetId,
|
|
12917
|
+
profileId: info.profileId
|
|
12918
|
+
}
|
|
12919
|
+
);
|
|
12920
|
+
}
|
|
12921
|
+
const tabOrder = snap.tabs.map((t) => t.id);
|
|
12922
|
+
return { tabs, tabOrder, activeId: tabs[snap.activeId] ? snap.activeId : tabOrder[0] };
|
|
12923
|
+
}),
|
|
12924
|
+
addTab: (id) => {
|
|
12925
|
+
const s = get();
|
|
12926
|
+
if (!canAddTab(s.tabOrder.length, s.settings.maxTabs)) return null;
|
|
12927
|
+
const next = id ?? newTabId();
|
|
12928
|
+
if (s.tabs[next]) {
|
|
12929
|
+
set({ activeId: next });
|
|
12930
|
+
return next;
|
|
12931
|
+
}
|
|
12932
|
+
set({ tabs: { ...s.tabs, [next]: blankTab() }, tabOrder: [...s.tabOrder, next], activeId: next });
|
|
12933
|
+
return next;
|
|
12934
|
+
},
|
|
12935
|
+
closeTab: (id) => set((s) => {
|
|
12936
|
+
if (!s.tabs[id]) return {};
|
|
12937
|
+
const result = closeTab(
|
|
12938
|
+
s.tabOrder.map((tabId) => ({ id: tabId })),
|
|
12939
|
+
id,
|
|
12940
|
+
s.activeId
|
|
12941
|
+
);
|
|
12942
|
+
if (result.activeId === null) {
|
|
12943
|
+
const fresh = newTabId();
|
|
12944
|
+
return { tabs: { [fresh]: blankTab() }, tabOrder: [fresh], activeId: fresh };
|
|
12945
|
+
}
|
|
12946
|
+
const tabs = { ...s.tabs };
|
|
12947
|
+
delete tabs[id];
|
|
12948
|
+
return { tabs, tabOrder: result.tabs.map((t) => t.id), activeId: result.activeId };
|
|
12949
|
+
}),
|
|
12950
|
+
activateTab: (id) => set((s) => s.tabs[id] ? { activeId: id } : {})
|
|
12833
12951
|
}));
|
|
12952
|
+
function selectTab(s) {
|
|
12953
|
+
return s.tabs[s.activeId];
|
|
12954
|
+
}
|
|
12834
12955
|
function selectScreen(s) {
|
|
12835
|
-
const
|
|
12956
|
+
const tab = selectTab(s);
|
|
12957
|
+
const preset = SCREEN_PRESETS.find((p) => p.id === tab.presetId);
|
|
12836
12958
|
return preset ? {
|
|
12837
12959
|
width: preset.width,
|
|
12838
12960
|
height: preset.height,
|
|
12839
12961
|
diagonalInches: preset.diagonalInches,
|
|
12840
12962
|
deviceScaleFactor: preset.deviceScaleFactor
|
|
12841
|
-
} :
|
|
12963
|
+
} : tab.custom;
|
|
12842
12964
|
}
|
|
12843
12965
|
function selectDeviceScaleFactor(s) {
|
|
12844
12966
|
return selectScreen(s).deviceScaleFactor ?? 1;
|
|
@@ -12858,7 +12980,7 @@ function calibratedScale(s) {
|
|
|
12858
12980
|
diagonalInches: s.settings.hostDiagonalInches,
|
|
12859
12981
|
scaleFactor: s.host.scaleFactor
|
|
12860
12982
|
};
|
|
12861
|
-
const scale = computeScale(host, screen, s.pixelExact);
|
|
12983
|
+
const scale = computeScale(host, screen, selectTab(s).pixelExact);
|
|
12862
12984
|
return Number.isFinite(scale) && scale > 0 ? scale : null;
|
|
12863
12985
|
}
|
|
12864
12986
|
function selectScale(s) {
|
|
@@ -12871,13 +12993,15 @@ function selectHostNits(s) {
|
|
|
12871
12993
|
return s.settings.hostNits > 0 ? s.settings.hostNits : DEFAULT_SETTINGS.hostNits;
|
|
12872
12994
|
}
|
|
12873
12995
|
function selectProfile(s) {
|
|
12874
|
-
|
|
12996
|
+
const tab = selectTab(s);
|
|
12997
|
+
return tab.profileOverride ?? findProfile(tab.profileId);
|
|
12875
12998
|
}
|
|
12876
12999
|
function selectPanelParams(s) {
|
|
12877
13000
|
return profileToParams(selectProfile(s), selectHostNits(s));
|
|
12878
13001
|
}
|
|
12879
13002
|
function selectUrlBarText(s) {
|
|
12880
|
-
|
|
13003
|
+
const tab = selectTab(s);
|
|
13004
|
+
return tab.mode === "image" ? tab.image?.name ?? "" : tab.url;
|
|
12881
13005
|
}
|
|
12882
13006
|
const SCALES = [1, 2, 3];
|
|
12883
13007
|
const DEFAULT_SCALE = 2;
|
|
@@ -13054,10 +13178,10 @@ function TargetFooter() {
|
|
|
13054
13178
|
const params = useStore(useShallow(selectPanelParams));
|
|
13055
13179
|
const scale = useStore(selectScale);
|
|
13056
13180
|
const profile = useStore(selectProfile);
|
|
13057
|
-
const image = useStore((s) => s.image);
|
|
13058
|
-
const mode = useStore((s) => s.mode);
|
|
13059
|
-
const viewMode = useStore((s) => s.viewMode);
|
|
13060
|
-
const fitScale2 = useStore((s) => s.fitScale);
|
|
13181
|
+
const image = useStore((s) => selectTab(s).image);
|
|
13182
|
+
const mode = useStore((s) => selectTab(s).mode);
|
|
13183
|
+
const viewMode = useStore((s) => selectTab(s).viewMode);
|
|
13184
|
+
const fitScale2 = useStore((s) => selectTab(s).fitScale);
|
|
13061
13185
|
const dsf = useStore(selectDeviceScaleFactor);
|
|
13062
13186
|
const size = mode === "image" && image ? `${image.width}×${image.height}` : `${viewport.width}×${viewport.height}${dsf > 1 ? ` @${dsf}x` : ""}`;
|
|
13063
13187
|
const depth = params.levels <= 63 ? "6-bit" : "8-bit";
|
|
@@ -13115,6 +13239,98 @@ function NativeSlot() {
|
|
|
13115
13239
|
/* @__PURE__ */ jsxRuntimeExports.jsx(NativeFooter, { width: size.width, height: size.height })
|
|
13116
13240
|
] });
|
|
13117
13241
|
}
|
|
13242
|
+
const MIN_PANE_PX = 240;
|
|
13243
|
+
const KEY_STEP = 0.02;
|
|
13244
|
+
const KEY_STEP_COARSE = 0.1;
|
|
13245
|
+
function PaneDivider() {
|
|
13246
|
+
const split = useStore((s) => s.settings.split);
|
|
13247
|
+
const ref = reactExports.useRef(null);
|
|
13248
|
+
const drag = reactExports.useRef(null);
|
|
13249
|
+
const geometry = () => {
|
|
13250
|
+
const el = ref.current;
|
|
13251
|
+
const row = el?.parentElement;
|
|
13252
|
+
if (!el || !row) return { left: 0, usable: 1, min: 0.5, max: 0.5 };
|
|
13253
|
+
const r = row.getBoundingClientRect();
|
|
13254
|
+
const usable2 = Math.max(1, r.width - el.getBoundingClientRect().width);
|
|
13255
|
+
const floor = MIN_PANE_PX / usable2;
|
|
13256
|
+
return {
|
|
13257
|
+
left: r.left,
|
|
13258
|
+
usable: usable2,
|
|
13259
|
+
min: Math.min(Math.max(SPLIT_MIN, floor), 0.5),
|
|
13260
|
+
max: Math.max(Math.min(SPLIT_MAX, 1 - floor), 0.5)
|
|
13261
|
+
};
|
|
13262
|
+
};
|
|
13263
|
+
const clamp = (ratio) => {
|
|
13264
|
+
const g = geometry();
|
|
13265
|
+
return Math.min(Math.max(ratio, g.min), g.max);
|
|
13266
|
+
};
|
|
13267
|
+
const preview = (ratio) => {
|
|
13268
|
+
const s = useStore.getState();
|
|
13269
|
+
const next = clamp(ratio);
|
|
13270
|
+
if (next === s.settings.split) return;
|
|
13271
|
+
s.setSettings({ ...s.settings, split: next });
|
|
13272
|
+
};
|
|
13273
|
+
const persist = (before) => {
|
|
13274
|
+
const now = useStore.getState().settings;
|
|
13275
|
+
if (now.split === before.split) return;
|
|
13276
|
+
window.obsrv.setSettings(now).catch(() => useStore.getState().setSettings(before));
|
|
13277
|
+
};
|
|
13278
|
+
const commit = (ratio) => {
|
|
13279
|
+
const before = useStore.getState().settings;
|
|
13280
|
+
preview(ratio);
|
|
13281
|
+
persist(before);
|
|
13282
|
+
};
|
|
13283
|
+
const onPointerDown = (e) => {
|
|
13284
|
+
if (e.button !== 0) return;
|
|
13285
|
+
e.preventDefault();
|
|
13286
|
+
const el = e.currentTarget;
|
|
13287
|
+
el.setPointerCapture(e.pointerId);
|
|
13288
|
+
drag.current = {
|
|
13289
|
+
before: useStore.getState().settings,
|
|
13290
|
+
grab: e.clientX - el.getBoundingClientRect().left
|
|
13291
|
+
};
|
|
13292
|
+
};
|
|
13293
|
+
const onPointerMove = (e) => {
|
|
13294
|
+
const d = drag.current;
|
|
13295
|
+
if (!d) return;
|
|
13296
|
+
const g = geometry();
|
|
13297
|
+
preview((e.clientX - d.grab - g.left) / g.usable);
|
|
13298
|
+
};
|
|
13299
|
+
const onPointerUp = (e) => {
|
|
13300
|
+
const d = drag.current;
|
|
13301
|
+
if (!d) return;
|
|
13302
|
+
drag.current = null;
|
|
13303
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
13304
|
+
persist(d.before);
|
|
13305
|
+
};
|
|
13306
|
+
const onKeyDown = (e) => {
|
|
13307
|
+
const step = e.shiftKey ? KEY_STEP_COARSE : KEY_STEP;
|
|
13308
|
+
const delta = e.key === "ArrowLeft" ? -step : e.key === "ArrowRight" ? step : 0;
|
|
13309
|
+
if (!delta) return;
|
|
13310
|
+
e.preventDefault();
|
|
13311
|
+
commit(useStore.getState().settings.split + delta);
|
|
13312
|
+
};
|
|
13313
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
13314
|
+
"div",
|
|
13315
|
+
{
|
|
13316
|
+
ref,
|
|
13317
|
+
className: "pane-divider",
|
|
13318
|
+
role: "separator",
|
|
13319
|
+
"aria-orientation": "vertical",
|
|
13320
|
+
"aria-label": "Pane split",
|
|
13321
|
+
tabIndex: 0,
|
|
13322
|
+
"aria-valuenow": Math.round(split * 100),
|
|
13323
|
+
"aria-valuemin": Math.round(SPLIT_MIN * 100),
|
|
13324
|
+
"aria-valuemax": Math.round(SPLIT_MAX * 100),
|
|
13325
|
+
onPointerDown,
|
|
13326
|
+
onPointerMove,
|
|
13327
|
+
onPointerUp,
|
|
13328
|
+
onPointerCancel: onPointerUp,
|
|
13329
|
+
onDoubleClick: () => commit(0.5),
|
|
13330
|
+
onKeyDown
|
|
13331
|
+
}
|
|
13332
|
+
);
|
|
13333
|
+
}
|
|
13118
13334
|
const CONTRAST_MAX = 3e3;
|
|
13119
13335
|
const CUSTOM_PROFILE_ID = "custom";
|
|
13120
13336
|
function profileToControls(p, hostNits) {
|
|
@@ -13308,7 +13524,8 @@ function SettingsPanel() {
|
|
|
13308
13524
|
const host = useStore(useShallow((s) => s.host));
|
|
13309
13525
|
const settings = useStore(useShallow((s) => s.settings));
|
|
13310
13526
|
const update = useStore((s) => s.update);
|
|
13311
|
-
const
|
|
13527
|
+
const history = useStore((s) => s.history);
|
|
13528
|
+
const custom = useStore(useShallow((s) => selectTab(s).custom));
|
|
13312
13529
|
const viewport = useStore(useShallow(selectViewport));
|
|
13313
13530
|
const scale = useStore(selectScale);
|
|
13314
13531
|
const fallback = useStore(selectScaleIsFallback);
|
|
@@ -13464,7 +13681,52 @@ function SettingsPanel() {
|
|
|
13464
13681
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Check for updates automatically" })
|
|
13465
13682
|
] }),
|
|
13466
13683
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "check-now", onClick: () => void window.obsrv.checkUpdate(), children: "Check now" }),
|
|
13467
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: "One unauthenticated request to GitHub, at most once a day. No identifiers are sent." })
|
|
13684
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: "One unauthenticated request to GitHub, at most once a day. No identifiers are sent." }),
|
|
13685
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { children: "Tabs" }),
|
|
13686
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
13687
|
+
NumberField,
|
|
13688
|
+
{
|
|
13689
|
+
className: "max-tabs",
|
|
13690
|
+
label: "Maximum tabs",
|
|
13691
|
+
unit: "tabs",
|
|
13692
|
+
value: settings.maxTabs,
|
|
13693
|
+
min: MAX_TABS_MIN,
|
|
13694
|
+
step: 1,
|
|
13695
|
+
onCommit: (v) => commit({
|
|
13696
|
+
...useStore.getState().settings,
|
|
13697
|
+
maxTabs: Math.min(MAX_TABS_MAX, Math.max(MAX_TABS_MIN, Math.round(v)))
|
|
13698
|
+
}),
|
|
13699
|
+
onInvalid: setHostError
|
|
13700
|
+
}
|
|
13701
|
+
),
|
|
13702
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: "Each tab is two Chromium renderers — a live pane and an offscreen render — plus the GPU and utility processes they pull in: twelve empty tabs measured 27 child processes and about 2.5 GB. A real page costs more. The cap is a memory decision, not a preference. Lowering it never closes a tab that is already open; it only stops new ones." }),
|
|
13703
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { children: "History" }),
|
|
13704
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "control inline record-history-toggle", children: [
|
|
13705
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
13706
|
+
"input",
|
|
13707
|
+
{
|
|
13708
|
+
type: "checkbox",
|
|
13709
|
+
checked: settings.recordHistory,
|
|
13710
|
+
onChange: (e) => commit({ ...settings, recordHistory: e.target.checked })
|
|
13711
|
+
}
|
|
13712
|
+
),
|
|
13713
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Remember visited addresses" })
|
|
13714
|
+
] }),
|
|
13715
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
13716
|
+
"button",
|
|
13717
|
+
{
|
|
13718
|
+
type: "button",
|
|
13719
|
+
className: "clear-history",
|
|
13720
|
+
disabled: history.length === 0,
|
|
13721
|
+
onClick: () => void window.obsrv.clearHistory(),
|
|
13722
|
+
children: "Clear history"
|
|
13723
|
+
}
|
|
13724
|
+
),
|
|
13725
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "readout", children: [
|
|
13726
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "history-count num", children: history.length }),
|
|
13727
|
+
history.length === 1 ? " address remembered" : " addresses remembered"
|
|
13728
|
+
] }),
|
|
13729
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: "Typed into the URL bar as suggestions and nowhere else. Turning this off stops recording and keeps what is stored; Clear erases it." })
|
|
13468
13730
|
] });
|
|
13469
13731
|
}
|
|
13470
13732
|
const VERT_SRC = `#version 300 es
|
|
@@ -13910,10 +14172,11 @@ function TargetCanvas({ onFatal, imageFrame }) {
|
|
|
13910
14172
|
const params = useStore(useShallow(selectPanelParams));
|
|
13911
14173
|
const dsf = useStore(selectDeviceScaleFactor);
|
|
13912
14174
|
const requestedScale = useStore(selectScale);
|
|
13913
|
-
const mode = useStore((s) => s.mode);
|
|
13914
|
-
const viewMode = useStore((s) => s.viewMode);
|
|
14175
|
+
const mode = useStore((s) => selectTab(s).mode);
|
|
14176
|
+
const viewMode = useStore((s) => selectTab(s).viewMode);
|
|
13915
14177
|
const setViewMode = useStore((s) => s.setViewMode);
|
|
13916
14178
|
const setFitScale = useStore((s) => s.setFitScale);
|
|
14179
|
+
const activeId = useStore((s) => s.activeId);
|
|
13917
14180
|
const [stalled, setStalled] = reactExports.useState(false);
|
|
13918
14181
|
const stallTimer = reactExports.useRef(0);
|
|
13919
14182
|
const armedOnce = reactExports.useRef(false);
|
|
@@ -13945,7 +14208,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
|
|
|
13945
14208
|
const smooth = fit || scale < 1;
|
|
13946
14209
|
reactExports.useEffect(() => {
|
|
13947
14210
|
setFitScale(fit ? scale : null);
|
|
13948
|
-
}, [fit, scale, setFitScale]);
|
|
14211
|
+
}, [fit, scale, activeId, setFitScale]);
|
|
13949
14212
|
reactExports.useEffect(() => () => setFitScale(null), [setFitScale]);
|
|
13950
14213
|
const draw = reactExports.useRef({ scale, params, smooth, dsf });
|
|
13951
14214
|
const imageRef = reactExports.useRef(imageFrame);
|
|
@@ -13984,7 +14247,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
|
|
|
13984
14247
|
offFrame = window.obsrv.onFrame((m) => {
|
|
13985
14248
|
disarm();
|
|
13986
14249
|
if (!gl) return;
|
|
13987
|
-
if (useStore.getState().mode !== "url") return;
|
|
14250
|
+
if (selectTab(useStore.getState()).mode !== "url") return;
|
|
13988
14251
|
gl.resizeSource(m.frameWidth, m.frameHeight);
|
|
13989
14252
|
gl.uploadSlice(m.frame);
|
|
13990
14253
|
schedule();
|
|
@@ -14012,7 +14275,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
|
|
|
14012
14275
|
const onWheel = (e) => {
|
|
14013
14276
|
if (e.altKey) {
|
|
14014
14277
|
e.preventDefault();
|
|
14015
|
-
if (useStore.getState().viewMode !== "1:1") return;
|
|
14278
|
+
if (selectTab(useStore.getState()).viewMode !== "1:1") return;
|
|
14016
14279
|
const body = canvas.closest(".pane-body");
|
|
14017
14280
|
if (body instanceof HTMLElement) {
|
|
14018
14281
|
body.scrollLeft += e.deltaX;
|
|
@@ -14020,7 +14283,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
|
|
|
14020
14283
|
}
|
|
14021
14284
|
return;
|
|
14022
14285
|
}
|
|
14023
|
-
if (useStore.getState().mode !== "url") return;
|
|
14286
|
+
if (selectTab(useStore.getState()).mode !== "url") return;
|
|
14024
14287
|
e.preventDefault();
|
|
14025
14288
|
const r = canvas.getBoundingClientRect();
|
|
14026
14289
|
const cssPerTarget = draw.current.scale * draw.current.dsf / (window.devicePixelRatio || 1);
|
|
@@ -14029,7 +14292,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
|
|
|
14029
14292
|
};
|
|
14030
14293
|
canvas.addEventListener("wheel", onWheel, { passive: false });
|
|
14031
14294
|
const onWindowUp = (e) => {
|
|
14032
|
-
if (useStore.getState().mode !== "url") return;
|
|
14295
|
+
if (selectTab(useStore.getState()).mode !== "url") return;
|
|
14033
14296
|
if (panRef.current || e.button === 1) return;
|
|
14034
14297
|
if (e.altKey && !forwardDrag.current) return;
|
|
14035
14298
|
if (e.target === canvas) return;
|
|
@@ -14052,8 +14315,10 @@ function TargetCanvas({ onFatal, imageFrame }) {
|
|
|
14052
14315
|
};
|
|
14053
14316
|
}, [onFatal, disarm]);
|
|
14054
14317
|
reactExports.useEffect(
|
|
14055
|
-
() => window.obsrv.onTargetNavigating(() => {
|
|
14056
|
-
|
|
14318
|
+
() => window.obsrv.onTargetNavigating(({ tabId }) => {
|
|
14319
|
+
const s = useStore.getState();
|
|
14320
|
+
if (tabId !== s.activeId) return;
|
|
14321
|
+
if (selectTab(s).mode === "url") arm();
|
|
14057
14322
|
}),
|
|
14058
14323
|
[arm]
|
|
14059
14324
|
);
|
|
@@ -14167,7 +14432,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
|
|
|
14167
14432
|
);
|
|
14168
14433
|
setViewMode("1:1");
|
|
14169
14434
|
};
|
|
14170
|
-
const agentPan = useStore((s) => s.agentPan);
|
|
14435
|
+
const agentPan = useStore((s) => selectTab(s).agentPan);
|
|
14171
14436
|
const clearAgentPan = useStore((s) => s.clearAgentPan);
|
|
14172
14437
|
reactExports.useEffect(() => {
|
|
14173
14438
|
if (!agentPan) return;
|
|
@@ -14184,7 +14449,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
|
|
|
14184
14449
|
body.scrollTop = jump.top;
|
|
14185
14450
|
}
|
|
14186
14451
|
}, [agentPan]);
|
|
14187
|
-
const agentHighlight = useStore((s) => s.agentHighlight);
|
|
14452
|
+
const agentHighlight = useStore((s) => selectTab(s).agentHighlight);
|
|
14188
14453
|
const clearAgentHighlight = useStore((s) => s.clearAgentHighlight);
|
|
14189
14454
|
reactExports.useEffect(() => {
|
|
14190
14455
|
if (!agentHighlight) return;
|
|
@@ -14279,6 +14544,33 @@ function Toast() {
|
|
|
14279
14544
|
if (!toast) return null;
|
|
14280
14545
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "toast", role: "status", children: toast });
|
|
14281
14546
|
}
|
|
14547
|
+
const HISTORY_SUGGESTIONS = 6;
|
|
14548
|
+
function byRank(a, b) {
|
|
14549
|
+
if (a.lastVisit !== b.lastVisit) return b.lastVisit - a.lastVisit;
|
|
14550
|
+
if (a.visits !== b.visits) return b.visits - a.visits;
|
|
14551
|
+
return a.url < b.url ? -1 : a.url > b.url ? 1 : 0;
|
|
14552
|
+
}
|
|
14553
|
+
function matchHistory(entries, query, limit = HISTORY_SUGGESTIONS) {
|
|
14554
|
+
const needle = query.trim().toLowerCase();
|
|
14555
|
+
return entries.filter((e) => e.url.toLowerCase().includes(needle)).sort(byRank).slice(0, Math.max(0, limit));
|
|
14556
|
+
}
|
|
14557
|
+
const AGENT_ACTIVITY_MS = 3e3;
|
|
14558
|
+
function useAgentActivity() {
|
|
14559
|
+
const [active, setActive] = reactExports.useState(false);
|
|
14560
|
+
reactExports.useEffect(() => {
|
|
14561
|
+
let timer;
|
|
14562
|
+
const off = window.obsrv.onAgentActivity(() => {
|
|
14563
|
+
setActive(true);
|
|
14564
|
+
clearTimeout(timer);
|
|
14565
|
+
timer = setTimeout(() => setActive(false), AGENT_ACTIVITY_MS);
|
|
14566
|
+
});
|
|
14567
|
+
return () => {
|
|
14568
|
+
clearTimeout(timer);
|
|
14569
|
+
off();
|
|
14570
|
+
};
|
|
14571
|
+
}, []);
|
|
14572
|
+
return active;
|
|
14573
|
+
}
|
|
14282
14574
|
/**
|
|
14283
14575
|
* @license lucide-react v1.34.0 - ISC
|
|
14284
14576
|
*
|
|
@@ -14406,42 +14698,53 @@ const createLucideIcon = (iconName, iconNode) => {
|
|
|
14406
14698
|
* This source code is licensed under the ISC license.
|
|
14407
14699
|
* See the LICENSE file in the root directory of this source tree.
|
|
14408
14700
|
*/
|
|
14409
|
-
const __iconNode$
|
|
14701
|
+
const __iconNode$8 = [
|
|
14410
14702
|
["path", { d: "m12 19-7-7 7-7", key: "1l729n" }],
|
|
14411
14703
|
["path", { d: "M19 12H5", key: "x3x0zl" }]
|
|
14412
14704
|
];
|
|
14413
|
-
const ArrowLeft = createLucideIcon("arrow-left", __iconNode$
|
|
14705
|
+
const ArrowLeft = createLucideIcon("arrow-left", __iconNode$8);
|
|
14414
14706
|
/**
|
|
14415
14707
|
* @license lucide-react v1.34.0 - ISC
|
|
14416
14708
|
*
|
|
14417
14709
|
* This source code is licensed under the ISC license.
|
|
14418
14710
|
* See the LICENSE file in the root directory of this source tree.
|
|
14419
14711
|
*/
|
|
14420
|
-
const __iconNode$
|
|
14712
|
+
const __iconNode$7 = [
|
|
14421
14713
|
["path", { d: "M5 12h14", key: "1ays0h" }],
|
|
14422
14714
|
["path", { d: "m12 5 7 7-7 7", key: "xquz4c" }]
|
|
14423
14715
|
];
|
|
14424
|
-
const ArrowRight = createLucideIcon("arrow-right", __iconNode$
|
|
14716
|
+
const ArrowRight = createLucideIcon("arrow-right", __iconNode$7);
|
|
14425
14717
|
/**
|
|
14426
14718
|
* @license lucide-react v1.34.0 - ISC
|
|
14427
14719
|
*
|
|
14428
14720
|
* This source code is licensed under the ISC license.
|
|
14429
14721
|
* See the LICENSE file in the root directory of this source tree.
|
|
14430
14722
|
*/
|
|
14431
|
-
const __iconNode$
|
|
14432
|
-
const ChevronDown = createLucideIcon("chevron-down", __iconNode$
|
|
14723
|
+
const __iconNode$6 = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
|
|
14724
|
+
const ChevronDown = createLucideIcon("chevron-down", __iconNode$6);
|
|
14433
14725
|
/**
|
|
14434
14726
|
* @license lucide-react v1.34.0 - ISC
|
|
14435
14727
|
*
|
|
14436
14728
|
* This source code is licensed under the ISC license.
|
|
14437
14729
|
* See the LICENSE file in the root directory of this source tree.
|
|
14438
14730
|
*/
|
|
14439
|
-
const __iconNode$
|
|
14731
|
+
const __iconNode$5 = [
|
|
14440
14732
|
["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }],
|
|
14441
14733
|
["circle", { cx: "12", cy: "5", r: "1", key: "gxeob9" }],
|
|
14442
14734
|
["circle", { cx: "12", cy: "19", r: "1", key: "lyex9k" }]
|
|
14443
14735
|
];
|
|
14444
|
-
const EllipsisVertical = createLucideIcon("ellipsis-vertical", __iconNode$
|
|
14736
|
+
const EllipsisVertical = createLucideIcon("ellipsis-vertical", __iconNode$5);
|
|
14737
|
+
/**
|
|
14738
|
+
* @license lucide-react v1.34.0 - ISC
|
|
14739
|
+
*
|
|
14740
|
+
* This source code is licensed under the ISC license.
|
|
14741
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
14742
|
+
*/
|
|
14743
|
+
const __iconNode$4 = [
|
|
14744
|
+
["path", { d: "M5 12h14", key: "1ays0h" }],
|
|
14745
|
+
["path", { d: "M12 5v14", key: "s699le" }]
|
|
14746
|
+
];
|
|
14747
|
+
const Plus = createLucideIcon("plus", __iconNode$4);
|
|
14445
14748
|
/**
|
|
14446
14749
|
* @license lucide-react v1.34.0 - ISC
|
|
14447
14750
|
*
|
|
@@ -14507,7 +14810,8 @@ const ICONS = {
|
|
|
14507
14810
|
close: X,
|
|
14508
14811
|
sliders: SlidersHorizontal,
|
|
14509
14812
|
gear: Settings,
|
|
14510
|
-
chevron: ChevronDown
|
|
14813
|
+
chevron: ChevronDown,
|
|
14814
|
+
plus: Plus
|
|
14511
14815
|
};
|
|
14512
14816
|
function Icon({ name, size = 16 }) {
|
|
14513
14817
|
const Glyph = ICONS[name];
|
|
@@ -14578,6 +14882,77 @@ function Segmented({
|
|
|
14578
14882
|
o.id
|
|
14579
14883
|
)) });
|
|
14580
14884
|
}
|
|
14885
|
+
function TabBar() {
|
|
14886
|
+
const tabOrder = useStore(useShallow((s) => s.tabOrder));
|
|
14887
|
+
const activeId = useStore((s) => s.activeId);
|
|
14888
|
+
const maxTabs = useStore((s) => s.settings.maxTabs);
|
|
14889
|
+
const driving = useStore((s) => s.settings.agentControl);
|
|
14890
|
+
const agentActive = useAgentActivity();
|
|
14891
|
+
const canAdd = canAddTab(tabOrder.length, maxTabs);
|
|
14892
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "chrome-row chrome-tabs", children: [
|
|
14893
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tabs", role: "tablist", "aria-label": "Open tabs", children: tabOrder.map((id) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
14894
|
+
Tab,
|
|
14895
|
+
{
|
|
14896
|
+
id,
|
|
14897
|
+
active: id === activeId,
|
|
14898
|
+
driven: driving && id === activeId,
|
|
14899
|
+
busy: agentActive
|
|
14900
|
+
},
|
|
14901
|
+
id
|
|
14902
|
+
)) }),
|
|
14903
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
14904
|
+
"button",
|
|
14905
|
+
{
|
|
14906
|
+
className: "tab-new",
|
|
14907
|
+
type: "button",
|
|
14908
|
+
"aria-label": "New tab",
|
|
14909
|
+
disabled: !canAdd,
|
|
14910
|
+
title: canAdd ? "New tab" : `${maxTabs} tabs is the limit — each one is two Chromium renderers, and twelve tabs cost about 2.5 GB. Raise it in Settings.`,
|
|
14911
|
+
onClick: () => {
|
|
14912
|
+
void window.obsrv.addTab();
|
|
14913
|
+
},
|
|
14914
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: "plus", size: 14 })
|
|
14915
|
+
}
|
|
14916
|
+
)
|
|
14917
|
+
] });
|
|
14918
|
+
}
|
|
14919
|
+
function Tab({
|
|
14920
|
+
id,
|
|
14921
|
+
active,
|
|
14922
|
+
driven,
|
|
14923
|
+
busy
|
|
14924
|
+
}) {
|
|
14925
|
+
const label = useStore((s) => {
|
|
14926
|
+
const t = s.tabs[id];
|
|
14927
|
+
return t ? tabTitle(t.url, t.title) : "";
|
|
14928
|
+
});
|
|
14929
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `tab${driven ? " driven" : ""}${driven && busy ? " busy" : ""}`, children: [
|
|
14930
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
14931
|
+
"button",
|
|
14932
|
+
{
|
|
14933
|
+
className: "tab-label",
|
|
14934
|
+
type: "button",
|
|
14935
|
+
role: "tab",
|
|
14936
|
+
"aria-selected": active,
|
|
14937
|
+
title: driven ? `${label}
|
|
14938
|
+
Agent control is driving this tab` : label,
|
|
14939
|
+
onClick: () => window.obsrv.activateTab(id),
|
|
14940
|
+
children: label
|
|
14941
|
+
}
|
|
14942
|
+
),
|
|
14943
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
14944
|
+
"button",
|
|
14945
|
+
{
|
|
14946
|
+
className: "tab-close",
|
|
14947
|
+
type: "button",
|
|
14948
|
+
"aria-label": `Close ${label}`,
|
|
14949
|
+
title: "Close tab",
|
|
14950
|
+
onClick: () => window.obsrv.closeTab(id),
|
|
14951
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: "close", size: 12 })
|
|
14952
|
+
}
|
|
14953
|
+
)
|
|
14954
|
+
] });
|
|
14955
|
+
}
|
|
14581
14956
|
function Select({ className, value, label, ariaLabel, onChange, children }) {
|
|
14582
14957
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "select-shell", children: [
|
|
14583
14958
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "select-label", "aria-hidden": "true", children: label }),
|
|
@@ -14608,12 +14983,12 @@ const PANES = [
|
|
|
14608
14983
|
{ id: "target", label: "Target", title: "The target render alone, full width" }
|
|
14609
14984
|
];
|
|
14610
14985
|
function Toolbar({ drawer, onTogglePanel, onToggleSettings }) {
|
|
14611
|
-
const mode = useStore((s) => s.mode);
|
|
14612
|
-
const presetId = useStore((s) => s.presetId);
|
|
14613
|
-
const profileId = useStore((s) => s.profileId);
|
|
14614
|
-
const pixelExact = useStore((s) => s.pixelExact);
|
|
14615
|
-
const error = useStore((s) => s.error);
|
|
14616
|
-
const loading = useStore((s) => s.targetLoading);
|
|
14986
|
+
const mode = useStore((s) => selectTab(s).mode);
|
|
14987
|
+
const presetId = useStore((s) => selectTab(s).presetId);
|
|
14988
|
+
const profileId = useStore((s) => selectTab(s).profileId);
|
|
14989
|
+
const pixelExact = useStore((s) => selectTab(s).pixelExact);
|
|
14990
|
+
const error = useStore((s) => selectTab(s).error);
|
|
14991
|
+
const loading = useStore((s) => selectTab(s).targetLoading);
|
|
14617
14992
|
const barText = useStore(selectUrlBarText);
|
|
14618
14993
|
const viewport = useStore(useShallow(selectViewport));
|
|
14619
14994
|
const setUrl = useStore((s) => s.setUrl);
|
|
@@ -14625,27 +15000,25 @@ function Toolbar({ drawer, onTogglePanel, onToggleSettings }) {
|
|
|
14625
15000
|
const surround = useStore((s) => s.surround);
|
|
14626
15001
|
const update = useStore((s) => s.update);
|
|
14627
15002
|
const setSurround = useStore((s) => s.setSurround);
|
|
14628
|
-
const viewMode = useStore((s) => s.viewMode);
|
|
15003
|
+
const viewMode = useStore((s) => selectTab(s).viewMode);
|
|
14629
15004
|
const setViewMode = useStore((s) => s.setViewMode);
|
|
14630
15005
|
const panes = useStore((s) => s.panes);
|
|
14631
15006
|
const setPanes = useStore((s) => s.setPanes);
|
|
14632
15007
|
const agentControl = useStore((s) => s.settings.agentControl);
|
|
14633
15008
|
const setSettings = useStore((s) => s.setSettings);
|
|
15009
|
+
const history = useStore((s) => s.history);
|
|
14634
15010
|
const inputRef = reactExports.useRef(null);
|
|
14635
15011
|
const [draft, setDraft] = reactExports.useState(barText);
|
|
14636
|
-
const [
|
|
14637
|
-
reactExports.
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
off();
|
|
14647
|
-
};
|
|
14648
|
-
}, []);
|
|
15012
|
+
const [open, setOpen] = reactExports.useState(false);
|
|
15013
|
+
const [highlight, setHighlight] = reactExports.useState(-1);
|
|
15014
|
+
const matches = reactExports.useMemo(() => open ? matchHistory(history, draft) : [], [open, history, draft]);
|
|
15015
|
+
const listOpen = open && matches.length > 0;
|
|
15016
|
+
const closeList = () => {
|
|
15017
|
+
setOpen(false);
|
|
15018
|
+
setHighlight(-1);
|
|
15019
|
+
};
|
|
15020
|
+
const picked = highlight >= 0 && highlight < matches.length ? matches[highlight] : null;
|
|
15021
|
+
const agentActive = useAgentActivity();
|
|
14649
15022
|
const toggleAgent = () => {
|
|
14650
15023
|
const current = useStore.getState().settings;
|
|
14651
15024
|
const next = { ...current, agentControl: !current.agentControl };
|
|
@@ -14665,17 +15038,54 @@ function Toolbar({ drawer, onTogglePanel, onToggleSettings }) {
|
|
|
14665
15038
|
});
|
|
14666
15039
|
}, []);
|
|
14667
15040
|
const readOnly = mode === "image";
|
|
14668
|
-
|
|
14669
|
-
|
|
14670
|
-
|
|
15041
|
+
reactExports.useEffect(() => {
|
|
15042
|
+
if (readOnly) closeList();
|
|
15043
|
+
}, [readOnly]);
|
|
15044
|
+
const go = async (url) => {
|
|
15045
|
+
closeList();
|
|
14671
15046
|
setError(null);
|
|
14672
|
-
const applied = await window.obsrv.navigate(
|
|
15047
|
+
const applied = await window.obsrv.navigate(url);
|
|
14673
15048
|
setUrl(applied);
|
|
14674
15049
|
setDraft(applied);
|
|
14675
15050
|
};
|
|
15051
|
+
const submit = (e) => {
|
|
15052
|
+
e.preventDefault();
|
|
15053
|
+
if (readOnly || draft.trim() === "") return;
|
|
15054
|
+
void go(draft);
|
|
15055
|
+
};
|
|
15056
|
+
const onKeyDown = (e) => {
|
|
15057
|
+
if (readOnly) return;
|
|
15058
|
+
if (e.key === "ArrowDown") {
|
|
15059
|
+
e.preventDefault();
|
|
15060
|
+
if (!open) {
|
|
15061
|
+
setOpen(true);
|
|
15062
|
+
setHighlight(0);
|
|
15063
|
+
return;
|
|
15064
|
+
}
|
|
15065
|
+
setHighlight((h) => h + 1 >= matches.length ? -1 : h + 1);
|
|
15066
|
+
return;
|
|
15067
|
+
}
|
|
15068
|
+
if (e.key === "ArrowUp" && listOpen) {
|
|
15069
|
+
e.preventDefault();
|
|
15070
|
+
setHighlight((h) => h <= -1 ? matches.length - 1 : h - 1);
|
|
15071
|
+
return;
|
|
15072
|
+
}
|
|
15073
|
+
if (e.key === "Enter" && picked) {
|
|
15074
|
+
e.preventDefault();
|
|
15075
|
+
void go(picked.url);
|
|
15076
|
+
return;
|
|
15077
|
+
}
|
|
15078
|
+
if (e.key === "Escape") {
|
|
15079
|
+
const dismissed = listOpen;
|
|
15080
|
+
closeList();
|
|
15081
|
+
if (dismissed) return;
|
|
15082
|
+
setDraft(barText);
|
|
15083
|
+
}
|
|
15084
|
+
};
|
|
14676
15085
|
const presetLabel = SCREEN_PRESETS.find((p) => p.id === presetId)?.label ?? "Custom";
|
|
14677
15086
|
const profileLabel = PANEL_PROFILES.find((p) => p.id === profileId)?.label ?? profileId;
|
|
14678
15087
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "chrome", children: [
|
|
15088
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TabBar, {}),
|
|
14679
15089
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "chrome-row chrome-browse", children: [
|
|
14680
15090
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
14681
15091
|
"button",
|
|
@@ -14710,20 +15120,49 @@ function Toolbar({ drawer, onTogglePanel, onToggleSettings }) {
|
|
|
14710
15120
|
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: "reload" })
|
|
14711
15121
|
}
|
|
14712
15122
|
),
|
|
14713
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
14714
|
-
|
|
14715
|
-
|
|
14716
|
-
|
|
14717
|
-
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
15123
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("form", { className: "url-form", onSubmit: submit, children: [
|
|
15124
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
15125
|
+
"input",
|
|
15126
|
+
{
|
|
15127
|
+
ref: inputRef,
|
|
15128
|
+
value: draft,
|
|
15129
|
+
readOnly,
|
|
15130
|
+
spellCheck: false,
|
|
15131
|
+
placeholder: "Enter a URL, or drop a PNG",
|
|
15132
|
+
role: "combobox",
|
|
15133
|
+
"aria-expanded": listOpen,
|
|
15134
|
+
"aria-controls": "url-history",
|
|
15135
|
+
"aria-autocomplete": "list",
|
|
15136
|
+
"aria-activedescendant": picked ? `url-history-${highlight}` : void 0,
|
|
15137
|
+
onChange: (e) => {
|
|
15138
|
+
setDraft(e.target.value);
|
|
15139
|
+
setOpen(true);
|
|
15140
|
+
setHighlight(-1);
|
|
15141
|
+
},
|
|
15142
|
+
onKeyDown,
|
|
15143
|
+
onBlur: closeList
|
|
14724
15144
|
}
|
|
14725
|
-
|
|
14726
|
-
|
|
15145
|
+
),
|
|
15146
|
+
listOpen && /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: "url-history", id: "url-history", role: "listbox", "aria-label": "Visited addresses", children: matches.map((m, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
15147
|
+
"li",
|
|
15148
|
+
{
|
|
15149
|
+
id: `url-history-${i}`,
|
|
15150
|
+
className: `url-history-row${i === highlight ? " active" : ""}`,
|
|
15151
|
+
role: "option",
|
|
15152
|
+
"aria-selected": i === highlight,
|
|
15153
|
+
onMouseDown: (e) => {
|
|
15154
|
+
e.preventDefault();
|
|
15155
|
+
void go(m.url);
|
|
15156
|
+
},
|
|
15157
|
+
onMouseEnter: () => setHighlight(i),
|
|
15158
|
+
children: [
|
|
15159
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "url-history-url", children: /* @__PURE__ */ jsxRuntimeExports.jsx("bdi", { children: m.url }) }),
|
|
15160
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "url-history-age", children: formatAge(m.lastVisit, Date.now()) })
|
|
15161
|
+
]
|
|
15162
|
+
},
|
|
15163
|
+
m.url
|
|
15164
|
+
)) })
|
|
15165
|
+
] }),
|
|
14727
15166
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "status-cluster", children: [
|
|
14728
15167
|
mode === "image" && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
14729
15168
|
"button",
|
|
@@ -14878,50 +15317,71 @@ function Toolbar({ drawer, onTogglePanel, onToggleSettings }) {
|
|
|
14878
15317
|
function App() {
|
|
14879
15318
|
const [fatal, setFatal] = reactExports.useState(null);
|
|
14880
15319
|
const [drawer, setDrawer] = reactExports.useState("none");
|
|
14881
|
-
const [
|
|
15320
|
+
const [images, setImages] = reactExports.useState({});
|
|
14882
15321
|
const dropToken = reactExports.useRef(0);
|
|
14883
15322
|
const targetPaneRef = reactExports.useRef(null);
|
|
14884
15323
|
const [targetBounds, setTargetBounds] = reactExports.useState(null);
|
|
14885
15324
|
const [canvasBounds, setCanvasBounds] = reactExports.useState(null);
|
|
15325
|
+
const [tabsKnown, setTabsKnown] = reactExports.useState(false);
|
|
14886
15326
|
const toggle = (which) => () => setDrawer((d) => d === which ? "none" : which);
|
|
14887
15327
|
const setHost = useStore((s) => s.setHost);
|
|
14888
15328
|
const setSettings = useStore((s) => s.setSettings);
|
|
14889
|
-
const
|
|
14890
|
-
const
|
|
14891
|
-
const
|
|
15329
|
+
const setTabUrl = useStore((s) => s.setTabUrl);
|
|
15330
|
+
const setTabTitle = useStore((s) => s.setTabTitle);
|
|
15331
|
+
const setTabError = useStore((s) => s.setTabError);
|
|
15332
|
+
const setTabLoading = useStore((s) => s.setTabLoading);
|
|
15333
|
+
const syncTabs = useStore((s) => s.syncTabs);
|
|
14892
15334
|
const setUpdate = useStore((s) => s.setUpdate);
|
|
15335
|
+
const setHistory = useStore((s) => s.setHistory);
|
|
14893
15336
|
const setImageMeta = useStore((s) => s.setImage);
|
|
14894
15337
|
const setMode = useStore((s) => s.setMode);
|
|
14895
15338
|
const setToast = useStore((s) => s.setToast);
|
|
14896
|
-
const mode = useStore((s) => s.mode);
|
|
15339
|
+
const mode = useStore((s) => selectTab(s).mode);
|
|
14897
15340
|
const surround = useStore((s) => s.surround);
|
|
14898
15341
|
const viewport = useStore(useShallow(selectViewport));
|
|
14899
15342
|
const deviceScaleFactor = useStore(selectDeviceScaleFactor);
|
|
14900
|
-
const presetId = useStore((s) => s.presetId);
|
|
14901
|
-
const profileId = useStore((s) => s.profileId);
|
|
14902
|
-
const viewMode = useStore((s) => s.viewMode);
|
|
15343
|
+
const presetId = useStore((s) => selectTab(s).presetId);
|
|
15344
|
+
const profileId = useStore((s) => selectTab(s).profileId);
|
|
15345
|
+
const viewMode = useStore((s) => selectTab(s).viewMode);
|
|
15346
|
+
const activeId = useStore((s) => s.activeId);
|
|
15347
|
+
const tabOrder = useStore((s) => s.tabOrder);
|
|
14903
15348
|
const panes = useStore((s) => s.panes);
|
|
15349
|
+
const split = useStore((s) => s.settings.split);
|
|
14904
15350
|
reactExports.useEffect(() => {
|
|
14905
15351
|
window.obsrv.getHostInfo().then(setHost, (e) => console.warn("obsrv: getHostInfo failed", e));
|
|
14906
15352
|
window.obsrv.getSettings().then(setSettings, (e) => console.warn("obsrv: getSettings failed", e));
|
|
14907
15353
|
window.obsrv.getUpdate().then(setUpdate, (e) => console.warn("obsrv: getUpdate failed", e));
|
|
15354
|
+
window.obsrv.getHistory().then(setHistory, (e) => console.warn("obsrv: getHistory failed", e));
|
|
15355
|
+
window.obsrv.getTabs().then(syncTabs, (e) => console.warn("obsrv: getTabs failed", e)).finally(() => setTabsKnown(true));
|
|
14908
15356
|
const offs = [
|
|
14909
15357
|
window.obsrv.onHostChanged(setHost),
|
|
15358
|
+
// Every one of these names its tab, so a background tab keeps its own
|
|
15359
|
+
// strip entry current without rewriting the address bar of the tab in
|
|
15360
|
+
// front — which is what an unnamed report from a background tab did, and
|
|
15361
|
+
// why main used to gate them on the tab being in front at all.
|
|
15362
|
+
//
|
|
14910
15363
|
// A committed navigation — back, forward, reload, a link — supersedes
|
|
14911
|
-
//
|
|
14912
|
-
//
|
|
14913
|
-
|
|
14914
|
-
|
|
14915
|
-
|
|
15364
|
+
// that tab's last load failure, and only a committed one: Chromium
|
|
15365
|
+
// commits its error page without emitting `did-navigate`, so a failed
|
|
15366
|
+
// load reports no URL at all (measured on Electron 43; a bad host, a
|
|
15367
|
+
// refused connection, a missing file and a Back onto an error page all
|
|
15368
|
+
// fire `did-fail-load` alone). The badge therefore lands last by having
|
|
15369
|
+
// nothing race it.
|
|
15370
|
+
window.obsrv.onUrlChanged(({ tabId, url }) => {
|
|
15371
|
+
setTabError(tabId, null);
|
|
15372
|
+
setTabUrl(tabId, url);
|
|
14916
15373
|
}),
|
|
14917
|
-
window.obsrv.
|
|
14918
|
-
window.obsrv.
|
|
14919
|
-
window.obsrv.
|
|
15374
|
+
window.obsrv.onTitleChanged(({ tabId, title }) => setTabTitle(tabId, title)),
|
|
15375
|
+
window.obsrv.onLoadError(({ tabId, error }) => setTabError(tabId, error)),
|
|
15376
|
+
window.obsrv.onTargetLoading(({ tabId, loading }) => setTabLoading(tabId, loading)),
|
|
15377
|
+
window.obsrv.onTabsChanged(syncTabs),
|
|
15378
|
+
window.obsrv.onUpdateStatus(setUpdate),
|
|
15379
|
+
window.obsrv.onHistoryChanged(setHistory)
|
|
14920
15380
|
];
|
|
14921
15381
|
return () => {
|
|
14922
15382
|
for (const off of offs) off();
|
|
14923
15383
|
};
|
|
14924
|
-
}, [setHost, setSettings,
|
|
15384
|
+
}, [setHost, setSettings, setTabUrl, setTabTitle, setTabError, setTabLoading, syncTabs, setUpdate, setHistory]);
|
|
14925
15385
|
reactExports.useEffect(() => {
|
|
14926
15386
|
void window.obsrv.setViewport(viewport.width, viewport.height, deviceScaleFactor);
|
|
14927
15387
|
}, [viewport.width, viewport.height, deviceScaleFactor]);
|
|
@@ -14961,8 +15421,9 @@ function App() {
|
|
|
14961
15421
|
};
|
|
14962
15422
|
}, []);
|
|
14963
15423
|
reactExports.useEffect(() => {
|
|
14964
|
-
|
|
14965
|
-
|
|
15424
|
+
if (!tabsKnown) return;
|
|
15425
|
+
window.obsrv.reportUiState({ tabId: activeId, presetId, profileId, viewMode, panes, mode, targetBounds, canvasBounds });
|
|
15426
|
+
}, [tabsKnown, activeId, presetId, profileId, viewMode, panes, mode, targetBounds, canvasBounds]);
|
|
14966
15427
|
reactExports.useEffect(() => {
|
|
14967
15428
|
return window.obsrv.onAgentApply((patch) => {
|
|
14968
15429
|
const s = useStore.getState();
|
|
@@ -14978,21 +15439,24 @@ function App() {
|
|
|
14978
15439
|
reactExports.useEffect(() => {
|
|
14979
15440
|
document.documentElement.dataset.surround = surround;
|
|
14980
15441
|
}, [surround]);
|
|
15442
|
+
const image = images[activeId] ?? null;
|
|
14981
15443
|
const onImage = async (file, exportScale) => {
|
|
14982
15444
|
const token = ++dropToken.current;
|
|
15445
|
+
const tabId = activeId;
|
|
14983
15446
|
try {
|
|
14984
15447
|
const limits = {
|
|
14985
15448
|
...DEFAULT_IMAGE_LIMITS,
|
|
14986
15449
|
maxDimension: Math.min(DEFAULT_IMAGE_LIMITS.maxDimension, probeMaxTextureSize())
|
|
14987
15450
|
};
|
|
14988
15451
|
const loaded = await loadImage(file, exportScale, limits);
|
|
14989
|
-
if (token !== dropToken.current) {
|
|
15452
|
+
if (token !== dropToken.current || useStore.getState().activeId !== tabId) {
|
|
14990
15453
|
URL.revokeObjectURL(loaded.objectUrl);
|
|
14991
15454
|
return;
|
|
14992
15455
|
}
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
15456
|
+
setImages((previous) => {
|
|
15457
|
+
const stale = previous[tabId];
|
|
15458
|
+
if (stale) URL.revokeObjectURL(stale.objectUrl);
|
|
15459
|
+
return { ...previous, [tabId]: loaded };
|
|
14996
15460
|
});
|
|
14997
15461
|
setImageMeta({
|
|
14998
15462
|
name: file.name,
|
|
@@ -15011,11 +15475,28 @@ function App() {
|
|
|
15011
15475
|
};
|
|
15012
15476
|
reactExports.useEffect(() => {
|
|
15013
15477
|
if (mode === "image") return;
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
return
|
|
15478
|
+
setImages((previous) => {
|
|
15479
|
+
const going = previous[activeId];
|
|
15480
|
+
if (!going) return previous;
|
|
15481
|
+
URL.revokeObjectURL(going.objectUrl);
|
|
15482
|
+
const next = { ...previous };
|
|
15483
|
+
delete next[activeId];
|
|
15484
|
+
return next;
|
|
15017
15485
|
});
|
|
15018
|
-
}, [mode]);
|
|
15486
|
+
}, [mode, activeId]);
|
|
15487
|
+
reactExports.useEffect(() => {
|
|
15488
|
+
setImages((previous) => {
|
|
15489
|
+
const open = new Set(tabOrder);
|
|
15490
|
+
const gone = Object.keys(previous).filter((id) => !open.has(id));
|
|
15491
|
+
if (gone.length === 0) return previous;
|
|
15492
|
+
const next = { ...previous };
|
|
15493
|
+
for (const id of gone) {
|
|
15494
|
+
URL.revokeObjectURL(next[id].objectUrl);
|
|
15495
|
+
delete next[id];
|
|
15496
|
+
}
|
|
15497
|
+
return next;
|
|
15498
|
+
});
|
|
15499
|
+
}, [tabOrder]);
|
|
15019
15500
|
const imageFrame = reactExports.useMemo(() => {
|
|
15020
15501
|
if (mode !== "image" || !image) return null;
|
|
15021
15502
|
return {
|
|
@@ -15031,29 +15512,46 @@ function App() {
|
|
|
15031
15512
|
};
|
|
15032
15513
|
}, [mode, image]);
|
|
15033
15514
|
if (fatal) return /* @__PURE__ */ jsxRuntimeExports.jsx(Fatal, { message: fatal });
|
|
15034
|
-
return
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15515
|
+
return (
|
|
15516
|
+
// The split and the panes mode are published here rather than on `.panes`
|
|
15517
|
+
// because the chrome needs them too: the URL bar's history dropdown is
|
|
15518
|
+
// clamped to the native pane's right edge, and a custom property set on
|
|
15519
|
+
// `.panes` reaches nothing above it. Everything below still inherits them.
|
|
15520
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
15521
|
+
"div",
|
|
15522
|
+
{
|
|
15523
|
+
className: "app",
|
|
15524
|
+
"data-panes": panes,
|
|
15525
|
+
style: { "--split": split, "--pane-min": `${MIN_PANE_PX}px` },
|
|
15526
|
+
children: [
|
|
15527
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Toolbar, { drawer, onTogglePanel: toggle("panel"), onToggleSettings: toggle("settings") }),
|
|
15528
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DropZone, { onImage }),
|
|
15529
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "body", children: [
|
|
15530
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "panes", children: [
|
|
15531
|
+
panes === "both" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
15532
|
+
mode === "image" && image ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
15533
|
+
ImagePane,
|
|
15534
|
+
{
|
|
15535
|
+
src: image.objectUrl,
|
|
15536
|
+
width: image.natural.width,
|
|
15537
|
+
height: image.natural.height
|
|
15538
|
+
}
|
|
15539
|
+
) : /* @__PURE__ */ jsxRuntimeExports.jsx(NativeSlot, {}),
|
|
15540
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(PaneDivider, {})
|
|
15541
|
+
] }),
|
|
15542
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pane target-pane", ref: targetPaneRef, children: [
|
|
15543
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pane-body", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TargetCanvas, { onFatal: setFatal, imageFrame }) }),
|
|
15544
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TargetFooter, {})
|
|
15545
|
+
] })
|
|
15546
|
+
] }),
|
|
15547
|
+
drawer === "panel" && /* @__PURE__ */ jsxRuntimeExports.jsx("aside", { className: "drawer", children: /* @__PURE__ */ jsxRuntimeExports.jsx(PanelControls, {}) }),
|
|
15548
|
+
drawer === "settings" && /* @__PURE__ */ jsxRuntimeExports.jsx("aside", { className: "drawer", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsPanel, {}) })
|
|
15549
|
+
] }),
|
|
15550
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Toast, {})
|
|
15551
|
+
]
|
|
15552
|
+
}
|
|
15553
|
+
)
|
|
15554
|
+
);
|
|
15057
15555
|
}
|
|
15058
15556
|
function BrowserNotice() {
|
|
15059
15557
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "browser-notice", role: "note", children: [
|