forked from AkkomaGang/akkoma-fe
Refactoring, forgotten files
This commit is contained in:
parent
38f2b969e4
commit
622c9d388e
5 changed files with 577 additions and 421 deletions
65
src/components/color_input/color_input.scss
Normal file
65
src/components/color_input/color_input.scss
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.color-input {
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
&-field.input {
|
||||||
|
display: inline-flex;
|
||||||
|
flex: 0 0 0;
|
||||||
|
max-width: 9em;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: .2em 8px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
background: none;
|
||||||
|
color: $fallback--lightText;
|
||||||
|
color: var(--inputText, $fallback--lightText);
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&.textColor {
|
||||||
|
flex: 1 0 3em;
|
||||||
|
min-width: 3em;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.nativeColor {
|
||||||
|
flex: 0 0 2em;
|
||||||
|
min-width: 2em;
|
||||||
|
align-self: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.transparentIndicator {
|
||||||
|
flex: 0 0 2em;
|
||||||
|
min-width: 2em;
|
||||||
|
align-self: center;
|
||||||
|
height: 100%;
|
||||||
|
// forgot to install counter-strike source, ooops
|
||||||
|
background-color: #FF00FF;
|
||||||
|
position: relative;
|
||||||
|
&::before, &::after {
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
background-color: #000000;
|
||||||
|
position: absolute;
|
||||||
|
height: 50%;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
&::before {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,9 +1,16 @@
|
||||||
import { map } from 'lodash'
|
import { invertLightness, convert, contrastRatio } from 'chromatism'
|
||||||
|
|
||||||
// useful for visualizing color when debugging
|
// useful for visualizing color when debugging
|
||||||
export const consoleColor = (color) => console.log('%c##########', 'background: ' + color + '; color: ' + color)
|
export const consoleColor = (color) => console.log('%c##########', 'background: ' + color + '; color: ' + color)
|
||||||
|
|
||||||
const rgb2hex = (r, g, b) => {
|
/**
|
||||||
|
* Convert r, g, b values into hex notation. All components are [0-255]
|
||||||
|
*
|
||||||
|
* @param {Number|String|Object} r - Either red component, {r,g,b} object, or hex string
|
||||||
|
* @param {Number} [g] - Green component
|
||||||
|
* @param {Number} [b] - Blue component
|
||||||
|
*/
|
||||||
|
export const rgb2hex = (r, g, b) => {
|
||||||
if (r === null || typeof r === 'undefined') {
|
if (r === null || typeof r === 'undefined') {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -14,7 +21,7 @@ const rgb2hex = (r, g, b) => {
|
||||||
if (typeof r === 'object') {
|
if (typeof r === 'object') {
|
||||||
({ r, g, b } = r)
|
({ r, g, b } = r)
|
||||||
}
|
}
|
||||||
[r, g, b] = map([r, g, b], (val) => {
|
[r, g, b] = [r, g, b].map(val => {
|
||||||
val = Math.ceil(val)
|
val = Math.ceil(val)
|
||||||
val = val < 0 ? 0 : val
|
val = val < 0 ? 0 : val
|
||||||
val = val > 255 ? 255 : val
|
val = val > 255 ? 255 : val
|
||||||
|
@ -82,6 +89,7 @@ const getContrastRatio = (a, b) => {
|
||||||
|
|
||||||
return (l1 + 0.05) / (l2 + 0.05)
|
return (l1 + 0.05) / (l2 + 0.05)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as `getContrastRatio` but for multiple layers in-between
|
* Same as `getContrastRatio` but for multiple layers in-between
|
||||||
*
|
*
|
||||||
|
@ -101,7 +109,7 @@ export const getContrastRatioLayers = (text, layers, bedrock) => {
|
||||||
* @param {Object} bg - bottom layer color
|
* @param {Object} bg - bottom layer color
|
||||||
* @returns {Object} sRGB of resulting color
|
* @returns {Object} sRGB of resulting color
|
||||||
*/
|
*/
|
||||||
const alphaBlend = (fg, fga, bg) => {
|
export const alphaBlend = (fg, fga, bg) => {
|
||||||
if (fga === 1 || typeof fga === 'undefined') return fg
|
if (fga === 1 || typeof fga === 'undefined') return fg
|
||||||
return 'rgb'.split('').reduce((acc, c) => {
|
return 'rgb'.split('').reduce((acc, c) => {
|
||||||
// Simplified https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
|
// Simplified https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
|
||||||
|
@ -121,14 +129,20 @@ export const alphaBlendLayers = (bedrock, layers) => layers.reduce((acc, [color,
|
||||||
return alphaBlend(color, opacity, acc)
|
return alphaBlend(color, opacity, acc)
|
||||||
}, bedrock)
|
}, bedrock)
|
||||||
|
|
||||||
const invert = (rgb) => {
|
export const invert = (rgb) => {
|
||||||
return 'rgb'.split('').reduce((acc, c) => {
|
return 'rgb'.split('').reduce((acc, c) => {
|
||||||
acc[c] = 255 - rgb[c]
|
acc[c] = 255 - rgb[c]
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const hex2rgb = (hex) => {
|
/**
|
||||||
|
* Converts #rrggbb hex notation into an {r, g, b} object
|
||||||
|
*
|
||||||
|
* @param {String} hex - #rrggbb string
|
||||||
|
* @returns {Object} rgb representation of the color, values are 0-255
|
||||||
|
*/
|
||||||
|
export const hex2rgb = (hex) => {
|
||||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||||
return result ? {
|
return result ? {
|
||||||
r: parseInt(result[1], 16),
|
r: parseInt(result[1], 16),
|
||||||
|
@ -137,18 +151,75 @@ const hex2rgb = (hex) => {
|
||||||
} : null
|
} : null
|
||||||
}
|
}
|
||||||
|
|
||||||
const mixrgb = (a, b) => {
|
/**
|
||||||
|
* Old somewhat weird function for mixing two colors together
|
||||||
|
*
|
||||||
|
* @param {Object} a - one color (rgb)
|
||||||
|
* @param {Object} b - other color (rgb)
|
||||||
|
* @returns {Object} result
|
||||||
|
*/
|
||||||
|
export const mixrgb = (a, b) => {
|
||||||
return Object.keys(a).reduce((acc, k) => {
|
return Object.keys(a).reduce((acc, k) => {
|
||||||
acc[k] = (a[k] + b[k]) / 2
|
acc[k] = (a[k] + b[k]) / 2
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
export {
|
* Converts rgb object into a CSS rgba() color
|
||||||
rgb2hex,
|
*
|
||||||
hex2rgb,
|
* @param {Object} color - rgb
|
||||||
mixrgb,
|
* @returns {String} CSS rgba() color
|
||||||
invert,
|
*/
|
||||||
getContrastRatio,
|
export const rgba2css = function (rgba) {
|
||||||
alphaBlend
|
return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get text color for given background color and intended text color
|
||||||
|
* This checks if text and background don't have enough color and inverts
|
||||||
|
* text color's lightness if needed. If text color is still not enough it
|
||||||
|
* will fall back to black or white
|
||||||
|
*
|
||||||
|
* @param {Object} bg - background color
|
||||||
|
* @param {Object} text - intended text color
|
||||||
|
* @param {Boolean} preserve - try to preserve intended text color's hue/saturation (i.e. no BW)
|
||||||
|
*/
|
||||||
|
export const getTextColor = function (bg, text, preserve) {
|
||||||
|
const bgIsLight = convert(bg).hsl.l > 50
|
||||||
|
const textIsLight = convert(text).hsl.l > 50
|
||||||
|
|
||||||
|
if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) {
|
||||||
|
const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
|
||||||
|
const result = Object.assign(base, invertLightness(text).rgb)
|
||||||
|
if (!preserve && getContrastRatio(bg, result) < 4.5) {
|
||||||
|
// B&W
|
||||||
|
return contrastRatio(bg, text).rgb
|
||||||
|
}
|
||||||
|
// Inverted color
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts color to CSS Color value
|
||||||
|
*
|
||||||
|
* @param {Object|String} input - color
|
||||||
|
* @param {Number} [a] - alpha value
|
||||||
|
* @returns {String} a CSS Color value
|
||||||
|
*/
|
||||||
|
export const getCssColor = (input, a) => {
|
||||||
|
let rgb = {}
|
||||||
|
if (typeof input === 'object') {
|
||||||
|
rgb = input
|
||||||
|
} else if (typeof input === 'string') {
|
||||||
|
if (input.startsWith('#')) {
|
||||||
|
rgb = hex2rgb(input)
|
||||||
|
} else if (input.startsWith('--')) {
|
||||||
|
return `var(${input})`
|
||||||
|
} else {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rgba2css({ ...rgb, a })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,275 +1,13 @@
|
||||||
import { times } from 'lodash'
|
import { times } from 'lodash'
|
||||||
import { brightness, invertLightness, convert, contrastRatio } from 'chromatism'
|
import { convert } from 'chromatism'
|
||||||
import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend, alphaBlendLayers } from '../color_convert/color_convert.js'
|
import { rgb2hex, hex2rgb, rgba2css, getCssColor } from '../color_convert/color_convert.js'
|
||||||
|
import { getColors } from '../theme_data/theme_data.service.js'
|
||||||
export const CURRENT_VERSION = 3
|
|
||||||
/* This is a definition of all layer combinations
|
|
||||||
* each key is a topmost layer, each value represents layer underneath
|
|
||||||
* this is essentially a simplified tree
|
|
||||||
*/
|
|
||||||
export const LAYERS = {
|
|
||||||
undelay: null, // root
|
|
||||||
topBar: null, // no transparency support
|
|
||||||
badge: null, // no transparency support
|
|
||||||
fg: null,
|
|
||||||
bg: 'underlay',
|
|
||||||
panel: 'bg',
|
|
||||||
btn: 'bg',
|
|
||||||
btnPanel: 'panel',
|
|
||||||
btnTopBar: 'topBar',
|
|
||||||
input: 'bg',
|
|
||||||
inputPanel: 'panel',
|
|
||||||
inputTopBar: 'topBar',
|
|
||||||
alert: 'bg',
|
|
||||||
alertPanel: 'panel'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SLOT_INHERITANCE = {
|
|
||||||
bg: null,
|
|
||||||
fg: null,
|
|
||||||
text: null,
|
|
||||||
underlay: '#000000',
|
|
||||||
link: '--accent',
|
|
||||||
accent: '--link',
|
|
||||||
faint: '--text',
|
|
||||||
faintLink: '--link',
|
|
||||||
|
|
||||||
cBlue: '#0000ff',
|
|
||||||
cRed: '#FF0000',
|
|
||||||
cGreen: '#00FF00',
|
|
||||||
cOrange: '#E3FF00',
|
|
||||||
|
|
||||||
lightBg: {
|
|
||||||
depends: ['bg'],
|
|
||||||
color: (mod, bg) => brightness(5 * mod, bg).rgb
|
|
||||||
},
|
|
||||||
lightText: {
|
|
||||||
depends: ['text'],
|
|
||||||
color: (mod, text) => brightness(20 * mod, text).rgb
|
|
||||||
},
|
|
||||||
|
|
||||||
border: {
|
|
||||||
depends: 'fg',
|
|
||||||
color: (mod, fg) => brightness(2 * mod, fg).rgb
|
|
||||||
},
|
|
||||||
|
|
||||||
linkBg: {
|
|
||||||
depends: ['accent', 'bg'],
|
|
||||||
color: (mod, accent, bg) => alphaBlend(accent, 0.4, bg).rgb
|
|
||||||
},
|
|
||||||
|
|
||||||
icon: {
|
|
||||||
depends: ['bg', 'text'],
|
|
||||||
color: (mod, bg, text) => mixrgb(bg, text)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Foreground
|
|
||||||
fgText: {
|
|
||||||
depends: ['text'],
|
|
||||||
layer: 'fg',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
fgLink: {
|
|
||||||
depends: ['link'],
|
|
||||||
layer: 'fg',
|
|
||||||
textColor: 'preserve'
|
|
||||||
},
|
|
||||||
|
|
||||||
// Panel header
|
|
||||||
panel: '--fg',
|
|
||||||
panelText: {
|
|
||||||
depends: ['fgText'],
|
|
||||||
layer: 'panel',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
panelFaint: {
|
|
||||||
depends: ['fgText'],
|
|
||||||
layer: 'panel',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
panelLink: {
|
|
||||||
depends: ['fgLink'],
|
|
||||||
layer: 'panel',
|
|
||||||
textColor: 'preserve'
|
|
||||||
},
|
|
||||||
|
|
||||||
// Top bar
|
|
||||||
topBar: '--fg',
|
|
||||||
topBarText: {
|
|
||||||
depends: ['fgText'],
|
|
||||||
layer: 'topBar',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
topBarLink: {
|
|
||||||
depends: ['fgLink'],
|
|
||||||
layer: 'topBar',
|
|
||||||
textColor: 'preserve'
|
|
||||||
},
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
btn: '--fg',
|
|
||||||
btnText: {
|
|
||||||
depends: ['fgText'],
|
|
||||||
layer: 'btn'
|
|
||||||
},
|
|
||||||
btnPanelText: {
|
|
||||||
depends: ['panelText'],
|
|
||||||
layer: 'btnPanel',
|
|
||||||
variant: 'btn',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
btnTopBarText: {
|
|
||||||
depends: ['topBarText'],
|
|
||||||
layer: 'btnTopBar',
|
|
||||||
variant: 'btn',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
|
|
||||||
// Input fields
|
|
||||||
input: '--fg',
|
|
||||||
inputText: {
|
|
||||||
depends: ['text'],
|
|
||||||
layer: 'input',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
inputPanelText: {
|
|
||||||
depends: ['panelText'],
|
|
||||||
layer: 'inputPanel',
|
|
||||||
variant: 'input',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
inputTopbarText: {
|
|
||||||
depends: ['topBarText'],
|
|
||||||
layer: 'inputTopBar',
|
|
||||||
variant: 'input',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
|
|
||||||
alertError: '--cRed',
|
|
||||||
alertErrorText: {
|
|
||||||
depends: ['text', 'alertError'],
|
|
||||||
layer: 'alert',
|
|
||||||
variant: 'alertError',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
alertErrorPanelText: {
|
|
||||||
depends: ['panelText', 'alertError'],
|
|
||||||
layer: 'alertPanel',
|
|
||||||
variant: 'alertError',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
|
|
||||||
alertWarning: '--cOrange',
|
|
||||||
alertWarningText: {
|
|
||||||
depends: ['text', 'alertWarning'],
|
|
||||||
layer: 'alert',
|
|
||||||
variant: 'alertWarning',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
alertWarningPanelText: {
|
|
||||||
depends: ['panelText', 'alertWarning'],
|
|
||||||
layer: 'alertPanel',
|
|
||||||
variant: 'alertWarning',
|
|
||||||
textColor: true
|
|
||||||
},
|
|
||||||
|
|
||||||
badgeNotification: '--cRed',
|
|
||||||
badgeNotificationText: {
|
|
||||||
depends: ['text', 'badgeNotification'],
|
|
||||||
layer: 'badge',
|
|
||||||
variant: 'badgeNotification',
|
|
||||||
textColor: 'bw'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getLayersArray = (layer, data = LAYERS) => {
|
|
||||||
let array = [layer]
|
|
||||||
let parent = data[layer]
|
|
||||||
while (parent) {
|
|
||||||
array.unshift(parent)
|
|
||||||
parent = data[parent]
|
|
||||||
}
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getLayers = (layer, variant = layer, colors, opacity) => {
|
|
||||||
return getLayersArray(layer).map((currentLayer) => ([
|
|
||||||
currentLayer === layer
|
|
||||||
? colors[variant]
|
|
||||||
: colors[currentLayer],
|
|
||||||
opacity[currentLayer]
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDependencies = (key, inheritance) => {
|
|
||||||
const data = inheritance[key]
|
|
||||||
if (typeof data === 'string' && data.startsWith('--')) {
|
|
||||||
return [data.substring(2)]
|
|
||||||
} else {
|
|
||||||
if (data === null) return []
|
|
||||||
const { depends, layer, variant } = data
|
|
||||||
const layerDeps = layer
|
|
||||||
? getLayersArray(layer).map(currentLayer => {
|
|
||||||
return currentLayer === layer
|
|
||||||
? variant || layer
|
|
||||||
: currentLayer
|
|
||||||
})
|
|
||||||
: []
|
|
||||||
if (Array.isArray(depends)) {
|
|
||||||
return [...depends, ...layerDeps]
|
|
||||||
} else {
|
|
||||||
return [...layerDeps]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const topoSort = (
|
|
||||||
inheritance = SLOT_INHERITANCE,
|
|
||||||
getDeps = getDependencies
|
|
||||||
) => {
|
|
||||||
// This is an implementation of https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
|
|
||||||
|
|
||||||
const allKeys = Object.keys(inheritance)
|
|
||||||
const whites = new Set(allKeys)
|
|
||||||
const grays = new Set()
|
|
||||||
const blacks = new Set()
|
|
||||||
const unprocessed = [...allKeys]
|
|
||||||
const output = []
|
|
||||||
|
|
||||||
const step = (node) => {
|
|
||||||
if (whites.has(node)) {
|
|
||||||
// Make node "gray"
|
|
||||||
whites.delete(node)
|
|
||||||
grays.add(node)
|
|
||||||
// Do step for each node connected to it (one way)
|
|
||||||
getDeps(node, inheritance).forEach(step)
|
|
||||||
// Make node "black"
|
|
||||||
grays.delete(node)
|
|
||||||
blacks.add(node)
|
|
||||||
// Put it into the output list
|
|
||||||
output.push(node)
|
|
||||||
} else if (grays.has(node)) {
|
|
||||||
console.debug('Cyclic depenency in topoSort, ignoring')
|
|
||||||
output.push(node)
|
|
||||||
} else if (blacks.has(node)) {
|
|
||||||
// do nothing
|
|
||||||
} else {
|
|
||||||
throw new Error('Unintended condition in topoSort!')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (unprocessed.length > 0) {
|
|
||||||
step(unprocessed.pop())
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SLOT_ORDERED = topoSort(SLOT_INHERITANCE)
|
|
||||||
|
|
||||||
// While this is not used anymore right now, I left it in if we want to do custom
|
// While this is not used anymore right now, I left it in if we want to do custom
|
||||||
// styles that aren't just colors, so user can pick from a few different distinct
|
// styles that aren't just colors, so user can pick from a few different distinct
|
||||||
// styles as well as set their own colors in the future.
|
// styles as well as set their own colors in the future.
|
||||||
|
|
||||||
const setStyle = (href, commit) => {
|
export const setStyle = (href, commit) => {
|
||||||
/***
|
/***
|
||||||
What's going on here?
|
What's going on here?
|
||||||
I want to make it easy for admins to style this application. To have
|
I want to make it easy for admins to style this application. To have
|
||||||
|
@ -315,30 +53,7 @@ const setStyle = (href, commit) => {
|
||||||
cssEl.addEventListener('load', setDynamic)
|
cssEl.addEventListener('load', setDynamic)
|
||||||
}
|
}
|
||||||
|
|
||||||
const rgb2rgba = function (rgba) {
|
export const applyTheme = (input, commit) => {
|
||||||
return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTextColor = function (bg, text, preserve) {
|
|
||||||
const bgIsLight = convert(bg).hsl.l > 50
|
|
||||||
const textIsLight = convert(text).hsl.l > 50
|
|
||||||
|
|
||||||
console.log(bgIsLight, textIsLight)
|
|
||||||
|
|
||||||
if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) {
|
|
||||||
const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
|
|
||||||
const result = Object.assign(base, invertLightness(text).rgb)
|
|
||||||
if (!preserve && getContrastRatio(bg, result) < 4.5) {
|
|
||||||
// B&W
|
|
||||||
return contrastRatio(bg, text).rgb
|
|
||||||
}
|
|
||||||
// Inverted color
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
const applyTheme = (input, commit) => {
|
|
||||||
const { rules, theme } = generatePreset(input)
|
const { rules, theme } = generatePreset(input)
|
||||||
const head = document.head
|
const head = document.head
|
||||||
const body = document.body
|
const body = document.body
|
||||||
|
@ -399,22 +114,6 @@ const getCssShadowFilter = (input) => {
|
||||||
.join(' ')
|
.join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCssColor = (input, a) => {
|
|
||||||
let rgb = {}
|
|
||||||
if (typeof input === 'object') {
|
|
||||||
rgb = input
|
|
||||||
} else if (typeof input === 'string') {
|
|
||||||
if (input.startsWith('#')) {
|
|
||||||
rgb = hex2rgb(input)
|
|
||||||
} else if (input.startsWith('--')) {
|
|
||||||
return `var(${input})`
|
|
||||||
} else {
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rgb2rgba({ ...rgb, a })
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateColors = (themeData) => {
|
const generateColors = (themeData) => {
|
||||||
const rawOpacity = Object.assign({
|
const rawOpacity = Object.assign({
|
||||||
panel: 1,
|
panel: 1,
|
||||||
|
@ -435,14 +134,16 @@ const generateColors = (themeData) => {
|
||||||
}, {}))
|
}, {}))
|
||||||
|
|
||||||
const inputColors = themeData.colors || themeData
|
const inputColors = themeData.colors || themeData
|
||||||
const transparentsOpacity = Object.entries(inputColors).reduce((acc, [k, v]) => {
|
|
||||||
|
const opacity = {
|
||||||
|
...rawOpacity,
|
||||||
|
...Object.entries(inputColors).reduce((acc, [k, v]) => {
|
||||||
if (v === 'transparent') {
|
if (v === 'transparent') {
|
||||||
acc[k] = 0
|
acc[k] = 0
|
||||||
}
|
}
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
|
}
|
||||||
const opacity = { ...rawOpacity, ...transparentsOpacity }
|
|
||||||
|
|
||||||
// Cycle one: just whatever we have
|
// Cycle one: just whatever we have
|
||||||
const sourceColors = Object.entries(inputColors).reduce((acc, [k, v]) => {
|
const sourceColors = Object.entries(inputColors).reduce((acc, [k, v]) => {
|
||||||
|
@ -462,55 +163,7 @@ const generateColors = (themeData) => {
|
||||||
const isLightOnDark = convert(sourceColors.bg).hsl.l < convert(sourceColors.text).hsl.l
|
const isLightOnDark = convert(sourceColors.bg).hsl.l < convert(sourceColors.text).hsl.l
|
||||||
const mod = isLightOnDark ? 1 : -1
|
const mod = isLightOnDark ? 1 : -1
|
||||||
|
|
||||||
const colors = SLOT_ORDERED.reduce((acc, key) => {
|
const colors = getColors(sourceColors, opacity, mod)
|
||||||
const value = SLOT_INHERITANCE[key]
|
|
||||||
if (sourceColors[key]) {
|
|
||||||
return { ...acc, [key]: { ...sourceColors[key] } }
|
|
||||||
} else if (typeof value === 'string' && value.startsWith('#')) {
|
|
||||||
return { ...acc, [key]: convert(value).rgb }
|
|
||||||
} else {
|
|
||||||
const isObject = typeof value === 'object'
|
|
||||||
const defaultColorFunc = (mod, dep) => ({ ...dep })
|
|
||||||
const deps = getDependencies(key, SLOT_INHERITANCE)
|
|
||||||
const colorFunc = (isObject && value.color) || defaultColorFunc
|
|
||||||
|
|
||||||
if (value.textColor) {
|
|
||||||
const bg = alphaBlendLayers(
|
|
||||||
{ ...acc[deps[0]] },
|
|
||||||
getLayers(
|
|
||||||
value.layer,
|
|
||||||
value.variant || value.layer,
|
|
||||||
acc,
|
|
||||||
opacity
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (value.textColor === 'bw') {
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
[key]: contrastRatio(bg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
[key]: getTextColor(
|
|
||||||
bg,
|
|
||||||
{ ...acc[deps[0]] },
|
|
||||||
value.textColor === 'preserve'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('BENIS', key, deps, deps.map((dep) => ({ ...acc[dep] })))
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
[key]: colorFunc(
|
|
||||||
mod,
|
|
||||||
...deps.map((dep) => ({ ...acc[dep] }))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
// Inheriting opacities
|
// Inheriting opacities
|
||||||
Object.entries(opacity).forEach(([ k, v ]) => {
|
Object.entries(opacity).forEach(([ k, v ]) => {
|
||||||
|
@ -541,7 +194,7 @@ const generateColors = (themeData) => {
|
||||||
.reduce((acc, [k, v]) => {
|
.reduce((acc, [k, v]) => {
|
||||||
if (!v) return acc
|
if (!v) return acc
|
||||||
acc.solid[k] = rgb2hex(v)
|
acc.solid[k] = rgb2hex(v)
|
||||||
acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgb2rgba(v)
|
acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgba2css(v)
|
||||||
return acc
|
return acc
|
||||||
}, { complete: {}, solid: {} })
|
}, { complete: {}, solid: {} })
|
||||||
return {
|
return {
|
||||||
|
@ -740,14 +393,12 @@ const composePreset = (colors, radii, shadows, fonts) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const generatePreset = (input) => {
|
const generatePreset = (input) => composePreset(
|
||||||
const shadows = generateShadows(input)
|
generateColors(input),
|
||||||
const colors = generateColors(input)
|
generateRadii(input),
|
||||||
const radii = generateRadii(input)
|
generateShadows(input),
|
||||||
const fonts = generateFonts(input)
|
generateFonts(input)
|
||||||
|
)
|
||||||
return composePreset(colors, radii, shadows, fonts)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getThemes = () => {
|
const getThemes = () => {
|
||||||
return window.fetch('/static/styles.json')
|
return window.fetch('/static/styles.json')
|
||||||
|
@ -779,33 +430,24 @@ const getThemes = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const setPreset = (val, commit) => {
|
export const setPreset = (val, commit) => {
|
||||||
return getThemes().then((themes) => {
|
return getThemes().then((themes) => {
|
||||||
const theme = themes[val] ? themes[val] : themes['pleroma-dark']
|
const theme = themes[val] ? themes[val] : themes['pleroma-dark']
|
||||||
const isV1 = Array.isArray(theme)
|
const isV1 = Array.isArray(theme)
|
||||||
const data = isV1 ? {} : theme.theme
|
const data = isV1 ? {} : theme.theme
|
||||||
|
|
||||||
if (isV1) {
|
if (isV1) {
|
||||||
const bgRgb = hex2rgb(theme[1])
|
const bg = hex2rgb(theme[1])
|
||||||
const fgRgb = hex2rgb(theme[2])
|
const fg = hex2rgb(theme[2])
|
||||||
const textRgb = hex2rgb(theme[3])
|
const text = hex2rgb(theme[3])
|
||||||
const linkRgb = hex2rgb(theme[4])
|
const link = hex2rgb(theme[4])
|
||||||
|
|
||||||
const cRedRgb = hex2rgb(theme[5] || '#FF0000')
|
const cRed = hex2rgb(theme[5] || '#FF0000')
|
||||||
const cGreenRgb = hex2rgb(theme[6] || '#00FF00')
|
const cGreen = hex2rgb(theme[6] || '#00FF00')
|
||||||
const cBlueRgb = hex2rgb(theme[7] || '#0000FF')
|
const cBlue = hex2rgb(theme[7] || '#0000FF')
|
||||||
const cOrangeRgb = hex2rgb(theme[8] || '#E3FF00')
|
const cOrange = hex2rgb(theme[8] || '#E3FF00')
|
||||||
|
|
||||||
data.colors = {
|
data.colors = { bg, fg, text, link, cRed, cBlue, cGreen, cOrange }
|
||||||
bg: bgRgb,
|
|
||||||
fg: fgRgb,
|
|
||||||
text: textRgb,
|
|
||||||
link: linkRgb,
|
|
||||||
cRed: cRedRgb,
|
|
||||||
cBlue: cBlueRgb,
|
|
||||||
cGreen: cGreenRgb,
|
|
||||||
cOrange: cOrangeRgb
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a hack, this function is only called during initial load.
|
// This is a hack, this function is only called during initial load.
|
||||||
|
@ -819,19 +461,3 @@ const setPreset = (val, commit) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
setStyle,
|
|
||||||
setPreset,
|
|
||||||
applyTheme,
|
|
||||||
getTextColor,
|
|
||||||
generateColors,
|
|
||||||
generateRadii,
|
|
||||||
generateShadows,
|
|
||||||
generateFonts,
|
|
||||||
generatePreset,
|
|
||||||
getThemes,
|
|
||||||
composePreset,
|
|
||||||
getCssShadow,
|
|
||||||
getCssShadowFilter
|
|
||||||
}
|
|
||||||
|
|
315
src/services/theme_data/theme_data.service.js
Normal file
315
src/services/theme_data/theme_data.service.js
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
import { convert, brightness, contrastRatio } from 'chromatism'
|
||||||
|
import { alphaBlend, alphaBlendLayers, getTextColor, mixrgb } from '../color_convert/color_convert.js'
|
||||||
|
|
||||||
|
export const CURRENT_VERSION = 3
|
||||||
|
/* This is a definition of all layer combinations
|
||||||
|
* each key is a topmost layer, each value represents layer underneath
|
||||||
|
* this is essentially a simplified tree
|
||||||
|
*/
|
||||||
|
export const LAYERS = {
|
||||||
|
undelay: null, // root
|
||||||
|
topBar: null, // no transparency support
|
||||||
|
badge: null, // no transparency support
|
||||||
|
fg: null,
|
||||||
|
bg: 'underlay',
|
||||||
|
panel: 'bg',
|
||||||
|
btn: 'bg',
|
||||||
|
btnPanel: 'panel',
|
||||||
|
btnTopBar: 'topBar',
|
||||||
|
input: 'bg',
|
||||||
|
inputPanel: 'panel',
|
||||||
|
inputTopBar: 'topBar',
|
||||||
|
alert: 'bg',
|
||||||
|
alertPanel: 'panel'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SLOT_INHERITANCE = {
|
||||||
|
bg: null,
|
||||||
|
fg: null,
|
||||||
|
text: null,
|
||||||
|
underlay: '#000000',
|
||||||
|
link: '--accent',
|
||||||
|
accent: '--link',
|
||||||
|
faint: '--text',
|
||||||
|
faintLink: '--link',
|
||||||
|
|
||||||
|
cBlue: '#0000ff',
|
||||||
|
cRed: '#FF0000',
|
||||||
|
cGreen: '#00FF00',
|
||||||
|
cOrange: '#E3FF00',
|
||||||
|
|
||||||
|
lightBg: {
|
||||||
|
depends: ['bg'],
|
||||||
|
color: (mod, bg) => brightness(5 * mod, bg).rgb
|
||||||
|
},
|
||||||
|
lightText: {
|
||||||
|
depends: ['text'],
|
||||||
|
color: (mod, text) => brightness(20 * mod, text).rgb
|
||||||
|
},
|
||||||
|
|
||||||
|
border: {
|
||||||
|
depends: 'fg',
|
||||||
|
color: (mod, fg) => brightness(2 * mod, fg).rgb
|
||||||
|
},
|
||||||
|
|
||||||
|
linkBg: {
|
||||||
|
depends: ['accent', 'bg'],
|
||||||
|
color: (mod, accent, bg) => alphaBlend(accent, 0.4, bg).rgb
|
||||||
|
},
|
||||||
|
|
||||||
|
icon: {
|
||||||
|
depends: ['bg', 'text'],
|
||||||
|
color: (mod, bg, text) => mixrgb(bg, text)
|
||||||
|
},
|
||||||
|
|
||||||
|
// Foreground
|
||||||
|
fgText: {
|
||||||
|
depends: ['text'],
|
||||||
|
layer: 'fg',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
fgLink: {
|
||||||
|
depends: ['link'],
|
||||||
|
layer: 'fg',
|
||||||
|
textColor: 'preserve'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Panel header
|
||||||
|
panel: '--fg',
|
||||||
|
panelText: {
|
||||||
|
depends: ['fgText'],
|
||||||
|
layer: 'panel',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
panelFaint: {
|
||||||
|
depends: ['fgText'],
|
||||||
|
layer: 'panel',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
panelLink: {
|
||||||
|
depends: ['fgLink'],
|
||||||
|
layer: 'panel',
|
||||||
|
textColor: 'preserve'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Top bar
|
||||||
|
topBar: '--fg',
|
||||||
|
topBarText: {
|
||||||
|
depends: ['fgText'],
|
||||||
|
layer: 'topBar',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
topBarLink: {
|
||||||
|
depends: ['fgLink'],
|
||||||
|
layer: 'topBar',
|
||||||
|
textColor: 'preserve'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
btn: '--fg',
|
||||||
|
btnText: {
|
||||||
|
depends: ['fgText'],
|
||||||
|
layer: 'btn'
|
||||||
|
},
|
||||||
|
btnPanelText: {
|
||||||
|
depends: ['panelText'],
|
||||||
|
layer: 'btnPanel',
|
||||||
|
variant: 'btn',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
btnTopBarText: {
|
||||||
|
depends: ['topBarText'],
|
||||||
|
layer: 'btnTopBar',
|
||||||
|
variant: 'btn',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Input fields
|
||||||
|
input: '--fg',
|
||||||
|
inputText: {
|
||||||
|
depends: ['text'],
|
||||||
|
layer: 'input',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
inputPanelText: {
|
||||||
|
depends: ['panelText'],
|
||||||
|
layer: 'inputPanel',
|
||||||
|
variant: 'input',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
inputTopbarText: {
|
||||||
|
depends: ['topBarText'],
|
||||||
|
layer: 'inputTopBar',
|
||||||
|
variant: 'input',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
|
||||||
|
alertError: '--cRed',
|
||||||
|
alertErrorText: {
|
||||||
|
depends: ['text', 'alertError'],
|
||||||
|
layer: 'alert',
|
||||||
|
variant: 'alertError',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
alertErrorPanelText: {
|
||||||
|
depends: ['panelText', 'alertError'],
|
||||||
|
layer: 'alertPanel',
|
||||||
|
variant: 'alertError',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
|
||||||
|
alertWarning: '--cOrange',
|
||||||
|
alertWarningText: {
|
||||||
|
depends: ['text', 'alertWarning'],
|
||||||
|
layer: 'alert',
|
||||||
|
variant: 'alertWarning',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
alertWarningPanelText: {
|
||||||
|
depends: ['panelText', 'alertWarning'],
|
||||||
|
layer: 'alertPanel',
|
||||||
|
variant: 'alertWarning',
|
||||||
|
textColor: true
|
||||||
|
},
|
||||||
|
|
||||||
|
badgeNotification: '--cRed',
|
||||||
|
badgeNotificationText: {
|
||||||
|
depends: ['text', 'badgeNotification'],
|
||||||
|
layer: 'badge',
|
||||||
|
variant: 'badgeNotification',
|
||||||
|
textColor: 'bw'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLayersArray = (layer, data = LAYERS) => {
|
||||||
|
let array = [layer]
|
||||||
|
let parent = data[layer]
|
||||||
|
while (parent) {
|
||||||
|
array.unshift(parent)
|
||||||
|
parent = data[parent]
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLayers = (layer, variant = layer, colors, opacity) => {
|
||||||
|
return getLayersArray(layer).map((currentLayer) => ([
|
||||||
|
currentLayer === layer
|
||||||
|
? colors[variant]
|
||||||
|
: colors[currentLayer],
|
||||||
|
opacity[currentLayer]
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDependencies = (key, inheritance) => {
|
||||||
|
const data = inheritance[key]
|
||||||
|
if (typeof data === 'string' && data.startsWith('--')) {
|
||||||
|
return [data.substring(2)]
|
||||||
|
} else {
|
||||||
|
if (data === null) return []
|
||||||
|
const { depends, layer, variant } = data
|
||||||
|
const layerDeps = layer
|
||||||
|
? getLayersArray(layer).map(currentLayer => {
|
||||||
|
return currentLayer === layer
|
||||||
|
? variant || layer
|
||||||
|
: currentLayer
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
if (Array.isArray(depends)) {
|
||||||
|
return [...depends, ...layerDeps]
|
||||||
|
} else {
|
||||||
|
return [...layerDeps]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const topoSort = (
|
||||||
|
inheritance = SLOT_INHERITANCE,
|
||||||
|
getDeps = getDependencies
|
||||||
|
) => {
|
||||||
|
// This is an implementation of https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
|
||||||
|
|
||||||
|
const allKeys = Object.keys(inheritance)
|
||||||
|
const whites = new Set(allKeys)
|
||||||
|
const grays = new Set()
|
||||||
|
const blacks = new Set()
|
||||||
|
const unprocessed = [...allKeys]
|
||||||
|
const output = []
|
||||||
|
|
||||||
|
const step = (node) => {
|
||||||
|
if (whites.has(node)) {
|
||||||
|
// Make node "gray"
|
||||||
|
whites.delete(node)
|
||||||
|
grays.add(node)
|
||||||
|
// Do step for each node connected to it (one way)
|
||||||
|
getDeps(node, inheritance).forEach(step)
|
||||||
|
// Make node "black"
|
||||||
|
grays.delete(node)
|
||||||
|
blacks.add(node)
|
||||||
|
// Put it into the output list
|
||||||
|
output.push(node)
|
||||||
|
} else if (grays.has(node)) {
|
||||||
|
console.debug('Cyclic depenency in topoSort, ignoring')
|
||||||
|
output.push(node)
|
||||||
|
} else if (blacks.has(node)) {
|
||||||
|
// do nothing
|
||||||
|
} else {
|
||||||
|
throw new Error('Unintended condition in topoSort!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (unprocessed.length > 0) {
|
||||||
|
step(unprocessed.pop())
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SLOT_ORDERED = topoSort(SLOT_INHERITANCE)
|
||||||
|
|
||||||
|
export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.reduce((acc, key) => {
|
||||||
|
const value = SLOT_INHERITANCE[key]
|
||||||
|
if (sourceColors[key]) {
|
||||||
|
return { ...acc, [key]: { ...sourceColors[key] } }
|
||||||
|
} else if (typeof value === 'string' && value.startsWith('#')) {
|
||||||
|
return { ...acc, [key]: convert(value).rgb }
|
||||||
|
} else {
|
||||||
|
const isObject = typeof value === 'object'
|
||||||
|
const defaultColorFunc = (mod, dep) => ({ ...dep })
|
||||||
|
const deps = getDependencies(key, SLOT_INHERITANCE)
|
||||||
|
const colorFunc = (isObject && value.color) || defaultColorFunc
|
||||||
|
|
||||||
|
if (value.textColor) {
|
||||||
|
const bg = alphaBlendLayers(
|
||||||
|
{ ...acc[deps[0]] },
|
||||||
|
getLayers(
|
||||||
|
value.layer,
|
||||||
|
value.variant || value.layer,
|
||||||
|
acc,
|
||||||
|
sourceOpacity
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (value.textColor === 'bw') {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: contrastRatio(bg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: getTextColor(
|
||||||
|
bg,
|
||||||
|
{ ...acc[deps[0]] },
|
||||||
|
value.textColor === 'preserve'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('BENIS', key, deps, deps.map((dep) => ({ ...acc[dep] })))
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: colorFunc(
|
||||||
|
mod,
|
||||||
|
...deps.map((dep) => ({ ...acc[dep] }))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {})
|
79
test/unit/specs/services/style_setter/style_setter.spec.js
Normal file
79
test/unit/specs/services/style_setter/style_setter.spec.js
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import { getLayersArray, topoSort } from 'src/services/style_setter/style_setter'
|
||||||
|
|
||||||
|
describe('getLayersArray', () => {
|
||||||
|
const fixture = {
|
||||||
|
layer1: null,
|
||||||
|
layer2: 'layer1',
|
||||||
|
layer3a: 'layer2',
|
||||||
|
layer3b: 'layer2'
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should expand layers properly (3b)', () => {
|
||||||
|
const out = getLayersArray('layer3b', fixture)
|
||||||
|
expect(out).to.eql(['layer1', 'layer2', 'layer3b'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should expand layers properly (3a)', () => {
|
||||||
|
const out = getLayersArray('layer3a', fixture)
|
||||||
|
expect(out).to.eql(['layer1', 'layer2', 'layer3a'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should expand layers properly (2)', () => {
|
||||||
|
const out = getLayersArray('layer2', fixture)
|
||||||
|
expect(out).to.eql(['layer1', 'layer2'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should expand layers properly (1)', () => {
|
||||||
|
const out = getLayersArray('layer1', fixture)
|
||||||
|
expect(out).to.eql(['layer1'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('topoSort', () => {
|
||||||
|
const fixture1 = {
|
||||||
|
layerA: [],
|
||||||
|
layer1A: ['layerA'],
|
||||||
|
layer2A: ['layer1A'],
|
||||||
|
layerB: [],
|
||||||
|
layer1B: ['layerB'],
|
||||||
|
layer2B: ['layer1B'],
|
||||||
|
layer3AB: ['layer2B', 'layer2A']
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same thing but messed up order
|
||||||
|
const fixture2 = {
|
||||||
|
layer1A: ['layerA'],
|
||||||
|
layer1B: ['layerB'],
|
||||||
|
layer2A: ['layer1A'],
|
||||||
|
layerB: [],
|
||||||
|
layer3AB: ['layer2B', 'layer2A'],
|
||||||
|
layer2B: ['layer1B'],
|
||||||
|
layerA: []
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should make a topologically sorted array', () => {
|
||||||
|
const out = topoSort(fixture1, (node, inheritance) => inheritance[node])
|
||||||
|
// This basically checks all ordering that matters
|
||||||
|
expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A'))
|
||||||
|
expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A'))
|
||||||
|
expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B'))
|
||||||
|
expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B'))
|
||||||
|
expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB'))
|
||||||
|
expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('order in object shouldn\'t matter', () => {
|
||||||
|
const out = topoSort(fixture2, (node, inheritance) => inheritance[node])
|
||||||
|
// This basically checks all ordering that matters
|
||||||
|
expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A'))
|
||||||
|
expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A'))
|
||||||
|
expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B'))
|
||||||
|
expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B'))
|
||||||
|
expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB'))
|
||||||
|
expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB'))
|
||||||
|
})
|
||||||
|
it('ignores cyclic dependencies', () => {
|
||||||
|
const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => inheritance[node])
|
||||||
|
expect(out.indexOf('a')).to.be.below(out.indexOf('c'))
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue