42
packages/icons/src/icons-fetcher.ts
Normal file
42
packages/icons/src/icons-fetcher.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { GitHubIconRepository } from "./repositories/github.icon-repository";
|
||||
import { JsdelivrIconRepository } from "./repositories/jsdelivr.icon-repository";
|
||||
import type { RepositoryIconGroup } from "./types";
|
||||
|
||||
const repositories = [
|
||||
new GitHubIconRepository(
|
||||
"Walkxcode",
|
||||
"walkxcode/dashboard-icons",
|
||||
undefined,
|
||||
new URL("https://github.com/walkxcode/dashboard-icons"),
|
||||
new URL(
|
||||
"https://api.github.com/repos/walkxcode/dashboard-icons/git/trees/main?recursive=true",
|
||||
),
|
||||
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/{0}",
|
||||
),
|
||||
new JsdelivrIconRepository(
|
||||
"Papirus",
|
||||
"PapirusDevelopmentTeam/papirus-icon-theme",
|
||||
"GPL-3.0",
|
||||
new URL("https://github.com/PapirusDevelopmentTeam/papirus-icon-theme"),
|
||||
new URL(
|
||||
"https://data.jsdelivr.com/v1/packages/gh/PapirusDevelopmentTeam/papirus_icons@master?structure=flat",
|
||||
),
|
||||
"https://cdn.jsdelivr.net/gh/PapirusDevelopmentTeam/papirus_icons/{0}",
|
||||
),
|
||||
new JsdelivrIconRepository(
|
||||
"Homelab SVG assets",
|
||||
"loganmarchione/homelab-svg-assets",
|
||||
"MIT",
|
||||
new URL("https://github.com/loganmarchione/homelab-svg-assets"),
|
||||
new URL(
|
||||
"https://data.jsdelivr.com/v1/packages/gh/loganmarchione/homelab-svg-assets@main?structure=flat",
|
||||
),
|
||||
"https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/{0}",
|
||||
),
|
||||
];
|
||||
|
||||
export const fetchIconsAsync = async (): Promise<RepositoryIconGroup[]> => {
|
||||
return await Promise.all(
|
||||
repositories.map(async (repository) => await repository.getAllIconsAsync()),
|
||||
);
|
||||
};
|
||||
72
packages/icons/src/repositories/github.icon-repository.ts
Normal file
72
packages/icons/src/repositories/github.icon-repository.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { IconRepositoryLicense } from "../types/icon-repository-license";
|
||||
import type { RepositoryIconGroup } from "../types/repository-icon-group";
|
||||
import { IconRepository } from "./icon-repository";
|
||||
|
||||
export class GitHubIconRepository extends IconRepository {
|
||||
constructor(
|
||||
public readonly name: string,
|
||||
public readonly slug: string,
|
||||
public readonly license: IconRepositoryLicense,
|
||||
public readonly repositoryUrl?: URL,
|
||||
public readonly repositoryIndexingUrl?: URL,
|
||||
public readonly repositoryBlobUrlTemplate?: string,
|
||||
) {
|
||||
super(
|
||||
name,
|
||||
slug,
|
||||
license,
|
||||
repositoryUrl,
|
||||
repositoryIndexingUrl,
|
||||
repositoryBlobUrlTemplate,
|
||||
);
|
||||
}
|
||||
|
||||
protected async getAllIconsInternalAsync(): Promise<RepositoryIconGroup> {
|
||||
if (!this.repositoryIndexingUrl || !this.repositoryBlobUrlTemplate) {
|
||||
throw new Error("Repository URLs are required for this repository");
|
||||
}
|
||||
|
||||
const response = await fetch(this.repositoryIndexingUrl);
|
||||
const listOfFiles = (await response.json()) as GitHubApiResponse;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
icons: listOfFiles.tree
|
||||
.filter((treeItem) =>
|
||||
this.allowedImageFileTypes.some((allowedExtension) =>
|
||||
treeItem.path.includes(allowedExtension),
|
||||
),
|
||||
)
|
||||
.map((treeItem) => {
|
||||
const fileNameWithExtension =
|
||||
this.getFileNameWithoutExtensionFromPath(treeItem.path);
|
||||
|
||||
return {
|
||||
imageUrl: new URL(
|
||||
this.repositoryBlobUrlTemplate!.replace("{0}", treeItem.path),
|
||||
),
|
||||
fileNameWithExtension: fileNameWithExtension,
|
||||
local: false,
|
||||
sizeInBytes: treeItem.size,
|
||||
checksum: treeItem.sha,
|
||||
};
|
||||
}),
|
||||
slug: this.slug,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface GitHubApiResponse {
|
||||
sha: string;
|
||||
url: string;
|
||||
tree: TreeItem[];
|
||||
truncated: boolean;
|
||||
}
|
||||
|
||||
export interface TreeItem {
|
||||
path: string;
|
||||
mode: string;
|
||||
sha: string;
|
||||
url: string;
|
||||
size?: number;
|
||||
}
|
||||
38
packages/icons/src/repositories/icon-repository.ts
Normal file
38
packages/icons/src/repositories/icon-repository.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { IconRepositoryLicense } from "../types/icon-repository-license";
|
||||
import type { RepositoryIconGroup } from "../types/repository-icon-group";
|
||||
|
||||
export abstract class IconRepository {
|
||||
protected readonly allowedImageFileTypes = [".png", ".svg", ".jpeg"];
|
||||
|
||||
protected constructor(
|
||||
public readonly name: string,
|
||||
public readonly slug: string,
|
||||
public readonly license: IconRepositoryLicense,
|
||||
public readonly repositoryUrl?: URL,
|
||||
public readonly repositoryIndexingUrl?: URL,
|
||||
public readonly repositoryBlobUrlTemplate?: string,
|
||||
) {}
|
||||
|
||||
public async getAllIconsAsync(): Promise<RepositoryIconGroup> {
|
||||
try {
|
||||
return await this.getAllIconsInternalAsync();
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`Unable to request icons from repository "${this.slug}": ${JSON.stringify(err)}`,
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
icons: [],
|
||||
slug: this.slug,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract getAllIconsInternalAsync(): Promise<RepositoryIconGroup>;
|
||||
|
||||
protected getFileNameWithoutExtensionFromPath(path: string) {
|
||||
return path.replace(/^.*[\\/]/, "");
|
||||
}
|
||||
}
|
||||
63
packages/icons/src/repositories/jsdelivr.icon-repository.ts
Normal file
63
packages/icons/src/repositories/jsdelivr.icon-repository.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { IconRepositoryLicense } from "../types/icon-repository-license";
|
||||
import type { RepositoryIconGroup } from "../types/repository-icon-group";
|
||||
import { IconRepository } from "./icon-repository";
|
||||
|
||||
export class JsdelivrIconRepository extends IconRepository {
|
||||
constructor(
|
||||
public readonly name: string,
|
||||
public readonly slug: string,
|
||||
public readonly license: IconRepositoryLicense,
|
||||
public readonly repositoryUrl: URL,
|
||||
public readonly repositoryIndexingUrl: URL,
|
||||
public readonly repositoryBlobUrlTemplate: string,
|
||||
) {
|
||||
super(
|
||||
name,
|
||||
slug,
|
||||
license,
|
||||
repositoryUrl,
|
||||
repositoryIndexingUrl,
|
||||
repositoryBlobUrlTemplate,
|
||||
);
|
||||
}
|
||||
|
||||
protected async getAllIconsInternalAsync(): Promise<RepositoryIconGroup> {
|
||||
const response = await fetch(this.repositoryIndexingUrl);
|
||||
const listOfFiles = (await response.json()) as JsdelivrApiResponse;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
icons: listOfFiles.files
|
||||
.filter((file) =>
|
||||
this.allowedImageFileTypes.some((allowedImageFileType) =>
|
||||
file.name.includes(allowedImageFileType),
|
||||
),
|
||||
)
|
||||
.map((file) => {
|
||||
const fileNameWithExtension =
|
||||
this.getFileNameWithoutExtensionFromPath(file.name);
|
||||
|
||||
return {
|
||||
imageUrl: new URL(
|
||||
this.repositoryBlobUrlTemplate.replace("{0}", file.name),
|
||||
),
|
||||
fileNameWithExtension: fileNameWithExtension,
|
||||
local: false,
|
||||
sizeInBytes: file.size,
|
||||
checksum: file.hash,
|
||||
};
|
||||
}),
|
||||
slug: this.slug,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface JsdelivrApiResponse {
|
||||
files: JsdelivrFile[];
|
||||
}
|
||||
|
||||
interface JsdelivrFile {
|
||||
name: string;
|
||||
size: number;
|
||||
hash: string;
|
||||
}
|
||||
1
packages/icons/src/types/icon-repository-license.ts
Normal file
1
packages/icons/src/types/icon-repository-license.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type IconRepositoryLicense = "MIT" | "GPL-3.0" | undefined;
|
||||
3
packages/icons/src/types/index.ts
Normal file
3
packages/icons/src/types/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./icon-repository-license";
|
||||
export * from "./repository-icon-group";
|
||||
export * from "./repository-icon";
|
||||
7
packages/icons/src/types/repository-icon-group.ts
Normal file
7
packages/icons/src/types/repository-icon-group.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { RepositoryIcon } from "./repository-icon";
|
||||
|
||||
export interface RepositoryIconGroup {
|
||||
icons: RepositoryIcon[];
|
||||
success: boolean;
|
||||
slug: string;
|
||||
}
|
||||
7
packages/icons/src/types/repository-icon.ts
Normal file
7
packages/icons/src/types/repository-icon.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface RepositoryIcon {
|
||||
fileNameWithExtension: string;
|
||||
sizeInBytes?: number;
|
||||
imageUrl: URL;
|
||||
local: boolean;
|
||||
checksum: string;
|
||||
}
|
||||
Reference in New Issue
Block a user