Skip to content

Commit

Permalink
feat(plugins): create plugin system that allows to extend SVGuitar wi…
Browse files Browse the repository at this point in the history
…th arbitrary functionality

re #40
  • Loading branch information
Voellmy Raphael authored and Voellmy Raphael committed Nov 28, 2020
1 parent 0d74f58 commit af28a21
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 3 deletions.
30 changes: 30 additions & 0 deletions src/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { SVGuitarChord } from './svguitar'
import { setUpSvgDom } from '../test/testutils'

const document = setUpSvgDom()

describe('SVGuitarChord Plugin', () => {
let container: HTMLElement

beforeEach(() => {
container = document.documentElement
})

test('should apply a basic plugin', () => {
// given
const spy = jest.fn()
function myFooPlugin(/* instance: SVGuitarChord */): { foo: jest.Mock } {
return {
foo: spy,
}
}

// when
const PluginTest = SVGuitarChord.plugin(myFooPlugin)
const withPlugin = new PluginTest(container)
withPlugin.foo()

// then
expect(spy).toHaveBeenCalledWith()
})
})
26 changes: 26 additions & 0 deletions src/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { SVGuitarChord } from './svguitar'

export type ApiExtension = { [key: string]: any }

export type Constructor<T> = new (...args: any[]) => T

export type AnyFunction = (...args: any) => any

/**
* @author https://stackoverflow.com/users/2887218/jcalz
* @see https://stackoverflow.com/a/50375286/10325032
*/
type UnionToIntersection<Union> = (Union extends any ? (argument: Union) => void : never) extends (
argument: infer Intersection,
) => void // tslint:disable-line: no-unused
? Intersection
: never

export type ReturnTypeOf<T extends AnyFunction | AnyFunction[]> = T extends AnyFunction
? ReturnType<T>
: T extends AnyFunction[]
? UnionToIntersection<ReturnType<T[number]>>
: never

export type SVGuitarPlugin = (instance: SVGuitarChord) => ApiExtension | undefined
4 changes: 2 additions & 2 deletions test/svguitar.test.ts → src/svguitar.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FretLabelPosition, Shape, SVGuitarChord } from '../src/svguitar'
import { saveSvg, setUpSvgDom } from './testutils'
import { FretLabelPosition, Shape, SVGuitarChord } from './svguitar'
import { saveSvg, setUpSvgDom } from '../test/testutils'

const document = setUpSvgDom()

Expand Down
28 changes: 27 additions & 1 deletion src/svguitar.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/* eslint-disable max-classes-per-file */
import { QuerySelector } from '@svgdotjs/svg.js'
import { range } from './utils'
import { constants } from './constants'
import { Alignment, GraphcisElement, Renderer, RoughJsRenderer, SvgJsRenderer } from './renderer'
import { SVGuitarPlugin, Constructor, ReturnTypeOf } from './plugin'

// Chord diagram input types (compatible with Vexchords input, see https://github.com/0xfe/vexchords)
export type SilentString = 'x'
Expand Down Expand Up @@ -268,13 +270,37 @@ const defaultSettings: RequiredChordSettings = {
}

export class SVGuitarChord {
static plugins: SVGuitarPlugin[] = []

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static plugin<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
S extends Constructor<any> & { plugins: any[] },
T extends SVGuitarPlugin | SVGuitarPlugin[]
>(this: S, plugin: T) {
const currentPlugins = this.plugins

const BaseWithPlugins = class extends this {
static plugins = currentPlugins.concat(plugin)
}

return BaseWithPlugins as typeof BaseWithPlugins & Constructor<ReturnTypeOf<T>>
}

private rendererInternal?: Renderer

private settings: ChordSettings = {}

private chordInternal: Chord = { fingers: [], barres: [] }

constructor(private container: QuerySelector | HTMLElement) {}
constructor(private container: QuerySelector | HTMLElement) {
// apply plugins
// https://stackoverflow.com/a/16345172
const classConstructor = this.constructor as typeof SVGuitarChord
classConstructor.plugins.forEach((plugin) => {
Object.assign(this, plugin(this))
})
}

private get renderer(): Renderer {
if (!this.rendererInternal) {
Expand Down

0 comments on commit af28a21

Please sign in to comment.