diff --git a/package.json b/package.json index 6f7737d3ac..e92fe3b092 100644 --- a/package.json +++ b/package.json @@ -186,6 +186,7 @@ "filehound": "^1.17.4", "filenamify": "^4.1.0", "fs-extra": "^9.0.1", + "grapheme-splitter": "^1.0.4", "handlebars": "^4.7.6", "http-proxy": "^1.18.1", "immer": "^8.0.1", diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 6f26bab2da..16a077277e 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -19,3 +19,6 @@ export * from "./downloadFile"; export * from "./escapeRegExp"; export * from "./tar"; export * from "./type-narrowing"; +import * as iter from "./iter"; + +export { iter }; diff --git a/src/common/utils/iter.ts b/src/common/utils/iter.ts new file mode 100644 index 0000000000..f364ce4aee --- /dev/null +++ b/src/common/utils/iter.ts @@ -0,0 +1,25 @@ +/** + * Create a new type safe empty Iterable + * @returns An `Iterable` that yields 0 items + */ +export function* newEmpty(): Iterable { + return; +} + +/** + * Creates a new `Iterable` that yields at most n items from src. + * Does not modify `src` which can be used later. + * @param src An initial iterator + * @param n The maximum number of elements to take from src. Yields up to the floor of `n` and 0 items if n < 0 + */ +export function* take(src: Iterable, n: number): Iterable { + outer: for (let i = 0; i < n; i += 1) { + for (const item of src) { + yield item; + continue outer; + } + + // if we are here that means that `src` has been exhausted. Don't bother trying again. + break outer; + } +} diff --git a/src/renderer/components/hotbar/hotbar-icon.tsx b/src/renderer/components/hotbar/hotbar-icon.tsx index 91e8685642..0a6b6f56bb 100644 --- a/src/renderer/components/hotbar/hotbar-icon.tsx +++ b/src/renderer/components/hotbar/hotbar-icon.tsx @@ -2,18 +2,19 @@ import "./hotbar-icon.scss"; import React, { DOMAttributes } from "react"; import { observer } from "mobx-react"; -import { cssNames, IClassName } from "../../utils"; +import { cssNames, IClassName, iter } from "../../utils"; import { Tooltip } from "../tooltip"; import { Avatar } from "@material-ui/core"; import { CatalogEntity, CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../../common/catalog"; import { Menu, MenuItem } from "../menu"; import { Icon } from "../icon"; -import { observable } from "mobx"; +import { computed, observable } from "mobx"; import { navigate } from "../../navigation"; import { HotbarStore } from "../../../common/hotbar-store"; import { ConfirmDialog } from "../confirm-dialog"; import randomColor from "randomcolor"; import { catalogCategoryRegistry } from "../../api/catalog-category-registry"; +import GraphemeSplitter from "grapheme-splitter"; interface Props extends DOMAttributes { entity: CatalogEntity; @@ -23,6 +24,22 @@ interface Props extends DOMAttributes { isActive?: boolean; } +function getNameParts(name: string): string[] { + const byWhitespace = name.split(/\s+/); + + if (byWhitespace.length > 1) { + return byWhitespace; + } + + const byDashes = name.split(/[-_]+/); + + if (byDashes.length > 1) { + return byDashes; + } + + return name.split(/@+/); +} + @observer export class HotbarIcon extends React.Component { @observable.deep private contextMenu: CatalogEntityContextMenuContext; @@ -35,26 +52,18 @@ export class HotbarIcon extends React.Component { }; } - get iconString() { - let splittedName = this.props.entity.metadata.name.split(" "); + @computed get iconString() { + const [rawFirst, rawSecond, rawThird] = getNameParts(this.props.entity.metadata.name); + const splitter = new GraphemeSplitter(); + const first = splitter.iterateGraphemes(rawFirst); + const second = rawSecond ? splitter.iterateGraphemes(rawSecond): first; + const third = rawThird ? splitter.iterateGraphemes(rawThird) : iter.newEmpty(); - if (splittedName.length === 1) { - splittedName = splittedName[0].split("-"); - } - - if (splittedName.length === 1) { - splittedName = splittedName[0].split("@"); - } - - splittedName = splittedName.map((part) => part.replace(/\W/g, "")); - - if (splittedName.length === 1) { - return splittedName[0].substring(0, 2); - } else if (splittedName.length === 2) { - return splittedName[0].substring(0, 1) + splittedName[1].substring(0, 1); - } else { - return splittedName[0].substring(0, 1) + splittedName[1].substring(0, 1) + splittedName[2].substring(0, 1); - } + return [ + ...iter.take(first, 1), + ...iter.take(second, 1), + ...iter.take(third, 1), + ].filter(Boolean).join(""); } get badgeIcon() { diff --git a/yarn.lock b/yarn.lock index e23b90d0a3..cddedb6c2e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6583,7 +6583,7 @@ graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2 resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= -grapheme-splitter@^1.0.2: +grapheme-splitter@^1.0.2, grapheme-splitter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==