mirror of
https://github.com/ajnart/homarr.git
synced 2026-01-19 14:02:20 +01:00
♻️ Refactor icon picker (#724)
This commit is contained in:
32
src/tools/server/images/abstract-icons-repository.ts
Normal file
32
src/tools/server/images/abstract-icons-repository.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export abstract class AbstractIconRepository {
|
||||
constructor(readonly copyright?: string) {}
|
||||
|
||||
async fetch(): Promise<NormalizedIconRepositoryResult> {
|
||||
try {
|
||||
return await this.fetchInternally();
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
count: 0,
|
||||
entries: [],
|
||||
name: '',
|
||||
copyright: this.copyright,
|
||||
};
|
||||
}
|
||||
}
|
||||
protected abstract fetchInternally(): Promise<NormalizedIconRepositoryResult>;
|
||||
}
|
||||
|
||||
export type NormalizedIconRepositoryResult = {
|
||||
name: string;
|
||||
success: boolean;
|
||||
count: number;
|
||||
copyright: string | undefined;
|
||||
entries: NormalizedIcon[];
|
||||
};
|
||||
|
||||
export type NormalizedIcon = {
|
||||
url: string;
|
||||
name: string;
|
||||
size: number;
|
||||
};
|
||||
71
src/tools/server/images/jsdelivr-icons-repository.ts
Normal file
71
src/tools/server/images/jsdelivr-icons-repository.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
AbstractIconRepository,
|
||||
NormalizedIcon,
|
||||
NormalizedIconRepositoryResult,
|
||||
} from './abstract-icons-repository';
|
||||
|
||||
export class JsdelivrIconsRepository extends AbstractIconRepository {
|
||||
static readonly tablerRepository = {
|
||||
api: 'https://data.jsdelivr.com/v1/packages/gh/walkxcode/dashboard-icons@main?structure=flat',
|
||||
blob: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/{0}/{1}',
|
||||
} as JsdelivrRepositoryUrl;
|
||||
|
||||
static readonly papirusRepository = {
|
||||
api: 'https://data.jsdelivr.com/v1/packages/gh/PapirusDevelopmentTeam/papirus_icons@master?structure=flat',
|
||||
blob: 'https://cdn.jsdelivr.net/gh/PapirusDevelopmentTeam/papirus_icons/src/{1}',
|
||||
} as JsdelivrRepositoryUrl;
|
||||
|
||||
static readonly homelabSvgAssetsRepository = {
|
||||
api: 'https://data.jsdelivr.com/v1/packages/gh/loganmarchione/homelab-svg-assets@main?structure=flat',
|
||||
blob: 'https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/assets/{1}',
|
||||
} as JsdelivrRepositoryUrl;
|
||||
|
||||
constructor(
|
||||
private readonly repository: JsdelivrRepositoryUrl,
|
||||
private readonly displayName: string,
|
||||
copyright: string,
|
||||
) {
|
||||
super(copyright);
|
||||
}
|
||||
|
||||
protected async fetchInternally(): Promise<NormalizedIconRepositoryResult> {
|
||||
const response = await fetch(this.repository.api);
|
||||
const body = (await response.json()) as JsdelivrResponse;
|
||||
|
||||
const normalizedEntries = body.files
|
||||
.filter((file) => !['_banner.png', '_logo.png'].some((x) => file.name.includes(x)))
|
||||
.filter((file) => ['.png', '.svg'].some((x) => file.name.endsWith(x)))
|
||||
.map((file): NormalizedIcon => {
|
||||
const fileNameParts = file.name.split('/');
|
||||
const fileName = fileNameParts[fileNameParts.length - 1];
|
||||
const extensions = fileName.split('.')[1];
|
||||
return {
|
||||
url: this.repository.blob.replace('{0}', extensions).replace('{1}', fileName),
|
||||
name: fileName,
|
||||
size: file.size,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
entries: normalizedEntries,
|
||||
count: normalizedEntries.length,
|
||||
success: true,
|
||||
name: this.displayName,
|
||||
copyright: this.copyright,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
type JsdelivrRepositoryUrl = {
|
||||
api: string;
|
||||
blob: string;
|
||||
};
|
||||
|
||||
type JsdelivrResponse = {
|
||||
files: JsdelivrFile[];
|
||||
};
|
||||
|
||||
type JsdelivrFile = {
|
||||
name: string;
|
||||
size: number;
|
||||
};
|
||||
44
src/tools/server/images/local-icons-repository.ts
Normal file
44
src/tools/server/images/local-icons-repository.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import fs from 'fs';
|
||||
import {
|
||||
AbstractIconRepository,
|
||||
NormalizedIcon,
|
||||
NormalizedIconRepositoryResult,
|
||||
} from './abstract-icons-repository';
|
||||
|
||||
export class LocalIconsRepository extends AbstractIconRepository {
|
||||
constructor() {
|
||||
super('');
|
||||
}
|
||||
|
||||
protected async fetchInternally(): Promise<NormalizedIconRepositoryResult> {
|
||||
if (!fs.existsSync('./public/icons')) {
|
||||
return {
|
||||
count: 0,
|
||||
entries: [],
|
||||
name: 'Local',
|
||||
success: true,
|
||||
copyright: this.copyright,
|
||||
};
|
||||
}
|
||||
|
||||
const files = fs.readdirSync('./public/icons');
|
||||
|
||||
const normalizedEntries = files
|
||||
.filter((file) => ['.png', '.svg', '.jpeg', '.jpg'].some((x) => file.endsWith(x)))
|
||||
.map(
|
||||
(file): NormalizedIcon => ({
|
||||
name: file,
|
||||
url: `./icons/${file}`,
|
||||
size: 0,
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
entries: normalizedEntries,
|
||||
count: normalizedEntries.length,
|
||||
success: true,
|
||||
name: 'Local',
|
||||
copyright: this.copyright,
|
||||
};
|
||||
}
|
||||
}
|
||||
52
src/tools/server/images/unpkg-icons-repository.ts
Normal file
52
src/tools/server/images/unpkg-icons-repository.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
AbstractIconRepository,
|
||||
NormalizedIcon,
|
||||
NormalizedIconRepositoryResult,
|
||||
} from './abstract-icons-repository';
|
||||
|
||||
export class UnpkgIconsRepository extends AbstractIconRepository {
|
||||
static tablerRepository = 'https://unpkg.com/@tabler/icons-png@2.0.0-beta/icons/';
|
||||
|
||||
constructor(
|
||||
private readonly repository: string,
|
||||
private readonly displayName: string,
|
||||
copyright: string
|
||||
) {
|
||||
super(copyright);
|
||||
}
|
||||
|
||||
protected async fetchInternally(): Promise<NormalizedIconRepositoryResult> {
|
||||
const response = await fetch(`${this.repository}?meta`);
|
||||
const body = (await response.json()) as UnpkgResponse;
|
||||
|
||||
const normalizedEntries = body.files
|
||||
.filter((file) => file.type === 'file')
|
||||
.map((file): NormalizedIcon => {
|
||||
const fileName = file.path.replace('/icons/', '');
|
||||
const url = `${this.repository}${fileName}`;
|
||||
return {
|
||||
name: fileName,
|
||||
url,
|
||||
size: file.size,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
entries: normalizedEntries,
|
||||
count: normalizedEntries.length,
|
||||
success: true,
|
||||
name: this.displayName,
|
||||
copyright: this.copyright,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
type UnpkgResponse = {
|
||||
files: UnpkgFile[];
|
||||
};
|
||||
|
||||
type UnpkgFile = {
|
||||
path: string;
|
||||
type: string;
|
||||
size: number;
|
||||
};
|
||||
@@ -3,7 +3,6 @@ export const dashboardNamespaces = [
|
||||
'layout/element-selector/selector',
|
||||
'layout/modals/add-app',
|
||||
'layout/modals/change-position',
|
||||
'layout/modals/icon-picker',
|
||||
'layout/modals/about',
|
||||
'layout/header/actions/toggle-edit-mode',
|
||||
'layout/mobile/drawer',
|
||||
|
||||
Reference in New Issue
Block a user