diff --git a/.eslintrc.js b/.eslintrc.js index 9f0366e628..5df8184c87 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -35,6 +35,7 @@ module.exports = { files: [ '.ember-cli.js', '.eslintrc.js', + '.prettierrc.js', '.stylelintrc.js', '.template-lintrc.js', 'ember-cli-build.js', diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000000..e1eb1e906c --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = { + singleQuote: true, + overrides: [ + { + files: '**/*.hbs', + options: { + parser: 'glimmer', + singleQuote: false + } + } + ] +}; diff --git a/app/controllers/component-tree.js b/app/controllers/component-tree.js index c02a862148..d550f33cea 100644 --- a/app/controllers/component-tree.js +++ b/app/controllers/component-tree.js @@ -218,6 +218,14 @@ class RenderItem { return this.renderNode.name; } + get args() { + return this.renderNode.args; + } + + get isCurlyInvocation() { + return this.renderNode.args && this.renderNode.args.positional; + } + get hasInstance() { let { instance } = this.renderNode; return typeof instance === 'object' && instance !== null; diff --git a/app/helpers/component-argument.js b/app/helpers/component-argument.js new file mode 100644 index 0000000000..883da07f70 --- /dev/null +++ b/app/helpers/component-argument.js @@ -0,0 +1,22 @@ +import { helper } from '@ember/component/helper'; +import truncate from 'ember-inspector/utils/truncate'; + +/** + * Determine the type of the component argument for display + * + * @method componentArgumentDisplay + * @param {*} argument + * @return {*} The argument with the correct type for display + */ +export function componentArgumentDisplay([argument]) { + if (typeof argument === 'string') { + // Escape any interior quotes – we will add the surrounding quotes in the template + return truncate(argument.replace(/"/g, '\\"')); + } else if (typeof argument === 'object' && argument !== null) { + return '...'; + } + + return String(argument); +} + +export default helper(componentArgumentDisplay); diff --git a/app/helpers/is-string.js b/app/helpers/is-string.js new file mode 100644 index 0000000000..865e5732ef --- /dev/null +++ b/app/helpers/is-string.js @@ -0,0 +1,7 @@ +import { helper } from '@ember/component/helper'; + +export function isString([str]) { + return typeof str === 'string'; +} + +export default helper(isString); diff --git a/app/styles/colors.scss b/app/styles/colors.scss index 6e10be981d..8ddb830af1 100644 --- a/app/styles/colors.scss +++ b/app/styles/colors.scss @@ -32,8 +32,10 @@ --transparent: transparent; --focus: #3F81EE; --focus-text: var(--white); - --component-text: #7c027c; + --component-name: #1171E8; --component-brackets-selected: rgba(255, 255, 255, 0.4); + --component-attr: #DB00A9; + --component-highlighted-bg: #F2F2F3; --pill-bg: rgba(0, 0, 0, 0.1); --pill-bg-active: rgba(255, 255, 255, 0.3); --warning-text: #735c0f; @@ -74,8 +76,10 @@ --transparent: transparent; --focus: #2270EC; --focus-text: var(--white); - --component-text: #FF8BC9; + --component-name: #77BEFF; --component-brackets-selected: rgba(255, 255, 255, 0.4); + --component-attr: #FE7AE9; + --component-highlighted-bg: #333333; --pill-bg: rgba(255, 255, 255, 0.2); --pill-bg-active: rgba(255, 255, 255, 0.3); --warning-text: #8ca3f0; diff --git a/app/styles/component_tree.scss b/app/styles/component_tree.scss index 786913a9da..9d4fc652db 100644 --- a/app/styles/component_tree.scss +++ b/app/styles/component_tree.scss @@ -1,63 +1,139 @@ .component-tree-item { - align-items: center; - border-radius: 4px; color: var(--base12); - display: flex; font-size: 12px; - margin: 0 3px; min-height: 22px; - position: relative; -} -.component-tree-item__expand { - align-self: stretch; - cursor: pointer; - padding-left: 3px; - padding-right: 3px; -} + &__actions { + min-height: 22px; + opacity: 0; + right: var(--unit1); + + /** + * __actions is position:sticky so it is always visible and on top. + * Due to margins and padding on various elements, it was + * difficult to keep the background of __actions on top + * of .tree-item text and align with the .tree-item background. + * This pseudo element is 100% of the width of __actions plus + * a little extra to guarantee alignment. + */ + &:after { + border-bottom-right-radius: var(--unit1); + border-top-right-radius: var(--unit1); + bottom: 0; + content: ''; + left: calc(var(--unit1) * -1); + position: absolute; + right: calc(var(--unit1) * -1); + top: 0; + } -.component-tree-item__action { - align-items: center; - background: transparent; - border: 0; - cursor: pointer; - display: inline-flex; - height: 100%; - margin-left: 10px; - opacity: 0; - padding: 0; - - &:focus { - outline: none; + /** + * __actions is position:sticky so it is always visible and on top. + * :before is a gradient to soften the edge when overlaying text. + */ + &:before { + bottom: 0; + content: ''; + left: calc(var(--unit3) * -1); + position: absolute; + top: 0; + width: var(--unit3); + } } - &.disabled { - cursor: not-allowed; + &__action { + &:focus { + outline: none; + } + + polygon, + rect, + path { + fill: var(--base15); + } } - polygon, - rect, - path { - fill: var(--base15); + /** + * This element helps with a visual bug. + * When an item overflows the bounds of the list, + * the item background color clips at the point of the overflow. + * This child of the item has the same background color + * and will extend past the overflow point. + */ + .component-tree-item-background { + min-height: 22px; + } + + &:hover { + background-color: var(--component-highlighted-bg); + + .component-tree-item-background { + background-color: var(--component-highlighted-bg); + } + + .component-tree-item__actions { + opacity: 1; + + &:after { + background-color: var(--component-highlighted-bg); + } + + &:before { + background: linear-gradient(to right, var(--transparent), var(--component-highlighted-bg) 75%); + } + } } } .component-tree-item__tag { - cursor: default; - display: flex; + .component-name { + color: var(--component-name); + } + + .bracket-token { + color: var(--base09); + } + + .key-token { + color: var(--component-attr); + } + + .value-token { + color: var(--spec03); + } } -.component-tree-item--has-instance .component-tree-item__tag { - cursor: pointer; +/** + * Modifier + * highlighted - the component currently being previewed + */ + +.component-tree-item.component-tree-item--highlighted { + background-color: var(--component-highlighted-bg); + border-radius: 0; +} + +.component-tree-item--selected .component-tree-item__tag { + .component-name, + .bracket-token, + .key-token, + .value-token { + color: var(--focus-text); + } +} + +.component-tree-item__bracket:before, +.component-tree-item__bracket:after, +.component-tree-item-classic__bracket:before, +.component-tree-item-classic__bracket:after { + color: var(--base09); } .component-tree-item__bracket:before { - color: var(--base07); content: '<'; } .component-tree-item__bracket:after { - color: var(--base07); content: '>'; } @@ -65,31 +141,17 @@ content: '/>'; } -.component-tree-item:hover { - background-color: var(--spec06); - - .component-tree-item__action { - opacity: 1; - } - - .component-tree-item__action.disabled { - opacity: 0.65; - } +.component-tree-item-classic__bracket:before { + content: '{{'; } -/** - * Modifier - * highlighted - children of selected component - */ - -.component-tree-item.component-tree-item--highlighted { - background-color: var(--spec06); - border-radius: 0; +.component-tree-item-classic__bracket:after { + content: '}}'; } /** * Modifier - * selected + * selected - user clicked on component */ .component-tree-item.component-tree-item--selected { @@ -100,6 +162,22 @@ background: var(--focus); } + .component-tree-item-background { + background: var(--focus); + } + + .component-tree-item__actions { + opacity: 1; + + &:after { + background: var(--focus); + } + + &:before { + background: linear-gradient(to right, var(--transparent), var(--focus) 75%); + } + } + .component-tree-item__bracket:before, .component-tree-item__bracket:after { color: var(--component-backets-selected); @@ -115,12 +193,8 @@ fill: var(--focus-text); } } - - .component-tree-item__action.disabled { - opacity: 0.65; - } } .component-tree-item--component { - color: var(--component-text); + color: var(--base09); } diff --git a/app/styles/utils.scss b/app/styles/utils.scss index cbc20cfb33..19979733f9 100644 --- a/app/styles/utils.scss +++ b/app/styles/utils.scss @@ -1,6 +1,18 @@ $units: 0, 1, 2, 3, 4; +.relative { position: relative; } + +.sticky { + position: -webkit-sticky; + position: sticky; +} + +.right-0 { + right: 0; +} + .flex { display: flex; } +.inline-flex { display: inline-flex; } .flex-grow { flex-grow: 1; } .flex-grow-0 { flex-grow: 0; } @@ -8,6 +20,7 @@ $units: 0, 1, 2, 3, 4; .flex-shrink-0 { flex-shrink: 0; } .items-center { align-items: center; } +.self-stretch { align-self: stretch; } .w-5 { width: var(--unit5); } .w-6 { width: var(--unit6); } @@ -18,10 +31,13 @@ $units: 0, 1, 2, 3, 4; .w-11 { width: var(--unit11); } .w-12 { width: var(--unit12); } +.h-full { height: 100%; } + @each $key in $units { .w-#{$key} { width: var(--unit#{$key}); } .h-#{$key} { height: var(--unit#{$key}); } + .p-#{$key} { padding: var(--unit#{$key}); } .pt-#{$key} { padding-top: var(--unit#{$key}); } .pr-#{$key} { padding-right: var(--unit#{$key}); } .pb-#{$key} { padding-bottom: var(--unit#{$key}); } @@ -37,6 +53,7 @@ $units: 0, 1, 2, 3, 4; padding-top: var(--unit#{$key}); } + .m-#{$key} { margin: var(--unit#{$key}); } .mt-#{$key} { margin-top: var(--unit#{$key}); } .mr-#{$key} { margin-right: var(--unit#{$key}); } .mb-#{$key} { margin-bottom: var(--unit#{$key}); } @@ -53,10 +70,12 @@ $units: 0, 1, 2, 3, 4; } } +.border-0 { border-width: 0; } + .rounded-none { border-radius: 0; } -.rounded-sm { border-radius: 0.125rem; } -.rounded { border-radius: 0.25rem; } -.rounded-lg { border-radius: 0.5rem; } +.rounded-sm { border-radius: calc(var(--unit1) / 2); } +.rounded { border-radius: var(--unit1); } +.rounded-lg { border-radius: var(--unit2); } .rounded-full { border-radius: 9999px; } .font-bold { font-weight: bold; } @@ -65,7 +84,14 @@ $units: 0, 1, 2, 3, 4; .text-left { text-align: left; } .text-center { text-align: center; } .text-right { text-align: right; } +.whitespace-no-wrap { white-space: nowrap; } +.bg-transparent { background-color: var(--transparent); } .bg-base00 { background-color: var(--base00); } .bg-base01 { background-color: var(--base01); } .bg-base02 { background-color: var(--base02); } + +.cursor-default { cursor: default; } +.cursor-pointer { cursor: pointer; } + +.z-10 { z-index: 10; } diff --git a/app/templates/components/component-tree-item.hbs b/app/templates/components/component-tree-item.hbs index ffbf4cb031..4d71fb5ff1 100644 --- a/app/templates/components/component-tree-item.hbs +++ b/app/templates/components/component-tree-item.hbs @@ -1,54 +1,115 @@
- {{#if @item.isComponent}}
- {{classify @item.name}}
- {{else if @item.isOutlet}}
- \{{outlet "{{@item.name}}"}}
- {{else if @item.isEngine}}
- \{{mount "{{@item.name}}"}}
- {{else if @item.isRouteTemplate}}
- {{@item.name}} route
+
+ {{#if @item.hasChildren}}
+
{{/if}}
-
- {{#if @item.hasBounds}}
-
+ {{#if @item.isComponent}}
+ {{#if @item.isCurlyInvocation}}
+
+ {{@item.name}}
+
-
+ {{#each @item.args.positional as |value|}}
+
+ {{if (is-string value) "\""}}
+
+ {{component-argument value}}
+
+ {{if (is-string value) "\""}}
+
+ {{/each}}
+
+ {{#each-in @item.args.named as |name value|}}
+
+
+ {{name}}
+
+ ={{if (is-string value) "\""}}
+
+ {{component-argument value}}
+
+ {{if (is-string value) "\""}}
+
+ {{/each-in}}
+ {{else}}
+
+ {{classify @item.name}}
+
+
+ {{#each-in @item.args.named as |name value|}}
+
+
+ @{{name}}
+
+ ={{#if (is-string value)}}"{{else}}\{{{{/if}}
+
+ {{component-argument value}}
+
+ {{#if (is-string value)}}"{{else}}}}{{/if}}
+
+ {{/each-in}}
+ {{/if}}
+ {{else if @item.isOutlet}}
+ \{{outlet "{{@item.name}}"}}
+ {{else if @item.isEngine}}
+ \{{mount "{{@item.name}}"}}
+ {{else if @item.isRouteTemplate}}
+ {{@item.name}} route
+ {{/if}}
+
+
+
+ {{#if @item.hasBounds}}
+
+
+
+
+
{{/if}}