tycho-components 0.22.6 → 0.23.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/dist/configs/index.d.ts +2 -0
- package/dist/configs/index.js +1 -0
- package/dist/configs/tour/anchorResolver.d.ts +4 -0
- package/dist/configs/tour/anchorResolver.js +91 -0
- package/dist/configs/tour/driverTour.d.ts +5 -0
- package/dist/configs/tour/driverTour.js +143 -0
- package/dist/configs/tour/index.d.ts +5 -0
- package/dist/configs/tour/index.js +4 -0
- package/dist/configs/tour/knowledgeBase.d.ts +6 -0
- package/dist/configs/tour/knowledgeBase.js +50 -0
- package/dist/configs/tour/style.scss +22 -0
- package/dist/configs/tour/types.d.ts +45 -0
- package/dist/configs/tour/types.js +1 -0
- package/dist/configs/tour/useDriverTour.d.ts +2 -0
- package/dist/configs/tour/useDriverTour.js +95 -0
- package/dist/features/DocumentDrawer/DocumentExport/DocumentExport.js +2 -2
- package/dist/features/DocumentDrawer/localization/ExportTexts.d.ts +0 -6
- package/dist/features/DocumentDrawer/localization/ExportTexts.js +3 -9
- package/dist/features/DocumentDrawer/types/Export.d.ts +2 -2
- package/dist/features/DocumentDrawer/types/Export.js +2 -2
- package/package.json +4 -2
package/dist/configs/index.d.ts
CHANGED
|
@@ -25,3 +25,5 @@ export { useCorpusUtils } from './useCorpusUtils';
|
|
|
25
25
|
export { useLoggedUtils } from './useLoggedUtils';
|
|
26
26
|
export { useMessageUtils } from './useMessageUtils';
|
|
27
27
|
export { useTourUtils } from './useTourUtils';
|
|
28
|
+
export { useDriverTour, loadKnowledgeBase, defaultMatchPathnames, buildAnchorMap, flattenWorkflowSteps, getWorkflowsForPath, buildDriveSteps, createTourDriver, hasMatchingWorkflow, resolveAnchor, resolveAnchorById, waitForSelector, } from './tour';
|
|
29
|
+
export type { AnchorSelector, KnowledgeBase, PopoverSide, ResolveKnowledgeBaseUrl, TourAnchor, TourButtonLabels, TourStep, TourWorkflow, UseDriverTourOptions, } from './tour';
|
package/dist/configs/index.js
CHANGED
|
@@ -14,3 +14,4 @@ export { useCorpusUtils } from './useCorpusUtils';
|
|
|
14
14
|
export { useLoggedUtils } from './useLoggedUtils';
|
|
15
15
|
export { useMessageUtils } from './useMessageUtils';
|
|
16
16
|
export { useTourUtils } from './useTourUtils';
|
|
17
|
+
export { useDriverTour, loadKnowledgeBase, defaultMatchPathnames, buildAnchorMap, flattenWorkflowSteps, getWorkflowsForPath, buildDriveSteps, createTourDriver, hasMatchingWorkflow, resolveAnchor, resolveAnchorById, waitForSelector, } from './tour';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AnchorSelector, TourAnchor } from './types';
|
|
2
|
+
export declare function resolveAnchor(anchor: TourAnchor, anchorFallbacks?: Record<string, AnchorSelector>): Element | null;
|
|
3
|
+
export declare function resolveAnchorById(anchorId: string, anchorMap: Map<string, TourAnchor>, anchorFallbacks?: Record<string, AnchorSelector>): Element | null;
|
|
4
|
+
export declare function waitForSelector(selector: string, timeoutMs?: number, intervalMs?: number): Promise<Element | null>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const INTERACTIVE_SELECTOR = 'button, a[href], [role="button"], .ds-button, .ds-icon-button, input[type="button"], input[type="submit"]';
|
|
2
|
+
function normalizeText(value) {
|
|
3
|
+
return value.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
4
|
+
}
|
|
5
|
+
function resolveCssSelector(selector) {
|
|
6
|
+
try {
|
|
7
|
+
return document.querySelector(selector);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function resolveAriaSelector(label) {
|
|
14
|
+
const byAria = document.querySelector(`[aria-label="${label}"]`);
|
|
15
|
+
if (byAria)
|
|
16
|
+
return byAria;
|
|
17
|
+
const byTitle = document.querySelector(`[title="${label}"]`);
|
|
18
|
+
if (byTitle)
|
|
19
|
+
return byTitle;
|
|
20
|
+
const normalizedLabel = normalizeText(label);
|
|
21
|
+
const candidates = document.querySelectorAll(INTERACTIVE_SELECTOR);
|
|
22
|
+
for (const candidate of candidates) {
|
|
23
|
+
const aria = candidate.getAttribute('aria-label');
|
|
24
|
+
const title = candidate.getAttribute('title');
|
|
25
|
+
if ((aria && normalizeText(aria) === normalizedLabel) ||
|
|
26
|
+
(title && normalizeText(title) === normalizedLabel)) {
|
|
27
|
+
return candidate;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function resolveTextSelector(expected, mode) {
|
|
33
|
+
const normalizedExpected = normalizeText(expected);
|
|
34
|
+
const candidates = document.querySelectorAll(INTERACTIVE_SELECTOR);
|
|
35
|
+
for (const candidate of candidates) {
|
|
36
|
+
const text = normalizeText(candidate.textContent ?? '');
|
|
37
|
+
const matches = mode === 'exact'
|
|
38
|
+
? text === normalizedExpected
|
|
39
|
+
: text.startsWith(normalizedExpected);
|
|
40
|
+
if (matches)
|
|
41
|
+
return candidate;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function resolveSelector(selector) {
|
|
46
|
+
if (selector.startsWith('aria:')) {
|
|
47
|
+
return resolveAriaSelector(selector.slice(5));
|
|
48
|
+
}
|
|
49
|
+
if (selector.startsWith('textPrefix:')) {
|
|
50
|
+
return resolveTextSelector(selector.slice(11), 'prefix');
|
|
51
|
+
}
|
|
52
|
+
if (selector.startsWith('text:')) {
|
|
53
|
+
return resolveTextSelector(selector.slice(5), 'exact');
|
|
54
|
+
}
|
|
55
|
+
return resolveCssSelector(selector);
|
|
56
|
+
}
|
|
57
|
+
function resolveSelectorList(selectors) {
|
|
58
|
+
const list = Array.isArray(selectors) ? selectors : [selectors];
|
|
59
|
+
for (const selector of list) {
|
|
60
|
+
const element = resolveSelector(selector);
|
|
61
|
+
if (element)
|
|
62
|
+
return element;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
export function resolveAnchor(anchor, anchorFallbacks) {
|
|
67
|
+
const element = resolveSelectorList(anchor.selector);
|
|
68
|
+
if (element)
|
|
69
|
+
return element;
|
|
70
|
+
const fallback = anchorFallbacks?.[anchor.id];
|
|
71
|
+
if (fallback) {
|
|
72
|
+
return resolveSelectorList(fallback);
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
export function resolveAnchorById(anchorId, anchorMap, anchorFallbacks) {
|
|
77
|
+
const anchor = anchorMap.get(anchorId);
|
|
78
|
+
if (!anchor)
|
|
79
|
+
return null;
|
|
80
|
+
return resolveAnchor(anchor, anchorFallbacks);
|
|
81
|
+
}
|
|
82
|
+
export async function waitForSelector(selector, timeoutMs = 10000, intervalMs = 100) {
|
|
83
|
+
const deadline = Date.now() + timeoutMs;
|
|
84
|
+
while (Date.now() < deadline) {
|
|
85
|
+
const element = resolveCssSelector(selector);
|
|
86
|
+
if (element)
|
|
87
|
+
return element;
|
|
88
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type DriveStep, type Driver } from 'driver.js';
|
|
2
|
+
import type { AnchorSelector, KnowledgeBase, TourAnchor, TourButtonLabels } from './types';
|
|
3
|
+
export declare function buildDriveSteps(kb: KnowledgeBase, pathnames: string | string[], anchorFallbacks?: Record<string, AnchorSelector>): DriveStep[];
|
|
4
|
+
export declare function hasMatchingWorkflow(kb: KnowledgeBase, pathnames: string | string[]): boolean;
|
|
5
|
+
export declare function createTourDriver(labels: TourButtonLabels, onDestroyed: () => void, anchorMap: Map<string, TourAnchor>, anchorFallbacks?: Record<string, AnchorSelector>): Driver;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { driver } from 'driver.js';
|
|
2
|
+
import { resolveAnchor, resolveAnchorById, waitForSelector, } from './anchorResolver';
|
|
3
|
+
import { buildAnchorMap, flattenWorkflowSteps, getWorkflowsForPath, } from './knowledgeBase';
|
|
4
|
+
const CLICK_DEMO_DELAY_MS = 300;
|
|
5
|
+
function isPanelOpen(panelSelector) {
|
|
6
|
+
return Boolean(document.querySelector(panelSelector));
|
|
7
|
+
}
|
|
8
|
+
function clickElement(element) {
|
|
9
|
+
if (element instanceof HTMLElement)
|
|
10
|
+
element.click();
|
|
11
|
+
}
|
|
12
|
+
function closePanel(anchor, anchorMap, anchorFallbacks) {
|
|
13
|
+
const panelSelector = anchor.panelSelector;
|
|
14
|
+
if (!panelSelector || !isPanelOpen(panelSelector))
|
|
15
|
+
return;
|
|
16
|
+
if (anchor.closeSelector) {
|
|
17
|
+
clickElement(document.querySelector(anchor.closeSelector) ?? undefined);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (anchor.triggerAnchor) {
|
|
21
|
+
clickElement(resolveAnchorById(anchor.triggerAnchor, anchorMap, anchorFallbacks) ??
|
|
22
|
+
undefined);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function findPanelAnchor(panelSelector, anchorMap) {
|
|
26
|
+
return [...anchorMap.values()].find((a) => a.panelSelector === panelSelector);
|
|
27
|
+
}
|
|
28
|
+
function closeGuardPanel(guardClickPanel, anchorMap, anchorFallbacks) {
|
|
29
|
+
if (!guardClickPanel || !isPanelOpen(guardClickPanel))
|
|
30
|
+
return;
|
|
31
|
+
const panelAnchor = findPanelAnchor(guardClickPanel, anchorMap);
|
|
32
|
+
if (panelAnchor) {
|
|
33
|
+
closePanel(panelAnchor, anchorMap, anchorFallbacks);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function closeOpenPanels(anchorMap, anchorFallbacks) {
|
|
37
|
+
for (const anchor of anchorMap.values()) {
|
|
38
|
+
if (anchor.panelSelector && isPanelOpen(anchor.panelSelector)) {
|
|
39
|
+
closePanel(anchor, anchorMap, anchorFallbacks);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function chainPopoverClick(driveStep, key, beforeAdvance, advance) {
|
|
44
|
+
const popover = driveStep.popover ?? {};
|
|
45
|
+
const existing = popover[key];
|
|
46
|
+
const handler = (element, step, opts) => {
|
|
47
|
+
beforeAdvance();
|
|
48
|
+
if (existing) {
|
|
49
|
+
existing(element, step, opts);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
advance(opts);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
driveStep.popover = {
|
|
56
|
+
...popover,
|
|
57
|
+
[key]: handler,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function attachClickDemoHooks(driveStep, anchor, anchorMap, anchorFallbacks) {
|
|
61
|
+
driveStep.onHighlighted = (element, _step, opts) => {
|
|
62
|
+
window.setTimeout(() => {
|
|
63
|
+
if (anchor.guardClickPanel && isPanelOpen(anchor.guardClickPanel)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
clickElement(element);
|
|
67
|
+
opts.driver.refresh();
|
|
68
|
+
}, CLICK_DEMO_DELAY_MS);
|
|
69
|
+
};
|
|
70
|
+
chainPopoverClick(driveStep, 'onPrevClick', () => closeGuardPanel(anchor.guardClickPanel, anchorMap, anchorFallbacks), (opts) => opts.driver.movePrevious());
|
|
71
|
+
}
|
|
72
|
+
function attachPanelStepHooks(driveStep, anchor, anchorMap, anchorFallbacks) {
|
|
73
|
+
const { panelSelector, triggerAnchor } = anchor;
|
|
74
|
+
if (!panelSelector || !triggerAnchor)
|
|
75
|
+
return;
|
|
76
|
+
driveStep.element = () => resolveAnchor(anchor, anchorFallbacks);
|
|
77
|
+
driveStep.onHighlightStarted = async (_element, _step, opts) => {
|
|
78
|
+
if (!isPanelOpen(panelSelector)) {
|
|
79
|
+
clickElement(resolveAnchorById(triggerAnchor, anchorMap, anchorFallbacks) ?? undefined);
|
|
80
|
+
}
|
|
81
|
+
await waitForSelector(panelSelector);
|
|
82
|
+
opts.driver.refresh();
|
|
83
|
+
};
|
|
84
|
+
const close = () => closePanel(anchor, anchorMap, anchorFallbacks);
|
|
85
|
+
chainPopoverClick(driveStep, 'onNextClick', close, (opts) => opts.driver.moveNext());
|
|
86
|
+
chainPopoverClick(driveStep, 'onPrevClick', close, (opts) => opts.driver.movePrevious());
|
|
87
|
+
}
|
|
88
|
+
export function buildDriveSteps(kb, pathnames, anchorFallbacks) {
|
|
89
|
+
const workflows = getWorkflowsForPath(kb, pathnames);
|
|
90
|
+
const steps = flattenWorkflowSteps(workflows);
|
|
91
|
+
const anchorMap = buildAnchorMap(kb);
|
|
92
|
+
return steps.map((step) => {
|
|
93
|
+
const driveStep = {
|
|
94
|
+
popover: {
|
|
95
|
+
title: step.title,
|
|
96
|
+
description: step.body,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
if (step.anchor) {
|
|
100
|
+
const anchor = anchorMap.get(step.anchor);
|
|
101
|
+
if (!anchor)
|
|
102
|
+
return driveStep;
|
|
103
|
+
if (anchor.resolveAtHighlight) {
|
|
104
|
+
attachPanelStepHooks(driveStep, anchor, anchorMap, anchorFallbacks);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const element = resolveAnchorById(step.anchor, anchorMap, anchorFallbacks);
|
|
108
|
+
if (element) {
|
|
109
|
+
driveStep.element = element;
|
|
110
|
+
if (anchor.click) {
|
|
111
|
+
attachClickDemoHooks(driveStep, anchor, anchorMap, anchorFallbacks);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (anchor.popoverSide) {
|
|
116
|
+
driveStep.popover = {
|
|
117
|
+
...driveStep.popover,
|
|
118
|
+
side: anchor.popoverSide,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return driveStep;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
export function hasMatchingWorkflow(kb, pathnames) {
|
|
126
|
+
return getWorkflowsForPath(kb, pathnames).length > 0;
|
|
127
|
+
}
|
|
128
|
+
export function createTourDriver(labels, onDestroyed, anchorMap, anchorFallbacks) {
|
|
129
|
+
return driver({
|
|
130
|
+
showProgress: true,
|
|
131
|
+
allowClose: true,
|
|
132
|
+
smoothScroll: true,
|
|
133
|
+
overlayOpacity: 0.5,
|
|
134
|
+
popoverClass: 'tycho-tour-popover',
|
|
135
|
+
nextBtnText: labels.next,
|
|
136
|
+
prevBtnText: labels.previous,
|
|
137
|
+
doneBtnText: labels.done,
|
|
138
|
+
onDestroyed: () => {
|
|
139
|
+
closeOpenPanels(anchorMap, anchorFallbacks);
|
|
140
|
+
onDestroyed();
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { useDriverTour } from './useDriverTour';
|
|
2
|
+
export { buildAnchorMap, defaultMatchPathnames, flattenWorkflowSteps, getWorkflowsForPath, loadKnowledgeBase, } from './knowledgeBase';
|
|
3
|
+
export { buildDriveSteps, createTourDriver, hasMatchingWorkflow, } from './driverTour';
|
|
4
|
+
export { resolveAnchor, resolveAnchorById, waitForSelector, } from './anchorResolver';
|
|
5
|
+
export type { AnchorSelector, KnowledgeBase, PopoverSide, ResolveKnowledgeBaseUrl, TourAnchor, TourButtonLabels, TourStep, TourWorkflow, UseDriverTourOptions, } from './types';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { useDriverTour } from './useDriverTour';
|
|
2
|
+
export { buildAnchorMap, defaultMatchPathnames, flattenWorkflowSteps, getWorkflowsForPath, loadKnowledgeBase, } from './knowledgeBase';
|
|
3
|
+
export { buildDriveSteps, createTourDriver, hasMatchingWorkflow, } from './driverTour';
|
|
4
|
+
export { resolveAnchor, resolveAnchorById, waitForSelector, } from './anchorResolver';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { KnowledgeBase, TourAnchor, TourStep, TourWorkflow } from './types';
|
|
2
|
+
export declare function loadKnowledgeBase(url: string): Promise<KnowledgeBase>;
|
|
3
|
+
export declare function defaultMatchPathnames(routerPathname: string): string[];
|
|
4
|
+
export declare function getWorkflowsForPath(kb: KnowledgeBase, pathname: string | string[]): TourWorkflow[];
|
|
5
|
+
export declare function flattenWorkflowSteps(workflows: TourWorkflow[]): TourStep[];
|
|
6
|
+
export declare function buildAnchorMap(kb: KnowledgeBase): Map<string, TourAnchor>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const cachedByUrl = new Map();
|
|
2
|
+
const loadPromiseByUrl = new Map();
|
|
3
|
+
export async function loadKnowledgeBase(url) {
|
|
4
|
+
const cached = cachedByUrl.get(url);
|
|
5
|
+
if (cached)
|
|
6
|
+
return cached;
|
|
7
|
+
const existingPromise = loadPromiseByUrl.get(url);
|
|
8
|
+
if (existingPromise)
|
|
9
|
+
return existingPromise;
|
|
10
|
+
const loadPromise = fetch(url)
|
|
11
|
+
.then((response) => {
|
|
12
|
+
if (!response.ok) {
|
|
13
|
+
throw new Error(`Failed to load tour knowledge base (${response.status})`);
|
|
14
|
+
}
|
|
15
|
+
return response.json();
|
|
16
|
+
})
|
|
17
|
+
.then((data) => {
|
|
18
|
+
cachedByUrl.set(url, data);
|
|
19
|
+
return data;
|
|
20
|
+
})
|
|
21
|
+
.catch((error) => {
|
|
22
|
+
loadPromiseByUrl.delete(url);
|
|
23
|
+
throw error;
|
|
24
|
+
});
|
|
25
|
+
loadPromiseByUrl.set(url, loadPromise);
|
|
26
|
+
return loadPromise;
|
|
27
|
+
}
|
|
28
|
+
export function defaultMatchPathnames(routerPathname) {
|
|
29
|
+
const windowPath = window.location.pathname;
|
|
30
|
+
const trimmed = windowPath.length > 1 && windowPath.endsWith('/')
|
|
31
|
+
? windowPath.slice(0, -1)
|
|
32
|
+
: windowPath;
|
|
33
|
+
const candidates = new Set([
|
|
34
|
+
windowPath,
|
|
35
|
+
trimmed,
|
|
36
|
+
`${trimmed}/`,
|
|
37
|
+
routerPathname,
|
|
38
|
+
]);
|
|
39
|
+
return [...candidates];
|
|
40
|
+
}
|
|
41
|
+
export function getWorkflowsForPath(kb, pathname) {
|
|
42
|
+
const pathnames = Array.isArray(pathname) ? pathname : [pathname];
|
|
43
|
+
return kb.workflows.filter((workflow) => pathnames.some((p) => new RegExp(workflow.matchRoute).test(p)));
|
|
44
|
+
}
|
|
45
|
+
export function flattenWorkflowSteps(workflows) {
|
|
46
|
+
return workflows.flatMap((workflow) => workflow.steps);
|
|
47
|
+
}
|
|
48
|
+
export function buildAnchorMap(kb) {
|
|
49
|
+
return new Map(kb.anchors.map((anchor) => [anchor.id, anchor]));
|
|
50
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@import 'driver.js/dist/driver.css';
|
|
2
|
+
|
|
3
|
+
.driver-popover.tycho-tour-popover {
|
|
4
|
+
max-width: 500px;
|
|
5
|
+
padding: 32px 32px 16px;
|
|
6
|
+
|
|
7
|
+
.driver-popover-title {
|
|
8
|
+
@include subtitle-small-2;
|
|
9
|
+
color: var(--text-primary);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.driver-popover-description {
|
|
13
|
+
@include body-medium-1;
|
|
14
|
+
color: var(--text-secondary);
|
|
15
|
+
margin-top: 16px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.driver-popover-footer button {
|
|
19
|
+
@include label-medium-1;
|
|
20
|
+
text-shadow: none;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type AnchorSelector = string | string[];
|
|
2
|
+
export type PopoverSide = 'top' | 'right' | 'bottom' | 'left';
|
|
3
|
+
export type TourAnchor = {
|
|
4
|
+
id: string;
|
|
5
|
+
selector: AnchorSelector;
|
|
6
|
+
intent?: string;
|
|
7
|
+
needsAddedTo?: string;
|
|
8
|
+
click?: boolean;
|
|
9
|
+
resolveAtHighlight?: boolean;
|
|
10
|
+
triggerAnchor?: string;
|
|
11
|
+
panelSelector?: string;
|
|
12
|
+
closeSelector?: string;
|
|
13
|
+
guardClickPanel?: string;
|
|
14
|
+
popoverSide?: PopoverSide;
|
|
15
|
+
};
|
|
16
|
+
export type TourStep = {
|
|
17
|
+
title: string;
|
|
18
|
+
body: string;
|
|
19
|
+
anchor?: string;
|
|
20
|
+
};
|
|
21
|
+
export type TourWorkflow = {
|
|
22
|
+
id: string;
|
|
23
|
+
title: string;
|
|
24
|
+
matchRoute: string;
|
|
25
|
+
steps: TourStep[];
|
|
26
|
+
};
|
|
27
|
+
export type KnowledgeBase = {
|
|
28
|
+
title: string;
|
|
29
|
+
module: string;
|
|
30
|
+
anchors: TourAnchor[];
|
|
31
|
+
workflows: TourWorkflow[];
|
|
32
|
+
};
|
|
33
|
+
export type TourButtonLabels = {
|
|
34
|
+
next: string;
|
|
35
|
+
previous: string;
|
|
36
|
+
done: string;
|
|
37
|
+
};
|
|
38
|
+
export type ResolveKnowledgeBaseUrl = () => string | Promise<string>;
|
|
39
|
+
export type UseDriverTourOptions = {
|
|
40
|
+
resolveKnowledgeBaseUrl: ResolveKnowledgeBaseUrl;
|
|
41
|
+
reloadDeps?: readonly unknown[];
|
|
42
|
+
anchorFallbacks?: Record<string, AnchorSelector>;
|
|
43
|
+
getMatchPathnames?: (pathname: string) => string[];
|
|
44
|
+
waitForSelector?: string;
|
|
45
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useContext, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { useLocation } from 'react-router-dom';
|
|
4
|
+
import CommonContext from '../CommonContext';
|
|
5
|
+
import Storage from '../Storage';
|
|
6
|
+
import { useTourUtils } from '../useTourUtils';
|
|
7
|
+
import { waitForSelector } from './anchorResolver';
|
|
8
|
+
import { buildDriveSteps, createTourDriver, hasMatchingWorkflow, } from './driverTour';
|
|
9
|
+
import { buildAnchorMap, defaultMatchPathnames, loadKnowledgeBase, } from './knowledgeBase';
|
|
10
|
+
export function useDriverTour(options) {
|
|
11
|
+
const { state } = useContext(CommonContext);
|
|
12
|
+
const { turnOn, turnOff } = useTourUtils();
|
|
13
|
+
const { t } = useTranslation('common');
|
|
14
|
+
const { pathname } = useLocation();
|
|
15
|
+
const [knowledgeBase, setKnowledgeBase] = useState(null);
|
|
16
|
+
const driverRef = useRef(null);
|
|
17
|
+
const autoOpenTriggered = useRef(false);
|
|
18
|
+
const turnOffRef = useRef(turnOff);
|
|
19
|
+
const labelsRef = useRef({ next: '', previous: '', done: '' });
|
|
20
|
+
const optionsRef = useRef(options);
|
|
21
|
+
optionsRef.current = options;
|
|
22
|
+
turnOffRef.current = turnOff;
|
|
23
|
+
labelsRef.current = {
|
|
24
|
+
next: t('button.next'),
|
|
25
|
+
previous: t('button.back'),
|
|
26
|
+
done: t('button.close'),
|
|
27
|
+
};
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
let cancelled = false;
|
|
30
|
+
const load = async () => {
|
|
31
|
+
const resolvedUrl = await optionsRef.current.resolveKnowledgeBaseUrl();
|
|
32
|
+
if (cancelled)
|
|
33
|
+
return;
|
|
34
|
+
try {
|
|
35
|
+
const kb = await loadKnowledgeBase(resolvedUrl);
|
|
36
|
+
if (!cancelled)
|
|
37
|
+
setKnowledgeBase(kb);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error(error);
|
|
41
|
+
if (!cancelled)
|
|
42
|
+
setKnowledgeBase(null);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
setKnowledgeBase(null);
|
|
46
|
+
load();
|
|
47
|
+
return () => {
|
|
48
|
+
cancelled = true;
|
|
49
|
+
};
|
|
50
|
+
}, [pathname, options.resolveKnowledgeBaseUrl, options.reloadDeps]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!knowledgeBase || autoOpenTriggered.current)
|
|
53
|
+
return;
|
|
54
|
+
const matchPathnames = (optionsRef.current.getMatchPathnames ?? defaultMatchPathnames)(pathname);
|
|
55
|
+
if (!Storage.getTourAutoOpen() ||
|
|
56
|
+
!hasMatchingWorkflow(knowledgeBase, matchPathnames)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
autoOpenTriggered.current = true;
|
|
60
|
+
turnOn();
|
|
61
|
+
}, [knowledgeBase, pathname, turnOn]);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!state.tour || !knowledgeBase)
|
|
64
|
+
return;
|
|
65
|
+
let cancelled = false;
|
|
66
|
+
const matchPathnames = (optionsRef.current.getMatchPathnames ?? defaultMatchPathnames)(pathname);
|
|
67
|
+
if (!hasMatchingWorkflow(knowledgeBase, matchPathnames)) {
|
|
68
|
+
turnOffRef.current();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const startTour = async () => {
|
|
72
|
+
const { waitForSelector: startSelector, anchorFallbacks } = optionsRef.current;
|
|
73
|
+
if (startSelector) {
|
|
74
|
+
await waitForSelector(startSelector);
|
|
75
|
+
}
|
|
76
|
+
if (cancelled)
|
|
77
|
+
return;
|
|
78
|
+
driverRef.current?.destroy();
|
|
79
|
+
const anchorMap = buildAnchorMap(knowledgeBase);
|
|
80
|
+
const driverObj = createTourDriver(labelsRef.current, () => turnOffRef.current(), anchorMap, anchorFallbacks);
|
|
81
|
+
driverRef.current = driverObj;
|
|
82
|
+
driverObj.setSteps(buildDriveSteps(knowledgeBase, matchPathnames, anchorFallbacks));
|
|
83
|
+
driverObj.drive(0);
|
|
84
|
+
};
|
|
85
|
+
startTour().catch((error) => {
|
|
86
|
+
console.error(error);
|
|
87
|
+
turnOffRef.current();
|
|
88
|
+
});
|
|
89
|
+
return () => {
|
|
90
|
+
cancelled = true;
|
|
91
|
+
driverRef.current?.destroy();
|
|
92
|
+
driverRef.current = null;
|
|
93
|
+
};
|
|
94
|
+
}, [state.tour, knowledgeBase, pathname]);
|
|
95
|
+
}
|
|
@@ -58,14 +58,14 @@ export default function DocumentExport({ doc }) {
|
|
|
58
58
|
});
|
|
59
59
|
};
|
|
60
60
|
const handleExportClick = (type) => {
|
|
61
|
-
if (type === '
|
|
61
|
+
if (type === 'TEXT') {
|
|
62
62
|
setOpenTxtTierModal(true);
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
65
|
process(type);
|
|
66
66
|
};
|
|
67
67
|
const handleTxtTierSelect = (tier) => {
|
|
68
|
-
process('
|
|
68
|
+
process('TEXT', tier);
|
|
69
69
|
};
|
|
70
70
|
const handleError = (err) => {
|
|
71
71
|
const message = err.response.data.message || err.response.data.error;
|
|
@@ -11,8 +11,6 @@ export declare const ExportTexts: {
|
|
|
11
11
|
'export.desc.csv': string;
|
|
12
12
|
'export.label.text': string;
|
|
13
13
|
'export.desc.text': string;
|
|
14
|
-
'export.label.txt': string;
|
|
15
|
-
'export.desc.txt': string;
|
|
16
14
|
'modal.title.txt.tier': string;
|
|
17
15
|
'modal.desc.txt.tier': string;
|
|
18
16
|
'modal.txt.tier.diplomatic': string;
|
|
@@ -37,8 +35,6 @@ export declare const ExportTexts: {
|
|
|
37
35
|
'export.desc.csv': string;
|
|
38
36
|
'export.label.text': string;
|
|
39
37
|
'export.desc.text': string;
|
|
40
|
-
'export.label.txt': string;
|
|
41
|
-
'export.desc.txt': string;
|
|
42
38
|
'modal.title.txt.tier': string;
|
|
43
39
|
'modal.desc.txt.tier': string;
|
|
44
40
|
'modal.txt.tier.diplomatic': string;
|
|
@@ -63,8 +59,6 @@ export declare const ExportTexts: {
|
|
|
63
59
|
'export.desc.csv': string;
|
|
64
60
|
'export.label.text': string;
|
|
65
61
|
'export.desc.text': string;
|
|
66
|
-
'export.label.txt': string;
|
|
67
|
-
'export.desc.txt': string;
|
|
68
62
|
'modal.title.txt.tier': string;
|
|
69
63
|
'modal.desc.txt.tier': string;
|
|
70
64
|
'modal.txt.tier.diplomatic': string;
|
|
@@ -10,9 +10,7 @@ export const ExportTexts = {
|
|
|
10
10
|
'export.label.csv': 'CSV',
|
|
11
11
|
'export.desc.csv': 'Generates a text file with comma separated values (CSV), ready to be imported to Microsoft Excel.',
|
|
12
12
|
'export.label.text': 'Text file',
|
|
13
|
-
'export.desc.text': '
|
|
14
|
-
'export.label.txt': 'Text file',
|
|
15
|
-
'export.desc.txt': 'Generates a plain text file with the document sentences at the chosen transcription layer.',
|
|
13
|
+
'export.desc.text': 'Generates a plain text file with the document sentences at the chosen transcription layer.',
|
|
16
14
|
'modal.title.txt.tier': 'Choose export type',
|
|
17
15
|
'modal.desc.txt.tier': 'Select which transcription layer to use for the text export.',
|
|
18
16
|
'modal.txt.tier.diplomatic': 'Diplomatic',
|
|
@@ -36,9 +34,7 @@ export const ExportTexts = {
|
|
|
36
34
|
'export.label.csv': 'CSV',
|
|
37
35
|
'export.desc.csv': 'Gera um arquivo de texto com valores separados por vírgula (CSV), pronto para ser importado para o Microsoft Excel.',
|
|
38
36
|
'export.label.text': 'Arquivo de texto',
|
|
39
|
-
'export.desc.text': '
|
|
40
|
-
'export.label.txt': 'Arquivo de texto',
|
|
41
|
-
'export.desc.txt': 'Gera um arquivo de texto simples com as sentenças do documento na camada de transcrição escolhida.',
|
|
37
|
+
'export.desc.text': 'Gera um arquivo de texto simples com as sentenças do documento na camada de transcrição escolhida.',
|
|
42
38
|
'modal.title.txt.tier': 'Escolha o tipo de exportação',
|
|
43
39
|
'modal.desc.txt.tier': 'Selecione qual camada de transcrição usar na exportação de texto.',
|
|
44
40
|
'modal.txt.tier.diplomatic': 'Diplomática',
|
|
@@ -62,9 +58,7 @@ export const ExportTexts = {
|
|
|
62
58
|
'export.label.csv': 'CSV',
|
|
63
59
|
'export.desc.csv': 'Genera un file di testo con valori separati da virgola (CSV), pronto per essere importato in Microsoft Excel.',
|
|
64
60
|
'export.label.text': 'File di testo',
|
|
65
|
-
'export.desc.text': '
|
|
66
|
-
'export.label.txt': 'File di testo',
|
|
67
|
-
'export.desc.txt': 'Genera un file di testo semplice con le frasi del documento al livello di trascrizione scelto.',
|
|
61
|
+
'export.desc.text': 'Genera un file di testo semplice con le frasi del documento al livello di trascrizione scelto.',
|
|
68
62
|
'modal.title.txt.tier': 'Scegli il tipo di esportazione',
|
|
69
63
|
'modal.desc.txt.tier': 'Seleziona quale livello di trascrizione usare per l\'esportazione del testo.',
|
|
70
64
|
'modal.txt.tier.diplomatic': 'Diplomatica',
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export type ExportType = 'TYCHO' | 'PSD' | 'CSV' | 'POS' | '
|
|
1
|
+
export type ExportType = 'TYCHO' | 'PSD' | 'CSV' | 'POS' | 'TEXT' | 'CHAT' | 'CONLLU';
|
|
2
2
|
export declare const ExportTypeNames: {
|
|
3
3
|
TYCHO: string;
|
|
4
4
|
PSD: string;
|
|
5
5
|
CSV: string;
|
|
6
6
|
POS: string;
|
|
7
|
-
|
|
7
|
+
TEXT: string;
|
|
8
8
|
CHAT: string;
|
|
9
9
|
CONLLU: string;
|
|
10
10
|
};
|
|
@@ -3,7 +3,7 @@ export const ExportTypeNames = {
|
|
|
3
3
|
PSD: 'export.type.psd',
|
|
4
4
|
CSV: 'export.type.csv',
|
|
5
5
|
POS: 'export.type.pos',
|
|
6
|
-
|
|
6
|
+
TEXT: 'export.type.text',
|
|
7
7
|
CHAT: 'export.type.chat',
|
|
8
8
|
CONLLU: 'export.type.conllu',
|
|
9
9
|
};
|
|
@@ -34,7 +34,7 @@ export const EXPORT_TYPE_DISPLAY = [
|
|
|
34
34
|
active: true,
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
|
-
type: '
|
|
37
|
+
type: 'TEXT',
|
|
38
38
|
icon: 'description',
|
|
39
39
|
active: true,
|
|
40
40
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tycho-components",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.23.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"import": "./dist/cytoscape-context-menus.js",
|
|
41
41
|
"require": "./dist/cytoscape-context-menus.js"
|
|
42
42
|
},
|
|
43
|
-
"./dist/styles/main": "./dist/styles/main.scss"
|
|
43
|
+
"./dist/styles/main": "./dist/styles/main.scss",
|
|
44
|
+
"./dist/configs/tour/style.scss": "./dist/configs/tour/style.scss"
|
|
44
45
|
},
|
|
45
46
|
"files": [
|
|
46
47
|
"dist/"
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"cytoscape-dagre": "^2.5.0",
|
|
55
56
|
"cytoscape-node-html-label": "^1.2.2",
|
|
56
57
|
"date-fns": "^4.1.0",
|
|
58
|
+
"driver.js": "^1.4.0",
|
|
57
59
|
"date-fns-tz": "^3.2.0",
|
|
58
60
|
"file-saver": "^2.0.5",
|
|
59
61
|
"html2canvas": "^1.4.1",
|