diff options
| author | Henry Jameson <me@hjkos.com> | 2024-09-26 01:10:57 +0300 |
|---|---|---|
| committer | Henry Jameson <me@hjkos.com> | 2024-09-26 01:10:57 +0300 |
| commit | 6fc929a0a040bfdf3b403c5f2b708ab4a51fe2e8 (patch) | |
| tree | c85c68d9e09826a9d81b4ef21706f782c5a9a5d6 /src/services | |
| parent | 22cc96705ab3af42a6373ccab6df473ba1243fed (diff) | |
| parent | 05c0b16a935c2799ece31845897d3a29d39e3eee (diff) | |
Merge remote-tracking branch 'origin/develop' into shadow-control-2.0
Diffstat (limited to 'src/services')
| -rw-r--r-- | src/services/date_utils/date_utils.js | 43 | ||||
| -rw-r--r-- | src/services/locale/locale.service.js | 2 | ||||
| -rw-r--r-- | src/services/theme_data/iss_deserializer.js | 157 | ||||
| -rw-r--r-- | src/services/theme_data/iss_serializer.js | 48 | ||||
| -rw-r--r-- | src/services/theme_data/theme_data_3.service.js | 16 |
5 files changed, 261 insertions, 5 deletions
diff --git a/src/services/date_utils/date_utils.js b/src/services/date_utils/date_utils.js index ed8e1417..69398c0c 100644 --- a/src/services/date_utils/date_utils.js +++ b/src/services/date_utils/date_utils.js @@ -6,10 +6,13 @@ export const WEEK = 7 * DAY export const MONTH = 30 * DAY export const YEAR = 365.25 * DAY -export const relativeTime = (date, nowThreshold = 1) => { +export const relativeTimeMs = (date) => { if (typeof date === 'string') date = Date.parse(date) + return Math.abs(Date.now() - date) +} +export const relativeTime = (date, nowThreshold = 1) => { const round = Date.now() > date ? Math.floor : Math.ceil - const d = Math.abs(Date.now() - date) + const d = relativeTimeMs(date) const r = { num: round(d / YEAR), key: 'time.unit.years' } if (d < nowThreshold * SECOND) { r.num = 0 @@ -57,3 +60,39 @@ export const secondsToUnit = (unit, amount) => { case 'days': return (1000 * amount) / DAY } } + +export const isSameYear = (a, b) => { + return a.getFullYear() === b.getFullYear() +} + +export const isSameMonth = (a, b) => { + return a.getFullYear() === b.getFullYear() && + a.getMonth() === b.getMonth() +} + +export const isSameDay = (a, b) => { + return a.getFullYear() === b.getFullYear() && + a.getMonth() === b.getMonth() && + a.getDate() === b.getDate() +} + +export const durationStrToMs = (str) => { + if (typeof str !== 'string') { + return 0 + } + + const unit = str.replace(/[0-9,.]+/, '') + const value = str.replace(/[^0-9,.]+/, '') + switch (unit) { + case 'd': + return value * DAY + case 'h': + return value * HOUR + case 'm': + return value * MINUTE + case 's': + return value * SECOND + default: + return 0 + } +} diff --git a/src/services/locale/locale.service.js b/src/services/locale/locale.service.js index 1b108b85..9a96cf01 100644 --- a/src/services/locale/locale.service.js +++ b/src/services/locale/locale.service.js @@ -19,7 +19,7 @@ const internalToBackendLocaleMulti = codes => { const getLanguageName = (code) => { const specialLanguageNames = { - pdc: 'Pennsylvania Dutch', + pdc: 'Pennsilfaanisch-Deitsch', ja_easy: 'やさしいにほんご', 'nan-TW': '臺語(閩南語)', zh: '简体中文', diff --git a/src/services/theme_data/iss_deserializer.js b/src/services/theme_data/iss_deserializer.js new file mode 100644 index 00000000..909e9411 --- /dev/null +++ b/src/services/theme_data/iss_deserializer.js @@ -0,0 +1,157 @@ +import { flattenDeep } from 'lodash' + +const parseShadow = string => { + const modes = ['_full', 'inset', 'x', 'y', 'blur', 'spread', 'color', 'alpha'] + const regexPrep = [ + // inset keyword (optional) + '^(?:(inset)\\s+)?', + // x + '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)', + // y + '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)', + // blur (optional) + '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)?', + // spread (optional) + '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)?', + // either hex, variable or function + '(#[0-9a-f]{6}|--[a-z\\-_]+|\\$[a-z\\-()_]+)', + // opacity (optional) + '(?:\\s+\\/\\s+([0-9]+(?:\\.[0-9]+)?)\\s*)?$' + ].join('') + const regex = new RegExp(regexPrep, 'gis') // global, (stable) indices, single-string + const result = regex.exec(string) + if (result == null) { + return string + } else { + const numeric = new Set(['x', 'y', 'blur', 'spread', 'alpha']) + const { x, y, blur, spread, alpha, inset, color } = Object.fromEntries(modes.map((mode, i) => { + if (numeric.has(mode)) { + return [mode, Number(result[i])] + } else if (mode === 'inset') { + return [mode, !!result[i]] + } else { + return [mode, result[i]] + } + }).filter(([k, v]) => v !== false).slice(1)) + + return { x, y, blur, spread, color, alpha, inset } + } +} +// this works nearly the same as HTML tree converter +const parseIss = (input) => { + const buffer = [{ selector: null, content: [] }] + let textBuffer = '' + + const getCurrentBuffer = () => { + let current = buffer[buffer.length - 1] + if (current == null) { + current = { selector: null, content: [] } + } + return current + } + + // Processes current line buffer, adds it to output buffer and clears line buffer + const flushText = (kind) => { + if (textBuffer === '') return + if (kind === 'content') { + getCurrentBuffer().content.push(textBuffer.trim()) + } else { + getCurrentBuffer().selector = textBuffer.trim() + } + textBuffer = '' + } + + for (let i = 0; i < input.length; i++) { + const char = input[i] + + if (char === ';') { + flushText('content') + } else if (char === '{') { + flushText('header') + } else if (char === '}') { + flushText('content') + buffer.push({ selector: null, content: [] }) + textBuffer = '' + } else { + textBuffer += char + } + } + + return buffer +} +export const deserialize = (input) => { + const ast = parseIss(input) + const finalResult = ast.filter(i => i.selector != null).map(item => { + const { selector, content } = item + let stateCount = 0 + const selectors = selector.split(/,/g) + const result = selectors.map(selector => { + const output = { component: '' } + let currentDepth = null + + selector.split(/ /g).reverse().forEach((fragment, index, arr) => { + const fragmentObject = { component: '' } + + let mode = 'component' + for (let i = 0; i < fragment.length; i++) { + const char = fragment[i] + switch (char) { + case '.': { + mode = 'variant' + fragmentObject.variant = '' + break + } + case ':': { + mode = 'state' + fragmentObject.state = fragmentObject.state || [] + stateCount++ + break + } + default: { + if (mode === 'state') { + const currentState = fragmentObject.state[stateCount - 1] + if (currentState == null) { + fragmentObject.state.push('') + } + fragmentObject.state[stateCount - 1] += char + } else { + fragmentObject[mode] += char + } + } + } + } + if (currentDepth !== null) { + currentDepth.parent = { ...fragmentObject } + currentDepth = currentDepth.parent + } else { + Object.keys(fragmentObject).forEach(key => { + output[key] = fragmentObject[key] + }) + if (index !== (arr.length - 1)) { + output.parent = { component: '' } + } + currentDepth = output + } + }) + + output.directives = Object.fromEntries(content.map(d => { + const [property, value] = d.split(':') + let realValue = value.trim() + if (property === 'shadow') { + if (realValue === 'none') { + realValue = [] + } else { + realValue = value.split(',').map(v => parseShadow(v.trim())) + } + } if (!Number.isNaN(Number(value))) { + realValue = Number(value) + } + return [property, realValue] + })) + + return output + }) + return result + }) + return flattenDeep(finalResult) +} diff --git a/src/services/theme_data/iss_serializer.js b/src/services/theme_data/iss_serializer.js new file mode 100644 index 00000000..8b7cf5d8 --- /dev/null +++ b/src/services/theme_data/iss_serializer.js @@ -0,0 +1,48 @@ +import { unroll } from './iss_utils.js' + +const serializeShadow = s => { + if (typeof s === 'object') { + return `${s.inset ? 'inset ' : ''}${s.x} ${s.y} ${s.blur} ${s.spread} ${s.color} / ${s.alpha}` + } else { + return s + } +} + +export const serialize = (ruleset) => { + return ruleset.map((rule) => { + if (Object.keys(rule.directives || {}).length === 0) return false + + const header = unroll(rule).reverse().map(rule => { + const { component } = rule + const newVariant = (rule.variant == null || rule.variant === 'normal') ? '' : ('.' + rule.variant) + const newState = (rule.state || []).filter(st => st !== 'normal') + + return `${component}${newVariant}${newState.map(st => ':' + st).join('')}` + }).join(' ') + + const content = Object.entries(rule.directives).map(([directive, value]) => { + if (directive.startsWith('--')) { + const [valType, newValue] = value.split('|') // only first one! intentional! + switch (valType) { + case 'shadow': + return ` ${directive}: ${valType.trim()} | ${newValue.map(serializeShadow).map(s => s.trim()).join(', ')}` + default: + return ` ${directive}: ${valType.trim()} | ${newValue.trim()}` + } + } else { + switch (directive) { + case 'shadow': + if (value.length > 0) { + return ` ${directive}: ${value.map(serializeShadow).join(', ')}` + } else { + return ` ${directive}: none` + } + default: + return ` ${directive}: ${value}` + } + } + }) + + return `${header} {\n${content.join(';\n')}\n}` + }).filter(x => x).join('\n\n') +} diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index 8a0c170b..3c2f8a63 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -522,9 +522,21 @@ export const init = ({ console.debug('Eager processing took ' + (t2 - t1) + ' ms') } + // optimization to traverse big-ass array only once instead of twice + const eager = [] + const lazy = [] + + result.forEach(x => { + if (typeof x === 'function') { + lazy.push(x) + } else { + eager.push(x) + } + }) + return { - lazy: result.filter(x => typeof x === 'function'), - eager: result.filter(x => typeof x !== 'function'), + lazy, + eager, staticVars, engineChecksum } |
