Skip to content

Commit

Permalink
Merge pull request #242 from ComfyFluffy/feat/custom-nodes
Browse files Browse the repository at this point in the history
feat: support adding custom konva nodes
  • Loading branch information
lavrton authored Aug 11, 2024
2 parents 7f20ff2 + ac1b37d commit c71fc3d
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 107 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,30 @@ Vue.use(VueKonva, { prefix: 'Konva'});
<konva-stage ref="stage" :config="stage">
```

## Custom Konva Nodes

By passing a `Record<string, new (...args: any) => Node<any>>` object to `customNodes` in options, you can use your own konva node classes in Vue Konva.

```js
import Vue from 'vue';
import VueKonva from 'vue-konva'

class MyRect extends Konva.Rect {
constructor() {
super()
console.log('MyRect')
}
}

Vue.use(VueKonva, {
// The keys are used as component names.
customNodes: { MyRect }
})

// in template:
<v-my-rect />
```

## Change log

The change log can be found on the [Releases page](https://github.com/konvajs/vue-konva/releases).
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>;
}
}
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@
],
"homepage": "https://github.com/konvajs/vue-konva#readme",
"author": "Rafael Escala <[email protected]>",
"main": "dist/vue-konva.umd.js",
"types": "dist/index.d.ts",
"main": "dist/vue-konva.umd.js",
"module": "dist/vue-konva.mjs",
"exports": {
"import": "./dist/vue-konva.mjs",
"require": "./dist/vue-konva.umd.js",
"types": "./dist/index.d.ts"
},
"vetur": {
"tags": "vetur/tags.json",
"attributes": "vetur/attributes.json"
Expand Down
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);
},
});
}
6 changes: 3 additions & 3 deletions src/components/Stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import {
defineComponent,
PropType,
} from 'vue';
import type Konva from 'konva';
import Konva from 'konva';
import { applyNodeProps, checkOrder } from '../utils';

export default defineComponent({
name: 'Stage',
props: {
config: {
type: Object as PropType<Konva.StageConfig>,
default: function() {
default: function () {
return {};
},
},
Expand All @@ -36,7 +36,7 @@ export default defineComponent({

const container = ref<HTMLDivElement | null>(null);

const __konvaNode = new window.Konva.Stage({
const __konvaNode = new Konva.Stage({
width: props.config.width,
height: props.config.height,
container: document.createElement('div'), // Fake container. Will be replaced
Expand Down
52 changes: 45 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,57 @@
import type { App } from 'vue';
import type { 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: any,
// We use any here as it seems TypeScript will complain
// if the user uses a different version of Vue.
options?: { prefix?: string; customNodes?: Record<string, KonvaNodeConstructor> },
) => {
const prefixToUse = options?.prefix || componentPrefix;

const konvaNodeConstructors: Record<string, KonvaNodeConstructor> = {
Arc: Konva.Arc,
Arrow: Konva.Arrow,
Circle: Konva.Circle,
Ellipse: Konva.Ellipse,
FastLayer: Konva.FastLayer,
Group: Konva.Group,
Image: Konva.Image,
Label: Konva.Label,
Layer: Konva.Layer,
Line: Konva.Line,
Path: Konva.Path,
Rect: Konva.Rect,
RegularPolygon: Konva.RegularPolygon,
Ring: Konva.Ring,
Shape: Konva.Shape,
Sprite: Konva.Sprite,
Star: Konva.Star,
Tag: Konva.Tag,
Text: Konva.Text,
TextPath: Konva.TextPath,
Transformer: Konva.Transformer,
Wedge: Konva.Wedge,
...options?.customNodes,
};

[Stage, ...KONVA_NODES.map(KonvaNode)].map((k) => {
app.component(`${prefixToUse}${k.name}`, k);
const components: Component[] = [
Stage,
...Object.entries(konvaNodeConstructors).map(([name, constructor]) =>
KonvaNode(name, constructor),
),
];
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

0 comments on commit c71fc3d

Please sign in to comment.