Browse Source

at last... it's complete

Henry Jameson 2 months ago
parent
commit
71a4781080

+ 2 - 2
src/components/button.style.js

@@ -34,8 +34,8 @@ export default {
       directives: {
         '--defaultButtonHoverGlow': 'shadow | 0 0 4 --text',
         '--defaultButtonShadow': 'shadow | 0 0 2 #000000',
-        '--defaultButtonBevel': 'shadow | $borderSide(#FFFFFF, top, 0.2) | $borderSide(#000000, bottom, 0.2)',
-        '--pressedButtonBevel': 'shadow | $borderSide(#FFFFFF, bottom, 0.2)| $borderSide(#000000, top, 0.2)'
+        '--defaultButtonBevel': 'shadow | $borderSide(#FFFFFF, top, 0.2), $borderSide(#000000, bottom, 0.2)',
+        '--pressedButtonBevel': 'shadow | $borderSide(#FFFFFF, bottom, 0.2), $borderSide(#000000, top, 0.2)'
       }
     },
     {

+ 113 - 13
src/services/theme_data/iss_deserializer.js

@@ -1,24 +1,51 @@
+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 {
+    return Object.fromEntries(modes.map((mode, i) => [mode, result[i]]))
+  }
+}
 // this works nearly the same as HTML tree converter
-export const deserialize = (input) => {
-  const buffer = []
+const parseIss = (input) => {
+  const buffer = [{ selector: null, content: [] }]
   let textBuffer = ''
 
   const getCurrentBuffer = () => {
-    let current = buffer[buffer.length - 1][1]
+    let current = buffer[buffer.length - 1]
     if (current == null) {
-      current = { name: null, content: [] }
+      current = { selector: null, content: [] }
     }
-    buffer.push(current)
     return current
   }
 
   // Processes current line buffer, adds it to output buffer and clears line buffer
-  const flushText = (content) => {
+  const flushText = (kind) => {
     if (textBuffer === '') return
-    if (content) {
-      getCurrentBuffer().content.push(textBuffer)
+    if (kind === 'content') {
+      getCurrentBuffer().content.push(textBuffer.trim())
     } else {
-      getCurrentBuffer().name = textBuffer
+      getCurrentBuffer().selector = textBuffer.trim()
     }
     textBuffer = ''
   }
@@ -27,17 +54,90 @@ export const deserialize = (input) => {
     const char = input[i]
 
     if (char === ';') {
-      flushText(true)
+      flushText('content')
     } else if (char === '{') {
-      flushText(false)
+      flushText('header')
     } else if (char === '}') {
-      buffer.push({ name: null, content: [] })
+      flushText('content')
+      buffer.push({ selector: null, content: [] })
       textBuffer = ''
     } else {
       textBuffer += char
     }
   }
 
-  flushText()
   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(':')
+        console.log(property, value)
+        let realValue = value.trim()
+        if (property === 'shadow') {
+          realValue = parseShadow(value.split(',').map(v => v.trim()))
+        } if (!Number.isNaN(Number(value))) {
+          realValue = Number(value)
+        }
+        return [property, realValue]
+      }))
+
+      return output
+    })
+    return result
+  })
+  return flattenDeep(finalResult)
+}

+ 11 - 5
src/services/theme_data/iss_serializer.js

@@ -1,6 +1,12 @@
 import { unroll } from './iss_utils.js'
 
-const serializeShadow = s => `{${s.inset ? 'inset ' : ''}${s.x} ${s.y} ${s.blur} ${s.spread} ${s.color} / ${s.alpha}}`
+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) => {
@@ -8,8 +14,8 @@ export const serialize = (ruleset) => {
 
     const header = unroll(rule).reverse().map(rule => {
       const { component } = rule
-      const newVariant = rule.variant === 'normal' ? '' : ('.' + rule.variant)
-      const newState = rule.state.filter(st => st !== 'normal')
+      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(' ')
@@ -19,9 +25,9 @@ export const serialize = (ruleset) => {
         const [valType, newValue] = value.split('|') // only first one! intentional!
         switch (valType) {
           case 'shadow':
-            return `  ${directive}: ${newValue.map(serializeShadow).join(', ')}`
+            return `  ${directive}: ${valType.trim()} | ${newValue.map(serializeShadow).map(s => s.trim()).join(', ')}`
           default:
-            return `  ${directive}: ${newValue}`
+            return `  ${directive}: ${valType.trim()} | ${newValue.trim()}`
         }
       } else {
         switch (directive) {

+ 0 - 3
src/services/theme_data/theme_data_3.service.js

@@ -23,9 +23,6 @@ import {
   findRules
 } from './iss_utils.js'
 import { parseCssShadow } from './css_utils.js'
-import {
-  serialize
-} from './iss_serializer.js'
 
 // Ensuring the order of components
 const components = {

+ 10 - 41
test/unit/specs/services/theme_data/iss_deserializer.spec.js

@@ -1,47 +1,16 @@
 import { deserialize } from 'src/services/theme_data/iss_deserializer.js'
+import { serialize } from 'src/services/theme_data/iss_serializer.js'
+import Button from 'src/components/button.style.js'
 
-/* eslint-disable quotes */
-const testData = ```
-    Root {
-        --accent: color | #e2b188;
-        --badgeNotification: color | #e15932;
-        --bg: color | #0f161e;
-        --cBlue: color | #81beea;
-        --cGreen: color | #5dc94a;
-        --cOrange: color | #ffc459;
-        --cRed: color | #d31014;
-        --defaultButtonBevel: shadow | $borderSide(#FFFFFF, top, 0.2) | $borderSide(#000000, bottom, 0.2);
-        --defaultButtonHoverGlow: shadow | 0 0 4 --text;
-        --defaultButtonShadow: shadow | 0 0 2 #000000;
-        --defaultInputBevel: shadow | $borderSide(#FFFFFF, bottom, 0.2)| $borderSide(#000000, top, 0.2);
-        --fg: color | #151e2b;
-        --font: generic | sans-serif;
-        --link: color | #e2b188;
-        --monoFont: generic | monospace;
-        --pressedButtonBevel: shadow | $borderSide(#FFFFFF, bottom, 0.2)| $borderSide(#000000, top, 0.2);
-        --selectionBackground: color | --accent;
-        --selectionText: color | $textColor(--accent, --text, no-preserve);
-        --text: color | #b9b9ba;
-        --wallpaper: color | #0c1118;
-        background: transparent;
-        opacity: 0;
-    }
+describe.only('ISS (de)serialization', () => {
+  describe('ISS deserialization', () => {
+    it('Output should = input', () => {
+      const normalized = Button.defaultRules.map(x => ({ component: 'Button', ...x }))
+      const serialized = serialize(normalized)
+      const deserialized = deserialize(serialized)
+      // deserialized.toString()
 
-    Root Underlay {
-        background: #000000;
-        opacity: 0.6;
-    }
-
-    Root Underlay, test {
-        background: #000000;
-        opacity: 0.6;
-    }
-    ```
-
-describe.only('html_tree_converter', () => {
-  describe('convertHtmlToTree', () => {
-    it('should parse ISS correctly', () => {
-      console.log(deserialize(testData))
+      expect(JSON.stringify(deserialized)).to.equal(JSON.stringify(normalized))
     })
   })
 })