GitFOSS
feat(repository): add support for all kind of images to RepositoryBrowserView
+ 113
- 15
@@ -1,5 +1,5 @@
 {
-  "_generatedAtUnix": 1663726257814,
+  "_generatedAtUnix": 1663770423607,
   "_hashAlgorithm": "sha1",
   "_version": 2,
   "islands": {

...
@@ -50,7 +50,7 @@
       "pathSource": "./app/views/auth/RegisterView.tsx"
     },
     "RepositoryBrowserView": {
-      "hash": "e21209cae1f4aaab6678ac937259c085084abbff",
+      "hash": "d9f8ac892f78a75e1f7f2a1cb82602fb87dafd72",
       "pathSource": "./app/views/repository/RepositoryBrowserView.tsx"
     },
     "RepositoryCommitsLogView": {

app/plugins/codeAnalysis.ts
@@ -1,8 +1,12 @@
 // 3rd-party
 import { FastifyPluginAsync } from "fastify";
 import fp from "fastify-plugin";
+import getFilePathExt from "file-extension";
+import imageExts from "image-extensions";
+import isImageFilePath from "is-image";
 import languagesMap from "language-map";
 import languageDetect from "language-detect";
+import mimeTypes from "mime-db";
 // app
 import { makeCodeAnalysisService } from "../services/codeAnalysis";
 

...
@@ -10,8 +14,27 @@ const serviceKey = "codeAnalysisService";
 export const codeAnalysisPlugin: FastifyPluginAsync<never> = fp(
   async (server) => {
     const service = makeCodeAnalysisService({
+      getFilePathExt,
+      imageExts,
+      isImageFilePath,
       languageDetect,
       languagesMap,
+      extsMimeTypesMap: Object.entries(mimeTypes).reduce(
+        (acc, [mimeType, metas]) => {
+          const { extensions } = metas;
+          if (extensions == null) return acc;
+          extensions.forEach((ext) => {
+            acc = {
+              ...acc,
+              [ext]: mimeType,
+            };
+          });
+          return acc;
+        },
+        {} as {
+          [extension: string]: string; // ext => mime-type
+        }
+      ),
     });
     server.decorate(serviceKey, {
       getter: () => service,

app/services/codeAnalysis/getLinguistFileInfos.ts
@@ -9,6 +9,20 @@ const makeGetLinguistFileInfos: ServiceMethodFactory<
   Promise<LinguistFileInfos>
 > = (deps) => {
   return async (path, content = undefined) => {
+    const ext = deps.getFilePathExt(path);
+    if (deps.imageExts.includes(ext)) {
+      return {
+        // TODO(improvement): make an algorithm to generate one color per ext?
+        color: "gray",
+        extensions: [ext],
+        languageDisplayName: ext.toUpperCase(),
+        language: ext,
+        languageId: -1,
+        mimeType: deps.extsMimeTypesMap[ext] || `image/${ext}`,
+        type: "image",
+      };
+    }
+
     const language =
       content != null
         ? deps.languageDetect.contents(path, content)

app/services/codeAnalysis/types.ts
@@ -23,8 +23,10 @@ export interface CodeAnalysisServiceAPI extends ServiceApiContract {
 }
 
 export interface CodeAnalysisServiceDeps {
+  extsMimeTypesMap: { [extension: string]: string };
+  getFilePathExt: (path: string) => string;
+  imageExts: string[];
+  isImageFilePath: (path: string) => boolean;
   languageDetect: LanguageDetectFn;
-
-  // types for NPM package's: language-map
   languagesMap: LanguagesMap;
 }

app/views/repository/RepositoryBrowserView.tsx
@@ -8,7 +8,13 @@ import type { Organization, Repository, User } from "@prisma/client";
 import type { LinguistFileInfos } from "../../services/codeAnalysis/types";
 // app
 import type { CommonProps, RepositoryFileContent } from "../../types";
-import { Code, Layout, PageWrapper, getThemedCodeCss } from "../../components";
+import {
+  Code,
+  Grid,
+  Layout,
+  PageWrapper,
+  getThemedCodeCss,
+} from "../../components";
 
 export interface RepositoryBrowserViewProps extends CommonProps {
   currentUser: null | User;

...
@@ -42,15 +48,37 @@ const RepositoryBrowserView: ReactView<RepositoryBrowserViewProps> = ({
           {" / "}
           {path}
         </h1>
-        <div style={{ width: "100%" }}>
-          <span>lang: {linguistInfos.language}</span>
-          {getThemedCodeCss(commonProps.themeScheme)}
-          <Code
-            code={fileContent.content}
-            language={linguistInfos.language}
-            themeScheme={commonProps.themeScheme}
-          />
-        </div>
+        <Grid.Col fluid>
+          <Grid.Row
+            nowrap
+            alignItems={"center"}
+            style={{ width: "100%", marginTop: 16 }}
+          >
+            <div>
+              <strong>lang:</strong> <span>{linguistInfos.language}</span>
+            </div>
+            <div style={{ marginLeft: 16 }}>
+              <strong>mime:</strong> <span>{linguistInfos.mimeType}</span>
+            </div>
+          </Grid.Row>
+          {linguistInfos.type === "image" ? (
+            <img
+              src={`data:image/${linguistInfos.mimeType};base64,${Buffer.from(
+                fileContent.content
+              ).toString("base64")}`}
+              style={{ width: "100%", height: "auto" }}
+            />
+          ) : (
+            <>
+              {getThemedCodeCss(commonProps.themeScheme)}
+              <Code
+                code={fileContent.content}
+                language={linguistInfos.language}
+                themeScheme={commonProps.themeScheme}
+              />
+            </>
+          )}
+        </Grid.Col>
       </PageWrapper>
     </Layout>
   );

@@ -38,11 +38,15 @@
     "dotenv-flow": "^3.2.0",
     "fastify": "^3.27.4",
     "fastify-static": "^4.6.1",
+    "file-extension": "^4.0.5",
     "gray-matter": "^4.0.3",
+    "image-extensions": "^1.1.0",
+    "is-image": "^3.1.0",
     "language-detect": "^1.1.0",
     "language-map": "^1.5.0",
     "markdown-to-jsx": "^7.1.7",
     "markdown-toc": "^1.2.0",
+    "mime-db": "^1.52.0",
     "patch-package": "^6.4.7",
     "pg": "^8.7.3",
     "postinstall-postinstall": "^2.1.0",

...
@@ -58,6 +62,7 @@
     "@types/dotenv-flow": "^3.2.0",
     "@types/fastify-static": "^2.2.1",
     "@types/jest": "^28.1.6",
+    "@types/mime-db": "^1.43.1",
     "@types/node": "^18.6.1",
     "@types/pg": "^8.6.5",
     "@types/prismjs": "^1.26.0",

new file
public/assets/bitmaps/landscape.jpg
new file
types/file-extension.d.ts
@@ -0,0 +1,5 @@
+declare module "file-extension";
+
+export declare const getFilePathExtension: (path: string) => string;
+
+export default getFilePathExtension;

new file
types/image-extensions.d.ts
@@ -0,0 +1,5 @@
+declare module "image-extensions";
+
+export declare const imageExtensions: string[];
+
+export default imageExtensions;

@@ -989,6 +989,10 @@
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
 
+"@types/mime-db@^1.43.1":
+  version "1.43.1"
+  resolved "https://registry.yarnpkg.com/@types/mime-db/-/mime-db-1.43.1.tgz#c2a0522453bb9b6e84ee48b7eef765d19bcd519e"
+
 "@types/mime@*":
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"

...
@@ -2156,6 +2160,10 @@ fb-watchman@^2.0.0:
   dependencies:
     bser "2.1.1"
 
+file-extension@^4.0.5:
+  version "4.0.5"
+  resolved "https://registry.yarnpkg.com/file-extension/-/file-extension-4.0.5.tgz#ae6cef34c28e7313a92baa4aa955755cacdf0ce3"
+
 fill-range@^2.1.0:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565"

...
@@ -2577,6 +2585,10 @@ iconv-lite@0.4.24:
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
+image-extensions@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/image-extensions/-/image-extensions-1.1.0.tgz#b8e6bf6039df0056e333502a00b6637a3105d894"
+
 import-local@^3.0.2:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4"

...
@@ -2692,6 +2704,10 @@ is-glob@^4.0.1, is-glob@~4.0.1:
   dependencies:
     is-extglob "^2.1.1"
 
+is-image@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/is-image/-/is-image-3.1.0.tgz#d8e5e8370474297cba8ad259ec6f213ed506133e"
+
 is-negative-zero@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150"

...
@@ -3653,7 +3669,7 @@ micromatch@^4.0.2, micromatch@^4.0.4:
     braces "^3.0.2"
     picomatch "^2.3.1"
 
-mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
+mime-db@1.52.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.52.0:
   version "1.52.0"
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"