...
 
Commits (2)
......@@ -13,7 +13,7 @@
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
},
"dependencies": {
"chroma-js": "^1.2.1",
"chroma-js": "^1.3.7",
"color-blind": "^0.1.1",
"color2": "^1.0.8",
"d3": "^4.3.0",
......@@ -22,6 +22,8 @@
"hyperform": "^0.8.9",
"lodash": "^4.16.4",
"node-sass": "^4.9.0",
"orbit-controls-es6": "^1.0.12",
"three": "^0.94.0",
"tinycolor2": "^1.4.1",
"vue": "^2.5.16",
"vue-clipboards": "^0.2.5",
......
......@@ -12,7 +12,9 @@
@change="onInputChange"
@focus="onInputFocus"
/>
<div class="color-input__sample"
<div
v-if="!noSample"
class="color-input__sample"
:style="sampleStyle"
@mouseenter="colorFocus = true"
@mouseleave="colorFocus = false"
......@@ -36,6 +38,9 @@
};
},
},
noSample: {
type: Boolean,
},
},
data() {
return {
......@@ -81,7 +86,6 @@
.color-input {
position: relative;
padding: 16px;
margin-bottom: 8px;
background-color: #f0f0f0;
overflow: hidden;
}
......
<template>
<div
:class="classes"
@click="onClick"
>
<color-swatch
:color="color"
></color-swatch>
<color-input
:value="color.toRgb()"
no-sample
@color-change="onChange"
></color-input>
</div>
</template>
<script>
import Color from '../scripts/Color';
import ColorInput from './ColorInput';
import ColorSwatch from './ColorSwatch';
export default {
name: 'color-selector',
props: {
color: {
type: Color,
},
selected: {
type: Boolean,
},
compact: {
type: Boolean,
},
},
computed: {
classes() {
const { name } = this.$options;
return {
[name]: true,
[`${name}--selected`]: this.selected,
[`${name}--compact`]: this.compact,
};
},
},
methods: {
onClick() {
this.$emit('select', this.color);
},
onChange(d) {
this.$emit('change', new Color(d));
},
},
components: {
ColorInput,
ColorSwatch,
},
};
</script>
<style lang="scss">
@import "../styles/core";
.color-selector {
min-width: 120px;
display: flex;
flex-direction: column;
&.color-selector--selected {
.color-swatch {
box-shadow: inset 0 0 0 8px rgba(0, 0, 0, 0.4);
}
}
&.color-selector--compact {
flex-direction: row;
.color-swatch {
height: auto;
width: 4rem;
}
}
}
</style>
......@@ -6,21 +6,21 @@
</template>
<script>
import Color from '../scripts/Color';
import Color from '../scripts/Color';
export default {
name: 'color-swatch',
props: {
color: Color,
},
computed: {
style() {
return {
backgroundColor: this.color.toRgbString(),
};
export default {
name: 'color-swatch',
props: {
color: Color,
},
},
};
computed: {
style() {
return {
backgroundColor: this.color.toRgbString(),
};
},
},
};
</script>
<style lang="scss">
......
......@@ -28,6 +28,11 @@ export default [
path: '/accessibility',
component: views.AccessibilityView,
},
{
name: 'three',
path: '/three',
component: views.ThreeView,
},
{
name: 'gradients',
path: '/gradients',
......
import Tinycolor from 'tinycolor2';
import tinycolor from 'tinycolor2';
import blinder from 'color-blind';
class Color {
constructor(value) {
this._color = new Tinycolor(value);
constructor(value, isRatio = false) {
this._color = (isRatio)
? tinycolor.fromRatio(value)
: tinycolor(value);
}
toRgbString() {
......@@ -14,6 +16,14 @@ class Color {
return this._color.toRgb();
}
toHexString() {
return this._color.toHexString();
}
toHex() {
return this._color.toHex();
}
deuteranomaly() {
return new Color(
blinder.deuteranomaly(this.toRgbString())
......
/* globals window, document */
import * as THREE from 'three';
import OrbitControls from 'orbit-controls-es6';
import chroma from 'chroma-js';
const times = (count) => [...Array(count).keys()];
class ThreePicker {
static RADIUS = 5;
constructor(element, {
columns = 24,
rows = 32,
useBezier = false,
colors = ['white', 'green', 'black'],
} = {}) {
this.element = element;
this.scene = new THREE.Scene();
this._columns = columns;
this._rows = rows;
this._useBezier = useBezier;
this._colors = colors;
this.setup();
this.draw();
this.render();
}
get radius() {
return this._radius;
}
set radius(v) {
this._radius = v;
this.draw();
}
get columns() {
return this._columns;
}
set columns(v) {
this._columns = v;
this.draw();
}
get rows() {
return this._rows;
}
set rows(v) {
this._rows = v;
this.draw();
}
get useBezier() {
return this._useBezier;
}
set useBezier(v) {
this._useBezier = v;
}
get colors() {
return this._colors;
}
set colors(v) {
this._colors = v;
this.tint();
}
setup() {
const width = 800;
const camera = this.camera = new THREE.PerspectiveCamera(75, 1, 0.1, 50);
camera.position.z = 10;
const renderer = this.renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, width);
renderer.setClearColor(0xffffff, 1);
this.element.appendChild(renderer.domElement);
const light = this.light = new THREE.AmbientLight(0xffffff);
this.scene.add(light);
const orbit = this.orbit = new OrbitControls(camera, renderer.domElement);
orbit.enableZoom = false;
document.addEventListener('pointerdown', this._onPointerDown.bind(this), false);
}
draw() {
this.scene.remove(this.sphere);
const geometry = new THREE.SphereGeometry(
ThreePicker.RADIUS,
this.columns,
this.rows,
);
const material = new THREE.MeshBasicMaterial(
{ color: 0xffffff, vertexColors: THREE.FaceColors }
);
const sphere = this.sphere = new THREE.Mesh(geometry, material);
this.scene.add(sphere);
this.tint();
}
tint() {
const { columns, rows, colors } = this;
const hueStep = 360 / columns;
const { geometry } = this.sphere;
let i = 0;
let j = 0;
let color;
function toRgb(chromaColor) {
return chromaColor.rgb(false).map(v => v / 255);
}
const scales = times(columns).map(i => {
const newColors = [...colors]
.map((c, j) => {
if (j === 0 || j > (colors.length - 2)) {
return c;
}
const hueAngle = i * hueStep;
return chroma(c)
.set('hsl.h', `+${hueAngle}`)
.hex();
});
return chroma
.scale(newColors)
.mode('rgb')
// .correctLightness()
.domain([0, rows]);
});
while (i < columns) {
color = toRgb(scales[i](0));
geometry.faces[i].color.setRGB(...color);
i++;
}
while (i < (geometry.faces.length - columns)) {
const row = Math.floor((i + columns) / (2 * columns));
const column = Math.floor((i + columns) / 2) % columns;
color = toRgb(scales[column](row));
geometry.faces[i].color.setRGB(...color);
geometry.faces[i + 1].color.setRGB(...color);
i += 2;
}
j = 0;
while (i < geometry.faces.length) {
color = toRgb(scales[j++](rows));
geometry.faces[i].color.setRGB(...color);
i++;
}
geometry.colorsNeedUpdate = true;
}
_onPointerDown(event) {
const { camera } = this;
const { devicePixelRatio: ratio } = window;
const rect = this.renderer.domElement.getBoundingClientRect();
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
mouse.x = (((event.clientX - rect.left) / (rect.right - rect.left)) * ratio) - 1;
mouse.y = -(((event.clientY - rect.top) / (rect.bottom - rect.top)) * ratio) + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects([this.sphere]);
if (intersects.length > 0) {
const color = intersects[0].face.color;
this.onColorSelect(color);
}
}
render() {
this.renderer.render(this.scene, this.camera);
window.requestAnimationFrame(this.render.bind(this));
}
/* eslint-disable class-methods-use-this */
onColorSelect(value) {
// callback
}
}
export default ThreePicker;
export default {};
import Color from '../scripts/Color';
export default {
currentColor: (state) => new Color(state.currentColor),
};
import gradients from './gradients';
import palettes from './palettes';
import accessibility from './accessibility';
import three from './three';
export default {
gradients,
palettes,
accessibility,
three,
};
export default {
currentColor(state, getters, rootState) {
return { ...rootState.currentColor };
},
};
export default {};
import Color from '../../../scripts/Color';
export default {
colors: (state) => state.colors.map(d => new Color(d)),
};
import state from './state';
import mutations from './mutations';
import actions from './actions';
import getters from './getters';
export default {
state,
mutations,
actions,
getters,
};
import Color from '../../../scripts/Color';
import types from './types';
export default {
[types.UPDATE_COLORS](state, colors) {
const _colors = colors.map(d => ((d instanceof Color) ? d.toRgb() : d));
state.colors = _colors;
},
};
export default {
colors: [
{ r: 255, g: 255, b: 255 },
{ r: 255, g: 0, b: 0 },
{ r: 0, g: 0, b: 0 },
],
};
export default {
UPDATE_COLORS: 'three/updateColors',
};
/* eslint-disable no-multi-spaces */
import _ from '../utils';
import Color from '../scripts/Color';
import types from './types';
const toValidColor = _.utils.toValidColor;
......@@ -23,7 +23,8 @@ export default {
state.notifications.splice(index, 1);
},
[types.UPDATE_CURRENT_COLOR](state, data) {
state.currentColor = toValidColor(data);
[types.UPDATE_CURRENT_COLOR](state, color) {
// TODO: always accept only color instances
state.currentColor = (color instanceof Color) ? color.toRgb() : color;
},
};
......@@ -10,5 +10,8 @@ export default {
}, {
name: 'Accessibility',
route: 'accessibility',
}, {
name: '3D Space',
route: 'three',
}],
};
import gradients from './modules/gradients/types';
import palettes from './modules/palettes/types';
import accessibility from './modules/accessibility/types';
import three from './modules/three/types';
// TODO: move all mutations to namespaced and add ...
export default {
gradients,
palettes,
...accessibility,
accessibility,
three,
ADD_NOTIFICATION: 'core/addNotification',
DELETE_NOTIFICATION: 'core/deleteNotification',
UPDATE_CURRENT_COLOR: 'core/udpateCurrentColor',
......
import GradientView from './views/GradientView';
import MixerView from './views/MixerView';
import AccessibilityView from './views/AccessibilityView';
import ThreeView from './views/ThreeView';
export default {
GradientView,
MixerView,
AccessibilityView,
ThreeView,
};
......@@ -101,8 +101,8 @@ export default {
},
methods: {
...mapMutations({
setColorA: types.SET_COLOR_A,
setColorB: types.SET_COLOR_B,
setColorA: types.accessibility.SET_COLOR_A,
setColorB: types.accessibility.SET_COLOR_B,
updateCurrentColor: types.UPDATE_CURRENT_COLOR,
}),
setCurrentColor(d) {
......@@ -136,12 +136,6 @@ export default {
ColorSwatch,
AccessibilitySample,
},
watch: {
gradient: {
handler: 'handleGradientChange',
deep: true,
},
},
created() {
this.saveCurrentColor = _.debounce(d => {
this.updateCurrentColor(d);
......
......@@ -299,7 +299,6 @@ export default {
max-width: 100%;
height: 100%;
z-index: 0;
margin-top: 48px;
.ui-input {
&.ui-input--large {
......@@ -327,6 +326,7 @@ export default {
.gradient-view__content {
flex: 1 1;
overflow: auto;
margin-top: 48px;
}
.gradient-view__sidebar {
flex: 0 0 480px;
......
<template>
<div class="three-view">
<div class="three-view__content">
<div
class="three-view__canvas"
ref="canvas"
></div>
<div class="three-view__controls">
<color-selector
v-for="(color, i) in localColors"
:key="i"
:color="color"
compact
:selected="i === selectedColorIndex"
@select="d => onColorSelect(i, d)"
@change="d => onColorChange(i, d)"
></color-selector>
<ui-stepper
icon="segments"
:max="64"
v-model="rows"
></ui-stepper>
<ui-stepper
icon="segments"
:max="64"
v-model="columns"
></ui-stepper>
</div>
</div>
<div class="three-view__sidebar">
<mixer-bar
:value="localCurrentColor.toRgb()"
@color-change="onMixerColorChange"
></mixer-bar>
</div>
</div>
</template>
<script>
/* globals window */
import { mapMutations, mapGetters } from 'vuex';
import _ from '../utils';
import types from '../store/types';
import Color from '../scripts/Color';
import ThreePicker from '../scripts/ThreePicker';
import MixerBar from '../modules/MixerBar';
import ColorSelector from '../components/ColorSelector';
export default {
name: 'three-view',
data() {
return {
selectedColorIndex: null,
localCurrentColor: null,
localColors: [],
rows: 32,
columns: 24,
};
},
computed: {
...mapGetters([
'currentColor',
'colors',
]),
},
methods: {
...mapMutations({
updateCurrentColor: types.UPDATE_CURRENT_COLOR,
updateColors: types.three.UPDATE_COLORS,
}),
setCurrentColor(color) {
console.log(color);
this.localCurrentColor = color;
this.saveCurrentColor();
},
changeColor(i, color) {
this.localColors[i] = color;
this.saveColors();
this.picker.colors = this.localColors.map(d => d.toHex());
},
onMixerColorChange(rgb) {
const color = new Color(rgb);
this.setCurrentColor(color);
this.changeColor(this.selectedColorIndex, color);
},
onColorSelect(i, color) {
this.selectedColorIndex = i;
this.setCurrentColor(color);
},
onColorChange(i, color) {
this.setCurrentColor(color);
this.changeColor(this.selectedColorIndex, color);
},
handleCurrentColorChange(color) {
this.localCurrentColor = color;
},
handleColorsChange(color) {
this.localColors = color;
},
handleRowsChange(v) {
this.picker.rows = v;
},
handleColumnsChange(v) {
this.picker.columns = v;
},
},
watch: {
currentColor: 'handleCurrentColorChange',
colors: 'handleColorsChange',
rows: 'handleRowsChange',
columns: 'handleColumnsChange',
},
components: {
MixerBar,
ColorSelector,
},
created() {
this.saveCurrentColor = _.debounce(d => {
this.updateCurrentColor(this.localCurrentColor);
}, 500);
this.saveColors = _.debounce(() => {
this.updateColors(this.localColors);
}, 500);
this.handleCurrentColorChange(this.currentColor);
this.handleColorsChange(this.colors);
},
mounted() {
const { localColors, rows, columns } = this;
const picker = this.picker = new ThreePicker(this.$refs.canvas, {
rows,
columns,
colors: localColors.map(d => d.toHex()),
});
picker.onColorSelect = (d) => {
const color = new Color(d, true);
this.setCurrentColor(color);
};
window.picker = this.picker;
},
};
</script>
<style lang="scss">
@import "../styles/core";
.three-view {
display: flex;
max-width: 100%;
height: 100%;
z-index: 0;
}
.three-view__content {
flex: 1 1 auto;
overflow: auto;
display: flex;
justify-content: center;
align-items: center;
.color-selector {
margin-bottom: 0.5rem;
}
.ui-stepper {
background-color: #F0F0F0;
padding: 8px;
margin-bottom: 0.5rem;
> .ui-icon {
fill: #808080;
}
.ui-stepper__input {
input {
border: none;
background-color: rgba(white, 0.7);
}
}
}
}
.three-view__canvas {
width: 800px;
height: 800px;
}
.three-view__sidebar {
flex: 0 0 480px;
overflow-x: hidden;
overflow-y: auto;
border-left: 1px solid black;
max-height: 100vh;
}
</style>
This diff is collapsed.