mirror of
https://github.com/directus/directus.git
synced 2026-01-24 10:28:01 -05:00
use lib instead of own functions
This commit is contained in:
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user