arrmatura 4.2.2 → 5.0.2

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 (93) hide show
  1. package/README.md +30 -25
  2. package/commons/auth/UserService.ts +107 -0
  3. package/commons/auth/index.ts +4 -0
  4. package/commons/auth/user.xml +66 -0
  5. package/commons/components/buttons.xml +23 -0
  6. package/commons/components/index.ts +13 -0
  7. package/commons/components/inputs.xml +33 -0
  8. package/commons/components/layouts.xml +77 -0
  9. package/commons/components/media.xml +21 -0
  10. package/commons/components/modal.xml +37 -0
  11. package/commons/components/navigation.xml +59 -0
  12. package/commons/components/popups.xml +12 -0
  13. package/commons/components/searchbar.xml +40 -0
  14. package/commons/components/table.xml +120 -0
  15. package/commons/components/toasts.xml +13 -0
  16. package/commons/components/viewport.xml +48 -0
  17. package/commons/forms/FieldItem.xml +20 -0
  18. package/commons/forms/FormController.ts +73 -0
  19. package/commons/forms/Item.ts +68 -0
  20. package/commons/forms/ItemCollection.ts +152 -0
  21. package/commons/forms/ItemController.ts +62 -0
  22. package/commons/forms/MetadataService.ts +45 -0
  23. package/commons/forms/MultiEnumField.xml +44 -0
  24. package/commons/forms/MultivalueController.ts +49 -0
  25. package/commons/forms/SmartareaField.xml +51 -0
  26. package/commons/forms/SuggestionController.ts +80 -0
  27. package/commons/forms/SuggestionField.xml +28 -0
  28. package/commons/forms/fields.xml +138 -0
  29. package/commons/forms/form.xml +37 -0
  30. package/commons/forms/index.ts +29 -0
  31. package/commons/index.ts +6 -0
  32. package/commons/pipes/index.ts +21 -0
  33. package/commons/pipes/showCounts.ts +16 -0
  34. package/commons/services/ErrorHandlingService.ts +11 -0
  35. package/commons/services/FirebaseService.ts +185 -0
  36. package/commons/services/GSheetsService.ts +92 -0
  37. package/commons/services/HeartbeatService.ts +35 -0
  38. package/commons/services/Invoke.ts +13 -0
  39. package/commons/services/JsonpService.ts +18 -0
  40. package/commons/services/Loader.ts +17 -0
  41. package/commons/services/LocalStorage.ts +46 -0
  42. package/commons/services/NavigationService.ts +55 -0
  43. package/commons/services/Service.ts +68 -0
  44. package/commons/services/ServiceWorker.ts +111 -0
  45. package/commons/services/ToastService.ts +49 -0
  46. package/commons/services/index.ts +14 -0
  47. package/core/Arrmatura.ts +79 -0
  48. package/core/CNode.ts +112 -0
  49. package/core/Component.ts +431 -0
  50. package/core/Platform.ts +3 -0
  51. package/core/cnodes/composition.ts +100 -0
  52. package/core/cnodes/conditionals.ts +51 -0
  53. package/core/cnodes/elementary.ts +45 -0
  54. package/core/cnodes/iterations.ts +94 -0
  55. package/core/cnodes/routing.ts +25 -0
  56. package/core/compileNode.ts +39 -0
  57. package/core/expression.ts +73 -0
  58. package/core/index.ts +2 -0
  59. package/core/register.ts +43 -0
  60. package/core/utils.ts +25 -0
  61. package/core-web/WebPlatform.ts +25 -0
  62. package/core-web/attributes.ts +197 -0
  63. package/core-web/elements.ts +64 -0
  64. package/core-web/index.ts +19 -0
  65. package/core-web/reflow.ts +30 -0
  66. package/core-web/type.ts +19 -0
  67. package/docs/index.js +191 -4077
  68. package/lib/arrayGroupBy.ts +19 -0
  69. package/lib/arraySortBy.ts +49 -0
  70. package/lib/arrayToObject.ts +18 -0
  71. package/lib/codus.ts +25 -0
  72. package/lib/consts.ts +3 -0
  73. package/lib/createListeners.ts +12 -0
  74. package/lib/curry.ts +8 -0
  75. package/lib/dateFormat.ts +69 -0
  76. package/lib/dateOf.ts +31 -0
  77. package/lib/dateParseISO8601.ts +27 -0
  78. package/lib/fx.ts +62 -0
  79. package/lib/guid.ts +7 -0
  80. package/lib/index.ts +19 -0
  81. package/lib/l10n.ts +40 -0
  82. package/lib/nextId.ts +9 -0
  83. package/lib/noop.ts +8 -0
  84. package/lib/obj.ts +62 -0
  85. package/lib/predicat.ts +26 -0
  86. package/lib/resources.ru.ts +20 -0
  87. package/lib/resources.ts +20 -0
  88. package/lib/scalar.ts +25 -0
  89. package/lib/str.ts +76 -0
  90. package/lib/urls.ts +91 -0
  91. package/lib/xml.ts +117 -0
  92. package/package.json +17 -8
  93. package/docs/index.js.map +0 -1
