arrmatura 5.2.0 → 6.2.1

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.
Files changed (110) hide show
  1. package/index.ts +20 -0
  2. package/package.json +14 -44
  3. package/src/core/composition.ts +71 -0
  4. package/src/core/conditionals.ts +80 -0
  5. package/src/core/elementary.ts +40 -0
  6. package/src/core/fragment.ts +21 -0
  7. package/src/core/index.ts +19 -0
  8. package/src/core/iterations.ts +121 -0
  9. package/src/core/root.ts +46 -0
  10. package/src/core/routing.ts +37 -0
  11. package/src/core/selection.ts +57 -0
  12. package/src/core/slot.ts +36 -0
  13. package/src/ctx/Ctx.ts +424 -0
  14. package/src/ctx/CtxNode.ts +159 -0
  15. package/src/utils/compileExpression.ts +126 -0
  16. package/src/utils/stringify.ts +39 -0
  17. package/LICENSE +0 -21
  18. package/README.md +0 -54
  19. package/commons/auth/AuthService.ts +0 -111
  20. package/commons/auth/UserService.ts +0 -57
  21. package/commons/auth/index.ts +0 -5
  22. package/commons/auth/user.xml +0 -64
  23. package/commons/components/buttons.xml +0 -25
  24. package/commons/components/index.ts +0 -13
  25. package/commons/components/inputs.xml +0 -33
  26. package/commons/components/layouts.xml +0 -77
  27. package/commons/components/media.xml +0 -21
  28. package/commons/components/modal.xml +0 -37
  29. package/commons/components/navigation.xml +0 -73
  30. package/commons/components/popups.xml +0 -12
  31. package/commons/components/searchbar.xml +0 -54
  32. package/commons/components/table.xml +0 -140
  33. package/commons/components/toasts.xml +0 -18
  34. package/commons/components/viewport.xml +0 -48
  35. package/commons/forms/FieldItem.xml +0 -20
  36. package/commons/forms/FormController.ts +0 -87
  37. package/commons/forms/Item.ts +0 -132
  38. package/commons/forms/ItemCollectionController.ts +0 -113
  39. package/commons/forms/ItemController.ts +0 -91
  40. package/commons/forms/MultiEnumField.xml +0 -44
  41. package/commons/forms/MultivalueController.ts +0 -49
  42. package/commons/forms/NewItemController.ts +0 -46
  43. package/commons/forms/SmartareaField.xml +0 -51
  44. package/commons/forms/SuggestionController.ts +0 -80
  45. package/commons/forms/SuggestionField.xml +0 -28
  46. package/commons/forms/fields.xml +0 -151
  47. package/commons/forms/form.xml +0 -37
  48. package/commons/forms/index.ts +0 -29
  49. package/commons/index.ts +0 -6
  50. package/commons/services/ApiClientService.ts +0 -32
  51. package/commons/services/ErrorHandlingService.ts +0 -11
  52. package/commons/services/FirebaseService.ts +0 -185
  53. package/commons/services/GSheetsService.ts +0 -120
  54. package/commons/services/HeartbeatService.ts +0 -35
  55. package/commons/services/Invoke.ts +0 -29
  56. package/commons/services/JsonpService.ts +0 -18
  57. package/commons/services/Loader.ts +0 -17
  58. package/commons/services/MetadataService.ts +0 -50
  59. package/commons/services/NavigationService.ts +0 -74
  60. package/commons/services/Service.ts +0 -62
  61. package/commons/services/ServiceWorker.ts +0 -111
  62. package/commons/services/Storage.ts +0 -55
  63. package/commons/services/ToastService.ts +0 -61
  64. package/commons/services/index.ts +0 -13
  65. package/core/Arrmatura.ts +0 -79
  66. package/core/CNode.ts +0 -111
  67. package/core/Component.ts +0 -424
  68. package/core/cnodes/composition.ts +0 -100
  69. package/core/cnodes/conditionals.ts +0 -52
  70. package/core/cnodes/elementary.ts +0 -44
  71. package/core/cnodes/iterations.ts +0 -92
  72. package/core/cnodes/routing.ts +0 -25
  73. package/core/compileNode.ts +0 -39
  74. package/core/expression.ts +0 -73
  75. package/core/index.ts +0 -1
  76. package/core/register.ts +0 -43
  77. package/core/utils.ts +0 -23
  78. package/core-web/WebPlatform.ts +0 -25
  79. package/core-web/attributes.ts +0 -218
  80. package/core-web/elements.ts +0 -81
  81. package/core-web/index.ts +0 -28
  82. package/core-web/reflow.ts +0 -30
  83. package/core-web/support.ts +0 -27
  84. package/core-web/type.ts +0 -18
  85. package/docs/index.js +0 -485
  86. package/lib/array/arrayGroupBy.ts +0 -19
  87. package/lib/array/arraySortBy.ts +0 -49
  88. package/lib/array/arrayToObject.ts +0 -18
  89. package/lib/array/arrayish.ts +0 -7
  90. package/lib/array/index.ts +0 -4
  91. package/lib/crypto/index.ts +0 -25
  92. package/lib/date/index.ts +0 -127
  93. package/lib/func/curry.ts +0 -11
  94. package/lib/func/fx.ts +0 -56
  95. package/lib/func/identity.ts +0 -6
  96. package/lib/func/nextId.ts +0 -9
  97. package/lib/func/noop.ts +0 -6
  98. package/lib/geo/index.js +0 -22
  99. package/lib/index.ts +0 -17
  100. package/lib/object/index.ts +0 -69
  101. package/lib/parsing/codus.ts +0 -25
  102. package/lib/parsing/compiler.ts +0 -26
  103. package/lib/parsing/scalarParse.ts +0 -25
  104. package/lib/parsing/urls.ts +0 -91
  105. package/lib/parsing/xml.ts +0 -117
  106. package/lib/resources/l10n.ts +0 -40
  107. package/lib/resources/resources.ru.ts +0 -20
  108. package/lib/resources/resources.ts +0 -20
  109. package/lib/string/index.ts +0 -58
  110. package/lib/support/createListeners.ts +0 -12
