use lib instead of own functions

This commit is contained in:
Nitwel
2020-09-21 12:00:08 +02:00
parent f4dee0b10b
commit 614d656a2a
4 changed files with 78 additions and 209 deletions

View File

@@ -1,6 +1,6 @@
<template>
<div class="color-dot">
<value-null v-if="value === null" />
<value-null v-if="value === null && defaultColor === null" />
<div class="dot" :style="styles"></div>
</div>
</template>
@@ -9,7 +9,7 @@
import { defineComponent, computed, PropType } from '@vue/composition-api';
import formatTitle from '@directus/format-title';
import Color from 'color';
import colorString from 'color-string';
import { isHex } from '@/utils/color';
export default defineComponent({
props: {
@@ -20,7 +20,7 @@ export default defineComponent({
defaultColor: {
type: String,
default: '#B0BEC5',
validator: (value: string) => colorString.get.rgb(value) !== null,
validator: (value: string) => value === null || isHex(value),
},
},
setup(props) {
@@ -31,17 +31,14 @@ export default defineComponent({
const styles = computed(() => {
const style: Record<string, any> = { 'background-color': props.defaultColor };
if (Color(props.value) !== undefined) style['background-color'] = props.value;
if (props.value !== null) style['background-color'] = props.value;
const pageColorString = getComputedStyle(document.body).getPropertyValue('--background-page');
const pageColorString = getComputedStyle(document.body).getPropertyValue('--background-page').trim();
const pageColorRGB = colorString.get.rgb(pageColorString) || colorString.get.rgb('#FFF');
const colorRGB = colorString.get.rgb(props.value) || colorString.get.rgb(props.defaultColor);
const pageColorRGB = Color(pageColorString);
const colorRGB = props.value === null ? Color(props.defaultColor) : Color(props.value);
if (pageColorRGB == null || colorRGB == null) return {};
if (Color.rgb(...colorRGB.slice(0.3)).contrast(Color.rgb(...pageColorRGB.slice(0, 3))) < 3)
style['border'] = '1px solid var(--border-normal-alt)';
if (colorRGB.contrast(pageColorRGB) < 3) style['border'] = '1px solid var(--border-normal-alt)';
return style;
});

View File

@@ -4,20 +4,20 @@
<v-input
:disabled="disabled"
:placeholder="$t('interfaces.color.placeholder')"
v-model="hexValue"
v-model="hex"
:pattern="/#([a-f\d]{2}){3}/i"
class="color-input"
maxlength="7"
@focus="menuActive = true"
>
<template #prepend>
<v-input type="color" class="html-color-select" v-model="hexValue" ref="htmlColorInput" />
<v-input type="color" class="html-color-select" v-model="hex" ref="htmlColorInput" />
<v-button
@click="activateColorPicker"
class="swatch"
:icon="true"
:style="{
'--v-button-background-color': isValidColor ? hexValue : 'transparent',
'--v-button-background-color': isValidColor ? hex : 'transparent',
border: isValidColor ? 'none' : 'var(--border-width) solid var(--border-normal)',
}"
>
@@ -36,28 +36,11 @@
</div>
<template v-if="colorType === 'RGB'">
<v-input
:value="rgb.r"
@input="rgb = { ...rgb, r: $event }"
class="color-data-input"
pattern="\d*"
:min="0"
:max="255"
:step="1"
maxlength="3"
/>
<v-input
:value="rgb.g"
@input="rgb = { ...rgb, g: $event }"
class="color-data-input"
pattern="\d*"
:min="0"
:max="255"
:step="1"
maxlength="3"
/>
<v-input
:value="rgb.b"
@input="rgb = { ...rgb, b: $event }"
type="number"
v-for="(val, i) in rgb"
:key="i"
:value="val"
@input="setValue('rgb', i, $event)"
class="color-data-input"
pattern="\d*"
:min="0"
@@ -68,32 +51,15 @@
</template>
<template v-if="colorType === 'HSL'">
<v-input
:value="hsl.h"
@input="hsl = { ...hsl, h: $event }"
type="number"
v-for="(val, i) in hsl"
:key="i"
:value="val"
@input="setValue('hsl', i, $event)"
class="color-data-input"
pattern="\d*"
:min="0"
:max="360"
:step="1"
maxlength="3"
/>
<v-input
:value="hsl.s"
@input="hsl = { ...hsl, s: $event }"
class="color-data-input"
pattern="\d*"
:min="0"
:max="100"
:step="1"
maxlength="3"
/>
<v-input
:value="hsl.l"
@input="hsl = { ...hsl, l: $event }"
class="color-data-input"
pattern="\d*"
:min="0"
:max="100"
:max="i === 1 ? 360 : 100"
:step="1"
maxlength="3"
/>
@@ -108,14 +74,15 @@
icon
:style="{ '--v-button-background-color': preset.color }"
v-tooltip="preset.name"
@click="() => (hexValue = preset.color)"
@click="() => (hex = preset.color)"
/>
</div>
</v-menu>
</template>
<script lang="ts">
import { defineComponent, ref, computed, PropType, watch } from '@vue/composition-api';
import color, { RGB, HSL } from '@/utils/color';
import { isHex } from '@/utils/color';
import Color from 'color';
export default defineComponent({
props: {
@@ -126,7 +93,7 @@ export default defineComponent({
value: {
type: String,
default: null,
validator: (val: string) => val === null || val === '' || color.isHex(val),
validator: (val: string) => val === null || val === '' || isHex(val),
},
presets: {
type: Array as PropType<string[]>,
@@ -185,9 +152,9 @@ export default defineComponent({
(htmlColorInput.value?.$el as HTMLElement).getElementsByTagName('input')[0].click();
}
const isValidColor = computed<boolean>(() => hexValue.value != null && color.isHex(hexValue.value as string));
const isValidColor = computed<boolean>(() => rgb.value != null);
const { rgb, hsl, hexValue } = useColor();
const { hsl, rgb, hex } = useColor();
const menuActive = ref(false);
@@ -196,58 +163,78 @@ export default defineComponent({
colorType,
rgb,
hsl,
hexValue,
hex,
htmlColorInput,
activateColorPicker,
isValidColor,
menuActive,
Color,
setValue,
};
function setValue(type: 'rgb' | 'hsl', i: number, val: number) {
if (type === 'rgb') {
const newArray = [...rgb.value];
newArray[i] = val;
rgb.value = newArray;
} else {
const newArray = [...hsl.value];
newArray[i] = val;
hsl.value = newArray;
}
}
function useColor() {
const hexValue = ref<string | null>(props.value);
const _rgb = ref<Color | null>(null);
watch(hexValue, (newHex) => {
if (newHex === props.value) return;
watch(_rgb, (newColor) => {
if (newColor === null) return emit('input', null);
if (!newHex) emit('input', null);
else if (newHex.length === 0) emit('input', null);
else if (newHex.length === 7) emit('input', newHex);
const hex = newColor.hex();
if (hex.length === 0) emit('input', null);
else emit('input', hex);
});
watch(
() => props.value,
(newValue) => {
if (newValue === hexValue.value) return;
if (newValue !== null && color.isHex(newValue)) {
hexValue.value = props.value;
}
if (newValue === null) {
hexValue.value = null;
}
if (newValue === null) return;
const newColor = Color(newValue);
if (newColor === null || newColor === _rgb.value) return;
_rgb.value = newColor;
}
);
const hsl = computed<HSL<string | null>>({
const rgb = computed<number[]>({
get() {
return color.hexToHsl(hexValue.value);
},
set(newHSL) {
hexValue.value = color.hslToHex(newHSL);
},
});
const rgb = computed<RGB<string | null>>({
get() {
return color.hexToRgb(hexValue.value);
return _rgb.value !== null ? _rgb.value.rgb().array() : [0, 0, 0];
},
set(newRGB) {
hexValue.value = color.rgbToHex(newRGB);
_rgb.value = Color.rgb(newRGB);
},
});
return { rgb, hsl, hexValue };
const hsl = computed<number[]>({
get() {
return _rgb.value !== null ? _rgb.value.hsl().array() : [0, 0, 0];
},
set(newHSL) {
_rgb.value = Color.hsl(newHSL);
},
});
const hex = computed<string | null>({
get() {
return _rgb.value !== null ? _rgb.value.hex() : null;
},
set(newHex) {
if (newHex === null || isHex(newHex) === false) return;
_rgb.value = Color(newHex);
},
});
return { rgb, hsl, hex };
}
},
});

View File

@@ -14,7 +14,7 @@
--background-normal-alt: #DDE3E6;
--background-subdued: #F5F7F8;
--background-highlight: #F9FAFB;
--background-page: #FFF;
--background-page: #FFFFFF;
--background-page-rgb: 255, 255, 255;
--background-inverted: #263238;

View File

@@ -1,122 +1,7 @@
export interface RGB<T> {
r: T;
g: T;
b: T;
}
export interface HSL<T> {
h: T;
s: T;
l: T;
}
export function isNullish(obj: RGB<string | null> | HSL<string | null>): boolean {
return Object.values(obj).every((e) => e === null);
}
export function isEmptyStringIsh(obj: RGB<string | null> | HSL<string | null>): boolean {
return Object.values(obj).every((e) => e === '');
}
export function componentToHex(c: number): string {
if (c > 255) return 'ff';
return c.toString(16).padStart(2, '0').toUpperCase();
}
export function toNum(x: string | number | null): number {
if (typeof x === 'string') {
const res = parseInt(x, 10);
return isNaN(res) ? 0 : res;
} else return x || 0;
}
export function rgbToHex(rgb: RGB<string | null>): string | null {
if (isNullish(rgb)) return null;
if (isEmptyStringIsh(rgb)) return '';
const r = componentToHex(toNum(rgb.r));
const g = componentToHex(toNum(rgb.g));
const b = componentToHex(toNum(rgb.b));
return `#${r}${g}${b}`;
}
export function isHex(hex: string): boolean {
return /^#(([a-f\d]{2}){3,4})$/i.test(hex);
}
export function hexToRgb(hex: string | null): RGB<string | null> {
if (hex === null) return { r: null, g: null, b: null };
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.exec(hex);
if (!result) return { r: '', g: '', b: '' };
const r = parseInt(result[1], 16);
const g = parseInt(result[2], 16);
const b = parseInt(result[3], 16);
const a = result[4] ? parseInt(result[4], 16) / 255 : 1;
return { r: r.toString(), g: g.toString(), b: b.toString() };
}
export function hexToHsl(hex: string | null): HSL<string | null> {
return rgbToHsl(hexToRgb(hex));
}
export function hslToHex(hsl: HSL<string | null>): string | null {
return rgbToHex(hslToRgb(hsl));
}
// r,g,b in [0,255]; h in [0,360); s,l in [0,100]
export function rgbToHsl(rgb: RGB<string | null>): HSL<string | null> {
if (isNullish(rgb)) return { h: null, s: null, l: null };
if (isEmptyStringIsh(rgb)) return { h: '', s: '', l: '' };
let r = toNum(rgb.r);
let g = toNum(rgb.g);
let b = toNum(rgb.b);
// Make r, g, and b fractions of 1
r /= 255;
g /= 255;
b /= 255;
const a = Math.max(r, g, b),
n = a - Math.min(r, g, b),
f = 1 - Math.abs(a + a - n - 1);
let h = n && (a == r ? (g - b) / n : a == g ? 2 + (b - r) / n : 4 + (r - g) / n);
h = 60 * (h < 0 ? h + 6 : h);
let s = f ? n / f : 0;
let l = (a + a - n) / 2;
s *= 100;
l *= 100;
return {
h: Math.round(h).toString(),
s: Math.round(s).toString(),
l: Math.round(l).toString(),
};
}
// h in [0, 360); s,l in [0,100]
export function hslToRgb(hsl: HSL<string | null>): RGB<string | null> {
if (isNullish(hsl)) return { r: null, g: null, b: null };
if (isEmptyStringIsh(hsl)) return { r: '', g: '', b: '' };
const h = toNum(hsl.h);
let s = toNum(hsl.s);
let l = toNum(hsl.l);
// Must be fractions of 1
s /= 100;
l /= 100;
const a = s * Math.min(l, 1 - l);
const f = (n: number, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
const r = Math.round(f(0) * 255);
const g = Math.round(f(8) * 255);
const b = Math.round(f(4) * 255);
return { r: r.toString(), g: g.toString(), b: b.toString() };
}
export default {
componentToHex,
isHex,
rgbToHex,
hexToRgb,
rgbToHsl,
hslToRgb,
hexToHsl,
hslToHex,
};