@@ -0,0 +1,49 @@
1
+ import { arrayToObject } from '../../lib';
2
+ import { Service } from '../services/Service';
3
+
4
+ export class MultivalueController extends Service {
5
+ value: string = '';
6
+ options: any;
7
+ optionsHash?: Data;
8
+
9
+ get items() {
10
+ return this.bits.map((id) => ({ id, name: this.optionsHash?.[id]?.name || id }));
11
+ }
12
+
13
+ get bits() {
14
+ return this.value ? this.value.split(',') : [];
15
+ }
16
+
17
+ setOptions(opts: Obj[]) {
18
+ this.optionsHash = arrayToObject(opts);
19
+ this.options = opts;
20
+ }
21
+
22
+ get availableOptions() {
23
+ const result = this.options?.filter((e) => !this.value?.includes(e.id));
24
+ return result?.length ? result : null;
25
+ }
26
+
27
+ change(data?: Hash) {
28
+ throw new Error('change not defined.' + data);
29
+ }
30
+
31
+ onToggleOpened(_: never, { opened = false }) {
32
+ return { opened: !opened };
33
+ }
34
+
35
+ onReset() {
36
+ this.change({ value: '' });
37
+ }
38
+
39
+ onInsert({ value = '' }) {
40
+ this.change({ value: [...this.bits, value].join(',') });
41
+ return { opened: false };
42
+ }
43
+
44
+ onDelete({ value = '' }) {
45
+ this.change({
46
+ value: this.bits.filter((e) => e !== value).join(','),
47
+ });
48
+ }
49
+ }
@@ -0,0 +1,51 @@
1
+ <component id="Smartarea">
2
+ <div class="form-autocomplete" ui:if="options">
3
+
4
+ <ui:fragment ui:if="disabled">
5
+ <ui:then>
6
+ <div class="form-input" style="height: auto;min-height:1.5rem;">{value}</div>
7
+ </ui:then>
8
+ <ui:else>
9
+ <div class="has-icon-right" click="{open|bindArgs:true}">
10
+ <div class="form-input text-{value|then::gray}" style="height: auto;min-height:1.5rem;">{value|or:@placeholder}</div>
11
+ <i class="form-icon icon icon-edit c-hand"></i>
12
+ </div>
13
+ </ui:else>
14
+ </ui:fragment>
15
+
16
+ <Modal open="{opened}" mode="sm" ui:if="{opened}" close="{open|bindArgs:false}">
17
+ <Modal:header>
18
+ <h6>{caption}</h6>
19
+ </Modal:header>
20
+
21
+ <div class="form-autocomplete">
22
+ <div class="has-icon-right">
23
+ <textarea class="form-input" style="padding-right: 3rem;height:6rem;" keypress="{change}" value="{value}" />
24
+ <i class="form-icon icon icon-{value|then:cross:arrow-down} c-hand" click="{reset}" ui:if="value"></i>
25
+ </div>
26
+ <div style="overflow-y: auto;height:15rem;margin:0.5rem 0;">
27
+ <ul class="menu2" style="min-width: 180px;list-style: none;">
28
+ <li class="menu-item" ui:for="item of options" data-value="{item.id}" click="{append}">
29
+ <a>
30
+ <div class="tile tile-centered">
31
+ <div class="tile-content">🏷️ {item.name|or:@item.id}</div>
32
+ </div>
33
+ </a>
34
+ </li>
35
+ </ul>
36
+ </div>
37
+ </div>
38
+
39
+ <Modal:footer>
40
+ <Btn class="float-right" mode="primary" title="OK" action="{open|bindArgs:false}"/>
41
+ </Modal:footer>
42
+ </Modal>
43
+ </div>
44
+ </component>
45
+
46
+ <component id="SmartareaField">
47
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
48
+ <SuggestionController ui:ref="ctrl" options="{options}" value="{value}" change="{onChange}"/>
49
+ <Smartarea caption="{caption}" disabled="{disabled}" options="<-ctrl.smartOptions" value="{value}" disabled="{disabled}" opened="{opened}" open="->opened" placeholder="{placeholder}" change="->ctrl.change" append="->ctrl.append" reset="->ctrl.cutLast"/>
50
+ </FieldItem>
51
+ </component>
@@ -0,0 +1,80 @@
1
+ import { Service } from '../services/Service';
2
+
3
+ export class SuggestionController extends Service {
4
+ value: string = '';
5
+ options: any;
6
+
7
+ get bits() {
8
+ return this.value
9
+ ? this.value
10
+ .split(',')
11
+ .map((s) => s.trim())
12
+ .filter(Boolean)
13
+ : [];
14
+ }
15
+
16
+ get lastBitIncluded() {
17
+ let bits = this.bits;
18
+ if (!bits.length) return false;
19
+ const lastBit = bits[bits.length - 1] || '';
20
+ return this.options.some((e) => e.id === lastBit);
21
+ }
22
+
23
+ get smartOptions() {
24
+ let options = this.options;
25
+ let bits = this.bits;
26
+ if (!bits.length) return options;
27
+ const lastBit = bits[bits.length - 1] || '';
28
+ if (lastBit.length > 1 && !this.lastBitIncluded) {
29
+ const keyword = lastBit.toLowerCase();
30
+ options = options.filter((e) => (e.name || e.id).toLowerCase().includes(keyword));
31
+ // if (options2.length) {
32
+ // options = options2;
33
+ // }
34
+ }
35
+
36
+ options = options.filter((e) => !bits.includes(e.id));
37
+ // if (options2.length === 1 && value === options2[0].id) return null;
38
+ return options.slice(0, 15);
39
+ }
40
+
41
+ get actualOptions() {
42
+ const options = this.options;
43
+ const value = String(this.value || '');
44
+ const keyword = value?.toLowerCase() || '';
45
+ if (keyword.length < 2) return options.slice(0, 15);
46
+ const options2 = options.filter((e) => (e.name || e.id).toLowerCase().startsWith(keyword));
47
+ if (options2.length === 1 && value === options2[0].id) return null;
48
+ return options2.slice(0, 15);
49
+ }
50
+
51
+ change(data?: Hash) {
52
+ throw new Error('change not defined.' + data);
53
+ }
54
+
55
+ onOpen(opened = false) {
56
+ return { opened };
57
+ }
58
+
59
+ onReset() {
60
+ this.change({ value: '' });
61
+ }
62
+
63
+ onAppend({ value = '' }) {
64
+ const bits = this.bits;
65
+ if (!this.lastBitIncluded) {
66
+ bits.pop();
67
+ }
68
+ this.change({ value: [...bits, value].join(', ') });
69
+ }
70
+ onCutLast() {
71
+ const bits = this.bits;
72
+ bits.pop();
73
+ this.change({ value: [...bits].join(', ') });
74
+ }
75
+
76
+ onChange({ value = '' }) {
77
+ this.change({ value });
78
+ return { opened: false };
79
+ }
80
+ }
@@ -0,0 +1,28 @@
1
+ <component id="SuggestionInput">
2
+ <div class="form-autocomplete">
3
+ <div class="has-icon-right">
4
+ <input class="form-input" type="text" disabled="{disabled}" placeholder="{placeholder}" focus="{open|bindArgs:true}" blur2="{open|bindArgs:false|nextTick:200}" keypress="{change}" value="{value}" />
5
+ <i class="form-icon icon icon-{value|then:cross:arrow-down} c-hand" style="margin-right:{options.length|then:1.5:0.25}rem;" click="{reset}" ui:if="disabled|not|and:@value"></i>
6
+ <ui:fragment ui:if="options|and:@options.length">
7
+ <i class="form-icon icon icon-arrow-up c-hand" click="{open|bindArgs:false}" ui:if="opened"></i>
8
+ <i class="form-icon icon icon-arrow-down c-hand" click="{open|bindArgs:true}" ui:if="opened|not"></i>
9
+ </ui:fragment>
10
+ </div>
11
+ <ul class="menu" style="position2: relative; min-width: 180px;max-height:7rem;overflow-y:auto;" ui:if="{options|and:@options.length|and:@opened}">
12
+ <li class="menu-item" ui:for="item of options" data-value="{item.id}" click="{change}">
13
+ <a>
14
+ <div class="tile tile-centered">
15
+ <div class="tile-content">{item.name|or:@item.id}</div>
16
+ </div>
17
+ </a>
18
+ </li>
19
+ </ul>
20
+ </div>
21
+ </component>
22
+
23
+ <component id="SuggestionField">
24
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
25
+ <SuggestionController ui:ref="ctrl" options="{options}" value="{value}" change="{onChange}"/>
26
+ <SuggestionInput options="<-ctrl.actualOptions" value="{value}" opened="<- ctrl.opened" open="-> ctrl.open" disabled="{disabled}" change="->ctrl.change" append="->ctrl.change" reset="->ctrl.reset"/>
27
+ </FieldItem>
28
+ </component>
@@ -0,0 +1,138 @@
1
+ <component id="Field">
2
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
3
+ <input class="form-input" type="text" disabled="{disabled}" placeholder="{placeholder}" value="{value}" change="{onChange}" />
4
+ </FieldItem>
5
+ </component>
6
+
7
+ <component id="TextField">
8
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
9
+ <input class="form-input" type="text" disabled="{disabled}" placeholder="{placeholder}" value="{value}" change="{onChange}" />
10
+ </FieldItem>
11
+ </component>
12
+
13
+ <component id="UrlField">
14
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
15
+ <div class="input-group has-icon-right">
16
+ <ui:fragment ui:if="{editing}">
17
+ <ui:then>
18
+ <input class="form-input border-0" disabled="{disabled}" type="url" blur="-> editing|not" placeholder="{caption}" value="{value}" change="{onChange}" />
19
+ <button class="btn btn-primary input-group-btn" click="-> editing|not">
20
+ <i class="icon icon-check"/>
21
+ </button>
22
+ </ui:then>
23
+ <ui:else>
24
+ <a class="form-input text-ellipsis" href="{value}" target="{id}">{value}</a>
25
+ <i class="form-icon icon icon-edit c-hand" click="->editing" ui:if="disabled|not"/>
26
+ </ui:else>
27
+ </ui:fragment>
28
+ </div>
29
+ </FieldItem>
30
+ </component>
31
+
32
+ <component id="EmailField">
33
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
34
+ <input class="form-input" disabled="{disabled}" type="email" placeholder="{caption}" value="{value}" change="{onChange}" />
35
+ </FieldItem>
36
+ </component>
37
+
38
+ <component id="DateField">
39
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
40
+ <input class="form-input" disabled="{disabled}" type="date" placeholder="{caption}" value="{value}" change="{onChange}" />
41
+ </FieldItem>
42
+ </component>
43
+
44
+ <component id="DateAndWorkingHoursField">
45
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
46
+ <div class="flexoid">
47
+ <input class="form-input" disabled="{disabled}" type="date" placeholder="{caption}" value="{value}" change="{onChange}" />
48
+ <Select class="form-select ml-2" disabled="{disabled}" change="{onChange|assignFirstArgKeyValue:key:@typeSpec}" emptyCaption="-" value="{data|dot:@typeSpec}" options=":enums.workingHours" />
49
+ </div>
50
+ </FieldItem>
51
+ </component>
52
+
53
+
54
+ <component id="DateAndTimeField">
55
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
56
+ <div class="flexoid">
57
+ <input class="form-input flex-1" disabled="{disabled}" type="date" placeholder="{caption}" value="{value}" change="{onChange}" />
58
+ <input class="form-input flex-1 ml-2" disabled="{value|not|or:@disabled}" type="text" value="{data|dot:@typeSpec}" change="{onChange|assignFirstArgKeyValue:key:@typeSpec}" />
59
+ </div>
60
+ </FieldItem>
61
+ </component>
62
+
63
+ <component id="DatePeriodField">
64
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
65
+ <div class="flexoid">
66
+ <input class="form-input flex-1" disabled="{disabled}" type="date" placeholder="{caption}" value="{value}" change="{onChange}" />
67
+ <input class="form-input flex-1 ml-2" style="min-width: 6rem;" disabled="{value|not|or:@disabled}" type="date" value="{data|dot:@typeSpec}" change="{onChange|assignFirstArgKeyValue:key:@typeSpec}" />
68
+ </div>
69
+ </FieldItem>
70
+ </component>
71
+
72
+ <component id="DateTimeField">
73
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
74
+ <input class="form-input" disabled="{disabled}" type="datetime-local" placeholder="{caption}" value="{value|dateFormat:yyyy-mm-ddTt}" change="{onChange}" />
75
+ </FieldItem>
76
+ </component>
77
+
78
+ <component id="TimeField">
79
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
80
+ <input class="form-input" disabled="{disabled}" type="text" placeholder="{caption}" value="{value|dateFormat:t}" change="{onChange}" />
81
+ </FieldItem>
82
+ </component>
83
+
84
+ <component id="NumberField">
85
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
86
+ <input class="form-input" disabled="{disabled}" type="number" placeholder="{placeholder}" value="{value}" change="{onChange}" />
87
+ </FieldItem>
88
+ </component>
89
+
90
+ <component id="TextareaField">
91
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
92
+ <textarea class="form-input" style="min-height: 15vw" disabled="{disabled}" placeholder="{placeholder}" rows="3" change="{onChange}" value="{value}"></textarea>
93
+ </FieldItem>
94
+ </component>
95
+
96
+ <component id="BooleanField">
97
+ <div class="form-group">
98
+ <div class="col-sm-12 flexoid">
99
+ <label class="form-switch">
100
+ <span>{caption}</span>
101
+ <input type="checkbox" toggle="{onChange}" checked="{value|truthy}" />
102
+ <i class="form-icon"></i>
103
+ </label>
104
+ <div class="border-1 centroid text-secondary s-circle m-1 px-2" style="height:1.3rem; width:1.3rem;" data-value="{showHint|not}" click="->showHint|dot:value" ui:if="{hint}">
105
+ <span>?</span>
106
+ </div>
107
+ </div>
108
+ <div class="col-12" ui:if="{showHint}">
109
+ <p class="form-input-hint mb-1">{hint}</p>
110
+ </div>
111
+ </div>
112
+ </component>
113
+
114
+ <component id="EnumField">
115
+ <FieldItem caption="{caption}" class={class} error="{error}" required="{required}" hint="{hint}">
116
+ <Select class="form-select" change="{onChange}" value="{value|or:@defaultValue}" options="{options}" emptyCaption="{emptyCaption}" disabled="{disabled}" />
117
+ </FieldItem>
118
+ </component>
119
+
120
+ <component id="SelectField">
121
+ <FieldItem caption="{caption}" class="{class}" error="{error}" required="{required}" hint="{hint}">
122
+ <Select class="form-select" change="{onChange}" value="{value|or:@defaultValue}" options="{options}" emptyCaption="{emptyCaption}" disabled="{disabled}" />
123
+ </FieldItem>
124
+ </component>
125
+
126
+ <component id="ChoiceField">
127
+ <FieldItem caption="{caption}" class="{class}" error="{error}" required="{required}" hint="{hint}">
128
+ <div class="btn-group btn-group-block">
129
+ <Btn title="{option.name}" class="option-{value}{option.id|equals:@value|then::-other}" mode="{option.id|equals:@value|then:primary}" data="{option.id|pack:value}" action="{onChange}" ui:for="option of options"/>
130
+ </div>
131
+ </FieldItem>
132
+ </component>
133
+
134
+ <component id="RadioField">
135
+ <FieldItem caption="{caption}" error="{error}" required="{required}" hint="{hint}">
136
+ <RadioGroup change="{onChange}" value="{value}" data="{data}" options=":enums.{typeSpec}" disabled="{disabled}" />
137
+ </FieldItem>
138
+ </component>
@@ -0,0 +1,37 @@
1
+ <component id="Forma">
2
+ <div autocomplete="off" class="form-horizontal px-2">
3
+ <FormController ui:ref="formController" data="{data}" meta="{meta}" change="{change}" />
4
+ <ui:tag tag="{field.type|capitalize}Field" ui:props="{field}" ui:if="field.visible" ui:for="field of <-formController.fieldProps" />
5
+ </div>
6
+ </component>
7
+
8
+ <component id="GroupForma">
9
+ <div autocomplete="off" class="form-horizontal px-2">
10
+ <FormController ui:ref="formController" data="{data}" meta="{meta}" change="{change}" />
11
+ <FieldGroup ui:props="{group}" ui:for="group of <-formController.fieldsGroups" />
12
+ </div>
13
+ </component>
14
+
15
+ <component id="TabbedForma">
16
+ <div autocomplete="off" class="form-horizontal px-2">
17
+ <FormController ui:ref="formController" data="{data}" meta="{meta}" change="{change}" />
18
+ <TabbedFormaContent ui:props="<-formController.tabsInfo" />
19
+ </div>
20
+ </component>
21
+
22
+ <component id="TabbedFormaContent">
23
+ <Tabs items="{tabs}" change="->tabId|dot:id" value="{tabId|or:@initialTab}" ui:if="enabled" />
24
+ <div ui:if="tabId|or:@initialTab|equals:@tab.id" ui:for="tab of tabs">
25
+ <FieldGroup ui:props="{group}" ui:for="group of tab.items" />
26
+ </div>
27
+ </component>
28
+
29
+ <component id="FieldGroup">
30
+ <h6 class="mt-2 pt-2 px-2 text-gray">{id}</h6>
31
+ <div class="panel">
32
+ <div class="p-2">
33
+ <ui:tag tag="{field.type|capitalize}Field" ui:props="{field}" ui:if="field.visible" ui:for="field of items" />
34
+ </div>
35
+ </div>
36
+ </component>
37
+
@@ -0,0 +1,29 @@
1
+ import { FormController } from './FormController';
2
+ import { ItemCollection } from './ItemCollection';
3
+ import { ItemController } from './ItemController';
4
+ import { MetadataService } from './MetadataService';
5
+ import { MultivalueController } from './MultivalueController';
6
+ import { SuggestionController } from './SuggestionController';
7
+
8
+ import form from './form.xml';
9
+ import fields from './fields.xml';
10
+
11
+ import SuggestionField from './SuggestionField.xml';
12
+ import SmartareaField from './SmartareaField.xml';
13
+ import FieldItem from './FieldItem.xml';
14
+ import MultiEnumField from './MultiEnumField.xml';
15
+
16
+ export default [
17
+ form,
18
+ fields,
19
+ SuggestionField,
20
+ SmartareaField,
21
+ FieldItem,
22
+ MultiEnumField,
23
+ FormController,
24
+ ItemCollection,
25
+ ItemController,
26
+ MetadataService,
27
+ MultivalueController,
28
+ SuggestionController,
29
+ ];
@@ -0,0 +1,6 @@
1
+ import components from './components';
2
+ import * as services from './services';
3
+ import forms from './forms';
4
+ import auth from './auth';
5
+
6
+ export default [...components, ...forms, ...auth, ...Object.values(services)];
@@ -0,0 +1,21 @@
1
+ import * as libs from '../../lib';
2
+ import { dateFormat, dateOf, urlParse } from '../../lib';
3
+ import { showCounts } from './showCounts';
4
+
5
+ // useful pipes
6
+ export const pipes: Hash<Pipe> = {
7
+ ...libs,
8
+
9
+ queryParam: (_, key: string) => urlParse(window.location.search).params[key],
10
+ queryParams: (_) => urlParse(window.location.search).params,
11
+ date: (x) => dateFormat(dateOf(x)),
12
+ rest: (x, pos = 1) => (x ? x.slice(pos) : []),
13
+ findByKey: (x, key, val) => x?.find?.((e: Hash) => e[key] == val),
14
+ filterByKey: (x, key, val) => x?.filter?.((e: Hash) => !e[key] || e[key] == val),
15
+ resolve: (x, id, key) => x?.find?.((e: Hash) => e.id == id)?.[key],
16
+ limit: (x, size = 50) => (x ? x.slice(0, size) : []),
17
+ ifAbove: (x, limit = 0) => (+x > +limit ? x : ''),
18
+ showCounts,
19
+ };
20
+
21
+ export default pipes;
@@ -0,0 +1,16 @@
1
+ export const showCounts = (counts: Obj, nicNema = '⭕', neZnojdzeno = '⭕') => {
2
+ if (!counts) {
3
+ return '…';
4
+ }
5
+ const { total, actual } = counts;
6
+ if (!total) {
7
+ return nicNema;
8
+ }
9
+ if (!actual) {
10
+ return neZnojdzeno;
11
+ }
12
+ if (actual === total) {
13
+ return total;
14
+ }
15
+ return actual + ' / ' + total;
16
+ };
@@ -0,0 +1,11 @@
1
+ import { Service } from './Service';
2
+
3
+ export class ErrorHandlingService extends Service {
4
+ handleError({ message = '', code, source = {} }) {
5
+ this.show({ message: source + ': ' + message, code, mode: 'error' });
6
+ }
7
+
8
+ show({ message = '', code = '', source }) {
9
+ console.error(source + ': ERROR: ', code, message);
10
+ }
11
+ }
@@ -0,0 +1,185 @@
1
+ import { Service } from './Service';
2
+
3
+ const unpackDocs = (s) =>
4
+ s.docs.reduce((r, e) => {
5
+ const d = e.data();
6
+ d.id = e.id;
7
+ r.push(d);
8
+ return r;
9
+ }, []);
10
+
11
+ const firebase = window.firebase;
12
+
13
+ export class FirebaseService {
14
+ firestore: any;
15
+ auth: any;
16
+ providers: { Google: any };
17
+
18
+ constructor({ ...config }) {
19
+ firebase.initializeApp(config);
20
+ this.firestore = firebase.firestore();
21
+ this.firestore.enablePersistence({ synchronizeTabs: true }).catch(function (err) {
22
+ if (err.code === 'failed-precondition') {
23
+ // Multiple tabs open, persistence can only be enabled
24
+ // in one tab at a time.
25
+ // ...
26
+ } else if (err.code === 'unimplemented') {
27
+ // The current browser does not support all of the
28
+ // features required to enable persistence
29
+ // ...
30
+ }
31
+ });
32
+ this.auth = firebase.auth();
33
+ this.auth.languageCode = 'by';
34
+ this.providers = {
35
+ Google: firebase.auth.GoogleAuthProvider,
36
+ };
37
+ }
38
+
39
+ done() {
40
+ firebase.app().delete();
41
+ }
42
+ // auth
43
+ signInAnonymously() {
44
+ this.auth.signInAnonymously().catch(function (error) {
45
+ var errorMessage = error.message;
46
+ console.log('signInAnonymously err=' + errorMessage);
47
+ });
48
+ }
49
+ listenUser(cb) {
50
+ this.auth.onAuthStateChanged(cb);
51
+ }
52
+ getCurrentUser() {
53
+ return this.auth.currentUser;
54
+ }
55
+ linkProvider() {
56
+ const provider = new this.providers.Google();
57
+ provider.addScope('https://www.googleapis.com/auth/contacts.readonly');
58
+ return this.auth
59
+ .signInWithPopup(provider)
60
+ .then((result) => {
61
+ // This gives you a Google Access Token. You can use it to access the Google API.
62
+ // var token = result.credential.accessToken
63
+ // The signed-in user info.
64
+ var googleUser = result.user;
65
+ var credential = this.auth.GoogleAuthProvider.credential(googleUser.getAuthResponse().id_token);
66
+ this.getCurrentUser()
67
+ .linkAndRetrieveDataWithCredential(credential)
68
+ .then(
69
+ function (usercred) {
70
+ var user = usercred.user;
71
+ console.log('Anonymous account successfully upgraded', user);
72
+ },
73
+ function (error) {
74
+ console.log('Error upgrading anonymous account', error);
75
+ }
76
+ );
77
+ // ...
78
+ })
79
+ .catch(function (error) {
80
+ // Handle Errors here.
81
+ // var errorCode = error.code
82
+ var errorMessage = error.message;
83
+ // The email of the user's account used.
84
+ // var email = error.email
85
+ // The firebase.auth.AuthCredential type that was used.
86
+ // var credential = error.credential
87
+ console.log('signInAnonymously err=' + errorMessage);
88
+ // ...
89
+ });
90
+ }
91
+ logout() {
92
+ return this.signInAnonymously();
93
+ }
94
+ getCollection(coll, since) {
95
+ return ((c) => (since ? c.where('modified_at', '>', since) : c))(this.firestore.collection(coll))
96
+ .get()
97
+ .then(unpackDocs);
98
+ }
99
+ // db
100
+ listenCollection(coll, ts = 0, cb) {
101
+ return ((c) => (ts ? c.where('modified_at', '>', ts) : c))(this.firestore.collection(coll)).onSnapshot(function (
102
+ querySnapshot
103
+ ) {
104
+ var r = [];
105
+ querySnapshot.forEach(function (e) {
106
+ const d = e.data();
107
+ d.id = e.id;
108
+ r.push(d);
109
+ });
110
+ cb(null, { [coll]: r });
111
+ });
112
+ }
113
+ nextId(coll) {
114
+ return this.firestore.collection(coll).doc().id;
115
+ }
116
+ update(delta) {
117
+ const now = Date.now().valueOf();
118
+ // Get a new write batch
119
+ var batch = this.firestore.batch();
120
+ Object.keys(delta).forEach((coll) => {
121
+ const c = this.firestore.collection(coll);
122
+ delta[coll].forEach((d) => {
123
+ d.modified_at = now;
124
+ if (!d.created_at) {
125
+ d.created_at = now;
126
+ }
127
+ var ref = c.doc('' + d.id);
128
+ batch.set(ref, d, { merge: true });
129
+ });
130
+ });
131
+ return batch.commit();
132
+ }
133
+ }
134
+
135
+ export class FirebaseUserService extends Service {
136
+ get fb() {
137
+ return this.$.app.firebase.impl;
138
+ }
139
+ init() {
140
+ this.fb.listenUser((user) => {
141
+ if (user) {
142
+ } else {
143
+ this.fb.signInAnonymously();
144
+ // User is signed out.
145
+ // ...
146
+ }
147
+ this.emit('signed');
148
+ });
149
+ }
150
+ getInfo() {
151
+ const user = this.fb.getCurrentUser();
152
+ if (user !== null) {
153
+ user.providerData.forEach(function (profile) {
154
+ // console.log('Sign-in provider: ' + profile.providerId)
155
+ // console.log(' Provider-specific UID: ' + profile.uid)
156
+ // console.log(' Name: ' + profile.displayName)
157
+ // console.log(' Email: ' + profile.email)
158
+ // console.log(' Photo URL: ' + profile.photoURL)
159
+ });
160
+ }
161
+ return !user
162
+ ? { isLoading: true }
163
+ : {
164
+ ...user,
165
+ isLoading: false,
166
+ };
167
+ }
168
+ onLogin() {
169
+ return this.fb.linkProvider();
170
+ }
171
+ onLogout() {
172
+ return this.fb.logout();
173
+ }
174
+ onSigned() {
175
+ return {
176
+ _: NaN,
177
+ };
178
+ }
179
+ }
180
+
181
+ const hot = typeof module === 'undefined' ? null : module.hot;
182
+ if (hot) {
183
+ // hot.addStatusHandler(function (d) {})
184
+ hot.accept();
185
+ }