@@ -1,54 +0,0 @@
1
- <component id="SearchBar">
2
- <span class="ant-input-search ant-input-affix-wrapper">
3
- <input placeholder="Поиск..." class="ant-input" type="text" value="{value}" enter="{action}" style="min-height: 40px" />
4
- <span class="ant-input-suffix">
5
- <i class="anticon anticon-search ant-input-search-icon" style="margin-top: 4px"></i>
6
- </span>
7
- </span>
8
- </component>
9
-
10
- <component id="SearchBarWithSuggestions">
11
- <!-- <ui:fragment ui:if="opened.value|or:@value">
12
- <ui:then> -->
13
- <div class="form-autocomplete mx-2">
14
- <!-- autocomplete input container -->
15
- <div class="form-autocomplete-input form-input input-sm">
16
- <div class="input-group input-inline">
17
- <input class="form-input" type="search" placeholder=":kluq_poshuku" keypress="{input}" value="{value}" focus="->opened|assignKeyValue:value:true" style="width:inherit" />
18
- <i class="icon icon-cross m-2 c-hand" data-value="" click="{enter}" click2="->opened|assignKeyValue:value:false" ui:if="value"></i>
19
- <i class="icon icon-search m-2 c-hand" click="->opened|assignKeyValue:value:false" ui:if="value|not"></i>
20
- </div>
21
- </div>
22
- <ui:fragment ui:if="opened.value|then:@suggestions">
23
- <div style="position:fixed; right:0; top:0; left:0; bottom:0;" click="->opened|assignKeyValue:value:false"></div>
24
- <ul class="menu" style="min-width: 140px;max-height:16rem;overflow-y:auto;">
25
- <ui:fragment ui:if="suggestions.length">
26
- <ui:then>
27
- <li class="menu-item c-hand" ui:for="item of suggestions" data-value="{item.id}" click="{enter}">
28
- <a>
29
- <div class="tile tile-centered">
30
- <div class="tile-content">{item.name}</div>
31
- </div>
32
- </a>
33
- </li>
34
- </ui:then>
35
- <ui:else>
36
- <li class="menu-item">
37
- <div class="tile tile-centered">
38
- <div class="tile-content text-gray">:strings.no_findings|or:no findings</div>
39
- </div>
40
- </li>
41
- </ui:else>
42
- </ui:fragment>
43
- </ul>
44
- </ui:fragment>
45
- </div>
46
- <!-- </ui:then>
47
- <ui:else>
48
- <div class="mx-2" click="{open|fnBindArgs:true}">
49
- <i class="icon icon-search m-2 c-hand" click="->opened|assignKeyValue:value:true"></i>
50
- </div>
51
- </ui:else>
52
- </ui:fragment> -->
53
-
54
- </component>
@@ -1,140 +0,0 @@
1
- <component id="LoadingIndicator">
2
- <div class="loading {large|then:loading-lg} {class}" />
3
- </component>
4
-
5
- <component id="EmptyData">
6
- <div class="empty w100 m-0 {class}">
7
- <div class="empty-icon">
8
- <Icon type="{icon|or:people}" />
9
- </div>
10
- <p class="empty-title h5">{title}</p>
11
- <p ui:if="{subtitle}" class="empty-subtitle">{subtitle}</p>
12
- <div class="empty-action">
13
- <ui:slot />
14
- </div>
15
- </div>
16
- </component>
17
-
18
- <component id="AsyncLoader">
19
- <ui:fragment ui:if="isLoading">
20
- <ui:then>
21
- <EmptyData class="h100" title="{title}" subtitle="{error|then::@subtitle}" icon="{icon|or:download}">
22
- <ui:fragment ui:if="error|not">
23
- <ui:then>
24
- <LoadingIndicator/>
25
- </ui:then>
26
- <ui:else>
27
- <Btn mode="error" action="{onError}" title="Error: {error}"/>
28
- </ui:else>
29
- </ui:fragment>
30
- </EmptyData>
31
- </ui:then>
32
- <ui:else>
33
- <ui:slot />
34
- </ui:else>
35
- </ui:fragment>
36
- </component>
37
-
38
- <component id="LoadableItems">
39
- <LoadingIndicator class="empty" ui:if="data|not">
40
- <ui:else>
41
- <h5 class="error text-error" ui:if="data.error">{data.error.message|or:@data.error}</h5>
42
- <EmptyData title="{emptyCaption|or:Empty}" ui:if="data.length|not">
43
- <ui:else>
44
- <div style="{style}" ui:for="item of data">
45
- <ui:tag tag="{itemType}" ui:props="{item}" item="{item}" context="{context}" action="{action}" />
46
- </div>
47
- </ui:else>
48
- </EmptyData>
49
- </ui:else>
50
- </LoadingIndicator>
51
- </component>
52
-
53
- <component id="LoadableEntity">
54
- <LoadingIndicator class="empty" ui:if="data|not">
55
- <ui:else>
56
- <h5 class="error text-error" ui:if="data.error">
57
- <span>{data.error.message|or:@data.error}</span>
58
- <ui:else>
59
- <EmptyData title="{emptyCaption|or:Empty}" ui:if="data.id|not">
60
- <ui:else>
61
- <ui:tag tag="{type}" data="{data}" context="{context}" action="{action}" />
62
- </ui:else>
63
- </EmptyData>
64
- </ui:else>
65
- </h5>
66
- </ui:else>
67
- </LoadingIndicator>
68
- </component>
69
-
70
- <component id="Cell">
71
- <span class="" style="padding-left: 0px">{value}</span>
72
- </component>
73
-
74
- <component id="EnumCell">
75
- <span class="" style="padding-left: 0px">:map.{typeSpec}.{value}</span>
76
- </component>
77
-
78
- <component id="DateCell">
79
- <span class="" style="padding-left: 0px">{value|date}</span>
80
- </component>
81
-
82
- <component id="Table">
83
- <table style="position: relative">
84
- <thead class="table-thead">
85
- <tr>
86
- <th class="col-index" style="position: sticky; top: -1"></th>
87
- <th class="col-index" style="position: sticky; top: -1" ui:for="item of columns">
88
- <span>{item.name}</span>
89
- <div class="table-column-sorter" ui:if="item.sortable">
90
- <span class="table-column-sorter-up off" title="↑" data-id="-{item.id}" click="{doSort}">
91
- <i class="anticon anticon-caret-up"></i
92
- > </span>
93
- <span class="table-column-sorter-down off" title="↓" data-id="{item.id}" click="{doSort}">
94
- <i class="anticon anticon-caret-down"></i
95
- > </span>
96
- </div>
97
- </th>
98
- </tr>
99
- </thead>
100
- <TableRow ui:for="item of data" data="{item}" selected="{item.selected}" columns="{columns}" kind="{kind}" />
101
- </table>
102
- </component>
103
-
104
- <component id="TableRow">
105
- <tbody class="table-tbody">
106
- <tr class="table-row table-row-level-0" style="position: sticky">
107
- <td class="col-index" data-id="{data.id}" click="{doItem}">
108
- <div ui:if="{expandable}" role="button" class="table-row-expand-icon table-row-expanded" aria-label="Collapse row" data-selected="false" click="->">
109
- <ui:else>
110
- <div role="button" class="table-row-expand-icon table-row-collapsed" aria-label="Expand row" data-selected="true" click="->"></div>
111
- </ui:else>
112
- </div>
113
- </td>
114
- <td class="col-index" ui:for="col of columns">
115
- <ui:tag tag="{col.type|capitalize}Cell" ui:props="{col}" value="{col.id|propObjectValueByKey}" data="{data}" />
116
- </td>
117
- </tr>
118
- <tr ui:if="{expanded}" class="table-expanded-row table-expanded-row-level-1">
119
- <td class=""></td>
120
- <td class="" colspan="100">
121
- <ui:tag tag="{kind|capitalize}RowInsider" data="{data}" />
122
- </td>
123
- </tr>
124
- </tbody>
125
- </component>
126
-
127
- <component id="Chips">
128
- <div class="chips mx-0 py-2 px-2 {class}">
129
- <div class="ui mini labels">
130
- <span class="chip {tag.selected|then:bg-success:bg-secondary} {tag.nonSelectable|then::c-hand} {tag.count|then:badge}" data-badge="{tag.count}" style="padding:1.2rem" data-id="{tag.id}" click="{tag.nonSelectable|then:undefined:@click}" ui:for="tag of data">
131
- <span>
132
- <b>{tag.name}</b>
133
- </span>
134
- </span>
135
- <span ui:if="data|find:true:selected" class="label py-1 px-2 s-circle c-hand" click="{click|assignFirstArgKeyValue:id:}">
136
- <Icon type="cross"/>
137
- </span>
138
- </div>
139
- </div>
140
- </component>
@@ -1,18 +0,0 @@
1
- <component id="ToastList">
2
- <div style="position:absolute;top:2.5rem;right:1rem;left:1rem;z-index:500;{style}">
3
- <div style="max-width: 600px;margin: auto;">
4
- <Toast ui:props={toast} ui:for="toast of <-toasters.items"/>
5
- </div>
6
- </div>
7
- </component>
8
-
9
- <component id="Toast">
10
- <div class="toast mt-1 toast-{mode|or:@mode|or:primary}">
11
- <button class="btn btn-clear float-right" click="{close}"></button>
12
- <p>
13
- <b ui:if="counter" class="mr-1">[x{counter}]</b>
14
- <span> {message}</span>
15
- <a class="mx-1" href="{link}" ui:if="link">link</a>
16
- </p>
17
- </div>
18
- </component>
@@ -1,48 +0,0 @@
1
- <component id="ViewPort">
2
- <ui:fragment ui:if="slot(aside)">
3
- <Sidebar caption="{caption}" size="{sidebarWidth}">
4
- <Sidebar:aside>
5
- <ui:slot id="aside" />
6
- </Sidebar:aside>
7
- <main>
8
- <ui:slot />
9
- </main>
10
- </Sidebar>
11
- <ui:else>
12
- <main>
13
- <ui:slot />
14
- </main>
15
- </ui:else>
16
- </ui:fragment>
17
- </component>
18
-
19
- <component id="Sidebar">
20
- <div class="off-canvas off-canvas-sidebar-show">
21
- <a class="off-canvas-toggle btn btn-primary btn-action show-lg" href="#sidebar">
22
- <Icon type="menu" />
23
- </a>
24
- <div id="sidebar" class="off-canvas-sidebar">
25
- <Aside caption="{caption}">
26
- <ui:slot id="aside" />
27
- </Aside>
28
- </div>
29
- <a class="off-canvas-overlay" href="#"></a>
30
- <div class="off-canvas-content">
31
- <ui:slot />
32
- </div>
33
- </div>
34
- </component>
35
-
36
- <component id="Aside">
37
- <aside style="display:flex; flex-direction: column; height: 100vh; width:{size|or:300}px">
38
- <div class="text-center" ui:if="{caption}">
39
- <a href="#/main">
40
- <h5 class="p-2">{caption}</h5>
41
- </a>
42
- </div>
43
- <div class="m-2" style="flex: 1; overflow-y: auto">
44
- <ui:slot />
45
- </div>
46
- </aside>
47
- </component>
48
-
@@ -1,20 +0,0 @@
1
- <component id="FieldItem">
2
- <div class="form-group {error|then:has-error} {class}">
3
- <div class="col-4 col-sm-12">
4
- <label class="form-label d-flex" for="input-example-1" ui:if="{caption}">
5
- <div class="text-ellipsis" title="{caption}">{caption}</div>
6
- <div ui:if="{required}" class="text-error text-tiny ml-1">✱</div>
7
- <div class="border-1 text-secondary s-circle centroid mx-1 px-2" style="height:1.3rem;width:1.3rem;" data-value="{showHint|not}" click="->showHint|dot:value" ui:if="{hint}">
8
- <span>?</span>
9
- </div>
10
- </label>
11
- </div>
12
- <div class="col-8 col-sm-12">
13
- <ui:slot />
14
- </div>
15
- <div class="col-12">
16
- <p class="form-input-hint mb-1" ui:if="{showHint}">{hint}</p>
17
- <p class="form-input-hint mb-1" ui:if="{error}">{error}</p>
18
- </div>
19
- </div>
20
- </component>
@@ -1,87 +0,0 @@
1
- import { arrayGroupBy } from '../../lib';
2
- import { strFormat, getPredicat } from '../../lib';
3
- import { Service } from '../services/Service';
4
-
5
- const getFieldsGroups = (fieldProps: Hash[], names?: StringHash): Obj[] => {
6
- return Object.values(arrayGroupBy(fieldProps, 'group')).map((e) => ({
7
- name: names?.[e.id] || e.id,
8
- ...e,
9
- }));
10
- };
11
-
12
- export class FormController extends Service {
13
- data: { [k: string]: unknown } = {};
14
- meta: any;
15
- error: any;
16
- change: any;
17
- tabs?: StringHash;
18
- groups?: StringHash;
19
-
20
- setData(d: Data) {
21
- const data = { ...d };
22
- this.data = data;
23
- }
24
-
25
- get fieldsGroups(): Obj[] {
26
- return getFieldsGroups(this.fieldProps, this.groups);
27
- }
28
-
29
- get tabsInfo() {
30
- const tabs = Object.values(arrayGroupBy(this.fieldProps, ({ tab = 'tab' }) => tab)).map(({ id, items }) => ({
31
- id,
32
- name: this.tabs?.[id] || id,
33
- items: getFieldsGroups(items, this.groups),
34
- }));
35
- return { enabled: tabs.length > 1, initialTab: tabs?.[0]?.id, tabs };
36
- }
37
-
38
- resolveEnum(typeSpec: string = '', data: Hash) {
39
- if (typeSpec[0] === '[') {
40
- return typeSpec
41
- .slice(1, typeSpec.length - 1)
42
- .split(',')
43
- .map((s) => s.trim().split('='))
44
- .map(([id, name = id]) => ({ id, name }));
45
- }
46
-
47
- if (!typeSpec.includes('.')) return this.res('enums.' + typeSpec) || [];
48
-
49
- const [key, subid, subKey] = strFormat(typeSpec, data).split('.');
50
- const enumItems = this.res('enums.' + key) || [];
51
- if (subKey) {
52
- return enumItems.find(({ id }) => id === subid)?.[subKey] || [];
53
- }
54
- return enumItems;
55
- }
56
-
57
- get fieldProps() {
58
- return (
59
- this.meta?.map?.((field: { [k: string]: string }) => {
60
- const { id, visible, disabled = false, caption, name, typeSpec } = field;
61
- const form = this;
62
- const data = this.data;
63
- const value = data[id];
64
- return {
65
- ...field,
66
- error: (form.error && form.error.fields && form.error.fields[id]) || null,
67
- get disabled(): boolean {
68
- return getPredicat(disabled)(data);
69
- },
70
- get visible(): boolean {
71
- return getPredicat(visible || 'true')(data);
72
- },
73
- get options(): Data[] | Data | null {
74
- return form.resolveEnum(typeSpec, data);
75
- },
76
- value: value === undefined ? null : value,
77
- caption: caption || name,
78
- data,
79
- onChange: ({ value, key }: { value: unknown; key?: string }) => {
80
- this.data = { ...this.data, [key || id]: value };
81
- this.change?.(this.data);
82
- },
83
- };
84
- }) || []
85
- );
86
- }
87
- }
@@ -1,132 +0,0 @@
1
- import { arraySortBy } from '../../lib';
2
-
3
- export const analyzeDataByTags = (
4
- data: Item[],
5
- selection: Hash<boolean>,
6
- initials = '',
7
- tags: Tag[] = [],
8
- labelsFields: string
9
- ) => {
10
- const sel = Object.entries(selection)
11
- .filter(([_, val]) => val)
12
- .map(([key]) => key);
13
- const level = sel.length;
14
- if (labelsFields) {
15
- const lFields = labelsFields.split(',');
16
- const fields = lFields.slice(0, level);
17
- const actualData = !level ? data : data.filter((e) => !fields.find((field, index) => e[field] != sel[index]));
18
- const tagsHash: Hash<Tag> = {};
19
- if (level) {
20
- tagsHash['*'] = { id: sel[level - 1], name: sel.join(' ➡ '), count: 0, selected: level > 0 };
21
- }
22
-
23
- actualData.forEach((e) => {
24
- const id = String(e[lFields[level]] || '');
25
- if (!id) return;
26
- const tag = tagsHash[id] || (tagsHash[id] = { id, name: id, count: 0, selected: selection[id] });
27
- if (!tag.selected) {
28
- tag.count++;
29
- }
30
- });
31
-
32
- tags.push(...Object.values(tagsHash));
33
- // tags.sort((a, b) => (a.selected === b.selected ? (a.id > b.id ? 1 : -1) : !a.selected ? 1 : -1));
34
- return actualData;
35
- }
36
- if (level || !initials) {
37
- const actualData = data.filter((e) => sel.reduce((r, s) => r && e.getLabels().has(s), true));
38
- const tagsHash: Hash<Tag> = {};
39
- sel.forEach((id) => {
40
- tagsHash[id] = { id, name: id, count: 0, selected: true };
41
- });
42
- actualData.forEach((e) => {
43
- e.getLabels().forEach((id: string) => {
44
- const tag =
45
- tagsHash[id] || (tagsHash[id] = { id, name: id, count: 0, selected: selection[id], nonSelectable: true });
46
- if (!tag.selected) {
47
- tag.nonSelectable = false;
48
- tag.count++;
49
- }
50
- });
51
- });
52
-
53
- tags.push(...Object.values(tagsHash));
54
- tags.sort((a, b) => (a.selected === b.selected ? (a.id > b.id ? 1 : -1) : !a.selected ? 1 : -1));
55
- return actualData;
56
- } else {
57
- tags.push(...initials.split(',').map((id) => ({ id, name: id, count: 0, selected: false })));
58
- data.forEach((e: Item) => {
59
- tags.forEach((tag) => {
60
- if (e.getLabels().has(tag.id)) {
61
- tag.count++;
62
- }
63
- });
64
- });
65
- return data;
66
- }
67
- };
68
-
69
- export const getSuggestions = (data: Item[], kluq: string) => {
70
- if (!kluq) return null;
71
-
72
- const words: Hash = {};
73
- data.forEach((e) =>
74
- e.$searchData.replace(/[a-z0-9а-яёўі]{3,}/g, (w) => {
75
- words[w] = 1;
76
- return '';
77
- })
78
- );
79
- const lokey = kluq.toLowerCase();
80
- return arraySortBy(
81
- Object.keys(words)
82
- .filter((e) => e.startsWith(lokey))
83
- .map((s) => ({ name: s, id: s })),
84
- 'name'
85
- );
86
- };
87
- export const applyNavInfo = (data: Item[]) => {
88
- let prev: Item | null = null;
89
- return !data
90
- ? null
91
- : data.map((item: Item, index) => {
92
- item.$navInfo = { prev, index, total: data.length };
93
- if (prev?.$navInfo) {
94
- prev.$navInfo.next = item;
95
- }
96
- prev = item;
97
- return item;
98
- });
99
- };
100
-
101
- export class Item implements Delta {
102
- id?: string;
103
- $row?: string;
104
- $: Hash<any> = {};
105
- $navInfo?: { index: number; total: number; prev?: Item | null; next?: Item };
106
- [key: string]: unknown;
107
-
108
- constructor({ $, ...opts }: Delta = {}) {
109
- Object.assign(this, opts);
110
- }
111
- getLabels(): Set<string> {
112
- const key = 'labels';
113
- return (
114
- this.$[key] ||
115
- (this.$[key] = new Set(
116
- String(this[key] || this.labels)
117
- .split(',')
118
- .filter(Boolean)
119
- ))
120
- );
121
- }
122
- get $searchData() {
123
- if (!('searchData' in this.$)) {
124
- this.$.searchData = String(this.searchData || this.name).toLowerCase();
125
- }
126
- return this.$.searchData;
127
- }
128
- get $dataset() {
129
- const { $row, $navInfo, $, ...d } = this;
130
- return d;
131
- }
132
- }
@@ -1,113 +0,0 @@
1
- import { Service } from '../services/Service';
2
- import { analyzeDataByTags, getSuggestions, applyNavInfo, Item } from './Item';
3
- import { arraySortBy } from '../../lib';
4
- import { Storage } from '../services/Storage';
5
-
6
- export class ItemCollectionController extends Service {
7
- selection: Hash<boolean> = {};
8
- shownLimit = 40;
9
- sortBy = 'title';
10
- kluqPoshuku = '';
11
- data?: Item[] | null;
12
- inputKluqPoshuku = '';
13
- newEntry: any;
14
- entry: any;
15
- initials = '';
16
- tags: Tag[] = [];
17
-
18
- local = new Storage('local');
19
-
20
- get searchHistory(): string[] {
21
- const str = this.local?.get('searchHistory');
22
- return str ? str.split(',') : null;
23
- }
24
-
25
- get searchSuggestion() {
26
- if (this.kluqPoshuku === this.inputKluqPoshuku) {
27
- if (this.kluqPoshuku) return null;
28
- return this.searchHistory?.map((s: string) => ({ name: s, id: s }));
29
- }
30
- return this.data ? getSuggestions(this.data, this.inputKluqPoshuku) : null;
31
- }
32
-
33
- set searchHistory(arr) {
34
- this.local?.set('searchHistory', (arr || []).join(','));
35
- }
36
- addHistoryItem(value: string) {
37
- if (value) {
38
- this.searchHistory = [value, ...(this.searchHistory || []).filter((s) => s != value)].slice(0, 20);
39
- }
40
- }
41
- setData(data: Hash[]) {
42
- this.data = !data ? null : data.map((e) => new Item(e));
43
- }
44
-
45
- get sortedData() {
46
- const data = this.data;
47
- if (!data) return null;
48
-
49
- const searchedData = !this.kluqPoshuku ? data : data.filter((e) => e.$searchData.startsWith(this.kluqPoshuku));
50
- this.tags = [];
51
- const actualData = analyzeDataByTags(searchedData, this.selection, this.initials, this.tags, this.labelsFields);
52
- return applyNavInfo(arraySortBy(actualData, this.sortBy) as unknown as Item[]);
53
- }
54
-
55
- getInfo() {
56
- const data = this.sortedData;
57
- if (!data) {
58
- return {};
59
- }
60
- return {
61
- tags: this.tags,
62
- data: this.sortedData?.slice(0, this.shownLimit),
63
- counts: {
64
- total: data.length,
65
- actual: data.length,
66
- hasMore: this.shownLimit < data.length,
67
- },
68
- search: {
69
- kluq: this.kluqPoshuku,
70
- value: this.inputKluqPoshuku,
71
- suggestions: this.searchSuggestion,
72
- input: this.createEmitter('this.inputKluq'),
73
- enter: this.createEmitter('this.znajdzPoKluqu'),
74
- },
75
- };
76
- }
77
-
78
- // event handlers:
79
-
80
- onTag({ id }: Tag, { selection }: this) {
81
- return {
82
- selection: !id
83
- ? {}
84
- : {
85
- ...selection,
86
- [id]: !selection[id],
87
- },
88
- };
89
- }
90
- onZnajdzPoKluqu({ value }: StringHash) {
91
- this.addHistoryItem(value);
92
-
93
- return {
94
- inputKluqPoshuku: value || '',
95
- kluqPoshuku: value || '',
96
- };
97
- }
98
- onInputKluq({ value }: Delta) {
99
- return {
100
- inputKluqPoshuku: value || '',
101
- };
102
- }
103
- onSortBy({ value }: Delta) {
104
- return {
105
- sortBy: value || '-title',
106
- };
107
- }
108
- onShowMore({ size = 50 }, { shownLimit }: this) {
109
- return {
110
- shownLimit: shownLimit + size,
111
- };
112
- }
113
- }