@twip/web 0.0.0-canary-f16fa12
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/LICENSE +202 -0
- package/README.md +35 -0
- package/dist/THIRD-PARTY-NOTICES.md +5457 -0
- package/dist/commands.d.ts +637 -0
- package/dist/commands.js +188 -0
- package/dist/editor.d.ts +218 -0
- package/dist/editor.js +460 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.js +26 -0
- package/dist/events.d.ts +126 -0
- package/dist/events.js +1 -0
- package/dist/fonts/google.d.ts +3 -0
- package/dist/fonts/google.js +39 -0
- package/dist/fonts/library.d.ts +65 -0
- package/dist/fonts/library.js +130 -0
- package/dist/host/a11y.d.ts +3 -0
- package/dist/host/a11y.js +62 -0
- package/dist/host/chrome.d.ts +28 -0
- package/dist/host/chrome.js +144 -0
- package/dist/host/clipboard.d.ts +16 -0
- package/dist/host/clipboard.js +128 -0
- package/dist/host/cursors.d.ts +15 -0
- package/dist/host/cursors.js +5 -0
- package/dist/host/dom.d.ts +45 -0
- package/dist/host/dom.js +122 -0
- package/dist/host/frames.d.ts +14 -0
- package/dist/host/frames.js +87 -0
- package/dist/host/gpu.d.ts +58 -0
- package/dist/host/gpu.js +79 -0
- package/dist/host/ime.d.ts +3 -0
- package/dist/host/ime.js +11 -0
- package/dist/host/input.d.ts +12 -0
- package/dist/host/input.js +86 -0
- package/dist/host/keys.d.ts +16 -0
- package/dist/host/keys.js +27 -0
- package/dist/host/mount.d.ts +11 -0
- package/dist/host/mount.js +72 -0
- package/dist/host/pointer.d.ts +4 -0
- package/dist/host/pointer.js +196 -0
- package/dist/host/scroll.d.ts +14 -0
- package/dist/host/scroll.js +95 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +10 -0
- package/dist/review.d.ts +156 -0
- package/dist/review.js +38 -0
- package/dist/services/accessibility.d.ts +52 -0
- package/dist/services/accessibility.js +71 -0
- package/dist/services/dialogs.d.ts +181 -0
- package/dist/services/dialogs.js +18 -0
- package/dist/services/elements.d.ts +146 -0
- package/dist/services/elements.js +18 -0
- package/dist/services/find.d.ts +50 -0
- package/dist/services/find.js +12 -0
- package/dist/services/menus.d.ts +82 -0
- package/dist/services/menus.js +8 -0
- package/dist/services/navigation.d.ts +58 -0
- package/dist/services/navigation.js +12 -0
- package/dist/services/ruler.d.ts +95 -0
- package/dist/services/ruler.js +16 -0
- package/dist/services/spelling.d.ts +34 -0
- package/dist/services/spelling.js +14 -0
- package/dist/services/view.d.ts +70 -0
- package/dist/services/view.js +19 -0
- package/dist/state.d.ts +483 -0
- package/dist/state.js +1 -0
- package/dist/transport/direct.d.ts +31 -0
- package/dist/transport/direct.js +405 -0
- package/dist/transport/module.d.ts +51 -0
- package/dist/transport/module.js +17 -0
- package/dist/transport/protocol.d.ts +265 -0
- package/dist/transport/protocol.js +132 -0
- package/dist/transport/worker.d.ts +54 -0
- package/dist/transport/worker.js +203 -0
- package/dist/twip-wasm.js +2 -0
- package/dist/twip_wasm.wasm +0 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.js +16 -0
- package/dist/worker/entry.d.ts +1 -0
- package/dist/worker/entry.js +53 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +2 -0
- package/package.json +30 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type Transport } from '../transport/protocol.js';
|
|
2
|
+
/** A font the engine needs. */
|
|
3
|
+
export interface FontRequest {
|
|
4
|
+
/** The family. */
|
|
5
|
+
family: string;
|
|
6
|
+
/** Bold. */
|
|
7
|
+
bold: boolean;
|
|
8
|
+
/** Italic. */
|
|
9
|
+
italic: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** A face a library holds. */
|
|
12
|
+
export interface FaceInfo {
|
|
13
|
+
/** The family. */
|
|
14
|
+
family: string;
|
|
15
|
+
/** Bold. */
|
|
16
|
+
bold: boolean;
|
|
17
|
+
/** Italic. */
|
|
18
|
+
italic: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Draws one family (optionally one style of it) with another. */
|
|
21
|
+
export interface SubstitutionRule {
|
|
22
|
+
/** The family the document asks for. */
|
|
23
|
+
from: string;
|
|
24
|
+
/** Only the bold faces (or only the regular ones). */
|
|
25
|
+
bold?: boolean;
|
|
26
|
+
/** Only the italic faces (or only the upright ones). */
|
|
27
|
+
italic?: boolean;
|
|
28
|
+
/** The family to draw instead. */
|
|
29
|
+
to: string;
|
|
30
|
+
}
|
|
31
|
+
/** Where fonts come from for a request: adds the faces it finds, and resolves whether it found the family. */
|
|
32
|
+
export type FontSource = (request: FontRequest, signal: AbortSignal) => Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* An engine an editor started, and the font requests it sends. Internal: the library's changes so far are replayed
|
|
35
|
+
* into it, and calls made after this keep their order with the editors'.
|
|
36
|
+
*/
|
|
37
|
+
export declare function useEngine(transport: Transport): Transport;
|
|
38
|
+
/**
|
|
39
|
+
* Answers the engine's request for a font: the application's resolver first, then Google Fonts. When neither adds
|
|
40
|
+
* the family, the engine marks it unresolved. Internal.
|
|
41
|
+
*/
|
|
42
|
+
export declare function answerFontRequest(request: FontRequest): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* The fonts editors draw with. Share one per page: every editor on the page uses {@link FontLibrary.default}, and a
|
|
45
|
+
* font added there reaches every editor, including ones already open.
|
|
46
|
+
*/
|
|
47
|
+
export declare class FontLibrary {
|
|
48
|
+
private constructor();
|
|
49
|
+
/** The page's library. */
|
|
50
|
+
static default(): FontLibrary;
|
|
51
|
+
/** Adds font files: TTF, OTF and TTC bytes, or a URL to fetch them from. */
|
|
52
|
+
add(source: Uint8Array | URL | string): Promise<FaceInfo[]>;
|
|
53
|
+
/** Fetches fonts the engine asks for: resolve with the bytes, a URL, or null when there is none. */
|
|
54
|
+
setResolver(resolver: (request: FontRequest, signal: AbortSignal) => Promise<Uint8Array | URL | null>): void;
|
|
55
|
+
/** Resolves missing fonts from Google Fonts, optionally only some families. */
|
|
56
|
+
useGoogleFonts(options?: {
|
|
57
|
+
families?: string[];
|
|
58
|
+
}): void;
|
|
59
|
+
/** Draws families with others: `{ 'Times New Roman': 'Tinos' }`, or rules per style. */
|
|
60
|
+
substitute(rules: Record<string, string> | SubstitutionRule[]): void;
|
|
61
|
+
/** The family for text whose font can't be found. */
|
|
62
|
+
setDefault(family: string): void;
|
|
63
|
+
/** Every family the library can draw now, for a font box. */
|
|
64
|
+
families(): Promise<string[]>;
|
|
65
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { EditorError } from '../errors.js';
|
|
2
|
+
import { directTransport } from '../transport/direct.js';
|
|
3
|
+
import { Kind, Method } from '../transport/protocol.js';
|
|
4
|
+
import { googleFonts } from './google.js';
|
|
5
|
+
let page;
|
|
6
|
+
let appResolver;
|
|
7
|
+
let google;
|
|
8
|
+
/** The page's engines, first started first: this thread's and the engine worker's, once editors started them. */
|
|
9
|
+
const engines = [];
|
|
10
|
+
/** The library's changes so far, replayed into an engine that starts later. */
|
|
11
|
+
const changes = [];
|
|
12
|
+
/**
|
|
13
|
+
* An engine an editor started, and the font requests it sends. Internal: the library's changes so far are replayed
|
|
14
|
+
* into it, and calls made after this keep their order with the editors'.
|
|
15
|
+
*/
|
|
16
|
+
export function useEngine(transport) {
|
|
17
|
+
if (!engines.includes(transport)) {
|
|
18
|
+
engines.push(transport);
|
|
19
|
+
for (const apply of changes)
|
|
20
|
+
report(apply(transport));
|
|
21
|
+
// The library is the engine's, so its requests name no session.
|
|
22
|
+
transport.listen(0, (m) => {
|
|
23
|
+
if (m.kind === Kind.FontRequest)
|
|
24
|
+
void answerFontRequest(m.payload);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return transport;
|
|
28
|
+
}
|
|
29
|
+
/** Runs `use` on the page's first engine, loading the engine on this thread when no editor has started one. */
|
|
30
|
+
function withEngine(use) {
|
|
31
|
+
return engines[0] ? use(engines[0]) : directTransport().then((t) => use(engines[0] ?? useEngine(t)));
|
|
32
|
+
}
|
|
33
|
+
/** Applies a change to every engine and to the ones that start later; resolves with the first engine's answer. */
|
|
34
|
+
function change(apply) {
|
|
35
|
+
return withEngine((first) => {
|
|
36
|
+
changes.push(apply);
|
|
37
|
+
for (const e of engines)
|
|
38
|
+
if (e !== first)
|
|
39
|
+
report(apply(e));
|
|
40
|
+
return apply(first);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** A library call: the font library is the engine's, so no session is named. Only `FontFamilies` changes nothing. */
|
|
44
|
+
function libraryCall(method, request) {
|
|
45
|
+
const call = (e) => e.call(0, method, request);
|
|
46
|
+
return method === Method.FontFamilies ? withEngine(call) : change(call);
|
|
47
|
+
}
|
|
48
|
+
/** The failure of a call a synchronous method started: reported, as there is no promise to reject. */
|
|
49
|
+
function report(call) {
|
|
50
|
+
call.catch((e) => console.error(e));
|
|
51
|
+
}
|
|
52
|
+
/** Fetches a font file. */
|
|
53
|
+
async function fetchFont(url, signal) {
|
|
54
|
+
const response = await fetch(url, { signal });
|
|
55
|
+
if (!response.ok)
|
|
56
|
+
throw new EditorError('unavailable', `${String(url)}: HTTP ${response.status}`);
|
|
57
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
58
|
+
}
|
|
59
|
+
/** Adds font bytes, or the file at a URL, to the page's library. */
|
|
60
|
+
async function addFont(source, signal) {
|
|
61
|
+
const bytes = source instanceof Uint8Array ? source : await fetchFont(source, signal);
|
|
62
|
+
return change((e) => e.addFont(bytes));
|
|
63
|
+
}
|
|
64
|
+
/** With a source to ask, the engine requests missing faces instead of calling them unresolved at once. */
|
|
65
|
+
function askEngine() {
|
|
66
|
+
report(libraryCall(Method.FontSetResolver, true));
|
|
67
|
+
}
|
|
68
|
+
const sameFamily = (a, b) => a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0;
|
|
69
|
+
/**
|
|
70
|
+
* Answers the engine's request for a font: the application's resolver first, then Google Fonts. When neither adds
|
|
71
|
+
* the family, the engine marks it unresolved. Internal.
|
|
72
|
+
*/
|
|
73
|
+
export async function answerFontRequest(request) {
|
|
74
|
+
// shortcut: the signal never fires; abort it when every session that asked for the font is destroyed.
|
|
75
|
+
const signal = new AbortController().signal;
|
|
76
|
+
const resolver = appResolver;
|
|
77
|
+
const fromResolver = resolver &&
|
|
78
|
+
(async (r, sig) => {
|
|
79
|
+
const found = await resolver(r, sig);
|
|
80
|
+
return found !== null && (await addFont(found, sig)).some((face) => sameFamily(face.family, r.family));
|
|
81
|
+
});
|
|
82
|
+
for (const source of [fromResolver, google]) {
|
|
83
|
+
try {
|
|
84
|
+
if (source && (await source(request, signal)))
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
console.error(e);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
report(libraryCall(Method.FontMarkUnresolved, request));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The fonts editors draw with. Share one per page: every editor on the page uses {@link FontLibrary.default}, and a
|
|
95
|
+
* font added there reaches every editor, including ones already open.
|
|
96
|
+
*/
|
|
97
|
+
export class FontLibrary {
|
|
98
|
+
constructor() { }
|
|
99
|
+
/** The page's library. */
|
|
100
|
+
static default() {
|
|
101
|
+
return (page ??= new FontLibrary());
|
|
102
|
+
}
|
|
103
|
+
/** Adds font files: TTF, OTF and TTC bytes, or a URL to fetch them from. */
|
|
104
|
+
add(source) {
|
|
105
|
+
return addFont(source);
|
|
106
|
+
}
|
|
107
|
+
/** Fetches fonts the engine asks for: resolve with the bytes, a URL, or null when there is none. */
|
|
108
|
+
setResolver(resolver) {
|
|
109
|
+
appResolver = resolver;
|
|
110
|
+
askEngine();
|
|
111
|
+
}
|
|
112
|
+
/** Resolves missing fonts from Google Fonts, optionally only some families. */
|
|
113
|
+
useGoogleFonts(options) {
|
|
114
|
+
google = googleFonts(addFont, options?.families);
|
|
115
|
+
askEngine();
|
|
116
|
+
}
|
|
117
|
+
/** Draws families with others: `{ 'Times New Roman': 'Tinos' }`, or rules per style. */
|
|
118
|
+
substitute(rules) {
|
|
119
|
+
const list = Array.isArray(rules) ? rules : Object.entries(rules).map(([from, to]) => ({ from, to }));
|
|
120
|
+
report(libraryCall(Method.FontSetSubstitutions, list));
|
|
121
|
+
}
|
|
122
|
+
/** The family for text whose font can't be found. */
|
|
123
|
+
setDefault(family) {
|
|
124
|
+
report(libraryCall(Method.FontSetDefault, family));
|
|
125
|
+
}
|
|
126
|
+
/** Every family the library can draw now, for a font box. */
|
|
127
|
+
families() {
|
|
128
|
+
return libraryCall(Method.FontFamilies);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Kind } from '../transport/protocol.js';
|
|
2
|
+
const roles = {
|
|
3
|
+
document: 'document',
|
|
4
|
+
page: 'region',
|
|
5
|
+
paragraph: 'paragraph',
|
|
6
|
+
heading: 'heading',
|
|
7
|
+
list: 'list',
|
|
8
|
+
listItem: 'listitem',
|
|
9
|
+
table: 'table',
|
|
10
|
+
row: 'row',
|
|
11
|
+
cell: 'cell',
|
|
12
|
+
image: 'img',
|
|
13
|
+
link: 'link',
|
|
14
|
+
textBox: 'group',
|
|
15
|
+
note: 'note',
|
|
16
|
+
header: 'region',
|
|
17
|
+
footer: 'region',
|
|
18
|
+
};
|
|
19
|
+
const hidden = 'position:absolute;left:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;';
|
|
20
|
+
function build(node) {
|
|
21
|
+
const e = document.createElement('div');
|
|
22
|
+
e.setAttribute('role', roles[node.role] ?? 'group');
|
|
23
|
+
if (node.name)
|
|
24
|
+
e.setAttribute('aria-label', node.name);
|
|
25
|
+
if (node.level !== undefined)
|
|
26
|
+
e.setAttribute('aria-level', String(node.level));
|
|
27
|
+
const marks = node.marks ?? [];
|
|
28
|
+
if (marks.includes('misspelling'))
|
|
29
|
+
e.setAttribute('aria-invalid', 'spelling');
|
|
30
|
+
const review = marks.filter((m) => m !== 'misspelling');
|
|
31
|
+
if (review.length)
|
|
32
|
+
e.setAttribute('aria-description', review.join(' '));
|
|
33
|
+
if (node.text)
|
|
34
|
+
e.append(node.text);
|
|
35
|
+
for (const child of node.children)
|
|
36
|
+
e.append(build(child));
|
|
37
|
+
return e;
|
|
38
|
+
}
|
|
39
|
+
export function startA11y(s, dom, ledger) {
|
|
40
|
+
const tree = ledger.node(dom.layer, document.createElement('div'));
|
|
41
|
+
tree.dataset.twip = 'a11y';
|
|
42
|
+
tree.style.cssText = hidden;
|
|
43
|
+
const live = ledger.node(dom.layer, document.createElement('div'));
|
|
44
|
+
live.dataset.twip = 'live';
|
|
45
|
+
live.setAttribute('aria-live', 'polite');
|
|
46
|
+
live.setAttribute('role', 'status');
|
|
47
|
+
live.style.cssText = hidden;
|
|
48
|
+
const onMessage = (m) => {
|
|
49
|
+
const r = m.payload;
|
|
50
|
+
if (m.kind === Kind.HostRequest && r.type === 'a11y')
|
|
51
|
+
tree.replaceChildren(build(r.tree));
|
|
52
|
+
};
|
|
53
|
+
s.hostListeners.add(onMessage);
|
|
54
|
+
s.disposers.push(() => s.hostListeners.delete(onMessage));
|
|
55
|
+
// A repeated announcement is read again: the region empties first.
|
|
56
|
+
let cancel;
|
|
57
|
+
s.disposers.push(s.on('announcement', ({ text }) => {
|
|
58
|
+
live.textContent = '';
|
|
59
|
+
cancel?.();
|
|
60
|
+
cancel = ledger.timeout(() => (live.textContent = text), 50);
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Op } from '../commands.js';
|
|
2
|
+
import type { Session } from '../editor.js';
|
|
3
|
+
import type { View } from '../services/view.js';
|
|
4
|
+
import { type FrameInfo } from '../transport/protocol.js';
|
|
5
|
+
import type { ClientPoint, CommentId, ElementId } from '../types.js';
|
|
6
|
+
import type { Dom, Ledger } from './dom.js';
|
|
7
|
+
import type { Frames } from './frames.js';
|
|
8
|
+
export interface Chrome {
|
|
9
|
+
view: View;
|
|
10
|
+
balloonLayer: HTMLElement;
|
|
11
|
+
attachBalloon(thread: CommentId, card: HTMLElement): () => void;
|
|
12
|
+
setMarkupPaneWidth(width: number): Promise<void>;
|
|
13
|
+
hitTest(at: ClientPoint): Promise<{
|
|
14
|
+
element: ElementId | null;
|
|
15
|
+
editable: boolean;
|
|
16
|
+
}>;
|
|
17
|
+
dragOver(at: ClientPoint): void;
|
|
18
|
+
dragLeave(): void;
|
|
19
|
+
drop(at: ClientPoint, op: Op): Promise<void>;
|
|
20
|
+
/** After each frame: the visible pages, and the balloons for the anchors it reported. */
|
|
21
|
+
framed(info: FrameInfo): void;
|
|
22
|
+
/** The engine draws the balloons' leader lines (the `drawLeaders` mount option). */
|
|
23
|
+
drawLeaders(on: boolean): void;
|
|
24
|
+
}
|
|
25
|
+
export declare function startChrome(s: Session, dom: Dom, ledger: Ledger, frames: Frames, report: (e: unknown) => void, toViewport: (at: ClientPoint) => {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
}): Chrome;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { stackBalloons } from '../review.js';
|
|
2
|
+
import { Method } from '../transport/protocol.js';
|
|
3
|
+
/** The space between a page and its balloons, and between balloons, CSS px. */
|
|
4
|
+
const GAP = 8;
|
|
5
|
+
export function startChrome(s, dom, ledger, frames, report, toViewport) {
|
|
6
|
+
const layer = ledger.node(dom.content, document.createElement('div'));
|
|
7
|
+
layer.dataset.twip = 'balloons';
|
|
8
|
+
layer.style.cssText = 'position:absolute;inset:0;pointer-events:none;';
|
|
9
|
+
// Balloons: each attached card beside its thread's anchor, stacked, and placed again after every anchors change.
|
|
10
|
+
const cards = new Map();
|
|
11
|
+
let anchors = [];
|
|
12
|
+
let pane = 0;
|
|
13
|
+
let leaders = false;
|
|
14
|
+
const place = () => {
|
|
15
|
+
const shown = anchors.filter((a) => cards.has(a.thread));
|
|
16
|
+
for (const [thread, card] of cards)
|
|
17
|
+
card.style.display = shown.some((a) => a.thread === thread) ? '' : 'none';
|
|
18
|
+
if (!shown.length && !cards.size)
|
|
19
|
+
return;
|
|
20
|
+
const heights = shown.map((a) => cards.get(a.thread).offsetHeight);
|
|
21
|
+
const tops = stackBalloons(shown, heights, GAP);
|
|
22
|
+
const placements = shown.map((a, i) => {
|
|
23
|
+
const left = pane > 0 ? frames.info.contentWidth - pane + GAP : a.pageEdge + GAP;
|
|
24
|
+
const top = tops[i];
|
|
25
|
+
const card = cards.get(a.thread);
|
|
26
|
+
card.style.left = `${left}px`;
|
|
27
|
+
card.style.top = `${top}px`;
|
|
28
|
+
return { thread: a.thread, top, left, leader: [a.start, { x: a.pageEdge, y: a.start.y }, { x: left, y: top + Math.min(heights[i], a.lineBottom - a.lineTop) / 2 }] };
|
|
29
|
+
});
|
|
30
|
+
s.emit('balloonsLaidOut', placements);
|
|
31
|
+
if (leaders)
|
|
32
|
+
s.call(Method.HostSetLeaders, placements.map((p) => p.leader)).then(() => frames.request(), report);
|
|
33
|
+
};
|
|
34
|
+
s.disposers.push(s.on('commentAnchors', (a) => {
|
|
35
|
+
anchors = a;
|
|
36
|
+
place();
|
|
37
|
+
}));
|
|
38
|
+
// The pages on screen, viewport px from the engine after each frame; client px when read.
|
|
39
|
+
let pages = [];
|
|
40
|
+
const framed = () => {
|
|
41
|
+
if (s.destroyed)
|
|
42
|
+
return;
|
|
43
|
+
s.call(Method.HostVisiblePages).then((p) => (pages = p), report);
|
|
44
|
+
};
|
|
45
|
+
// The caret's blink follows the platform's reduced-motion setting until the application sets it.
|
|
46
|
+
const blink = async (ms, stopAfterMs) => {
|
|
47
|
+
await s.call(Method.HostSetCaretBlink, { intervalMs: ms > 0 ? ms : null, stopAfterMs: stopAfterMs ?? null });
|
|
48
|
+
};
|
|
49
|
+
let motion = globalThis.matchMedia?.('(prefers-reduced-motion: reduce)');
|
|
50
|
+
if (motion) {
|
|
51
|
+
const follow = () => {
|
|
52
|
+
if (motion)
|
|
53
|
+
blink(motion.matches ? 0 : 530).catch(report);
|
|
54
|
+
};
|
|
55
|
+
ledger.listen(motion, 'change', follow);
|
|
56
|
+
if (motion.matches)
|
|
57
|
+
follow();
|
|
58
|
+
}
|
|
59
|
+
// The overlay colours: the engine's, or the system's in forced-colours mode until the application sets them.
|
|
60
|
+
const defaults = { caret: '#000000', selection: '#B3D7FF', object: '#606060', drop: '#404040', marks: '#2F6FB5', leaders: '#767676' };
|
|
61
|
+
let overlay = defaults;
|
|
62
|
+
const system = (css) => {
|
|
63
|
+
const probe = Object.assign(document.createElement('span'), { hidden: true });
|
|
64
|
+
probe.style.color = css;
|
|
65
|
+
document.body.append(probe);
|
|
66
|
+
const [r, g, b] = (getComputedStyle(probe).color.match(/\d+/g) ?? ['0', '0', '0']).map(Number);
|
|
67
|
+
probe.remove();
|
|
68
|
+
return `#${[r, g, b].map((v) => (v ?? 0).toString(16).padStart(2, '0')).join('').toUpperCase()}`;
|
|
69
|
+
};
|
|
70
|
+
const paint = (colors) => s.call(Method.HostSetOverlayPalette, colors).then(() => frames.request());
|
|
71
|
+
let forced = globalThis.matchMedia?.('(forced-colors: active)');
|
|
72
|
+
if (forced) {
|
|
73
|
+
const follow = () => {
|
|
74
|
+
if (!forced)
|
|
75
|
+
return;
|
|
76
|
+
const text = system('CanvasText');
|
|
77
|
+
overlay = forced.matches ? { caret: text, selection: system('Highlight'), object: text, drop: text, marks: system('GrayText'), leaders: system('GrayText') } : defaults;
|
|
78
|
+
paint(overlay).catch(report);
|
|
79
|
+
};
|
|
80
|
+
ledger.listen(forced, 'change', follow);
|
|
81
|
+
if (forced.matches)
|
|
82
|
+
follow();
|
|
83
|
+
}
|
|
84
|
+
const set = async (method, value) => {
|
|
85
|
+
await s.call(method, value);
|
|
86
|
+
frames.request();
|
|
87
|
+
};
|
|
88
|
+
const view = {
|
|
89
|
+
revealSelection: () => s.call(Method.HostRevealSelection),
|
|
90
|
+
setBackground: async (color) => {
|
|
91
|
+
await set(Method.HostSetBackground, color);
|
|
92
|
+
dom.scroller.style.background = color;
|
|
93
|
+
},
|
|
94
|
+
visiblePages: () => {
|
|
95
|
+
const r = dom.canvas.getBoundingClientRect();
|
|
96
|
+
return pages.map(({ page, rect }) => ({ page, rect: { ...rect, left: rect.left + r.left, top: rect.top + r.top } }));
|
|
97
|
+
},
|
|
98
|
+
frameStats: () => s.call(Method.HostFrameStats),
|
|
99
|
+
setOverlayColors: (colors) => {
|
|
100
|
+
forced = undefined;
|
|
101
|
+
overlay = { ...overlay, ...colors };
|
|
102
|
+
return paint(overlay);
|
|
103
|
+
},
|
|
104
|
+
setCaretBlink: (ms, stopAfterMs) => {
|
|
105
|
+
motion = undefined;
|
|
106
|
+
return blink(ms, stopAfterMs);
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
view,
|
|
111
|
+
balloonLayer: layer,
|
|
112
|
+
drawLeaders: (on) => {
|
|
113
|
+
leaders = on;
|
|
114
|
+
if (on)
|
|
115
|
+
place();
|
|
116
|
+
else
|
|
117
|
+
s.call(Method.HostSetLeaders, []).then(() => frames.request(), report);
|
|
118
|
+
},
|
|
119
|
+
attachBalloon: (thread, card) => {
|
|
120
|
+
cards.get(thread)?.remove();
|
|
121
|
+
card.style.position = 'absolute';
|
|
122
|
+
card.style.pointerEvents = 'auto';
|
|
123
|
+
cards.set(thread, card);
|
|
124
|
+
layer.append(card);
|
|
125
|
+
place();
|
|
126
|
+
return () => {
|
|
127
|
+
if (cards.get(thread) !== card)
|
|
128
|
+
return;
|
|
129
|
+
cards.delete(thread);
|
|
130
|
+
card.remove();
|
|
131
|
+
place();
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
setMarkupPaneWidth: async (width) => {
|
|
135
|
+
await set(Method.HostSetMarkupPaneWidth, width);
|
|
136
|
+
pane = Number.isFinite(width) ? Math.max(0, width) : 0;
|
|
137
|
+
},
|
|
138
|
+
hitTest: (at) => s.call(Method.HostHitTest, toViewport(at)),
|
|
139
|
+
dragOver: (at) => void s.call(Method.HostDragOver, toViewport(at)).then(frames.request, report),
|
|
140
|
+
dragLeave: () => void s.call(Method.HostDragLeave).then(frames.request, report),
|
|
141
|
+
drop: (at, op) => s.commit(Method.HostDrop, { at: toViewport(at), command: op }),
|
|
142
|
+
framed,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Session } from '../editor.js';
|
|
2
|
+
import type { Dom, Ledger } from './dom.js';
|
|
3
|
+
/** The clipboard of a mounted editor. */
|
|
4
|
+
export interface Clipboard {
|
|
5
|
+
/** `commands.paste`: reads the system clipboard (`not-allowed` when the browser refuses), then pastes. */
|
|
6
|
+
paste(options?: {
|
|
7
|
+
as?: 'formatted' | 'text';
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export declare function startClipboard(s: Session, dom: Dom, input: HTMLTextAreaElement, ledger: Ledger, report: (e: unknown) => void, toViewport: (e: {
|
|
11
|
+
clientX: number;
|
|
12
|
+
clientY: number;
|
|
13
|
+
}) => {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
}): Clipboard;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { EditorError } from '../errors.js';
|
|
2
|
+
import { Kind, Method } from '../transport/protocol.js';
|
|
3
|
+
/** The last copy on the page: the system clipboard has no place for its token, so a paste brings it back (the engine
|
|
4
|
+
* pastes the copy it names only while the text is still that copy's). */
|
|
5
|
+
let lastCopy;
|
|
6
|
+
function escapeHtml(text) {
|
|
7
|
+
return text.replace(/[&<>]/g, (c) => `&${{ '&': 'amp', '<': 'lt', '>': 'gt' }[c]};`).replace(/\n/g, '<br>');
|
|
8
|
+
}
|
|
9
|
+
export function startClipboard(s, dom, input, ledger, report, toViewport) {
|
|
10
|
+
const write = (data) => {
|
|
11
|
+
// Without `ClipboardItem` (older Firefox) the event's synchronous text is the copy.
|
|
12
|
+
if (typeof ClipboardItem === 'undefined' || !navigator.clipboard?.write)
|
|
13
|
+
return;
|
|
14
|
+
const blob = (type, pick) => data.then((d) => {
|
|
15
|
+
if (!d)
|
|
16
|
+
throw new Error('nothing was copied');
|
|
17
|
+
return new Blob([pick(d)], { type });
|
|
18
|
+
});
|
|
19
|
+
let item;
|
|
20
|
+
try {
|
|
21
|
+
item = new ClipboardItem({
|
|
22
|
+
'text/plain': blob('text/plain', (d) => d.text),
|
|
23
|
+
'text/html': blob('text/html', (d) => d.html ?? escapeHtml(d.text)),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// A `ClipboardItem` that takes no promises: the synchronous text stands.
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// With nothing copied the write fails, and the clipboard keeps what it had.
|
|
31
|
+
navigator.clipboard.write([item]).catch((e) => data.then((d) => d && report(e), () => { }));
|
|
32
|
+
};
|
|
33
|
+
const onMessage = (m) => {
|
|
34
|
+
const r = m.payload;
|
|
35
|
+
if (m.kind !== Kind.HostRequest || r.type !== 'clipboard')
|
|
36
|
+
return;
|
|
37
|
+
// A copy the engine asks for (a menu's).
|
|
38
|
+
lastCopy = r.data;
|
|
39
|
+
write(Promise.resolve(r.data));
|
|
40
|
+
};
|
|
41
|
+
s.hostListeners.add(onMessage);
|
|
42
|
+
s.disposers.push(() => s.hostListeners.delete(onMessage));
|
|
43
|
+
// Queued now, so keys pressed after it run after it.
|
|
44
|
+
const copy = (cut) => s.call(Method.Copy, { cut }).then((d) => {
|
|
45
|
+
if (d)
|
|
46
|
+
lastCopy = d;
|
|
47
|
+
return d;
|
|
48
|
+
});
|
|
49
|
+
const onCopy = (cut) => (e) => {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
// The engine answers later (in worker mode, from another thread), so the event gets the selection's plain text
|
|
52
|
+
// now, mirrored in the state: it is the copy where the formatted write below can't run.
|
|
53
|
+
const text = s.state.selection.text;
|
|
54
|
+
if (text)
|
|
55
|
+
e.clipboardData?.setData('text/plain', text);
|
|
56
|
+
const data = copy(cut);
|
|
57
|
+
data.catch(report);
|
|
58
|
+
// The write starts inside the event, which keeps the user's activation; the data follows from the engine.
|
|
59
|
+
write(data);
|
|
60
|
+
};
|
|
61
|
+
ledger.listen(input, 'copy', onCopy(false));
|
|
62
|
+
ledger.listen(input, 'cut', onCopy(true));
|
|
63
|
+
const lines = (text) => text.replace(/\r\n/g, '\n');
|
|
64
|
+
const paste = async (data, asText) => {
|
|
65
|
+
const text = lines(data.text);
|
|
66
|
+
if (!text)
|
|
67
|
+
return;
|
|
68
|
+
const internal = lastCopy?.internal;
|
|
69
|
+
await s.call(Method.Paste, { data: { ...data, text, ...(internal ? { internal } : {}) }, asText });
|
|
70
|
+
};
|
|
71
|
+
// Ctrl+Shift+V pastes as text.
|
|
72
|
+
let plain = false;
|
|
73
|
+
ledger.listen(input, 'keydown', (e) => {
|
|
74
|
+
plain = (e.ctrlKey || e.metaKey) && e.shiftKey && e.code === 'KeyV';
|
|
75
|
+
});
|
|
76
|
+
ledger.listen(input, 'paste', (e) => {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
const t = e.clipboardData;
|
|
79
|
+
if (!t)
|
|
80
|
+
return;
|
|
81
|
+
const html = t.getData('text/html');
|
|
82
|
+
paste({ text: t.getData('text/plain'), ...(html ? { html } : {}) }, plain).catch(report);
|
|
83
|
+
plain = false;
|
|
84
|
+
});
|
|
85
|
+
// External drags: text and images from outside the editor, shown with the drop caret.
|
|
86
|
+
const external = (e) => !!e.dataTransfer && ['Files', 'text/plain', 'text/html'].some((t) => e.dataTransfer.types.includes(t));
|
|
87
|
+
ledger.listen(dom.scroller, 'dragover', (e) => {
|
|
88
|
+
if (!external(e))
|
|
89
|
+
return;
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
e.dataTransfer.dropEffect = 'copy';
|
|
92
|
+
s.call(Method.HostDragOver, toViewport(e)).catch(report);
|
|
93
|
+
});
|
|
94
|
+
ledger.listen(dom.scroller, 'dragleave', (e) => {
|
|
95
|
+
if (!external(e) || dom.scroller.contains(e.relatedTarget))
|
|
96
|
+
return;
|
|
97
|
+
s.call(Method.HostDragLeave).catch(report);
|
|
98
|
+
});
|
|
99
|
+
ledger.listen(dom.scroller, 'drop', (e) => {
|
|
100
|
+
if (!external(e))
|
|
101
|
+
return;
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
const t = e.dataTransfer;
|
|
104
|
+
const at = toViewport(e);
|
|
105
|
+
const image = [...t.files].find((f) => f.type.startsWith('image/'));
|
|
106
|
+
const op = image
|
|
107
|
+
? image.arrayBuffer().then((b) => ({ type: 'insertPicture', data: new Uint8Array(b) }))
|
|
108
|
+
: Promise.resolve(t.getData('text/plain') ? { type: 'pasteText', value: t.getData('text/plain') } : null);
|
|
109
|
+
op.then((command) => (command ? s.commit(Method.HostDrop, { at, command }) : s.call(Method.HostDragLeave))).catch(report);
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
paste: async (options) => {
|
|
113
|
+
let items;
|
|
114
|
+
try {
|
|
115
|
+
items = await navigator.clipboard.read();
|
|
116
|
+
}
|
|
117
|
+
catch (cause) {
|
|
118
|
+
throw new EditorError('not-allowed', 'the browser refused to read the clipboard', { cause });
|
|
119
|
+
}
|
|
120
|
+
const read = async (type) => {
|
|
121
|
+
const item = items.find((i) => i.types.includes(type));
|
|
122
|
+
return item ? (await item.getType(type)).text() : '';
|
|
123
|
+
};
|
|
124
|
+
const [text, html] = await Promise.all([read('text/plain'), read('text/html')]);
|
|
125
|
+
await s.guard(() => paste({ text, ...(html ? { html } : {}) }, options?.as === 'text'));
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const CELL_SELECT: {
|
|
2
|
+
url: string;
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
};
|
|
6
|
+
export declare const COLUMN_SELECT: {
|
|
7
|
+
url: string;
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const ROW_SELECT: {
|
|
12
|
+
url: string;
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Generated by bindings/dotnet/Twip.WinForms/Cursors/make-cursors.py: do not edit.
|
|
2
|
+
// The table and margin selection cursors as .cur data URLs, with their hotspots.
|
|
3
|
+
export const CELL_SELECT = { url: 'data:image/x-icon;base64,AAACAAEAICAAAB0AAwCoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///9Q////UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////YP//////////////YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///2D/////YGBg/2BgYP//////////YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///9Q/////2BgYP8AAAD/AAAA/2BgYP//////////YAAAAAAAAAAAAAAAAAAAAAD///9Q////QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///1D/////YGBg/wAAAP8AAAD/AAAA/2BgYP//////////YAAAAAAAAAAA////YP////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///2D/////YGBg/wAAAP8AAAD/AAAA/2BgYP//////////YP///2D/////z8/P/////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///2D/////YGBg/wAAAP8AAAD/AAAA/2BgYP///////////2BgYP+AgID/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///2D/////YGBg/wAAAP8AAAD/AAAA/1BQUP9QUFD/AAAA/4CAgP////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///2D/////YGBg/wAAAP8AAAD/AAAA/wAAAP8AAAD/gICA/////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///2D/////UFBQ/wAAAP8AAAD/AAAA/wAAAP+AgID/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////YP////9QUFD/AAAA/wAAAP8AAAD/AAAA/4CAgP////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///2D/////YGBg/wAAAP8AAAD/AAAA/wAAAP8AAAD/gICA/////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///9Q/////8/Pz/+AgID/gICA/4CAgP+AgID/gICA/4CAgP+/v7//////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///0D///+A////gP///4D///+A////gP///4D///+A////gP///4D///9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', x: 29, y: 3 };
|
|
4
|
+
export const COLUMN_SELECT = { url: 'data:image/x-icon;base64,AAACAAEAICAAABAADgCoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////UP///1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///2D//////////////2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///9g/////2BgYP9gYGD//////////2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////YP////9gYGD/AAAA/wAAAP9gYGD//////////2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///2D/////YGBg/wAAAP8AAAD/AAAA/wAAAP9gYGD//////////2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///9Q/////8/Pz/+AgID/QEBA/wAAAP8AAAD/QEBA/4CAgP/Pz8///////////1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///0D///+A////gP///7+AgID/AAAA/wAAAP+AgID/////v////4D///+A////QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////gICAgP8AAAD/AAAA/4CAgP////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+AgICA/wAAAP8AAAD/gICA/////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///4CAgID/AAAA/wAAAP+AgID/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////gICAgP8AAAD/AAAA/4CAgP////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+Av7+//4CAgP+AgID/v7+//////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///0D///+A////gP///4D///+A////QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', x: 16, y: 14 };
|
|
5
|
+
export const ROW_SELECT = { url: 'data:image/x-icon;base64,AAACAAEAICAAAB0AAwCoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAA3xAQEP8AAADfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAQEBD//////yAgIP8AAABgAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+fn5//n5+f/wAAAN8AAAAAAAAAAAAAAAAAAABgAAAA/wAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYCAgIP//////ICAg/wAAAGAAAAAAAAAAYAAAAP8wMDD/AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA35+fn/+fn5//AAAA3wAAAGAAAAD/n5+f/4CAgP8AAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgICAg//////8gICD/AAAA/5+fn///////gICA/wAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAgAAAAIAAAACAAAAAgAAAAIAAAAD/n5+f/7+/v/+vr6////////////+AgID/AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAD/MDAw/4CAgP+AgID/gICA/4CAgP+/v7///////////////////////4CAgP8AAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAD/n5+f////////////////////////////////////////////gICA/wAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAD/n5+f//////////////////////////////////////+AgID/AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAD/n5+f/////////////////////////////////4CAgP8AAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAD/n5+f////////////////////////////gICA/wAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAD/n5+f//////////////////////+AgID/AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAD/n5+f/////////////////4CAgP8AAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAD/n5+f////////////gICA/wAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAD/n5+f//////+AgID/AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAD/n5+f/4CAgP8AAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAD/MDAw/wAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAD/AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', x: 29, y: 3 };
|