Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85831216
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
49 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Nov 1, 12:00 PM (8 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1802998
Default Alt Text
(49 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline