mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-31 08:46:58 +02:00
feat: Roll out Lucide icons to Nodes, remove FontAwesome icons (#20477)
Co-authored-by: Elias Meire <elias@meire.dev>
This commit is contained in:
parent
18a871efe8
commit
596cdfec91
|
|
@ -64,7 +64,6 @@ export const frontendConfig = tseslint.config(
|
|||
'error',
|
||||
{
|
||||
ignorePatterns: [
|
||||
'FontAwesomeIcon', // Globally registered in plugins/icons/index.ts
|
||||
'RouterLink', // Vue Router global component
|
||||
'RouterView', // Vue Router global component
|
||||
'Teleport', // Vue 3 built-in
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import { fas } from '@fortawesome/free-solid-svg-icons';
|
||||
import { sharedTags } from '@n8n/storybook/main';
|
||||
import { withThemeByDataAttribute } from '@storybook/addon-themes';
|
||||
import { setup } from '@storybook/vue3';
|
||||
|
|
@ -13,8 +11,6 @@ import './storybook.scss';
|
|||
// import '../src/css/tailwind/index.css';
|
||||
|
||||
setup((app) => {
|
||||
library.add(fas);
|
||||
|
||||
app.use(ElementPlus, {
|
||||
locale: lang,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -50,9 +50,6 @@
|
|||
"@vue/test-utils": "catalog:frontend"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.3",
|
||||
"@n8n/composables": "workspace:*",
|
||||
"@n8n/utils": "workspace:*",
|
||||
"@tanstack/vue-table": "^8.21.2",
|
||||
|
|
|
|||
|
|
@ -5,21 +5,6 @@ import { createRouter, createWebHistory } from 'vue-router';
|
|||
import IconPicker from '.';
|
||||
import { ALL_ICON_PICKER_ICONS } from './constants';
|
||||
|
||||
// Create a proxy handler that returns a mock icon object for any icon name
|
||||
// and mock the entire icon library with the proxy
|
||||
vi.mock(
|
||||
'@fortawesome/free-solid-svg-icons',
|
||||
() =>
|
||||
new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_target, prop) => {
|
||||
return { prefix: 'fas', iconName: prop.toString().replace('fa', '').toLowerCase() };
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import N8nNodeIcon from '.';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import type { IconName } from '../N8nIcon/icons';
|
||||
import { isSupportedIconName } from '../N8nIcon/icons';
|
||||
|
||||
type IconType = 'file' | 'icon' | 'unknown';
|
||||
|
||||
interface IconContentProps {
|
||||
type: IconType;
|
||||
src?: string;
|
||||
name?: string;
|
||||
nodeTypeName?: string;
|
||||
size?: number;
|
||||
badge?: { src: string; type: IconType };
|
||||
}
|
||||
|
||||
const props = defineProps<IconContentProps>();
|
||||
|
||||
const badgeSize = computed((): number => {
|
||||
switch (props.size) {
|
||||
case 40:
|
||||
return 18;
|
||||
case 24:
|
||||
return 10;
|
||||
case 18:
|
||||
default:
|
||||
return 12;
|
||||
}
|
||||
});
|
||||
|
||||
const fontStyleData = computed((): Record<string, string> => {
|
||||
if (!props.size) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
'max-width': `${props.size}px`,
|
||||
};
|
||||
});
|
||||
|
||||
const badgeStyleData = computed((): Record<string, string> => {
|
||||
const size = badgeSize.value;
|
||||
return {
|
||||
padding: `${Math.floor(size / 4)}px`,
|
||||
right: `-${Math.floor(size / 2)}px`,
|
||||
bottom: `-${Math.floor(size / 2)}px`,
|
||||
};
|
||||
});
|
||||
|
||||
const supportedIconName = computed((): IconName | undefined => {
|
||||
return isSupportedIconName(props.name) ? props.name : undefined;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="type !== 'unknown'" :class="$style.icon">
|
||||
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
|
||||
<N8nIcon v-else-if="supportedIconName" :icon="supportedIconName" :style="fontStyleData" />
|
||||
<div v-else :class="$style.nodeIconPlaceholder">
|
||||
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
|
||||
</div>
|
||||
|
||||
<!-- Badge icon, for example used for HTTP based nodes -->
|
||||
<div v-if="badge" :class="$style.badge" :style="badgeStyleData">
|
||||
<N8nNodeIcon :type="badge.type" :src="badge.src" :size="badgeSize" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else :class="$style.nodeIconPlaceholder">
|
||||
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.icon {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
svg {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.nodeIconPlaceholder {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nodeIconImage {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
background: var(--color-background-node-icon-badge, var(--color--background));
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
import { render } from '@testing-library/vue';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import NodeIcon from './NodeIcon.vue';
|
||||
|
||||
// Mock N8nTooltip component
|
||||
vi.mock('../N8nTooltip', () => ({
|
||||
default: {
|
||||
name: 'N8nTooltip',
|
||||
template: '<div data-testid="tooltip"><slot /></div>',
|
||||
props: ['placement', 'disabled'],
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock N8nIcon component
|
||||
vi.mock('../N8nIcon', () => ({
|
||||
default: {
|
||||
name: 'N8nIcon',
|
||||
template: '<span data-testid="n8n-icon" :data-icon="icon"></span>',
|
||||
props: ['icon'],
|
||||
},
|
||||
}));
|
||||
|
||||
describe('NodeIcon', () => {
|
||||
describe('icon rendering', () => {
|
||||
it('renders file type icon with image', () => {
|
||||
const { container } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'file',
|
||||
src: 'https://example.com/icon.png',
|
||||
},
|
||||
});
|
||||
|
||||
const img = container.querySelector('img');
|
||||
expect(img).toBeTruthy();
|
||||
expect(img?.getAttribute('src')).toBe('https://example.com/icon.png');
|
||||
});
|
||||
|
||||
it('renders icon type with supported icon name', () => {
|
||||
const { container } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'icon',
|
||||
name: 'check',
|
||||
},
|
||||
});
|
||||
|
||||
// N8nIcon should be rendered (via IconContent)
|
||||
const icon = container.querySelector('[data-testid="n8n-icon"]');
|
||||
expect(icon).toBeTruthy();
|
||||
expect(icon?.getAttribute('data-icon')).toBe('check');
|
||||
});
|
||||
|
||||
it('renders unknown type with placeholder', () => {
|
||||
const { getByText } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'unknown',
|
||||
nodeTypeName: 'TestNode',
|
||||
},
|
||||
});
|
||||
|
||||
expect(getByText('T')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders unknown type with question mark when no nodeTypeName', () => {
|
||||
const { getByText } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'unknown',
|
||||
},
|
||||
});
|
||||
|
||||
expect(getByText('?')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('styling', () => {
|
||||
it('applies custom size to wrapper', () => {
|
||||
const { container } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'file',
|
||||
src: 'test.png',
|
||||
size: 40,
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = container.querySelector('[class*="nodeIconWrapper"]');
|
||||
expect(wrapper).toBeTruthy();
|
||||
const style = (wrapper as HTMLElement).style;
|
||||
expect(style.width).toBe('40px');
|
||||
expect(style.height).toBe('40px');
|
||||
});
|
||||
|
||||
it('applies custom color', () => {
|
||||
const { container } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'file',
|
||||
src: 'test.png',
|
||||
color: '#ff0000',
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = container.querySelector('[class*="nodeIconWrapper"]');
|
||||
const style = (wrapper as HTMLElement).style;
|
||||
expect(style.color).toBe('rgb(255, 0, 0)');
|
||||
});
|
||||
|
||||
it('applies circle class when circle prop is true', () => {
|
||||
const { container } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'file',
|
||||
src: 'test.png',
|
||||
circle: true,
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = container.querySelector('[class*="nodeIconWrapper"]');
|
||||
expect(wrapper?.className).toContain('circle');
|
||||
});
|
||||
|
||||
it('applies disabled class when disabled prop is true', () => {
|
||||
const { container } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'file',
|
||||
src: 'test.png',
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = container.querySelector('[class*="nodeIconWrapper"]');
|
||||
expect(wrapper?.className).toContain('disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('tooltip', () => {
|
||||
it('renders tooltip when showTooltip is true', () => {
|
||||
const { container } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'file',
|
||||
src: 'test.png',
|
||||
showTooltip: true,
|
||||
nodeTypeName: 'Test Node',
|
||||
},
|
||||
});
|
||||
|
||||
const tooltip = container.querySelector('[data-testid="tooltip"]');
|
||||
expect(tooltip).toBeTruthy();
|
||||
});
|
||||
|
||||
it('does not render tooltip when showTooltip is false', () => {
|
||||
const { container } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'file',
|
||||
src: 'test.png',
|
||||
showTooltip: false,
|
||||
},
|
||||
});
|
||||
|
||||
const tooltip = container.querySelector('[data-testid="tooltip"]');
|
||||
expect(tooltip).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('badge', () => {
|
||||
it('renders badge when badge prop is provided', () => {
|
||||
const { container } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'file',
|
||||
src: 'test.png',
|
||||
size: 40,
|
||||
badge: {
|
||||
type: 'file',
|
||||
src: 'badge.png',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const badge = container.querySelector('[class*="badge"]');
|
||||
expect(badge).toBeTruthy();
|
||||
});
|
||||
|
||||
it('does not render badge when badge prop is not provided', () => {
|
||||
const { container } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'file',
|
||||
src: 'test.png',
|
||||
},
|
||||
});
|
||||
|
||||
const badge = container.querySelector('[class*="badge"]');
|
||||
expect(badge).toBeNull();
|
||||
});
|
||||
|
||||
it('renders placeholder when icon name is not supported', () => {
|
||||
const { getByText } = render(NodeIcon, {
|
||||
props: {
|
||||
type: 'icon',
|
||||
name: 'unsupported-icon-name',
|
||||
nodeTypeName: 'CustomNode',
|
||||
},
|
||||
});
|
||||
|
||||
expect(getByText('C')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
<script lang="ts" setup>
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import type { Placement } from 'element-plus';
|
||||
import { computed, getCurrentInstance } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import type { IconName } from '../N8nIcon/icons';
|
||||
import { isSupportedIconName } from '../N8nIcon/icons';
|
||||
import N8nTooltip from '../N8nTooltip';
|
||||
import IconContent from './IconContent.vue';
|
||||
|
||||
type IconType = 'file' | 'icon' | 'unknown';
|
||||
|
||||
|
|
@ -22,8 +19,6 @@ interface NodeIconProps {
|
|||
showTooltip?: boolean;
|
||||
tooltipPosition?: Placement;
|
||||
badge?: { src: string; type: IconType };
|
||||
// temporarily until we roll out FA icons for all nodes
|
||||
useUpdatedIcons?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<NodeIconProps>(), {
|
||||
|
|
@ -45,44 +40,6 @@ const iconStyleData = computed((): Record<string, string> => {
|
|||
'line-height': `${props.size}px`,
|
||||
};
|
||||
});
|
||||
|
||||
const badgeSize = computed((): number => {
|
||||
switch (props.size) {
|
||||
case 40:
|
||||
return 18;
|
||||
case 24:
|
||||
return 10;
|
||||
case 18:
|
||||
default:
|
||||
return 12;
|
||||
}
|
||||
});
|
||||
|
||||
const fontStyleData = computed((): Record<string, string> => {
|
||||
if (!props.size) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
'max-width': `${props.size}px`,
|
||||
};
|
||||
});
|
||||
|
||||
const badgeStyleData = computed((): Record<string, string> => {
|
||||
const size = badgeSize.value;
|
||||
return {
|
||||
padding: `${Math.floor(size / 4)}px`,
|
||||
right: `-${Math.floor(size / 2)}px`,
|
||||
bottom: `-${Math.floor(size / 2)}px`,
|
||||
};
|
||||
});
|
||||
|
||||
const updatedIconName = computed((): IconName | undefined => {
|
||||
return props.useUpdatedIcons && isSupportedIconName(props.name) ? props.name : undefined;
|
||||
});
|
||||
|
||||
// Get self component to avoid dependency cycle
|
||||
const N8nNodeIcon = getCurrentInstance()?.type;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -100,32 +57,24 @@ const N8nNodeIcon = getCurrentInstance()?.type;
|
|||
<template #content>
|
||||
{{ nodeTypeName }}
|
||||
</template>
|
||||
<div v-if="type !== 'unknown'" :class="$style.icon">
|
||||
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
|
||||
<FontAwesomeIcon v-else :icon="`${name}`" :class="$style.iconFa" :style="fontStyleData" />
|
||||
</div>
|
||||
<div v-else :class="$style.nodeIconPlaceholder">
|
||||
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
|
||||
</div>
|
||||
<IconContent
|
||||
:type="type"
|
||||
:src="src"
|
||||
:name="name"
|
||||
:node-type-name="nodeTypeName"
|
||||
:size="size"
|
||||
:badge="badge"
|
||||
/>
|
||||
</N8nTooltip>
|
||||
<template v-else>
|
||||
<div v-if="type !== 'unknown'" :class="$style.icon">
|
||||
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
|
||||
<N8nIcon
|
||||
v-else-if="updatedIconName"
|
||||
:icon="updatedIconName"
|
||||
:style="fontStyleData"
|
||||
size="xlarge"
|
||||
/>
|
||||
<FontAwesomeIcon v-else :icon="`${name}`" :style="fontStyleData" />
|
||||
<div v-if="badge" :class="$style.badge" :style="badgeStyleData">
|
||||
<N8nNodeIcon :type="badge.type" :src="badge.src" :size="badgeSize" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else :class="$style.nodeIconPlaceholder">
|
||||
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
|
||||
</div>
|
||||
</template>
|
||||
<IconContent
|
||||
v-else
|
||||
:type="type"
|
||||
:src="src"
|
||||
:name="name"
|
||||
:node-type-name="nodeTypeName"
|
||||
:size="size"
|
||||
:badge="badge"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -143,40 +92,6 @@ const N8nNodeIcon = getCurrentInstance()?.type;
|
|||
font-size: 20px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
svg {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.nodeIconPlaceholder {
|
||||
text-align: center;
|
||||
}
|
||||
.nodeIconImage {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
background: var(--color-background-node-icon-badge, var(--color--background));
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,11 +143,5 @@
|
|||
"vitest-mock-extended": "catalog:",
|
||||
"vue-tsc": "catalog:frontend",
|
||||
"z-vue-scan": "^0.0.35"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "*",
|
||||
"@fortawesome/free-regular-svg-icons": "*",
|
||||
"@fortawesome/free-solid-svg-icons": "*",
|
||||
"@fortawesome/vue-fontawesome": "*"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import type { Plugin } from 'vue';
|
|||
import { render, type RenderOptions as TestingLibraryRenderOptions } from '@testing-library/vue';
|
||||
import { i18nInstance } from '@n8n/i18n';
|
||||
import { GlobalDirectivesPlugin } from '@/plugins/directives';
|
||||
import { FontAwesomePlugin } from '@/plugins/icons';
|
||||
import { N8nPlugin } from '@n8n/design-system';
|
||||
import type { Pinia } from 'pinia';
|
||||
import { PiniaVuePlugin } from 'pinia';
|
||||
|
|
@ -33,14 +32,7 @@ const defaultOptions = {
|
|||
},
|
||||
VueJsonPretty: vueJsonPretty,
|
||||
},
|
||||
plugins: [
|
||||
i18nInstance,
|
||||
PiniaVuePlugin,
|
||||
FontAwesomePlugin,
|
||||
N8nPlugin,
|
||||
GlobalDirectivesPlugin,
|
||||
TelemetryPlugin,
|
||||
],
|
||||
plugins: [i18nInstance, PiniaVuePlugin, N8nPlugin, GlobalDirectivesPlugin, TelemetryPlugin],
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,15 @@ describe('CredentialIcon', () => {
|
|||
const renderComponent = createComponentRenderer(CredentialIcon, {
|
||||
pinia: createTestingPinia(),
|
||||
global: {
|
||||
stubs: ['N8nTooltip'],
|
||||
stubs: {
|
||||
N8nTooltip: true,
|
||||
N8nNodeIcon: {
|
||||
template: `
|
||||
<svg v-if="type === 'icon'" class="n8n-icon" :data-icon="name"></svg>
|
||||
`,
|
||||
props: ['type', 'src', 'name', 'color', 'size'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
let pinia: TestingPinia;
|
||||
|
|
@ -50,16 +58,16 @@ describe('CredentialIcon', () => {
|
|||
}),
|
||||
]);
|
||||
|
||||
const { getByRole } = renderComponent({
|
||||
const { container } = renderComponent({
|
||||
pinia,
|
||||
props: {
|
||||
credentialTypeName: 'test',
|
||||
},
|
||||
});
|
||||
|
||||
const icon = getByRole('img', { hidden: true });
|
||||
expect(icon.tagName).toBe('svg');
|
||||
expect(icon).toHaveClass('fa-clock');
|
||||
const icon = container.querySelector('.n8n-icon');
|
||||
expect(icon).toBeInTheDocument();
|
||||
expect(icon?.getAttribute('data-icon')).toBe('clock');
|
||||
});
|
||||
|
||||
it('shows correct icon when credential has an icon with node: prefix', () => {
|
||||
|
|
|
|||
|
|
@ -19,13 +19,7 @@ defineProps<Props>();
|
|||
:show-action-arrow="true"
|
||||
>
|
||||
<template #icon>
|
||||
<N8nNodeIcon
|
||||
type="icon"
|
||||
:name="link.icon"
|
||||
:circle="false"
|
||||
:show-tooltip="false"
|
||||
:use-updated-icons="true"
|
||||
/>
|
||||
<N8nNodeIcon type="icon" :name="link.icon" :circle="false" :show-tooltip="false" />
|
||||
</template>
|
||||
</N8nNodeCreatorNode>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -20,13 +20,7 @@ defineProps<Props>();
|
|||
:is-trigger="false"
|
||||
>
|
||||
<template v-if="openTemplate.icon" #icon>
|
||||
<N8nNodeIcon
|
||||
type="icon"
|
||||
:name="openTemplate.icon"
|
||||
:circle="false"
|
||||
:show-tooltip="false"
|
||||
:use-updated-icons="true"
|
||||
/>
|
||||
<N8nNodeIcon type="icon" :name="openTemplate.icon" :circle="false" :show-tooltip="false" />
|
||||
</template>
|
||||
|
||||
<template v-if="openTemplate.nodes" #extraDetails>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ const subcategoryName = computed(() => camelCase(props.item.subcategory || props
|
|||
:name="item.icon"
|
||||
:circle="false"
|
||||
:show-tooltip="false"
|
||||
:use-updated-icons="true"
|
||||
v-bind="item.iconProps"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -19,13 +19,7 @@ defineProps<Props>();
|
|||
:show-action-arrow="true"
|
||||
>
|
||||
<template #icon>
|
||||
<N8nNodeIcon
|
||||
type="icon"
|
||||
:name="view.icon"
|
||||
:circle="false"
|
||||
:show-tooltip="false"
|
||||
:use-updated-icons="true"
|
||||
/>
|
||||
<N8nNodeIcon type="icon" :name="view.icon" :circle="false" :show-tooltip="false" />
|
||||
</template>
|
||||
</N8nNodeCreatorNode>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -194,7 +194,6 @@ function onBackButton() {
|
|||
:circle="false"
|
||||
:show-tooltip="false"
|
||||
:size="20"
|
||||
:use-updated-icons="true"
|
||||
/>
|
||||
<p v-if="activeViewStack.title" :class="$style.title" v-text="activeViewStack.title" />
|
||||
|
||||
|
|
@ -286,6 +285,7 @@ function onBackButton() {
|
|||
}
|
||||
.nodeIcon {
|
||||
--node-icon-size: 20px;
|
||||
--node-icon-color: var(--color--text);
|
||||
margin-right: var(--spacing--sm);
|
||||
}
|
||||
.renderedItems {
|
||||
|
|
|
|||
|
|
@ -71,8 +71,7 @@ const activeSettings = computed(() => {
|
|||
<div v-if="activeSettings.length > 0" :class="$style.settingsHint">
|
||||
<div v-for="setting in activeSettings" :key="setting.key" :class="$style.settingItem">
|
||||
<div :class="$style.iconWrapper">
|
||||
<FontAwesomeIcon v-if="setting.icon === 'power'" icon="power" :class="$style.icon" />
|
||||
<N8nIcon v-else :icon="setting.icon" :class="$style.icon" />
|
||||
<N8nIcon :icon="setting.icon" :class="$style.icon" />
|
||||
</div>
|
||||
<N8nText size="small" :class="$style.message">
|
||||
{{ setting.message }}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { N8nIcon } from '@n8n/design-system';
|
||||
import Draggable from './Draggable.vue';
|
||||
import type { XYPosition } from '@/Interface';
|
||||
|
||||
|
|
@ -38,9 +39,9 @@ const onDragStart = () => {
|
|||
>
|
||||
<template #default="{ isDragging }">
|
||||
<button :class="[$style.dragButton, { [$style.dragging]: isDragging }]">
|
||||
<FontAwesomeIcon v-if="canMoveLeft" :class="$style.arrow" icon="arrow-left" />
|
||||
<FontAwesomeIcon :class="$style.handle" icon="bars" />
|
||||
<FontAwesomeIcon v-if="canMoveRight" :class="$style.arrow" icon="arrow-right" />
|
||||
<N8nIcon v-if="canMoveLeft" :class="$style.arrow" icon="arrow-left" />
|
||||
<N8nIcon :class="$style.handle" icon="menu" />
|
||||
<N8nIcon v-if="canMoveRight" :class="$style.arrow" icon="arrow-right" />
|
||||
</button>
|
||||
</template>
|
||||
</Draggable>
|
||||
|
|
@ -65,7 +66,7 @@ const onDragStart = () => {
|
|||
|
||||
.arrow {
|
||||
opacity: 0;
|
||||
width: 7px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.handle {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ const renderComponent = createComponentRenderer(ResourceLocator, {
|
|||
ExpressionParameterInput: true,
|
||||
ParameterIssues: true,
|
||||
N8nCallout: true,
|
||||
FontAwesomeIcon: true,
|
||||
FromAiOverrideField: true,
|
||||
FromAiOverrideButton: true,
|
||||
ParameterOverrideSelectableList: true,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import type { usePinnedData } from '@/composables/usePinnedData';
|
|||
|
||||
const renderComponent = createComponentRenderer(RunDataPinButton, {
|
||||
global: {
|
||||
stubs: ['FontAwesomeIcon'],
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
initialState: {
|
||||
|
|
|
|||
|
|
@ -210,6 +210,10 @@ describe('VirtualSchema.vue', () => {
|
|||
DynamicScrollerItem: DynamicScrollerItemStub,
|
||||
N8nIcon: true,
|
||||
Notice: NoticeStub,
|
||||
NodeIcon: {
|
||||
template: '<node-icon-stub :node-type="nodeType.name" :size="size"></node-icon-stub>',
|
||||
props: ['node-type', 'size'],
|
||||
},
|
||||
},
|
||||
},
|
||||
pinia,
|
||||
|
|
|
|||
|
|
@ -45,28 +45,12 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
|
|||
spin="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon nodeIcon icon"
|
||||
<node-icon-stub
|
||||
class="icon"
|
||||
data-v-882a318e=""
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 12px; height: 12px; font-size: 12px; line-height: 12px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="icon"
|
||||
>
|
||||
<img
|
||||
class="nodeIconImage"
|
||||
src="/nodes/test-node/icon.svg"
|
||||
/>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
node-type="n8n-nodes-base.set"
|
||||
size="12"
|
||||
/>
|
||||
<div
|
||||
class="title"
|
||||
data-v-882a318e=""
|
||||
|
|
@ -333,28 +317,12 @@ exports[`VirtualSchema.vue > renders previous nodes schema for AI tools 1`] = `
|
|||
spin="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon nodeIcon icon"
|
||||
<node-icon-stub
|
||||
class="icon"
|
||||
data-v-882a318e=""
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 12px; height: 12px; font-size: 12px; line-height: 12px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="icon"
|
||||
>
|
||||
<img
|
||||
class="nodeIconImage"
|
||||
src="/nodes/test-node/icon.svg"
|
||||
/>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
node-type="n8n-nodes-base.if"
|
||||
size="12"
|
||||
/>
|
||||
<div
|
||||
class="title"
|
||||
data-v-882a318e=""
|
||||
|
|
@ -418,28 +386,12 @@ exports[`VirtualSchema.vue > renders schema for empty objects and arrays 1`] = `
|
|||
spin="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon icon-trigger nodeIcon icon icon-trigger"
|
||||
<node-icon-stub
|
||||
class="icon icon-trigger"
|
||||
data-v-882a318e=""
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 12px; height: 12px; font-size: 12px; line-height: 12px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="icon"
|
||||
>
|
||||
<img
|
||||
class="nodeIconImage"
|
||||
src="/nodes/test-node/icon.svg"
|
||||
/>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
node-type="n8n-nodes-base.manualTrigger"
|
||||
size="12"
|
||||
/>
|
||||
<div
|
||||
class="title"
|
||||
data-v-882a318e=""
|
||||
|
|
@ -1309,28 +1261,12 @@ exports[`VirtualSchema.vue > renders schema with spaces and dots 1`] = `
|
|||
spin="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon icon-trigger nodeIcon icon icon-trigger"
|
||||
<node-icon-stub
|
||||
class="icon icon-trigger"
|
||||
data-v-882a318e=""
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 12px; height: 12px; font-size: 12px; line-height: 12px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="icon"
|
||||
>
|
||||
<img
|
||||
class="nodeIconImage"
|
||||
src="/nodes/test-node/icon.svg"
|
||||
/>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
node-type="n8n-nodes-base.manualTrigger"
|
||||
size="12"
|
||||
/>
|
||||
<div
|
||||
class="title"
|
||||
data-v-882a318e=""
|
||||
|
|
@ -1713,28 +1649,12 @@ exports[`VirtualSchema.vue > renders schema with spaces and dots 1`] = `
|
|||
spin="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon nodeIcon icon"
|
||||
<node-icon-stub
|
||||
class="icon"
|
||||
data-v-882a318e=""
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 12px; height: 12px; font-size: 12px; line-height: 12px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="icon"
|
||||
>
|
||||
<img
|
||||
class="nodeIconImage"
|
||||
src="/nodes/test-node/icon.svg"
|
||||
/>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
node-type="n8n-nodes-base.set"
|
||||
size="12"
|
||||
/>
|
||||
<div
|
||||
class="title"
|
||||
data-v-882a318e=""
|
||||
|
|
@ -1845,28 +1765,12 @@ exports[`VirtualSchema.vue > renders variables and context section 1`] = `
|
|||
spin="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon icon-trigger nodeIcon icon icon-trigger"
|
||||
<node-icon-stub
|
||||
class="icon icon-trigger"
|
||||
data-v-882a318e=""
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 12px; height: 12px; font-size: 12px; line-height: 12px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="icon"
|
||||
>
|
||||
<img
|
||||
class="nodeIconImage"
|
||||
src="/nodes/test-node/icon.svg"
|
||||
/>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
node-type="n8n-nodes-base.manualTrigger"
|
||||
size="12"
|
||||
/>
|
||||
<div
|
||||
class="title"
|
||||
data-v-882a318e=""
|
||||
|
|
@ -2669,28 +2573,12 @@ exports[`VirtualSchema.vue > should expand all nodes when searching 1`] = `
|
|||
spin="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon icon-trigger nodeIcon icon icon-trigger"
|
||||
<node-icon-stub
|
||||
class="icon icon-trigger"
|
||||
data-v-882a318e=""
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 12px; height: 12px; font-size: 12px; line-height: 12px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="icon"
|
||||
>
|
||||
<img
|
||||
class="nodeIconImage"
|
||||
src="/nodes/test-node/icon.svg"
|
||||
/>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
node-type="n8n-nodes-base.manualTrigger"
|
||||
size="12"
|
||||
/>
|
||||
<div
|
||||
class="title"
|
||||
data-v-882a318e=""
|
||||
|
|
@ -2808,28 +2696,12 @@ exports[`VirtualSchema.vue > should expand all nodes when searching 1`] = `
|
|||
spin="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon nodeIcon icon"
|
||||
<node-icon-stub
|
||||
class="icon"
|
||||
data-v-882a318e=""
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 12px; height: 12px; font-size: 12px; line-height: 12px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="icon"
|
||||
>
|
||||
<img
|
||||
class="nodeIconImage"
|
||||
src="/nodes/test-node/icon.svg"
|
||||
/>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
node-type="n8n-nodes-base.set"
|
||||
size="12"
|
||||
/>
|
||||
<div
|
||||
class="title"
|
||||
data-v-882a318e=""
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ const renderComponent = createComponentRenderer(ExecutionsList, {
|
|||
params: {},
|
||||
},
|
||||
},
|
||||
stubs: ['FontAwesomeIcon'],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ const globalExecutionsListItemQueuedTooltipRenderSpy = vi.fn();
|
|||
const renderComponent = createComponentRenderer(GlobalExecutionsListItem, {
|
||||
global: {
|
||||
stubs: {
|
||||
FontAwesomeIcon: true,
|
||||
N8nTooltip: true,
|
||||
N8nButton: true,
|
||||
I18nT: true,
|
||||
|
|
|
|||
|
|
@ -9,8 +9,17 @@ import { fireEvent } from '@testing-library/vue';
|
|||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { createTestWorkflowObject } from '@/__tests__/mocks';
|
||||
|
||||
const stubs = {
|
||||
NodeIcon: {
|
||||
template:
|
||||
'<node-icon-stub :icon-source="iconSource" :size="size" :shrink="shrink" :disabled="disabled"></node-icon-stub>',
|
||||
props: ['icon-source', 'size', 'shrink', 'disabled'],
|
||||
},
|
||||
};
|
||||
|
||||
const renderComponent = createComponentRenderer(CanvasNodeDefault, {
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasProvide(),
|
||||
},
|
||||
|
|
@ -32,6 +41,7 @@ describe('CanvasNodeDefault', () => {
|
|||
provide: {
|
||||
...createCanvasNodeProvide(),
|
||||
},
|
||||
stubs,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -51,6 +61,7 @@ describe('CanvasNodeDefault', () => {
|
|||
(inputCount, outputCount, expected) => {
|
||||
const { getByText } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide({
|
||||
data: {
|
||||
|
|
@ -78,6 +89,7 @@ describe('CanvasNodeDefault', () => {
|
|||
it('should apply selected class when node is selected', () => {
|
||||
const { getByText } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide({ selected: true }),
|
||||
},
|
||||
|
|
@ -89,6 +101,7 @@ describe('CanvasNodeDefault', () => {
|
|||
it('should not apply selected class when node is not selected', () => {
|
||||
const { getByText } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide(),
|
||||
},
|
||||
|
|
@ -102,6 +115,7 @@ describe('CanvasNodeDefault', () => {
|
|||
it('should apply disabled class when node is disabled', () => {
|
||||
const { getByText } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide({
|
||||
data: {
|
||||
|
|
@ -119,6 +133,7 @@ describe('CanvasNodeDefault', () => {
|
|||
it('should not apply disabled class when node is enabled', () => {
|
||||
const { getByText } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide(),
|
||||
},
|
||||
|
|
@ -130,6 +145,7 @@ describe('CanvasNodeDefault', () => {
|
|||
it('should render strike-through when node is disabled and has node input and output handles', () => {
|
||||
const { container } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide({
|
||||
data: {
|
||||
|
|
@ -162,6 +178,7 @@ describe('CanvasNodeDefault', () => {
|
|||
it('should apply waiting class when node is waiting', () => {
|
||||
const { getByText } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide({ data: { execution: { running: true, waiting: '123' } } }),
|
||||
},
|
||||
|
|
@ -175,6 +192,7 @@ describe('CanvasNodeDefault', () => {
|
|||
it('should apply running class when node is running', () => {
|
||||
const { getByText } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide({ data: { execution: { running: true } } }),
|
||||
},
|
||||
|
|
@ -188,6 +206,7 @@ describe('CanvasNodeDefault', () => {
|
|||
it('should render configurable node correctly', () => {
|
||||
const { getByTestId } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide({
|
||||
data: {
|
||||
|
|
@ -246,6 +265,7 @@ describe('CanvasNodeDefault', () => {
|
|||
(_, nonMainInputs, expected) => {
|
||||
const { getByText } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide({
|
||||
data: {
|
||||
|
|
@ -273,6 +293,7 @@ describe('CanvasNodeDefault', () => {
|
|||
it('should render configuration node correctly', () => {
|
||||
const { getByTestId } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide({
|
||||
data: {
|
||||
|
|
@ -292,6 +313,7 @@ describe('CanvasNodeDefault', () => {
|
|||
it('should render configurable configuration node correctly', () => {
|
||||
const { getByTestId } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide({
|
||||
data: {
|
||||
|
|
@ -313,6 +335,7 @@ describe('CanvasNodeDefault', () => {
|
|||
it('should render trigger node correctly', () => {
|
||||
const { getByTestId } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide({
|
||||
data: {
|
||||
|
|
@ -333,6 +356,7 @@ describe('CanvasNodeDefault', () => {
|
|||
it('should emit "activate" on double click', async () => {
|
||||
const { getByText, emitted } = renderComponent({
|
||||
global: {
|
||||
stubs,
|
||||
provide: {
|
||||
...createCanvasNodeProvide(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,24 +7,12 @@ exports[`CanvasNodeDefault > configurable > should render configurable node corr
|
|||
style="--canvas-node--width: 224px; --canvas-node--height: 96px; --node-icon-size: 40px;"
|
||||
>
|
||||
<!--v-if-->
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon nodeIcon icon"
|
||||
<node-icon-stub
|
||||
class="icon"
|
||||
disabled="false"
|
||||
shrink="false"
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 40px; height: 40px; font-size: 40px; line-height: 40px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="nodeIconPlaceholder"
|
||||
>
|
||||
?
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
size="40"
|
||||
/>
|
||||
<div
|
||||
class="settingsIcons"
|
||||
>
|
||||
|
|
@ -60,24 +48,12 @@ exports[`CanvasNodeDefault > configuration > should render configurable configur
|
|||
style="--canvas-node--width: 240px; --canvas-node--height: 80px; --node-icon-size: 30px;"
|
||||
>
|
||||
<!--v-if-->
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon nodeIcon icon"
|
||||
<node-icon-stub
|
||||
class="icon"
|
||||
disabled="false"
|
||||
shrink="false"
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 30px; height: 30px; font-size: 30px; line-height: 30px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="nodeIconPlaceholder"
|
||||
>
|
||||
?
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
size="30"
|
||||
/>
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<div
|
||||
|
|
@ -106,24 +82,12 @@ exports[`CanvasNodeDefault > configuration > should render configuration node co
|
|||
style="--canvas-node--width: 80px; --canvas-node--height: 80px; --node-icon-size: 30px;"
|
||||
>
|
||||
<!--v-if-->
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon nodeIcon icon"
|
||||
<node-icon-stub
|
||||
class="icon"
|
||||
disabled="false"
|
||||
shrink="false"
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 30px; height: 30px; font-size: 30px; line-height: 30px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="nodeIconPlaceholder"
|
||||
>
|
||||
?
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
size="30"
|
||||
/>
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<div
|
||||
|
|
@ -152,24 +116,12 @@ exports[`CanvasNodeDefault > should render node correctly 1`] = `
|
|||
style="--canvas-node--width: 96px; --canvas-node--height: 96px; --node-icon-size: 40px;"
|
||||
>
|
||||
<!--v-if-->
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon nodeIcon icon"
|
||||
<node-icon-stub
|
||||
class="icon"
|
||||
disabled="false"
|
||||
shrink="false"
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 40px; height: 40px; font-size: 40px; line-height: 40px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="nodeIconPlaceholder"
|
||||
>
|
||||
?
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
size="40"
|
||||
/>
|
||||
<div
|
||||
class="settingsIcons"
|
||||
>
|
||||
|
|
@ -205,24 +157,12 @@ exports[`CanvasNodeDefault > trigger > should render trigger node correctly 1`]
|
|||
style="--canvas-node--width: 96px; --canvas-node--height: 96px; --node-icon-size: 40px;"
|
||||
>
|
||||
<!--v-if-->
|
||||
<div
|
||||
class="n8n-node-icon nodeIcon icon nodeIcon icon"
|
||||
<node-icon-stub
|
||||
class="icon"
|
||||
disabled="false"
|
||||
shrink="false"
|
||||
>
|
||||
<div
|
||||
class="nodeIconWrapper"
|
||||
style="width: 40px; height: 40px; font-size: 40px; line-height: 40px;"
|
||||
>
|
||||
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
|
||||
|
||||
<div
|
||||
class="nodeIconPlaceholder"
|
||||
>
|
||||
?
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
size="40"
|
||||
/>
|
||||
<div
|
||||
class="settingsIcons"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import { i18nInstance } from '@n8n/i18n';
|
|||
import { TelemetryPlugin } from './plugins/telemetry';
|
||||
import { GlobalComponentsPlugin } from './plugins/components';
|
||||
import { GlobalDirectivesPlugin } from './plugins/directives';
|
||||
import { FontAwesomePlugin } from './plugins/icons';
|
||||
|
||||
import { createPinia, PiniaVuePlugin } from 'pinia';
|
||||
import { ChartJSPlugin } from '@/plugins/chartjs';
|
||||
|
|
@ -43,7 +42,6 @@ registerModuleRoutes(router);
|
|||
|
||||
app.use(TelemetryPlugin);
|
||||
app.use(PiniaVuePlugin);
|
||||
app.use(FontAwesomePlugin);
|
||||
app.use(GlobalComponentsPlugin);
|
||||
app.use(GlobalDirectivesPlugin);
|
||||
app.use(pinia);
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,416 +0,0 @@
|
|||
import type { Plugin } from 'vue';
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import type { IconDefinition, Library } from '@fortawesome/fontawesome-svg-core';
|
||||
import {
|
||||
faAngleDoubleLeft,
|
||||
faAngleDown,
|
||||
faAngleLeft,
|
||||
faAngleRight,
|
||||
faAngleUp,
|
||||
faArchive,
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
faArrowUp,
|
||||
faArrowDown,
|
||||
faAt,
|
||||
faBan,
|
||||
faBalanceScaleLeft,
|
||||
faBars,
|
||||
faBell,
|
||||
faBolt,
|
||||
faBook,
|
||||
faBoxOpen,
|
||||
faBug,
|
||||
faBrain,
|
||||
faCalculator,
|
||||
faCalendar,
|
||||
faCaretDown,
|
||||
faCaretRight,
|
||||
faCaretUp,
|
||||
faChartBar,
|
||||
faCheck,
|
||||
faCheckCircle,
|
||||
faCheckDouble,
|
||||
faCheckSquare,
|
||||
faChevronDown,
|
||||
faChevronUp,
|
||||
faCircle,
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
faCode,
|
||||
faCodeBranch,
|
||||
faCog,
|
||||
faCogs,
|
||||
faComment,
|
||||
faComments,
|
||||
faCompress,
|
||||
faClipboardList,
|
||||
faClock,
|
||||
faClone,
|
||||
faCloud,
|
||||
faCloudDownloadAlt,
|
||||
faCopy,
|
||||
faCube,
|
||||
faCut,
|
||||
faDatabase,
|
||||
faDotCircle,
|
||||
faEdit,
|
||||
faEllipsisH,
|
||||
faEllipsisV,
|
||||
faEnvelope,
|
||||
faEquals,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faExclamationTriangle,
|
||||
faExpand,
|
||||
faExpandAlt,
|
||||
faExternalLinkAlt,
|
||||
faExchangeAlt,
|
||||
faFile,
|
||||
faFileAlt,
|
||||
faFileArchive,
|
||||
faFileCode,
|
||||
faFileDownload,
|
||||
faFileExport,
|
||||
faFileImport,
|
||||
faFilePdf,
|
||||
faFilter,
|
||||
faFingerprint,
|
||||
faFlask,
|
||||
faFolder,
|
||||
faFolderOpen,
|
||||
faFolderPlus,
|
||||
faFont,
|
||||
faGlobeAmericas,
|
||||
faGift,
|
||||
faGlobe,
|
||||
faGraduationCap,
|
||||
faGripLinesVertical,
|
||||
faGripVertical,
|
||||
faHandHoldingUsd,
|
||||
faHandScissors,
|
||||
faHandPointLeft,
|
||||
faHandshake,
|
||||
faUserCheck,
|
||||
faHashtag,
|
||||
faHdd,
|
||||
faHistory,
|
||||
faHome,
|
||||
faHourglass,
|
||||
faImage,
|
||||
faInbox,
|
||||
faInfo,
|
||||
faInfoCircle,
|
||||
faKey,
|
||||
faLanguage,
|
||||
faLayerGroup,
|
||||
faLink,
|
||||
faList,
|
||||
faLightbulb,
|
||||
faLock,
|
||||
faMapSigns,
|
||||
faMousePointer,
|
||||
faNetworkWired,
|
||||
faPalette,
|
||||
faPause,
|
||||
faPauseCircle,
|
||||
faPen,
|
||||
faPencilAlt,
|
||||
faPlay,
|
||||
faPlayCircle,
|
||||
faPlug,
|
||||
faPlus,
|
||||
faPlusCircle,
|
||||
faPlusSquare,
|
||||
faQuestion,
|
||||
faQuestionCircle,
|
||||
faRedo,
|
||||
faRobot,
|
||||
faRss,
|
||||
faSave,
|
||||
faSatelliteDish,
|
||||
faSearch,
|
||||
faSearchMinus,
|
||||
faSearchPlus,
|
||||
faServer,
|
||||
faScrewdriver,
|
||||
faShare,
|
||||
faSmile,
|
||||
faSignInAlt,
|
||||
faSignOutAlt,
|
||||
faSlidersH,
|
||||
faSpinner,
|
||||
faStop,
|
||||
faSun,
|
||||
faSync,
|
||||
faSyncAlt,
|
||||
faTable,
|
||||
faTags,
|
||||
faTasks,
|
||||
faTerminal,
|
||||
faThLarge,
|
||||
faThumbtack,
|
||||
faThumbsDown,
|
||||
faThumbsUp,
|
||||
faTimes,
|
||||
faTimesCircle,
|
||||
faToolbox,
|
||||
faTrash,
|
||||
faUndo,
|
||||
faUnlink,
|
||||
faUser,
|
||||
faUserCircle,
|
||||
faUserFriends,
|
||||
faUsers,
|
||||
faVectorSquare,
|
||||
faVideo,
|
||||
faTree,
|
||||
faStickyNote as faSolidStickyNote,
|
||||
faUserLock,
|
||||
faGem,
|
||||
faDownload,
|
||||
faRemoveFormat,
|
||||
faTools,
|
||||
faProjectDiagram,
|
||||
faStream,
|
||||
faPowerOff,
|
||||
faPaperPlane,
|
||||
faExclamationCircle,
|
||||
faMinusCircle,
|
||||
faAdjust,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import {
|
||||
faVariable,
|
||||
faXmark,
|
||||
faVault,
|
||||
faRefresh,
|
||||
faTriangle,
|
||||
statusCompleted,
|
||||
statusWaiting,
|
||||
statusError,
|
||||
statusCanceled,
|
||||
statusNew,
|
||||
statusUnknown,
|
||||
statusWarning,
|
||||
faPopOut,
|
||||
faJSON,
|
||||
faSchema,
|
||||
faBinary,
|
||||
faText,
|
||||
} from './custom';
|
||||
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
|
||||
function addIcon(icon: IconDefinition) {
|
||||
library.add(icon);
|
||||
}
|
||||
|
||||
// this can be removed once nodes stop using FA icons
|
||||
export const FontAwesomePlugin: Plugin = {
|
||||
install: (app) => {
|
||||
addIcon(faAngleDoubleLeft);
|
||||
addIcon(faAngleDown);
|
||||
addIcon(faAngleLeft);
|
||||
addIcon(faAngleRight);
|
||||
addIcon(faAngleUp);
|
||||
addIcon(faArchive);
|
||||
addIcon(faArrowLeft);
|
||||
addIcon(faArrowRight);
|
||||
addIcon(faArrowUp);
|
||||
addIcon(faArrowDown);
|
||||
addIcon(faAt);
|
||||
addIcon(faBan);
|
||||
addIcon(faBalanceScaleLeft);
|
||||
addIcon(faBars);
|
||||
addIcon(faBell);
|
||||
addIcon(faBolt);
|
||||
addIcon(faBook);
|
||||
addIcon(faBoxOpen);
|
||||
addIcon(faBug);
|
||||
addIcon(faBrain);
|
||||
addIcon(faCalculator);
|
||||
addIcon(faCalendar);
|
||||
addIcon(faCaretDown);
|
||||
addIcon(faCaretRight);
|
||||
addIcon(faCaretUp);
|
||||
addIcon(faChartBar);
|
||||
addIcon(faCheck);
|
||||
addIcon(faCheckCircle);
|
||||
addIcon(faCheckDouble);
|
||||
addIcon(faCheckSquare);
|
||||
addIcon(faChevronLeft);
|
||||
addIcon(faChevronRight);
|
||||
addIcon(faChevronDown);
|
||||
addIcon(faChevronUp);
|
||||
addIcon(faCircle);
|
||||
addIcon(faCode);
|
||||
addIcon(faCodeBranch);
|
||||
addIcon(faCog);
|
||||
addIcon(faCogs);
|
||||
addIcon(faComment);
|
||||
addIcon(faComments);
|
||||
addIcon(faCompress);
|
||||
addIcon(faClipboardList);
|
||||
addIcon(faClock);
|
||||
addIcon(faClone);
|
||||
addIcon(faCloud);
|
||||
addIcon(faCloudDownloadAlt);
|
||||
addIcon(faCopy);
|
||||
addIcon(faCube);
|
||||
addIcon(faCut);
|
||||
addIcon(faDatabase);
|
||||
addIcon(faDotCircle);
|
||||
addIcon(faGripLinesVertical);
|
||||
addIcon(faGripVertical);
|
||||
addIcon(faEdit);
|
||||
addIcon(faEllipsisH);
|
||||
addIcon(faEllipsisV);
|
||||
addIcon(faEnvelope);
|
||||
addIcon(faEquals);
|
||||
addIcon(faEye);
|
||||
addIcon(faEyeSlash);
|
||||
addIcon(faExclamationTriangle);
|
||||
addIcon(faExclamationCircle);
|
||||
addIcon(faExpand);
|
||||
addIcon(faExpandAlt);
|
||||
addIcon(faExternalLinkAlt);
|
||||
addIcon(faExchangeAlt);
|
||||
addIcon(faFile);
|
||||
addIcon(faFileAlt);
|
||||
addIcon(faFileArchive);
|
||||
addIcon(faFileCode);
|
||||
addIcon(faFileDownload);
|
||||
addIcon(faFileExport);
|
||||
addIcon(faFileImport);
|
||||
addIcon(faFilePdf);
|
||||
addIcon(faFilter);
|
||||
addIcon(faFingerprint);
|
||||
addIcon(faFlask);
|
||||
addIcon(faFolder);
|
||||
addIcon(faFolderOpen);
|
||||
addIcon(faFolderPlus);
|
||||
addIcon(faFont);
|
||||
addIcon(faGift);
|
||||
addIcon(faGlobe);
|
||||
addIcon(faGlobeAmericas);
|
||||
addIcon(faGraduationCap);
|
||||
addIcon(faHandHoldingUsd);
|
||||
addIcon(faHandScissors);
|
||||
addIcon(faHandshake);
|
||||
addIcon(faHandPointLeft);
|
||||
addIcon(faHashtag);
|
||||
addIcon(faUserCheck);
|
||||
addIcon(faHdd);
|
||||
addIcon(faHistory);
|
||||
addIcon(faHome);
|
||||
addIcon(faHourglass);
|
||||
addIcon(faImage);
|
||||
addIcon(faInbox);
|
||||
addIcon(faInfo);
|
||||
addIcon(faInfoCircle);
|
||||
addIcon(faKey);
|
||||
addIcon(faLanguage);
|
||||
addIcon(faLayerGroup);
|
||||
addIcon(faLink);
|
||||
addIcon(faList);
|
||||
addIcon(faLightbulb);
|
||||
addIcon(faLock);
|
||||
addIcon(faMapSigns);
|
||||
addIcon(faMousePointer);
|
||||
addIcon(faNetworkWired);
|
||||
addIcon(faPalette);
|
||||
addIcon(faPause);
|
||||
addIcon(faPauseCircle);
|
||||
addIcon(faPen);
|
||||
addIcon(faPencilAlt);
|
||||
addIcon(faPlay);
|
||||
addIcon(faPlayCircle);
|
||||
addIcon(faPlug);
|
||||
addIcon(faPlus);
|
||||
addIcon(faPlusCircle);
|
||||
addIcon(faPlusSquare);
|
||||
addIcon(faProjectDiagram);
|
||||
addIcon(faQuestion);
|
||||
addIcon(faQuestionCircle);
|
||||
addIcon(faRedo);
|
||||
addIcon(faRemoveFormat);
|
||||
addIcon(faRobot);
|
||||
addIcon(faRss);
|
||||
addIcon(faSave);
|
||||
addIcon(faSatelliteDish);
|
||||
addIcon(faSearch);
|
||||
addIcon(faSearchMinus);
|
||||
addIcon(faSearchPlus);
|
||||
addIcon(faServer);
|
||||
addIcon(faScrewdriver);
|
||||
addIcon(faShare);
|
||||
addIcon(faSmile);
|
||||
addIcon(faSignInAlt);
|
||||
addIcon(faSignOutAlt);
|
||||
addIcon(faSlidersH);
|
||||
addIcon(faSpinner);
|
||||
addIcon(faSolidStickyNote);
|
||||
addIcon(faStickyNote as IconDefinition);
|
||||
addIcon(faStop);
|
||||
addIcon(faStream);
|
||||
addIcon(faSun);
|
||||
addIcon(faSync);
|
||||
addIcon(faSyncAlt);
|
||||
addIcon(faTable);
|
||||
addIcon(faTags);
|
||||
addIcon(faTasks);
|
||||
addIcon(faTerminal);
|
||||
addIcon(faThLarge);
|
||||
addIcon(faThumbtack);
|
||||
addIcon(faThumbsDown);
|
||||
addIcon(faThumbsUp);
|
||||
addIcon(faTimes);
|
||||
addIcon(faTimesCircle);
|
||||
addIcon(faToolbox);
|
||||
addIcon(faTools);
|
||||
addIcon(faTrash);
|
||||
addIcon(faTriangle);
|
||||
addIcon(faUndo);
|
||||
addIcon(faUnlink);
|
||||
addIcon(faUser);
|
||||
addIcon(faUserCircle);
|
||||
addIcon(faUserFriends);
|
||||
addIcon(faUsers);
|
||||
addIcon(faVariable);
|
||||
addIcon(faVault);
|
||||
addIcon(faVectorSquare);
|
||||
addIcon(faVideo);
|
||||
addIcon(faTree);
|
||||
addIcon(faUserLock);
|
||||
addIcon(faGem);
|
||||
addIcon(faXmark);
|
||||
addIcon(faDownload);
|
||||
addIcon(faPowerOff);
|
||||
addIcon(faPaperPlane);
|
||||
addIcon(faRefresh);
|
||||
addIcon(faMinusCircle);
|
||||
addIcon(faAdjust);
|
||||
// statuses
|
||||
addIcon(statusCompleted);
|
||||
addIcon(statusWaiting);
|
||||
addIcon(statusError);
|
||||
addIcon(statusCanceled);
|
||||
addIcon(statusNew);
|
||||
addIcon(statusUnknown);
|
||||
addIcon(statusWarning);
|
||||
|
||||
addIcon(faPopOut);
|
||||
addIcon(faSchema);
|
||||
addIcon(faJSON);
|
||||
addIcon(faBinary);
|
||||
addIcon(faText);
|
||||
|
||||
app.component('FontAwesomeIcon', FontAwesomeIcon);
|
||||
},
|
||||
};
|
||||
|
||||
type LibraryWithDefinitions = Library & {
|
||||
definitions: Record<string, Record<string, IconDefinition>>;
|
||||
};
|
||||
|
||||
export const iconLibrary = library as LibraryWithDefinitions;
|
||||
|
|
@ -2160,15 +2160,6 @@ importers:
|
|||
|
||||
packages/frontend/@n8n/design-system:
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-svg-core':
|
||||
specifier: ^1.2.36
|
||||
version: 1.2.36
|
||||
'@fortawesome/free-solid-svg-icons':
|
||||
specifier: ^5.15.4
|
||||
version: 5.15.4
|
||||
'@fortawesome/vue-fontawesome':
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3(@fortawesome/fontawesome-svg-core@1.2.36)(vue@3.5.13(typescript@5.9.2))
|
||||
'@n8n/composables':
|
||||
specifier: workspace:*
|
||||
version: link:../composables
|
||||
|
|
@ -2545,18 +2536,6 @@ importers:
|
|||
'@dagrejs/dagre':
|
||||
specifier: ^1.1.4
|
||||
version: 1.1.4
|
||||
'@fortawesome/fontawesome-svg-core':
|
||||
specifier: '*'
|
||||
version: 1.2.36
|
||||
'@fortawesome/free-regular-svg-icons':
|
||||
specifier: '*'
|
||||
version: 6.2.0
|
||||
'@fortawesome/free-solid-svg-icons':
|
||||
specifier: '*'
|
||||
version: 5.15.4
|
||||
'@fortawesome/vue-fontawesome':
|
||||
specifier: '*'
|
||||
version: 3.0.3(@fortawesome/fontawesome-svg-core@1.2.36)(vue@3.5.13(typescript@5.9.2))
|
||||
'@lezer/common':
|
||||
specifier: 1.1.0
|
||||
version: 1.1.0
|
||||
|
|
@ -4790,32 +4769,6 @@ packages:
|
|||
'@floating-ui/vue@1.1.6':
|
||||
resolution: {integrity: sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A==}
|
||||
|
||||
'@fortawesome/fontawesome-common-types@0.2.36':
|
||||
resolution: {integrity: sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/fontawesome-common-types@6.2.0':
|
||||
resolution: {integrity: sha512-rBevIsj2nclStJ7AxTdfsa3ovHb1H+qApwrxcTVo+NNdeJiB9V75hsKfrkG5AwNcRUNxrPPiScGYCNmLMoh8pg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/fontawesome-svg-core@1.2.36':
|
||||
resolution: {integrity: sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/free-regular-svg-icons@6.2.0':
|
||||
resolution: {integrity: sha512-M1dG+PAmkYMTL9BSUHFXY5oaHwBYfHCPhbJ8qj8JELsc9XCrUJ6eEHWip4q0tE+h9C0DVyFkwIM9t7QYyCpprQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/free-solid-svg-icons@5.15.4':
|
||||
resolution: {integrity: sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/vue-fontawesome@3.0.3':
|
||||
resolution: {integrity: sha512-KCPHi9QemVXGMrfuwf3nNnNo129resAIQWut9QTAMXmXqL2ErABC6ohd2yY5Ipq0CLWNbKHk8TMdTXL/Zf3ZhA==}
|
||||
peerDependencies:
|
||||
'@fortawesome/fontawesome-svg-core': ~1 || ~6
|
||||
vue: '>= 3.0.0 < 4'
|
||||
|
||||
'@gar/promisify@1.1.3':
|
||||
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
|
||||
|
||||
|
|
@ -19448,27 +19401,6 @@ snapshots:
|
|||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@fortawesome/fontawesome-common-types@0.2.36': {}
|
||||
|
||||
'@fortawesome/fontawesome-common-types@6.2.0': {}
|
||||
|
||||
'@fortawesome/fontawesome-svg-core@1.2.36':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 0.2.36
|
||||
|
||||
'@fortawesome/free-regular-svg-icons@6.2.0':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 6.2.0
|
||||
|
||||
'@fortawesome/free-solid-svg-icons@5.15.4':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 0.2.36
|
||||
|
||||
'@fortawesome/vue-fontawesome@3.0.3(@fortawesome/fontawesome-svg-core@1.2.36)(vue@3.5.13(typescript@5.9.2))':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-svg-core': 1.2.36
|
||||
vue: 3.5.13(typescript@5.9.2)
|
||||
|
||||
'@gar/promisify@1.1.3':
|
||||
optional: true
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user