webdesserts / alchemist-js

The extensible color library
https://webdesserts.gitbooks.io/alchemist-js/
MIT License
11 stars 2 forks source link

Plugins as Functions #28

Open webdesserts opened 8 years ago

webdesserts commented 8 years ago

Note: it's been awhile since i've worked with the internals of the library itself, so I'm going to have to remind myself how every thing hooks together. After a quick review, most of these comments don't make any sense, I'll be cleaning it up as I go.

Currently, we are using objects as plugins. The upside to this is that we have more control over how a user interacts with alchemist within plugins. The downside is that it limits what the user can do.

In my mind the "upside" of plugins as objects is a false benefit as a user who really wants do whatever they want can always avoid the plugin system all together with a top-level function

function SUBVERT_THE_SYSTEM (alchemist) {
  /* whatever the hell I want */
}

alchemist.use(xyz());
alchemist.use(rgb());
SUBVERT_THE_SYSTEM(alchemist)

So if someone wants to "subvert the system" we might as well make alchemist extensible enough to do so cleanly.

alchemist.use(function(alchemist) {
  /* define color conversions directly on alchemist */
})

This would mean that we would need a way for users to define color-spaces and methods directly on alchemist. Here are my initial thoughts on the API.

Color Space Plugins

In this example I am using the concept of a ColorSpacePlugin object. I'm a little conflicted on this, mainly because of the to and from methods. If we use the internal BaseSpace instead this would mean I would have to overload our main conversion methods and I have a feeling this would affect performance. I like how intuitive and readable the to and from is, but if we were to use BaseSpace directly I would need to rename these methods to something like defineConversionTo, which is longer (larger alchemist bundles) and bleeeehhhh, but only as bleeeeh as having two ColorSpace objects (which would also lead to larger bundles).

alchemist.use(function(alchemist) {
  var rgb = alchemist.ColorSpacePlugin.create('rgb')
  rgb.min([0,0,0])
  rgb.max([255,255,255])
  rgb.to('xyz', function (values) { /* ... */ })
  rgb.from('xyz', function (values) { /* ... */})
  alchemist.spaces.add(rgb)
})

Method Plugins

Even though a user could now technically define the methods themselves I would still like them to go through a define function so that we can throw an error if they overwrite an existing method rather than plugins silently overwriting each other when a user includes multiple plugins using the same method name.

  alchemist.spaces.define('lighten', function (amount) {
    var lab = this.as('lab');
    /* do stuff... */
    return lab;
  })
  alchemist.define('lighten', function (color, amount) {
    return color.lighten(amount)
  })
})

I'm leaning towards putting the define for color spaces on the ColorSpaceStore itself as opposed to putting it on the BaseSpace as I want to keep the BaseSpace as simple and readable as possible.

webdesserts commented 8 years ago

Initial Psuedo code for ColorSpacePlugin off the top of my head

import Crux from '@webdesserts/crux'
import Limiter from './limiter'
import ConversionStore from './conversion-store'
import ColorSpaceStore from './color-space-store'

var ColorSpacePlugin = Crux.create()

ColorSpacePlugin.init = function init (name) {
  // this.BaseSpace should be added on after a ColorSpacePlugin.extend()
  let space = this.BaseSpace.create()
  space.space = name
  space.is_concrete = true
  space.limits = BaseSpace.limits.create(null, null)
  space.conversions = ConversionStore.create()

  this.space = space
  this.abstract_spaces = ColorSpaceStore.create()
}

ColorSpacePlugin.min = function min (values) {
  let { space } = this
  space.limits.merge(space.limits.create(values, null))
}

ColorSpacePlugin.max = function max (values) {
  let { space } = this
  space.limits.merge(space.limits.create(null, values))
}

ColorSpacePlugin.to = function addConversionTo (dest_space, conversion) {
  let { space } = this
  space.conversions.add(dest_space, conversion)
}

ColorSpacePlugin.from = function addConversionFrom (src_space, conversion) {
  let { abstract_spaces, space } = this
  let abstract_space = BaseSpace.create()

  abstract_space.is_concrete = false

  abstract_space.space = src_space
  abstract_space.conversions.add(this.space.space, conversion)
  this.abstract_spaces.add(abstract_space)
}