sagadeck 1.7.1 → 1.8.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 +40 -0
- package/docs/ANDAMENTO.md +122 -0
- package/docs/REFERENCIA.md +58 -0
- package/package.json +2 -1
- package/src/ai/deck-ai.js +13 -4
- package/src/ai/map-analysis-run.js +119 -0
- package/src/ai/map-data.js +221 -0
- package/src/build.js +6 -2
- package/src/export/browser.js +23 -1
- package/src/export/pptx.js +2 -1
- package/src/export/shots.js +3 -1
- package/src/layouts.js +10 -0
- package/src/map-analysis.js +106 -0
- package/src/map-config.js +83 -0
- package/src/map-routing.js +82 -0
- package/src/map-services.js +60 -0
- package/src/map.js +261 -0
- package/src/runtime/base.css +20 -0
- package/src/runtime/map.js +234 -0
- package/src/runtime/runtime.js +1 -1
- package/src/runtime/vendor/LEAFLET-LICENSE.txt +26 -0
- package/src/runtime/vendor/geo-contornos.json +1 -0
- package/src/runtime/vendor/leaflet.css +661 -0
- package/src/runtime/vendor/leaflet.js +6 -0
- package/src/studio/ai-chat-route.js +20 -0
- package/src/studio/demo-decks.js +3 -1
- package/src/studio/geo-routes.js +138 -0
- package/src/studio/layout-samples.js +71 -0
- package/src/studio/project.js +1 -1
- package/src/studio/public/ai-settings.js +4 -1
- package/src/studio/public/app.js +7 -3
- package/src/studio/public/explorer.js +23 -8
- package/src/studio/public/index.html +4 -0
- package/src/studio/public/library.html +2 -0
- package/src/studio/public/library.js +6 -1
- package/src/studio/public/map-data.js +154 -0
- package/src/studio/public/map-editor.js +287 -0
- package/src/studio/public/map-fields.js +21 -0
- package/src/studio/public/map-settings.js +84 -0
- package/src/studio/public/slide-form.js +1 -1
- package/src/studio/public/style.css +13 -0
- package/src/studio/public/ui-icons.js +1 -1
- package/src/studio/public/viewers.js +2 -1
- package/src/studio/public/visual-editor.js +1 -1
- package/src/studio/server.js +10 -0
- package/src/studio/tour.js +76 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// Mapa ao vivo do slide `map` (dados montados por src/map.js). Regras:
|
|
2
|
+
// - preguiçoso: só monta quando o slide aparece (na apresentação) ou quando o Studio pede (prévia grande); o Leaflet
|
|
3
|
+
// só carrega na primeira vez (no HTML exportado ele já vem embutido);
|
|
4
|
+
// - uma tentativa e para: se os primeiros tiles falham sem nenhum sucesso, o fundo sai (as camadas ficam), aparece o
|
|
5
|
+
// aviso, e nenhum outro mapa desta sessão tenta de novo até alguém clicar em "Tentar de novo";
|
|
6
|
+
// - nada de baixar tiles fora da tela; atribuição sempre visível.
|
|
7
|
+
(function () {
|
|
8
|
+
const FALHAS = 6; // tiles que podem falhar, sem nenhum sucesso, antes de desligar o fundo
|
|
9
|
+
const KEY = "sagadeck.mapa.fundo";
|
|
10
|
+
const store = { get() { try { return sessionStorage.getItem(KEY); } catch { return null; } }, set(v) { try { v ? sessionStorage.setItem(KEY, v) : sessionStorage.removeItem(KEY); } catch { /* sem armazenamento: só nesta página */ } } };
|
|
11
|
+
let broken = store.get() === "falhou";
|
|
12
|
+
let loader;
|
|
13
|
+
|
|
14
|
+
function load() {
|
|
15
|
+
if (window.L) return Promise.resolve();
|
|
16
|
+
return loader ||= new Promise((resolve, reject) => {
|
|
17
|
+
if (!document.querySelector('link[data-leaflet]')) { const css = document.createElement("link"); css.rel = "stylesheet"; css.href = "leaflet.css"; css.dataset.leaflet = "1"; document.head.append(css); }
|
|
18
|
+
const s = document.createElement("script"); s.src = "leaflet.js"; s.onload = resolve;
|
|
19
|
+
s.onerror = () => { loader = null; s.remove(); reject(new Error("Não foi possível carregar o mapa.")); };
|
|
20
|
+
document.head.append(s);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// cor do tema (s1…, em, #hex) em rgb, lida do próprio slide
|
|
25
|
+
function resolver(box) {
|
|
26
|
+
const probe = document.createElement("i"); probe.style.display = "none"; box.append(probe);
|
|
27
|
+
const cache = {};
|
|
28
|
+
const rgb = (c) => {
|
|
29
|
+
if (cache[c]) return cache[c];
|
|
30
|
+
// a mesma regra de src/map.js (mapColorCSS): paleta do tema c1…c5 com recuo no destaque
|
|
31
|
+
probe.style.color = /^#?[0-9a-f]{6}$/i.test(c) ? `#${c.replace("#", "")}` : /^(c\d|accent|alert|ink|paper)$/.test(c) ? `var(--c-${c},var(--em))` : `var(--${c})`;
|
|
32
|
+
const m = getComputedStyle(probe).color.match(/\d+(\.\d+)?/g) || [0, 0, 0];
|
|
33
|
+
return (cache[c] = m.slice(0, 3).map(Number));
|
|
34
|
+
};
|
|
35
|
+
const css = (c) => `rgb(${rgb(c).join(",")})`;
|
|
36
|
+
const mix = (a, b, t) => `rgb(${rgb(a).map((v, i) => Math.round(v + (rgb(b)[i] - v) * t)).join(",")})`;
|
|
37
|
+
return { css, mix, done: () => probe.remove() };
|
|
38
|
+
}
|
|
39
|
+
const num = (v) => { const s = String(v ?? "").trim().replace(/^R\$/i, "").replace(/%$/, "").replace(/\s/g, ""); const n = Number(/^-?\d{1,3}(\.\d{3})+(,\d+)?$/.test(s) ? s.replace(/\./g, "").replace(",", ".") : s.replace(",", ".")); return s !== "" && Number.isFinite(n) ? n : null; };
|
|
40
|
+
const fmt = (n) => (Number.isFinite(n) ? n.toLocaleString(document.documentElement.lang || "pt-BR", { maximumFractionDigits: 1 }) : "");
|
|
41
|
+
const escHTML = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
42
|
+
// exportando (PDF, PowerPoint, fotos) também sem animação: a foto sai no lugar final
|
|
43
|
+
const reduced = () => matchMedia("(prefers-reduced-motion: reduce)").matches || document.documentElement.dataset.motion === "none" || document.documentElement.classList.contains("export");
|
|
44
|
+
|
|
45
|
+
const live = new Map(); // .map-box -> estado
|
|
46
|
+
// vista guardada entre redesenhos do Studio (chave: o slide e a sua forma de enquadrar)
|
|
47
|
+
const views = new Map();
|
|
48
|
+
const keyOf = (box) => { const s = box.closest("[data-idx]"); return s && !s.closest("#stage") ? `${s.dataset.uid || s.dataset.idx}|${box.querySelector(".map-data")?.textContent.match(/"view":(null|\{[^}]*\}|"fit")/)?.[1] || ""}` : null; };
|
|
49
|
+
let readyResolve, readyPromise = Promise.resolve();
|
|
50
|
+
|
|
51
|
+
function notice(box, text, retry) {
|
|
52
|
+
let n = box.querySelector(".map-notice");
|
|
53
|
+
if (!n) { n = document.createElement("div"); n.className = "map-notice f-label"; box.append(n); }
|
|
54
|
+
n.innerHTML = `<span>${escHTML(text)}</span>${retry ? '<button type="button">Tentar de novo</button>' : ""}`;
|
|
55
|
+
n.querySelector("button")?.addEventListener("click", (e) => { e.stopPropagation(); store.set(null); broken = false; n.remove(); const st = live.get(box); if (st) { dispose(box); mount(box.closest(".slide") || box.parentElement, { force: true }); } });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function mountBox(box) {
|
|
59
|
+
if (live.has(box) || box.dataset.mapMounted) return;
|
|
60
|
+
box.dataset.mapMounted = "pending";
|
|
61
|
+
let m;
|
|
62
|
+
try { m = JSON.parse(box.querySelector(".map-data").textContent); } catch { box.dataset.mapMounted = "error"; return; }
|
|
63
|
+
let settle; const settled = new Promise((r) => (settle = r));
|
|
64
|
+
const st = { m, settled, map: null, layers: [], tilesDone: false };
|
|
65
|
+
live.set(box, st);
|
|
66
|
+
try { await load(); } catch (e) { notice(box, e.message); box.dataset.mapMounted = "error"; settle(); return; }
|
|
67
|
+
const L = window.L;
|
|
68
|
+
const el = box.querySelector(".map-live");
|
|
69
|
+
const map = L.map(el, { zoomControl: true, attributionControl: true, keyboard: false, worldCopyJump: true, zoomSnap: 0.25, fadeAnimation: !reduced(), zoomAnimation: !reduced() });
|
|
70
|
+
st.map = map;
|
|
71
|
+
map.attributionControl.setPrefix('<a href="https://leafletjs.com" target="_blank" rel="noopener">Leaflet</a>');
|
|
72
|
+
// a roda e o arrastar mexem no mapa, não trocam de slide
|
|
73
|
+
for (const ev of ["wheel", "pointerdown", "mousedown", "touchstart", "click", "keydown"]) el.addEventListener(ev, (e) => e.stopPropagation());
|
|
74
|
+
|
|
75
|
+
// fundo: antes de ligar a camada, testa UM tile (o do centro). Se ele falha, nenhum outro é pedido e nenhum
|
|
76
|
+
// outro mapa desta sessão tenta; se passa, a camada liga (e ainda desiste se os primeiros falharem todos)
|
|
77
|
+
const wantSat = m.basemap === "satelite";
|
|
78
|
+
const tiles = m.basemap === "nenhum" ? null : (wantSat && m.tiles?.satelite) || m.tiles?.tiles || null;
|
|
79
|
+
if (wantSat && !m.tiles?.satelite) notice(box, "Satélite não configurado nesta máquina (Configurar mapa); mostrando as ruas.");
|
|
80
|
+
const giveUp = () => {
|
|
81
|
+
broken = true; store.set("falhou"); box.dataset.mapTiles = "falhou";
|
|
82
|
+
notice(box, "Mapa de fundo indisponível nesta rede. As camadas continuam.", true);
|
|
83
|
+
st.tilesDone = true; settle();
|
|
84
|
+
};
|
|
85
|
+
const startTiles = () => {
|
|
86
|
+
if (!tiles) { st.tilesDone = true; return; }
|
|
87
|
+
if (broken) { box.dataset.mapTiles = "falhou"; notice(box, "Mapa de fundo indisponível nesta rede. As camadas continuam.", true); st.tilesDone = true; return; }
|
|
88
|
+
const z = Math.round(map.getZoom()), c = map.project(map.getCenter(), z).divideBy(256).floor();
|
|
89
|
+
const probe = new Image();
|
|
90
|
+
probe.onerror = () => { if (live.get(box) === st) giveUp(); };
|
|
91
|
+
probe.onload = () => {
|
|
92
|
+
if (live.get(box) !== st) return;
|
|
93
|
+
let ok = 0, fail = 0;
|
|
94
|
+
const t = L.tileLayer(tiles.url, { attribution: tiles.attribution, maxZoom: tiles.maxZoom || 19 });
|
|
95
|
+
t.on("tileload", () => { ok++; });
|
|
96
|
+
t.on("tileerror", () => { fail++; if (!ok && fail >= FALHAS) { map.removeLayer(t); giveUp(); } });
|
|
97
|
+
t.on("load", () => { st.tilesDone = true; settle(); });
|
|
98
|
+
t.addTo(map);
|
|
99
|
+
};
|
|
100
|
+
probe.src = L.Util.template(tiles.url, { z, x: c.x, y: c.y, s: "a", r: "" });
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// camadas
|
|
104
|
+
const col = resolver(box);
|
|
105
|
+
const legend = document.createElement("div"); legend.className = "map-legend f-body";
|
|
106
|
+
m.layers.forEach((layer, i) => {
|
|
107
|
+
const s = layer.style;
|
|
108
|
+
const hidden = new Set();
|
|
109
|
+
const colorOf = (f) => {
|
|
110
|
+
const v = s.colorBy ? f.properties?.[s.colorBy] : null;
|
|
111
|
+
if (s.scale) { const n = num(v); const t = n == null || s.scale.max === s.scale.min ? 1 : (n - s.scale.min) / (s.scale.max - s.scale.min); return col.mix("surface", "em", 0.2 + 0.8 * t); }
|
|
112
|
+
const cat = s.categories?.find((c) => c.value === String(v ?? "").trim());
|
|
113
|
+
return col.css(cat?.color || s.color || "c1");
|
|
114
|
+
};
|
|
115
|
+
const radius = (f) => { if (!s.sizeBy) return 14; const n = num(f.properties?.[s.sizeBy]); const r = s.sizeScale; return n == null || r.max === r.min ? 16 : 9 + 24 * Math.sqrt((n - r.min) / (r.max - r.min)); }; // em px do slide (1920)
|
|
116
|
+
const card = (f) => {
|
|
117
|
+
const p = f.properties || {};
|
|
118
|
+
const title = s.label ? p[s.label] : p.nome || p.name || "";
|
|
119
|
+
const rows = s.popup.filter((c) => c !== s.label).map((c) => `<tr><th>${escHTML(c)}</th><td>${escHTML(p[c])}</td></tr>`).join("");
|
|
120
|
+
return `<div class="map-card-in">${title ? `<b>${escHTML(title)}</b>` : ""}${rows ? `<table>${rows}</table>` : ""}</div>`;
|
|
121
|
+
};
|
|
122
|
+
const g = L.geoJSON(layer.data, {
|
|
123
|
+
filter: (f) => !s.colorBy || !hidden.has(String(f.properties?.[s.colorBy] ?? "").trim()),
|
|
124
|
+
pointToLayer: (f, ll) => L.circleMarker(ll, { radius: radius(f), color: col.css("bg"), weight: 2.5, fillColor: colorOf(f), fillOpacity: 0.92 }),
|
|
125
|
+
style: (f) => (f.geometry.type.includes("Polygon") ? { color: colorOf(f), weight: 2, fillColor: colorOf(f), fillOpacity: layer.kind === "areas" ? 0.75 : 0.32 } : { color: colorOf(f), weight: 5, opacity: 0.95 }),
|
|
126
|
+
onEachFeature: (f, lyr) => {
|
|
127
|
+
if (s.popup.length || s.label) lyr.bindPopup(card(f), { className: "map-card", maxWidth: 420 });
|
|
128
|
+
// no Studio, clicar no ponto seleciona a linha da planilha aberta (map-data.js)
|
|
129
|
+
lyr.on("click", () => document.dispatchEvent(new CustomEvent("sagamap:ponto", { detail: { path: layer.source, linha: f.properties?._linha } })));
|
|
130
|
+
if (s.label && f.properties?.[s.label] != null && layer.data.features.length <= 60) lyr.bindTooltip(escHTML(f.properties[s.label]), { permanent: true, direction: "top", offset: [0, -10], className: "map-label" });
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
const entry = { layer, g, on: layer.step == null, hidden };
|
|
134
|
+
st.layers.push(entry);
|
|
135
|
+
if (entry.on) g.addTo(map);
|
|
136
|
+
// legenda: o nome liga/desliga a camada; cada categoria liga/desliga os seus pontos
|
|
137
|
+
const item = document.createElement("div"); item.className = "map-leg-layer"; item.dataset.layer = i;
|
|
138
|
+
const cats = s.categories ? s.categories.map((c) => `<button type="button" class="map-leg-cat" data-cat="${escHTML(c.value)}"><i style="background:${col.css(c.color)}"></i>${escHTML(c.value)}</button>`).join("") : "";
|
|
139
|
+
const scale = s.scale ? `<div class="map-leg-scale"><span>${escHTML(layer.prefix + fmt(s.scale.min) + layer.suffix)}</span><i style="background:linear-gradient(90deg,${col.mix("surface", "em", 0.2)},${col.css("em")})"></i><span>${escHTML(layer.prefix + fmt(s.scale.max) + layer.suffix)}</span></div>` : "";
|
|
140
|
+
item.innerHTML = `<button type="button" class="map-leg-name">${!s.categories && !s.scale ? `<i style="background:${col.css(s.color || "c1")}"></i>` : ""}${escHTML(layer.name)}${layer.legend ? ` <small>${escHTML(layer.legend)}</small>` : ""}</button>${cats}${scale}`;
|
|
141
|
+
item.querySelector(".map-leg-name").onclick = (e) => { e.stopPropagation(); entry.manualOff = !entry.manualOff; sync(entry); item.classList.toggle("off", entry.manualOff); };
|
|
142
|
+
item.querySelectorAll(".map-leg-cat").forEach((b) => (b.onclick = (e) => {
|
|
143
|
+
e.stopPropagation();
|
|
144
|
+
const v = b.dataset.cat; hidden.has(v) ? hidden.delete(v) : hidden.add(v); b.classList.toggle("off", hidden.has(v));
|
|
145
|
+
g.clearLayers(); g.addData(layer.data);
|
|
146
|
+
}));
|
|
147
|
+
item.hidden = !entry.on;
|
|
148
|
+
entry.legend = item;
|
|
149
|
+
legend.append(item);
|
|
150
|
+
});
|
|
151
|
+
col.done();
|
|
152
|
+
if (m.layers.length) box.append(legend);
|
|
153
|
+
const sync = (entry) => {
|
|
154
|
+
const show = entry.on && !entry.manualOff;
|
|
155
|
+
if (show && !map.hasLayer(entry.g)) entry.g.addTo(map); else if (!show && map.hasLayer(entry.g)) map.removeLayer(entry.g);
|
|
156
|
+
if (entry.legend) entry.legend.hidden = !entry.on;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// enquadramento: o do slide, senão os dados, senão o mundo
|
|
160
|
+
// O enquadramento automático deixa os pontos acima da legenda, para continuarem visíveis e clicáveis.
|
|
161
|
+
const fit = (b, fly) => { if (!b) return; const opts = { paddingTopLeft: [48, 48], paddingBottomRight: [48, 48 + legend.offsetHeight], maxZoom: 17 }; fly && !reduced() ? map.flyToBounds(b, { ...opts, duration: 1.6 }) : map.fitBounds(b, opts); };
|
|
162
|
+
const go = (v, fly) => { if (!v) return; if (v === "fit") return; fly && !reduced() ? map.flyTo([v.center[0], v.center[1]], v.zoom, { duration: 1.6 }) : map.setView([v.center[0], v.center[1]], v.zoom); };
|
|
163
|
+
// no Studio, o slide redesenha a cada edição: a vista que a pessoa estava olhando continua (na apresentação, não)
|
|
164
|
+
const kept = keyOf(box) && views.get(keyOf(box));
|
|
165
|
+
if (kept) map.setView(kept.center, kept.zoom, { animate: false });
|
|
166
|
+
else if (m.view && m.view !== "fit") go(m.view); else if (m.bounds) fit(m.bounds); else map.setView([0, 0], 2);
|
|
167
|
+
startTiles();
|
|
168
|
+
|
|
169
|
+
// camada por clique: os marcadores invisíveis data-step do slide ganham "in" (src/runtime/runtime.js)
|
|
170
|
+
const steps = [...box.querySelectorAll(".map-step")];
|
|
171
|
+
const apply = (fly) => {
|
|
172
|
+
let lastView = null;
|
|
173
|
+
for (const s of steps) {
|
|
174
|
+
const entry = st.layers[Number(s.dataset.layer)]; if (!entry) continue;
|
|
175
|
+
const on = s.classList.contains("in");
|
|
176
|
+
if (on !== entry.on) { entry.on = on; sync(entry); if (on && entry.layer.view) lastView = entry; }
|
|
177
|
+
}
|
|
178
|
+
if (lastView && fly) lastView.layer.view === "fit" ? fit(lastView.g.getBounds(), true) : go(lastView.layer.view, true);
|
|
179
|
+
};
|
|
180
|
+
apply(false);
|
|
181
|
+
st.observer = new MutationObserver(() => apply(true));
|
|
182
|
+
steps.forEach((s) => st.observer.observe(s, { attributes: true, attributeFilter: ["class"] }));
|
|
183
|
+
|
|
184
|
+
box.dataset.mapMounted = "ready";
|
|
185
|
+
requestAnimationFrame(() => map.invalidateSize());
|
|
186
|
+
if (st.tilesDone) settle();
|
|
187
|
+
setTimeout(settle, 8000); // a exportação não espera para sempre
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// linha selecionada na planilha (Studio) -> abre o cartão do ponto dessa linha
|
|
191
|
+
document.addEventListener("sagamap:linha", (e) => {
|
|
192
|
+
const { path, linha } = e.detail || {};
|
|
193
|
+
for (const st of live.values()) for (const entry of st.layers) {
|
|
194
|
+
if (!path || entry.layer.source !== path || !st.map?.hasLayer(entry.g)) continue;
|
|
195
|
+
entry.g.eachLayer((lyr) => { if (lyr.feature?.properties?._linha === linha) { lyr.openPopup(); if (lyr.getLatLng) st.map.panTo(lyr.getLatLng()); } });
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
function mount(root = document, { force = false } = {}) {
|
|
200
|
+
const boxes = [...root.querySelectorAll(".map-box")];
|
|
201
|
+
if (root.classList?.contains("map-box")) boxes.push(root);
|
|
202
|
+
const jobs = boxes.filter((b) => force || !live.has(b)).map(mountBox);
|
|
203
|
+
readyPromise = Promise.all(jobs).then(() => Promise.all(boxes.map((b) => live.get(b)?.settled)));
|
|
204
|
+
return readyPromise;
|
|
205
|
+
}
|
|
206
|
+
function dispose(root = document, { keepView = false } = {}) {
|
|
207
|
+
const boxes = root.classList?.contains("map-box") ? [root] : [...root.querySelectorAll(".map-box")];
|
|
208
|
+
for (const b of boxes) {
|
|
209
|
+
const st = live.get(b); if (!st) continue;
|
|
210
|
+
if (keepView && keyOf(b) && st.map) views.set(keyOf(b), { center: st.map.getCenter(), zoom: st.map.getZoom() });
|
|
211
|
+
st.observer?.disconnect(); st.map?.remove(); live.delete(b);
|
|
212
|
+
delete b.dataset.mapMounted; b.querySelector(".map-legend")?.remove();
|
|
213
|
+
b.querySelector(".map-notice")?.remove();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// na apresentação: só o slide atual monta (o resto não pede tile nenhum); a exportação espera o "pronto"
|
|
218
|
+
function watchDeck() {
|
|
219
|
+
const slides = [...document.querySelectorAll("#stage > .slide")].filter((s) => s.querySelector(".map-box"));
|
|
220
|
+
if (!slides.length) return;
|
|
221
|
+
const check = () => { for (const s of slides) if (s.classList.contains("current")) mount(s); };
|
|
222
|
+
const obs = new MutationObserver(check);
|
|
223
|
+
slides.forEach((s) => obs.observe(s, { attributes: true, attributeFilter: ["class"] }));
|
|
224
|
+
check();
|
|
225
|
+
}
|
|
226
|
+
window.SagaMap = {
|
|
227
|
+
mount, dispose,
|
|
228
|
+
// o editor do Studio (map-editor.js) pega o mapa e as camadas montadas desta caixa
|
|
229
|
+
instance: (box) => live.get(box),
|
|
230
|
+
// a exportação chama depois de ir a um slide: espera o mapa dele (tiles ou desistência), no máximo 8 s
|
|
231
|
+
settled: () => { const cur = document.querySelector("#stage > .slide.current"); return cur ? mount(cur) : Promise.resolve(); },
|
|
232
|
+
};
|
|
233
|
+
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", watchDeck); else watchDeck();
|
|
234
|
+
})();
|
package/src/runtime/runtime.js
CHANGED
|
@@ -819,7 +819,7 @@
|
|
|
819
819
|
const t = e.changedTouches[0], dx = t.clientX - tx, dy = t.clientY - ty, dt = Date.now() - tt;
|
|
820
820
|
tx = ty = null;
|
|
821
821
|
if (Math.abs(dx) > 50 && Math.abs(dx) > Math.abs(dy)) { cmd(dx < 0 ? "next" : "prev"); return; }
|
|
822
|
-
if (Math.abs(dx) < 12 && Math.abs(dy) < 12 && dt < 500 && !e.target.closest("a,button,input,textarea,select,[contenteditable],video,audio,iframe,.widget,[data-goto]")) {
|
|
822
|
+
if (Math.abs(dx) < 12 && Math.abs(dy) < 12 && dt < 500 && !e.target.closest("a,button,input,textarea,select,[contenteditable],video,audio,iframe,.widget,.map-box,[data-goto]")) {
|
|
823
823
|
const sel = typeof getSelection === "function" && getSelection();
|
|
824
824
|
if (sel && !sel.isCollapsed) return; // selecionando texto: não avança
|
|
825
825
|
cmd("next");
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2010-2023, Volodymyr Agafonkin
|
|
4
|
+
Copyright (c) 2010-2011, CloudMade
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
Redistribution and use in source and binary forms, with or without
|
|
8
|
+
modification, are permitted provided that the following conditions are met:
|
|
9
|
+
|
|
10
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
11
|
+
list of conditions and the following disclaimer.
|
|
12
|
+
|
|
13
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
14
|
+
this list of conditions and the following disclaimer in the documentation
|
|
15
|
+
and/or other materials provided with the distribution.
|
|
16
|
+
|
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
18
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
19
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
20
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
21
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
22
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
23
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
24
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
25
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
26
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|