Skip to content

Commit

Permalink
Refactor UI components using React and TSX and providing new features (
Browse files Browse the repository at this point in the history
…#113)

* Use react for settings popup

* Update options

* Update styling

* Remove unused type conversions

* Remove unused controls function

* Update project structure

* Delete unused code

* Legacy ui components

* Refactor files

* Update styling

* Update UI components

* Remove unused code

* Remove unused code

* Improve theming

* Update icon color

* Improve custom domains

* Update extension

* Add delete functionalty for custom domains

* Improve icon sizes on screen

* Implement icon bindings dialog

* Minor improvements

* Add tooltips

* Support lookup of language ids in manifest

* Implement watch mode for development purposes

* Improve language id binding customization

* Adjust node script

* Improve reset functionality

* Adjust node script

* Minor improvements

* Update binding controls with icons

* Organize imports

* Update error message

* Adjust icon binding dialog

* Add Info Popover

* Update autocomplete behavior

* Fix image issue

* Minor improvements

* Clean up code

* Make appbar sticky

* Improve project structure

* Update info text

* Adjust styling

* Update styling

* Improve adding new bindings

* Adjust tsconfig

* Support switch of themes for the icon preview

* Update watch script

* Improve error handling

* Move build languages step before build src
  • Loading branch information
PKief authored Oct 23, 2024
1 parent 061bba8 commit 17c71b8
Show file tree
Hide file tree
Showing 55 changed files with 4,561 additions and 1,233 deletions.
3,058 changes: 2,838 additions & 220 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 13 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@
"url": "https://github.com/material-extensions/material-icons-browser-extension/issues"
},
"dependencies": {
"@emotion/react": "11.13.3",
"@emotion/styled": "11.13.0",
"@mui/icons-material": "6.1.2",
"@mui/material": "6.1.2",
"material-icon-theme": "5.11.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"selector-observer": "2.1.6",
"webextension-polyfill": "0.12.0"
},
Expand All @@ -28,13 +34,17 @@
"@types/fs-extra": "11.0.4",
"@types/json-stable-stringify": "1.0.36",
"@types/node": "20.14.10",
"@types/react": "18.3.11",
"@types/react-dom": "18.3.0",
"@types/webextension-polyfill": "0.10.7",
"dotenv": "16.4.5",
"esbuild": "0.21.5",
"esbuild-sass-plugin": "3.3.1",
"fs-extra": "11.2.0",
"husky": "9.0.11",
"json-stable-stringify": "1.1.1",
"lint-staged": "15.2.7",
"nodemon": "3.1.7",
"npm-run-all": "4.1.5",
"rimraf": "5.0.7",
"sharp": "0.33.4",
Expand All @@ -44,10 +54,11 @@
},
"scripts": {
"prebuild": "rimraf --glob *.zip ./dist",
"build": "run-s build-languages build-src compile bundle",
"build": "run-s build-languages build-src check-type-safety bundle",
"build-languages": "ts-node ./scripts/build-languages.ts",
"build-src": "ts-node ./scripts/build-src.ts",
"compile": "tsc -p ./",
"build-src-watch": "nodemon --watch ./src --ext ts,tsx,css,html --exec npm run build-src",
"check-type-safety": "tsc -p ./",
"rebuild-logos": "ts-node ./scripts/build-icons.ts",
"bundle": "run-p bundle-edge bundle-chrome bundle-firefox",
"bundle-edge": "zip -r -j github-material-icons-edge-extension.zip dist/chrome-edge",
Expand Down
11 changes: 4 additions & 7 deletions scripts/build-src.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ function bundleJS(
minify: true,
sourcemap: false,
outdir: outDir,
loader: { '.svg': 'dataurl' },
};
return esbuild.build(buildOptions);
}
Expand All @@ -61,21 +62,17 @@ function src(
const bundlePopupScript = (): Promise<esbuild.BuildResult> =>
bundleJS(
distPath,
path.resolve(srcPath, 'ui', 'popup', 'settings-popup.ts')
path.resolve(srcPath, 'ui', 'popup', 'settings-popup.tsx')
);
const bundleOptionsScript = (): Promise<esbuild.BuildResult> =>
bundleJS(distPath, path.resolve(srcPath, 'ui', 'options', 'options.ts'));
bundleJS(distPath, path.resolve(srcPath, 'ui', 'options', 'options.tsx'));

const bundleAll: Promise<esbuild.BuildResult> = bundleMainScript()
.then(bundlePopupScript)
.then(bundleOptionsScript);

const copyPopup: Promise<void[]> = Promise.all(
[
'settings-popup.html',
'settings-popup.css',
'settings-popup.github-logo.svg',
].map((file) =>
['settings-popup.html', 'settings-popup.css'].map((file) =>
fs.copy(
path.resolve(srcPath, 'ui', 'popup', file),
path.resolve(distPath, file)
Expand Down
8 changes: 8 additions & 0 deletions src/lib/custom-providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ export const addCustomProvider = (

return Browser.storage.sync.set({ customProviders });
});

export const removeCustomProvider = (name: string) => {
return getCustomProviders().then((customProviders) => {
delete customProviders[name];

Browser.storage.sync.set({ customProviders });
});
};
6 changes: 3 additions & 3 deletions src/lib/icon-sizes.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { addConfigChangeListener, getConfig } from './user-config';

export type IconSize = 'sm' | 'md' | 'lg' | 'xl';
export const iconSizes = ['sm', 'md', 'lg', 'xl'];
export type IconSize = (typeof iconSizes)[number];

const setSizeAttribute = (iconSize: IconSize) =>
document.body.setAttribute(`data-material-icons-extension-size`, iconSize);

export const initIconSizes = () => {
const setIconSize = () =>
getConfig<IconSize>('iconSize').then(setSizeAttribute);
const setIconSize = () => getConfig('iconSize').then(setSizeAttribute);

document.addEventListener('DOMContentLoaded', setIconSize, false);

Expand Down
35 changes: 26 additions & 9 deletions src/lib/replace-icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function replaceIcon(
}

// get correct icon name from icon list
iconName = iconsListTyped[iconName] ?? 'file.svg';
iconName = iconsListTyped[iconName] ?? (isDir ? 'folder.svg' : 'file.svg');

replaceElementWithIcon(iconEl, iconName, fileName, provider);
}
Expand Down Expand Up @@ -122,14 +122,14 @@ function lookForMatch(
if (manifest.languageIds?.[ext]) return manifest.languageIds?.[ext];
}

if (languageMapTyped.fileNames[fileName])
return languageMapTyped.fileNames[fileName];
if (languageMapTyped.fileNames[lowerFileName])
return languageMapTyped.fileNames[lowerFileName];
for (const ext of fileExtensions) {
if (languageMapTyped.fileExtensions[ext])
return languageMapTyped.fileExtensions[ext];
}
const languageIcon = getLanguageIcon(
fileName,
lowerFileName,
fileExtensions
);

if (languageIcon)
return manifest.languageIds?.[languageIcon] ?? languageIcon;

return 'file';
}
Expand All @@ -141,6 +141,23 @@ function lookForMatch(
return 'folder';
}

function getLanguageIcon(
fileName: string,
lowerFileName: string,
fileExtensions: string[]
): string | undefined {
if (languageMapTyped.fileNames[fileName])
return languageMapTyped.fileNames[fileName];
if (languageMapTyped.fileNames[lowerFileName])
return languageMapTyped.fileNames[lowerFileName];
for (const ext of fileExtensions) {
if (languageMapTyped.fileExtensions[ext])
return languageMapTyped.fileExtensions[ext];
}

return undefined;
}

function lookForLightMatch(
iconName: string,
fileName: string,
Expand Down
18 changes: 15 additions & 3 deletions src/lib/replace-icons.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import { IconPackValue, generateManifest } from 'material-icon-theme';
import {
IconAssociations,
IconPackValue,
generateManifest,
} from 'material-icon-theme';
import { observe } from 'selector-observer';
import { Provider } from '../models';
import { replaceElementWithIcon, replaceIconInRow } from './replace-icon';

export const observePage = (
gitProvider: Provider,
iconPack: IconPackValue
iconPack: IconPackValue,
fileBindings?: IconAssociations,
folderBindings?: IconAssociations,
languageBindings?: IconAssociations
): void => {
const manifest = generateManifest({
activeIconPack: iconPack ?? undefined,
activeIconPack: iconPack || undefined,
files: { associations: fileBindings },
folders: { associations: folderBindings },
languages: {
associations: languageBindings,
},
});

observe(gitProvider.selectors.row, {
Expand Down
31 changes: 21 additions & 10 deletions src/lib/user-config.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
import { IconAssociations, IconPackValue } from 'material-icon-theme';
import Browser from 'webextension-polyfill';
import { IconSize } from './icon-sizes';

export type UserConfig = {
iconPack: string;
iconSize: string;
iconPack: IconPackValue;
iconSize: IconSize;
extEnabled: boolean;
fileIconBindings?: IconAssociations;
folderIconBindings?: IconAssociations;
languageIconBindings?: IconAssociations;
};

const hardDefaults: UserConfig = {
export const hardDefaults: UserConfig = {
iconPack: 'react',
iconSize: 'md',
extEnabled: true,
fileIconBindings: {},
folderIconBindings: {},
languageIconBindings: {},
};

export const getConfig = async <T = unknown>(
configName: keyof UserConfig,
type ConfigValueType<T extends keyof UserConfig> = UserConfig[T];

export const getConfig = async <T extends keyof UserConfig>(
configName: T,
domain = window.location.hostname,
useDefault = true
): Promise<T> => {
): Promise<ConfigValueType<T>> => {
const keys = {
[`${domain !== 'default' ? domain : 'SKIP'}:${configName}`]: null,
[`default:${configName}`]: hardDefaults[configName],
Expand All @@ -29,14 +39,15 @@ export const getConfig = async <T = unknown>(
return domainSpecificValue ?? (useDefault ? defaultValue : null);
};

export const setConfig = (
configName: keyof UserConfig,
value: unknown,
export const setConfig = <T extends keyof UserConfig>(
configName: T,
value: ConfigValueType<T>,
domain = window.location.hostname
) =>
) => {
Browser.storage.sync.set({
[`${domain}:${configName}`]: value,
});
};

export const clearConfig = (
configName: keyof UserConfig,
Expand Down
6 changes: 0 additions & 6 deletions src/lib/utils.ts

This file was deleted.

19 changes: 14 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,22 @@ const handleProvider = async (href: string) => {
const provider: Provider | null = await getGitProvider(href);
if (!provider) return;

const iconPack = await getConfig<IconPackValue>('iconPack');
const extEnabled = await getConfig<boolean>('extEnabled');
const globalExtEnabled = await getConfig<boolean>('extEnabled', 'default');
const iconPack = await getConfig('iconPack');
const fileBindings = await getConfig('fileIconBindings');
const folderBindings = await getConfig('folderIconBindings');
const languageBindings = await getConfig('languageIconBindings');
const extEnabled = await getConfig('extEnabled');
const globalExtEnabled = await getConfig('extEnabled', 'default');

if (!globalExtEnabled || !extEnabled) return;

observePage(provider, iconPack);
observePage(
provider,
iconPack,
fileBindings,
folderBindings,
languageBindings
);
addConfigChangeListener('iconPack', () => replaceAllIcons(provider));
};

Expand All @@ -49,7 +58,7 @@ const handlers: Handlers = {

Browser.runtime.onMessage.addListener(
(
message: { cmd: keyof Handlers; args?: any[] },
message: { cmd: keyof Handlers; args?: unknown[] },
_: Browser.Runtime.MessageSender,
sendResponse: (response?: any) => void
) => {
Expand Down
10 changes: 8 additions & 2 deletions src/models/provider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface Provider {
export type Provider = {
name: string;
domains: { host: string; test: RegExp }[];
selectors: {
Expand All @@ -20,4 +20,10 @@ export interface Provider {
iconEl: HTMLElement,
fileName: string
) => string;
}
};

export type Domain = Pick<Provider, 'name' | 'isCustom'> & {
isDefault: boolean;
};

export type ProviderMap = Record<string, string>;
2 changes: 1 addition & 1 deletion src/providers/gitlab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function gitlab(): Provider {
.tree-item svg, .file-header-content svg:not(.gl-button-icon),
.gl-link svg.gl-icon[data-testid="doc-code-icon"]`,
// Element by which to detect if the tested domain is gitlab.
detect: 'body.page-initialized[data-page]',
detect: 'head meta[content="GitLab"]',
},
canSelfHost: true,
isCustom: false,
Expand Down
4 changes: 4 additions & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export const addGitProvider = (
providerConfig[name] = provider;
};

export const removeGitProvider = (name: string) => {
delete providerConfig[name];
};

export const getGitProviders = () =>
getCustomProviders().then((customProviders) => {
for (const [domain, handler] of Object.entries(customProviders)) {
Expand Down
15 changes: 15 additions & 0 deletions src/ui/options/api/domains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Domain } from '@/models';
import { getGitProviders } from '@/providers';

export function getDomains(): Promise<Domain[]> {
return getGitProviders().then((providers) => [
{ name: 'default', isCustom: false, isDefault: true },
...Object.values(providers).flatMap((p) =>
p.domains.map((d) => ({
name: d.host,
isCustom: p.isCustom,
isDefault: false,
}))
),
]);
}
39 changes: 39 additions & 0 deletions src/ui/options/api/icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import iconsList from '../../../icon-list.json';

const iconsListTyped = iconsList as Record<string, string>;
const blacklist = ['_light', '_highContrast'];

function isNotBlacklisted(name: string): boolean {
return !blacklist.some((term) => name.includes(term));
}

function filterIcons(predicate: (name: string) => boolean): string[] {
return Object.keys(iconsListTyped).filter(predicate).sort();
}

export function getIconFileName(
iconName: string,
isLightMode: boolean
): string {
const lightIconName = `${iconName}_light`;
if (isLightMode && iconsListTyped[lightIconName]) {
return iconsListTyped[lightIconName];
}
return iconsListTyped[iconName];
}

export function getListOfFileIcons(): string[] {
return filterIcons(
(name) => !name.startsWith('folder') && isNotBlacklisted(name)
);
}

export function getListOfFolderIcons(): string[] {
return filterIcons(
(name) =>
name.startsWith('folder') &&
!name.includes('-open') &&
!name.includes('-root') &&
isNotBlacklisted(name)
).map((name) => name.replace('folder-', ''));
}
Loading

0 comments on commit 17c71b8

Please sign in to comment.