Last active
July 1, 2019 20:18
-
-
Save zerobias/3f55c88b7f80619190ea1c3cd1bae696 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import ReactDOM from 'react-dom' | |
function iterate(unit) { | |
const source = createEvent() | |
const begin = createEvent() | |
const end = createEvent() | |
const iteration = createEvent() | |
const item = iteration.map(({value}) => value) | |
const {emptyList, nonEmptyList} = split(source, { | |
emptyList: (list) => list.length === 0, | |
nonEmptyList: (list) => list.length > 0, | |
}) | |
forward({ | |
from: nonEmptyList.map(list => ({list, index: 0, value: list[0]})), | |
to: iteration, | |
}) | |
forward({from: emptyList, to: begin}) | |
forward({from: emptyList, to: end}) | |
forward({from: nonEmptyList, to: begin}) | |
forwardWhen(iteration, iteration, step => { | |
if (step.index + 1 < step.list.length) { | |
step.index += 1 | |
step.value = step.list[step.index] | |
return step | |
} | |
}) | |
forwardWhen(iteration, end, step => { | |
if (step.index === step.list.length - 1) { | |
return step.list | |
} | |
}) | |
if (unit) forward({from: unit, to: source}) | |
return { | |
source, | |
item, | |
lifecycle: { | |
begin, | |
end, | |
}, | |
} | |
} | |
function forwardWhen(from, to, fn) { | |
return forward({from: from.filterMap(fn), to}) | |
} | |
function split(unit, cases) { | |
const result = {} | |
let current = is.store(unit) ? unit.updates : unit | |
for (const key in cases) { | |
result[key] = current.filter({fn: cases[key]}) | |
current = current.filter({fn: invertCondition(cases[key])}) | |
} | |
result.__ = current | |
return result | |
function invertCondition(fn) { | |
return (data) => !fn(data) | |
} | |
} | |
const url = | |
'https://gist.githubusercontent.com/zerobias/' + | |
'e01f8a66fb11544a4d8db1f2d0d44298/raw/' + | |
'd157ee3cbb9fa14887c0f83cd96b9de26f5013cf/' + | |
'button.css' | |
const fetchRawCSS = createEffect('fetch raw data', { | |
async handler(params: any) { | |
const req = await fetch(url) | |
const result = await req.text() | |
return result | |
}, | |
}) | |
const rawLines = fetchRawCSS.done.map(({result}) => result.split(`\n`)) | |
const totalLines = createStore(0).on(rawLines, (_, lines) => lines.length) | |
const linesIterator = iterate(rawLines) | |
const lineTypes = split(linesIterator.item, { | |
empty: line => line === '', | |
declarationHead: line => /^\./.test(line), | |
declarationBody: line => /^ [a-zA-Z\-]+/.test(line), | |
declarationBodyClose: line => /^\}/.test(line), | |
commentInline: line => /^\/\*.*\*\/$/.test(line), | |
commentOpen: line => /^\/\*/.test(line), | |
commentBody: line => /^ +\w+/.test(line), | |
commentClose: line => /\*\/$/.test(line), | |
}) | |
const lineTypeHeadGroup = lineTypes.declarationHead.map(head => { | |
if (head.endsWith(',')) return head.slice(0, -1) | |
else if (head.endsWith(' {')) return head.slice(0, -2) | |
return head | |
}) | |
const lineTypeBodyGroup = lineTypes.declarationBody.map(body => { | |
const cleanLine = body.slice(2, -1) | |
const colon = cleanLine.indexOf(':') | |
return { | |
property: cleanLine.slice(0, colon), | |
value: cleanLine.slice(colon + 1), | |
} | |
}) | |
const declarations = createStore({ | |
list: [], | |
current: { | |
head: [], | |
body: [], | |
}, | |
}) | |
.on(lineTypeHeadGroup, (state, head) => { | |
state.current.head.push(head) | |
return state | |
}) | |
.on(lineTypeBodyGroup, (state, body) => { | |
state.current.body.push(body) | |
return state | |
}) | |
.on(lineTypes.declarationBodyClose, state => { | |
state.list.push({ | |
head: state.current.head, | |
body: state.current.body, | |
}) | |
state.current.head = [] | |
state.current.body = [] | |
return state | |
}) | |
const declarationList = sample({ | |
source: declarations, | |
clock: linesIterator.lifecycle.end, | |
target: createStore([]), | |
fn: ({list}) => list, | |
}) | |
const declarationListSize = sample({ | |
source: declarations, | |
clock: linesIterator.lifecycle.end, | |
target: createStore(0), | |
fn: ({list}) => list.length, | |
}) | |
const nextDeclaration = sample({ | |
source: declarations, | |
clock: lineTypes.declarationBodyClose, | |
fn: ({list}) => list[list.length - 1], | |
greedy: true, | |
}) | |
const easyRegex = /^[a-zA-Z\-.:]+$/ | |
const nextEasyDeclaration = nextDeclaration.map(({head}) => { | |
return head.filter((row) => easyRegex.test(row)) | |
}).filter({ | |
fn: head => head.length > 0, | |
}) | |
const easyDeclarations = sample({ | |
source: createStore([]) | |
.on(nextEasyDeclaration, (list, item) => { | |
list.push(item) | |
return list | |
}), | |
clock: linesIterator.lifecycle.end, | |
target: createStore([]) | |
}) | |
const lineTypesStats = (() => { | |
const statsList = [] | |
for (const key in lineTypes) { | |
const name = key === '__' ? 'other' : key | |
const store = createStore(0).on(lineTypes[key], (x) => x + 1) | |
const state = sample(store) | |
const View = () => <KeyVal.Item name={name} content={state} /> | |
statsList.push(<View key={name} />) | |
} | |
return statsList | |
})() | |
const Stats = () => ( | |
<KeyVal> | |
<KeyVal.Item name="declarations" content={declarationListSize} /> | |
<KeyVal.Item name="total lines" content={totalLines} /> | |
{lineTypesStats} | |
</KeyVal> | |
) | |
const LoadCss = () => ( | |
<button onClick={fetchRawCSS} disabled={useStore(fetchRawCSS.pending)}> | |
<code class="name"><load css></code> | |
</button> | |
) | |
const SourceFile = () => ( | |
<a href={url} target="_blank" rel="noopener noreferrer"> | |
<view parsed css file> | |
</a> | |
) | |
const Declaration = ({head, body, showBody}) => ( | |
<> | |
<section class="declaration"> | |
<header class="declaration__header"> | |
<h4> | |
{head.map((row) => ( | |
<div key={row}>{row}</div> | |
))} | |
</h4> | |
</header> | |
<footer> | |
{showBody && | |
body.map((row, i) => ( | |
<p key={i}> | |
<span class="property">{row.property}:</span> | |
{row.value} | |
</p> | |
))} | |
</footer> | |
</section> | |
</> | |
) | |
const EasyDeclarations = () => | |
useStore(easyDeclarations).map((head, i) => ( | |
<Declaration showBody={false} head={head} body={[]} key={i} /> | |
)) | |
const DeclarationList = () => | |
useStore(declarationList).map(({head, body}, i) => ( | |
<Declaration showBody head={head} body={body} key={i} /> | |
)) | |
const App = () => ( | |
<> | |
<LoadCss /> | |
<SourceFile/> | |
<Details> | |
<Details.Head>Stats</Details.Head> | |
<Stats /> | |
</Details> | |
<Details open> | |
<Details.Head>Declaration list</Details.Head> | |
<DeclarationList /> | |
</Details> | |
<Details> | |
<Details.Head>List of declarations with simple syntax</Details.Head> | |
<EasyDeclarations /> | |
</Details> | |
</> | |
) | |
const theme = { | |
borderRadius: '0 0 0 6px', | |
} | |
styled` | |
.property { | |
color: grey; | |
} | |
.declaration + .declaration { | |
margin-top: 1rem; | |
} | |
.declaration footer { | |
border-left: solid #217ac0 1px; | |
border-bottom: solid #217ac0 1px; | |
border-radius: ${theme.borderRadius}; | |
margin: -2px 0 0 5px; | |
} | |
.declaration p { | |
line-height: 1.3em; | |
margin: 0; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
padding-left: 7px; | |
} | |
.declaration footer:empty { | |
visibility: hidden; | |
} | |
.declaration p:first-of-type { | |
padding-top: 5px; | |
} | |
.declaration p:last-of-type { | |
padding-bottom: 4px; | |
} | |
.declaration h4 div { | |
overflow: hidden; | |
text-overflow: ellipsis; | |
} | |
.declaration h4 { | |
margin: 0; | |
display: inline-block; | |
font-family: 'Fira Sans', sans-serif; | |
background: #217ac0; | |
padding: 2px 6px 2px 6px; | |
border-radius: ${theme.borderRadius}; | |
color: white; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
} | |
.declaration__header { | |
display: flex; | |
} | |
button, a { | |
--button-color: #217ac0; | |
padding: 0.5rem 1rem; | |
} | |
button[disabled] { | |
--button-color: #7a7a7a; | |
} | |
.name, a { | |
cursor: pointer; | |
font-family: monospace; | |
font-weight: bold; | |
color: var(--button-color); | |
font-size: 14px; | |
text-decoration: none; | |
} | |
details {font-family: "Open Sans Light",Helvetica,Arial} | |
.attributes { margin-left: 22px; font-size: 90% } | |
.attributes p { margin-left: 16px; font-style: italic } | |
dd {margin-bottom: 1em;} | |
` | |
const KeyVal = ({children}) => <ul>{children}</ul> | |
KeyVal.Item = ({name, content}) => ( | |
<li> | |
<span>{name}</span> | |
<strong>{useStore(content)}</strong> | |
</li> | |
) | |
const Details = ({children, open}) => <details open={open}>{children}</details> | |
Details.Head = ({children}) => <summary>{children}</summary> | |
let perfStart | |
linesIterator.lifecycle.begin.map(() => { | |
perfStart = performance.now() | |
}) | |
sample(linesIterator.lifecycle.end).map(list => { | |
const time = performance.now() - perfStart | |
const ln = list.length | |
requestAnimationFrame(() => { | |
console.log(` | |
complete iteration over ${ln} items in ${time|0}ms | |
${(time/ln).toFixed(3)}ms per item | |
`) | |
}) | |
}) | |
ReactDOM.render(<App />, document.getElementById('root')) | |
setTimeout(fetchRawCSS, 2000) | |
function styled(tags, ...attrs) { | |
const value = style(tags, ...attrs) | |
const node = document.createElement('style') | |
node.id = "insertedStyle" | |
node.appendChild(document.createTextNode(value)) | |
const sheet = document.getElementById('insertedStyle') | |
if (sheet) { | |
sheet.disabled = true; | |
sheet.parentNode.removeChild(sheet) | |
} | |
document.head.appendChild(node) | |
function style(tags, ...attrs) { | |
if (tags.length === 0) return '' | |
let result = ' ' + tags[0] | |
for (let i = 0; i < attrs.length; i++) { | |
result += attrs[i] | |
result += tags[i + 1] | |
} | |
return result | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment