2
0
mirror of https://github.com/iconify/iconify.git synced 2025-01-06 07:20:40 +00:00

Rewite icon set functions to allow deep aliases tree

This commit is contained in:
Vjacheslav Trushkin 2022-06-20 20:00:56 +03:00
parent e6336fe3da
commit 89a2def126
9 changed files with 156 additions and 172 deletions

View File

@ -11,10 +11,11 @@ import {
*/
export function mergeCustomisations<T extends FullIconCustomisations>(
defaults: T,
item: IconifyIconCustomisations
item: IconifyIconCustomisations,
keepOtherProps = true
): T {
// Merge transformations
const result = mergeIconTransformations(defaults, item);
const result = mergeIconTransformations(defaults, item, keepOtherProps);
// Merge dimensions
for (const key in defaultIconSizeCustomisations) {

View File

@ -1,7 +1,8 @@
import type { IconifyDimenisons, IconifyJSON } from '@iconify/types';
import { defaultIconProps, defaultIconDimensions } from '../icon/defaults';
import type { IconifyJSON } from '@iconify/types';
import { defaultIconProps } from '../icon/defaults';
import type { IconifyIcon, FullIconifyIcon } from '../icon/defaults';
import { mergeIconData } from '../icon/merge';
import { getIconsTree } from './tree';
/**
* Get data for icon
@ -21,54 +22,43 @@ export function getIconData(
name: string,
full = false
): FullIconifyIcon | IconifyIcon | null {
function getIcon(name: string, iteration: number): IconifyIcon | null {
if (data.icons[name] !== void 0) {
// Return icon
return Object.assign({}, data.icons[name]);
}
const icons = data.icons;
const aliases = data.aliases || {};
// Check loop
if (iteration > 5) {
let currentProps = {} as IconifyIcon;
// Parse parent item
function parse(name: string) {
currentProps = mergeIconData(
icons[name] || aliases[name],
currentProps,
false
);
}
const icon = icons[name];
if (icon) {
// Parse only icon
parse(name);
} else {
// Resolve tree
const tree = getIconsTree(data, [name])[name];
if (!tree) {
return null;
}
// Check if alias exists
const aliases = data.aliases;
if (aliases && aliases[name] !== void 0) {
const item = aliases[name];
const result = getIcon(item.parent, iteration + 1);
if (result) {
return mergeIconData(result, item);
}
return result;
}
// Check if character exists
const chars = data.chars;
if (!iteration && chars && chars[name] !== void 0) {
return getIcon(chars[name], iteration + 1);
}
return null;
parse(name);
tree.forEach(parse);
}
const result = getIcon(name, 0);
// Add default properties
if (result) {
for (const key in defaultIconDimensions) {
if (
result[key as keyof IconifyDimenisons] === void 0 &&
data[key as keyof IconifyDimenisons] !== void 0
) {
(result as unknown as Record<string, unknown>)[key] =
data[key as keyof IconifyDimenisons];
}
}
}
// Add default values
currentProps = mergeIconData(
data,
currentProps,
false
) as unknown as IconifyIcon;
// Return icon
return result && full
? Object.assign({}, defaultIconProps, result)
: result;
return full
? Object.assign({}, defaultIconProps, currentProps)
: currentProps;
}

View File

@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { IconifyJSON } from '@iconify/types';
import { defaultIconProps } from '../icon/defaults';
import type { IconifyAliases, IconifyJSON } from '@iconify/types';
import { defaultIconDimensions } from '../icon/defaults';
import { getIconsTree } from './tree';
/**
* Optional properties that must be copied when copying icon set
*/
export const propsToCopy = Object.keys(defaultIconProps).concat([
export const propsToCopy = Object.keys(defaultIconDimensions).concat([
'provider',
]) as (keyof IconifyJSON)[];
@ -14,61 +14,43 @@ export const propsToCopy = Object.keys(defaultIconProps).concat([
*/
export function getIcons(
data: IconifyJSON,
icons: string[],
names: string[],
not_found?: boolean
): IconifyJSON | null {
const icons = Object.create(null) as IconifyJSON['icons'];
const aliases = Object.create(null) as IconifyAliases;
const result: IconifyJSON = {
prefix: data.prefix,
icons: Object.create(null) as never,
icons,
};
const tested: Set<string> = new Set();
const sourceIcons = data.icons;
const sourceAliases = data.aliases || {};
const tree = getIconsTree(data, names);
let empty = true;
function copy(name: string, iteration: number): boolean {
if (iteration > 5 || tested.has(name)) {
// Already copied or too much nesting
return true;
}
tested.add(name);
// Check for icon
if (data.icons[name] !== void 0) {
// Copy all icons
for (const name in tree) {
if (!tree[name]) {
// Failed
if (not_found && names.indexOf(name) !== -1) {
// Add to not_found
(result.not_found || (result.not_found = [])).push(name);
}
} else if (sourceIcons[name]) {
// Icon
icons[name] = {
...sourceIcons[name],
};
empty = false;
result.icons[name] = { ...data.icons[name] };
return true;
} else {
// Alias
aliases[name] = {
...sourceAliases[name],
};
result.aliases = aliases;
}
// Check for alias
const aliases = data.aliases;
if (aliases && aliases[name] !== void 0) {
const copied = copy(aliases[name].parent, iteration + 1);
if (copied) {
if (result.aliases === void 0) {
result.aliases = Object.create(null) as never;
}
result.aliases[name] = { ...aliases[name] };
}
return copied;
}
// Check for character, return as alias
const chars = data.chars;
if (chars && chars[name] !== void 0) {
const parent = chars[name];
const copied = copy(parent, iteration + 1);
if (copied) {
if (result.aliases === void 0) {
result.aliases = Object.create(null) as never;
}
result.aliases[name] = {
parent,
};
}
return copied;
}
// Not found
return false;
}
// Copy common properties
@ -78,15 +60,5 @@ export function getIcons(
}
});
// Copy all icons
icons.forEach((name) => {
if (!copy(name, 0) && not_found === true) {
if (result.not_found === void 0) {
result.not_found = [];
}
result.not_found.push(name);
}
});
return empty && not_found !== true ? null : result;
}

View File

@ -11,18 +11,20 @@ export type ParentIconsTree = Record<string, ParentIconsList | null>;
*
* Returns parent icon for each icon
*/
export function getIconsTree(data: IconifyJSON): ParentIconsTree {
const resolved = Object.create(null) as ParentIconsTree;
// Add all icons
for (const key in data.icons) {
resolved[key] = [];
}
// Add all aliases
export function getIconsTree(
data: IconifyJSON,
names?: string[]
): ParentIconsTree {
const icons = data.icons;
const aliases = data.aliases || {};
function resolveAlias(name: string): ParentIconsList | null {
const resolved = Object.create(null) as ParentIconsTree;
function resolve(name: string): ParentIconsList | null {
if (icons[name]) {
return (resolved[name] = []);
}
if (resolved[name] === void 0) {
// Mark as failed if parent alias points to this icon to avoid infinite loop
resolved[name] = null;
@ -31,7 +33,7 @@ export function getIconsTree(data: IconifyJSON): ParentIconsTree {
const parent = aliases[name] && aliases[name].parent;
// Get value for parent
const value = parent && resolveAlias(parent);
const value = parent && resolve(parent);
if (value) {
resolved[name] = [parent].concat(value);
}
@ -40,9 +42,8 @@ export function getIconsTree(data: IconifyJSON): ParentIconsTree {
return resolved[name];
}
for (const name in aliases) {
resolveAlias(name);
}
// Resolve only required icons
(names || Object.keys(aliases).concat(Object.keys(icons))).forEach(resolve);
return resolved;
}

View File

@ -1,22 +1,43 @@
import type { IconifyDimenisons, IconifyOptional } from '@iconify/types';
import type {
IconifyDimenisons,
IconifyIcon,
IconifyOptional,
IconifyTransformations,
} from '@iconify/types';
import { defaultIconDimensions } from './defaults';
import { mergeIconTransformations } from './transformations';
// Props to copy: all icon properties, except transformations
type PropsToCopy = Omit<IconifyIcon, keyof IconifyTransformations>;
const propsToMerge: Required<PropsToCopy> = {
...defaultIconDimensions,
body: '',
};
/**
* Merge icon and alias
*
* Can also be used to merge default values and icon
*/
export function mergeIconData<T extends IconifyOptional>(
icon: T,
alias: IconifyOptional
parent: T,
child: IconifyOptional | IconifyIcon,
keepOtherParentProps = true
): T {
// Merge transformations, while keeping other props
const result = mergeIconTransformations(icon, alias);
// Merge transformations
const result = mergeIconTransformations(
parent,
child,
keepOtherParentProps
);
// Merge dimensions
for (const key in defaultIconDimensions) {
// Merge icon properties that aren't transformations
for (const key in propsToMerge) {
const prop = key as keyof IconifyDimenisons;
if (alias[prop] !== void 0) {
result[prop] = alias[prop];
if (child[prop] !== void 0) {
result[prop] = child[prop];
} else if (parent[prop] !== void 0) {
result[prop] = parent[prop];
}
}

View File

@ -1,23 +1,22 @@
import type { IconifyTransformations } from '@iconify/types';
/**
* Merge transformations. Also copies other properties from first parameter
* Merge transformations
*/
export function mergeIconTransformations<T extends IconifyTransformations>(
obj1: T,
obj2: IconifyTransformations
obj2: IconifyTransformations,
keepOtherProps = true
): T {
const result = {
...obj1,
};
if (obj2.hFlip) {
result.hFlip = !result.hFlip;
const result = keepOtherProps ? { ...obj1 } : ({} as T);
if (obj1.hFlip || obj2.hFlip) {
result.hFlip = obj1.hFlip !== obj2.hFlip;
}
if (obj2.vFlip) {
result.vFlip = !result.vFlip;
if (obj1.vFlip || obj2.vFlip) {
result.vFlip = obj1.vFlip !== obj2.vFlip;
}
if (obj2.rotate) {
result.rotate = ((result.rotate || 0) + obj2.rotate) % 4;
if (obj1.rotate || obj2.rotate) {
result.rotate = ((obj1.rotate || 0) + (obj2.rotate || 0)) % 4;
}
return result;
}

View File

@ -33,3 +33,20 @@ export function unmergeObjects<T extends Record<string, unknown>>(
}
return result;
}
/**
* Get common properties in 2 objects
*/
export function commonObjectProps<T extends Record<string, unknown>>(
item: unknown,
reference: T
): Partial<T> {
const result = {} as T;
for (const key in reference) {
const value = (item as T)[key];
if (value !== void 0) {
result[key] = value;
}
}
return result;
}

View File

@ -137,38 +137,10 @@ describe('Testing retrieving icons from icon set', () => {
});
// Character
expect(getIcons(data, ['f00'])).toEqual({
prefix: 'foo',
icons: {
bar2: {
body: '<g />',
},
},
aliases: {
f00: {
parent: 'bar2',
},
},
});
expect(getIcons(data, ['f00'])).toBeNull();
// Character that points to alias
expect(getIcons(data, ['f02'])).toEqual({
prefix: 'foo',
icons: {
bar: {
body: '<g />',
},
},
aliases: {
f02: {
parent: 'foo',
},
foo: {
parent: 'bar',
hFlip: true,
},
},
});
expect(getIcons(data, ['f02'])).toBeNull();
// Bad character
expect(getIcons(data, ['f04'])).toBeNull();

View File

@ -173,6 +173,7 @@ describe('Testing parsing icon set', () => {
'alias2z4',
'alias2z5',
'alias2z6',
'alias2z7',
];
const namesCopy = names.slice(0);
@ -275,6 +276,17 @@ describe('Testing parsing icon set', () => {
vFlip: true,
rotate: 1,
},
alias2z7: {
// alias of alias2z6
body: '<path d="icon2" />',
width: 21,
height: 24,
top: 0,
left: 0,
hFlip: false,
vFlip: true,
rotate: 1,
},
};
// Do stuff
@ -333,7 +345,6 @@ describe('Testing parsing icon set', () => {
},
alias2z7: {
// 7 parents: alias2z6, alias2z5, alias2z4, alias2z3, alias2z, alias2f, icon2
// nesting is too deep and should not be parsed
parent: 'alias2z6',
},
alias3: {