diff --git a/client/web-sveltekit/BUILD.bazel b/client/web-sveltekit/BUILD.bazel index 01f96b9a072..2555c0564c9 100644 --- a/client/web-sveltekit/BUILD.bazel +++ b/client/web-sveltekit/BUILD.bazel @@ -82,7 +82,10 @@ BUILD_DEPS = [ ":node_modules/@graphql-codegen/typescript", ":node_modules/@graphql-codegen/typescript-operations", ":node_modules/@graphql-tools/utils", + ":node_modules/@iconify-json/devicon-plain", ":node_modules/@iconify-json/lucide", + ":node_modules/@iconify-json/ph", + ":node_modules/@iconify-json/simple-icons", ":node_modules/@melt-ui/svelte", ":node_modules/@sentry/sveltekit", ":node_modules/@sourcegraph/branded", diff --git a/client/web-sveltekit/package.json b/client/web-sveltekit/package.json index 1dfe4f66708..31482f0d3e0 100644 --- a/client/web-sveltekit/package.json +++ b/client/web-sveltekit/package.json @@ -31,7 +31,10 @@ "@graphql-codegen/typescript-operations": "^4.0.1", "@graphql-tools/utils": "^10.0.11", "@graphql-typed-document-node/core": "^3.2.0", + "@iconify-json/devicon-plain": "^1.1.42", "@iconify-json/lucide": "^1.1.188", + "@iconify-json/ph": "^1.1.13", + "@iconify-json/simple-icons": "^1.1.104", "@playwright/test": "1.42.1", "@storybook/addon-essentials": "^8.0.5", "@storybook/addon-interactions": "^7.2.0", diff --git a/client/web-sveltekit/src/auto-imports.d.ts b/client/web-sveltekit/src/auto-imports.d.ts index 0faed7228ee..312a85e1a55 100644 --- a/client/web-sveltekit/src/auto-imports.d.ts +++ b/client/web-sveltekit/src/auto-imports.d.ts @@ -5,6 +5,7 @@ // Generated by unplugin-auto-import export {} declare global { + const IDeviconPlainJava: typeof import('~icons/devicon-plain/java')['default'] const ILucideArrowDownFromLine: typeof import('~icons/lucide/arrow-down-from-line')['default'] const ILucideArrowLeftFromLine: typeof import('~icons/lucide/arrow-left-from-line')['default'] const ILucideArrowRightFromLine: typeof import('~icons/lucide/arrow-right-from-line')['default'] @@ -13,7 +14,13 @@ declare global { const ILucideChevronLast: typeof import('~icons/lucide/chevron-last')['default'] const ILucideChevronLeft: typeof import('~icons/lucide/chevron-left')['default'] const ILucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] + const ILucideDatabase: typeof import('~icons/lucide/database')['default'] + const ILucideEarth: typeof import('~icons/lucide/earth')['default'] const ILucideEllipsis: typeof import('~icons/lucide/ellipsis')['default'] + const ILucideFileCode: typeof import('~icons/lucide/file-code')['default'] + const ILucideFileJson: typeof import('~icons/lucide/file-json')['default'] + const ILucideFileTerminal: typeof import('~icons/lucide/file-terminal')['default'] + const ILucideFileText: typeof import('~icons/lucide/file-text')['default'] const ILucideFolder: typeof import('~icons/lucide/folder')['default'] const ILucideGitCompareArrows: typeof import('~icons/lucide/git-compare-arrows')['default'] const ILucideGitMerge: typeof import('~icons/lucide/git-merge')['default'] @@ -23,7 +30,77 @@ declare global { const ILucidePanelLeftClose: typeof import('~icons/lucide/panel-left-close')['default'] const ILucidePanelLeftOpen: typeof import('~icons/lucide/panel-left-open')['default'] const ILucideRegex: typeof import('~icons/lucide/regex')['default'] + const ILucideSettings: typeof import('~icons/lucide/settings')['default'] + const ILucideSetttings: typeof import('~icons/lucide/setttings')['default'] const ILucideSquareSlash: typeof import('~icons/lucide/square-slash')['default'] const ILucideStar: typeof import('~icons/lucide/star')['default'] const ILucideX: typeof import('~icons/lucide/x')['default'] + const IPhFileJpgLight: typeof import('~icons/ph/file-jpg-light')['default'] + const IPhFilePngLight: typeof import('~icons/ph/file-png-light')['default'] + const IPhGifFill: typeof import('~icons/ph/gif-fill')['default'] + const IPhJpgLight: typeof import('~icons/ph/jpg-light')['default'] + const IPhPngLight: typeof import('~icons/ph/png-light')['default'] + const IPhPnglight: typeof import('~icons/ph/pnglight')['default'] + const IPhosphorPngLight: typeof import('~icons/ph/osphor-png-light')['default'] + const IPhosphorePngLight: typeof import('~icons/ph/osphore-png-light')['default'] + const ISimpleIconsApachegroovy: typeof import('~icons/simple-icons/apachegroovy')['default'] + const ISimpleIconsC: typeof import('~icons/simple-icons/c')['default'] + const ISimpleIconsCSS3: typeof import('~icons/simple-icons/c-s-s3')['default'] + const ISimpleIconsClojure: typeof import('~icons/simple-icons/clojure')['default'] + const ISimpleIconsCmake: typeof import('~icons/simple-icons/cmake')['default'] + const ISimpleIconsCoffeescript: typeof import('~icons/simple-icons/coffeescript')['default'] + const ISimpleIconsCplusplus: typeof import('~icons/simple-icons/cplusplus')['default'] + const ISimpleIconsCrystal: typeof import('~icons/simple-icons/crystal')['default'] + const ISimpleIconsCsharp: typeof import('~icons/simple-icons/csharp')['default'] + const ISimpleIconsCss3: typeof import('~icons/simple-icons/css3')['default'] + const ISimpleIconsD: typeof import('~icons/simple-icons/d')['default'] + const ISimpleIconsDart: typeof import('~icons/simple-icons/dart')['default'] + const ISimpleIconsDocker: typeof import('~icons/simple-icons/docker')['default'] + const ISimpleIconsEditorconfig: typeof import('~icons/simple-icons/editorconfig')['default'] + const ISimpleIconsElixir: typeof import('~icons/simple-icons/elixir')['default'] + const ISimpleIconsElm: typeof import('~icons/simple-icons/elm')['default'] + const ISimpleIconsErlang: typeof import('~icons/simple-icons/erlang')['default'] + const ISimpleIconsFortran: typeof import('~icons/simple-icons/fortran')['default'] + const ISimpleIconsFsharp: typeof import('~icons/simple-icons/fsharp')['default'] + const ISimpleIconsGit: typeof import('~icons/simple-icons/git')['default'] + const ISimpleIconsGnuemacs: typeof import('~icons/simple-icons/gnuemacs')['default'] + const ISimpleIconsGo: typeof import('~icons/simple-icons/go')['default'] + const ISimpleIconsGraphql: typeof import('~icons/simple-icons/graphql')['default'] + const ISimpleIconsHaskell: typeof import('~icons/simple-icons/haskell')['default'] + const ISimpleIconsHtml5: typeof import('~icons/simple-icons/html5')['default'] + const ISimpleIconsJavascript: typeof import('~icons/simple-icons/javascript')['default'] + const ISimpleIconsJinja: typeof import('~icons/simple-icons/jinja')['default'] + const ISimpleIconsJpeg: typeof import('~icons/simple-icons/jpeg')['default'] + const ISimpleIconsJulia: typeof import('~icons/simple-icons/julia')['default'] + const ISimpleIconsKotlin: typeof import('~icons/simple-icons/kotlin')['default'] + const ISimpleIconsLlvm: typeof import('~icons/simple-icons/llvm')['default'] + const ISimpleIconsLua: typeof import('~icons/simple-icons/lua')['default'] + const ISimpleIconsMarkdown: typeof import('~icons/simple-icons/markdown')['default'] + const ISimpleIconsNginx: typeof import('~icons/simple-icons/nginx')['default'] + const ISimpleIconsNim: typeof import('~icons/simple-icons/nim')['default'] + const ISimpleIconsNixos: typeof import('~icons/simple-icons/nixos')['default'] + const ISimpleIconsNpm: typeof import('~icons/simple-icons/npm')['default'] + const ISimpleIconsOcaml: typeof import('~icons/simple-icons/ocaml')['default'] + const ISimpleIconsPerl: typeof import('~icons/simple-icons/perl')['default'] + const ISimpleIconsPhp: typeof import('~icons/simple-icons/php')['default'] + const ISimpleIconsPurescript: typeof import('~icons/simple-icons/purescript')['default'] + const ISimpleIconsPython: typeof import('~icons/simple-icons/python')['default'] + const ISimpleIconsR: typeof import('~icons/simple-icons/r')['default'] + const ISimpleIconsRuby: typeof import('~icons/simple-icons/ruby')['default'] + const ISimpleIconsRust: typeof import('~icons/simple-icons/rust')['default'] + const ISimpleIconsSass: typeof import('~icons/simple-icons/sass')['default'] + const ISimpleIconsScala: typeof import('~icons/simple-icons/scala')['default'] + const ISimpleIconsSvelte: typeof import('~icons/simple-icons/svelte')['default'] + const ISimpleIconsSvg: typeof import('~icons/simple-icons/svg')['default'] + const ISimpleIconsSwift: typeof import('~icons/simple-icons/swift')['default'] + const ISimpleIconsTerraform: typeof import('~icons/simple-icons/terraform')['default'] + const ISimpleIconsToml: typeof import('~icons/simple-icons/toml')['default'] + const ISimpleIconsTypescript: typeof import('~icons/simple-icons/typescript')['default'] + const ISimpleIconsUnrealengine: typeof import('~icons/simple-icons/unrealengine')['default'] + const ISimpleIconsVim: typeof import('~icons/simple-icons/vim')['default'] + const ISimpleIconsVisualbasic: typeof import('~icons/simple-icons/visualbasic')['default'] + const ISimpleIconsVuedotjs: typeof import('~icons/simple-icons/vuedotjs')['default'] + const ISimpleIconsWebassembly: typeof import('~icons/simple-icons/webassembly')['default'] + const ISimpleIconsWolframmathematica: typeof import('~icons/simple-icons/wolframmathematica')['default'] + const ISimpleIconsZig: typeof import('~icons/simple-icons/zig')['default'] } diff --git a/client/web-sveltekit/src/lib/Icon2.svelte b/client/web-sveltekit/src/lib/Icon2.svelte index 7ed1a092ce6..d963fe29503 100644 --- a/client/web-sveltekit/src/lib/Icon2.svelte +++ b/client/web-sveltekit/src/lib/Icon2.svelte @@ -30,4 +30,4 @@ export let inline: boolean = false - + diff --git a/client/web-sveltekit/src/lib/LanguageIcon.svelte b/client/web-sveltekit/src/lib/LanguageIcon.svelte index d668d01ffc5..a989232594c 100644 --- a/client/web-sveltekit/src/lib/LanguageIcon.svelte +++ b/client/web-sveltekit/src/lib/LanguageIcon.svelte @@ -1,15 +1,18 @@ - + + + diff --git a/client/web-sveltekit/src/lib/repo/FileIcon.svelte b/client/web-sveltekit/src/lib/repo/FileIcon.svelte index 6ef24e0ad42..b0200b9d056 100644 --- a/client/web-sveltekit/src/lib/repo/FileIcon.svelte +++ b/client/web-sveltekit/src/lib/repo/FileIcon.svelte @@ -1,16 +1,21 @@ - + diff --git a/client/web-sveltekit/src/lib/search/dynamicFilters/Sidebar.svelte b/client/web-sveltekit/src/lib/search/dynamicFilters/Sidebar.svelte index c33e7277ae2..01143c7a393 100644 --- a/client/web-sveltekit/src/lib/search/dynamicFilters/Sidebar.svelte +++ b/client/web-sveltekit/src/lib/search/dynamicFilters/Sidebar.svelte @@ -156,7 +156,7 @@ onFilterSelect={handleFilterSelect} > -   +   {label} diff --git a/client/web-sveltekit/src/lib/wildcard/index.ts b/client/web-sveltekit/src/lib/wildcard/index.ts index 382959cb5d2..26edee5740f 100644 --- a/client/web-sveltekit/src/lib/wildcard/index.ts +++ b/client/web-sveltekit/src/lib/wildcard/index.ts @@ -15,4 +15,4 @@ export { default as Input } from './Input.svelte' export { default as PanelGroup } from './resizable-panel/PanelGroup.svelte' export { default as Panel } from './resizable-panel/Panel.svelte' export { default as PanelResizeHandle } from './resizable-panel/PanelResizeHandle.svelte' -export { getFileIconInfo } from '@sourcegraph/wildcard/src/components/Icon' +export { getFileIconInfo, DEFAULT_ICON_COLOR } from './language-icons' diff --git a/client/web-sveltekit/src/lib/wildcard/language-icons.ts b/client/web-sveltekit/src/lib/wildcard/language-icons.ts new file mode 100644 index 00000000000..be6678ffe7f --- /dev/null +++ b/client/web-sveltekit/src/lib/wildcard/language-icons.ts @@ -0,0 +1,152 @@ +import type { ComponentType, SvelteComponent } from 'svelte' +import type { SvelteHTMLElements } from 'svelte/elements' + +interface LanguageIcon { + icon: ComponentType> + color: string +} + +const BLUE = 'var(--blue)' +const PINK = 'var(--pink)' +const YELLOW = 'var(--yellow)' +const RED = 'var(--red)' +const GREEN = 'var(--green)' +const CYAN = 'var(--blue)' +const GRAY = 'var(--gray-05)' +export const DEFAULT_ICON_COLOR = GRAY + +/** + * The keys of this map must be present in the list of `languageFilter.ALL_LANGUAGES`. + * + * This map is deliberately not public, use {@link getFileIconInfo} instead. + * + * See FIXME(id: language-detection) for context on why this map uses the + * language as the key instead of something simpler like a file extension. + */ +export const FILE_ICONS_BY_LANGUAGE: Map = new Map([ + ['Bash', { icon: ILucideFileTerminal, color: DEFAULT_ICON_COLOR }], + ['BASIC', { icon: ISimpleIconsVisualbasic, color: DEFAULT_ICON_COLOR }], + ['C', { icon: ISimpleIconsC, color: BLUE }], + ['C++', { icon: ISimpleIconsCplusplus, color: BLUE }], + ['C#', { icon: ISimpleIconsCsharp, color: BLUE }], + ['Clojure', { icon: ISimpleIconsClojure, color: BLUE }], + ['CMake', { icon: ISimpleIconsCmake, color: DEFAULT_ICON_COLOR }], + ['CoffeeScript', { icon: ISimpleIconsCoffeescript, color: DEFAULT_ICON_COLOR }], + + // TODO: Decide icon for CSV? + ['Crystal', { icon: ISimpleIconsCrystal, color: BLUE }], + ['CSS', { icon: ISimpleIconsCss3, color: BLUE }], + ['D', { icon: ISimpleIconsD, color: RED }], + ['Dart', { icon: ISimpleIconsDart, color: BLUE }], + ['Dockerfile', { icon: ISimpleIconsDocker, color: BLUE }], + ['EditorConfig', { icon: ISimpleIconsEditorconfig, color: DEFAULT_ICON_COLOR }], + ['Elixir', { icon: ISimpleIconsElixir, color: BLUE }], + ['Elm', { icon: ISimpleIconsElm, color: BLUE }], + ['Emacs Lisp', { icon: ISimpleIconsGnuemacs, color: DEFAULT_ICON_COLOR }], + ['Erlang', { icon: ISimpleIconsErlang, color: BLUE }], + ['Fortran', { icon: ISimpleIconsFortran, color: DEFAULT_ICON_COLOR }], + ['Fortran Free Form', { icon: ISimpleIconsFortran, color: DEFAULT_ICON_COLOR }], + ['F#', { icon: ISimpleIconsFsharp, color: BLUE }], + ['Git Attributes', { icon: ISimpleIconsGit, color: RED }], + ['Go', { icon: ISimpleIconsGo, color: BLUE }], + ['Go Module', { icon: ISimpleIconsGo, color: PINK }], + ['Go Checksums', { icon: ISimpleIconsGo, color: PINK }], + ['Groovy', { icon: ISimpleIconsApachegroovy, color: BLUE }], + ['GraphQL', { icon: ISimpleIconsGraphql, color: PINK }], + ['Haskell', { icon: ISimpleIconsHaskell, color: BLUE }], + ['HTML', { icon: ISimpleIconsHtml5, color: BLUE }], + ['HTML+ECR', { icon: ISimpleIconsCrystal, color: BLUE }], + ['HTML+EEX', { icon: ISimpleIconsElixir, color: BLUE }], + ['HTML+ERB', { icon: ISimpleIconsRuby, color: BLUE }], + ['HTML+PHP', { icon: ISimpleIconsPhp, color: BLUE }], + ['HTML+Razor', { icon: ISimpleIconsCsharp, color: BLUE }], + ['Ignore List', { icon: ILucideSettings, color: DEFAULT_ICON_COLOR }], + ['Java', { icon: IDeviconPlainJava, color: DEFAULT_ICON_COLOR }], + ['JavaScript', { icon: ISimpleIconsJavascript, color: YELLOW }], + ['Jinja', { icon: ISimpleIconsJinja, color: DEFAULT_ICON_COLOR }], + ['JSON with Comments', { icon: ILucideFileJson, color: DEFAULT_ICON_COLOR }], + ['JSON', { icon: ILucideFileJson, color: DEFAULT_ICON_COLOR }], + ['JSON5', { icon: ILucideFileJson, color: DEFAULT_ICON_COLOR }], + ['JSONLD', { icon: ILucideFileJson, color: DEFAULT_ICON_COLOR }], + ['Julia', { icon: ISimpleIconsJulia, color: DEFAULT_ICON_COLOR }], + ['Kotlin', { icon: ISimpleIconsKotlin, color: GREEN }], + ['LLVM', { icon: ISimpleIconsLlvm, color: GRAY }], + ['Lua', { icon: ISimpleIconsLua, color: BLUE }], + ['Markdown', { icon: ISimpleIconsMarkdown, color: BLUE }], + ['Mathematica', { icon: ISimpleIconsWolframmathematica, color: RED }], + + // https://github.com/NCAR/ncl, not tweag/nickel + ['NCL', { icon: ILucideEarth, color: DEFAULT_ICON_COLOR }], + ['Nginx', { icon: ISimpleIconsNginx, color: DEFAULT_ICON_COLOR }], + ['Nim', { icon: ISimpleIconsNim, color: YELLOW }], + ['Nix', { icon: ISimpleIconsNixos, color: GRAY }], + ['NPM Config', { icon: ISimpleIconsNpm, color: RED }], + + // Missing an icon for Objective-C + ['OCaml', { icon: ISimpleIconsOcaml, color: YELLOW }], + ['PHP', { icon: ISimpleIconsPhp, color: CYAN }], + ['Perl', { icon: ISimpleIconsPerl, color: DEFAULT_ICON_COLOR }], + ['PLpgSQL', { icon: ILucideDatabase, color: BLUE }], + ['PowerShell', { icon: ILucideFileTerminal, color: DEFAULT_ICON_COLOR }], + + // Missing icon for Protobuf + ['PureScript', { icon: ISimpleIconsPurescript, color: DEFAULT_ICON_COLOR }], + ['Python', { icon: ISimpleIconsPython, color: BLUE }], + ['R', { icon: ISimpleIconsR, color: RED }], + ['Ruby', { icon: ISimpleIconsRuby, color: RED }], + ['Rust', { icon: ISimpleIconsRust, color: DEFAULT_ICON_COLOR }], + ['Scala', { icon: ISimpleIconsScala, color: RED }], + ['Sass', { icon: ISimpleIconsSass, color: PINK }], + ['SCSS', { icon: ISimpleIconsSass, color: PINK }], + ['SQL', { icon: ILucideDatabase, color: BLUE }], + + // Missing icon for Starlark + ['Svelte', { icon: ISimpleIconsSvelte, color: RED }], + ['SVG', { icon: ISimpleIconsSvg, color: YELLOW }], + ['Swift', { icon: ISimpleIconsSwift, color: BLUE }], + ['Terraform', { icon: ISimpleIconsTerraform, color: BLUE }], + ['TSX', { icon: ISimpleIconsTypescript, color: BLUE }], + ['TypeScript', { icon: ISimpleIconsTypescript, color: BLUE }], + ['Text', { icon: ILucideFileText, color: DEFAULT_ICON_COLOR }], + + // Missing icon for Thrift + ['TOML', { icon: ISimpleIconsToml, color: DEFAULT_ICON_COLOR }], + ['UnrealScript', { icon: ISimpleIconsUnrealengine, color: DEFAULT_ICON_COLOR }], + ['VBA', { icon: ISimpleIconsVisualbasic, color: BLUE }], + ['VBScript', { icon: ISimpleIconsVisualbasic, color: BLUE }], + ['Vim Script', { icon: ISimpleIconsVim, color: DEFAULT_ICON_COLOR }], + ['Vue', { icon: ISimpleIconsVuedotjs, color: GREEN }], + ['WebAssembly', { icon: ISimpleIconsWebassembly, color: BLUE }], + ['XML', { icon: ILucideSettings, color: DEFAULT_ICON_COLOR }], + ['YAML', { icon: ILucideSettings, color: DEFAULT_ICON_COLOR }], + ['Zig', { icon: ISimpleIconsZig, color: YELLOW }], +]) + +/** + * DO NOT add any extensions here for which there are multiple different + * file formats in practice which use the same extensions. + * + * For programming languages, update {@link FILE_ICONS_BY_LANGUAGE}. + */ +const BINARY_FILE_ICONS_BY_EXTENSION: Map = new Map([ + ['gif', { icon: IPhGifFill, color: DEFAULT_ICON_COLOR }], + ['giff', { icon: IPhGifFill, color: DEFAULT_ICON_COLOR }], + ['jpg', { icon: IPhFileJpgLight, color: YELLOW }], + ['jpeg', { icon: IPhFileJpgLight, color: YELLOW }], + ['png', { icon: IPhFilePngLight, color: DEFAULT_ICON_COLOR }], +]) + +/** + * + * See FIXME(id: language-detection) for context on why this takes a + * languages argument instead of directly using the file extension + * for determining the language. + * + * @param path The path of the file (or just its name). + * @param language Alias to the file language name. + * @returns undefined if the language is not a known language. + */ +export function getFileIconInfo(path: string, language: string): LanguageIcon | undefined { + const extension = path.split('.').at(-1) ?? '' + return BINARY_FILE_ICONS_BY_EXTENSION.get(extension) ?? FILE_ICONS_BY_LANGUAGE.get(language) +} diff --git a/client/wildcard/BUILD.bazel b/client/wildcard/BUILD.bazel index 609cbb3c728..e531f392019 100644 --- a/client/wildcard/BUILD.bazel +++ b/client/wildcard/BUILD.bazel @@ -565,6 +565,7 @@ ts_project( "//:node_modules/js-cookie", "//:node_modules/mdi-react", "//:node_modules/react", + "//:node_modules/react-icons", "//:node_modules/rxjs", "//:node_modules/sinon", "//:node_modules/vitest", diff --git a/client/wildcard/src/components/Icon/language-icon/LanguageIcon.tsx b/client/wildcard/src/components/Icon/language-icon/LanguageIcon.tsx index 75bd5e10dcc..7377ddf4ac8 100644 --- a/client/wildcard/src/components/Icon/language-icon/LanguageIcon.tsx +++ b/client/wildcard/src/components/Icon/language-icon/LanguageIcon.tsx @@ -24,8 +24,8 @@ export const LanguageIcon: FC = props => { if (fileIcon) { return ( ) diff --git a/client/wildcard/src/components/Icon/language-icon/language-icons.test.ts b/client/wildcard/src/components/Icon/language-icon/language-icons.test.ts index 48b5a024c96..960edcf4b36 100644 --- a/client/wildcard/src/components/Icon/language-icon/language-icons.test.ts +++ b/client/wildcard/src/components/Icon/language-icon/language-icons.test.ts @@ -1,50 +1,35 @@ -import { mdiFileCodeOutline, mdiFilePngBox, mdiLanguageJavascript } from '@mdi/js' +import { PiFilePngLight } from 'react-icons/pi' +import { SiJavascript } from 'react-icons/si' import { describe, expect, it } from 'vitest' import { getFileIconInfo } from './language-icons' describe('getFileIconInfo', () => { - const tests: { - name: string - file: string - language: string - expectedSvgPath: string | undefined - expectedIsTest: boolean - }[] = [ + const tests = [ { name: 'check that png works', file: 'myfile.png', language: '', - expectedSvgPath: mdiFilePngBox, - expectedIsTest: false, + expectedIcon: PiFilePngLight, }, { name: 'works with simple file name', file: 'my-file.js', language: 'JavaScript', - expectedSvgPath: mdiLanguageJavascript, - expectedIsTest: false, - }, - { - name: 'check fallback behavior', - file: 'placeholder', - language: 'Vim Script', - expectedSvgPath: mdiFileCodeOutline, - expectedIsTest: false, + expectedIcon: SiJavascript, }, { name: 'check unknown language', file: 'my-file.test.unknown', language: 'Unknown', - expectedSvgPath: undefined, - expectedIsTest: true, + expectedIcon: undefined, }, ] for (const t of tests) { it(t.name, () => { const iconInfo = getFileIconInfo(t.file, t.language) - expect(iconInfo?.svg.path).toBe(t.expectedSvgPath) + expect(iconInfo?.icon).toBe(t.expectedIcon) }) } }) diff --git a/client/wildcard/src/components/Icon/language-icon/language-icons.ts b/client/wildcard/src/components/Icon/language-icon/language-icons.ts index de688680e35..d33b212454a 100644 --- a/client/wildcard/src/components/Icon/language-icon/language-icons.ts +++ b/client/wildcard/src/components/Icon/language-icon/language-icons.ts @@ -1,57 +1,5 @@ import type { ComponentType } from 'react' -// TODO(id: md-icons-and-react-icons) -// -// We're using react-icons for the React version of the web app -// since it has a large number of icons. However, those aren't -// usable in SvelteKit as the icons are React components. -// -// So we also use Material Design icons which are exposed as SVGs. -// However, this has two drawbacks: -// - It has many fewer languages compared to react-icons. -// - A future version of Material Design icons will remove programming -// language and file type icons. -// https://github.com/Templarian/MaterialDesign/issues/6602 -// -// It would be valuable to explore other icon libraries that can -// be used both by React and SvelteKit or make our own. -import { - mdiCodeJson, - mdiCog, - mdiConsole, - mdiDocker, - mdiEarth, - mdiFileCodeOutline, - mdiFileGifBox, - mdiFileJpgBox, - mdiFilePngBox, - mdiGit, - mdiGraphql, - mdiLanguageC, - mdiLanguageCpp, - mdiLanguageCsharp, - mdiLanguageCss3, - mdiLanguageGo, - mdiLanguageHaskell, - mdiLanguageHtml5, - mdiLanguageJava, - mdiLanguageJavascript, - mdiLanguageKotlin, - mdiLanguageLua, - mdiLanguageMarkdown, - mdiLanguagePhp, - mdiLanguagePython, - mdiLanguageR, - mdiLanguageRuby, - mdiLanguageRust, - mdiLanguageSwift, - mdiLanguageTypescript, - mdiNix, - mdiNpm, - mdiSass, - mdiSvg, - mdiText, -} from '@mdi/js' import { CiSettings, CiTextAlignLeft } from 'react-icons/ci' import { FaCss3Alt, FaSass, FaVuejs } from 'react-icons/fa' import { GoDatabase, GoTerminal } from 'react-icons/go' @@ -123,21 +71,11 @@ import styles from './LanguageIcon.module.scss' export type CustomIcon = ComponentType<{ className?: string }> -export interface ReactIcon { +export interface IconInfo { icon: CustomIcon className: string } -export interface SvgIcon { - path: string - color: string -} - -interface UnifiedIcon { - react: ReactIcon - svg?: SvgIcon['path'] -} - /** * The keys of this map must be present in the list of `languageFilter.ALL_LANGUAGES`. * @@ -146,133 +84,119 @@ interface UnifiedIcon { * See FIXME(id: language-detection) for context on why this map uses the * language as the key instead of something simpler like a file extension. */ -export const FILE_ICONS_BY_LANGUAGE: Map = new Map([ - ['Bash', { react: { icon: GoTerminal, className: styles.defaultIcon }, svg: mdiConsole }], - ['BASIC', { react: { icon: SiVisualbasic, className: styles.defaultIcon } }], - ['C', { react: { icon: SiC, className: styles.blue }, svg: mdiLanguageC }], - ['C++', { react: { icon: SiCplusplus, className: styles.blue }, svg: mdiLanguageCpp }], - ['C#', { react: { icon: SiCsharp, className: styles.blue }, svg: mdiLanguageCsharp }], - ['Clojure', { react: { icon: SiClojure, className: styles.blue } }], - ['CMake', { react: { icon: SiCmake, className: styles.defaultIcon } }], - ['CoffeeScript', { react: { icon: SiCoffeescript, className: styles.defaultIcon } }], +export const FILE_ICONS_BY_LANGUAGE: Map = new Map([ + ['Bash', { icon: GoTerminal, className: styles.defaultIcon }], + ['BASIC', { icon: SiVisualbasic, className: styles.defaultIcon }], + ['C', { icon: SiC, className: styles.blue }], + ['C++', { icon: SiCplusplus, className: styles.blue }], + ['C#', { icon: SiCsharp, className: styles.blue }], + ['Clojure', { icon: SiClojure, className: styles.blue }], + ['CMake', { icon: SiCmake, className: styles.defaultIcon }], + ['CoffeeScript', { icon: SiCoffeescript, className: styles.defaultIcon }], // TODO: Decide icon for CSV? - ['Crystal', { react: { icon: SiCrystal, className: styles.blue } }], - ['CSS', { react: { icon: FaCss3Alt, className: styles.blue }, svg: mdiLanguageCss3 }], - ['D', { react: { icon: SiD, className: styles.red } }], - ['Dart', { react: { icon: SiDart, className: styles.blue } }], - ['Dockerfile', { react: { icon: SiDocker, className: styles.blue }, svg: mdiDocker }], - ['EditorConfig', { react: { icon: SiEditorconfig, className: styles.defaultIcon } }], - ['Elixir', { react: { icon: SiElixir, className: styles.blue } }], - ['Elm', { react: { icon: SiElm, className: styles.blue } }], - ['Emacs Lisp', { react: { icon: SiGnuemacs, className: styles.defaultIcon } }], - ['Erlang', { react: { icon: SiErlang, className: styles.blue } }], - ['Fortran', { react: { icon: SiFortran, className: styles.defaultIcon } }], - ['Fortran Free Form', { react: { icon: SiFortran, className: styles.defaultIcon } }], - ['F#', { react: { icon: SiFsharp, className: styles.blue } }], - ['Git Attributes', { react: { icon: SiGit, className: styles.red }, svg: mdiGit }], - ['Go', { react: { icon: SiGo, className: styles.blue }, svg: mdiLanguageGo }], - ['Go Module', { react: { icon: SiGo, className: styles.pink }, svg: mdiLanguageGo }], - ['Go Checksums', { react: { icon: SiGo, className: styles.pink }, svg: mdiLanguageGo }], - ['Groovy', { react: { icon: SiApachegroovy, className: styles.blue } }], - ['GraphQL', { react: { icon: SiGraphql, className: styles.pink }, svg: mdiGraphql }], - ['Haskell', { react: { icon: SiHaskell, className: styles.blue }, svg: mdiLanguageHaskell }], - ['HTML', { react: { icon: SiHtml5, className: styles.blue }, svg: mdiLanguageHtml5 }], - ['HTML+ECR', { react: { icon: SiCrystal, className: styles.blue } }], - ['HTML+EEX', { react: { icon: SiElixir, className: styles.blue } }], - ['HTML+ERB', { react: { icon: SiRuby, className: styles.blue } }], - ['HTML+PHP', { react: { icon: SiPhp, className: styles.blue } }], - ['HTML+Razor', { react: { icon: SiCsharp, className: styles.blue } }], - ['Ignore List', { react: { icon: CiSettings, className: styles.defaultIcon }, svg: mdiCog }], - ['Java', { react: { icon: GrJava, className: styles.defaultIcon }, svg: mdiLanguageJava }], - ['JavaScript', { react: { icon: SiJavascript, className: styles.yellow }, svg: mdiLanguageJavascript }], - ['Jinja', { react: { icon: SiJinja, className: styles.defaultIcon } }], - ['JSON with Comments', { react: { icon: VscJson, className: styles.defaultIcon } }], - ['JSON', { react: { icon: VscJson, className: styles.defaultIcon }, svg: mdiCodeJson }], - ['JSON5', { react: { icon: VscJson, className: styles.defaultIcon }, svg: mdiCodeJson }], - ['JSONLD', { react: { icon: VscJson, className: styles.defaultIcon }, svg: mdiCodeJson }], - ['Julia', { react: { icon: SiJulia, className: styles.defaultIcon } }], - ['Kotlin', { react: { icon: SiKotlin, className: styles.green }, svg: mdiLanguageKotlin }], - ['LLVM', { react: { icon: SiLlvm, className: styles.gray } }], - ['Lua', { react: { icon: SiLua, className: styles.blue }, svg: mdiLanguageLua }], - ['Markdown', { react: { icon: SiMarkdown, className: styles.blue }, svg: mdiLanguageMarkdown }], - ['Mathematica', { react: { icon: SiWolframmathematica, className: styles.red } }], + ['Crystal', { icon: SiCrystal, className: styles.blue }], + ['CSS', { icon: FaCss3Alt, className: styles.blue }], + ['D', { icon: SiD, className: styles.red }], + ['Dart', { icon: SiDart, className: styles.blue }], + ['Dockerfile', { icon: SiDocker, className: styles.blue }], + ['EditorConfig', { icon: SiEditorconfig, className: styles.defaultIcon }], + ['Elixir', { icon: SiElixir, className: styles.blue }], + ['Elm', { icon: SiElm, className: styles.blue }], + ['Emacs Lisp', { icon: SiGnuemacs, className: styles.defaultIcon }], + ['Erlang', { icon: SiErlang, className: styles.blue }], + ['Fortran', { icon: SiFortran, className: styles.defaultIcon }], + ['Fortran Free Form', { icon: SiFortran, className: styles.defaultIcon }], + ['F#', { icon: SiFsharp, className: styles.blue }], + ['Git Attributes', { icon: SiGit, className: styles.red }], + ['Go', { icon: SiGo, className: styles.blue }], + ['Go Module', { icon: SiGo, className: styles.pink }], + ['Go Checksums', { icon: SiGo, className: styles.pink }], + ['Groovy', { icon: SiApachegroovy, className: styles.blue }], + ['GraphQL', { icon: SiGraphql, className: styles.pink }], + ['Haskell', { icon: SiHaskell, className: styles.blue }], + ['HTML', { icon: SiHtml5, className: styles.blue }], + ['HTML+ECR', { icon: SiCrystal, className: styles.blue }], + ['HTML+EEX', { icon: SiElixir, className: styles.blue }], + ['HTML+ERB', { icon: SiRuby, className: styles.blue }], + ['HTML+PHP', { icon: SiPhp, className: styles.blue }], + ['HTML+Razor', { icon: SiCsharp, className: styles.blue }], + ['Ignore List', { icon: CiSettings, className: styles.defaultIcon }], + ['Java', { icon: GrJava, className: styles.defaultIcon }], + ['JavaScript', { icon: SiJavascript, className: styles.yellow }], + ['Jinja', { icon: SiJinja, className: styles.defaultIcon }], + ['JSON with Comments', { icon: VscJson, className: styles.defaultIcon }], + ['JSON', { icon: VscJson, className: styles.defaultIcon }], + ['JSON5', { icon: VscJson, className: styles.defaultIcon }], + ['JSONLD', { icon: VscJson, className: styles.defaultIcon }], + ['Julia', { icon: SiJulia, className: styles.defaultIcon }], + ['Kotlin', { icon: SiKotlin, className: styles.green }], + ['LLVM', { icon: SiLlvm, className: styles.gray }], + ['Lua', { icon: SiLua, className: styles.blue }], + ['Markdown', { icon: SiMarkdown, className: styles.blue }], + ['Mathematica', { icon: SiWolframmathematica, className: styles.red }], // https://github.com/NCAR/ncl, not tweag/nickel - ['NCL', { react: { icon: ImEarth, className: styles.defaultIcon }, svg: mdiEarth }], - ['Nginx', { react: { icon: SiNginx, className: styles.defaultIcon } }], - ['Nim', { react: { icon: SiNim, className: styles.yellow } }], - ['Nix', { react: { icon: SiNixos, className: styles.gray }, svg: mdiNix }], - ['NPM Config', { react: { icon: SiNpm, className: styles.red }, svg: mdiNpm }], + ['NCL', { icon: ImEarth, className: styles.defaultIcon }], + ['Nginx', { icon: SiNginx, className: styles.defaultIcon }], + ['Nim', { icon: SiNim, className: styles.yellow }], + ['Nix', { icon: SiNixos, className: styles.gray }], + ['NPM Config', { icon: SiNpm, className: styles.red }], // Missing an icon for Objective-C - ['OCaml', { react: { icon: SiOcaml, className: styles.yellow } }], - ['PHP', { react: { icon: SiPhp, className: styles.cyan }, svg: mdiLanguagePhp }], - ['Perl', { react: { icon: SiPerl, className: styles.defaultIcon } }], - ['PLpgSQL', { react: { icon: GoDatabase, className: styles.blue } }], - ['PowerShell', { react: { icon: GoTerminal, className: styles.defaultIcon }, svg: mdiConsole }], + ['OCaml', { icon: SiOcaml, className: styles.yellow }], + ['PHP', { icon: SiPhp, className: styles.cyan }], + ['Perl', { icon: SiPerl, className: styles.defaultIcon }], + ['PLpgSQL', { icon: GoDatabase, className: styles.blue }], + ['PowerShell', { icon: GoTerminal, className: styles.defaultIcon }], // Missing icon for Protobuf - ['PureScript', { react: { icon: SiPurescript, className: styles.defaultIcon } }], - ['Python', { react: { icon: SiPython, className: styles.blue }, svg: mdiLanguagePython }], - ['R', { react: { icon: SiR, className: styles.red }, svg: mdiLanguageR }], - ['Ruby', { react: { icon: SiRuby, className: styles.red }, svg: mdiLanguageRuby }], - ['Rust', { react: { icon: SiRust, className: styles.defaultIcon }, svg: mdiLanguageRust }], - ['Scala', { react: { icon: SiScala, className: styles.red } }], - ['Sass', { react: { icon: FaSass, className: styles.pink }, svg: mdiSass }], - ['SCSS', { react: { icon: FaSass, className: styles.pink } }], - ['SQL', { react: { icon: GoDatabase, className: styles.blue } }], + ['PureScript', { icon: SiPurescript, className: styles.defaultIcon }], + ['Python', { icon: SiPython, className: styles.blue }], + ['R', { icon: SiR, className: styles.red }], + ['Ruby', { icon: SiRuby, className: styles.red }], + ['Rust', { icon: SiRust, className: styles.defaultIcon }], + ['Scala', { icon: SiScala, className: styles.red }], + ['Sass', { icon: FaSass, className: styles.pink }], + ['SCSS', { icon: FaSass, className: styles.pink }], + ['SQL', { icon: GoDatabase, className: styles.blue }], // Missing icon for Starlark - ['Svelte', { react: { icon: SiSvelte, className: styles.red } }], - ['SVG', { react: { icon: SiSvg, className: styles.yellow }, svg: mdiSvg }], - ['Swift', { react: { icon: SiSwift, className: styles.blue }, svg: mdiLanguageSwift }], - ['Terraform', { react: { icon: SiTerraform, className: styles.blue } }], - ['TSX', { react: { icon: SiTypescript, className: styles.blue }, svg: mdiLanguageTypescript }], - ['TypeScript', { react: { icon: SiTypescript, className: styles.blue }, svg: mdiLanguageTypescript }], - ['Text', { react: { icon: CiTextAlignLeft, className: styles.defaultIcon }, svg: mdiText }], + ['Svelte', { icon: SiSvelte, className: styles.red }], + ['SVG', { icon: SiSvg, className: styles.yellow }], + ['Swift', { icon: SiSwift, className: styles.blue }], + ['Terraform', { icon: SiTerraform, className: styles.blue }], + ['TSX', { icon: SiTypescript, className: styles.blue }], + ['TypeScript', { icon: SiTypescript, className: styles.blue }], + ['Text', { icon: CiTextAlignLeft, className: styles.defaultIcon }], // Missing icon for Thrift - ['TOML', { react: { icon: SiToml, className: styles.defaultIcon } }], - ['UnrealScript', { react: { icon: SiUnrealengine, className: styles.defaultIcon } }], - ['VBA', { react: { icon: SiVisualbasic, className: styles.blue } }], - ['VBScript', { react: { icon: SiVisualbasic, className: styles.blue } }], - ['Vim Script', { react: { icon: SiVim, className: styles.defaultIcon } }], - ['Vue', { react: { icon: FaVuejs, className: styles.green } }], - ['WebAssembly', { react: { icon: SiWebassembly, className: styles.blue } }], - ['XML', { react: { icon: CiSettings, className: styles.defaultIcon }, svg: mdiCog }], - ['YAML', { react: { icon: CiSettings, className: styles.defaultIcon }, svg: mdiCog }], - ['Zig', { react: { icon: SiZig, className: styles.yellow } }], + ['TOML', { icon: SiToml, className: styles.defaultIcon }], + ['UnrealScript', { icon: SiUnrealengine, className: styles.defaultIcon }], + ['VBA', { icon: SiVisualbasic, className: styles.blue }], + ['VBScript', { icon: SiVisualbasic, className: styles.blue }], + ['Vim Script', { icon: SiVim, className: styles.defaultIcon }], + ['Vue', { icon: FaVuejs, className: styles.green }], + ['WebAssembly', { icon: SiWebassembly, className: styles.blue }], + ['XML', { icon: CiSettings, className: styles.defaultIcon }], + ['YAML', { icon: CiSettings, className: styles.defaultIcon }], + ['Zig', { icon: SiZig, className: styles.yellow }], ]) -export interface BinaryFileIcon { - react: ReactIcon - svg: string -} - /** * DO NOT add any extensions here for which there are multiple different * file formats in practice which use the same extensions. * * For programming languages, update {@link FILE_ICONS_BY_LANGUAGE}. */ -const BINARY_FILE_ICONS_BY_EXTENSION: Map = new Map([ - ['gif', { react: { icon: MdGif, className: styles.defaultIcon }, svg: mdiFileGifBox }], - ['giff', { react: { icon: MdGif, className: styles.defaultIcon }, svg: mdiFileGifBox }], - ['jpg', { react: { icon: SiJpeg, className: styles.yellow }, svg: mdiFileJpgBox }], - ['jpeg', { react: { icon: SiJpeg, className: styles.yellow }, svg: mdiFileJpgBox }], - ['png', { react: { icon: PiFilePngLight, className: styles.defaultIcon }, svg: mdiFilePngBox }], +const BINARY_FILE_ICONS_BY_EXTENSION: Map = new Map([ + ['gif', { icon: MdGif, className: styles.defaultIcon }], + ['giff', { icon: MdGif, className: styles.defaultIcon }], + ['jpg', { icon: SiJpeg, className: styles.yellow }], + ['jpeg', { icon: SiJpeg, className: styles.yellow }], + ['png', { icon: PiFilePngLight, className: styles.defaultIcon }], ]) -// See TODO(id: md-icons-and-react-icons) for context -export interface IconInfo { - // For use in the React webapp - react: ReactIcon - - // For use in the SvelteKit rewrite - svg: SvgIcon -} - /** * * See FIXME(id: language-detection) for context on why this takes a @@ -285,58 +209,5 @@ export interface IconInfo { */ export function getFileIconInfo(path: string, language: string): IconInfo | undefined { const extension = path.split('.').at(-1) ?? '' - const icon1 = BINARY_FILE_ICONS_BY_EXTENSION.get(extension) - - if (icon1 !== undefined) { - return { - react: icon1.react, - svg: { path: icon1.svg, color: classNameToColor(icon1.react.className) }, - } - } - - const icon2 = FILE_ICONS_BY_LANGUAGE.get(language) - - if (icon2 !== undefined) { - return { - react: icon2.react, - svg: { path: icon2.svg ?? mdiFileCodeOutline, color: classNameToColor(icon2.react.className) }, - } - } - - return -} - -const BLUE = 'var(--blue)' -const PINK = 'var(--pink)' -const YELLOW = 'var(--yellow)' -const RED = 'var(--red)' -const GREEN = 'var(--green)' -const CYAN = 'var(--blue)' -const GRAY = 'var(--gray-05)' - -function classNameToColor(name: string): string { - switch (name) { - case styles.blue: { - return BLUE - } - case styles.red: { - return RED - } - case styles.yellow: { - return YELLOW - } - case styles.pink: { - return PINK - } - case styles.green: { - return GREEN - } - case styles.cyan: { - return CYAN - } - case styles.gray: - default: { - return GRAY - } - } + return BINARY_FILE_ICONS_BY_EXTENSION.get(extension) ?? FILE_ICONS_BY_LANGUAGE.get(language) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a42de46c6f1..dea3349d422 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1639,9 +1639,18 @@ importers: '@graphql-typed-document-node/core': specifier: ^3.2.0 version: 3.2.0(graphql@15.4.0) + '@iconify-json/devicon-plain': + specifier: ^1.1.42 + version: 1.1.42 '@iconify-json/lucide': specifier: ^1.1.188 version: 1.1.188 + '@iconify-json/ph': + specifier: ^1.1.13 + version: 1.1.13 + '@iconify-json/simple-icons': + specifier: ^1.1.104 + version: 1.1.104 '@playwright/test': specifier: 1.42.1 version: 1.42.1 @@ -5508,12 +5517,30 @@ packages: resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} dev: true + /@iconify-json/devicon-plain@1.1.42: + resolution: {integrity: sha512-NGCuw6BHn8lpjbVVa4XvYnprhWZ6IWkr6O19b69ySpDyi8QF3lh/Pe5As7C6eAG1KsblxQFqwcztgILOOmZqAA==} + dependencies: + '@iconify/types': 2.0.0 + dev: true + /@iconify-json/lucide@1.1.188: resolution: {integrity: sha512-yJNoU7vX11OvdeSKBiAvmbk/etAq2HPCZQkZX1U687NZhn8dTfx1PfyNhPxtJilrd288XGDKK+NQgygcZ+Ho4g==} dependencies: '@iconify/types': 2.0.0 dev: true + /@iconify-json/ph@1.1.13: + resolution: {integrity: sha512-xtM4JJ63HCKj09WRqrBswXiHrpliBlqboWSZH8odcmqYXbvIFceU9/Til4V+MQr6+MoUC+KB72cxhky2+A6r/g==} + dependencies: + '@iconify/types': 2.0.0 + dev: true + + /@iconify-json/simple-icons@1.1.104: + resolution: {integrity: sha512-986r56QHsIqa84MdGpV+tUJVhJjDXFb1OfFJZKMDiuQOi0CacK5QsM3if5RNjmT9WhrQtm5uTRHy4/4u7Cb9oQ==} + dependencies: + '@iconify/types': 2.0.0 + dev: true + /@iconify/types@2.0.0: resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} dev: true