tycho-components 0.3.7 → 0.4.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.
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { faDiagramProject, faDownload, faExpand, faInfoCircle, faUpRightAndDownLeftFromCenter, } from '@fortawesome/free-solid-svg-icons';
2
+ import { faDiagramProject, faDownload, faExpand, faInfoCircle, faMagnifyingGlass, faUpRightAndDownLeftFromCenter, } from '@fortawesome/free-solid-svg-icons';
3
3
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4
4
  import { saveAs } from 'file-saver';
5
5
  import html2canvas from 'html2canvas';
@@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next';
8
8
  import AppPlaceholder from '../AppPlaceholder';
9
9
  import DateUtils from '../functions/DateUtils';
10
10
  import SentenceUtils from '../functions/SentenceUtils';
11
+ import TreeViewSearch from './TreeViewSearch/TreeViewSearch';
11
12
  import CytoscapeTreeConverter from './cytoscape/CytoscapeTreeConverter';
12
13
  import SyntreesCytoscape from './cytoscape/SyntreesCytoscape';
13
14
  import './style.scss';
@@ -16,6 +17,13 @@ export default function TreeView({ struct, expression, selector = 'canvas-tree',
16
17
  const [cy, setCy] = useState(null);
17
18
  const [showInfo, setShowInfo] = useState(renderWithInfo);
18
19
  const [invalid, setInvalid] = useState();
20
+ const [showSearchModal, setShowSearchModal] = useState(false);
21
+ const [searchCriteria, setSearchCriteria] = useState({
22
+ syntacticCategory: [],
23
+ posTag: [],
24
+ emptyCategory: [],
25
+ wordValue: [],
26
+ });
19
27
  const load = () => {
20
28
  setCy(null);
21
29
  const element = document.getElementById(selector);
@@ -96,6 +104,12 @@ export default function TreeView({ struct, expression, selector = 'canvas-tree',
96
104
  setShowInfo(newVal);
97
105
  applyInfo(cy, struct, newVal);
98
106
  };
107
+ const openSearchModal = () => {
108
+ setShowSearchModal(true);
109
+ };
110
+ const closeSearchModal = () => {
111
+ setShowSearchModal(false);
112
+ };
99
113
  useEffect(() => {
100
114
  setInvalid(undefined);
101
115
  load();
@@ -104,15 +118,15 @@ export default function TreeView({ struct, expression, selector = 'canvas-tree',
104
118
  return (_jsx(AppPlaceholder, { text: placeholder || t('placeholder.sentence.notparsed') }));
105
119
  }
106
120
  return (_jsxs("div", { className: "tree-view-container", children: [cy &&
107
- getButtons(generateImage, reset, onClickExpression || downloadPsd, toggleInfo, onExpand, expression)
121
+ getButtons(generateImage, reset, onClickExpression || downloadPsd, toggleInfo, onExpand, expression, openSearchModal)
108
122
  .filter((btn) => btn.condition)
109
123
  .map((btn, i) => {
110
124
  const right = 12 + i * 44;
111
125
  return (_jsx("button", { className: "floating-button", type: "button", title: t(btn.title), onClick: btn.onClick, style: { right: `${right}px` }, children: _jsx(FontAwesomeIcon, { icon: btn.icon, className: btn.extraClass }) }, i));
112
- }), _jsx("div", { id: selector, className: "canvas-tree" }), showInfo && (_jsxs("div", { className: "info", children: [_jsx("span", { children: SentenceUtils.getAsText(struct) }), translations &&
126
+ }), showSearchModal && cy && (_jsx(TreeViewSearch, { struct: struct, cy: cy, onClose: closeSearchModal, searchCriteria: searchCriteria, setSearchCriteria: setSearchCriteria })), _jsx("div", { id: selector, className: "canvas-tree" }), showInfo && (_jsxs("div", { className: "info", children: [_jsx("span", { children: SentenceUtils.getAsText(struct) }), translations &&
113
127
  Object.entries(translations).map(([k, v]) => (_jsxs("div", { className: "translation", children: [_jsxs("b", { children: [k, ":"] }), _jsx("span", { children: v })] }, k))), struct.parsed && (_jsxs("span", { className: "date", children: [t('date.parsed'), ' ', DateUtils.formatDateTime(struct.parsed, 'dd/MM/yyyy HH:mm:ss')] }))] }))] }));
114
128
  }
115
- const getButtons = (generateImage, reset, downloadPsd, toggleInfo, onExpand, expression) => [
129
+ const getButtons = (generateImage, reset, downloadPsd, toggleInfo, onExpand, expression, openSearchModal) => [
116
130
  {
117
131
  condition: true,
118
132
  title: 'button.download.tree',
@@ -144,4 +158,10 @@ const getButtons = (generateImage, reset, downloadPsd, toggleInfo, onExpand, exp
144
158
  onClick: toggleInfo,
145
159
  icon: faInfoCircle,
146
160
  },
161
+ {
162
+ condition: true,
163
+ title: 'button.search',
164
+ onClick: openSearchModal || (() => { }),
165
+ icon: faMagnifyingGlass,
166
+ },
147
167
  ];
@@ -0,0 +1,18 @@
1
+ import { Core } from 'cytoscape';
2
+ import { Struct } from '../../configs/types/Struct';
3
+ import './style.scss';
4
+ export type SearchCriteria = {
5
+ syntacticCategory: string[];
6
+ posTag: string[];
7
+ emptyCategory: string[];
8
+ wordValue: string[];
9
+ };
10
+ type Props = {
11
+ struct: Struct;
12
+ cy: Core;
13
+ onClose: () => void;
14
+ searchCriteria: SearchCriteria;
15
+ setSearchCriteria: React.Dispatch<React.SetStateAction<SearchCriteria>>;
16
+ };
17
+ export default function TreeViewSearch({ struct, cy, onClose, searchCriteria, setSearchCriteria, }: Props): import("react/jsx-runtime").JSX.Element;
18
+ export {};
@@ -0,0 +1,103 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import AppModal from '../../AppModal';
5
+ import TreeViewSearchField from './TreeViewSearchField';
6
+ import './style.scss';
7
+ export default function TreeViewSearch({ struct, cy, onClose, searchCriteria, setSearchCriteria, }) {
8
+ const { t } = useTranslation('tree');
9
+ const [inputValues, setInputValues] = useState({
10
+ syntacticCategory: '',
11
+ posTag: '',
12
+ emptyCategory: '',
13
+ wordValue: '',
14
+ });
15
+ const addBadge = (type) => {
16
+ const value = inputValues[type].trim();
17
+ if (!value)
18
+ return;
19
+ setSearchCriteria((prev) => ({
20
+ ...prev,
21
+ [type]: [...prev[type], value],
22
+ }));
23
+ setInputValues((prev) => ({
24
+ ...prev,
25
+ [type]: '',
26
+ }));
27
+ };
28
+ const removeBadge = (type, index) => {
29
+ setSearchCriteria((prev) => ({
30
+ ...prev,
31
+ [type]: prev[type].filter((_, i) => i !== index),
32
+ }));
33
+ };
34
+ const performSearch = () => {
35
+ if (!cy || !struct)
36
+ return;
37
+ // Remove previous highlights
38
+ cy.nodes().removeClass('highlight');
39
+ cy.nodes().forEach((node) => {
40
+ const data = node.data();
41
+ // Check syntactic category (chunks)
42
+ if (data.chunk && searchCriteria.syntacticCategory.length > 0) {
43
+ const chunkCategory = data.chunk.t || '';
44
+ if (searchCriteria.syntacticCategory.some((cat) => chunkCategory.toLowerCase().includes(cat.toLowerCase()))) {
45
+ node.addClass('highlight');
46
+ }
47
+ }
48
+ // Check POS tag (tokens)
49
+ if (data.token && searchCriteria.posTag.length > 0) {
50
+ const posTag = data.token.t || '';
51
+ if (searchCriteria.posTag.some((tag) => posTag.toLowerCase() === tag.toLowerCase() ||
52
+ posTag.toLowerCase().includes(tag.toLowerCase()))) {
53
+ node.addClass('highlight');
54
+ }
55
+ }
56
+ // Check empty category
57
+ if (data.token && searchCriteria.emptyCategory.length > 0) {
58
+ const isEmpty = data.token.ec === true;
59
+ const emptyValue = data.token.v;
60
+ if (searchCriteria.emptyCategory.some((ec) => {
61
+ return ((emptyValue.toLowerCase() === ec.toLowerCase() ||
62
+ emptyValue.toLowerCase().includes(ec.toLowerCase())) &&
63
+ isEmpty);
64
+ })) {
65
+ node.addClass('highlight');
66
+ }
67
+ }
68
+ // Check word value
69
+ if (data.token && searchCriteria.wordValue.length > 0) {
70
+ const wordValue = data.token.v || '';
71
+ if (searchCriteria.wordValue.some((val) => wordValue.toLowerCase().includes(val.toLowerCase()))) {
72
+ node.addClass('highlight');
73
+ }
74
+ }
75
+ });
76
+ onClose();
77
+ };
78
+ const searchFields = [
79
+ {
80
+ key: 'syntacticCategory',
81
+ label: t('label.syntactic.category'),
82
+ },
83
+ {
84
+ key: 'posTag',
85
+ label: t('label.pos.tag'),
86
+ },
87
+ {
88
+ key: 'emptyCategory',
89
+ label: t('label.empty.category'),
90
+ },
91
+ {
92
+ key: 'wordValue',
93
+ label: t('label.word.value'),
94
+ },
95
+ ];
96
+ return (_jsx(AppModal, { title: t('button.search') || 'Search Tree', close: onClose, className: "tree-search-modal", confirm: performSearch, disableConfirm: searchCriteria.syntacticCategory.length === 0 &&
97
+ searchCriteria.posTag.length === 0 &&
98
+ searchCriteria.emptyCategory.length === 0 &&
99
+ searchCriteria.wordValue.length === 0, children: searchFields.map((field) => (_jsx(TreeViewSearchField, { label: field.label, value: inputValues[field.key], badges: searchCriteria[field.key], onValueChange: (value) => setInputValues((prev) => ({
100
+ ...prev,
101
+ [field.key]: value,
102
+ })), onAddBadge: () => addBadge(field.key), onRemoveBadge: (index) => removeBadge(field.key, index) }, field.key))) }));
103
+ }
@@ -0,0 +1,11 @@
1
+ import './style.scss';
2
+ type Props = {
3
+ label: string;
4
+ value: string;
5
+ badges: string[];
6
+ onValueChange: (value: string) => void;
7
+ onAddBadge: () => void;
8
+ onRemoveBadge: (index: number) => void;
9
+ };
10
+ export default function TreeViewSearchField({ label, value, badges, onValueChange, onAddBadge, onRemoveBadge, }: Props): import("react/jsx-runtime").JSX.Element;
11
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTranslation } from 'react-i18next';
3
+ import './style.scss';
4
+ import { Icon, IconButton, Tag } from 'tycho-storybook';
5
+ export default function TreeViewSearchField({ label, value, badges, onValueChange, onAddBadge, onRemoveBadge, }) {
6
+ const { t } = useTranslation('tree');
7
+ const handleKeyPress = (e) => {
8
+ if (e.key === 'Enter') {
9
+ onAddBadge();
10
+ }
11
+ };
12
+ const renderTag = (value, idx) => {
13
+ return (_jsxs(_Fragment, { children: [_jsx("span", { children: value }), _jsx(Icon, { name: "close", size: "small", onClick: (e) => {
14
+ onRemoveBadge(idx);
15
+ e.stopPropagation();
16
+ } })] }));
17
+ };
18
+ return (_jsxs("div", { className: "search-field", children: [_jsx("label", { children: label }), _jsxs("div", { className: "input-with-button", children: [_jsx("input", { type: "text", value: value, onChange: (e) => onValueChange(e.target.value), onKeyUp: handleKeyPress, placeholder: t('common:generic.placeholder') }), _jsx(IconButton, { onClick: onAddBadge, name: "add", size: "medium" })] }), _jsx("div", { className: "badges", children: badges.map((badge, index) => (_jsx(Tag, { text: renderTag(badge, index) }, index))) })] }));
19
+ }
@@ -0,0 +1,49 @@
1
+ .tree-search-modal {
2
+ .body {
3
+ min-width: 500px;
4
+ display: grid;
5
+ grid-template-columns: 1fr 1fr;
6
+ gap: 24px;
7
+
8
+ .search-field {
9
+ margin-bottom: 0;
10
+
11
+ label {
12
+ display: block;
13
+ margin-bottom: 8px;
14
+ font-weight: 600;
15
+ color: var(--color-text-primary, #333);
16
+ }
17
+
18
+ .input-with-button {
19
+ display: flex;
20
+ gap: 8px;
21
+ margin-bottom: 8px;
22
+
23
+ input {
24
+ flex: 1;
25
+ padding: 8px 12px;
26
+ border: 1px solid var(--color-border-default, #ddd);
27
+ border-radius: var(--border-radius-button, 4px);
28
+ font-size: 14px;
29
+
30
+ &:focus {
31
+ outline: none;
32
+ border-color: var(--color-primary, #007bff);
33
+ }
34
+ }
35
+ }
36
+
37
+ .badges {
38
+ display: flex;
39
+ flex-wrap: wrap;
40
+ gap: 8px;
41
+ margin-top: 8px;
42
+
43
+ .ds-icon {
44
+ cursor: pointer;
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
@@ -61,4 +61,121 @@
61
61
  color: red;
62
62
  }
63
63
  }
64
+
65
+ .tree-search-modal {
66
+ padding: 16px;
67
+ min-width: 500px;
68
+
69
+ .search-field {
70
+ margin-bottom: 24px;
71
+
72
+ label {
73
+ display: block;
74
+ margin-bottom: 8px;
75
+ font-weight: 600;
76
+ color: var(--color-text-primary, #333);
77
+ }
78
+
79
+ .input-with-button {
80
+ display: flex;
81
+ gap: 8px;
82
+ margin-bottom: 8px;
83
+
84
+ input {
85
+ flex: 1;
86
+ padding: 8px 12px;
87
+ border: 1px solid var(--color-border-default, #ddd);
88
+ border-radius: var(--border-radius-button, 4px);
89
+ font-size: 14px;
90
+
91
+ &:focus {
92
+ outline: none;
93
+ border-color: var(--color-primary, #007bff);
94
+ }
95
+ }
96
+
97
+ .add-badge-btn {
98
+ padding: 8px 16px;
99
+ background-color: var(--color-primary, #007bff);
100
+ color: white;
101
+ border: none;
102
+ border-radius: var(--border-radius-button, 4px);
103
+ cursor: pointer;
104
+ font-size: 14px;
105
+ white-space: nowrap;
106
+
107
+ &:hover {
108
+ background-color: var(--color-primary-dark, #0056b3);
109
+ }
110
+ }
111
+ }
112
+
113
+ .badges {
114
+ display: flex;
115
+ flex-wrap: wrap;
116
+ gap: 8px;
117
+ margin-top: 8px;
118
+
119
+ .badge {
120
+ display: inline-flex;
121
+ align-items: center;
122
+ gap: 6px;
123
+ padding: 4px 12px;
124
+ background-color: var(--color-surface-secondary, #f0f0f0);
125
+ border: 1px solid var(--color-border-default, #ddd);
126
+ border-radius: 16px;
127
+ font-size: 13px;
128
+ color: var(--color-text-primary, #333);
129
+
130
+ .badge-remove {
131
+ cursor: pointer;
132
+ font-size: 12px;
133
+ color: var(--color-text-secondary, #666);
134
+ transition: color 0.2s;
135
+
136
+ &:hover {
137
+ color: var(--color-danger, #dc3545);
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ .search-actions {
145
+ display: flex;
146
+ justify-content: flex-end;
147
+ margin-top: 24px;
148
+ padding-top: 16px;
149
+ border-top: 1px solid var(--color-border-default, #ddd);
150
+
151
+ .search-btn {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 8px;
155
+ padding: 10px 20px;
156
+ background-color: var(--color-primary, #007bff);
157
+ color: white;
158
+ border: none;
159
+ border-radius: var(--border-radius-button, 4px);
160
+ cursor: pointer;
161
+ font-size: 14px;
162
+ font-weight: 600;
163
+
164
+ &:hover:not(:disabled) {
165
+ background-color: var(--color-primary-dark, #0056b3);
166
+ }
167
+
168
+ &:disabled {
169
+ background-color: var(--color-disabled, #ccc);
170
+ cursor: not-allowed;
171
+ opacity: 0.6;
172
+ }
173
+
174
+ svg {
175
+ width: 16px;
176
+ height: 16px;
177
+ }
178
+ }
179
+ }
180
+ }
64
181
  }
@@ -157,6 +157,11 @@ export declare const commonResources: {
157
157
  'button.info': string;
158
158
  'placeholder.sentence.notparsed': string;
159
159
  'date.parsed': string;
160
+ 'button.search': string;
161
+ 'label.syntactic.category': string;
162
+ 'label.pos.tag': string;
163
+ 'label.empty.category': string;
164
+ 'label.word.value': string;
160
165
  };
161
166
  sentence: {
162
167
  'label.word': string;
@@ -406,6 +411,11 @@ export declare const commonResources: {
406
411
  'button.info': string;
407
412
  'placeholder.sentence.notparsed': string;
408
413
  'date.parsed': string;
414
+ 'button.search': string;
415
+ 'label.syntactic.category': string;
416
+ 'label.pos.tag': string;
417
+ 'label.empty.category': string;
418
+ 'label.word.value': string;
409
419
  };
410
420
  upload: {
411
421
  'label.dropzone': string;
@@ -638,6 +648,11 @@ export declare const commonResources: {
638
648
  'button.download.penn': string;
639
649
  'button.info': string;
640
650
  'date.parsed': string;
651
+ 'button.search': string;
652
+ 'label.syntactic.category': string;
653
+ 'label.pos.tag': string;
654
+ 'label.empty.category': string;
655
+ 'label.word.value': string;
641
656
  };
642
657
  upload: {
643
658
  'label.dropzone': string;
@@ -7,6 +7,11 @@ export declare const TreeTexts: {
7
7
  'button.info': string;
8
8
  'placeholder.sentence.notparsed': string;
9
9
  'date.parsed': string;
10
+ 'button.search': string;
11
+ 'label.syntactic.category': string;
12
+ 'label.pos.tag': string;
13
+ 'label.empty.category': string;
14
+ 'label.word.value': string;
10
15
  };
11
16
  'pt-BR': {
12
17
  'button.expand.tree': string;
@@ -16,6 +21,11 @@ export declare const TreeTexts: {
16
21
  'button.info': string;
17
22
  'placeholder.sentence.notparsed': string;
18
23
  'date.parsed': string;
24
+ 'button.search': string;
25
+ 'label.syntactic.category': string;
26
+ 'label.pos.tag': string;
27
+ 'label.empty.category': string;
28
+ 'label.word.value': string;
19
29
  };
20
30
  it: {
21
31
  'button.expand.tree': string;
@@ -24,5 +34,10 @@ export declare const TreeTexts: {
24
34
  'button.download.penn': string;
25
35
  'button.info': string;
26
36
  'date.parsed': string;
37
+ 'button.search': string;
38
+ 'label.syntactic.category': string;
39
+ 'label.pos.tag': string;
40
+ 'label.empty.category': string;
41
+ 'label.word.value': string;
27
42
  };
28
43
  };
@@ -7,6 +7,11 @@ export const TreeTexts = {
7
7
  'button.info': 'Display translations and glosses',
8
8
  'placeholder.sentence.notparsed': 'This sentence has not been parsed.',
9
9
  'date.parsed': 'Parsing executed at:',
10
+ 'button.search': 'Search',
11
+ 'label.syntactic.category': 'Syntactic Category',
12
+ 'label.pos.tag': 'POS Tag',
13
+ 'label.empty.category': 'Empty Category',
14
+ 'label.word.value': 'Word',
10
15
  },
11
16
  'pt-BR': {
12
17
  'button.expand.tree': 'Expandir árvore',
@@ -16,6 +21,11 @@ export const TreeTexts = {
16
21
  'button.info': 'Mostrar traduções e glosas',
17
22
  'placeholder.sentence.notparsed': 'O parser não foi realizado nesta sentença.',
18
23
  'date.parsed': 'Parser executado às:',
24
+ 'button.search': 'Buscar',
25
+ 'label.syntactic.category': 'Categoria Sintática',
26
+ 'label.pos.tag': 'Etiqueta POS',
27
+ 'label.empty.category': 'Categoria Vazia',
28
+ 'label.word.value': 'Palavra',
19
29
  },
20
30
  it: {
21
31
  'button.expand.tree': 'Espandi albero',
@@ -24,5 +34,10 @@ export const TreeTexts = {
24
34
  'button.download.penn': 'Scarica in formato Penn Tree',
25
35
  'button.info': 'Visualizza traduzioni e glosse',
26
36
  'date.parsed': 'Parser eseguito alle:',
37
+ 'button.search': 'Cerca',
38
+ 'label.syntactic.category': 'Categoria Sintattica',
39
+ 'label.pos.tag': 'Etichetta POS',
40
+ 'label.empty.category': 'Categoria Vuota',
41
+ 'label.word.value': 'Parola',
27
42
  },
28
43
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tycho-components",
3
3
  "private": false,
4
- "version": "0.3.7",
4
+ "version": "0.4.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {