chromosomix 0.16.0 → 0.17.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 +94 -10
- package/dist/THIRD-PARTY-NOTICES.txt +2 -2
- package/dist/assets/{ConnectCard-CWRFeF4q.js → ConnectCard-Dxqttpl6.js} +1 -1
- package/dist/assets/{Tour-5RTXNjqt.js → Tour-Bc2ltrKR.js} +1 -1
- package/dist/assets/analysis.worker-D6DADpOm.js +8 -0
- package/dist/assets/annotations.worker-B-6-la1Q.js +28 -0
- package/dist/assets/index-Cxw-esX1.css +1 -0
- package/dist/assets/index-VoOi0yM1.js +106 -0
- package/dist/assets/likelihood.worker-CUgAXfAe.js +1 -0
- package/dist/assets/reader.worker-Zet76cxB.js +9 -0
- package/dist/assets/{three-D5ICz1As.js → three-B_ANgE46.js} +1 -1
- package/dist/assets/{tutorial-qCyAYx6Y.js → tutorial-BOLFVhL8.js} +1 -1
- package/dist/assets/workspace-DxOEjrSc.js +17 -0
- package/dist/index.html +2 -2
- package/dist-cli/THIRD-PARTY-NOTICES.txt +2 -2
- package/dist-cli/chromosomix.mjs +5145 -1005
- package/package.json +6 -1
- package/dist/assets/analysis.worker-DZ9FXIW0.js +0 -8
- package/dist/assets/annotations.worker-Bybt4X5J.js +0 -27
- package/dist/assets/index-CJPGLvTr.css +0 -1
- package/dist/assets/index-DkMx8MnH.js +0 -103
- package/dist/assets/reader.worker-CuEqyC9X.js +0 -9
- package/dist/assets/workspace-tu5v3vK_.js +0 -17
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ chromosomix launches in about a second and reads your data where it lives, on yo
|
|
|
20
20
|
|
|
21
21
|
- Support for all genomic data types:
|
|
22
22
|
- Supports 1D genomic formats: bigWig, bigBed, BED, bedGraph, wig, GTF/GFF3, FASTA (+`.fai`), `.2bit`, BAM, VCF, barChart/bigBarChart, genePred/refFlat/bigGenePred, bedMethyl/bigMethyl, chrom.sizes, ...
|
|
23
|
-
- Supports 2D genomic formats: `.*cool`, `.hic`, HiC-Pro, `pairs(.gz)`, ..., at every resolution the file stores. Also supports interaction formats: BEDPE, and UCSC's `interact`/`bigInteract` and `longrange`/longTabix — drawn as arcs over the contact map and as their own lane in the 1D browser.
|
|
23
|
+
- Supports 2D genomic formats: `.*cool`, `.hic`, HiC-Pro, `pairs(.gz)`, ..., at every resolution the file stores. A `.pairs` is binned on import, and the same pass counts what `pairtools stats` counts — mapped, duplicates, cis and trans, pair types, strand convergence, library size — shown in the General panel and saved as a `.stats` file MultiQC reads. Also supports interaction formats: BEDPE, and UCSC's `interact`/`bigInteract` and `longrange`/longTabix — drawn as arcs over the contact map and as their own lane in the 1D browser.
|
|
24
24
|
- Supports 3D genomic formats: WashU's g3d (text or binary), `.3dg`, and `bed3d`, drawn in 3D and synchronized with the maps and tracks view. Every model also leaves as a PDB for PyMOL, ChimeraX and the other molecular viewers, a chain per chromosome and bonds along the fibre, and reads back here.
|
|
25
25
|
|
|
26
26
|
- Perform analyses directly in the browser:
|
|
@@ -46,7 +46,9 @@ chromosomix launches in about a second and reads your data where it lives, on yo
|
|
|
46
46
|
|
|
47
47
|
1. **No backend or install of any kind.** No tile server, no specific process on a host, no accounts. The viewer is static files; the data are objects on a bucket, or a file on your disk. This is what makes the app free to run and free to archive.
|
|
48
48
|
|
|
49
|
-
2. **Nothing is uploaded.** A local file is handed to an air-gapped worker by reference and read in place through the Emscripten filesystem. Nothing crosses the network, which is why there is no account system, no storage
|
|
49
|
+
2. **Nothing is uploaded.** A local file is handed to an air-gapped worker by reference and read in place through the Emscripten filesystem. Nothing of it crosses the network, which is why there is no account system, no storage and no hosting cost. You can open unpublished patient-derived genomic data on a laptop with the network cable out.
|
|
50
|
+
|
|
51
|
+
The website does send anonymous telemetry about how it is used — which kinds of file are opened, which analyses run, which errors are met — as words from fixed lists, with no cookie and nothing that identifies you: never a file name, an address or a place on the genome. **About → telemetry** turns it off, and a browser sending Global Privacy Control sends nothing. The desktop app, the static HTML file, the npm package and embeds never send any ([SPECS §31](SPECS.md#31-telemetry)).
|
|
50
52
|
|
|
51
53
|
3. **No pre-parsing of genomic files is required.** Contact maps and 1D genomic files are read in bounded reads, and the rest is pure canvas transforms with **zero** further I/O. That is the central difference from HiGlass, which needs a tile server, a tile cache and a tile compositor to stream contact maps.
|
|
52
54
|
|
|
@@ -96,6 +98,54 @@ A minimal version of the viewer can be added to any HTML page as an `<iframe>`.
|
|
|
96
98
|
|
|
97
99
|
Paste it into a page and it works (not rendered here though because GitHub does not allow `<iframe>`). The `src` URL is the same as the one you would use to open the viewer in a browser, with the addition of `?embed=1` to tell it to hide the header and tab strip.
|
|
98
100
|
|
|
101
|
+
The page holding the frame can drive it too: `postMessage` moves the view, adds tracks beside the map, and is told where the reader has taken it (SPECS §21.4).
|
|
102
|
+
|
|
103
|
+
## In a Jupyter notebook
|
|
104
|
+
|
|
105
|
+
The same viewer is a Jupyter widget: the app itself in a cell, driven from Python and telling Python where the reader has taken it. It works in JupyterLab, Notebook 7, VS Code, Colab and marimo.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install chromosomix # from a clone: pip install -e widgets/python
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from chromosomix import Chromosomix
|
|
113
|
+
|
|
114
|
+
view = Chromosomix(
|
|
115
|
+
'sample.mcool::/resolutions/5000', # a path on the kernel's disk, or any address the app opens
|
|
116
|
+
region='chr2:10000000-14000000',
|
|
117
|
+
tracks=['ATAC.bw', 'genes.gtf.gz'],
|
|
118
|
+
transform='log10',
|
|
119
|
+
)
|
|
120
|
+
view # the map, its tracks, its side panels
|
|
121
|
+
|
|
122
|
+
view.goto('chr2:11000000-12000000') # frame a region (or a locus, in the 1D browser)
|
|
123
|
+
view.navigate(transform='log2', oe=True) # any key the address bar carries
|
|
124
|
+
view.add('model.g3d.gz') # the header's +: a track, a map, a 3D model beside the map
|
|
125
|
+
view.observe(lambda change: print(change['new']['region']), names='state')
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Files on the kernel's disk are served to the viewer by byte range from a server on `127.0.0.1`, with their sidecars beside them; nothing is uploaded. [`widgets/python/demo.ipynb`](widgets/python/demo.ipynb) walks through it on the yeast demo, and [`widgets/python/README.md`](widgets/python/README.md) says which viewer build the frame holds and how to pin one.
|
|
129
|
+
|
|
130
|
+
## In R: R Markdown, Quarto, RStudio and Shiny
|
|
131
|
+
|
|
132
|
+
The same viewer is an htmlwidget, in the R package under [`widgets/r`](widgets/r/README.md):
|
|
133
|
+
|
|
134
|
+
```r
|
|
135
|
+
install.packages("widgets/r", repos = NULL, type = "source") # from a clone
|
|
136
|
+
|
|
137
|
+
library(chromosomix)
|
|
138
|
+
chromosomix("sample.mcool::/resolutions/5000", region = "chr2:10000000-14000000",
|
|
139
|
+
tracks = c("ATAC.bw", "genes.gtf.gz"), transform = "log10") |>
|
|
140
|
+
chromosomix_goto("chr2:11000000-12000000") |>
|
|
141
|
+
chromosomix_navigate(oe = TRUE) |>
|
|
142
|
+
chromosomix_add("model.g3d.gz")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
In Shiny, `chromosomixOutput()`, `renderChromosomix()` and `chromosomixProxy()` drive a view on screen,
|
|
146
|
+
and `input$<id>_state` follows the reader. `system.file("examples", package = "chromosomix")` holds an
|
|
147
|
+
R Markdown page of the yeast demo and a Shiny app driving it.
|
|
148
|
+
|
|
99
149
|
## Using the chromosomix analysis CLI
|
|
100
150
|
|
|
101
151
|
Over thirty analysis tasks can be executed programatically through the CLI, with the same parameters and producing the same results as the Analysis panel interactively. This can be used in a pipeline or a script.
|
|
@@ -173,10 +223,15 @@ chromosomix tasks
|
|
|
173
223
|
## --factor (default 1.1) log bins per step
|
|
174
224
|
## --minSeparation (default 2) minimum separation
|
|
175
225
|
## --derivative (default true) log-log slope
|
|
176
|
-
## --stranded (default false) four orientations
|
|
177
|
-
## --pairsUri .pairs file
|
|
178
226
|
## --no-balance read the raw counts (balanced by default where the file carries weights)
|
|
179
227
|
##
|
|
228
|
+
## pairsqc
|
|
229
|
+
## Pairs QC
|
|
230
|
+
## --pairs .pairs file
|
|
231
|
+
## --reference reference
|
|
232
|
+
## --enzyme (default "none") enzyme
|
|
233
|
+
## --sites (default "") sites
|
|
234
|
+
##
|
|
180
235
|
## scalogram
|
|
181
236
|
## Scalogram
|
|
182
237
|
## --quantiles (default "standard") quantiles
|
|
@@ -324,6 +379,7 @@ chromosomix tasks
|
|
|
324
379
|
## --region sequences
|
|
325
380
|
## --alpha (default 3) exponent α
|
|
326
381
|
## --weights (default "contacts") weight pairs by
|
|
382
|
+
## --seed (default 0) seed
|
|
327
383
|
## --iterations (default 150) refinement steps
|
|
328
384
|
## --no-balance read the raw counts (balanced by default where the file carries weights)
|
|
329
385
|
##
|
|
@@ -349,6 +405,13 @@ chromosomix tasks
|
|
|
349
405
|
## --mismatches (default 0) mismatches
|
|
350
406
|
## --limit (default 500000) most sites
|
|
351
407
|
##
|
|
408
|
+
## restriction
|
|
409
|
+
## Restriction sites
|
|
410
|
+
## --sequence reference
|
|
411
|
+
## --sequences sequences
|
|
412
|
+
## --enzyme (default "none") enzyme
|
|
413
|
+
## --sites (default "") sites
|
|
414
|
+
##
|
|
352
415
|
## peaks
|
|
353
416
|
## Peak calling
|
|
354
417
|
## --signal track
|
|
@@ -479,7 +542,14 @@ Each analysis is held to the package it reproduces — cooltools, coolpuppy, chr
|
|
|
479
542
|
bedtools and a dozen others — on public data, command for command, in a notebook rendered with every
|
|
480
543
|
release: <https://chromosomix.app/validation/>, with every number its figures draw in one table beside
|
|
481
544
|
it, <https://chromosomix.app/validation/validation.txt>; and how long each command takes beside its
|
|
482
|
-
reference, <https://chromosomix.app/timings/>.
|
|
545
|
+
reference, <https://chromosomix.app/timings/>. A third notebook holds the app to this command: ten
|
|
546
|
+
tasks run in the app, in a headless browser, then from the line its console wrote for each, and every
|
|
547
|
+
file the two wrote compared, <https://chromosomix.app/parity/>. And what the commands cost on a machine
|
|
548
|
+
like yours — the time and the peak memory of 23 analyses run genome-wide, up to a 1.08-billion-contact
|
|
549
|
+
human map — is <https://chromosomix.app/laptop/>, rendered on a laptop and committed as
|
|
550
|
+
`notebooks/laptop.html`; render `notebooks/laptop.qmd` with Quarto from a clone to measure your own: it
|
|
551
|
+
downloads the data, runs each analysis once and tabulates them. The app lists all four in
|
|
552
|
+
**About → Notebooks**, each with the one line of what it found.
|
|
483
553
|
|
|
484
554
|
Every task writes its results where `--out` points, each named after the file it came from up to
|
|
485
555
|
its first dot — `sample.coverage.coverage-total.tsv`. Two files of one command that would share a
|
|
@@ -492,11 +562,14 @@ value after it: `--stranded` turns it on, `--stranded=false` or `--no-stranded`
|
|
|
492
562
|
is none of its values is refused before any file is opened, with exit 2 and the values there are,
|
|
493
563
|
and so is a number that is not one.
|
|
494
564
|
|
|
495
|
-
Files are read from disk. A
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
565
|
+
Files are read from disk or from where they are published. A map named by an http or https address —
|
|
566
|
+
a cooler, an `.mcool` rung, a `.hic`, an `.scool` or a `.pairs`, as the positional or after
|
|
567
|
+
`--against` — and a bigWig, a `.2bit` or an indexed FASTA after `--tracks` or `--sequence` is read
|
|
568
|
+
**by range**, as the app reads it: one rung of a seven-gigabyte `.mcool` costs that rung, and nothing
|
|
569
|
+
is written to disk; a bedGraph or a wig after `--tracks` is read in one request. What is read whole
|
|
570
|
+
from a copy — a `--features` or `--pairs` set, a HiC-Pro matrix, a workspace — and any file whose host
|
|
571
|
+
will not serve a range is downloaded into `$TMPDIR` instead, with its progress on stderr, and removed
|
|
572
|
+
when the command ends.
|
|
500
573
|
|
|
501
574
|
```bash
|
|
502
575
|
chromosomix insulation *.mcool --sequences II --out results/
|
|
@@ -521,6 +594,13 @@ chromosomix apa sample.cool --features domains.bed --anchors features \
|
|
|
521
594
|
chromosomix motif sample.cool --sequence ref.fa.gz --pattern GGTCANNNTGACC --mismatches 1 --out results/
|
|
522
595
|
chromosomix overlap sample.cool --features peaks.bed --features boundaries.bed --a peaks.bed --b boundaries.bed
|
|
523
596
|
|
|
597
|
+
# A library's quality from its contact list: the pairtools counts as figures,
|
|
598
|
+
# the four orientations' P(s), and — with a reference and an enzyme — pair
|
|
599
|
+
# classes, distance to the cut, fragments seen and coverage against sites.
|
|
600
|
+
# Restriction sites alone digests the reference into a lane of cuts.
|
|
601
|
+
chromosomix pairsqc sample.pairs.gz --bin-size 10000 --sequence ref.fa.gz --enzyme DpnII --out qc/
|
|
602
|
+
chromosomix restriction sample.cool --sequence ref.fa.gz --enzyme Arima --out results/
|
|
603
|
+
|
|
524
604
|
# The tasks that read tracks take them as --tracks: a bigWig, a bedGraph, a
|
|
525
605
|
# wig, or a FASTA read as GC content. Repeat the flag or separate with commas.
|
|
526
606
|
chromosomix metaplot sample.cool --tracks ATAC.bw --features peaks.narrowPeak --flank 2000 --out results/
|
|
@@ -570,6 +650,10 @@ chromosomix insulation sample.metacool --value balanced_by_depth --circular pCEN
|
|
|
570
650
|
chromosomix coverage Hum53.pairs.gz --bin-size 5000 --rungs 4
|
|
571
651
|
|
|
572
652
|
chromosomix replay workspace.chromosomix.json --over data/*.hic
|
|
653
|
+
|
|
654
|
+
# What `pairtools stats` writes for a contact list, counted by the pass the
|
|
655
|
+
# app's import makes, in pairtools' own format — for MultiQC.
|
|
656
|
+
chromosomix stats sample.pairs.gz --out qc/
|
|
573
657
|
```
|
|
574
658
|
|
|
575
659
|
### Recovering the commands you ran
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Third-party notices for chromosomix 0.
|
|
1
|
+
Third-party notices for chromosomix 0.17.0
|
|
2
2
|
|
|
3
3
|
chromosomix itself is released under the GNU General Public License, version 3 or (at your option) any later version, reproduced below and in its repository, https://github.com/js2264/chromosomix.
|
|
4
4
|
The complete source of this build, and of every release, is at https://github.com/js2264/chromosomix.
|
|
@@ -32,7 +32,7 @@ Copied or ported into the source:
|
|
|
32
32
|
yapc 0.1 — GPL-3.0
|
|
33
33
|
|
|
34
34
|
========================================================================
|
|
35
|
-
chromosomix 0.
|
|
35
|
+
chromosomix 0.17.0 — GPL-3.0-or-later
|
|
36
36
|
https://github.com/js2264/chromosomix
|
|
37
37
|
========================================================================
|
|
38
38
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{e,f as b,b as g,i as f,n as O,r as R,w as P,a as A,s as I,g as T,c as G,d as M,m as F,h as H,j as _,D as $,k as B,l as N,o as j,p as K,M as z,q as U,t as W,u as X,v as Y,x as q,y as V,S as J,z as Q,A as Z}from"./index-DkMx8MnH.js";function ee(t){const s=new Set(t.map(i=>i.id));for(let i=t.length;;i++){const o=`b${i}-${Math.floor(Math.random()*1e6).toString(36)}`;if(!s.has(o))return o}}let te=0;function ne(t){const s=e("input",{class:"input",type:"text",placeholder:"optional"}),i=e("input",{class:"input",type:"text",placeholder:"https://bucket.s3.amazonaws.com/prefix/ or s3://bucket/prefix/"}),o=e("input",{class:"input",type:"text",placeholder:"eu-west-3"}),l=e("input",{class:"input",type:"text",placeholder:"AKIA…"}),a=e("input",{class:"input",type:"password",placeholder:"secret key"}),c=e("input",{class:"input",type:"password",placeholder:"session token (only for temporary credentials)"}),r=e("p",{class:"hint bucket-error"}),d=e("div",{class:"bucket-keys hidden",id:`bucket-keys-${++te}`},b("region",o,"The bucket's region. Part of the signature, so a wrong one is a 403."),b("access key ID",l),b("secret key",a),b("session token",c,"Only for STS or SSO credentials."));let n=!1;const p=g("additional settings",()=>h(!n),{class:"btn bucket-more"});p.setAttribute("aria-controls",d.id),p.prepend(f("chevron-down",{size:14}));function h(u){n=u,d.classList.toggle("hidden",!u),p.classList.toggle("is-on",u),p.setAttribute("aria-expanded",String(u)),p.replaceChildren(f(u?"chevron-up":"chevron-down",{size:14})),p.append("additional settings")}h(!1);const w=()=>o.value.trim()!==""||l.value.trim()!==""||a.value!=="",L=()=>{const u=O(i.value,{region:o.value});if(u===null){r.textContent="The prefix must be an https:// URL or an s3://bucket/prefix — a bucket reached over http:// would send the signature in clear, and a page served over https:// cannot read one at all.";return}const m=w();if(m&&(o.value.trim()===""||l.value.trim()===""||a.value==="")){r.textContent="Region, access key ID and secret key are all part of the signature.",h(!0);return}const k=R(),x=c.value.trim(),C={id:ee(k),label:s.value.trim()===""?u:s.value.trim(),base:u,...m?{region:o.value.trim(),accessKeyId:l.value.trim(),secretAccessKey:a.value,...x===""?{}:{sessionToken:x}}:{}};P([...k,C]),A(),r.textContent="";for(const E of[s,i,o,l,a,c])E.value="";t.onSaved(C)};return e("div",{class:"bucket-add"},t.heading===void 0?null:e("h3",{text:t.heading}),b("name",s),b("URL",i),e("div",{class:"bucket-more-row"},p),d,r,e("div",{class:"bucket-actions"},g("connect this bucket",L,{class:"btn btn-primary"})))}function oe(t){const s=e("input",{class:"input",type:"text",placeholder:"Run 2024"}),i=e("input",{class:"input",type:"text",placeholder:"https://drive.google.com/drive/folders/…"}),o=e("p",{class:"hint bucket-error"}),l=e("div",{class:"drive-controls"}),a=()=>{o.textContent="";const n=G(i.value);if(n===null){o.textContent="That is not a Drive folder link. Open the folder in Drive and copy the address bar — it looks like https://drive.google.com/drive/folders/<id> — or paste the id on its own. A link to a single file is not a folder; paste that into the file picker instead. Leave the box empty for all of My Drive.";return}const p=s.value.trim(),w=M({label:p!==""?p:n==="root"?"My Drive":n,folderId:n});s.value="",i.value="",A(),t(w.id)},c=e("div",{class:"drive-folder hidden"},l,o),r=()=>{if(!T().signedIn){l.replaceChildren(),o.textContent="",c.classList.add("hidden");return}c.classList.remove("hidden"),l.replaceChildren(e("p",{class:"hint",text:"A folder of your Drive, browsed in the Files panel:"}),b("name",s,"What this folder is called in the Files panel. Yours, not Drive’s"),b("folder link or id",i,"The address bar of the folder open in Drive, or its id. Empty means all of My Drive."),e("div",{class:"bucket-actions"},g("connect this folder",a,{class:"btn btn-primary"})))};r();const d=I(()=>{if(!c.isConnected){d();return}r()});return c}function S(t){return e("pre",{class:"connect-tip-code"},e("code",{text:t}))}let se=0;const ie=6,D=12,re=140;function v(t,s,i){const o=`connect-tip-${++se}`,l=e("div",{class:"connect-tip-box"},...i.map(n=>typeof n=="string"?e("p",{text:n}):n)),a=e("div",{class:"connect-tip",id:o,attrs:{role:"tooltip"}},l),c=e("button",{class:"connect-help",type:"button",text:"?",attrs:{"aria-label":`About ${t}`,"aria-describedby":o,"aria-expanded":"false"}}),r=e("span",{class:"connect-help-wrap"},c,a),d=()=>{const n=c.getBoundingClientRect(),p=document.documentElement.clientWidth;a.style.top=`${n.bottom}px`,a.style.right=`${Math.max(D,p-n.right)}px`,l.style.maxHeight=`${Math.max(re,window.innerHeight-n.bottom-ie-D)}px`};return c.addEventListener("pointerenter",d),c.addEventListener("focus",d),c.addEventListener("click",()=>{d();const n=r.classList.toggle("is-open");c.setAttribute("aria-expanded",String(n))}),c.addEventListener("blur",()=>{r.classList.remove("is-open"),c.setAttribute("aria-expanded","false")}),e("div",{class:"connect-head"},e("h3",{},f(s,{size:15}),e("span",{text:t})),r)}function y(t,s,i,o){return F(t,s,i,{class:"btn btn-mark btn-signin",disabled:o.disabled,...o.reason===void 0?{}:{reason:o.reason}})}function ae(t){const s=e("div",{class:"connect-signin"}),i=e("p",{class:"hint bucket-error"}),o=e("input",{class:"input",type:"text",placeholder:"the code Dropbox showed"}),l=e("a",{class:"hint",text:"the Dropbox page"});l.setAttribute("target","_blank"),l.setAttribute("rel","noopener noreferrer");const a=e("div",{class:"connect-code hidden"},e("p",{class:"hint",text:"Dropbox shows a code instead of sending you back here, because this page has no address to be sent back to. Copy the code into the box and press finish."}),e("p",{class:"hint"},e("span",{text:"If no tab opened, open "}),l,e("span",{text:"."})),b("code",o)),c=()=>{const n=_();if(n.signedIn&&n.expiresAt!==null){s.replaceChildren(e("span",{class:"hint",text:`Signed in until ${le(n.expiresAt)}.`}),g("browse my Dropbox",()=>t($),{class:"btn"}),g("sign out",()=>{B(),a.classList.add("hidden"),o.value=""},{class:"btn"}));return}const p=y(n.expiresAt!==null?"sign in again":"sign in to Dropbox","dropbox",()=>{i.textContent="",p.disabled=!0,N().then(({url:h})=>{l.setAttribute("href",h),window.open(h,"_blank","noopener,noreferrer"),a.classList.remove("hidden")}).catch(h=>{i.textContent=h instanceof Error?h.message:String(h)}).finally(()=>{c()})},{disabled:!n.configured,reason:"This site was built without a Dropbox app key, so there is no app to sign in to."});s.replaceChildren(p,...n.configured?[]:[e("p",{class:"hint",text:"Signing in to Dropbox is not set up on this site: it needs a Dropbox app key, which the site’s maintainer provides as DROPBOX_APP_KEY (docs/deploy.md)."})])};c();const r=()=>{i.textContent="",j(o.value).then(()=>{o.value="",a.classList.add("hidden")}).catch(n=>{i.textContent=n instanceof Error?n.message:String(n)}).finally(()=>{c()})};a.appendChild(e("div",{class:"bucket-actions"},g("finish",r,{class:"btn btn-primary"})));const d=H(()=>{if(!s.isConnected){d();return}c()});return e("div",{class:"connect-section"},v("Dropbox","dropbox",ce()),s,a,i)}function ce(){return["Sign in with your own Dropbox account to browse it in the Files panel.",`The app is given ${K.join(" and ")}: read access to everything in your Dropbox, or to the app’s own folder inside it if the site registered it that way.`,"The token lives only in this tab’s memory — never written to storage, gone when the tab closes, expiring on its own. A file already open keeps reading for the four hours its link lasts. No account information is asked for, so this page does not know whose Dropbox it is."]}function le(t){return new Date(t).toLocaleTimeString()}function ke(t){return new z({title:"Connect a folder",variant:"modal-wide",...t.onClose===void 0?{}:{onClose:t.onClose},body:s=>pe(t,s)})}function de(){const t={local:he,bucket:be,drive:fe,dropbox:s=>ae(s)??e("div")};return U().map(s=>({...s,pane:t[s.id]}))}function pe(t,s){const i=r=>{s(),t.onConnected(r)},o=de(),l=e("div",{class:"connect-pane"}),a=e("div",{class:"connect-providers",attrs:{role:"tablist"}}),c=r=>{const d=o.find(n=>n.id===r)??o[0];d.id;for(const n of a.children){const p=n.getAttribute("data-provider")===d.id;n.classList.toggle("is-on",p),n.setAttribute("aria-selected",String(p))}l.replaceChildren(d.pane(i))};for(const r of o){const d=e("button",{class:"connect-provider",type:"button",attrs:{role:"tab","data-provider":r.id,"aria-selected":"false"}},f(r.mark,{size:22}),e("span",{text:r.label}));d.addEventListener("click",()=>c(r.id)),a.appendChild(d)}return c(t.provider??"local"),e("div",{class:"modal-body bucket-body connect-body"},a,l,ye())}function he(t){const s=W(),i=X(),o=e("p",{class:"hint bucket-error"}),l=async()=>{o.textContent="";const a=await Z();a!==null&&t(a)};return e("div",{class:"connect-section"},v("This machine","hard-drive",ue(s,i)),e("div",{class:"connect-signin"},y(Y(),"hard-drive",()=>void l(),{disabled:!1})),o)}function ue(t,s){return["Read in place, never uploaded. The folder is listed one level at a time in the Files panel, and no byte of it is sent anywhere.",s?"The app keeps permission to list the folder across restarts, and the workspaces you save say which of your files are in it, so reopening one finds them there. Take it back any time under What this browser is remembering (Ctrl+P → Help).":t?"In a browser tab the folder is connected for this visit and kept nowhere, so nobody else at this browser finds it. Installed as an app — or in the desktop app — the folder is kept across restarts, and saved workspaces find their files in it again.":"This browser cannot keep that permission, so what you get is a snapshot of the file list for this visit only — it does not update and it does not survive a reload. Chrome, Edge and the desktop app keep the connection when installed as an app."]}function be(t){return e("div",{class:"connect-section"},v("A bucket","cloud",ge()),ne({onSaved:s=>t(Q(s.id))}))}function ge(){return["An https:// or s3:// prefix on S3, R2, MinIO, Google Cloud Storage or anything else that answers ListObjectsV2. Your browser reads it directly, with no server in between, so the bucket — not this page — decides what may be read. Two things have to be set.","CORS first, without which no read starts at all. Range and Content-Range are the pair that matters: a .mcool is read in pieces and never whole, so a bucket that will not expose them serves files this viewer cannot open. Narrow AllowedOrigins to the site once it works.",S(`[{
|
|
1
|
+
import{e,f as b,b as g,i as f,n as O,r as R,w as P,a as A,s as I,g as T,c as G,d as M,m as F,h as H,j as _,D as $,k as B,l as N,o as j,p as K,M as z,q as U,t as W,u as X,v as Y,x as q,y as V,S as J,z as Q,A as Z}from"./index-VoOi0yM1.js";function ee(t){const s=new Set(t.map(i=>i.id));for(let i=t.length;;i++){const o=`b${i}-${Math.floor(Math.random()*1e6).toString(36)}`;if(!s.has(o))return o}}let te=0;function ne(t){const s=e("input",{class:"input",type:"text",placeholder:"optional"}),i=e("input",{class:"input",type:"text",placeholder:"https://bucket.s3.amazonaws.com/prefix/ or s3://bucket/prefix/"}),o=e("input",{class:"input",type:"text",placeholder:"eu-west-3"}),l=e("input",{class:"input",type:"text",placeholder:"AKIA…"}),a=e("input",{class:"input",type:"password",placeholder:"secret key"}),c=e("input",{class:"input",type:"password",placeholder:"session token (only for temporary credentials)"}),r=e("p",{class:"hint bucket-error"}),d=e("div",{class:"bucket-keys hidden",id:`bucket-keys-${++te}`},b("region",o,"The bucket's region. Part of the signature, so a wrong one is a 403."),b("access key ID",l),b("secret key",a),b("session token",c,"Only for STS or SSO credentials."));let n=!1;const p=g("additional settings",()=>h(!n),{class:"btn bucket-more"});p.setAttribute("aria-controls",d.id),p.prepend(f("chevron-down",{size:14}));function h(u){n=u,d.classList.toggle("hidden",!u),p.classList.toggle("is-on",u),p.setAttribute("aria-expanded",String(u)),p.replaceChildren(f(u?"chevron-up":"chevron-down",{size:14})),p.append("additional settings")}h(!1);const w=()=>o.value.trim()!==""||l.value.trim()!==""||a.value!=="",L=()=>{const u=O(i.value,{region:o.value});if(u===null){r.textContent="The prefix must be an https:// URL or an s3://bucket/prefix — a bucket reached over http:// would send the signature in clear, and a page served over https:// cannot read one at all.";return}const m=w();if(m&&(o.value.trim()===""||l.value.trim()===""||a.value==="")){r.textContent="Region, access key ID and secret key are all part of the signature.",h(!0);return}const k=R(),x=c.value.trim(),C={id:ee(k),label:s.value.trim()===""?u:s.value.trim(),base:u,...m?{region:o.value.trim(),accessKeyId:l.value.trim(),secretAccessKey:a.value,...x===""?{}:{sessionToken:x}}:{}};P([...k,C]),A(),r.textContent="";for(const E of[s,i,o,l,a,c])E.value="";t.onSaved(C)};return e("div",{class:"bucket-add"},t.heading===void 0?null:e("h3",{text:t.heading}),b("name",s),b("URL",i),e("div",{class:"bucket-more-row"},p),d,r,e("div",{class:"bucket-actions"},g("connect this bucket",L,{class:"btn btn-primary"})))}function oe(t){const s=e("input",{class:"input",type:"text",placeholder:"Run 2024"}),i=e("input",{class:"input",type:"text",placeholder:"https://drive.google.com/drive/folders/…"}),o=e("p",{class:"hint bucket-error"}),l=e("div",{class:"drive-controls"}),a=()=>{o.textContent="";const n=G(i.value);if(n===null){o.textContent="That is not a Drive folder link. Open the folder in Drive and copy the address bar — it looks like https://drive.google.com/drive/folders/<id> — or paste the id on its own. A link to a single file is not a folder; paste that into the file picker instead. Leave the box empty for all of My Drive.";return}const p=s.value.trim(),w=M({label:p!==""?p:n==="root"?"My Drive":n,folderId:n});s.value="",i.value="",A(),t(w.id)},c=e("div",{class:"drive-folder hidden"},l,o),r=()=>{if(!T().signedIn){l.replaceChildren(),o.textContent="",c.classList.add("hidden");return}c.classList.remove("hidden"),l.replaceChildren(e("p",{class:"hint",text:"A folder of your Drive, browsed in the Files panel:"}),b("name",s,"What this folder is called in the Files panel. Yours, not Drive’s"),b("folder link or id",i,"The address bar of the folder open in Drive, or its id. Empty means all of My Drive."),e("div",{class:"bucket-actions"},g("connect this folder",a,{class:"btn btn-primary"})))};r();const d=I(()=>{if(!c.isConnected){d();return}r()});return c}function S(t){return e("pre",{class:"connect-tip-code"},e("code",{text:t}))}let se=0;const ie=6,D=12,re=140;function v(t,s,i){const o=`connect-tip-${++se}`,l=e("div",{class:"connect-tip-box"},...i.map(n=>typeof n=="string"?e("p",{text:n}):n)),a=e("div",{class:"connect-tip",id:o,attrs:{role:"tooltip"}},l),c=e("button",{class:"connect-help",type:"button",text:"?",attrs:{"aria-label":`About ${t}`,"aria-describedby":o,"aria-expanded":"false"}}),r=e("span",{class:"connect-help-wrap"},c,a),d=()=>{const n=c.getBoundingClientRect(),p=document.documentElement.clientWidth;a.style.top=`${n.bottom}px`,a.style.right=`${Math.max(D,p-n.right)}px`,l.style.maxHeight=`${Math.max(re,window.innerHeight-n.bottom-ie-D)}px`};return c.addEventListener("pointerenter",d),c.addEventListener("focus",d),c.addEventListener("click",()=>{d();const n=r.classList.toggle("is-open");c.setAttribute("aria-expanded",String(n))}),c.addEventListener("blur",()=>{r.classList.remove("is-open"),c.setAttribute("aria-expanded","false")}),e("div",{class:"connect-head"},e("h3",{},f(s,{size:15}),e("span",{text:t})),r)}function y(t,s,i,o){return F(t,s,i,{class:"btn btn-mark btn-signin",disabled:o.disabled,...o.reason===void 0?{}:{reason:o.reason}})}function ae(t){const s=e("div",{class:"connect-signin"}),i=e("p",{class:"hint bucket-error"}),o=e("input",{class:"input",type:"text",placeholder:"the code Dropbox showed"}),l=e("a",{class:"hint",text:"the Dropbox page"});l.setAttribute("target","_blank"),l.setAttribute("rel","noopener noreferrer");const a=e("div",{class:"connect-code hidden"},e("p",{class:"hint",text:"Dropbox shows a code instead of sending you back here, because this page has no address to be sent back to. Copy the code into the box and press finish."}),e("p",{class:"hint"},e("span",{text:"If no tab opened, open "}),l,e("span",{text:"."})),b("code",o)),c=()=>{const n=_();if(n.signedIn&&n.expiresAt!==null){s.replaceChildren(e("span",{class:"hint",text:`Signed in until ${le(n.expiresAt)}.`}),g("browse my Dropbox",()=>t($),{class:"btn"}),g("sign out",()=>{B(),a.classList.add("hidden"),o.value=""},{class:"btn"}));return}const p=y(n.expiresAt!==null?"sign in again":"sign in to Dropbox","dropbox",()=>{i.textContent="",p.disabled=!0,N().then(({url:h})=>{l.setAttribute("href",h),window.open(h,"_blank","noopener,noreferrer"),a.classList.remove("hidden")}).catch(h=>{i.textContent=h instanceof Error?h.message:String(h)}).finally(()=>{c()})},{disabled:!n.configured,reason:"This site was built without a Dropbox app key, so there is no app to sign in to."});s.replaceChildren(p,...n.configured?[]:[e("p",{class:"hint",text:"Signing in to Dropbox is not set up on this site: it needs a Dropbox app key, which the site’s maintainer provides as DROPBOX_APP_KEY (docs/deploy.md)."})])};c();const r=()=>{i.textContent="",j(o.value).then(()=>{o.value="",a.classList.add("hidden")}).catch(n=>{i.textContent=n instanceof Error?n.message:String(n)}).finally(()=>{c()})};a.appendChild(e("div",{class:"bucket-actions"},g("finish",r,{class:"btn btn-primary"})));const d=H(()=>{if(!s.isConnected){d();return}c()});return e("div",{class:"connect-section"},v("Dropbox","dropbox",ce()),s,a,i)}function ce(){return["Sign in with your own Dropbox account to browse it in the Files panel.",`The app is given ${K.join(" and ")}: read access to everything in your Dropbox, or to the app’s own folder inside it if the site registered it that way.`,"The token lives only in this tab’s memory — never written to storage, gone when the tab closes, expiring on its own. A file already open keeps reading for the four hours its link lasts. No account information is asked for, so this page does not know whose Dropbox it is."]}function le(t){return new Date(t).toLocaleTimeString()}function ke(t){return new z({title:"Connect a folder",variant:"modal-wide",...t.onClose===void 0?{}:{onClose:t.onClose},body:s=>pe(t,s)})}function de(){const t={local:he,bucket:be,drive:fe,dropbox:s=>ae(s)??e("div")};return U().map(s=>({...s,pane:t[s.id]}))}function pe(t,s){const i=r=>{s(),t.onConnected(r)},o=de(),l=e("div",{class:"connect-pane"}),a=e("div",{class:"connect-providers",attrs:{role:"tablist"}}),c=r=>{const d=o.find(n=>n.id===r)??o[0];d.id;for(const n of a.children){const p=n.getAttribute("data-provider")===d.id;n.classList.toggle("is-on",p),n.setAttribute("aria-selected",String(p))}l.replaceChildren(d.pane(i))};for(const r of o){const d=e("button",{class:"connect-provider",type:"button",attrs:{role:"tab","data-provider":r.id,"aria-selected":"false"}},f(r.mark,{size:22}),e("span",{text:r.label}));d.addEventListener("click",()=>c(r.id)),a.appendChild(d)}return c(t.provider??"local"),e("div",{class:"modal-body bucket-body connect-body"},a,l,ye())}function he(t){const s=W(),i=X(),o=e("p",{class:"hint bucket-error"}),l=async()=>{o.textContent="";const a=await Z();a!==null&&t(a)};return e("div",{class:"connect-section"},v("This machine","hard-drive",ue(s,i)),e("div",{class:"connect-signin"},y(Y(),"hard-drive",()=>void l(),{disabled:!1})),o)}function ue(t,s){return["Read in place, never uploaded. The folder is listed one level at a time in the Files panel, and no byte of it is sent anywhere.",s?"The app keeps permission to list the folder across restarts, and the workspaces you save say which of your files are in it, so reopening one finds them there. Take it back any time under What this browser is remembering (Ctrl+P → Help).":t?"In a browser tab the folder is connected for this visit and kept nowhere, so nobody else at this browser finds it. Installed as an app — or in the desktop app — the folder is kept across restarts, and saved workspaces find their files in it again.":"This browser cannot keep that permission, so what you get is a snapshot of the file list for this visit only — it does not update and it does not survive a reload. Chrome, Edge and the desktop app keep the connection when installed as an app."]}function be(t){return e("div",{class:"connect-section"},v("A bucket","cloud",ge()),ne({onSaved:s=>t(Q(s.id))}))}function ge(){return["An https:// or s3:// prefix on S3, R2, MinIO, Google Cloud Storage or anything else that answers ListObjectsV2. Your browser reads it directly, with no server in between, so the bucket — not this page — decides what may be read. Two things have to be set.","CORS first, without which no read starts at all. Range and Content-Range are the pair that matters: a .mcool is read in pieces and never whole, so a bucket that will not expose them serves files this viewer cannot open. Narrow AllowedOrigins to the site once it works.",S(`[{
|
|
2
2
|
"AllowedOrigins": ["*"],
|
|
3
3
|
"AllowedMethods": ["GET", "HEAD"],
|
|
4
4
|
"AllowedHeaders": ["Range", "Content-Type"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{e as l,b as v,B as A,i as f,C as O}from"./index-DkMx8MnH.js";const W=700,P=1500,_=500,z=150,y=6,c=14,d=8,$=18,H="Not on screen yet. If it does not appear, go back a step.",q={press:{icon:"mouse-pointer-click",word:"press"},type:{icon:"keyboard",word:"type"},choose:{icon:"chevrons-up-down",word:"choose"},drag:{icon:"move",word:"drag"},look:{icon:"eye",word:"look"}},R='input, textarea, select, canvas, [contenteditable], [role="slider"], [role="listbox"], [role="menu"], [role="grid"], [role="tablist"]';class Q{el;steps;options;veil=l("div",{class:"tour-veil"});ring=l("div",{class:"tour-ring hidden"});blocks=[0,1,2,3].map(()=>l("div",{class:"tour-block hidden"}));card;tail=l("span",{class:"tour-tail",attrs:{"aria-hidden":"true"}});chapterEl=l("span",{class:"tour-chapter"});countEl=l("span",{class:"tour-count"});segments;chipEl=l("span",{class:"tour-chip",attrs:{"aria-hidden":"true"}});badgeEl=l("span",{class:"tour-badge hidden",attrs:{"aria-hidden":"true"}});titleEl=l("h2",{class:"tour-title",id:"tour-title"});textEl=l("p",{class:"tour-text",id:"tour-text"});noteEl=l("p",{class:"tour-note hidden"});agendaEl=l("ol",{class:"tour-agenda hidden"});assistSlot=l("span",{class:"tour-assist"});backButton;nextButton;nextLabel=l("span");index=0;entry=N();target=null;spot=null;clips=[];painted="";chip=null;placed=!1;timer=null;frame=null;soonTimer=null;nudgeTimer=null;running=!1;constructor(t){this.options=t,this.steps=t.steps,this.backButton=v("back",()=>this.previous(),{class:"btn btn-quiet tour-back",title:A("The step before this one","←")}),this.nextButton=l("button",{class:"btn btn-primary tour-next",type:"button"},this.nextLabel,f("arrow-right",{size:14,class:"tour-next-arrow"})),this.nextButton.addEventListener("click",()=>this.next());const i=v("skip tutorial",()=>this.end("skipped"),{class:"btn btn-quiet tour-skip",title:"Leave the tutorial. About picks it up where you left it."});this.segments=B(this.steps).map(e=>{const n=l("span",{class:"tour-segment-fill"}),r=l("span",{class:"tour-segment",attrs:{title:e.chapter}},n);return r.style.flexGrow=String(e.count),{from:e.from,count:e.count,fill:n,el:r}}),this.card=l("div",{class:"tour-card",attrs:{role:"dialog","aria-modal":"false","aria-labelledby":"tour-title","aria-describedby":"tour-text"}},this.tail,l("div",{class:"tour-head"},this.chapterEl,this.countEl,i),l("div",{class:"tour-track",attrs:{"aria-hidden":"true"}},...this.segments.map(e=>e.el)),l("div",{class:"tour-body"},this.badgeEl,l("div",{class:"tour-heading"},this.chipEl,this.titleEl),l("div",{class:"tour-live",attrs:{"aria-live":"polite"}},this.textEl,this.noteEl),this.agendaEl),l("div",{class:"tour-actions"},this.assistSlot,this.backButton,this.nextButton));for(const e of this.blocks){for(const n of["pointerdown","mousedown","click","dblclick","contextmenu","touchstart"])e.addEventListener(n,r=>{r.preventDefault(),r.stopPropagation(),n==="pointerdown"&&this.nudge()});e.addEventListener("wheel",n=>n.stopPropagation(),{passive:!0})}this.el=l("div",{class:"tour"},this.veil,this.ring,...this.blocks,this.card)}get at(){return this.index}start(){if(!this.running){this.running=!0,document.body.appendChild(this.el);for(const t of["click","input","change","keyup","pointerup"])window.addEventListener(t,this.soon,!0);window.addEventListener("keydown",this.onKey),this.timer=setInterval(()=>this.tick(),z),this.loop(),this.go(this.options.start,!1)}}stop(){if(this.running){this.running=!1;for(const t of["click","input","change","keyup","pointerup"])window.removeEventListener(t,this.soon,!0);window.removeEventListener("keydown",this.onKey),this.timer!==null&&clearInterval(this.timer),this.frame!==null&&cancelAnimationFrame(this.frame),this.soonTimer!==null&&clearTimeout(this.soonTimer),this.nudgeTimer!==null&&clearTimeout(this.nudgeTimer),this.timer=null,this.frame=null,this.el.remove()}}go(t,i){let e=Math.max(0,Math.min(this.steps.length-1,t));if(!i)for(;e<this.steps.length-1&&this.isDone(this.steps[e]);)e++;this.index=e;const n=this.steps[e];this.entry=N(),this.entry.was=this.isDone(n),this.target=null,this.spot=null,this.clips=[],this.painted="",n.prepare?.(),this.paintCard(),this.card.classList.remove("is-entering"),this.card.offsetWidth,this.card.classList.add("is-entering"),n.hero===!0&&this.nextButton.focus({preventScroll:!0}),this.options.onStep(n,e),this.tick()}next(){if(this.index>=this.steps.length-1){this.end("finished");return}this.go(this.index+1,!1)}previous(){this.index>0&&this.go(this.index-1,!0)}end(t){const i=this.index;this.stop(),this.options.onEnd(t,i)}isDone(t){try{return t.done?.()===!0}catch{return!1}}waiting(){return this.steps[this.index].done!==void 0&&!this.entry.was&&this.index<this.steps.length-1}onKey=t=>{if(t.defaultPrevented||t.altKey||t.ctrlKey||t.metaKey||t.shiftKey||t.key!=="ArrowRight"&&t.key!=="ArrowLeft"||this.options.yielding?.()===!0)return;const i=t.target;i instanceof Element&&i.closest(R)!==null||(t.key==="ArrowLeft"?(t.preventDefault(),this.previous()):this.waiting()||(t.preventDefault(),this.next()))};soon=()=>{this.soonTimer===null&&(this.soonTimer=setTimeout(()=>{this.soonTimer=null,this.tick()},0))};tick(){if(!this.running)return;const t=this.options.yielding?.()===!0;if(this.el.classList.toggle("is-yielding",t),t)return;const i=this.steps[this.index],e=performance.now(),n=this.isDone(i);if(i.needs!==void 0&&!n&&!L(()=>i.needs?.holds()===!0)){if(this.entry.failingSince??=e,e-this.entry.failingSince>=_){const o=this.steps.findIndex(h=>h.id===i.needs?.back);if(o>=0&&o!==this.index){this.go(o,!1);return}}}else this.entry.failingSince=null;if(i.done!==void 0){n&&!this.entry.was&&(this.entry.doneAt=e),n||(this.entry.doneAt=null),this.entry.was=n;const o=this.index===this.steps.length-1;if(n&&!o&&this.entry.doneAt!==null&&e-this.entry.doneAt>=W){this.next();return}}const r=i.mistake===void 0?null:L(()=>i.mistake?.()??null);r!==this.entry.mistake&&(this.entry.mistake=r,this.blink(r!==null)),this.find(i,e),this.paintCard()}find(t,i){const e=t.target===void 0?null:L(()=>t.target?.()??null),n=e===null?null:I(e)?e.within:e;if(this.spot=e!==null&&I(e)?e.box:null,n!==this.target&&(this.target=n,this.clips=n===null?[]:Y(n)),n!==null){if(!this.entry.scrolled){this.entry.scrolled=!0;const r=this.lit(),o=M(n.getBoundingClientRect()),h=this.spot===null?o:w(this.spot,o);if(h!==null&&(r===null||K(r)<K(h)-1)){const a=h.bottom-h.top>window.innerHeight*.6;n.scrollIntoView({block:a?"start":"center",inline:"nearest"})}}t.focus===!0&&!this.entry.focused&&n instanceof HTMLElement&&(this.entry.focused=!0,n.focus({preventScroll:!0})),this.lit()!==null&&(this.entry.seenAt=i)}}lit(){const t=F(this.target,this.clips);return t===null||this.spot===null?t:w(t,this.spot)}paintCard(){const t=this.steps[this.index],i=this.index===this.steps.length-1,e=t.hero===!0,n=t.done!==void 0&&this.entry.was;g(this.chapterEl,t.chapter),g(this.countEl,`step ${this.index+1} of ${this.steps.length}`);for(const a of this.segments){const b=`${Math.max(0,Math.min(1,(this.index-a.from+1)/a.count))*100}%`;a.fill.style.width!==b&&(a.fill.style.width=b)}g(this.titleEl,t.title),this.paintText(t.text),this.card.dataset.step!==t.id&&(this.card.dataset.step=t.id),this.card.classList.toggle("is-hero",e),this.el.classList.toggle("is-done",n),this.el.classList.toggle("is-waiting",!e&&this.waiting()),this.paintChip(e?"":n?"done":t.action??(t.done===void 0?"look":"press")),this.badgeEl.classList.toggle("hidden",!(e&&i)),e&&i&&this.badgeEl.childElementCount===0&&this.badgeEl.append(f("check",{size:26})),this.paintAgenda(t);const r=t.target!==void 0&&t.done!==void 0&&!this.entry.was&&this.lit()===null&&performance.now()-this.entry.seenAt>=P;if(g(this.noteEl,r?t.missing??H:""),this.noteEl.classList.toggle("hidden",!r),this.assistSlot.dataset.step!==t.id){this.assistSlot.dataset.step=t.id;const a=t.assist;this.assistSlot.replaceChildren(...a===void 0?[]:[v(a.label,()=>a.run(),{class:"btn btn-quiet",title:a.title})])}this.backButton.disabled=this.index===0,this.backButton.classList.toggle("hidden",this.index===0);const o=this.waiting();g(this.nextLabel,t.next??(i?"finish":o?"skip step":"next"));const h=o?"btn btn-quiet tour-next":"btn btn-primary tour-next";this.nextButton.className!==h&&(this.nextButton.className=h),this.nextButton.classList.toggle("is-last",i),this.nextButton.title=o?"Go on without doing this step. The steps after it may need it.":i?"Close the tutorial":A("The next step","→")}paintChip(t){if(t===this.chip)return;if(this.chip=t,this.chipEl.classList.toggle("hidden",t===""),this.chipEl.classList.toggle("is-done",t==="done"),t===""){this.chipEl.replaceChildren();return}const i=t==="done"?null:q[t];this.chipEl.replaceChildren(f(i===null?"check":i.icon,{size:14})),this.chipEl.title=i===null?"done":i.word}paintAgenda(t){const i=t.agenda===!0;if(this.agendaEl.classList.toggle("hidden",!i),!i||this.agendaEl.dataset.step===t.id)return;this.agendaEl.dataset.step=t.id;const e=B(this.steps).filter(n=>this.steps.slice(n.from,n.from+n.count).some(r=>r.hero!==!0));this.agendaEl.replaceChildren(...e.map(n=>l("li",{text:n.chapter})))}paintText(t){const i=typeof t=="string"?t:JSON.stringify(t);this.textEl.dataset.text!==i&&(this.textEl.dataset.text=i,this.textEl.replaceChildren(...(typeof t=="string"?[t]:t).flatMap(e=>{if(typeof e=="string")return[document.createTextNode(e)];const n=l("code",{class:"tour-name",text:e.name});return e.copy===!0?[n,G(e.name,n)]:[n]})))}blink(t){for(const i of this.textEl.querySelectorAll(".tour-name"))i.classList.toggle("is-wrong",t),i.classList.remove("is-blinking"),t&&(i.offsetWidth,i.classList.add("is-blinking"))}loop(){this.frame=requestAnimationFrame(()=>{this.running&&(this.place(),this.loop())})}place(){const i=this.steps[this.index].hero===!0,e=i?null:this.lit(),n=window.innerWidth,r=window.innerHeight,o=e===null?null:{left:Math.max(0,e.left-y),top:Math.max(0,e.top-y),right:Math.min(n,e.right+y),bottom:Math.min(r,e.bottom+y)},h=this.card.offsetWidth,a=this.card.offsetHeight,x=(i?"hero:":"")+(o===null?`none:${n}:${r}:${h}:${a}`:`${Math.round(o.left)}:${Math.round(o.top)}:${Math.round(o.right)}:${Math.round(o.bottom)}:${n}:${r}:${h}:${a}`);if(x===this.painted)return;this.painted=x;const b=o!==null&&this.ring.classList.contains("hidden");this.veil.classList.toggle("hidden",o!==null),this.ring.classList.toggle("hidden",o===null);for(const p of this.blocks)p.classList.toggle("hidden",o===null);if(o!==null){D(this.ring,b,()=>m(this.ring,o));const[p,k,C,E]=this.blocks;m(p,{left:0,top:0,right:n,bottom:o.top}),m(k,{left:o.right,top:o.top,right:n,bottom:o.bottom}),m(C,{left:0,top:o.bottom,right:n,bottom:r}),m(E,{left:0,top:o.top,right:o.left,bottom:o.bottom})}const T={width:h,height:a},S={width:n,height:r},u=i?V(T,S):J(T,o,S);if(D(this.card,!this.placed,()=>{this.card.style.left=`${Math.round(u.left)}px`,this.card.style.top=`${Math.round(u.top)}px`}),this.placed=!0,this.card.dataset.side!==u.side&&(this.card.dataset.side=u.side),o!==null){const p=u.side==="below"||u.side==="above",k=p?(o.left+o.right)/2-u.left:(o.top+o.bottom)/2-u.top,E=Math.max($,Math.min((p?h:a)-$,k));this.card.style.setProperty("--tail-at",`${Math.round(E)}px`)}}nudge(){this.ring.classList.remove("is-nudged"),this.ring.offsetWidth,this.ring.classList.add("is-nudged"),this.nudgeTimer!==null&&clearTimeout(this.nudgeTimer),this.nudgeTimer=setTimeout(()=>this.ring.classList.remove("is-nudged"),700)}}function U(s,t){let i=s.findIndex(e=>e.id===t);if(i<0)return 0;for(;i>0&&s[i-1].chapter===s[i].chapter;)i--;return i}function B(s){const t=[];return s.forEach((i,e)=>{const n=t[t.length-1];n!==void 0&&n.chapter===i.chapter?n.count++:t.push({chapter:i.chapter,from:e,count:1})}),t}function N(){return{was:!1,doneAt:null,seenAt:performance.now(),failingSince:null,scrolled:!1,focused:!1,mistake:null}}function I(s){return!(s instanceof Element)}function g(s,t){s.textContent!==t&&(s.textContent=t)}function G(s,t){const i=`Copy ${s}`,e=l("button",{class:"tour-copy",type:"button",title:i,attrs:{"aria-label":i}},f("copy",{size:13}));let n=null;return e.addEventListener("click",()=>{O(s).then(()=>{e.classList.add("is-copied"),e.replaceChildren(f("check",{size:13})),e.title="Copied",n!==null&&clearTimeout(n),n=setTimeout(()=>{e.classList.remove("is-copied"),e.replaceChildren(f("copy",{size:13})),e.title=i},1400)},()=>{window.getSelection()?.selectAllChildren(t),e.title="Selected: copy it with the keyboard"})}),e}function L(s){try{return s()}catch{return null}}function D(s,t,i){if(!t){i();return}s.classList.add("is-snapping"),i(),s.offsetWidth,s.classList.remove("is-snapping")}function M(s){return{left:s.left,top:s.top,right:s.right,bottom:s.bottom}}function K(s){return Math.max(0,s.right-s.left)*Math.max(0,s.bottom-s.top)}function m(s,t){s.style.left=`${t.left}px`,s.style.top=`${t.top}px`,s.style.width=`${Math.max(0,t.right-t.left)}px`,s.style.height=`${Math.max(0,t.bottom-t.top)}px`}function Y(s){const t=[];for(let i=s.parentElement;i!==null&&i!==document.body;i=i.parentElement){const e=window.getComputedStyle(i);(e.overflowX!=="visible"||e.overflowY!=="visible")&&t.push(i)}return t}function X(s,t){if(!s.isConnected)return null;const i=s.getBoundingClientRect();if(i.width<=0||i.height<=0||s instanceof HTMLElement&&window.getComputedStyle(s).visibility==="hidden")return null;let e=M(i);for(const n of t)if(e=w(e,M(n.getBoundingClientRect())),e===null)return null;return w(e,{left:0,top:0,right:window.innerWidth,bottom:window.innerHeight})}function F(s,t){return s===null?null:X(s,t)}function w(s,t){const i={left:Math.max(s.left,t.left),top:Math.max(s.top,t.top),right:Math.min(s.right,t.right),bottom:Math.min(s.bottom,t.bottom)};return i.right-i.left<1||i.bottom-i.top<1?null:i}function J(s,t,i){const e=h=>Math.max(d,Math.min(i.width-s.width-d,h)),n=h=>Math.max(d,Math.min(i.height-s.height-d,h));if(t===null)return{left:e((i.width-s.width)/2),top:n(i.height-s.height-3*c),side:"none"};const r=(t.left+t.right)/2-s.width/2,o=(t.top+t.bottom)/2-s.height/2;return t.bottom+c+s.height<=i.height-d?{left:e(r),top:t.bottom+c,side:"below"}:t.top-c-s.height>=d?{left:e(r),top:t.top-c-s.height,side:"above"}:t.right+c+s.width<=i.width-d?{left:t.right+c,top:n(o),side:"right"}:t.left-c-s.width>=d?{left:t.left-c-s.width,top:n(o),side:"left"}:{left:e(i.width-s.width-2*c),top:n(i.height-s.height-2*c),side:"over"}}function V(s,t){return{left:Math.max(d,(t.width-s.width)/2),top:Math.max(d,(t.height-s.height)/2-t.height*.06),side:"none"}}export{Q as Tour,J as cardPlace,Y as clipsOf,V as heroPlace,B as partsOf,U as resumeIndex,X as shownBox};
|
|
1
|
+
import{e as l,b as v,B as A,i as f,C as O}from"./index-VoOi0yM1.js";const W=700,P=1500,_=500,z=150,y=6,c=14,d=8,$=18,H="Not on screen yet. If it does not appear, go back a step.",q={press:{icon:"mouse-pointer-click",word:"press"},type:{icon:"keyboard",word:"type"},choose:{icon:"chevrons-up-down",word:"choose"},drag:{icon:"move",word:"drag"},look:{icon:"eye",word:"look"}},R='input, textarea, select, canvas, [contenteditable], [role="slider"], [role="listbox"], [role="menu"], [role="grid"], [role="tablist"]';class Q{el;steps;options;veil=l("div",{class:"tour-veil"});ring=l("div",{class:"tour-ring hidden"});blocks=[0,1,2,3].map(()=>l("div",{class:"tour-block hidden"}));card;tail=l("span",{class:"tour-tail",attrs:{"aria-hidden":"true"}});chapterEl=l("span",{class:"tour-chapter"});countEl=l("span",{class:"tour-count"});segments;chipEl=l("span",{class:"tour-chip",attrs:{"aria-hidden":"true"}});badgeEl=l("span",{class:"tour-badge hidden",attrs:{"aria-hidden":"true"}});titleEl=l("h2",{class:"tour-title",id:"tour-title"});textEl=l("p",{class:"tour-text",id:"tour-text"});noteEl=l("p",{class:"tour-note hidden"});agendaEl=l("ol",{class:"tour-agenda hidden"});assistSlot=l("span",{class:"tour-assist"});backButton;nextButton;nextLabel=l("span");index=0;entry=N();target=null;spot=null;clips=[];painted="";chip=null;placed=!1;timer=null;frame=null;soonTimer=null;nudgeTimer=null;running=!1;constructor(t){this.options=t,this.steps=t.steps,this.backButton=v("back",()=>this.previous(),{class:"btn btn-quiet tour-back",title:A("The step before this one","←")}),this.nextButton=l("button",{class:"btn btn-primary tour-next",type:"button"},this.nextLabel,f("arrow-right",{size:14,class:"tour-next-arrow"})),this.nextButton.addEventListener("click",()=>this.next());const i=v("skip tutorial",()=>this.end("skipped"),{class:"btn btn-quiet tour-skip",title:"Leave the tutorial. About picks it up where you left it."});this.segments=B(this.steps).map(e=>{const n=l("span",{class:"tour-segment-fill"}),r=l("span",{class:"tour-segment",attrs:{title:e.chapter}},n);return r.style.flexGrow=String(e.count),{from:e.from,count:e.count,fill:n,el:r}}),this.card=l("div",{class:"tour-card",attrs:{role:"dialog","aria-modal":"false","aria-labelledby":"tour-title","aria-describedby":"tour-text"}},this.tail,l("div",{class:"tour-head"},this.chapterEl,this.countEl,i),l("div",{class:"tour-track",attrs:{"aria-hidden":"true"}},...this.segments.map(e=>e.el)),l("div",{class:"tour-body"},this.badgeEl,l("div",{class:"tour-heading"},this.chipEl,this.titleEl),l("div",{class:"tour-live",attrs:{"aria-live":"polite"}},this.textEl,this.noteEl),this.agendaEl),l("div",{class:"tour-actions"},this.assistSlot,this.backButton,this.nextButton));for(const e of this.blocks){for(const n of["pointerdown","mousedown","click","dblclick","contextmenu","touchstart"])e.addEventListener(n,r=>{r.preventDefault(),r.stopPropagation(),n==="pointerdown"&&this.nudge()});e.addEventListener("wheel",n=>n.stopPropagation(),{passive:!0})}this.el=l("div",{class:"tour"},this.veil,this.ring,...this.blocks,this.card)}get at(){return this.index}start(){if(!this.running){this.running=!0,document.body.appendChild(this.el);for(const t of["click","input","change","keyup","pointerup"])window.addEventListener(t,this.soon,!0);window.addEventListener("keydown",this.onKey),this.timer=setInterval(()=>this.tick(),z),this.loop(),this.go(this.options.start,!1)}}stop(){if(this.running){this.running=!1;for(const t of["click","input","change","keyup","pointerup"])window.removeEventListener(t,this.soon,!0);window.removeEventListener("keydown",this.onKey),this.timer!==null&&clearInterval(this.timer),this.frame!==null&&cancelAnimationFrame(this.frame),this.soonTimer!==null&&clearTimeout(this.soonTimer),this.nudgeTimer!==null&&clearTimeout(this.nudgeTimer),this.timer=null,this.frame=null,this.el.remove()}}go(t,i){let e=Math.max(0,Math.min(this.steps.length-1,t));if(!i)for(;e<this.steps.length-1&&this.isDone(this.steps[e]);)e++;this.index=e;const n=this.steps[e];this.entry=N(),this.entry.was=this.isDone(n),this.target=null,this.spot=null,this.clips=[],this.painted="",n.prepare?.(),this.paintCard(),this.card.classList.remove("is-entering"),this.card.offsetWidth,this.card.classList.add("is-entering"),n.hero===!0&&this.nextButton.focus({preventScroll:!0}),this.options.onStep(n,e),this.tick()}next(){if(this.index>=this.steps.length-1){this.end("finished");return}this.go(this.index+1,!1)}previous(){this.index>0&&this.go(this.index-1,!0)}end(t){const i=this.index;this.stop(),this.options.onEnd(t,i)}isDone(t){try{return t.done?.()===!0}catch{return!1}}waiting(){return this.steps[this.index].done!==void 0&&!this.entry.was&&this.index<this.steps.length-1}onKey=t=>{if(t.defaultPrevented||t.altKey||t.ctrlKey||t.metaKey||t.shiftKey||t.key!=="ArrowRight"&&t.key!=="ArrowLeft"||this.options.yielding?.()===!0)return;const i=t.target;i instanceof Element&&i.closest(R)!==null||(t.key==="ArrowLeft"?(t.preventDefault(),this.previous()):this.waiting()||(t.preventDefault(),this.next()))};soon=()=>{this.soonTimer===null&&(this.soonTimer=setTimeout(()=>{this.soonTimer=null,this.tick()},0))};tick(){if(!this.running)return;const t=this.options.yielding?.()===!0;if(this.el.classList.toggle("is-yielding",t),t)return;const i=this.steps[this.index],e=performance.now(),n=this.isDone(i);if(i.needs!==void 0&&!n&&!L(()=>i.needs?.holds()===!0)){if(this.entry.failingSince??=e,e-this.entry.failingSince>=_){const o=this.steps.findIndex(h=>h.id===i.needs?.back);if(o>=0&&o!==this.index){this.go(o,!1);return}}}else this.entry.failingSince=null;if(i.done!==void 0){n&&!this.entry.was&&(this.entry.doneAt=e),n||(this.entry.doneAt=null),this.entry.was=n;const o=this.index===this.steps.length-1;if(n&&!o&&this.entry.doneAt!==null&&e-this.entry.doneAt>=W){this.next();return}}const r=i.mistake===void 0?null:L(()=>i.mistake?.()??null);r!==this.entry.mistake&&(this.entry.mistake=r,this.blink(r!==null)),this.find(i,e),this.paintCard()}find(t,i){const e=t.target===void 0?null:L(()=>t.target?.()??null),n=e===null?null:I(e)?e.within:e;if(this.spot=e!==null&&I(e)?e.box:null,n!==this.target&&(this.target=n,this.clips=n===null?[]:Y(n)),n!==null){if(!this.entry.scrolled){this.entry.scrolled=!0;const r=this.lit(),o=M(n.getBoundingClientRect()),h=this.spot===null?o:w(this.spot,o);if(h!==null&&(r===null||K(r)<K(h)-1)){const a=h.bottom-h.top>window.innerHeight*.6;n.scrollIntoView({block:a?"start":"center",inline:"nearest"})}}t.focus===!0&&!this.entry.focused&&n instanceof HTMLElement&&(this.entry.focused=!0,n.focus({preventScroll:!0})),this.lit()!==null&&(this.entry.seenAt=i)}}lit(){const t=F(this.target,this.clips);return t===null||this.spot===null?t:w(t,this.spot)}paintCard(){const t=this.steps[this.index],i=this.index===this.steps.length-1,e=t.hero===!0,n=t.done!==void 0&&this.entry.was;g(this.chapterEl,t.chapter),g(this.countEl,`step ${this.index+1} of ${this.steps.length}`);for(const a of this.segments){const b=`${Math.max(0,Math.min(1,(this.index-a.from+1)/a.count))*100}%`;a.fill.style.width!==b&&(a.fill.style.width=b)}g(this.titleEl,t.title),this.paintText(t.text),this.card.dataset.step!==t.id&&(this.card.dataset.step=t.id),this.card.classList.toggle("is-hero",e),this.el.classList.toggle("is-done",n),this.el.classList.toggle("is-waiting",!e&&this.waiting()),this.paintChip(e?"":n?"done":t.action??(t.done===void 0?"look":"press")),this.badgeEl.classList.toggle("hidden",!(e&&i)),e&&i&&this.badgeEl.childElementCount===0&&this.badgeEl.append(f("check",{size:26})),this.paintAgenda(t);const r=t.target!==void 0&&t.done!==void 0&&!this.entry.was&&this.lit()===null&&performance.now()-this.entry.seenAt>=P;if(g(this.noteEl,r?t.missing??H:""),this.noteEl.classList.toggle("hidden",!r),this.assistSlot.dataset.step!==t.id){this.assistSlot.dataset.step=t.id;const a=t.assist;this.assistSlot.replaceChildren(...a===void 0?[]:[v(a.label,()=>a.run(),{class:"btn btn-quiet",title:a.title})])}this.backButton.disabled=this.index===0,this.backButton.classList.toggle("hidden",this.index===0);const o=this.waiting();g(this.nextLabel,t.next??(i?"finish":o?"skip step":"next"));const h=o?"btn btn-quiet tour-next":"btn btn-primary tour-next";this.nextButton.className!==h&&(this.nextButton.className=h),this.nextButton.classList.toggle("is-last",i),this.nextButton.title=o?"Go on without doing this step. The steps after it may need it.":i?"Close the tutorial":A("The next step","→")}paintChip(t){if(t===this.chip)return;if(this.chip=t,this.chipEl.classList.toggle("hidden",t===""),this.chipEl.classList.toggle("is-done",t==="done"),t===""){this.chipEl.replaceChildren();return}const i=t==="done"?null:q[t];this.chipEl.replaceChildren(f(i===null?"check":i.icon,{size:14})),this.chipEl.title=i===null?"done":i.word}paintAgenda(t){const i=t.agenda===!0;if(this.agendaEl.classList.toggle("hidden",!i),!i||this.agendaEl.dataset.step===t.id)return;this.agendaEl.dataset.step=t.id;const e=B(this.steps).filter(n=>this.steps.slice(n.from,n.from+n.count).some(r=>r.hero!==!0));this.agendaEl.replaceChildren(...e.map(n=>l("li",{text:n.chapter})))}paintText(t){const i=typeof t=="string"?t:JSON.stringify(t);this.textEl.dataset.text!==i&&(this.textEl.dataset.text=i,this.textEl.replaceChildren(...(typeof t=="string"?[t]:t).flatMap(e=>{if(typeof e=="string")return[document.createTextNode(e)];const n=l("code",{class:"tour-name",text:e.name});return e.copy===!0?[n,G(e.name,n)]:[n]})))}blink(t){for(const i of this.textEl.querySelectorAll(".tour-name"))i.classList.toggle("is-wrong",t),i.classList.remove("is-blinking"),t&&(i.offsetWidth,i.classList.add("is-blinking"))}loop(){this.frame=requestAnimationFrame(()=>{this.running&&(this.place(),this.loop())})}place(){const i=this.steps[this.index].hero===!0,e=i?null:this.lit(),n=window.innerWidth,r=window.innerHeight,o=e===null?null:{left:Math.max(0,e.left-y),top:Math.max(0,e.top-y),right:Math.min(n,e.right+y),bottom:Math.min(r,e.bottom+y)},h=this.card.offsetWidth,a=this.card.offsetHeight,x=(i?"hero:":"")+(o===null?`none:${n}:${r}:${h}:${a}`:`${Math.round(o.left)}:${Math.round(o.top)}:${Math.round(o.right)}:${Math.round(o.bottom)}:${n}:${r}:${h}:${a}`);if(x===this.painted)return;this.painted=x;const b=o!==null&&this.ring.classList.contains("hidden");this.veil.classList.toggle("hidden",o!==null),this.ring.classList.toggle("hidden",o===null);for(const p of this.blocks)p.classList.toggle("hidden",o===null);if(o!==null){D(this.ring,b,()=>m(this.ring,o));const[p,k,C,E]=this.blocks;m(p,{left:0,top:0,right:n,bottom:o.top}),m(k,{left:o.right,top:o.top,right:n,bottom:o.bottom}),m(C,{left:0,top:o.bottom,right:n,bottom:r}),m(E,{left:0,top:o.top,right:o.left,bottom:o.bottom})}const T={width:h,height:a},S={width:n,height:r},u=i?V(T,S):J(T,o,S);if(D(this.card,!this.placed,()=>{this.card.style.left=`${Math.round(u.left)}px`,this.card.style.top=`${Math.round(u.top)}px`}),this.placed=!0,this.card.dataset.side!==u.side&&(this.card.dataset.side=u.side),o!==null){const p=u.side==="below"||u.side==="above",k=p?(o.left+o.right)/2-u.left:(o.top+o.bottom)/2-u.top,E=Math.max($,Math.min((p?h:a)-$,k));this.card.style.setProperty("--tail-at",`${Math.round(E)}px`)}}nudge(){this.ring.classList.remove("is-nudged"),this.ring.offsetWidth,this.ring.classList.add("is-nudged"),this.nudgeTimer!==null&&clearTimeout(this.nudgeTimer),this.nudgeTimer=setTimeout(()=>this.ring.classList.remove("is-nudged"),700)}}function U(s,t){let i=s.findIndex(e=>e.id===t);if(i<0)return 0;for(;i>0&&s[i-1].chapter===s[i].chapter;)i--;return i}function B(s){const t=[];return s.forEach((i,e)=>{const n=t[t.length-1];n!==void 0&&n.chapter===i.chapter?n.count++:t.push({chapter:i.chapter,from:e,count:1})}),t}function N(){return{was:!1,doneAt:null,seenAt:performance.now(),failingSince:null,scrolled:!1,focused:!1,mistake:null}}function I(s){return!(s instanceof Element)}function g(s,t){s.textContent!==t&&(s.textContent=t)}function G(s,t){const i=`Copy ${s}`,e=l("button",{class:"tour-copy",type:"button",title:i,attrs:{"aria-label":i}},f("copy",{size:13}));let n=null;return e.addEventListener("click",()=>{O(s).then(()=>{e.classList.add("is-copied"),e.replaceChildren(f("check",{size:13})),e.title="Copied",n!==null&&clearTimeout(n),n=setTimeout(()=>{e.classList.remove("is-copied"),e.replaceChildren(f("copy",{size:13})),e.title=i},1400)},()=>{window.getSelection()?.selectAllChildren(t),e.title="Selected: copy it with the keyboard"})}),e}function L(s){try{return s()}catch{return null}}function D(s,t,i){if(!t){i();return}s.classList.add("is-snapping"),i(),s.offsetWidth,s.classList.remove("is-snapping")}function M(s){return{left:s.left,top:s.top,right:s.right,bottom:s.bottom}}function K(s){return Math.max(0,s.right-s.left)*Math.max(0,s.bottom-s.top)}function m(s,t){s.style.left=`${t.left}px`,s.style.top=`${t.top}px`,s.style.width=`${Math.max(0,t.right-t.left)}px`,s.style.height=`${Math.max(0,t.bottom-t.top)}px`}function Y(s){const t=[];for(let i=s.parentElement;i!==null&&i!==document.body;i=i.parentElement){const e=window.getComputedStyle(i);(e.overflowX!=="visible"||e.overflowY!=="visible")&&t.push(i)}return t}function X(s,t){if(!s.isConnected)return null;const i=s.getBoundingClientRect();if(i.width<=0||i.height<=0||s instanceof HTMLElement&&window.getComputedStyle(s).visibility==="hidden")return null;let e=M(i);for(const n of t)if(e=w(e,M(n.getBoundingClientRect())),e===null)return null;return w(e,{left:0,top:0,right:window.innerWidth,bottom:window.innerHeight})}function F(s,t){return s===null?null:X(s,t)}function w(s,t){const i={left:Math.max(s.left,t.left),top:Math.max(s.top,t.top),right:Math.min(s.right,t.right),bottom:Math.min(s.bottom,t.bottom)};return i.right-i.left<1||i.bottom-i.top<1?null:i}function J(s,t,i){const e=h=>Math.max(d,Math.min(i.width-s.width-d,h)),n=h=>Math.max(d,Math.min(i.height-s.height-d,h));if(t===null)return{left:e((i.width-s.width)/2),top:n(i.height-s.height-3*c),side:"none"};const r=(t.left+t.right)/2-s.width/2,o=(t.top+t.bottom)/2-s.height/2;return t.bottom+c+s.height<=i.height-d?{left:e(r),top:t.bottom+c,side:"below"}:t.top-c-s.height>=d?{left:e(r),top:t.top-c-s.height,side:"above"}:t.right+c+s.width<=i.width-d?{left:t.right+c,top:n(o),side:"right"}:t.left-c-s.width>=d?{left:t.left-c-s.width,top:n(o),side:"left"}:{left:e(i.width-s.width-2*c),top:n(i.height-s.height-2*c),side:"over"}}function V(s,t){return{left:Math.max(d,(t.width-s.width)/2),top:Math.max(d,(t.height-s.height)/2-t.height*.06),side:"none"}}export{Q as Tour,J as cardPlace,Y as clipsOf,V as heroPlace,B as partsOf,U as resumeIndex,X as shownBox};
|