@projectcaluma/ember-form 14.8.3 → 14.9.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.
- package/addon/components/cf-content.hbs +44 -39
- package/addon/components/cf-content.js +50 -14
- package/addon/components/cf-field/input/checkbox.hbs +3 -1
- package/addon/components/cf-field/input/checkbox.js +15 -0
- package/addon/components/cf-field/input/date.hbs +38 -31
- package/addon/components/cf-field/input/float.hbs +22 -15
- package/addon/components/cf-field/input/integer.hbs +22 -15
- package/addon/components/cf-field/input/number-separator.hbs +19 -12
- package/addon/components/cf-field/input/number-separator.js +10 -2
- package/addon/components/cf-field/input/powerselect.hbs +2 -2
- package/addon/components/cf-field/input/powerselect.js +12 -0
- package/addon/components/cf-field/input/radio.hbs +3 -1
- package/addon/components/cf-field/input/radio.js +15 -0
- package/addon/components/cf-field/input/table.hbs +31 -1
- package/addon/components/cf-field/input/table.js +10 -0
- package/addon/components/cf-field/input/text.hbs +21 -14
- package/addon/components/cf-field/input/textarea.hbs +21 -14
- package/addon/components/cf-field/input-compare/changes-note.hbs +9 -0
- package/addon/components/cf-field/input-compare/text-diff.hbs +6 -0
- package/addon/components/cf-field/input-compare/textarea-diff.hbs +6 -0
- package/addon/components/cf-field/input-compare.hbs +50 -0
- package/addon/components/cf-field/input-compare.js +60 -0
- package/addon/components/cf-field/input.hbs +1 -0
- package/addon/components/cf-field.hbs +17 -8
- package/addon/components/cf-field.js +15 -3
- package/addon/components/cf-form-wrapper.hbs +1 -0
- package/addon/components/cf-form.hbs +1 -0
- package/addon/components/cf-navigation-item.hbs +14 -4
- package/addon/components/cf-navigation.hbs +1 -0
- package/addon/components/timeline-select.hbs +20 -0
- package/addon/gql/queries/document-answers-compare.graphql +157 -0
- package/addon/helpers/get-widget.js +17 -31
- package/addon/instance-initializers/form-widget-overrides.js +6 -0
- package/addon/lib/answer.js +38 -3
- package/addon/lib/compare.js +161 -0
- package/addon/lib/document.js +10 -2
- package/addon/lib/field.js +81 -1
- package/addon/lib/fieldset.js +11 -0
- package/addon/lib/navigation.js +51 -1
- package/addon/lib/parsers.js +57 -5
- package/addon/services/caluma-store.js +12 -0
- package/app/components/cf-field/input-compare/changes-note.js +1 -0
- package/app/components/cf-field/input-compare/text-diff.js +1 -0
- package/app/components/cf-field/input-compare/textarea-diff.js +1 -0
- package/app/components/cf-field/input-compare.js +1 -0
- package/app/components/timeline-select.js +1 -0
- package/app/styles/@projectcaluma/ember-form.scss +1 -0
- package/app/styles/_cf-content-compare.scss +214 -0
- package/app/styles/_cf-navigation.scss +1 -0
- package/package.json +5 -5
- package/translations/de.yaml +9 -0
- package/translations/en.yaml +9 -0
- package/translations/fr.yaml +9 -0
- package/translations/it.yaml +9 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { isEmpty } from "@ember/utils";
|
|
2
|
+
import isEqual from "lodash.isequal";
|
|
3
|
+
|
|
4
|
+
import { decodeId } from "@projectcaluma/ember-core/helpers/decode-id";
|
|
5
|
+
import { parseDocument } from "@projectcaluma/ember-form/lib/parsers";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Serializes a value into a comparable format, ignoring
|
|
9
|
+
* falsey differences.
|
|
10
|
+
*
|
|
11
|
+
* @param {*} v
|
|
12
|
+
* @returns {*}
|
|
13
|
+
*/
|
|
14
|
+
export function comparisonValue(v) {
|
|
15
|
+
// ignore falsey differences.
|
|
16
|
+
if (isEmpty(v)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// compare arrays as sorted serialized strings.
|
|
21
|
+
if (Array.isArray(v)) {
|
|
22
|
+
return v.map(comparisonValue).toSorted().toString();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return v;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Filters a table answer to a comparable format, dropping all
|
|
30
|
+
* non-relevant fields for comparison.
|
|
31
|
+
*
|
|
32
|
+
* @param {*} answer
|
|
33
|
+
* @returns {Object|null}
|
|
34
|
+
*/
|
|
35
|
+
export function filterTableAnswer(answer) {
|
|
36
|
+
const v = { ...answer.node };
|
|
37
|
+
if (isEmpty(v)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// drop question object, but keep the slug for comparison.
|
|
42
|
+
v.questionSlug = v.question?.slug ?? "";
|
|
43
|
+
|
|
44
|
+
// drop all non-relevant fields for value comparison.
|
|
45
|
+
delete v.__typename;
|
|
46
|
+
delete v.historyDate;
|
|
47
|
+
delete v.historyType;
|
|
48
|
+
delete v.historyUserId;
|
|
49
|
+
delete v.question;
|
|
50
|
+
delete v.id;
|
|
51
|
+
delete v.documentId;
|
|
52
|
+
|
|
53
|
+
// flatten all remaining keys as comparable values.
|
|
54
|
+
for (const key of Object.keys(v)) {
|
|
55
|
+
v[key] = comparisonValue(v[key]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return v;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Creates an object that is suitable for comparison, filtering out
|
|
63
|
+
* non-relevant fields and sorting answers by question slug.
|
|
64
|
+
*
|
|
65
|
+
* @param {Object} doc
|
|
66
|
+
* @returns {Object}
|
|
67
|
+
*/
|
|
68
|
+
export function comparableDocument(doc) {
|
|
69
|
+
return {
|
|
70
|
+
id: doc.id,
|
|
71
|
+
answers: {
|
|
72
|
+
edges: doc.answers.edges
|
|
73
|
+
.map((answer) => ({
|
|
74
|
+
node: filterTableAnswer(answer),
|
|
75
|
+
}))
|
|
76
|
+
.toSorted((a, b) => {
|
|
77
|
+
return a.node.questionSlug.localeCompare(b.node.questionSlug);
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compares two table documents for equality.
|
|
85
|
+
*
|
|
86
|
+
* @param {Object} docA
|
|
87
|
+
* @param {Object} docB
|
|
88
|
+
* @returns {Boolean}
|
|
89
|
+
*/
|
|
90
|
+
export function compareTableDocument(docA, docB) {
|
|
91
|
+
return isEqual(comparableDocument(docA), comparableDocument(docB));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Compares current and historical table values, returning
|
|
96
|
+
* a list of documents with correct history types for diffing.
|
|
97
|
+
*
|
|
98
|
+
* @param {*} owner
|
|
99
|
+
* @param {*} field
|
|
100
|
+
* @param {*} value
|
|
101
|
+
* @param {*} historicalValue
|
|
102
|
+
* @returns a set of documents for comparison
|
|
103
|
+
*/
|
|
104
|
+
export function historicalTableValue(owner, field, value, historicalValue) {
|
|
105
|
+
const Document = owner.factoryFor("caluma-model:document").class;
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
value
|
|
109
|
+
.map((document) => {
|
|
110
|
+
let historyType = document.historyType;
|
|
111
|
+
|
|
112
|
+
// find corresponding historical document to compare.
|
|
113
|
+
const historicalDocument = historicalValue?.find(
|
|
114
|
+
(histDoc) => histDoc.id === document.id,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// if the document is marked as removed and there is no historical counterpart,
|
|
118
|
+
// we skip it from the diff view. (the original document does not
|
|
119
|
+
// include documents that were removed before the historical snapshot).
|
|
120
|
+
if (historyType === "-" && !historicalDocument) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (historyType === "~") {
|
|
125
|
+
if (!historicalDocument) {
|
|
126
|
+
// If a document is marked as modified, but has no historical counterpart,
|
|
127
|
+
// it means it was added as a new document in the current set.
|
|
128
|
+
// If there is another document in the historical set with identical
|
|
129
|
+
// flat table values, we treat this as an unchanged document.
|
|
130
|
+
// (when re-added the same with a different id)
|
|
131
|
+
historyType = historicalValue?.find(
|
|
132
|
+
(histDoc) =>
|
|
133
|
+
decodeId(histDoc.id) !== decodeId(document.id) &&
|
|
134
|
+
compareTableDocument(histDoc, document),
|
|
135
|
+
)
|
|
136
|
+
? "="
|
|
137
|
+
: "+";
|
|
138
|
+
} else if (compareTableDocument(document, historicalDocument)) {
|
|
139
|
+
// If the modified document has identical flat table values
|
|
140
|
+
// to the historical document, we treat it as identical.
|
|
141
|
+
historyType = "=";
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return new Document({
|
|
146
|
+
raw: parseDocument({
|
|
147
|
+
...document,
|
|
148
|
+
historicalAnswers: historicalDocument?.answers,
|
|
149
|
+
historyType,
|
|
150
|
+
}),
|
|
151
|
+
parentDocument: field.document,
|
|
152
|
+
historicalDocument: historicalDocument
|
|
153
|
+
? parseDocument(historicalDocument)
|
|
154
|
+
: undefined,
|
|
155
|
+
owner,
|
|
156
|
+
});
|
|
157
|
+
})
|
|
158
|
+
// filter out dropped documents;
|
|
159
|
+
.filter(Boolean)
|
|
160
|
+
);
|
|
161
|
+
}
|
package/addon/lib/document.js
CHANGED
|
@@ -27,19 +27,23 @@ export default class Document extends Base {
|
|
|
27
27
|
parentDocument,
|
|
28
28
|
parentField,
|
|
29
29
|
dataSourceContext,
|
|
30
|
+
historicalDocument,
|
|
31
|
+
compare,
|
|
30
32
|
...args
|
|
31
33
|
}) {
|
|
32
34
|
assert(
|
|
33
35
|
"A graphql document `raw` must be passed",
|
|
34
|
-
raw?.__typename
|
|
36
|
+
raw?.__typename.includes("Document"),
|
|
35
37
|
);
|
|
36
38
|
|
|
37
39
|
super({ raw, ...args });
|
|
38
40
|
|
|
41
|
+
this.historicalDocument = historicalDocument;
|
|
39
42
|
this.parentDocument = parentDocument;
|
|
40
43
|
this.parentField = parentField;
|
|
41
44
|
this.dataSourceContext =
|
|
42
45
|
dataSourceContext ?? parentDocument?.dataSourceContext;
|
|
46
|
+
this.compare = compare ?? parentDocument?.compare;
|
|
43
47
|
|
|
44
48
|
this.pushIntoStore();
|
|
45
49
|
|
|
@@ -66,7 +70,11 @@ export default class Document extends Base {
|
|
|
66
70
|
this,
|
|
67
71
|
this.calumaStore.find(`${this.pk}:Form:${form.slug}`) ||
|
|
68
72
|
new (owner.factoryFor("caluma-model:fieldset").class)({
|
|
69
|
-
raw: {
|
|
73
|
+
raw: {
|
|
74
|
+
form,
|
|
75
|
+
answers: this.raw.answers,
|
|
76
|
+
historicalAnswers: this.historicalDocument?.answers,
|
|
77
|
+
},
|
|
70
78
|
document: this,
|
|
71
79
|
owner,
|
|
72
80
|
}),
|
package/addon/lib/field.js
CHANGED
|
@@ -21,6 +21,7 @@ import saveDocumentTableAnswerMutation from "@projectcaluma/ember-form/gql/mutat
|
|
|
21
21
|
import getDocumentUsedDynamicOptionsQuery from "@projectcaluma/ember-form/gql/queries/document-used-dynamic-options.graphql";
|
|
22
22
|
import refreshAnswerQuery from "@projectcaluma/ember-form/gql/queries/refresh-answer.graphql";
|
|
23
23
|
import Base from "@projectcaluma/ember-form/lib/base";
|
|
24
|
+
import { comparisonValue } from "@projectcaluma/ember-form/lib/compare";
|
|
24
25
|
import dependencies from "@projectcaluma/ember-form/lib/dependencies";
|
|
25
26
|
|
|
26
27
|
export const TYPE_MAP = {
|
|
@@ -114,12 +115,23 @@ export default class Field extends Base {
|
|
|
114
115
|
return;
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
// If comparison is enabled and there is no answer, add the required
|
|
119
|
+
// historical raw answer data, and mark answer as not changed.
|
|
120
|
+
const rawHistorical = this.compare
|
|
121
|
+
? {
|
|
122
|
+
historyType: "=",
|
|
123
|
+
historyDate: this.compare.to,
|
|
124
|
+
}
|
|
125
|
+
: {};
|
|
126
|
+
|
|
117
127
|
answer = new Answer({
|
|
118
128
|
raw: {
|
|
119
129
|
id: null,
|
|
120
130
|
__typename: answerType,
|
|
121
131
|
question: { slug: this.raw.question.slug },
|
|
122
132
|
[camelize(answerType.replace(/Answer$/, "Value"))]: null,
|
|
133
|
+
historical: null,
|
|
134
|
+
...rawHistorical,
|
|
123
135
|
},
|
|
124
136
|
field: this,
|
|
125
137
|
owner,
|
|
@@ -127,7 +139,12 @@ export default class Field extends Base {
|
|
|
127
139
|
} else {
|
|
128
140
|
answer =
|
|
129
141
|
this.calumaStore.find(`Answer:${decodeId(this.raw.answer.id)}`) ||
|
|
130
|
-
new Answer({
|
|
142
|
+
new Answer({
|
|
143
|
+
raw: this.raw.answer,
|
|
144
|
+
historical: this.raw.historicalAnswer,
|
|
145
|
+
field: this,
|
|
146
|
+
owner,
|
|
147
|
+
});
|
|
131
148
|
}
|
|
132
149
|
|
|
133
150
|
this.answer = associateDestroyableChild(this, answer);
|
|
@@ -166,6 +183,14 @@ export default class Field extends Base {
|
|
|
166
183
|
*/
|
|
167
184
|
_components = new Set();
|
|
168
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Get the compare context via the fieldset.
|
|
188
|
+
* @property {Object} compare
|
|
189
|
+
*/
|
|
190
|
+
get compare() {
|
|
191
|
+
return this.fieldset.compare;
|
|
192
|
+
}
|
|
193
|
+
|
|
169
194
|
/**
|
|
170
195
|
* The primary key of the field. Consists of the document and question primary
|
|
171
196
|
* keys.
|
|
@@ -197,6 +222,27 @@ export default class Field extends Base {
|
|
|
197
222
|
return `${this.pk}:label`;
|
|
198
223
|
}
|
|
199
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Whether the field has been modified between two form timelines.
|
|
227
|
+
*
|
|
228
|
+
* @property {Boolean} isModified
|
|
229
|
+
*/
|
|
230
|
+
get isModified() {
|
|
231
|
+
// table answers are manually compared to check if values actually changed,
|
|
232
|
+
// use the historyType attribute to check if changes were made.
|
|
233
|
+
if (this.question.raw.__typename === "TableQuestion") {
|
|
234
|
+
return (this.answer?.value ?? []).some((a) => {
|
|
235
|
+
return a.raw.historyType !== "=";
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// other answer types can be compared directly, ignoring falsey differences.
|
|
240
|
+
return (
|
|
241
|
+
comparisonValue(this.answer?.value) !==
|
|
242
|
+
comparisonValue(this.answer?.historicalValue)
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
200
246
|
/**
|
|
201
247
|
* Whether the field is valid.
|
|
202
248
|
*
|
|
@@ -293,6 +339,19 @@ export default class Field extends Base {
|
|
|
293
339
|
return this.answer?.value;
|
|
294
340
|
}
|
|
295
341
|
|
|
342
|
+
/**
|
|
343
|
+
* The historical value of the field
|
|
344
|
+
*
|
|
345
|
+
* @property {*} historicalValue
|
|
346
|
+
*/
|
|
347
|
+
get historicalValue() {
|
|
348
|
+
if (this.question.isCalculated) {
|
|
349
|
+
return this.calculatedValue;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return this.answer?.historicalValue;
|
|
353
|
+
}
|
|
354
|
+
|
|
296
355
|
/**
|
|
297
356
|
* The computed value of a calculated float question
|
|
298
357
|
*
|
|
@@ -465,6 +524,27 @@ export default class Field extends Base {
|
|
|
465
524
|
return this.question.isMultipleChoice ? selected : selected[0];
|
|
466
525
|
}
|
|
467
526
|
|
|
527
|
+
/**
|
|
528
|
+
* The historically selected option. This property is only used for choice
|
|
529
|
+
* questions. It can either return null if no value is selected yet, an
|
|
530
|
+
* object for single choices or an array of objects for multiple choices.
|
|
531
|
+
*
|
|
532
|
+
* @property {null|Object|Object[]} selected
|
|
533
|
+
*/
|
|
534
|
+
get historicalSelected() {
|
|
535
|
+
if (!this.question.isChoice && !this.question.isMultipleChoice) {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const selected = this.options.filter(({ slug }) =>
|
|
540
|
+
this.question.isMultipleChoice
|
|
541
|
+
? (this.historicalValue || []).includes(slug)
|
|
542
|
+
: this.historicalValue === slug,
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
return this.question.isMultipleChoice ? selected : selected[0];
|
|
546
|
+
}
|
|
547
|
+
|
|
468
548
|
/**
|
|
469
549
|
* The field's JEXL context.
|
|
470
550
|
*
|
package/addon/lib/fieldset.js
CHANGED
|
@@ -59,6 +59,9 @@ export default class Fieldset extends Base {
|
|
|
59
59
|
answer: this.raw.answers.find(
|
|
60
60
|
(answer) => answer?.question?.slug === question.slug,
|
|
61
61
|
),
|
|
62
|
+
historicalAnswer: this.raw.historicalAnswers?.find(
|
|
63
|
+
(answer) => answer?.question?.slug === question.slug,
|
|
64
|
+
),
|
|
62
65
|
},
|
|
63
66
|
fieldset: this,
|
|
64
67
|
owner,
|
|
@@ -69,6 +72,14 @@ export default class Fieldset extends Base {
|
|
|
69
72
|
this.fields = fields;
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Get the compare context via the document.
|
|
77
|
+
* @property {Object} compare
|
|
78
|
+
*/
|
|
79
|
+
get compare() {
|
|
80
|
+
return this.document.compare;
|
|
81
|
+
}
|
|
82
|
+
|
|
72
83
|
/**
|
|
73
84
|
* The primary key of the fieldset. Consists of the document and form primary
|
|
74
85
|
* keys.
|
package/addon/lib/navigation.js
CHANGED
|
@@ -5,12 +5,17 @@ import {
|
|
|
5
5
|
registerDestructor,
|
|
6
6
|
} from "@ember/destroyable";
|
|
7
7
|
import { action } from "@ember/object";
|
|
8
|
-
import {
|
|
8
|
+
import { cancel, next, once } from "@ember/runloop";
|
|
9
9
|
import { inject as service } from "@ember/service";
|
|
10
10
|
import { cached } from "tracked-toolbox";
|
|
11
11
|
|
|
12
12
|
import Base from "@projectcaluma/ember-form/lib/base";
|
|
13
13
|
|
|
14
|
+
const COMPARE_STATES = {
|
|
15
|
+
EQUAL: "equal",
|
|
16
|
+
MODIFIED: "modified",
|
|
17
|
+
};
|
|
18
|
+
|
|
14
19
|
const STATES = {
|
|
15
20
|
EMPTY: "empty",
|
|
16
21
|
IN_PROGRESS: "in-progress",
|
|
@@ -177,6 +182,28 @@ export class NavigationItem extends Base {
|
|
|
177
182
|
return this.navigable || Boolean(this.visibleChildren.length);
|
|
178
183
|
}
|
|
179
184
|
|
|
185
|
+
/**
|
|
186
|
+
* The current compare state consisting of the items and the childrens fieldset
|
|
187
|
+
* state.
|
|
188
|
+
*
|
|
189
|
+
* This can be one of 2 states:
|
|
190
|
+
* - `equal` if every fieldset is `equal`
|
|
191
|
+
* - `modified` if there are `modified` fieldsets
|
|
192
|
+
*
|
|
193
|
+
* @property {String} state
|
|
194
|
+
*/
|
|
195
|
+
@cached
|
|
196
|
+
get compareState() {
|
|
197
|
+
const states = [
|
|
198
|
+
this.compareFieldsetState,
|
|
199
|
+
...this.visibleChildren.map((child) => child.compareFieldsetState),
|
|
200
|
+
].filter(Boolean);
|
|
201
|
+
|
|
202
|
+
return states.some((state) => state === COMPARE_STATES.MODIFIED)
|
|
203
|
+
? COMPARE_STATES.MODIFIED
|
|
204
|
+
: COMPARE_STATES.EQUAL;
|
|
205
|
+
}
|
|
206
|
+
|
|
180
207
|
/**
|
|
181
208
|
* The current state consisting of the items and the childrens fieldset
|
|
182
209
|
* state.
|
|
@@ -238,6 +265,29 @@ export class NavigationItem extends Base {
|
|
|
238
265
|
);
|
|
239
266
|
}
|
|
240
267
|
|
|
268
|
+
/**
|
|
269
|
+
* The current comparestate of the item's fieldset. This does not consider the state
|
|
270
|
+
* of children items.
|
|
271
|
+
*
|
|
272
|
+
* This can be one of 2 states:
|
|
273
|
+
* - `equal` if every field is equal
|
|
274
|
+
* - `modified` if there are modified fields
|
|
275
|
+
*
|
|
276
|
+
* @property {String} compareFieldsetState
|
|
277
|
+
*/
|
|
278
|
+
@cached
|
|
279
|
+
get compareFieldsetState() {
|
|
280
|
+
if (!this.visibleFields.length) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (this.visibleFields.some((f) => f.isModified)) {
|
|
285
|
+
return COMPARE_STATES.MODIFIED;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return COMPARE_STATES.EQUAL;
|
|
289
|
+
}
|
|
290
|
+
|
|
241
291
|
/**
|
|
242
292
|
* The current state of the item's fieldset. This does not consider the state
|
|
243
293
|
* of children items.
|
package/addon/lib/parsers.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { assert } from "@ember/debug";
|
|
1
|
+
import { assert, warn } from "@ember/debug";
|
|
2
2
|
|
|
3
3
|
export const parseDocument = (response) => {
|
|
4
4
|
assert(
|
|
5
5
|
"The passed document must be a GraphQL document",
|
|
6
|
-
response.__typename
|
|
6
|
+
response.__typename.includes("Document"),
|
|
7
7
|
);
|
|
8
8
|
assert("The passed document must include a form", response.form);
|
|
9
9
|
assert("The passed document must include answers", response.answers);
|
|
@@ -11,7 +11,12 @@ export const parseDocument = (response) => {
|
|
|
11
11
|
return {
|
|
12
12
|
...response,
|
|
13
13
|
rootForm: parseForm(response.form),
|
|
14
|
-
answers: response.answers.edges.map(({ node }) =>
|
|
14
|
+
answers: response.answers.edges.map(({ node }) =>
|
|
15
|
+
parseAnswer(node, response.historyType),
|
|
16
|
+
),
|
|
17
|
+
historicalAnswers: response.historicalAnswers?.edges.map(({ node }) =>
|
|
18
|
+
parseAnswer(node, response.historyType),
|
|
19
|
+
),
|
|
15
20
|
forms: parseFormTree(response.form),
|
|
16
21
|
};
|
|
17
22
|
};
|
|
@@ -42,15 +47,61 @@ export const parseFormTree = (response) => {
|
|
|
42
47
|
];
|
|
43
48
|
};
|
|
44
49
|
|
|
45
|
-
export const parseAnswer = (response) => {
|
|
50
|
+
export const parseAnswer = (response, historyType = null) => {
|
|
46
51
|
assert(
|
|
47
52
|
"The passed answer must be a GraphQL answer",
|
|
48
53
|
/Answer$/.test(response.__typename),
|
|
49
54
|
);
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
// if a whole document is marked as added or removed, we need to
|
|
57
|
+
// propagate that to all the underlying answers as well.
|
|
58
|
+
return {
|
|
59
|
+
...response,
|
|
60
|
+
// If the parent document was removed/added, propagate that
|
|
61
|
+
// history type to the answer as well.
|
|
62
|
+
historyType: ["-", "+"].includes(historyType)
|
|
63
|
+
? historyType
|
|
64
|
+
: response.historyType,
|
|
65
|
+
};
|
|
52
66
|
};
|
|
53
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Parse the widget and detects a widget override.
|
|
70
|
+
*
|
|
71
|
+
* @param {Array} params
|
|
72
|
+
* @param {Object} options
|
|
73
|
+
* @returns {Object} {widget: String, override: Boolean}
|
|
74
|
+
*/
|
|
75
|
+
export function parseWidgetType(calumaOptions, params, options = {}) {
|
|
76
|
+
for (const obj of params) {
|
|
77
|
+
let widget = obj?.raw?.meta?.widgetOverride;
|
|
78
|
+
if (obj?.useNumberSeparatorWidget) {
|
|
79
|
+
widget = "cf-field/input/number-separator";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!widget) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
!calumaOptions
|
|
88
|
+
.getComponentOverrides()
|
|
89
|
+
.find(({ component }) => component === widget)
|
|
90
|
+
) {
|
|
91
|
+
warn(
|
|
92
|
+
`Widget override "${widget}" is not registered. Please register it by calling \`calumaOptions.registerComponentOverride\``,
|
|
93
|
+
widget,
|
|
94
|
+
{ id: "ember-caluma.unregistered-override" },
|
|
95
|
+
);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { widget, override: true };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { widget: options?.default ?? "cf-field/input", override: false };
|
|
103
|
+
}
|
|
104
|
+
|
|
54
105
|
export const parseQuestion = (response) => {
|
|
55
106
|
assert(
|
|
56
107
|
"The passed question must be a GraphQL question",
|
|
@@ -66,4 +117,5 @@ export default {
|
|
|
66
117
|
parseFormTree,
|
|
67
118
|
parseAnswer,
|
|
68
119
|
parseQuestion,
|
|
120
|
+
parseWidgetType,
|
|
69
121
|
};
|
|
@@ -16,6 +16,18 @@ export default class CalumaStoreService extends Service {
|
|
|
16
16
|
obj.storeKey,
|
|
17
17
|
);
|
|
18
18
|
|
|
19
|
+
// Do not use the store for historical objects, because in the diff
|
|
20
|
+
// view we switch between different versions of the same object
|
|
21
|
+
// with the same ID, which should not be retrieved from the store.
|
|
22
|
+
const historyRegex = new RegExp("^Historical");
|
|
23
|
+
if (
|
|
24
|
+
historyRegex.test(obj?.raw?.__typename) ||
|
|
25
|
+
historyRegex.test(obj?.raw?.answer?.__typename) ||
|
|
26
|
+
(obj?.raw?.answers || []).some((a) => historyRegex.test(a?.__typename))
|
|
27
|
+
) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
const existing = this._store.get(obj.storeKey);
|
|
20
32
|
|
|
21
33
|
if (existing) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@projectcaluma/ember-form/components/cf-field/input-compare/changes-note";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@projectcaluma/ember-form/components/cf-field/input-compare/text-diff";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@projectcaluma/ember-form/components/cf-field/input-compare/textarea-diff";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@projectcaluma/ember-form/components/cf-field/input-compare";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@projectcaluma/ember-form/components/timeline-select";
|