@projectcaluma/ember-form 11.0.0-beta.4 → 11.0.0-beta.40

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 (62) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/addon/components/cf-content.hbs +38 -37
  3. package/addon/components/cf-content.js +7 -3
  4. package/addon/components/cf-field/hint.hbs +5 -0
  5. package/addon/components/cf-field/input/action-button.hbs +8 -4
  6. package/addon/components/cf-field/input/action-button.js +60 -59
  7. package/addon/components/cf-field/input/checkbox.hbs +2 -3
  8. package/addon/components/cf-field/input/date.hbs +12 -25
  9. package/addon/components/cf-field/input/date.js +19 -11
  10. package/addon/components/cf-field/input/files.hbs +38 -0
  11. package/addon/components/cf-field/input/files.js +113 -0
  12. package/addon/components/cf-field/input/powerselect.hbs +27 -29
  13. package/addon/components/cf-field/input/powerselect.js +8 -2
  14. package/addon/components/cf-field/input/radio.hbs +2 -2
  15. package/addon/components/cf-field/input/static.hbs +1 -1
  16. package/addon/components/cf-field/input/table.hbs +20 -18
  17. package/addon/components/cf-field/input/table.js +1 -11
  18. package/addon/components/cf-field/input.hbs +8 -21
  19. package/addon/components/cf-field/input.js +32 -14
  20. package/addon/components/cf-field/label.hbs +4 -2
  21. package/addon/components/cf-field-value.hbs +22 -7
  22. package/addon/components/cf-field-value.js +12 -36
  23. package/addon/components/cf-field.hbs +42 -9
  24. package/addon/components/cf-field.js +41 -17
  25. package/addon/components/cf-form-wrapper.hbs +4 -1
  26. package/addon/components/cf-form.hbs +6 -1
  27. package/addon/components/document-validity.js +16 -1
  28. package/addon/gql/fragments/field.graphql +32 -7
  29. package/addon/gql/mutations/save-document-files-answer.graphql +9 -0
  30. package/addon/gql/queries/document-forms.graphql +1 -1
  31. package/addon/gql/queries/dynamic-options.graphql +4 -4
  32. package/addon/gql/queries/{fileanswer-info.graphql → filesanswer-info.graphql} +4 -4
  33. package/addon/helpers/format-graphql-error.js +21 -0
  34. package/addon/helpers/get-widget.js +16 -2
  35. package/addon/instance-initializers/form-widget-overrides.js +52 -0
  36. package/addon/lib/document.js +9 -1
  37. package/addon/lib/field.js +96 -59
  38. package/addon/lib/navigation.js +3 -1
  39. package/addon/lib/question.js +18 -5
  40. package/addon/modifiers/autoresize.js +14 -0
  41. package/addon/services/caluma-store.js +2 -0
  42. package/app/components/cf-field/{input/file.js → hint.js} +1 -1
  43. package/app/components/cf-field/input/files.js +1 -0
  44. package/app/helpers/format-graphql-error.js +1 -0
  45. package/app/helpers/get-widget.js +1 -4
  46. package/app/instance-initializers/form-widget-overrides.js +4 -0
  47. package/app/modifiers/autoresize.js +1 -0
  48. package/app/styles/@projectcaluma/ember-form.scss +5 -15
  49. package/app/styles/_flatpickr.scss +47 -0
  50. package/blueprints/@projectcaluma/ember-form/index.js +1 -1
  51. package/index.js +12 -0
  52. package/package.json +47 -37
  53. package/translations/de.yaml +6 -6
  54. package/translations/en.yaml +6 -6
  55. package/translations/fr.yaml +6 -6
  56. package/addon/components/cf-field/input/file.hbs +0 -32
  57. package/addon/components/cf-field/input/file.js +0 -89
  58. package/addon/components/cf-field/label.js +0 -11
  59. package/addon/gql/mutations/remove-answer.graphql +0 -7
  60. package/addon/gql/mutations/save-document-file-answer.graphql +0 -9
  61. package/addon/instance-initializers/setup-pikaday-i18n.js +0 -35
  62. package/config/environment.js +0 -5
