From cbe7389d2b25a3ce1b5233310c1d98c9bf7290fa Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Tue, 26 Sep 2023 21:37:17 -0400 Subject: [PATCH 1/3] ProductAvatar --- web/app/components/doc/thumbnail.ts | 4 +- .../components/inputs/product-select/item.hbs | 34 +++++++------- web/app/components/product-avatar.gts | 45 +++++++++++++++++++ ...t-badge-link.ts => product-badge-link.gts} | 14 ++++++ web/app/components/product-badge-link.hbs | 12 ----- web/app/helpers/get-product-id.ts | 4 +- web/app/utils/get-product-id.ts | 8 ++-- web/types/hds/flight-icon.d.ts | 28 +++++++----- 8 files changed, 100 insertions(+), 49 deletions(-) create mode 100644 web/app/components/product-avatar.gts rename web/app/components/{product-badge-link.ts => product-badge-link.gts} (81%) delete mode 100644 web/app/components/product-badge-link.hbs diff --git a/web/app/components/doc/thumbnail.ts b/web/app/components/doc/thumbnail.ts index 47cfeb08f..0ee05ed72 100644 --- a/web/app/components/doc/thumbnail.ts +++ b/web/app/components/doc/thumbnail.ts @@ -20,11 +20,11 @@ export default class DocThumbnailComponent extends Component - + - - {{@product}} - +
- {{#if @abbreviation}} - - {{@abbreviation}} - - {{/if}} + {{#if @product}} + + {{@product}} + + {{else}} + + {{/if}} + + {{#if @abbreviation}} + + {{@abbreviation}} + + {{/if}} +
{{#if @isSelected}} { + get productID() { + return getProductID(this.args.productArea); + } + + get sizeStyles() { + const iconSize = this.args.iconSize || 12; + return `height: ${iconSize}px; width: ${iconSize}px;`; + } + + +} + +declare module "@glint/environment-ember-loose/registry" { + export default interface Registry { + ProductAvatar: typeof ProductAvatarComponent; + } +} diff --git a/web/app/components/product-badge-link.ts b/web/app/components/product-badge-link.gts similarity index 81% rename from web/app/components/product-badge-link.ts rename to web/app/components/product-badge-link.gts index 7db93ce8b..16cfc1950 100644 --- a/web/app/components/product-badge-link.ts +++ b/web/app/components/product-badge-link.gts @@ -1,6 +1,8 @@ import RouterService from "@ember/routing/router-service"; import { inject as service } from "@ember/service"; import Component from "@glimmer/component"; +import { LinkTo } from "@ember/routing"; +import ProductAvatar from "hermes/components/product-avatar"; interface ProductBadgeLinkComponentSignature { Element: HTMLAnchorElement; @@ -55,6 +57,18 @@ export default class ProductBadgeLinkComponent extends Component + + + {{this.productAreaName}} + + } declare module "@glint/environment-ember-loose/registry" { diff --git a/web/app/components/product-badge-link.hbs b/web/app/components/product-badge-link.hbs deleted file mode 100644 index 3c4fbf1f7..000000000 --- a/web/app/components/product-badge-link.hbs +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/web/app/helpers/get-product-id.ts b/web/app/helpers/get-product-id.ts index 7c8c7c60f..a0236d6f7 100644 --- a/web/app/helpers/get-product-id.ts +++ b/web/app/helpers/get-product-id.ts @@ -5,12 +5,12 @@ export interface GetProductIDSignature { Args: { Positional: [string | undefined]; }; - Return: string | null; + Return: string | undefined; } const getProductIDHelper = helper(([productName]) => { if (!productName) { - return null; + return; } return getProductId(productName); }); diff --git a/web/app/utils/get-product-id.ts b/web/app/utils/get-product-id.ts index d3b421713..926541383 100644 --- a/web/app/utils/get-product-id.ts +++ b/web/app/utils/get-product-id.ts @@ -1,8 +1,6 @@ -export default function getProductId( - productName: string | null -): string | null { +export default function getProductId(productName?: string) { if (!productName) { - return null; + return; } let product = productName.toLowerCase(); @@ -19,6 +17,6 @@ export default function getProductId( case "cloud platform": return "hcp"; default: - return null; + return; } } diff --git a/web/types/hds/flight-icon.d.ts b/web/types/hds/flight-icon.d.ts index 417d6403e..419e80327 100644 --- a/web/types/hds/flight-icon.d.ts +++ b/web/types/hds/flight-icon.d.ts @@ -1,13 +1,19 @@ -// https://helios.hashicorp.design/icons/usage-guidelines?tab=code +declare module "@hashicorp/ember-flight-icons/components/flight-icon" { + import Component from "@glimmer/component"; + import { ComponentLike } from "@glint/template"; -import { ComponentLike } from "@glint/template"; + // https://helios.hashicorp.design/icons/usage-guidelines?tab=code -export type FlightIconComponent = ComponentLike<{ - Element: SVGElement; - Args: { - name: string; - size?: "16" | "24"; - color?: string; - stretched?: boolean; - }; -}>; + interface FlightIconComponentSignature { + Element: SVGElement; + Args: { + name: string; + size?: "16" | "24"; + color?: string; + stretched?: boolean; + }; + } + + export type FlightIconComponent = ComponentLike; + export default class FlightIcon extends Component {} +} From 1b06441c8f8e419bc341ee896af54a27d59e514c Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Thu, 5 Oct 2023 17:21:20 -0400 Subject: [PATCH 2/3] LetterCount and ProductAbbreviations --- .../inputs/product-select/index.hbs | 21 -------- .../components/inputs/product-select/index.ts | 10 ---- web/app/components/product-avatar.gts | 21 ++++++-- web/app/helpers/get-letter-count.ts | 21 ++++++++ web/app/helpers/get-product-abbreviation.ts | 50 +++++++++++++++++++ web/app/routes/application.ts | 2 +- web/app/routes/authenticated.ts | 7 +++ web/app/services/product-areas.ts | 16 +++--- web/app/styles/app.scss | 1 + web/app/styles/components/product-avatar.scss | 11 ++++ web/app/styles/hashicorp/product-badge.scss | 6 ++- web/tests/unit/services/product-areas-test.ts | 2 +- 12 files changed, 123 insertions(+), 45 deletions(-) create mode 100644 web/app/helpers/get-letter-count.ts create mode 100644 web/app/helpers/get-product-abbreviation.ts create mode 100644 web/app/styles/components/product-avatar.scss diff --git a/web/app/components/inputs/product-select/index.hbs b/web/app/components/inputs/product-select/index.hbs index ab8a4c17c..05fd78f48 100644 --- a/web/app/components/inputs/product-select/index.hbs +++ b/web/app/components/inputs/product-select/index.hbs @@ -70,26 +70,5 @@ {{/if}} - {{else if this.fetchProductAreas.isRunning}} -
- -
- {{else if this.errorIsShown}} -
- Failed to load -
- - {{else}} -
{{/if}} diff --git a/web/app/components/inputs/product-select/index.ts b/web/app/components/inputs/product-select/index.ts index b3a28c44d..f62280b78 100644 --- a/web/app/components/inputs/product-select/index.ts +++ b/web/app/components/inputs/product-select/index.ts @@ -28,7 +28,6 @@ export default class InputsProductSelectComponent extends Component { - try { - await this.productAreas.fetch.perform(); - this.errorIsShown = false; - } catch { - this.errorIsShown = true; - } - }); } declare module "@glint/environment-ember-loose/registry" { diff --git a/web/app/components/product-avatar.gts b/web/app/components/product-avatar.gts index ef49d87eb..ab860226c 100644 --- a/web/app/components/product-avatar.gts +++ b/web/app/components/product-avatar.gts @@ -2,6 +2,8 @@ import Component from "@glimmer/component"; import getProductID from "hermes/utils/get-product-id"; import or from "ember-truth-helpers/helpers/or"; import FlightIcon from "@hashicorp/ember-flight-icons/components/flight-icon"; +import getLetterCount from "hermes/helpers/get-letter-count"; +import getProductAbbreviation from "hermes/helpers/get-product-abbreviation"; interface ProductAvatarComponentSignature { Element: HTMLDivElement; @@ -30,10 +32,21 @@ export default class ProductAvatarComponent extends Component - + {{#if this.productID}} + + {{else}} + + {{getProductAbbreviation @productArea}} + + {{/if}} } diff --git a/web/app/helpers/get-letter-count.ts b/web/app/helpers/get-letter-count.ts new file mode 100644 index 000000000..bb5c5c724 --- /dev/null +++ b/web/app/helpers/get-letter-count.ts @@ -0,0 +1,21 @@ +import { helper } from "@ember/component/helper"; + +export interface GetLetterCountHelperSignature { + Args: { + Positional: [text: string]; + }; + Return: string | null; +} +const getLetterCount = helper( + ([text]: [string]) => { + return text.length.toString(); + }, +); + +export default getLetterCount; + +declare module "@glint/environment-ember-loose/registry" { + export default interface Registry { + "get-letter-count": typeof getLetterCount; + } +} diff --git a/web/app/helpers/get-product-abbreviation.ts b/web/app/helpers/get-product-abbreviation.ts new file mode 100644 index 000000000..e0911cbd3 --- /dev/null +++ b/web/app/helpers/get-product-abbreviation.ts @@ -0,0 +1,50 @@ +import Helper from "@ember/component/helper"; +import { inject as service } from "@ember/service"; +import ProductAreasService from "hermes/services/product-areas"; + +export interface GetProductAbbreviationHelperSignature { + Args: { + Positional: [productName?: string]; + }; + Return: string; +} + +// const getProductAbbreviation = helper( +// ([productName]: [string]) => { + +// const nameParts = productName.split("-"); +// const abbreviation = nameParts[0]; + +// if (abbreviation) { +// return abbreviation.slice(0, 3).toUpperCase(); +// } + +// return ""; +// }, +// ); + +export default class GetProductAbbreviationHelper extends Helper { + @service declare productAreas: ProductAreasService; + + compute([productName]: [string | undefined]): string { + if (!productName) { + return ""; + } + + // need to search the productAreas index for an object called `productName` and return the abbreviation + const products = this.productAreas.index; + const product = products?.[productName]; + + if (!product) { + return ""; + } + + return product.abbreviation.slice(0, 3).toUpperCase(); + } +} + +declare module "@glint/environment-ember-loose/registry" { + export default interface Registry { + "get-doc-abbreviation": typeof GetProductAbbreviationHelper; + } +} diff --git a/web/app/routes/application.ts b/web/app/routes/application.ts index f68e91a23..afdf92647 100644 --- a/web/app/routes/application.ts +++ b/web/app/routes/application.ts @@ -59,7 +59,7 @@ export default class ApplicationRoute extends Route { JSON.stringify({ url: transitionTo, expiresOn: Date.now() + 60 * 5000, // 5 minutes - }) + }), ); } diff --git a/web/app/routes/authenticated.ts b/web/app/routes/authenticated.ts index 15b204d39..6a1a6e63a 100644 --- a/web/app/routes/authenticated.ts +++ b/web/app/routes/authenticated.ts @@ -2,12 +2,14 @@ import Route from "@ember/routing/route"; import { inject as service } from "@ember/service"; import AuthenticatedUserService from "hermes/services/authenticated-user"; import ConfigService from "hermes/services/config"; +import ProductAreasService from "hermes/services/product-areas"; import SessionService from "hermes/services/session"; export default class AuthenticatedRoute extends Route { @service("config") declare configSvc: ConfigService; @service declare session: SessionService; @service declare authenticatedUser: AuthenticatedUserService; + @service declare productAreas: ProductAreasService; beforeModel(transition: any) { /** @@ -29,6 +31,11 @@ export default class AuthenticatedRoute extends Route { */ await this.authenticatedUser.loadInfo.perform(); + /** + * Load the ProductAreas index + */ + await this.productAreas.fetch.perform(); + /** * Kick off the task to poll for expired auth. */ diff --git a/web/app/services/product-areas.ts b/web/app/services/product-areas.ts index 875d75fc1..cab0ef2b0 100644 --- a/web/app/services/product-areas.ts +++ b/web/app/services/product-areas.ts @@ -1,9 +1,8 @@ import Service, { inject as service } from "@ember/service"; import { tracked } from "@glimmer/tracking"; -import { action } from "@ember/object"; -import RouterService from "@ember/routing/router-service"; -import { task, timeout } from "ember-concurrency"; +import { task } from "ember-concurrency"; import FetchService from "./fetch"; +import { assert } from "@ember/debug"; export type ProductArea = { abbreviation: string; @@ -12,15 +11,20 @@ export type ProductArea = { export default class ProductAreasService extends Service { @service("fetch") declare fetchSvc: FetchService; - @tracked index: Record | null = null; + @tracked _index: Record | null = null; + + get index() { + assert("_index must exist", this._index); + return this._index; + } fetch = task(async () => { try { - this.index = await this.fetchSvc + this._index = await this.fetchSvc .fetch("/api/v1/products") .then((resp) => resp?.json()); } catch (err) { - this.index = null; + this._index = null; throw err; } }); diff --git a/web/app/styles/app.scss b/web/app/styles/app.scss index 49bd5cccc..e6d2c4c5f 100644 --- a/web/app/styles/app.scss +++ b/web/app/styles/app.scss @@ -35,6 +35,7 @@ @use "components/doc/state"; @use "components/table/sortable-header"; @use "components/preview-card"; +@use "components/product-avatar"; @use "components/notification"; @use "components/sidebar"; @use "components/document/related-resources"; diff --git a/web/app/styles/components/product-avatar.scss b/web/app/styles/components/product-avatar.scss new file mode 100644 index 000000000..b3bed14b4 --- /dev/null +++ b/web/app/styles/components/product-avatar.scss @@ -0,0 +1,11 @@ +.letter-avatar { + @apply tracking-tighter; + + &.letter-count-2 { + @apply text-[11px]; + } + + &.letter-count-3 { + @apply text-[9px]; + } +} diff --git a/web/app/styles/hashicorp/product-badge.scss b/web/app/styles/hashicorp/product-badge.scss index 6f575f700..c012c1510 100644 --- a/web/app/styles/hashicorp/product-badge.scss +++ b/web/app/styles/hashicorp/product-badge.scss @@ -1,7 +1,9 @@ .product-badge { - @apply grid place-items-center absolute bg-gradient-to-br from-color-palette-neutral-500 to-color-palette-neutral-600 text-color-foreground-high-contrast; + @apply absolute grid place-items-center bg-gradient-to-br from-color-palette-neutral-400 to-color-palette-neutral-500 text-color-foreground-high-contrast; - // `.hcp` uses the default styles + &.hcp { + @apply from-color-palette-neutral-500 to-color-palette-neutral-600; + } &.nomad { @apply from-color-nomad-gradient-primary-start to-color-nomad-gradient-primary-stop text-color-nomad-foreground; diff --git a/web/tests/unit/services/product-areas-test.ts b/web/tests/unit/services/product-areas-test.ts index a9a060084..b8a04e267 100644 --- a/web/tests/unit/services/product-areas-test.ts +++ b/web/tests/unit/services/product-areas-test.ts @@ -14,7 +14,7 @@ module("Unit | Service | product-areas", function (hooks) { test("can set or close an active modal", async function (this: MirageTestContext, assert) { const productAreas = this.owner.lookup( - "service:product-areas" + "service:product-areas", ) as ProductAreasService; this.server.create("product", { From a534ceea5f144e2fb727033a8d51a6f18114afca Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Thu, 5 Oct 2023 20:20:28 -0400 Subject: [PATCH 3/3] Default size --- web/app/components/product-avatar.gts | 5 ++++- web/app/styles/components/product-badge-link.scss | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/web/app/components/product-avatar.gts b/web/app/components/product-avatar.gts index ab860226c..02afbc44c 100644 --- a/web/app/components/product-avatar.gts +++ b/web/app/components/product-avatar.gts @@ -4,6 +4,7 @@ import or from "ember-truth-helpers/helpers/or"; import FlightIcon from "@hashicorp/ember-flight-icons/components/flight-icon"; import getLetterCount from "hermes/helpers/get-letter-count"; import getProductAbbreviation from "hermes/helpers/get-product-abbreviation"; +import eq from "ember-truth-helpers/helpers/eq"; interface ProductAvatarComponentSignature { Element: HTMLDivElement; @@ -29,7 +30,9 @@ export default class ProductAvatarComponent extends Component
{{#if this.productID}} diff --git a/web/app/styles/components/product-badge-link.scss b/web/app/styles/components/product-badge-link.scss index dfb9412d2..a539b64cc 100644 --- a/web/app/styles/components/product-badge-link.scss +++ b/web/app/styles/components/product-badge-link.scss @@ -1,5 +1,7 @@ .product-badge-link { - &:hover .hds-badge--color-neutral { - @apply bg-color-palette-neutral-200; + @apply relative inline-flex items-center gap-2; + + &:hover { + @apply text-color-foreground-strong underline; } }