From 789244ff331b3fb481a2be3bf650a3f7249032d6 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Tue, 19 Dec 2023 18:15:03 +0800 Subject: [PATCH] WIP: Timeline component improvements, added `` component --- addon/components/file-icon.hbs | 10 +- addon/components/file-icon.js | 59 +++- .../modal/layouts/option-prompt.hbs | 12 +- addon/components/timeline.hbs | 16 +- addon/components/timeline.js | 53 +-- addon/components/timeline/item.hbs | 2 +- addon/components/timeline/item.js | 14 +- addon/services/modals-manager.js | 1 + addon/styles/components/timeline.css | 56 ++- addon/styles/layout/utilities.css | 1 + addon/utils/format-currency.js | 2 +- lib/.gitkeep | 0 lib/ember-cli-accounting | 1 - package.json | 324 +++++++++--------- 14 files changed, 328 insertions(+), 223 deletions(-) delete mode 100644 lib/.gitkeep delete mode 160000 lib/ember-cli-accounting diff --git a/addon/components/file-icon.hbs b/addon/components/file-icon.hbs index fb5c4b1..f5ecbdd 100644 --- a/addon/components/file-icon.hbs +++ b/addon/components/file-icon.hbs @@ -1 +1,9 @@ -{{yield}} \ No newline at end of file +
+ + + {{this.extension}} + +
+ {{yield}} +
+
\ No newline at end of file diff --git a/addon/components/file-icon.js b/addon/components/file-icon.js index d6942bb..c372bd8 100644 --- a/addon/components/file-icon.js +++ b/addon/components/file-icon.js @@ -1,3 +1,60 @@ import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import getWithDefault from '@fleetbase/ember-core/utils/get-with-default'; -export default class FileIconComponent extends Component {} +export default class FileIconComponent extends Component { + @tracked file; + @tracked extension; + @tracked icon; + + constructor(owner, { file }) { + super(...arguments); + + this.file = file; + this.extension = this.getExtension(file); + this.icon = this.getIcon(file); + } + + getExtension(file) { + return getWithDefault( + { + 'application/vnd.ms-excel': 'xls', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xls', + 'vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xls', + 'vnd.ms-excel': 'xls', + 'text/csv': 'csv', + 'text/tsv': 'tsv', + xlsx: 'xls', + xls: 'xls', + xlsb: 'xls', + xlsm: 'xls', + docx: 'doc', + docm: 'doc', + }, + getWithDefault(file, 'extension', 'xls'), + 'xls' + ); + } + + getIcon(file) { + const extension = this.getExtension(file); + + return getWithDefault( + { + xlsx: 'file-excel', + xls: 'file-excel', + xlsb: 'file-excel', + xlsm: 'file-excel', + csv: 'file-spreadsheet', + tsv: 'file-spreadsheet', + docx: 'file-word', + docm: 'file-word', + pdf: 'file-pdf', + ppt: 'file-powerpoint', + pptx: 'file-powerpoint', + }, + extension, + 'file-alt' + ); + } +} diff --git a/addon/components/modal/layouts/option-prompt.hbs b/addon/components/modal/layouts/option-prompt.hbs index e035c8f..1ee94ff 100644 --- a/addon/components/modal/layouts/option-prompt.hbs +++ b/addon/components/modal/layouts/option-prompt.hbs @@ -1 +1,11 @@ - \ No newline at end of file + +
+ {{#each @options.promptOptions as |option index|}} +
+ + {{humanize option}} + +
+ {{/each}} +
+
\ No newline at end of file diff --git a/addon/components/timeline.hbs b/addon/components/timeline.hbs index e4636ea..6bbbcca 100644 --- a/addon/components/timeline.hbs +++ b/addon/components/timeline.hbs @@ -1,15 +1,21 @@
- +
+ +
- {{#each this.visibleTimelineData as |activity|}} - {{yield (component "timeline/item" activity=activity context=this.context)}} - {{/each}} +
+ {{#each this.visibleActivities as |activity|}} + {{yield (component "timeline/item" activity=activity context=this.context)}} + {{/each}} +
- +
+ +
\ No newline at end of file diff --git a/addon/components/timeline.js b/addon/components/timeline.js index fb58b45..da14ffb 100644 --- a/addon/components/timeline.js +++ b/addon/components/timeline.js @@ -1,61 +1,38 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; -import { action, computed, set } from '@ember/object'; -import { isArray } from '@ember/array'; +import { action } from '@ember/object'; import { htmlSafe } from '@ember/template'; export default class TimelineComponent extends Component { @tracked startIndex = 0; @tracked endIndex = Math.min(2, this.args.activity.length - 1); - @tracked timelineNode; - @tracked timelineWrapperNode; - @tracked timelineItemWidth; - - context = { - previous: this.previous, - next: this.next, - data: this.visibleTimelineData, - }; - - @computed('args.activity.length', 'endIndex', 'startIndex') get timelineActivity() { - if (isArray(this.args.activity)) { - return this.args.activity.map((activity, index) => { - if (index === this.args.activity.length - 1) { - set(activity, 'isActive', true); - } - - return activity; - }); - } - return []; - } - - @computed('timelineActivity.@each.isActive', 'startIndex', 'endIndex') get visibleTimelineData() { - return this.timelineActivity.slice(this.startIndex, this.endIndex + 1); - } - - @computed('startIndex', 'timelineItemWidth') get wrapperStyle() { - const translateX = `calc(-${this.startIndex} * ${this.timelineItemWidth}px)`; - return htmlSafe(`transform: translateX(${translateX});`); - } + @tracked visibleActivities = this.args.activity.slice(this.startIndex, this.endIndex + 1); @action setupComponent(timelineNode) { this.timelineNode = timelineNode; this.timelineWrapperNode = timelineNode.querySelector('.timeline-wrapper'); - this.timelineItemWidth = this.timelineWrapperNode?.firstElementChild?.offsetWidth; + this.timelineItemsContainerNode = this.timelineWrapperNode.firstElementChild; } @action previous() { if (this.startIndex > 0) { - this.startIndex--; - this.endIndex--; + this.startIndex -= 1; + this.endIndex -= 1; + this.updateTimelinePosition(); } } @action next() { if (this.endIndex < this.args.activity.length - 1) { - this.startIndex++; - this.endIndex++; + this.startIndex += 1; + this.endIndex += 1; + this.updateTimelinePosition(); } } + + updateTimelinePosition() { + const translateX = `calc(-${this.startIndex * (100 / 3)}%)`; // Assuming each item takes up 33.33% of the width + this.timelineItemsContainerNode.style.transform = `translateX(${translateX})`; + this.visibleActivities = this.args.activity.slice(this.startIndex, this.endIndex + 1); + } } diff --git a/addon/components/timeline/item.hbs b/addon/components/timeline/item.hbs index a8d0525..ca39d8f 100644 --- a/addon/components/timeline/item.hbs +++ b/addon/components/timeline/item.hbs @@ -1 +1 @@ -
{{yield @activity @context}}
\ No newline at end of file +
{{yield @activity @context}}
\ No newline at end of file diff --git a/addon/components/timeline/item.js b/addon/components/timeline/item.js index 25dc53e..3383752 100644 --- a/addon/components/timeline/item.js +++ b/addon/components/timeline/item.js @@ -1,3 +1,15 @@ import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; -export default class TimelineItemComponent extends Component {} +export default class TimelineItemComponent extends Component { + @tracked isActive = false; + + constructor(owner, { activeStatus, activity }) { + super(...arguments); + + this.isActive = typeof activeStatus === 'string' && typeof activity.code === 'string' && activeStatus.toLowerCase() === activity.code.toLowerCase(); + if (typeof activity.isActive === 'boolean') { + this.isActive = activity.isActive; + } + } +} diff --git a/addon/services/modals-manager.js b/addon/services/modals-manager.js index 2620002..040cbeb 100644 --- a/addon/services/modals-manager.js +++ b/addon/services/modals-manager.js @@ -194,6 +194,7 @@ export default class ModalsManagerService extends Service { resolve(selected); }, decline: () => { + this.done(); resolve(null); }, ...modalOptions, diff --git a/addon/styles/components/timeline.css b/addon/styles/components/timeline.css index 53e127e..4b979bb 100644 --- a/addon/styles/components/timeline.css +++ b/addon/styles/components/timeline.css @@ -1,8 +1,8 @@ .timeline { - display: flex; + display: grid; + grid-template-columns: repeat(14, 1fr); align-items: center; width: 100%; - max-width: 600px; position: relative; margin: 0 auto; box-sizing: border-box; @@ -17,9 +17,31 @@ .timeline-controls { display: flex; - justify-content: space-between; + align-items: center; position: relative; + grid-column: span 2; margin-top: -20px; + cursor: default; +} + +.timeline-controls button, +.timeline-controls a { + cursor: default; +} + +.timeline-controls > .timeline-controls-button-wrapper { + display: flex; + align-items: center; + justify-content: center; + padding: 0 1rem; +} + +.timeline > .timeline-controls:first-child { + justify-content: flex-end; +} + +.timeline > .timeline-controls:last-child { + justify-content: flex-start; } .timeline-arrow-left, @@ -40,18 +62,23 @@ } .timeline-wrapper { - display: flex; - flex-direction: row; - align-items: center; - position: relative; - width: 100%; - max-height: 400px; - flex: 1; + display: grid; + grid-template-columns: 1fr; + grid-column: span 10; transition: transform 0.5s ease; + max-height: 400px; + overflow: hidden; +} + +.timeline-items-container { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: .5rem; + position: relative; overflow: visible; margin-bottom: 10px; - border-bottom: 3px rgb(243, 244, 246) solid; padding-bottom: 20px; + border-bottom: 3px rgb(243, 244, 246) solid; } .timeline-item { @@ -59,6 +86,13 @@ flex-direction: column; will-change: transform, opacity; overflow: visible; + font-size: 0.875rem; + line-height: 1.25rem; + box-sizing: border-box; +} + +.timeline-item * { + box-sizing: border-box; } .timeline-item.active::after { diff --git a/addon/styles/layout/utilities.css b/addon/styles/layout/utilities.css index dcee91a..74f10db 100644 --- a/addon/styles/layout/utilities.css +++ b/addon/styles/layout/utilities.css @@ -300,6 +300,7 @@ body[data-theme='dark'] .dimensions-box:before { height: 19.53rem; } +.w-99pr, .w-almost-full { width: 99%; } diff --git a/addon/utils/format-currency.js b/addon/utils/format-currency.js index ef74680..d279163 100644 --- a/addon/utils/format-currency.js +++ b/addon/utils/format-currency.js @@ -1,5 +1,5 @@ import getCurrency from './get-currency'; -import formatMoney from 'accounting/format-money'; +import formatMoney from '@fleetbase/ember-accounting/utils/format-money'; export default function formatCurrency(amount = 0, currencyCode = 'USD') { let currency = getCurrency(currencyCode); diff --git a/lib/.gitkeep b/lib/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/ember-cli-accounting b/lib/ember-cli-accounting deleted file mode 160000 index 7864f81..0000000 --- a/lib/ember-cli-accounting +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7864f81b93be7b3616ceb535b44707d1171fffc0 diff --git a/package.json b/package.json index dfaaf89..5d044ff 100644 --- a/package.json +++ b/package.json @@ -1,165 +1,165 @@ { - "name": "@fleetbase/ember-ui", - "version": "0.2.7", - "description": "Fleetbase UI provides all the interface components, helpers, services and utilities for building a Fleetbase extension into the Console.", - "keywords": [ - "fleetbase-ui", - "fleetbase-extension-ui", - "fleetbase", - "ember-addon" - ], - "repository": "https://github.com/fleetbase/ember-ui", - "license": "MIT", - "author": "Fleetbase Pte Ltd ", - "directories": { - "app": "app", - "addon": "addon", - "tests": "tests" - }, - "scripts": { - "build": "ember build --environment=production", - "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"", - "lint:css": "stylelint \"**/*.css\"", - "lint:css:fix": "concurrently \"npm:lint:css -- --fix\"", - "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"", - "lint:hbs": "ember-template-lint .", - "lint:hbs:fix": "ember-template-lint . --fix", - "lint:js": "eslint . --cache", - "lint:js:fix": "eslint . --fix", - "start": "ember serve", - "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"", - "test:ember": "ember test", - "test:ember-compatibility": "ember try:each", - "publish:npm": "npm config set registry https://registry.npmjs.org/ && npm publish", - "publish:github": "npm config set '@fleetbase:registry' https://npm.pkg.github.com/ && npm publish" - }, - "dependencies": { - "@babel/core": "^7.23.2", - "@ember/render-modifiers": "^2.0.4", - "@ember/string": "^3.0.1", - "@embroider/addon": "^0.30.0", - "@embroider/macros": "^1.8.3", - "@floating-ui/dom": "^1.0.1", - "@fortawesome/ember-fontawesome": "^1.0.0", - "@fortawesome/fontawesome-svg-core": "^6.4.0", - "@fortawesome/free-brands-svg-icons": "^6.4.0", - "@fortawesome/free-solid-svg-icons": "^6.4.0", - "@fullcalendar/core": "^6.1.9", - "@fullcalendar/daygrid": "^6.1.9", - "@fullcalendar/interaction": "^6.1.9", - "@makepanic/ember-power-calendar-date-fns": "^0.4.2", - "@tailwindcss/forms": "^0.5.3", - "air-datepicker": "^3.3.5", - "autonumeric": "^4.6.2", - "autoprefixer": "^10.4.15", - "broccoli-funnel": "^3.0.8", - "broccoli-merge-trees": "^4.2.0", - "chart.js": "^4.2.1", - "chartjs-adapter-date-fns": "^3.0.0", - "date-fns": "^2.30.0", - "ember-animated": "^1.1.2", - "ember-auto-import": "^2.6.3", - "ember-basic-dropdown": "^7.3.0", - "ember-cli-accounting": "file:./lib/ember-cli-accounting", - "ember-cli-babel": "^8.2.0", - "ember-cli-htmlbars": "^6.3.0", - "ember-cli-postcss": "^8.2.0", - "ember-cli-string-helpers": "^6.1.0", - "ember-composable-helpers": "^5.0.0", - "ember-concurrency": "^3.1.1", - "ember-concurrency-decorators": "^2.0.3", - "ember-concurrency-test-waiter": "^0.4.0", - "ember-file-upload": "8.4.0", - "ember-focus-trap": "^1.0.1", - "ember-get-config": "^2.1.1", - "ember-inflector": "^4.0.2", - "ember-loading": "^2.0.0", - "ember-math-helpers": "^4.0.0", - "ember-modifier": "^4.1.0", - "ember-on-helper": "^0.1.0", - "ember-power-calendar": "^0.18.0", - "ember-power-select": "^7.2.0", - "ember-ref-bucket": "^4.1.0", - "ember-responsive": "^5.0.0", - "ember-style-modifier": "^3.0.1", - "ember-truth-helpers": "^4.0.3", - "ember-window-mock": "^0.9.0", - "ember-wormhole": "^0.6.0", - "imask": "^6.4.3", - "intl-tel-input": "^18.1.3", - "postcss-at-rules-variables": "^0.3.0", - "postcss-conditionals-renewed": "^1.0.0", - "postcss-each": "^1.1.0", - "postcss-import": "^15.1.0", - "postcss-mixins": "^9.0.4", - "postcss-preset-env": "^9.1.1", - "tailwindcss": "^3.1.8" - }, - "devDependencies": { - "@babel/eslint-parser": "^7.22.15", - "@babel/plugin-proposal-decorators": "^7.23.2", - "@ember/optional-features": "^2.0.0", - "@ember/test-helpers": "^3.2.0", - "@embroider/test-setup": "^3.0.2", - "@glimmer/component": "^1.1.2", - "@glimmer/tracking": "^1.1.2", - "broccoli-asset-rev": "^3.0.0", - "clean-css": "^5.3.2", - "concurrently": "^8.2.2", - "ember-cli": "~5.4.1", - "ember-cli-clean-css": "^3.0.0", - "ember-cli-dependency-checker": "^3.3.2", - "ember-cli-inject-live-reload": "^2.1.0", - "ember-cli-sri": "^2.1.1", - "ember-cli-terser": "^4.0.2", - "ember-load-initializers": "^2.1.2", - "ember-page-title": "^8.0.0", - "ember-qunit": "^8.0.1", - "ember-resolver": "^11.0.1", - "ember-source": "~5.4.0", - "ember-source-channel-url": "^3.0.0", - "ember-template-lint": "^5.11.2", - "ember-try": "^3.0.0", - "eslint": "^8.52.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-ember": "^11.11.1", - "eslint-plugin-n": "^16.2.0", - "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-qunit": "^8.0.1", - "loader.js": "^4.7.0", - "prettier": "^3.0.3", - "qunit": "^2.20.0", - "qunit-dom": "^2.0.0", - "stylelint": "^15.11.0", - "stylelint-config-standard": "^34.0.0", - "stylelint-prettier": "^4.0.2", - "webpack": "^5.89.0" - }, - "engines": { - "node": ">= 18" - }, - "ember": { - "edition": "octane" - }, - "ember-addon": { - "before": [ - "ember-cli-postcss" + "name": "@fleetbase/ember-ui", + "version": "0.2.7", + "description": "Fleetbase UI provides all the interface components, helpers, services and utilities for building a Fleetbase extension into the Console.", + "keywords": [ + "fleetbase-ui", + "fleetbase-extension-ui", + "fleetbase", + "ember-addon" ], - "configPath": "tests/dummy/config" - }, - "prettier": { - "trailingComma": "es5", - "tabWidth": 4, - "semi": true, - "singleQuote": true, - "printWidth": 190, - "overrides": [ - { - "files": "*.hbs", - "options": { - "singleQuote": false - } - } - ] - } + "repository": "https://github.com/fleetbase/ember-ui", + "license": "MIT", + "author": "Fleetbase Pte Ltd ", + "directories": { + "app": "app", + "addon": "addon", + "tests": "tests" + }, + "scripts": { + "build": "ember build --environment=production", + "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"", + "lint:css": "stylelint \"**/*.css\"", + "lint:css:fix": "concurrently \"npm:lint:css -- --fix\"", + "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"", + "lint:hbs": "ember-template-lint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", + "start": "ember serve", + "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"", + "test:ember": "ember test", + "test:ember-compatibility": "ember try:each", + "publish:npm": "npm config set registry https://registry.npmjs.org/ && npm publish", + "publish:github": "npm config set '@fleetbase:registry' https://npm.pkg.github.com/ && npm publish" + }, + "dependencies": { + "@fleetbase/ember-accounting": "link:../ember-accounting", + "@babel/core": "^7.23.2", + "@ember/render-modifiers": "^2.0.4", + "@ember/string": "^3.0.1", + "@embroider/addon": "^0.30.0", + "@embroider/macros": "^1.8.3", + "@floating-ui/dom": "^1.0.1", + "@fortawesome/ember-fontawesome": "^1.0.0", + "@fortawesome/fontawesome-svg-core": "^6.4.0", + "@fortawesome/free-brands-svg-icons": "^6.4.0", + "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@fullcalendar/core": "^6.1.9", + "@fullcalendar/daygrid": "^6.1.9", + "@fullcalendar/interaction": "^6.1.9", + "@makepanic/ember-power-calendar-date-fns": "^0.4.2", + "@tailwindcss/forms": "^0.5.3", + "air-datepicker": "^3.3.5", + "autonumeric": "^4.6.2", + "autoprefixer": "^10.4.15", + "broccoli-funnel": "^3.0.8", + "broccoli-merge-trees": "^4.2.0", + "chart.js": "^4.2.1", + "chartjs-adapter-date-fns": "^3.0.0", + "date-fns": "^2.30.0", + "ember-animated": "^1.1.2", + "ember-auto-import": "^2.6.3", + "ember-basic-dropdown": "^7.3.0", + "ember-cli-babel": "^8.2.0", + "ember-cli-htmlbars": "^6.3.0", + "ember-cli-postcss": "^8.2.0", + "ember-cli-string-helpers": "^6.1.0", + "ember-composable-helpers": "^5.0.0", + "ember-concurrency": "^3.1.1", + "ember-concurrency-decorators": "^2.0.3", + "ember-concurrency-test-waiter": "^0.4.0", + "ember-file-upload": "8.4.0", + "ember-focus-trap": "^1.0.1", + "ember-get-config": "^2.1.1", + "ember-inflector": "^4.0.2", + "ember-loading": "^2.0.0", + "ember-math-helpers": "^4.0.0", + "ember-modifier": "^4.1.0", + "ember-on-helper": "^0.1.0", + "ember-power-calendar": "^0.18.0", + "ember-power-select": "^7.2.0", + "ember-ref-bucket": "^4.1.0", + "ember-responsive": "^5.0.0", + "ember-style-modifier": "^3.0.1", + "ember-truth-helpers": "^4.0.3", + "ember-window-mock": "^0.9.0", + "ember-wormhole": "^0.6.0", + "imask": "^6.4.3", + "intl-tel-input": "^18.1.3", + "postcss-at-rules-variables": "^0.3.0", + "postcss-conditionals-renewed": "^1.0.0", + "postcss-each": "^1.1.0", + "postcss-import": "^15.1.0", + "postcss-mixins": "^9.0.4", + "postcss-preset-env": "^9.1.1", + "tailwindcss": "^3.1.8" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.22.15", + "@babel/plugin-proposal-decorators": "^7.23.2", + "@ember/optional-features": "^2.0.0", + "@ember/test-helpers": "^3.2.0", + "@embroider/test-setup": "^3.0.2", + "@glimmer/component": "^1.1.2", + "@glimmer/tracking": "^1.1.2", + "broccoli-asset-rev": "^3.0.0", + "clean-css": "^5.3.2", + "concurrently": "^8.2.2", + "ember-cli": "~5.4.1", + "ember-cli-clean-css": "^3.0.0", + "ember-cli-dependency-checker": "^3.3.2", + "ember-cli-inject-live-reload": "^2.1.0", + "ember-cli-sri": "^2.1.1", + "ember-cli-terser": "^4.0.2", + "ember-load-initializers": "^2.1.2", + "ember-page-title": "^8.0.0", + "ember-qunit": "^8.0.1", + "ember-resolver": "^11.0.1", + "ember-source": "~5.4.0", + "ember-source-channel-url": "^3.0.0", + "ember-template-lint": "^5.11.2", + "ember-try": "^3.0.0", + "eslint": "^8.52.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-ember": "^11.11.1", + "eslint-plugin-n": "^16.2.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-qunit": "^8.0.1", + "loader.js": "^4.7.0", + "prettier": "^3.0.3", + "qunit": "^2.20.0", + "qunit-dom": "^2.0.0", + "stylelint": "^15.11.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-prettier": "^4.0.2", + "webpack": "^5.89.0" + }, + "engines": { + "node": ">= 18" + }, + "ember": { + "edition": "octane" + }, + "ember-addon": { + "before": [ + "ember-cli-postcss" + ], + "configPath": "tests/dummy/config" + }, + "prettier": { + "trailingComma": "es5", + "tabWidth": 4, + "semi": true, + "singleQuote": true, + "printWidth": 190, + "overrides": [ + { + "files": "*.hbs", + "options": { + "singleQuote": false + } + } + ] + } }