@@ -0,0 +1,113 @@
1
+ import { action } from "@ember/object";
2
+ import { inject as service } from "@ember/service";
3
+ import { macroCondition, isTesting } from "@embroider/macros";
4
+ import Component from "@glimmer/component";
5
+ import { queryManager } from "ember-apollo-client";
6
+ import fetch from "fetch";
7
+
8
+ import getFilesAnswerInfoQuery from "@projectcaluma/ember-form/gql/queries/filesanswer-info.graphql";
9
+
10
+ export default class CfFieldInputFilesComponent extends Component {
11
+ @service intl;
12
+
13
+ @queryManager apollo;
14
+
15
+ get files() {
16
+ return this.args.field?.answer?.value;
17
+ }
18
+
19
+ @action
20
+ async download(fileId) {
21
+ if (!fileId) {
22
+ return;
23
+ }
24
+ const answers = await this.apollo.query(
25
+ {
26
+ query: getFilesAnswerInfoQuery,
27
+ variables: { id: this.args.field.answer.raw.id },
28
+ fetchPolicy: "network-only",
29
+ },
30
+ "node.value"
31
+ );
32
+ const { downloadUrl } =
33
+ answers.find((file) =>
34
+ // the testing graph-ql setup does a base64 encoding of `__typename: fileID`
35
+ macroCondition(isTesting())
36
+ ? file.id === fileId ||
37
+ atob(file.id).substring(file.__typename.length + 1) === fileId
38
+ : file.id === fileId
39
+ ) ?? {};
40
+ if (downloadUrl) {
41
+ window.open(downloadUrl, "_blank");
42
+ }
43
+ }
44
+
45
+ @action
46
+ async save({ target }) {
47
+ // store the old list of files
48
+ // unwrap files from FileList construct
49
+ let newFiles = Array.from(target.files).map((file) => ({
50
+ name: file.name,
51
+ value: file,
52
+ }));
53
+
54
+ const fileList = [...(this.files || []), ...newFiles];
55
+
56
+ if (newFiles.length === 0) {
57
+ return;
58
+ }
59
+
60
+ // trigger save action for file list of old and new files with
61
+ // reduces properties to match gql format
62
+ const { filesValue: savedAnswerValue } = await this.args.onSave(
63
+ fileList.map(({ name, id }) => ({ name, id }))
64
+ );
65
+
66
+ try {
67
+ // iterate over list of new files and enrich with graphql answer values
68
+ newFiles = newFiles.map((file) => ({
69
+ ...savedAnswerValue.find(
70
+ (value) =>
71
+ file.name === value.name &&
72
+ !fileList.find((file) => file.id === value.id)
73
+ ),
74
+ value: file.value,
75
+ }));
76
+
77
+ const uploadFunction = async (file) => {
78
+ const response = await fetch(file.uploadUrl, {
79
+ method: "PUT",
80
+ body: file.value,
81
+ });
82
+ if (!response.ok) {
83
+ throw new Error();
84
+ }
85
+ return response;
86
+ };
87
+
88
+ // upload the actual file to data storage
89
+ await Promise.all(newFiles.map((file) => uploadFunction(file)));
90
+
91
+ this.args.field.answer.value = savedAnswerValue;
92
+ } catch (error) {
93
+ await this.args.onSave([]);
94
+ this.args.field._errors = [{ type: "uploadFailed" }];
95
+ } finally {
96
+ // eslint-disable-next-line require-atomic-updates
97
+ target.value = "";
98
+ }
99
+ }
100
+
101
+ @action
102
+ async delete(fileId) {
103
+ const remainingFiles = this.files
104
+ .filter((file) => file.id !== fileId)
105
+ .map(({ name, id }) => ({ name, id }));
106
+
107
+ try {
108
+ await this.args.onSave(remainingFiles);
109
+ } catch (error) {
110
+ this.args.field._errors = [{ type: "deleteFailed" }];
111
+ }
112
+ }
113
+ }
@@ -1,29 +1,27 @@
1
- {{#let (component this.componentName) as |DynamicSelect|}}
2
- <DynamicSelect
3
- @options={{@field.options}}
4
- @selected={{@field.selected}}
5
- @disabled={{@disabled}}
6
- @allowClear={{true}}
7
- @preventScroll={{true}}
8
- @searchEnabled={{this.searchEnabled}}
9
- @searchField="label"
10
- @triggerId={{@field.pk}}
11
- @renderInPlace={{true}}
12
- @placeholder={{this.placeholder}}
13
- @loadingMessage={{t "caluma.form.power-select.options-loading"}}
14
- @searchMessage={{t "caluma.form.power-select.options-empty"}}
15
- @searchPlaceholder={{t "caluma.form.power-select.search-placeholder"}}
16
- @noMatchesMessage={{t "caluma.form.power-select.search-empty"}}
17
- @onChange={{this.change}}
18
- as |option|
19
- >
20
- {{#if (and option.disabled (not @disabled))}}
21
- <del
22
- class="uk-text-muted"
23
- title={{t "caluma.form.optionNotAvailable"}}
24
- >{{option.label}}</del>
25
- {{else}}
26
- {{option.label}}
27
- {{/if}}
28
- </DynamicSelect>
29
- {{/let}}
1
+ <this.selectComponent
2
+ @options={{@field.options}}
3
+ @selected={{@field.selected}}
4
+ @disabled={{@disabled}}
5
+ @allowClear={{true}}
6
+ @preventScroll={{true}}
7
+ @searchEnabled={{this.searchEnabled}}
8
+ @searchField="label"
9
+ @triggerId={{@field.pk}}
10
+ @renderInPlace={{true}}
11
+ @placeholder={{this.placeholder}}
12
+ @loadingMessage={{t "caluma.form.power-select.options-loading"}}
13
+ @searchMessage={{t "caluma.form.power-select.options-empty"}}
14
+ @searchPlaceholder={{t "caluma.form.power-select.search-placeholder"}}
15
+ @noMatchesMessage={{t "caluma.form.power-select.search-empty"}}
16
+ @onChange={{this.change}}
17
+ as |option|
18
+ >
19
+ {{#if (and option.disabled (not @disabled))}}
20
+ <del
21
+ class="uk-text-muted"
22
+ title={{t "caluma.form.optionNotAvailable"}}
23
+ >{{option.label}}</del>
24
+ {{else}}
25
+ {{option.label}}
26
+ {{/if}}
27
+ </this.selectComponent>
@@ -1,7 +1,10 @@
1
1
  import { getOwner } from "@ember/application";
2
2
  import { action } from "@ember/object";
3
3
  import { inject as service } from "@ember/service";
4
+ import { ensureSafeComponent } from "@embroider/util";
4
5
  import Component from "@glimmer/component";
6
+ import PowerSelectComponent from "ember-power-select/components/power-select";
7
+ import PowerSelectMultipleComponent from "ember-power-select/components/power-select-multiple";
5
8
 
6
9
  /**
7
10
  * Dropdown component for the single and multiple choice question type
@@ -16,8 +19,11 @@ export default class CfFieldInputPowerselectComponent extends Component {
16
19
  return this.args.field?.question.isMultipleChoice;
17
20
  }
18
21
 
19
- get componentName() {
20
- return this.multiple ? "power-select-multiple" : "power-select";
22
+ get selectComponent() {
23
+ return ensureSafeComponent(
24
+ this.multiple ? PowerSelectMultipleComponent : PowerSelectComponent,
25
+ this
26
+ );
21
27
  }
22
28
 
23
29
  get searchEnabled() {
@@ -1,5 +1,5 @@
1
1
  {{#each @field.options as |option i|}}
2
- {{#if (gt i 0)}}<br />{{/if}}
2
+ {{#if (and (gt i 0) (not @field.raw.question.meta.vertical))}}<br />{{/if}}
3
3
  <label
4
4
  class="{{if @field.isInvalid 'uk-form-danger'}}
5
5
  {{if @field.raw.question.meta.vertical 'uk-margin-large-right'}}"
@@ -10,7 +10,7 @@
10
10
  name={{@field.pk}}
11
11
  value={{option.slug}}
12
12
  checked={{eq option.slug @field.answer.value}}
13
- disabled={{@disabled}}
13
+ disabled={{or option.disabled @disabled}}
14
14
  {{on "change" (fn @onSave option.slug)}}
15
15
  />
16
16
  {{#if (and option.disabled (not @disabled))}}
@@ -1,4 +1,4 @@
1
1
  <MarkdownToHtml
2
- @markdown={{@field.question.staticContent}}
2
+ @markdown={{@field.question.raw.staticContent}}
3
3
  @openLinksInNewWindow={{true}}
4
4
  />
@@ -1,4 +1,4 @@
1
- <table class="uk-table uk-table-divider">
1
+ <table class="uk-table uk-table-divider uk-margin-remove-vertical">
2
2
  <thead>
3
3
  <tr>
4
4
  {{#each this.columns as |column|}}
@@ -25,27 +25,27 @@
25
25
  class="uk-animation-fade uk-text-danger"
26
26
  />
27
27
  {{/if}}
28
- <button
29
- data-test-edit-row
30
- type="button"
31
- class="uk-button uk-button-link uk-flex-inline uk-margin-small-left"
28
+ <UkButton
29
+ @color="link"
30
+ @onClick={{fn this.edit document}}
32
31
  title={{t "caluma.form.edit"}}
33
- {{on "click" (fn this.edit document)}}
32
+ class="uk-flex-inline uk-margin-small-left table-controls"
33
+ data-test-edit-row
34
34
  >
35
35
  <UkIcon @icon="pencil" />
36
- <span class="uk-hidden">{{t "caluma.form.edit"}}</span>
37
- </button>
36
+ <span hidden>{{t "caluma.form.edit"}}</span>
37
+ </UkButton>
38
38
  {{#unless @disabled}}
39
- <button
40
- data-test-delete-row
41
- type="button"
42
- class="uk-button uk-button-link uk-flex-inline uk-margin-small-left"
39
+ <UkButton
40
+ @color="link"
41
+ @onClick={{fn (perform this.delete) document}}
43
42
  title={{t "caluma.form.delete"}}
44
- {{on "click" (fn (perform this.delete) document)}}
43
+ class="uk-flex-inline uk-margin-small-left table-controls"
44
+ data-test-delete-row
45
45
  >
46
46
  <UkIcon @icon="trash" />
47
- <span class="uk-hidden">{{t "caluma.form.delete"}}</span>
48
- </button>
47
+ <span hidden>{{t "caluma.form.delete"}}</span>
48
+ </UkButton>
49
49
  {{/unless}}
50
50
  </div>
51
51
  </td>
@@ -57,12 +57,13 @@
57
57
  <tr>
58
58
  <td colspan={{add this.columns.length 1}} class="uk-text-center">
59
59
  <UkButton
60
- @size="small"
60
+ @color="link"
61
61
  @onClick={{perform this.add}}
62
+ title={{t "caluma.form.addRow"}}
62
63
  data-test-add-row
63
64
  >
64
65
  <UkIcon @icon="plus" />
65
- <span class="uk-hidden">{{t "caluma.form.addRow"}}</span>
66
+ <span hidden>{{t "caluma.form.addRow"}}</span>
66
67
  </UkButton>
67
68
  </td>
68
69
  </tr>
@@ -82,10 +83,11 @@
82
83
  @document={{this.documentToEdit}}
83
84
  @fieldset={{object-at 0 this.documentToEdit.fieldsets}}
84
85
  @disabled={{@disabled}}
86
+ @context={{@context}}
85
87
  />
86
88
  </modal.body>
87
89
 
88
- <modal.footer @class="uk-text-right">
90
+ <modal.footer class="uk-text-right">
89
91
  {{#if @disabled}}
90
92
  <UkButton
91
93
  @label={{t "caluma.form.close"}}
@@ -5,22 +5,12 @@ import Component from "@glimmer/component";
5
5
  import { tracked } from "@glimmer/tracking";
6
6
  import { queryManager } from "ember-apollo-client";
7
7
  import { dropTask } from "ember-concurrency";
8
- import UIkit from "uikit";
8
+ import { confirm } from "ember-uikit";
9
9
 
10
10
  import removeDocumentMutation from "@projectcaluma/ember-form/gql/mutations/remove-document.graphql";
11
11
  import saveDocumentMutation from "@projectcaluma/ember-form/gql/mutations/save-document.graphql";
12
12
  import { parseDocument } from "@projectcaluma/ember-form/lib/parsers";
13
13
 
14
- async function confirm(text) {
15
- try {
16
- await UIkit.modal.confirm(text);
17
-
18
- return true;
19
- } catch (error) {
20
- return false;
21
- }
22
- }
23
-
24
14
  export default class CfFieldInputTableComponent extends Component {
25
15
  @service notification;
26
16
  @service intl;
@@ -1,21 +1,8 @@
1
- {{#if this.type}}
2
- {{#let (component (concat "cf-field/input/" this.type)) as |InputComponent|}}
3
- <div
4
- class="uk-form-controls
5
- {{if
6
- (and
7
- (has-question-type @field.question 'multiple-choice' 'choice')
8
- @field.question.raw.meta.vertical
9
- )
10
- 'uk-flex'
11
- }}"
12
- >
13
- <InputComponent
14
- @field={{@field}}
15
- @disabled={{@disabled}}
16
- @onSave={{@onSave}}
17
- @context={{@context}}
18
- />
19
- </div>
20
- {{/let}}
21
- {{/if}}
1
+ <div class="uk-form-controls">
2
+ <this.inputComponent
3
+ @field={{@field}}
4
+ @disabled={{@disabled}}
5
+ @onSave={{@onSave}}
6
+ @context={{@context}}
7
+ />
8
+ </div>
@@ -1,12 +1,33 @@
1
- import { dasherize } from "@ember/string";
1
+ import { ensureSafeComponent } from "@embroider/util";
2
2
  import Component from "@glimmer/component";
3
3
 
4
- const mapping = {
5
- MultipleChoiceQuestion: "checkbox",
6
- ChoiceQuestion: "radio",
7
- DynamicMultipleChoiceQuestion: "checkbox",
8
- DynamicChoiceQuestion: "radio",
9
- CalculatedFloatQuestion: "float",
4
+ import ActionButtonComponent from "@projectcaluma/ember-form/components/cf-field/input/action-button";
5
+ import CheckboxComponent from "@projectcaluma/ember-form/components/cf-field/input/checkbox";
6
+ import DateComponent from "@projectcaluma/ember-form/components/cf-field/input/date";
7
+ import FilesComponent from "@projectcaluma/ember-form/components/cf-field/input/files";
8
+ import FloatComponent from "@projectcaluma/ember-form/components/cf-field/input/float";
9
+ import IntegerComponent from "@projectcaluma/ember-form/components/cf-field/input/integer";
10
+ import RadioComponent from "@projectcaluma/ember-form/components/cf-field/input/radio";
11
+ import StaticComponent from "@projectcaluma/ember-form/components/cf-field/input/static";
12
+ import TableComponent from "@projectcaluma/ember-form/components/cf-field/input/table";
13
+ import TextComponent from "@projectcaluma/ember-form/components/cf-field/input/text";
14
+ import TextareaComponent from "@projectcaluma/ember-form/components/cf-field/input/textarea";
15
+
16
+ const COMPONENT_MAPPING = {
17
+ ActionButtonQuestion: ActionButtonComponent,
18
+ CalculatedFloatQuestion: FloatComponent,
19
+ ChoiceQuestion: RadioComponent,
20
+ DateQuestion: DateComponent,
21
+ DynamicChoiceQuestion: RadioComponent,
22
+ DynamicMultipleChoiceQuestion: CheckboxComponent,
23
+ FilesQuestion: FilesComponent,
24
+ FloatQuestion: FloatComponent,
25
+ IntegerQuestion: IntegerComponent,
26
+ MultipleChoiceQuestion: CheckboxComponent,
27
+ StaticQuestion: StaticComponent,
28
+ TableQuestion: TableComponent,
29
+ TextareaQuestion: TextareaComponent,
30
+ TextQuestion: TextComponent,
10
31
  };
11
32
 
12
33
  /**
@@ -16,17 +37,14 @@ const mapping = {
16
37
  */
17
38
  export default class CfFieldInputComponent extends Component {
18
39
  /**
19
- * The input component type
40
+ * The input component
20
41
  *
21
- * @property {String} type
42
+ * @property {Component} inputComponent
22
43
  * @accessor
23
44
  */
24
- get type() {
45
+ get inputComponent() {
25
46
  const typename = this.args.field?.question.raw.__typename;
26
47
 
27
- return (
28
- typename &&
29
- (mapping[typename] || dasherize(typename.replace(/Question$/, "")))
30
- );
48
+ return ensureSafeComponent(COMPONENT_MAPPING[typename], this);
31
49
  }
32
50
  }
@@ -1,11 +1,13 @@
1
- <label class="uk-form-label" for={{@field.pk}}>
1
+ <label class="uk-form-label" for={{@field.pk}} ...attributes>
2
2
  <span class="uk-text-bold">
3
3
  {{@field.question.raw.label}}
4
4
  </span>
5
5
 
6
- {{#if this.optional}}
6
+ {{#if (and (not @field.question.isCalculated) @field.optional)}}
7
7
  <span
8
8
  class="uk-margin-small-left uk-text-muted uk-text-lowercase uk-text-normal"
9
9
  >({{t "caluma.form.optional"}})</span>
10
10
  {{/if}}
11
+
12
+ {{yield}}
11
13
  </label>
@@ -1,9 +1,24 @@
1
- {{#if this.value.fileAnswerId}}
2
- <UkButton
3
- @color="link"
4
- @label={{this.value.label}}
5
- @onClick={{fn this.download this.value.fileAnswerId}}
6
- />
1
+ {{#if (has-question-type @field.question "choice" "dynamic-choice")}}
2
+ {{@field.selected.label}}
3
+ {{else if (has-question-type
4
+ @field.question "multiple-choice" "multiple-dynamic-choice"
5
+ )}}
6
+ {{#each @field.selected as |opt i|}}{{if (gt i 0) ", "}}{{opt.label}}{{/each}}
7
+ {{else if (has-question-type @field.question "date")}}
8
+ {{format-date
9
+ @field.answer.value
10
+ day="2-digit"
11
+ month="2-digit"
12
+ year="numeric"
13
+ }}
14
+ {{else if (has-question-type @field.question "files")}}
15
+ {{#each @field.answer.value as |file|}}
16
+ <UkButton
17
+ @color="link"
18
+ @label={{file.name}}
19
+ @onClick={{fn this.download file.id}}
20
+ />
21
+ {{/each}}
7
22
  {{else}}
8
- {{this.value.label}}
23
+ {{@field.answer.value}}
9
24
  {{/if}}
@@ -1,50 +1,26 @@
1
1
  import { action } from "@ember/object";
2
2
  import Component from "@glimmer/component";
3
3
  import { queryManager } from "ember-apollo-client";
4
- import moment from "moment";
5
4
 
6
- import getFileAnswerInfoQuery from "@projectcaluma/ember-form/gql/queries/fileanswer-info.graphql";
5
+ import getFilesAnswerInfoQuery from "@projectcaluma/ember-form/gql/queries/filesanswer-info.graphql";
7
6
 
8
7
  export default class CfFieldValueComponent extends Component {
9
8
  @queryManager apollo;
10
9
 
11
- get value() {
12
- const field = this.args.field;
13
-
14
- switch (field.questionType) {
15
- case "ChoiceQuestion":
16
- case "DynamicChoiceQuestion": {
17
- return field.selected;
18
- }
19
- case "MultipleChoiceQuestion":
20
- case "DynamicMultipleChoiceQuestion": {
21
- return { label: field.selected.map(({ label }) => label).join(", ") };
22
- }
23
- case "FileQuestion": {
24
- const answerValue = field.answer.value;
25
- return {
26
- fileAnswerId: answerValue && field.answer.raw.id,
27
- label: answerValue?.name,
28
- };
29
- }
30
- case "DateQuestion": {
31
- return {
32
- label: field.answer.value && moment(field.answer.value).format("L"),
33
- };
34
- }
35
-
36
- default:
37
- return { label: field.answer.value };
38
- }
39
- }
40
-
41
10
  @action
42
11
  async download(id) {
43
- const { downloadUrl } = await this.apollo.query(
44
- { query: getFileAnswerInfoQuery, variables: { id } },
45
- "node.fileValue"
12
+ const files = await this.apollo.query(
13
+ {
14
+ query: getFilesAnswerInfoQuery,
15
+ variables: { id: this.args.field.answer.raw.id },
16
+ fetchPolicy: "network-only",
17
+ },
18
+ "node.value"
46
19
  );
47
20
 
48
- window.open(downloadUrl, "_blank");
21
+ const { downloadUrl } = files?.find((file) => file.id === id);
22
+ if (downloadUrl) {
23
+ window.open(downloadUrl, "_blank");
24
+ }
49
25
  }
50
26
  }
@@ -1,12 +1,23 @@
1
1
  {{#if this.visible}}
2
- <div class="uk-margin">
2
+ <div
3
+ class="uk-margin
4
+ {{if
5
+ (and @disabled (has-question-type @field.question 'action-button'))
6
+ 'uk-hidden'
7
+ }}"
8
+ {{did-insert this.registerComponent}}
9
+ {{will-destroy this.unregisterComponent}}
10
+ >
3
11
  {{#if this.labelVisible}}
4
- <CfField::label @field={{@field}} />
12
+ <CfField::Label @field={{@field}} />
5
13
  {{/if}}
6
14
 
7
15
  <div class="uk-flex">
8
16
  <div class="uk-width-expand">
9
- {{#let (component (get-widget @field.question)) as |FieldComponent|}}
17
+ {{#let
18
+ (component (ensure-safe-component (get-widget @field.question)))
19
+ as |FieldComponent|
20
+ }}
10
21
  <FieldComponent
11
22
  @field={{@field}}
12
23
  @disabled={{@disabled}}
@@ -17,26 +28,48 @@
17
28
  </div>
18
29
 
19
30
  {{#if (and @field.question.raw.infoText this.infoTextVisible)}}
20
- <CfField::info @text={{@field.question.raw.infoText}} />
31
+ <CfField::Info @text={{@field.question.raw.infoText}} />
21
32
  {{/if}}
22
33
 
23
34
  {{#if this.saveIndicatorVisible}}
24
35
  <div
25
36
  class="cf-field__icon uk-padding-remove-vertical uk-flex uk-flex-middle uk-flex-center"
26
37
  >
27
- {{#if @field.save.isRunning}}
38
+ {{#if this.save.isRunning}}
28
39
  <UkSpinner class="uk-animation-fade" />
29
- {{else if (or @field.save.last.isError @field.isInvalid)}}
30
- <UkIcon @icon="warning" class="uk-animation-fade uk-text-danger" />
31
- {{else if @field.save.last.isSuccessful}}
40
+ {{else if (or this.save.last.isError @field.isInvalid)}}
41
+ <div class="uk-flex-inline">
42
+ <UkIcon
43
+ @icon="warning"
44
+ class="uk-animation-fade uk-text-danger"
45
+ />
46
+ {{#if this.save.last.error}}
47
+ <div uk-dropdown="pos: bottom-left" class="uk-padding-small">
48
+ <div class="uk-alert uk-alert-danger uk-margin-small">
49
+ {{t "caluma.form.error.intro"}}
50
+ </div>
51
+ <p class="uk-text-meta uk-margin-small">
52
+ {{t "caluma.form.error.details"}}
53
+ </p>
54
+ <pre class="uk-margin-remove">
55
+ {{~format-graphql-error this.save.last.error~}}
56
+ </pre>
57
+ </div>
58
+ {{/if}}
59
+ </div>
60
+ {{else if this.save.last.isSuccessful}}
32
61
  <UkIcon @icon="check" class="uk-animation-fade uk-text-success" />
33
62
  {{/if}}
34
63
  </div>
35
64
  {{/if}}
36
65
  </div>
37
66
 
67
+ {{#if (and @field.question.raw.hintText this.hintTextVisible)}}
68
+ <CfField::Hint @field={{@field}} />
69
+ {{/if}}
70
+
38
71
  {{#if @field.errors.length}}
39
- <CfField::errors @field={{@field}} />
72
+ <CfField::Errors @field={{@field}} />
40
73
  {{/if}}
41
74
  </div>
42
75
  {{/if}}