@projectcaluma/ember-form 11.0.0-beta.20 → 11.0.0-beta.23

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,35 @@
1
+ # [@projectcaluma/ember-form-v11.0.0-beta.23](https://github.com/projectcaluma/ember-caluma/compare/@projectcaluma/ember-form-v11.0.0-beta.22...@projectcaluma/ember-form-v11.0.0-beta.23) (2022-08-15)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **files:** disable delete button when field is disabled ([#2086](https://github.com/projectcaluma/ember-caluma/issues/2086)) ([2055c53](https://github.com/projectcaluma/ember-caluma/commit/2055c53cbcfe07fbdcfa46e6042a763b4b643d65))
7
+ * **form:** allow deselecting disabled multiple choice options ([0dd663e](https://github.com/projectcaluma/ember-caluma/commit/0dd663e467b66f0f8742d667630c28255e03e161))
8
+
9
+ # [@projectcaluma/ember-form-v11.0.0-beta.22](https://github.com/projectcaluma/ember-caluma/compare/@projectcaluma/ember-form-v11.0.0-beta.21...@projectcaluma/ember-form-v11.0.0-beta.22) (2022-08-05)
10
+
11
+
12
+ * feat!: add multi file upload (#2040) ([c4fd004](https://github.com/projectcaluma/ember-caluma/commit/c4fd0049654b2d2e5ea62e5909a45d89cb888b40)), closes [#2040](https://github.com/projectcaluma/ember-caluma/issues/2040)
13
+
14
+
15
+ ### BREAKING CHANGES
16
+
17
+ * This requires the caluma backend version v8.0.0-beta.12
18
+ or later.
19
+
20
+ # [@projectcaluma/ember-form-v11.0.0-beta.21](https://github.com/projectcaluma/ember-caluma/compare/@projectcaluma/ember-form-v11.0.0-beta.20...@projectcaluma/ember-form-v11.0.0-beta.21) (2022-06-09)
21
+
22
+
23
+ ### Features
24
+
25
+ * **caluma:** use new filter syntax of caluma ([7a00c03](https://github.com/projectcaluma/ember-caluma/commit/7a00c03a103933d9e48dd88adb7382441a298742))
26
+
27
+
28
+ ### BREAKING CHANGES
29
+
30
+ * **caluma:** `ember-caluma` now requires Caluma version >=
31
+ 8.0.0-beta.6
32
+
1
33
  # [@projectcaluma/ember-form-v11.0.0-beta.20](https://github.com/projectcaluma/ember-caluma/compare/@projectcaluma/ember-form-v11.0.0-beta.19...@projectcaluma/ember-form-v11.0.0-beta.20) (2022-06-09)
2
34
 
3
35
 
@@ -10,7 +10,7 @@
10
10
  name={{@field.pk}}
11
11
  value={{option.slug}}
12
12
  checked={{includes option.slug @field.answer.value}}
13
- disabled={{or option.disabled @disabled}}
13
+ disabled={{@disabled}}
14
14
  {{on "change" this.update}}
15
15
  />
16
16
  {{#if (and option.disabled (not @disabled))}}
@@ -0,0 +1,38 @@
1
+ <div class="uk-flex-middle uk-grid-divider uk-grid-column-small" uk-grid>
2
+ <div uk-form-custom="target: true">
3
+
4
+ <input
5
+ type="file"
6
+ name={{@field.pk}}
7
+ id={{@field.pk}}
8
+ disabled={{@disabled}}
9
+ multiple
10
+ {{on "change" this.save}}
11
+ />
12
+ <UkButton disabled={{@disabled}}>
13
+ {{t "caluma.form.selectFile"}}
14
+ </UkButton>
15
+ </div>
16
+ <ul class="uk-list uk-list-collapse" data-test-file-list={{@field.pk}}>
17
+ {{#each this.files as |file|}}
18
+ <li class="uk-text-justify uk-text-middle">
19
+ <UkButton
20
+ data-test-download-link={{file.id}}
21
+ @color="link"
22
+ @onClick={{fn this.download file.id}}
23
+ >
24
+ {{file.name}}
25
+ </UkButton>
26
+ {{#unless @disabled}}
27
+ <UkIcon
28
+ data-test-delete={{file.id}}
29
+ class="uk-icon-button uk-margin-small-left"
30
+ role="button"
31
+ @icon="trash"
32
+ {{on "click" (fn this.delete file.id)}}
33
+ />
34
+ {{/unless}}
35
+ </li>
36
+ {{/each}}
37
+ </ul>
38
+ </div>
@@ -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
+ }
@@ -4,7 +4,7 @@ import Component from "@glimmer/component";
4
4
  import ActionButtonComponent from "@projectcaluma/ember-form/components/cf-field/input/action-button";
5
5
  import CheckboxComponent from "@projectcaluma/ember-form/components/cf-field/input/checkbox";
6
6
  import DateComponent from "@projectcaluma/ember-form/components/cf-field/input/date";
7
- import FileComponent from "@projectcaluma/ember-form/components/cf-field/input/file";
7
+ import FilesComponent from "@projectcaluma/ember-form/components/cf-field/input/files";
8
8
  import FloatComponent from "@projectcaluma/ember-form/components/cf-field/input/float";
9
9
  import IntegerComponent from "@projectcaluma/ember-form/components/cf-field/input/integer";
10
10
  import RadioComponent from "@projectcaluma/ember-form/components/cf-field/input/radio";
@@ -20,7 +20,7 @@ const COMPONENT_MAPPING = {
20
20
  DateQuestion: DateComponent,
21
21
  DynamicChoiceQuestion: RadioComponent,
22
22
  DynamicMultipleChoiceQuestion: CheckboxComponent,
23
- FileQuestion: FileComponent,
23
+ FilesQuestion: FilesComponent,
24
24
  FloatQuestion: FloatComponent,
25
25
  IntegerQuestion: IntegerComponent,
26
26
  MultipleChoiceQuestion: CheckboxComponent,
@@ -11,14 +11,14 @@
11
11
  month="2-digit"
12
12
  year="numeric"
13
13
  }}
14
- {{else if (has-question-type @field.question "file")}}
15
- {{#if @field.answer.value}}
14
+ {{else if (has-question-type @field.question "files")}}
15
+ {{#each @field.answer.value as |file|}}
16
16
  <UkButton
17
17
  @color="link"
18
- @label={{@field.answer.value.name}}
19
- @onClick={{fn this.download @field.answer.raw.id}}
18
+ @label={{file.name}}
19
+ @onClick={{fn this.download file.id}}
20
20
  />
21
- {{/if}}
21
+ {{/each}}
22
22
  {{else}}
23
23
  {{@field.answer.value}}
24
24
  {{/if}}
@@ -2,22 +2,23 @@ import { action } from "@ember/object";
2
2
  import Component from "@glimmer/component";
3
3
  import { queryManager } from "ember-apollo-client";
4
4
 
5
- import getFileAnswerInfoQuery from "@projectcaluma/ember-form/gql/queries/fileanswer-info.graphql";
5
+ import getFilesAnswerInfoQuery from "@projectcaluma/ember-form/gql/queries/filesanswer-info.graphql";
6
6
 
7
7
  export default class CfFieldValueComponent extends Component {
8
8
  @queryManager apollo;
9
9
 
10
10
  @action
11
11
  async download(id) {
12
- const { downloadUrl } = await this.apollo.query(
12
+ const files = await this.apollo.query(
13
13
  {
14
- query: getFileAnswerInfoQuery,
15
- variables: { id },
14
+ query: getFilesAnswerInfoQuery,
15
+ variables: { id: this.args.field.answer.raw.id },
16
16
  fetchPolicy: "network-only",
17
17
  },
18
- "node.fileValue"
18
+ "node.value"
19
19
  );
20
20
 
21
+ const { downloadUrl } = files?.find((file) => file.id === id);
21
22
  if (downloadUrl) {
22
23
  window.open(downloadUrl, "_blank");
23
24
  }
@@ -117,7 +117,7 @@ fragment SimpleQuestion on Question {
117
117
  calcExpression
118
118
  hintText
119
119
  }
120
- ... on FileQuestion {
120
+ ... on FilesQuestion {
121
121
  hintText
122
122
  }
123
123
  ... on ActionButtonQuestion {
@@ -234,8 +234,8 @@ fragment SimpleAnswer on Answer {
234
234
  ... on ListAnswer {
235
235
  listValue: value
236
236
  }
237
- ... on FileAnswer {
238
- fileValue: value {
237
+ ... on FilesAnswer {
238
+ filesValue: value {
239
239
  id
240
240
  uploadUrl
241
241
  downloadUrl
@@ -0,0 +1,9 @@
1
+ #import * from '../fragments/field.graphql'
2
+
3
+ mutation SaveDocumentFilesAnswer($input: SaveDocumentFilesAnswerInput!) {
4
+ saveDocumentFilesAnswer(input: $input) {
5
+ answer {
6
+ ...FieldAnswer
7
+ }
8
+ }
9
+ }
@@ -1,7 +1,7 @@
1
1
  #import FieldQuestion, FieldTableQuestion, SimpleQuestion from '../fragments/field.graphql'
2
2
 
3
3
  query DocumentForms($slug: String!) {
4
- allForms(filter: [{ slug: $slug }]) {
4
+ allForms(filter: [{ slugs: [$slug] }]) {
5
5
  edges {
6
6
  node {
7
7
  id
@@ -1,5 +1,5 @@
1
1
  query DynamicOptions($question: String!) {
2
- allQuestions(filter: [{ slug: $question }], first: 1) {
2
+ allQuestions(filter: [{ slugs: [$question] }], first: 1) {
3
3
  edges {
4
4
  node {
5
5
  id
@@ -1,8 +1,8 @@
1
- query FileAnswerInfo($id: ID!) {
1
+ query FilesAnswerInfo($id: ID!) {
2
2
  node(id: $id) {
3
- id
4
- ... on FileAnswer {
5
- fileValue: value {
3
+ ... on FilesAnswer {
4
+ id
5
+ value {
6
6
  id
7
7
  uploadUrl
8
8
  downloadUrl
@@ -13,7 +13,7 @@ import { cached } from "tracked-toolbox";
13
13
 
14
14
  import { decodeId } from "@projectcaluma/ember-core/helpers/decode-id";
15
15
  import saveDocumentDateAnswerMutation from "@projectcaluma/ember-form/gql/mutations/save-document-date-answer.graphql";
16
- import saveDocumentFileAnswerMutation from "@projectcaluma/ember-form/gql/mutations/save-document-file-answer.graphql";
16
+ import saveDocumentFilesAnswerMutation from "@projectcaluma/ember-form/gql/mutations/save-document-files-answer.graphql";
17
17
  import saveDocumentFloatAnswerMutation from "@projectcaluma/ember-form/gql/mutations/save-document-float-answer.graphql";
18
18
  import saveDocumentIntegerAnswerMutation from "@projectcaluma/ember-form/gql/mutations/save-document-integer-answer.graphql";
19
19
  import saveDocumentListAnswerMutation from "@projectcaluma/ember-form/gql/mutations/save-document-list-answer.graphql";
@@ -34,7 +34,7 @@ export const TYPE_MAP = {
34
34
  DynamicChoiceQuestion: "StringAnswer",
35
35
  TableQuestion: "TableAnswer",
36
36
  FormQuestion: null,
37
- FileQuestion: "FileAnswer",
37
+ FilesQuestion: "FilesAnswer",
38
38
  StaticQuestion: null,
39
39
  DateQuestion: "DateAnswer",
40
40
  };
@@ -44,7 +44,7 @@ const MUTATION_MAP = {
44
44
  IntegerAnswer: saveDocumentIntegerAnswerMutation,
45
45
  StringAnswer: saveDocumentStringAnswerMutation,
46
46
  ListAnswer: saveDocumentListAnswerMutation,
47
- FileAnswer: saveDocumentFileAnswerMutation,
47
+ FilesAnswer: saveDocumentFilesAnswerMutation,
48
48
  DateAnswer: saveDocumentDateAnswerMutation,
49
49
  TableAnswer: saveDocumentTableAnswerMutation,
50
50
  };
@@ -810,11 +810,11 @@ export default class Field extends Base {
810
810
  /**
811
811
  * Dummy method for the validation of file uploads.
812
812
  *
813
- * @method _validateFileQuestion
813
+ * @method _validateFilesQuestion
814
814
  * @return {Boolean} Always returns true
815
815
  * @private
816
816
  */
817
- _validateFileQuestion() {
817
+ _validateFilesQuestion() {
818
818
  return true;
819
819
  }
820
820
 
@@ -43,6 +43,8 @@ export default class CalumaStoreService extends Service {
43
43
  clear() {
44
44
  this._store.forEach((obj) => obj.destroy());
45
45
 
46
+ // `this._store` is not an ember array but a native map
47
+ // eslint-disable-next-line ember/no-array-prototype-extensions
46
48
  this._store.clear();
47
49
  }
48
50
  }
@@ -1 +1 @@
1
- export { default } from "@projectcaluma/ember-form/components/cf-field/input/file";
1
+ export { default } from "@projectcaluma/ember-form/components/cf-field/input/files";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectcaluma/ember-form",
3
- "version": "11.0.0-beta.20",
3
+ "version": "11.0.0-beta.23",
4
4
  "description": "Ember addon for rendering Caluma forms.",
5
5
  "keywords": [
6
6
  "ember-addon"
@@ -15,16 +15,16 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@ember/string": "^3.0.0",
18
- "@embroider/macros": "^1.7.1",
19
- "@embroider/util": "^1.7.1",
18
+ "@embroider/macros": "^1.8.3",
19
+ "@embroider/util": "^1.8.3",
20
20
  "@glimmer/component": "^1.1.2",
21
21
  "@glimmer/tracking": "^1.1.2",
22
- "@projectcaluma/ember-core": "^11.0.0-beta.7",
23
- "ember-apollo-client": "^4.0.2",
22
+ "@projectcaluma/ember-core": "^11.0.0-beta.9",
23
+ "ember-apollo-client": "~4.0.2",
24
24
  "ember-auto-import": "^2.4.2",
25
25
  "ember-autoresize-modifier": "^0.5.0",
26
26
  "ember-cli-babel": "^7.26.11",
27
- "ember-cli-htmlbars": "^6.0.1",
27
+ "ember-cli-htmlbars": "^6.1.0",
28
28
  "ember-cli-showdown": "^6.0.1",
29
29
  "ember-composable-helpers": "^5.0.0",
30
30
  "ember-concurrency": "^2.2.1",
@@ -33,22 +33,22 @@
33
33
  "ember-intl": "^5.7.2",
34
34
  "ember-math-helpers": "^2.18.2",
35
35
  "ember-pikaday": "^4.0.0",
36
- "ember-power-select": "^5.0.4",
37
- "ember-resources": "^4.8.2",
36
+ "ember-power-select": "^6.0.0",
37
+ "ember-resources": "^5.2.1",
38
38
  "ember-uikit": "^5.1.3",
39
39
  "ember-validators": "^4.1.2",
40
40
  "graphql": "^15.8.0",
41
41
  "jexl": "^2.3.0",
42
42
  "lodash.isequal": "^4.5.0",
43
- "luxon": "^2.4.0",
43
+ "luxon": "^3.0.1",
44
44
  "tracked-toolbox": "^1.2.3"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@ember/optional-features": "2.0.0",
48
48
  "@ember/test-helpers": "2.8.1",
49
- "@embroider/test-setup": "1.7.1",
50
- "@faker-js/faker": "7.2.0",
51
- "@projectcaluma/ember-testing": "11.0.0-beta.8",
49
+ "@embroider/test-setup": "1.8.3",
50
+ "@faker-js/faker": "7.4.0",
51
+ "@projectcaluma/ember-testing": "11.0.0-beta.11",
52
52
  "@projectcaluma/ember-workflow": "^11.0.0-beta.7",
53
53
  "broccoli-asset-rev": "3.0.0",
54
54
  "ember-cli": "3.28.5",
@@ -73,7 +73,7 @@
73
73
  "qunit": "2.19.1",
74
74
  "qunit-dom": "2.0.0",
75
75
  "uuid": "8.3.2",
76
- "webpack": "5.73.0"
76
+ "webpack": "5.74.0"
77
77
  },
78
78
  "peerDependencies": {
79
79
  "@projectcaluma/ember-workflow": "^11.0.0-beta.7"
@@ -1,32 +0,0 @@
1
- <div class="uk-flex-middle uk-grid-divider uk-grid-column-small" uk-grid>
2
- <div uk-form-custom="target: true">
3
-
4
- <input
5
- type="file"
6
- name={{@field.pk}}
7
- id={{@field.pk}}
8
- disabled={{@disabled}}
9
- {{on "change" this.save}}
10
- />
11
- <UkButton disabled={{@disabled}}>
12
- {{t "caluma.form.selectFile"}}
13
- </UkButton>
14
- </div>
15
- {{#if (and this.downloadUrl this.downloadName)}}
16
- <div>
17
- <UkButton
18
- data-test-download-link
19
- @color="link"
20
- @onClick={{this.download}}
21
- >
22
- {{this.downloadName}}
23
- </UkButton>
24
- <UkIcon
25
- class="uk-icon-button uk-margin-small-left"
26
- role="button"
27
- @icon="trash"
28
- {{on "click" this.delete}}
29
- />
30
- </div>
31
- {{/if}}
32
- </div>
@@ -1,89 +0,0 @@
1
- import { action } from "@ember/object";
2
- import { inject as service } from "@ember/service";
3
- import Component from "@glimmer/component";
4
- import { queryManager } from "ember-apollo-client";
5
- import fetch from "fetch";
6
-
7
- import removeAnswerMutation from "@projectcaluma/ember-form/gql/mutations/remove-answer.graphql";
8
- import getFileAnswerInfoQuery from "@projectcaluma/ember-form/gql/queries/fileanswer-info.graphql";
9
-
10
- export default class CfFieldInputFileComponent extends Component {
11
- @service intl;
12
-
13
- @queryManager apollo;
14
-
15
- get downloadUrl() {
16
- return this.args.field?.answer?.value?.downloadUrl;
17
- }
18
-
19
- get downloadName() {
20
- return this.args.field?.answer?.value?.name;
21
- }
22
-
23
- @action
24
- async download() {
25
- const { downloadUrl } = await this.apollo.query(
26
- {
27
- query: getFileAnswerInfoQuery,
28
- variables: { id: this.args.field.answer.raw.id },
29
- fetchPolicy: "network-only",
30
- },
31
- "node.fileValue"
32
- );
33
-
34
- if (downloadUrl) {
35
- window.open(downloadUrl, "_blank");
36
- }
37
- }
38
-
39
- @action
40
- async save({ target }) {
41
- const file = target.files[0];
42
-
43
- if (!file) {
44
- return;
45
- }
46
-
47
- const { fileValue } = await this.args.onSave(file.name);
48
-
49
- try {
50
- const response = await fetch(fileValue.uploadUrl, {
51
- method: "PUT",
52
- body: file,
53
- });
54
-
55
- if (!response.ok) {
56
- throw new Error();
57
- }
58
-
59
- this.args.field.answer.value = {
60
- name: file.name,
61
- downloadUrl: fileValue.downloadUrl,
62
- };
63
- } catch (error) {
64
- await this.args.onSave(null);
65
- this.args.field._errors = [{ type: "uploadFailed" }];
66
- } finally {
67
- // eslint-disable-next-line require-atomic-updates
68
- target.value = "";
69
- }
70
- }
71
-
72
- @action
73
- async delete() {
74
- try {
75
- await this.apollo.mutate({
76
- mutation: removeAnswerMutation,
77
- variables: {
78
- input: {
79
- answer: this.args.field.answer.uuid,
80
- },
81
- },
82
- });
83
-
84
- await this.args.onSave(null);
85
- } catch (error) {
86
- this.args.field._errors = [{ type: "deleteFailed" }];
87
- }
88
- }
89
- }
@@ -1,7 +0,0 @@
1
- mutation RemoveAnswer($input: RemoveAnswerInput!) {
2
- removeAnswer(input: $input) {
3
- answer {
4
- id
5
- }
6
- }
7
- }
@@ -1,9 +0,0 @@
1
- #import * from '../fragments/field.graphql'
2
-
3
- mutation SaveDocumentFileAnswer($input: SaveDocumentFileAnswerInput!) {
4
- saveDocumentFileAnswer(input: $input) {
5
- answer {
6
- ...FieldAnswer
7
- }
8
- }
9
- }