Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
49 KB
Referenced Files
None
Subscribers
None
diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
index 5cc0135e75..dfef37a636 100644
--- a/build/webpack.base.conf.js
+++ b/build/webpack.base.conf.js
@@ -1,106 +1,107 @@
var path = require('path')
var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
var FontelloPlugin = require("fontello-webpack-plugin")
var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
// various preprocessor loaders added to vue-loader at the end of this file
var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
var now = Date.now()
module.exports = {
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
filename: '[name].js'
},
optimization: {
splitChunks: {
chunks: 'all'
}
},
resolve: {
extensions: ['.js', '.vue'],
modules: [
path.join(__dirname, '../node_modules')
],
alias: {
'vue$': 'vue/dist/vue.runtime.common',
+ 'static': path.resolve(__dirname, '../static'),
'src': path.resolve(__dirname, '../src'),
'assets': path.resolve(__dirname, '../src/assets'),
'components': path.resolve(__dirname, '../src/components')
}
},
module: {
noParse: /node_modules\/localforage\/dist\/localforage.js/,
rules: [
{
enforce: 'pre',
test: /\.(js|vue)$/,
include: projectRoot,
exclude: /node_modules/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-friendly-formatter'),
sourceMap: config.build.productionSourceMap,
extract: true
}
}
},
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.jsx?$/,
include: projectRoot,
exclude: /node_modules\/(?!tributejs)/,
use: 'babel-loader'
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
},
]
},
plugins: [
new ServiceWorkerWebpackPlugin({
entry: path.join(__dirname, '..', 'src/sw.js'),
filename: 'sw-pleroma.js'
}),
new FontelloPlugin({
config: require('../static/fontello.json'),
name: 'fontello',
output: {
css: 'static/[name].' + now + '.css', // [hash] is not supported. Use the current timestamp instead for versioning.
font: 'static/font/[name].' + now + '.[ext]'
}
})
]
}
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index 0bf8f6460b..b546103837 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -1,224 +1,224 @@
import { invertLightness, contrastRatio } from 'chromatism'
// useful for visualizing color when debugging
export const consoleColor = (color) => console.log('%c##########', 'background: ' + color + '; color: ' + color)
/**
* 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') {
return undefined
}
// TODO: clean up this mess
if (r[0] === '#' || r === 'transparent') {
return r
}
if (typeof r === 'object') {
({ r, g, b } = r)
}
[r, g, b] = [r, g, b].map(val => {
val = Math.ceil(val)
val = val < 0 ? 0 : val
val = val > 255 ? 255 : val
return val
})
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`
}
/**
* Converts 8-bit RGB component into linear component
* https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
* https://www.w3.org/TR/2008/REC-WCAG20-20081211/relative-luminance.xml
* https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation
*
* @param {Number} bit - color component [0..255]
* @returns {Number} linear component [0..1]
*/
const c2linear = (bit) => {
// W3C gives 0.03928 while wikipedia states 0.04045
// what those magical numbers mean - I don't know.
// something about gamma-correction, i suppose.
// Sticking with W3C example.
const c = bit / 255
if (c < 0.03928) {
return c / 12.92
} else {
return Math.pow((c + 0.055) / 1.055, 2.4)
}
}
/**
* Converts sRGB into linear RGB
* @param {Object} srgb - sRGB color
* @returns {Object} linear rgb color
*/
const srgbToLinear = (srgb) => {
return 'rgb'.split('').reduce((acc, c) => { acc[c] = c2linear(srgb[c]); return acc }, {})
}
/**
* Calculates relative luminance for given color
* https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
* https://www.w3.org/TR/2008/REC-WCAG20-20081211/relative-luminance.xml
*
* @param {Object} srgb - sRGB color
* @returns {Number} relative luminance
*/
export const relativeLuminance = (srgb) => {
const { r, g, b } = srgbToLinear(srgb)
return 0.2126 * r + 0.7152 * g + 0.0722 * b
}
/**
* Generates color ratio between two colors. Order is unimporant
* https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
*
* @param {Object} a - sRGB color
* @param {Object} b - sRGB color
* @returns {Number} color ratio
*/
export const getContrastRatio = (a, b) => {
const la = relativeLuminance(a)
const lb = relativeLuminance(b)
const [l1, l2] = la > lb ? [la, lb] : [lb, la]
return (l1 + 0.05) / (l2 + 0.05)
}
/**
* Same as `getContrastRatio` but for multiple layers in-between
*
* @param {Object} text - text color (topmost layer)
* @param {[Object, Number]} layers[] - layers between text and bedrock
* @param {Object} bedrock - layer at the very bottom
*/
export const getContrastRatioLayers = (text, layers, bedrock) => {
return getContrastRatio(alphaBlendLayers(bedrock, layers), text)
}
/**
* This performs alpha blending between solid background and semi-transparent foreground
*
* @param {Object} fg - top layer color
* @param {Number} fga - top layer's alpha
* @param {Object} bg - bottom layer color
* @returns {Object} sRGB of resulting color
*/
export const alphaBlend = (fg, fga, bg) => {
if (fga === 1 || typeof fga === 'undefined') return fg
return 'rgb'.split('').reduce((acc, c) => {
// Simplified https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
// for opaque bg and transparent fg
acc[c] = (fg[c] * fga + bg[c] * (1 - fga))
return acc
}, {})
}
/**
* Same as `alphaBlend` but for multiple layers in-between
*
* @param {Object} bedrock - layer at the very bottom
* @param {[Object, Number]} layers[] - layers between text and bedrock
*/
export const alphaBlendLayers = (bedrock, layers) => layers.reduce((acc, [color, opacity]) => {
return alphaBlend(color, opacity, acc)
}, bedrock)
export const invert = (rgb) => {
return 'rgb'.split('').reduce((acc, c) => {
acc[c] = 255 - rgb[c]
return acc
}, {})
}
/**
* 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)
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null
}
/**
* 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 'rgb'.split('').reduce((acc, k) => {
acc[k] = (a[k] + b[k]) / 2
return acc
}, {})
}
/**
* Converts rgb object into a CSS rgba() color
*
* @param {Object} color - rgb
* @returns {String} CSS rgba() color
*/
export const rgba2css = function (rgba) {
- return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
+ return `rgba(${Math.floor(rgba.r)}, ${Math.floor(rgba.g)}, ${Math.floor(rgba.b)}, .5)`
}
/**
* 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 contrast = getContrastRatio(bg, text)
if (contrast < 4.5) {
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 })
}
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 0a6733a98c..49b991483a 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -1,370 +1,370 @@
import { convert, brightness, contrastRatio } from 'chromatism'
import { alphaBlendLayers, getTextColor, relativeLuminance } from '../color_convert/color_convert.js'
import { LAYERS, DEFAULT_OPACITY, SLOT_INHERITANCE } from './pleromafe.js'
/*
* # What's all this?
* Here be theme engine for pleromafe. All of this supposed to ease look
* and feel customization, making widget styles and make developer's life
* easier when it comes to supporting themes. Like many other theme systems
* it operates on color definitions, or "slots" - for example you define
* "button" color slot and then in UI component Button's CSS you refer to
* it as a CSS3 Variable.
*
* Some applications allow you to customize colors for certain things.
* Some UI toolkits allow you to define colors for each type of widget.
* Most of them are pretty barebones and have no assistance for common
* problems and cases, and in general themes themselves are very hard to
* maintain in all aspects. This theme engine tries to solve all of the
* common problems with themes.
*
* You don't have redefine several similar colors if you just want to
* change one color - all color slots are derived from other ones, so you
* can have at least one or two "basic" colors defined and have all other
* components inherit and modify basic ones.
*
* You don't have to test contrast ratio for colors or pick text color for
* each element even if you have light-on-dark elements in dark-on-light
* theme.
*
* You don't have to maintain order of code for inheriting slots from othet
* slots - dependency graph resolving does it for you.
*/
/* This indicates that this version of code outputs similar theme data and
* should be incremented if output changes - for instance if getTextColor
* function changes and older themes no longer render text colors as
* author intended previously.
*/
export const CURRENT_VERSION = 3
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, opacitySlot, colors, opacity) => {
return getLayersArray(layer).map((currentLayer) => ([
currentLayer === layer
? colors[variant]
: colors[currentLayer],
currentLayer === layer
? opacity[opacitySlot] || 1
: 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]
}
}
}
/**
* Sorts inheritance object topologically - dependant slots come after
* dependencies
*
* @property {Object} inheritance - object defining the nodes
* @property {Function} getDeps - function that returns dependencies for
* given value and inheritance object.
* @returns {String[]} keys of inheritance object, sorted in topological
* order. Additionally, dependency-less nodes will always be first in line
*/
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.sort((a, b) => {
const depsA = getDeps(a, inheritance).length
const depsB = getDeps(b, inheritance).length
if (depsA === depsB || (depsB !== 0 && depsA !== 0)) return 0
if (depsA === 0 && depsB !== 0) return -1
if (depsB === 0 && depsA !== 0) return 1
})
}
const expandSlotValue = (value) => {
if (typeof value === 'object') return value
return {
depends: value.startsWith('--') ? [value.substring(2)] : [],
default: value.startsWith('#') ? value : undefined
}
}
/**
* retrieves opacity slot for given slot. This goes up the depenency graph
* to find which parent has opacity slot defined for it.
* TODO refactor this
*/
export const getOpacitySlot = (
k,
inheritance = SLOT_INHERITANCE,
getDeps = getDependencies
) => {
const value = expandSlotValue(inheritance[k])
if (value.opacity === null) return
if (value.opacity) return value.opacity
const findInheritedOpacity = (key, visited = [k]) => {
const depSlot = getDeps(key, inheritance)[0]
if (depSlot === undefined) return
const dependency = inheritance[depSlot]
if (dependency === undefined) return
if (dependency.opacity || dependency === null) {
return dependency.opacity
} else if (dependency.depends && visited.includes(depSlot)) {
return findInheritedOpacity(depSlot, [...visited, depSlot])
} else {
return null
}
}
if (value.depends) {
return findInheritedOpacity(k)
}
}
/**
* retrieves layer slot for given slot. This goes up the depenency graph
* to find which parent has opacity slot defined for it.
* this is basically copypaste of getOpacitySlot except it checks if key is
* in LAYERS
* TODO refactor this
*/
export const getLayerSlot = (
k,
inheritance = SLOT_INHERITANCE,
getDeps = getDependencies
) => {
const value = expandSlotValue(inheritance[k])
if (LAYERS[k]) return k
if (value.layer === null) return
if (value.layer) return value.layer
const findInheritedLayer = (key, visited = [k]) => {
const depSlot = getDeps(key, inheritance)[0]
if (depSlot === undefined) return
const dependency = inheritance[depSlot]
if (dependency === undefined) return
if (dependency.layer || dependency === null) {
return dependency.layer
} else if (dependency.depends) {
return findInheritedLayer(dependency, [...visited, depSlot])
} else {
return null
}
}
if (value.depends) {
return findInheritedLayer(k)
}
}
/**
* topologically sorted SLOT_INHERITANCE
*/
export const SLOT_ORDERED = topoSort(
Object.entries(SLOT_INHERITANCE)
.sort(([aK, aV], [bK, bV]) => ((aV && aV.priority) || 0) - ((bV && bV.priority) || 0))
.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
)
/**
* All opacity slots used in color slots, their default values and affected
* color slots.
*/
export const OPACITIES = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => {
const opacity = getOpacitySlot(k, SLOT_INHERITANCE, getDependencies)
if (opacity) {
return {
...acc,
[opacity]: {
defaultValue: DEFAULT_OPACITY[opacity] || 1,
affectedSlots: [...((acc[opacity] && acc[opacity].affectedSlots) || []), k]
}
}
} else {
return acc
}
}, {})
/**
* Handle dynamic color
*/
export const computeDynamicColor = (sourceColor, getColor, mod) => {
if (typeof sourceColor !== 'string' || !sourceColor.startsWith('--')) return sourceColor
let targetColor = null
// Color references other color
const [variable, modifier] = sourceColor.split(/,/g).map(str => str.trim())
const variableSlot = variable.substring(2)
targetColor = getColor(variableSlot)
if (modifier) {
targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
}
return targetColor
}
/**
* THE function you want to use. Takes provided colors and opacities, mod
* value and uses inheritance data to figure out color needed for the slot.
*/
export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.reduce(({ colors, opacity }, key) => {
const value = SLOT_INHERITANCE[key]
const isObject = typeof value === 'object'
const isString = typeof value === 'string'
const sourceColor = sourceColors[key]
let outputColor = null
if (sourceColor) {
// Color is defined in source color
let targetColor = sourceColor
if (targetColor === 'transparent') {
// We take only layers below current one
const layers = getLayers(
getLayerSlot(key),
key,
getOpacitySlot(key) || key,
colors,
opacity
).slice(0, -1)
targetColor = {
// TODO: try to use alpha-blended background here
...alphaBlendLayers(
convert('#FF00FF').rgb,
layers
),
a: 0
}
} else if (typeof sourceColor === 'string' && sourceColor.startsWith('--')) {
targetColor = computeDynamicColor(
sourceColor,
variableSlot => colors[variableSlot] || sourceColors[variableSlot],
mod
)
} else if (typeof sourceColor === 'string' && sourceColor.startsWith('#')) {
targetColor = convert(targetColor).rgb
}
outputColor = { ...targetColor }
} else if (isString && value.startsWith('#')) {
// slot: '#000000' shorthand
outputColor = convert(value).rgb
} else if (isObject && value.default) {
// same as above except in object form
outputColor = convert(value.default).rgb
} else {
// calculate color
const defaultColorFunc = (mod, dep) => ({ ...dep })
const deps = getDependencies(key, SLOT_INHERITANCE)
const colorFunc = (isObject && value.color) || defaultColorFunc
if (value.textColor) {
// textColor case
const bg = alphaBlendLayers(
{ ...colors[deps[0]] },
getLayers(
value.layer,
value.variant || value.layer,
getOpacitySlot(value.variant || value.layer),
colors,
opacity
)
)
const isLightOnDark = relativeLuminance(bg) > 127
const mod = isLightOnDark ? 1 : -1
if (value.textColor === 'bw') {
outputColor = contrastRatio(bg).rgb
} else {
let color = { ...colors[deps[0]] }
if (value.color) {
color = colorFunc(mod, ...deps.map((dep) => ({ ...colors[dep] })))
}
outputColor = getTextColor(
bg,
{ ...color },
value.textColor === 'preserve'
)
}
} else {
// background color case
outputColor = colorFunc(
mod,
...deps.map((dep) => ({ ...colors[dep] }))
)
}
}
if (!outputColor) {
throw new Error('Couldn\'t generate color for ' + key)
}
const opacitySlot = getOpacitySlot(key)
if (opacitySlot && outputColor.a === undefined) {
const deps = getDependencies(key, SLOT_INHERITANCE)
const dependencySlot = deps && deps[0]
if (dependencySlot && sourceColors[dependencySlot] === 'transparent') {
outputColor.a = 0
} else {
- outputColor.a = sourceOpacity[opacitySlot] || OPACITIES[opacitySlot].defaultValue || 1
+ outputColor.a = Number(sourceOpacity[opacitySlot]) || OPACITIES[opacitySlot].defaultValue || 1
}
}
if (opacitySlot) {
return {
colors: { ...colors, [key]: outputColor },
opacity: { ...opacity, [opacitySlot]: outputColor.a }
}
} else {
return {
colors: { ...colors, [key]: outputColor },
opacity
}
}
}, { colors: {}, opacity: {} })
diff --git a/static/themes/redmond-xx-se.json b/static/themes/redmond-xx-se.json
index 1a867fcc1c..8deab7b79e 100644
--- a/static/themes/redmond-xx-se.json
+++ b/static/themes/redmond-xx-se.json
@@ -1,300 +1,300 @@
{
"_pleroma_theme_version": 2,
"name": "Redmond XX SE",
- "theme": {
+ "source": {
"shadows": {
"panel": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"panelHeader": [
{
"x": 0,
"y": 0,
"blur": 0,
"spread": "3",
"inset": true,
"color": "#c0c0c0",
"alpha": 1
},
{
"x": "-2200",
"y": 0,
"blur": "200",
"spread": "-2000",
"inset": true,
"color": "#1084d0",
"alpha": 1
}
],
"button": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"buttonHover": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"buttonPressed": [
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"input": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--input",
"alpha": "1",
"inset": true
}
]
},
"fonts": {},
"opacity": {
"input": "1",
"faint": "1"
},
"colors": {
"bg": "#c0c0c0",
"text": "#000000",
"link": "#0000ff",
"accent": "#000080",
"fg": "#c0c0c0",
"panel": "#000080",
"panelFaint": "#c0c0c0",
"input": "#ffffff",
"topBar": "#000080",
"topBarLink": "#ffffff",
"btn": "#c0c0c0",
"faint": "#3f3f3f",
"faintLink": "#404080",
"border": "#808080",
"cRed": "#FF0000",
"cBlue": "#008080",
"cGreen": "#008000",
"cOrange": "#808000",
"highlight": "--accent",
"selectedPost": "--bg,-10"
},
"radii": {
"btn": "0",
"input": "0",
"checkbox": "0",
"panel": "0",
"avatar": "0",
"avatarAlt": "0",
"tooltip": "0",
"attachment": "0"
}
}
}
diff --git a/static/themes/redmond-xx.json b/static/themes/redmond-xx.json
index 78c92f106f..cdb89fe36d 100644
--- a/static/themes/redmond-xx.json
+++ b/static/themes/redmond-xx.json
@@ -1,291 +1,291 @@
{
"_pleroma_theme_version": 2,
"name": "Redmond XX",
- "theme": {
+ "source": {
"shadows": {
"panel": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"panelHeader": [
{
"x": 0,
"y": 0,
"blur": 0,
"spread": "3",
"inset": true,
"color": "#c0c0c0",
"alpha": 1
}
],
"button": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"buttonHover": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"buttonPressed": [
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"input": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#000000",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--input",
"alpha": "1",
"inset": true
}
]
},
"fonts": {},
"opacity": {
"input": "1",
"faint": "1"
},
"colors": {
"bg": "#c0c0c0",
"text": "#000000",
"link": "#0000ff",
"accent": "#000080",
"fg": "#c0c0c0",
"panel": "#000080",
"panelFaint": "#c0c0c0",
"input": "#ffffff",
"topBar": "#000080",
"topBarLink": "#ffffff",
"btn": "#c0c0c0",
"faint": "#3f3f3f",
"faintLink": "#404080",
"border": "#808080",
"cRed": "#FF0000",
"cBlue": "#008080",
"cGreen": "#008000",
"cOrange": "#808000",
"highlight": "--accent",
"selectedPost": "--bg,-10"
},
"radii": {
"btn": "0",
"input": "0",
"checkbox": "0",
"panel": "0",
"avatar": "0",
"avatarAlt": "0",
"tooltip": "0",
"attachment": "0"
}
}
}
diff --git a/static/themes/redmond-xxi.json b/static/themes/redmond-xxi.json
index 28f6835111..79a2cb2660 100644
--- a/static/themes/redmond-xxi.json
+++ b/static/themes/redmond-xxi.json
@@ -1,273 +1,273 @@
{
"_pleroma_theme_version": 2,
"name": "Redmond XXI",
- "theme": {
+ "source": {
"shadows": {
"panel": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#404040",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#dfdfdf",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"panelHeader": [
{
"x": 0,
"y": 0,
"blur": 0,
"spread": "3",
"inset": true,
"color": "#d6d6ce",
"alpha": 1
},
{
"x": "-2200",
"y": 0,
"blur": "200",
"spread": "-2000",
"inset": true,
"color": "#a5cef7",
"alpha": 1
}
],
"button": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#404040",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"buttonHover": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#404040",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"buttonPressed": [
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#404040",
"alpha": "1",
"inset": true
},
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--bg",
"alpha": "1",
"inset": true
}
],
"input": [
{
"x": "-1",
"y": "-1",
"blur": "0",
"spread": 0,
"color": "#FFFFFF",
"alpha": "1",
"inset": true
},
{
"x": "1",
"y": "1",
"blur": "0",
"spread": 0,
"color": "#848484",
"alpha": "1",
"inset": true
},
{
"x": "-2",
"y": "-2",
"blur": "0",
"spread": 0,
"color": "#d4d0c8",
"alpha": "1",
"inset": true
},
{
"x": "2",
"y": "2",
"blur": "0",
"spread": 0,
"color": "#404040",
"alpha": "1",
"inset": true
},
{
"x": "0",
"y": "0",
"blur": "0",
"spread": "3",
"color": "--input",
"alpha": "1",
"inset": true
}
]
},
"fonts": {},
"opacity": {
"input": "1",
"faint": "1"
},
"colors": {
"bg": "#d6d6ce",
"text": "#000000",
"link": "#0000ff",
"accent": "#0a246a",
"fg": "#d6d6ce",
"panel": "#042967",
"panelFaint": "#FFFFFF",
"input": "#ffffff",
"topBar": "#042967",
"topBarLink": "#ffffff",
"btn": "#d6d6ce",
"faint": "#3f3f3f",
"faintLink": "#404080",
"border": "#808080",
"cRed": "#c42726",
"cBlue": "#6699cc",
"cGreen": "#669966",
"cOrange": "#cc6633",
"highlight": "--accent",
"selectedPost": "--bg,-10"
},
"radii": {
"btn": "0",
"input": "0",
"checkbox": "0",
"panel": "0",
"avatar": "0",
"avatarAlt": "0",
"tooltip": "0",
"attachment": "0"
}
}
}
diff --git a/test/unit/specs/services/theme_data/sanity_checks.spec.js b/test/unit/specs/services/theme_data/sanity_checks.spec.js
new file mode 100644
index 0000000000..f0072e7d56
--- /dev/null
+++ b/test/unit/specs/services/theme_data/sanity_checks.spec.js
@@ -0,0 +1,28 @@
+import { getColors } from 'src/services/theme_data/theme_data.service.js'
+
+const checkColors = (output) => {
+ expect(output).to.have.property('colors')
+ Object.entries(output.colors).forEach(([key, v]) => {
+ expect(v, key).to.be.an('object')
+ expect(v, key).to.include.all.keys('r', 'g', 'b')
+ 'rgba'.split('').forEach(k => {
+ if ((k === 'a' && v.hasOwnProperty('a')) || k !== 'a') {
+ expect(v[k], key + '.' + k).to.be.a('number')
+ expect(v[k], key + '.' + k).to.be.least(0)
+ expect(v[k], key + '.' + k).to.be.most(k === 'a' ? 1 : 255)
+ }
+ })
+ })
+}
+
+describe('Theme Data utility functions', () => {
+ const context = require.context('static/themes/', false, /\.json$/)
+ context.keys().forEach((key) => {
+ it(`Should render all colors for ${key} properly`, () => {
+ const { theme, source } = context(key)
+ const data = source || theme
+ const colors = getColors(data.colors, data.opacity, 1)
+ checkColors(colors)
+ })
+ })
+})
diff --git a/test/unit/specs/services/theme_data/theme_data.spec.js b/test/unit/specs/services/theme_data/theme_data.spec.js
index d8a04ba7db..67f4fd5a83 100644
--- a/test/unit/specs/services/theme_data/theme_data.spec.js
+++ b/test/unit/specs/services/theme_data/theme_data.spec.js
@@ -1,87 +1,90 @@
import { getLayersArray, topoSort } from 'src/services/theme_data/theme_data.service.js'
-describe('getLayersArray', () => {
- const fixture = {
- layer1: null,
- layer2: 'layer1',
- layer3a: 'layer2',
- layer3b: 'layer2'
- }
+describe('Theme Data utility functions', () => {
+ 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 (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 (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 (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'])
+ 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']
- }
+ 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: []
- }
+ // 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('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('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('dependentless nodes should be first', () => {
- const out = topoSort(fixture2, (node, inheritance) => inheritance[node])
- // This basically checks all ordering that matters
- expect(out.indexOf('layerA')).to.eql(0)
- expect(out.indexOf('layerB')).to.eql(1)
- })
+ it('dependentless nodes should be first', () => {
+ const out = topoSort(fixture2, (node, inheritance) => inheritance[node])
+ // This basically checks all ordering that matters
+ expect(out.indexOf('layerA')).to.eql(0)
+ expect(out.indexOf('layerB')).to.eql(1)
+ })
- 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'))
+ 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'))
+ })
})
+
})

File Metadata

Mime Type
text/x-diff
Expires
Sun, Nov 1, 12:00 PM (9 h, 29 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1802998
Default Alt Text
(49 KB)

Event Timeline