Skip to content

Commit

Permalink
feat: ✨ allow creating clones at build time
Browse files Browse the repository at this point in the history
support for creating clones by configuring them
in the fileIcons.ts and folderIcons.ts files
so that the icons are created at build time
allowing contributors to create clones that are
shipped with the extension
  • Loading branch information
lucas-labs committed Apr 26, 2024
1 parent 15ba192 commit 400d0cf
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 52 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -11,6 +11,7 @@ icons/folder.svg
icons/folder-open.svg
icons/folder-root.svg
icons/folder-root-open.svg
icons/*.clone.svg
icons/clones

src/scripts/preview/*.html
Expand Down
68 changes: 64 additions & 4 deletions src/icons/generator/clones/clonesGenerator.ts
@@ -1,6 +1,9 @@
import {
CustomClone,
FileIconClone,
FileIcons,
FolderIconClone,
FolderTheme,
IconConfiguration,
IconJsonOptions,
} from '../../../models';
Expand All @@ -14,10 +17,11 @@ import merge from 'lodash.merge';
import { getFileConfigHash } from '../../../helpers/fileConfig';
import { cloneIcon, createCloneConfig } from './utils/cloning';
import { writeFileSync } from 'fs';
import { cloneIconExtension, clonesFolder } from '../constants';

/**
* Creates custom icons by cloning already existing icons and changing
* their colors, allowing users create their own variations.
* their colors, based on the user's provided configurations.
*/
export function customClonesIcons(
config: IconConfiguration,
Expand All @@ -43,6 +47,62 @@ export function customClonesIcons(
return clonedIconsConfig;
}

/**
* Creates custom icons by cloning already existing icons and changing
* their colors, based on the configurations provided by the extension.
* (this is meant to be called at build time)
*/
export function generateConfiguredClones(
iconsList: FolderTheme[] | FileIcons,
config: IconConfiguration
) {
let iconsToClone: CustomClone[] = [];

if (Array.isArray(iconsList)) {
iconsToClone = iconsList.reduce((acc, theme) => {
const icons = theme.icons?.filter((icon) => icon.clone) ?? [];
return acc.concat(
icons.map((icon) => ({
folderNames: icon.folderNames,
name: icon.name,
...icon.clone!,
}))
);
}, [] as FolderIconClone[]);
} else {
const icons = iconsList.icons?.filter((icon) => icon.clone) ?? [];
iconsToClone = icons.map(
(icon) =>
({
fileExtensions: icon.fileExtensions,
fileNames: icon.fileNames,
name: icon.name,
...icon.clone!,
} as FileIconClone)
);
}

iconsToClone?.forEach((clone) => {
const clones = getCloneData(clone, config, '', '', cloneIconExtension);
if (!clones) {
return;
}

clones.forEach((clone) => {
try {
// generates the new icon content (svg)
const content = cloneIcon(clone.base.path, clone.color);

// write the new .svg file to the disk
writeFileSync(clone.path, content);
} catch (error) {
console.error(error);
return;
}
});
});
}

/** Checks if there are any custom clones to be created */
export function hasCustomClones(options: IconJsonOptions): boolean {
return (
Expand All @@ -58,13 +118,13 @@ export function hasCustomClones(options: IconJsonOptions): boolean {
* @param hash current hash being applied to the icons
* @returns a partial icon configuration for the new icon
*/
export function createIconClone(
function createIconClone(
cloneOpts: FolderIconClone | FileIconClone,
config: IconConfiguration,
hash: string
): IconConfiguration {
// get clones to be created
const clones = getCloneData(cloneOpts, config, hash);
const clones = getCloneData(cloneOpts, config, clonesFolder, hash);
if (!clones) {
return {};
}
Expand All @@ -74,7 +134,7 @@ export function createIconClone(
clones.forEach((clone) => {
try {
// generates the new icon content (svg)
const content = cloneIcon(clone.base.path, hash, clone.color);
const content = cloneIcon(clone.base.path, clone.color, hash);

try {
// write the new .svg file to the disk
Expand Down
81 changes: 49 additions & 32 deletions src/icons/generator/clones/utils/clone-data.ts
Expand Up @@ -43,7 +43,7 @@ function resolvePath(path: string): string {
return join(__dirname, String(path));
} else {
// executed via script
return join(__dirname, '..', '..', '..', String(path));
return join(__dirname, '..', '..', '..', '..', String(path));
}
}

Expand All @@ -65,7 +65,9 @@ const isDark = (daa: IconData) =>
export function getCloneData(
cloneOpts: CustomClone,
config: IconConfiguration,
hash: string
subFolder: string,
hash: string,
ext?: string
): CloneData[] | undefined {
const baseIcon = isFolder(cloneOpts)
? getFolderIconBaseData(cloneOpts, config)
Expand All @@ -74,15 +76,17 @@ export function getCloneData(
if (baseIcon) {
return baseIcon.map((base) => {
const cloneIcon = isFolder(cloneOpts)
? getFolderIconCloneData(base, cloneOpts, hash)
: getFileIconCloneData(base, cloneOpts, hash);
? getFolderIconCloneData(base, cloneOpts, hash, subFolder, ext)
: getFileIconCloneData(base, cloneOpts, hash, subFolder, ext);

return {
name: getIconName(cloneOpts.name, base),
color: isDark(base)
? cloneOpts.color
: cloneOpts.lightColor ?? cloneOpts.color,
inConfigPath: `${iconFolderPath}clones/${basename(cloneIcon.path)}`,
inConfigPath: `${iconFolderPath}${subFolder}${basename(
cloneIcon.path
)}`,
base,
...cloneIcon,
};
Expand Down Expand Up @@ -114,7 +118,7 @@ function getFileIconBaseData(
});
light &&
icons.push({
type: Type.Folder,
type: Type.File,
variant: Variant.Light,
path: resolvePath(light),
});
Expand All @@ -126,10 +130,12 @@ function getFileIconBaseData(
function getFileIconCloneData(
base: IconData,
cloneOpts: FileIconClone,
hash: string
hash: string,
subFolder: string,
ext = '.svg'
): IconData {
const name = getIconName(cloneOpts.name, base);
const clonePath = join(dirname(base.path), 'clones', `${name}${hash}.svg`);
const clonePath = join(dirname(base.path), subFolder, `${name}${hash}${ext}`);

return {
variant: base.variant,
Expand All @@ -140,12 +146,16 @@ function getFileIconCloneData(

/** returns path, type and variant for the base folder icons to be cloned */
function getFolderIconBaseData(
cloneOpts: FolderIconClone,
clone: FolderIconClone,
config: IconConfiguration
): IconData[] | undefined {
const icons = [];
const folderBase =
cloneOpts.base === 'folder' ? 'folder' : `folder-${cloneOpts.base}`;
clone.base === 'folder'
? 'folder'
: clone.base.startsWith('folder-')
? clone.base
: `folder-${clone.base}`;

const base = config.iconDefinitions?.[`${folderBase}`]?.iconPath;
const open =
Expand All @@ -158,20 +168,19 @@ function getFolderIconBaseData(
]?.iconPath;

if (base && open) {
base &&
icons.push({
type: Type.Folder,
variant: Variant.Base,
path: resolvePath(base),
});
open &&
icons.push({
type: Type.Folder,
variant: Variant.Open,
path: resolvePath(open),
});
icons.push({
type: Type.Folder,
variant: Variant.Base,
path: resolvePath(base),
});

if (cloneOpts.lightColor && (!light || !lightOpen)) {
icons.push({
type: Type.Folder,
variant: Variant.Open,
path: resolvePath(open),
});

if (clone.lightColor && (!light || !lightOpen)) {
// the original icon does not have a light version, so we re-use the base icons
light = base;
lightOpen = open;
Expand Down Expand Up @@ -201,10 +210,12 @@ function getFolderIconBaseData(
function getFolderIconCloneData(
base: IconData,
cloneOpts: FolderIconClone,
hash: string
hash: string,
subFolder: string,
ext = '.svg'
): IconData {
const name = getIconName(cloneOpts.name, base);
const path = join(dirname(base.path), 'clones', `${name}${hash}.svg`);
const path = join(dirname(base.path), subFolder, `${name}${hash}${ext}`);
return { type: base.type, variant: base.variant, path };
}

Expand All @@ -226,26 +237,32 @@ export function clearCloneFolder(keep: boolean = true): void {

function getIconName(baseName: string, data: IconData): string {
let prefix = '';
let sufix = '';
let suffix = '';

if (data.type === Type.Folder) {
prefix = baseName === 'folder' ? '' : `folder-`;
prefix =
baseName === 'folder'
? ''
: baseName.startsWith('folder-')
? ''
: 'folder-';

switch (data.variant) {
case Variant.Base:
break;
case Variant.Open:
sufix = openedFolder;
suffix = openedFolder;
break;
case Variant.Light:
sufix = lightColorFileEnding;
suffix = lightColorFileEnding;
break;
case Variant.LightOpen:
sufix = `${openedFolder}${lightColorFileEnding}`;
suffix = `${openedFolder}${lightColorFileEnding}`;
break;
}
} else {
sufix = data.variant === Variant.Light ? lightColorFileEnding : '';
suffix = data.variant === Variant.Light ? lightColorFileEnding : '';
}

return `${prefix}${baseName}${sufix}`;
return `${prefix}${baseName}${suffix}`;
}
2 changes: 1 addition & 1 deletion src/icons/generator/clones/utils/cloning.ts
Expand Up @@ -28,7 +28,7 @@ export function readIcon(path: string, hash: string): string {
}

/** Clones an icon and changes its colors according to the clone options. */
export function cloneIcon(path: string, hash: string, color: string): string {
export function cloneIcon(path: string, color: string, hash = ''): string {
const baseContent = readIcon(path, hash);
const svg = parseSync(baseContent);
const replacements = replacementMap(color, getColorList(svg));
Expand Down
10 changes: 10 additions & 0 deletions src/icons/generator/constants.ts
Expand Up @@ -23,6 +23,16 @@ export const lightColorFileEnding: string = '_light';
*/
export const highContrastColorFileEnding: string = '_highContrast';

/**
* Pattern to match the file icon definition.
*/
export const cloneIconExtension: string = '.clone.svg';

/**
* User Defined Clones subfolder
*/
export const clonesFolder: string = 'clones/';

/**
* Pattern to match wildcards for custom file icon mappings.
*/
Expand Down
21 changes: 16 additions & 5 deletions src/icons/generator/fileGenerator.ts
Expand Up @@ -8,6 +8,7 @@ import {
IconJsonOptions,
} from '../../models/index';
import {
cloneIconExtension,
highContrastColorFileEnding,
iconFolderPath,
lightColorFileEnding,
Expand Down Expand Up @@ -38,20 +39,26 @@ export const loadFileIconDefinitions = (

allFileIcons.forEach((icon) => {
if (icon.disabled) return;
config = merge({}, config, setIconDefinition(config, icon.name));
const isClone = icon.clone !== undefined;
config = merge({}, config, setIconDefinition(config, icon.name, isClone));

if (icon.light) {
config = merge(
{},
config,
setIconDefinition(config, icon.name, lightColorFileEnding)
setIconDefinition(config, icon.name, isClone, lightColorFileEnding)
);
}
if (icon.highContrast) {
config = merge(
{},
config,
setIconDefinition(config, icon.name, highContrastColorFileEnding)
setIconDefinition(
config,
icon.name,
isClone,
highContrastColorFileEnding
)
);
}

Expand Down Expand Up @@ -79,7 +86,7 @@ export const loadFileIconDefinitions = (
config = merge(
{},
config,
setIconDefinition(config, fileIcons.defaultIcon.name)
setIconDefinition(config, fileIcons.defaultIcon.name, false)
);
config.file = fileIcons.defaultIcon.name;

Expand All @@ -90,6 +97,7 @@ export const loadFileIconDefinitions = (
setIconDefinition(
config,
fileIcons.defaultIcon.name,
false,
lightColorFileEnding
)
);
Expand All @@ -105,6 +113,7 @@ export const loadFileIconDefinitions = (
setIconDefinition(
config,
fileIcons.defaultIcon.name,
false,
highContrastColorFileEnding
)
);
Expand Down Expand Up @@ -187,13 +196,15 @@ const disableIconsByPack = (
const setIconDefinition = (
config: IconConfiguration,
iconName: string,
isClone: boolean,
appendix: string = ''
) => {
const obj: Partial<IconConfiguration> = { iconDefinitions: {} };
const ext = isClone ? cloneIconExtension : '.svg';
if (config.options) {
const fileConfigHash = getFileConfigHash(config.options);
obj.iconDefinitions![`${iconName}${appendix}`] = {
iconPath: `${iconFolderPath}${iconName}${appendix}${fileConfigHash}.svg`,
iconPath: `${iconFolderPath}${iconName}${appendix}${fileConfigHash}${ext}`,
};
}
return obj;
Expand Down

0 comments on commit 400d0cf

Please sign in to comment.