Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support adding custom konva nodes #242

Merged
merged 8 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type Konva from 'konva';
import type { KonvaNodes } from './src/types';
import { Node } from 'konva/lib/Node';

declare global {
interface Window {
Expand All @@ -12,10 +13,10 @@ declare module 'vue' {
import { ComponentInternalInstance, VNode } from 'vue';

export interface ComponentInternalInstance {
__konvaNode?: KonvaNodes;
__konvaNode?: Node<any>;
}

export interface VNode {
__konvaNode?: KonvaNodes;
__konvaNode?: Node<any>;
}
}
42 changes: 12 additions & 30 deletions src/components/KonvaNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,9 @@ import {
defineComponent,
VNode,
} from 'vue';
import {
applyNodeProps,
findParentKonva,
updatePicture,
checkOrder,
} from '../utils';
import { KONVA_NODES } from '../types';
import { applyNodeProps, findParentKonva, updatePicture, checkOrder } from '../utils';
import Konva from 'konva';
import { KonvaNodeConstructor } from '../types';

const EVENTS_NAMESPACE = '.vue-konva-event';

Expand All @@ -26,13 +22,13 @@ const CONTAINERS = {
Label: true,
};

export default function(nameNode: typeof KONVA_NODES[number]) {
export default function (componentName: string, NodeConstructor: KonvaNodeConstructor) {
return defineComponent({
name: nameNode,
name: componentName,
props: {
config: {
type: Object,
default: function() {
default: function () {
return {};
},
},
Expand All @@ -46,15 +42,7 @@ export default function(nameNode: typeof KONVA_NODES[number]) {
if (!instance) return;
const oldProps = reactive({});

const NodeClass = window.Konva[nameNode];

if (!NodeClass) {
console.error('vue-konva error: Can not find node ' + nameNode);
return;
}

// @ts-ignore
const __konvaNode = new NodeClass();
const __konvaNode = new NodeConstructor();
instance.__konvaNode = __konvaNode;
instance.vnode.__konvaNode = __konvaNode;
uploadKonva();
Expand All @@ -81,18 +69,14 @@ export default function(nameNode: typeof KONVA_NODES[number]) {
...props.config,
...events,
};
applyNodeProps(
instance,
newProps,
existingProps,
props.__useStrictMode,
);
applyNodeProps(instance, newProps, existingProps, props.__useStrictMode);
Object.assign(oldProps, newProps);
}

onMounted(() => {
const parentKonvaNode = findParentKonva(instance)?.__konvaNode;
if (parentKonvaNode && 'add' in parentKonvaNode) parentKonvaNode.add(__konvaNode);
if (parentKonvaNode && 'add' in parentKonvaNode)
(parentKonvaNode as { add: (node: Konva.Node) => void }).add(__konvaNode);
updatePicture(__konvaNode);
});

Expand All @@ -114,10 +98,8 @@ export default function(nameNode: typeof KONVA_NODES[number]) {
getNode,
});

const isContainer = CONTAINERS.hasOwnProperty(nameNode);
return () => isContainer
? h('template', {}, slots.default?.())
: null;
const isContainer = CONTAINERS.hasOwnProperty(componentName);
return () => (isContainer ? h('template', {}, slots.default?.()) : null);
},
});
}
2 changes: 1 addition & 1 deletion src/components/Stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default defineComponent({
props: {
config: {
type: Object as PropType<Konva.StageConfig>,
default: function() {
default: function () {
return {};
},
},
Expand Down
47 changes: 40 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,52 @@
import type { App } from 'vue';
import type { App, Component } from 'vue';
import Stage from './components/Stage';
import KonvaNode from './components/KonvaNode';
import { componentPrefix } from './utils';
import { KONVA_NODES } from './types';
import Konva from 'konva';
import KonvaNode from './components/KonvaNode';
import { KonvaNodeConstructor } from './types';

if (typeof window !== 'undefined' && !window.Konva) {
require('konva');
}


const VueKonva = {
install: (app: App, options?: { prefix?: string }) => {
let prefixToUse = options?.prefix || componentPrefix;
install: (app: App, options?: { prefix?: string; customNodes?: KonvaNodeConstructor[] }) => {
const prefixToUse = options?.prefix || componentPrefix;

const konvaNodeConstructors: KonvaNodeConstructor[] = [
Konva.Arrow,
Konva.Arc,
Konva.Circle,
Konva.Ellipse,
Konva.FastLayer,
Konva.Image,
Konva.Label,
Konva.Line,
Konva.Path,
Konva.Rect,
Konva.RegularPolygon,
Konva.Ring,
Konva.Shape,
Konva.Sprite,
Konva.Stage,
Konva.Star,
Konva.Tag,
Konva.Text,
Konva.TextPath,
Konva.Transformer,
Konva.Wedge,
...(options?.customNodes || []),
];

[Stage, ...KONVA_NODES.map(KonvaNode)].map((k) => {
app.component(`${prefixToUse}${k.name}`, k);
const components: Component[] = [
Stage,
...konvaNodeConstructors.map((konvaNodeConstructor) =>
KonvaNode(konvaNodeConstructor.name, konvaNodeConstructor),
ComfyFluffy marked this conversation as resolved.
Show resolved Hide resolved
),
];
components.forEach((component) => {
app.component(`${prefixToUse}${component.name}`, component);
});
},
};
Expand Down
50 changes: 2 additions & 48 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,3 @@
import Konva from 'konva';
import { Node } from 'konva/lib/Node';

export const KONVA_NODES = [
'Arc',
'Arrow',
'Circle',
'Ellipse',
'FastLayer',
'Group',
'Image',
'Label',
'Layer',
'Line',
'Path',
'Rect',
'RegularPolygon',
'Ring',
'Shape',
'Sprite',
'Star',
'Tag',
'Text',
'TextPath',
'Transformer',
'Wedge',
] as const;

export type KonvaNodes =
Konva.Arrow |
Konva.Arc |
Konva.Circle |
Konva.Ellipse |
Konva.FastLayer |
Konva.Image |
Konva.Label |
Konva.Line |
Konva.Path |
Konva.Rect |
Konva.RegularPolygon |
Konva.Ring |
Konva.Shape |
Konva.Sprite |
Konva.Stage |
Konva.Star |
Konva.Tag |
Konva.Text |
Konva.TextPath |
Konva.Transformer |
Konva.Wedge
export type KonvaNodeConstructor = new (...args: any) => Node<any>;
13 changes: 3 additions & 10 deletions src/utils/applyNodeProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ export default function applyNodeProps(
if (isEvent && propChanged) {
let eventName = key.slice(2).toLowerCase();
if (eventName.slice(0, 7) === 'content') {
eventName =
'content' +
eventName.slice(7, 1).toUpperCase() +
eventName.slice(8);
eventName = 'content' + eventName.slice(7, 1).toUpperCase() + eventName.slice(8);
}
instance?.off(eventName + EVENTS_NAMESPACE, oldProps[key]);
}
Expand All @@ -46,10 +43,7 @@ export default function applyNodeProps(
if (isEvent && toAdd) {
let eventName = key.slice(2).toLowerCase();
if (eventName.slice(0, 7) === 'content') {
eventName =
'content' +
eventName.slice(7, 1).toUpperCase() +
eventName.slice(8);
eventName = 'content' + eventName.slice(7, 1).toUpperCase() + eventName.slice(8);
}
if (props[key]) {
instance?.off(eventName + EVENTS_NAMESPACE);
Expand All @@ -58,8 +52,7 @@ export default function applyNodeProps(
}
if (
!isEvent &&
(props[key] !== oldProps[key] ||
(useStrict && props[key] !== instance?.getAttr(key)))
(props[key] !== oldProps[key] || (useStrict && props[key] !== instance?.getAttr(key)))
) {
hasUpdates = true;
updatedProps[key] = props[key];
Expand Down
10 changes: 4 additions & 6 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ export function findParentKonva(instance: ComponentInternalInstance) {
export function findKonvaNode(instance: VNode): Konva.Node | null {
if (!instance.component) return null;

return (
instance.component.__konvaNode ||
findKonvaNode(instance.component.subTree)
);
return instance.component.__konvaNode || findKonvaNode(instance.component.subTree);
}

function checkTagAndGetNode(instance: VNode): Konva.Node | null {
Expand All @@ -54,8 +51,9 @@ function checkTagAndGetNode(instance: VNode): Konva.Node | null {
function getChildren(instance: VNode) {
const isVNode = (value: VNodeChild | VNodeNormalizedChildren): value is VNode =>
!!value?.hasOwnProperty('component');
const isVNodeArrayChildren = (value: VNodeChild | VNodeNormalizedChildren): value is VNodeArrayChildren =>
Array.isArray(value);
const isVNodeArrayChildren = (
value: VNodeChild | VNodeNormalizedChildren,
): value is VNodeArrayChildren => Array.isArray(value);

const recursivelyFindChildren = (item: VNodeChild | VNodeNormalizedChildren): VNode[] => {
if (isVNode(item)) return [item, ...recursivelyFindChildren(item.children)];
Expand Down