Add comprehensive frontend UI and distributed infrastructure

Frontend Enhancements:
- Complete React TypeScript frontend with modern UI components
- Distributed workflows management interface with real-time updates
- Socket.IO integration for live agent status monitoring
- Agent management dashboard with cluster visualization
- Project management interface with metrics and task tracking
- Responsive design with proper error handling and loading states

Backend Infrastructure:
- Distributed coordinator for multi-agent workflow orchestration
- Cluster management API with comprehensive agent operations
- Enhanced database models for agents and projects
- Project service for filesystem-based project discovery
- Performance monitoring and metrics collection
- Comprehensive API documentation and error handling

Documentation:
- Complete distributed development guide (README_DISTRIBUTED.md)
- Comprehensive development report with architecture insights
- System configuration templates and deployment guides

The platform now provides a complete web interface for managing the distributed AI cluster
with real-time monitoring, workflow orchestration, and agent coordination capabilities.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-07-10 08:41:59 +10:00
parent fc0eec91ef
commit 85bf1341f3
28348 changed files with 2646896 additions and 69 deletions

114
frontend/node_modules/goober/src/__tests__/css.test.js generated vendored Normal file
View File

@@ -0,0 +1,114 @@
import { css, glob, keyframes } from '../css';
import { hash } from '../core/hash';
import { compile } from '../core/compile';
import { getSheet } from '../core/get-sheet';
jest.mock('../core/hash', () => ({
hash: jest.fn().mockReturnValue('hash()')
}));
jest.mock('../core/compile', () => ({
compile: jest.fn().mockReturnValue('compile()')
}));
jest.mock('../core/get-sheet', () => ({
getSheet: jest.fn().mockReturnValue('getSheet()')
}));
describe('css', () => {
beforeEach(() => {
hash.mockClear();
compile.mockClear();
getSheet.mockClear();
});
it('type', () => {
expect(typeof css).toEqual('function');
});
it('args: tagged', () => {
const out = css`base${1}`;
expect(compile).toBeCalledWith(['base', ''], [1], undefined);
expect(getSheet).toBeCalled();
expect(hash).toBeCalledWith('compile()', 'getSheet()', undefined, undefined, undefined);
expect(out).toEqual('hash()');
});
it('args: object', () => {
const out = css({ foo: 1 });
expect(hash).toBeCalledWith({ foo: 1 }, 'getSheet()', undefined, undefined, undefined);
expect(compile).not.toBeCalled();
expect(getSheet).toBeCalled();
expect(out).toEqual('hash()');
});
it('args: array', () => {
const propsBased = jest.fn().mockReturnValue({
backgroundColor: 'gold'
});
const payload = [{ foo: 1 }, { baz: 2 }, { opacity: 0, color: 'red' }, propsBased];
const out = css(payload);
expect(propsBased).toHaveBeenCalled();
expect(hash).toBeCalledWith(
{ foo: 1, baz: 2, opacity: 0, color: 'red', backgroundColor: 'gold' },
'getSheet()',
undefined,
undefined,
undefined
);
expect(compile).not.toBeCalled();
expect(getSheet).toBeCalled();
expect(out).toEqual('hash()');
});
it('args: function', () => {
const incoming = { foo: 'foo' };
const out = css.call({ p: incoming }, (props) => ({ foo: props.foo }));
expect(hash).toBeCalledWith(incoming, 'getSheet()', undefined, undefined, undefined);
expect(compile).not.toBeCalled();
expect(getSheet).toBeCalled();
expect(out).toEqual('hash()');
});
it('bind', () => {
const target = '';
const p = {};
const g = true;
const out = css.bind({
target,
p,
g
})`foo: 1`;
expect(hash).toBeCalledWith('compile()', 'getSheet()', true, undefined, undefined);
expect(compile).toBeCalledWith(['foo: 1'], [], p);
expect(getSheet).toBeCalledWith(target);
expect(out).toEqual('hash()');
});
});
describe('glob', () => {
it('type', () => {
expect(typeof glob).toEqual('function');
});
it('args: g', () => {
glob`a:b`;
expect(hash).toBeCalledWith('compile()', 'getSheet()', 1, undefined, undefined);
});
});
describe('keyframes', () => {
it('type', () => {
expect(typeof keyframes).toEqual('function');
});
it('args: k', () => {
keyframes`a:b`;
expect(hash).toBeCalledWith('compile()', 'getSheet()', undefined, undefined, 1);
});
});

View File

@@ -0,0 +1,14 @@
import * as goober from '../index';
describe('goober', () => {
it('exports', () => {
expect(Object.keys(goober).sort()).toEqual([
'css',
'extractCss',
'glob',
'keyframes',
'setup',
'styled'
]);
});
});

View File

@@ -0,0 +1,246 @@
import { h, createContext, render } from 'preact';
import { useContext, forwardRef } from 'preact/compat';
import { setup, css, styled, keyframes } from '../index';
import { extractCss } from '../core/update';
describe('integrations', () => {
it('preact', () => {
const ThemeContext = createContext();
const useTheme = () => useContext(ThemeContext);
setup(h, null, useTheme);
const target = document.createElement('div');
const Span = styled('span', forwardRef)`
color: red;
`;
const SpanWrapper = styled('div')`
color: cyan;
${Span} {
border: 1px solid red;
}
`;
const BoxWithColor = styled('div')`
color: ${(props) => props.color};
`;
const BoxWithColorFn = styled('div')(
(props) => `
color: ${props.color};
`
);
const BoxWithThemeColor = styled('div')`
color: ${(props) => props.theme.color};
`;
const BoxWithThemeColorFn = styled('div')(
(props) => `
color: ${props.theme.color};
`
);
const fadeAnimation = keyframes`
0% {
opacity: 0;
}
99% {
opacity: 1;
color: dodgerblue;
}
`;
const BoxWithAnimation = styled('span')`
opacity: 0;
animation: ${fadeAnimation} 500ms ease-in-out;
`;
const BoxWithConditionals = styled('div')([
{ foo: 1 },
(props) => ({ color: props.isActive ? 'red' : 'tomato' }),
null,
{ baz: 0 },
false,
{ baz: 0 }
]);
const shared = { opacity: 0 };
const BoxWithShared = styled('div')(shared);
const BoxWithSharedAndConditional = styled('div')([shared, { baz: 0 }]);
const BoxWithHas = styled('div')`
label:has(input, select),
:has(foo, boo) {
color: red;
}
`;
const refSpy = jest.fn();
render(
<ThemeContext.Provider value={{ color: 'blue' }}>
<div>
<Span ref={refSpy} />
<Span as={'div'} />
<SpanWrapper>
<Span />
</SpanWrapper>
<BoxWithColor color={'red'} />
<BoxWithColorFn color={'red'} />
<BoxWithThemeColor />
<BoxWithThemeColorFn />
<BoxWithThemeColor theme={{ color: 'green' }} />
<BoxWithThemeColorFn theme={{ color: 'orange' }} />
<BoxWithAnimation />
<BoxWithConditionals isActive />
<BoxWithShared />
<BoxWithSharedAndConditional />
<div className={css([shared, { background: 'cyan' }])} />
<BoxWithHas />
</div>
</ThemeContext.Provider>,
target
);
expect(extractCss()).toMatchInlineSnapshot(
[
'"',
' ', // Empty white space that holds the textNode that the styles are appended
'@keyframes go384228713{0%{opacity:0;}99%{opacity:1;color:dodgerblue;}}',
'.go1127809067{opacity:0;background:cyan;}',
'.go3865451590{color:red;}',
'.go3991234422{color:cyan;}',
'.go3991234422 .go3865451590{border:1px solid red;}',
'.go1925576363{color:blue;}',
'.go3206651468{color:green;}',
'.go4276997079{color:orange;}',
'.go2069586824{opacity:0;animation:go384228713 500ms ease-in-out;}',
'.go631307347{foo:1;color:red;baz:0;}',
'.go3865943372{opacity:0;}',
'.go1162430001{opacity:0;baz:0;}',
'.go2602823658 label:has(input, select),.go2602823658 :has(foo, boo){color:red;}',
'"'
].join('')
);
expect(refSpy).toHaveBeenCalledWith(
expect.objectContaining({
tagName: 'SPAN'
})
);
});
it('support extending with as', () => {
const list = ['p', 'm', 'as', 'bg'];
setup(h, undefined, undefined, (props) => {
for (let prop in props) {
if (list.indexOf(prop) !== -1) {
delete props[prop];
}
}
});
const target = document.createElement('div');
const Base = styled('div')(({ p = 0, m }) => [
{
color: 'white',
padding: p + 'em'
},
m != null && { margin: m + 'em' }
]);
const Super = styled(Base)`
background: ${(p) => p.bg || 'none'};
`;
render(
<div>
<Base />
<Base p={2} />
<Base m={1} p={3} as={'span'} />
<Super m={1} bg={'dodgerblue'} as={'button'} />
</div>,
target
);
// Makes sure the resulting DOM does not contain any props
expect(target.innerHTML).toEqual(
[
'<div>',
'<div class="go103173764"></div>',
'<div class="go103194166"></div>',
'<span class="go2081835032"></span>',
'<button class="go1969245729 go1824201605"></button>',
'</div>'
].join('')
);
expect(extractCss()).toMatchInlineSnapshot(
[
'"',
'.go1969245729{color:white;padding:0em;margin:1em;}',
'.go103173764{color:white;padding:0em;}',
'.go103194166{color:white;padding:2em;}',
'.go2081835032{color:white;padding:3em;margin:1em;}',
'.go1824201605{background:dodgerblue;}',
'"'
].join('')
);
});
it('shouldForwardProps', () => {
const list = ['p', 'm', 'as'];
setup(h, undefined, undefined, (props) => {
for (let prop in props) {
if (list.indexOf(prop) !== -1) {
delete props[prop];
}
}
});
const target = document.createElement('div');
const Base = styled('div')(({ p = 0, m }) => [
{
color: 'white',
padding: p + 'em'
},
m != null && { margin: m + 'em' }
]);
render(
<div>
<Base />
<Base p={2} />
<Base m={1} p={3} as={'span'} />
</div>,
target
);
// Makes sure the resulting DOM does not contain any props
expect(target.innerHTML).toEqual(
[
'<div>',
'<div class="go103173764"></div>',
'<div class="go103194166"></div>',
'<span class="go2081835032"></span>',
'</div>'
].join(''),
`"<div><div class=\\"go103173764\\"></div><div class=\\"go103194166\\"></div><span class=\\"go2081835032\\"></span></div>"`
);
expect(extractCss()).toMatchInlineSnapshot(
[
'"',
'.go103173764{color:white;padding:0em;}',
'.go103194166{color:white;padding:2em;}',
'.go2081835032{color:white;padding:3em;margin:1em;}',
'"'
].join('')
);
});
});

View File

@@ -0,0 +1,159 @@
import { styled, setup } from '../styled';
import { extractCss } from '../core/update';
const pragma = jest.fn((tag, props) => {
return { tag, props: { ...props, className: props.className.replace(/go\d+/g, 'go') } };
});
expect.extend({
toMatchVNode(received, tag, props) {
expect(received.tag).toEqual(tag);
expect(received.props).toEqual(props);
return {
message: 'Expected vnode to match vnode',
pass: true
};
}
});
describe('styled', () => {
beforeEach(() => {
pragma.mockClear();
setup(pragma);
extractCss();
});
it('calls pragma', () => {
setup(undefined);
expect(() => styled()()()).toThrow();
setup(pragma);
const vnode = styled('div')``();
expect(pragma).toBeCalledTimes(1);
expect(vnode).toMatchVNode('div', {
className: 'go'
});
});
it('extend props', () => {
const vnode = styled('tag')`
color: peachpuff;
`({ bar: 1 });
expect(vnode).toMatchVNode('tag', {
bar: 1,
className: 'go'
});
expect(extractCss()).toEqual('.go3183460609{color:peachpuff;}');
});
it('concat className if present in props', () => {
const vnode = styled('tag')`
color: peachpuff;
`({ bar: 1, className: 'existing' });
expect(vnode).toMatchVNode('tag', {
bar: 1,
className: 'go existing'
});
});
it('pass template function', () => {
const vnode = styled('tag')((props) => ({ color: props.color }))({ color: 'red' });
expect(vnode).toMatchVNode('tag', {
className: 'go',
color: 'red'
});
expect(extractCss()).toEqual('.go3433634237{color:red;}');
});
it('change tag via "as" prop', () => {
const Tag = styled('tag')`
color: red;
`;
// Simulate a render
let vnode = Tag();
expect(vnode).toMatchVNode('tag', { className: 'go' });
// Simulate a render with
vnode = Tag({ as: 'foo' });
// Expect it to be changed to foo
expect(vnode).toMatchVNode('foo', { className: 'go' });
// Simulate a render
vnode = Tag();
expect(vnode).toMatchVNode('tag', { className: 'go' });
});
it('support forwardRef', () => {
const forwardRef = jest.fn((fn) => (props) => fn(props, 'ref'));
const vnode = styled('tag', forwardRef)`
color: red;
`({ bar: 1 });
expect(vnode).toMatchVNode('tag', {
bar: 1,
className: 'go',
ref: 'ref'
});
});
it('setup useTheme', () => {
setup(pragma, null, () => 'theme');
const styleFn = jest.fn(() => ({}));
const vnode = styled('tag')(styleFn)({ bar: 1 });
expect(styleFn).toBeCalledWith({ bar: 1, theme: 'theme' });
expect(vnode).toMatchVNode('tag', {
bar: 1,
className: 'go'
});
});
it('setup useTheme with theme prop override', () => {
setup(pragma, null, () => 'theme');
const styleFn = jest.fn(() => ({}));
const vnode = styled('tag')(styleFn)({ theme: 'override' });
expect(styleFn).toBeCalledWith({ theme: 'override' });
expect(vnode).toMatchVNode('tag', { className: 'go', theme: 'override' });
});
it('uses babel compiled classNames', () => {
const Comp = styled('tag')``;
Comp.className = 'foobar';
const vnode = Comp({});
expect(vnode).toMatchVNode('tag', { className: 'go foobar' });
});
it('omits css prop with falsy should forward prop function', () => {
const shouldForwardProp = (props) => {
for (let prop in props) {
if (prop.includes('$')) delete props[prop];
}
};
// Overwrite setup for this test
setup(pragma, undefined, undefined, shouldForwardProp);
const vnode = styled('tag')`
color: peachpuff;
`({ bar: 1, $templateColumns: '1fr 1fr' });
expect(vnode).toMatchVNode('tag', { className: 'go', bar: 1 });
});
it('pass truthy logical and operator', () => {
const Tag = styled('tag')((props) => props.draw && { color: 'yellow' });
// Simulate a render
let vnode = Tag({ draw: true });
expect(vnode).toMatchVNode('tag', { className: 'go', draw: true });
expect(extractCss()).toEqual('.go2986228274{color:yellow;}');
});
});

View File

@@ -0,0 +1,219 @@
import { astish } from '../astish';
describe('astish', () => {
it('regular', () => {
expect(
astish(`
prop: value;
`)
).toEqual({
prop: 'value'
});
});
it('nested', () => {
expect(
astish(`
prop: value;
@keyframes foo {
0% {
attr: value;
}
50% {
opacity: 1;
}
100% {
foo: baz;
}
}
named {
background-image: url('/path-to-jpg.png');
}
opacity: 0;
.class,
&:hover {
-webkit-touch: none;
}
`)
).toEqual({
prop: 'value',
opacity: '0',
'.class, &:hover': {
'-webkit-touch': 'none'
},
'@keyframes foo': {
'0%': {
attr: 'value'
},
'50%': {
opacity: '1'
},
'100%': {
foo: 'baz'
}
},
named: {
'background-image': "url('/path-to-jpg.png')"
}
});
});
it('merging', () => {
expect(
astish(`
.c {
font-size:24px;
}
.c {
color:red;
}
`)
).toEqual({
'.c': {
'font-size': '24px',
color: 'red'
}
});
});
it('regression', () => {
expect(
astish(`
&.g0ssss {
aa: foo;
box-shadow: 0 1px rgba(0, 2, 33, 4) inset;
}
named {
transform: scale(1.2), rotate(1, 1);
}
@media screen and (some-rule: 100px) {
foo: baz;
opacity: 1;
level {
one: 1;
level {
two: 2;
}
}
}
.a{
color: red;
}
.b {
color: blue;
}
`)
).toEqual({
'&.g0ssss': {
aa: 'foo',
'box-shadow': '0 1px rgba(0, 2, 33, 4) inset'
},
'.a': {
color: 'red'
},
'.b': {
color: 'blue'
},
named: {
transform: 'scale(1.2), rotate(1, 1)'
},
'@media screen and (some-rule: 100px)': {
foo: 'baz',
opacity: '1',
level: {
one: '1',
level: {
two: '2'
}
}
}
});
});
it('should strip comments', () => {
expect(
astish(`
color: red;
/*
some comment
*/
transform: translate3d(0, 0, 0);
/**
* other comment
*/
background: peachpuff;
font-size: xx-large; /* inline comment */
/* foo: bar */
font-weight: bold;
`)
).toEqual({
color: 'red',
transform: 'translate3d(0, 0, 0)',
background: 'peachpuff',
'font-size': 'xx-large',
'font-weight': 'bold'
});
});
// for reference on what is valid:
// https://www.w3.org/TR/CSS22/syndata.html#value-def-identifier
it('should not mangle valid css identifiers', () => {
expect(
astish(`
:root {
--azAZ-_中文09: 0;
}
`)
).toEqual({
':root': {
'--azAZ-_中文09': '0'
}
});
});
it('should parse multiline background declaration', () => {
expect(
astish(`
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="white"><path d="M7.5 36.7h58.4v10.6H7.5V36.7zm0-15.9h58.4v10.6H7.5V20.8zm0 31.9h58.4v10.6H7.5V52.7zm0 15.9h58.4v10.6H7.5V68.6zm63.8-15.9l10.6 15.9 10.6-15.9H71.3zm21.2-5.4L81.9 31.4 71.3 47.3h21.2z"/></svg>')
center/contain;
`)
).toEqual({
background: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="white"><path d="M7.5 36.7h58.4v10.6H7.5V36.7zm0-15.9h58.4v10.6H7.5V20.8zm0 31.9h58.4v10.6H7.5V52.7zm0 15.9h58.4v10.6H7.5V68.6zm63.8-15.9l10.6 15.9 10.6-15.9H71.3zm21.2-5.4L81.9 31.4 71.3 47.3h21.2z"/></svg>') center/contain`
});
});
it('should handle inline @media block', () => {
expect(
astish(
`h1 { font-size: 1rem; } @media only screen and (min-width: 850px) { h1 { font-size: 2rem; } }`
)
).toEqual({
h1: {
'font-size': '1rem'
},
'@media only screen and (min-width: 850px)': {
h1: {
'font-size': '2rem'
}
}
});
});
it('should handle newlines as part of the rule value', () => {
expect(
astish(
`tag {
font-size: first
second;
}`
)
).toEqual({
tag: {
'font-size': 'first second'
}
});
});
});

View File

@@ -0,0 +1,70 @@
import { compile } from '../compile';
const template = (str, ...defs) => {
return (props) => compile(str, defs, props);
};
describe('compile', () => {
it('simple', () => {
expect(template`prop: 1;`({})).toEqual('prop: 1;');
});
it('vnode', () => {
expect(template`prop: 1; ${() => ({ props: { className: 'class' } })}`({})).toEqual(
'prop: 1; .class'
);
// Empty or falsy
expect(template`prop: 1; ${() => ({ props: { foo: 1 } })}`({})).toEqual('prop: 1; ');
});
it('vanilla classname', () => {
expect(template`prop: 1; ${() => 'go0ber'}`({})).toEqual('prop: 1; .go0ber');
});
it('value interpolations', () => {
// This interpolations are testing the ability to interpolate thruty and falsy values
expect(template`prop: 1; ${() => 0},${() => undefined},${() => null},${2}`({})).toEqual(
'prop: 1; 0,,,2'
);
const tmpl = template`
background: dodgerblue;
${(props) =>
props.padding === 'bloo' &&
`
padding: ${props.padding}px;
`}
border: 1px solid blue;
`;
expect(tmpl({})).toEqual(`
background: dodgerblue;
border: 1px solid blue;
`);
});
describe('objects', () => {
it('normal', () => {
expect(template`prop: 1;${(p) => ({ color: p.color })}`({ color: 'red' })).toEqual(
'prop: 1;color:red;'
);
});
it('styled-system', () => {
const color = (p) => ({ color: p.color });
const background = (p) => ({ backgroundColor: p.backgroundColor });
const props = { color: 'red', backgroundColor: 'blue' };
const res = template`
prop: 1;
${color}
${background}
`(props);
expect(res.replace(/([\s|\n]+)/gm, '').trim()).toEqual(
'prop:1;color:red;background-color:blue;'
);
});
});
});

View File

@@ -0,0 +1,48 @@
import { getSheet } from '../get-sheet';
describe('getSheet', () => {
it('regression', () => {
const target = getSheet();
expect(target.nodeType).toEqual(3);
});
it('custom target', () => {
const custom = document.createElement('div');
const sheet = getSheet(custom);
expect(sheet.nodeType).toEqual(3);
expect(sheet.parentElement.nodeType).toEqual(1);
expect(sheet.parentElement.getAttribute('id')).toEqual('_goober');
});
it('reuse sheet', () => {
const custom = document.createElement('div');
const sheet = getSheet(custom);
const second = getSheet(custom);
expect(sheet === second).toBeTruthy();
});
it('server side', () => {
const bkp = global.document;
delete global.document;
expect(() => getSheet()).not.toThrow();
global.document = bkp;
});
it('server side with custom collector', () => {
const bkp = global.document;
const win = global.window;
delete global.document;
delete global.window;
const collector = { data: '' };
expect(collector === getSheet(collector)).toBeTruthy();
global.document = bkp;
global.window = win;
});
});

View File

@@ -0,0 +1,126 @@
import { hash } from '../hash';
import { toHash } from '../to-hash';
import { update } from '../update';
import { parse } from '../parse';
import { astish } from '../astish';
jest.mock('../astish', () => ({
astish: jest.fn().mockReturnValue('astish()')
}));
jest.mock('../parse', () => ({
parse: jest.fn().mockReturnValue('parse()')
}));
jest.mock('../to-hash', () => ({
toHash: jest.fn().mockReturnValue('toHash()')
}));
jest.mock('../update', () => ({
update: jest.fn().mockReturnValue('update()')
}));
jest.mock('../astish', () => ({
astish: jest.fn().mockReturnValue('astish()')
}));
jest.mock('../parse', () => ({
parse: jest.fn().mockReturnValue('parse()')
}));
describe('hash', () => {
beforeEach(() => {
toHash.mockClear();
update.mockClear();
parse.mockClear();
astish.mockClear();
});
it('regression', () => {
const res = hash('compiled', 'target');
expect(toHash).toBeCalledWith('compiled');
expect(update).toBeCalledWith('parse()', 'target', undefined, null);
expect(astish).toBeCalledWith('compiled');
expect(parse).toBeCalledWith('astish()', '.toHash()');
expect(res).toEqual('toHash()');
});
it('regression: cache', () => {
const res = hash('compiled', 'target');
expect(toHash).not.toBeCalled();
expect(astish).not.toBeCalled();
expect(parse).not.toBeCalled();
expect(update).toBeCalledWith('parse()', 'target', undefined, null);
expect(res).toEqual('toHash()');
});
it('regression: global', () => {
const res = hash('global', 'target', true);
expect(toHash).toBeCalledWith('global');
expect(astish).not.toBeCalled();
expect(parse).not.toBeCalled();
expect(update).toBeCalledWith('parse()', 'target', undefined, null);
expect(res).toEqual('toHash()');
});
it('regression: global-style-replace', () => {
const res = hash('global', 'target', true);
expect(toHash).not.toBeCalled();
expect(astish).not.toBeCalled();
expect(parse).not.toBeCalled();
expect(update).toBeCalledWith('parse()', 'target', undefined, 'parse()');
expect(res).toEqual('toHash()');
});
it('regression: keyframes', () => {
const res = hash('keyframes', 'target', undefined, undefined, 1);
expect(toHash).toBeCalledWith('keyframes');
expect(astish).not.toBeCalled();
expect(parse).not.toBeCalled();
expect(update).toBeCalledWith('parse()', 'target', undefined, null);
expect(res).toEqual('toHash()');
});
it('regression: object', () => {
const className = Math.random() + 'unique';
toHash.mockReturnValue(className);
const res = hash({ baz: 1 }, 'target');
expect(toHash).toBeCalledWith('baz1');
expect(astish).not.toBeCalled();
expect(parse).toBeCalledWith({ baz: 1 }, '.' + className);
expect(update).toBeCalledWith('parse()', 'target', undefined, null);
expect(res).toEqual(className);
});
it('regression: cache-object', () => {
const className = Math.random() + 'unique';
toHash.mockReturnValue(className);
// Since it's not yet cached
hash({ cacheObject: 1 }, 'target');
expect(toHash).toBeCalledWith('cacheObject1');
toHash.mockClear();
// Different object
hash({ foo: 2 }, 'target');
expect(toHash).toBeCalledWith('foo2');
toHash.mockClear();
// First object should not call .toHash
hash({ cacheObject: 1 }, 'target');
expect(toHash).not.toBeCalled();
});
});

View File

@@ -0,0 +1,327 @@
import { parse } from '../parse';
describe('parse', () => {
it('regular', () => {
const out = parse(
{
display: 'value',
button: {
border: '0'
},
'&.nested': {
foo: '1px',
baz: 'scale(1), translate(1)'
}
},
'hush'
);
expect(out).toEqual(
[
'hush{display:value;}',
'hush button{border:0;}',
'hush.nested{foo:1px;baz:scale(1), translate(1);}'
].join('')
);
});
it('camelCase', () => {
const out = parse(
{
fooBarProperty: 'value',
button: {
webkitPressSomeButton: '0'
},
'&.nested': {
foo: '1px',
backgroundEffect: 'scale(1), translate(1)'
}
},
'hush'
);
expect(out).toEqual(
[
'hush{foo-bar-property:value;}',
'hush button{webkit-press-some-button:0;}',
'hush.nested{foo:1px;background-effect:scale(1), translate(1);}'
].join('')
);
});
it('keyframes', () => {
const out = parse(
{
'@keyframes superAnimation': {
'11.1%': {
opacity: '0.9999'
},
'111%': {
opacity: '1'
}
},
'@keyframes foo': {
to: {
baz: '1px',
foo: '1px'
}
},
'@keyframes complex': {
'from, 20%, 53%, 80%, to': {
transform: 'translate3d(0,0,0)'
},
'40%, 43%': {
transform: 'translate3d(0, -30px, 0)'
},
'70%': {
transform: 'translate3d(0, -15px, 0)'
},
'90%': {
transform: 'translate3d(0,-4px,0)'
}
}
},
'hush'
);
expect(out).toEqual(
[
'@keyframes superAnimation{11.1%{opacity:0.9999;}111%{opacity:1;}}',
'@keyframes foo{to{baz:1px;foo:1px;}}',
'@keyframes complex{from, 20%, 53%, 80%, to{transform:translate3d(0,0,0);}40%, 43%{transform:translate3d(0, -30px, 0);}70%{transform:translate3d(0, -15px, 0);}90%{transform:translate3d(0,-4px,0);}}'
].join('')
);
});
it('font-face', () => {
const out = parse(
{
'@font-face': {
'font-weight': 100
}
},
'FONTFACE'
);
expect(out).toEqual(['@font-face{font-weight:100;}'].join(''));
});
it('@media', () => {
const out = parse(
{
'@media any all (no-really-anything)': {
position: 'super-absolute'
}
},
'hush'
);
expect(out).toEqual(
['@media any all (no-really-anything){hush{position:super-absolute;}}'].join('')
);
});
it('@import', () => {
const out = parse(
{
'@import': "url('https://domain.com/path?1=s')"
},
'hush'
);
expect(out).toEqual(["@import url('https://domain.com/path?1=s');"].join(''));
});
it('cra', () => {
expect(
parse(
{
'@import': "url('path/to')",
'@font-face': {
'font-weight': 100
},
'text-align': 'center',
'.logo': {
animation: 'App-logo-spin infinite 20s linear',
height: '40vmin',
'pointer-events': 'none'
},
'.header': {
'background-color': '#282c34',
'min-height': '100vh',
display: 'flex',
'flex-direction': 'column',
'align-items': 'center',
'justify-content': 'center',
'font-size': 'calc(10px + 2vmin)',
color: 'white'
},
'.link': {
color: '#61dafb'
},
'@keyframes App-logo-spin': {
from: {
transform: 'rotate(0deg)'
},
to: {
transform: 'rotate(360deg)'
}
}
},
'App'
)
).toEqual(
[
"@import url('path/to');",
'App{text-align:center;}',
'@font-face{font-weight:100;}',
'App .logo{animation:App-logo-spin infinite 20s linear;height:40vmin;pointer-events:none;}',
'App .header{background-color:#282c34;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:calc(10px + 2vmin);color:white;}',
'App .link{color:#61dafb;}',
'@keyframes App-logo-spin{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}'
].join('')
);
});
it('@supports', () => {
expect(
parse(
{
'@supports (some: 1px)': {
'@media (s: 1)': {
display: 'flex'
}
},
'@supports': {
opacity: 1
}
},
'hush'
)
).toEqual(
[
'@supports (some: 1px){@media (s: 1){hush{display:flex;}}}',
'@supports{hush{opacity:1;}}'
].join('')
);
});
it('unwrapp', () => {
expect(
parse(
{
'--foo': 1,
opacity: 1,
'@supports': {
'--bar': 'none'
},
html: {
background: 'goober'
}
},
''
)
).toEqual(
['--foo:1;opacity:1;', '@supports{--bar:none;}', 'html{background:goober;}'].join('')
);
});
it('nested with multiple selector', () => {
const out = parse(
{
display: 'value',
'&:hover,&:focus': {
border: '0',
span: {
index: 'unset'
}
},
'p,b,i': {
display: 'block',
'&:focus,input': {
opacity: 1,
'div,span': {
opacity: 0
}
}
}
},
'hush'
);
expect(out).toEqual(
[
'hush{display:value;}',
'hush:hover,hush:focus{border:0;}',
'hush:hover span,hush:focus span{index:unset;}',
'hush p,hush b,hush i{display:block;}',
'hush p:focus,hush p input,hush b:focus,hush b input,hush i:focus,hush i input{opacity:1;}',
'hush p:focus div,hush p:focus span,hush p input div,hush p input span,hush b:focus div,hush b:focus span,hush b input div,hush b input span,hush i:focus div,hush i:focus span,hush i input div,hush i input span{opacity:0;}'
].join('')
);
});
it('should handle the :where(a,b) cases', () => {
expect(
parse(
{
div: {
':where(a, b)': {
color: 'blue'
}
}
},
''
)
).toEqual('div :where(a, b){color:blue;}');
});
it('should handle null and undefined values', () => {
expect(
parse(
{
div: {
opacity: 0,
color: null
}
},
''
)
).toEqual('div{opacity:0;}');
expect(
parse(
{
div: {
opacity: 0,
color: undefined // or `void 0` when minified
}
},
''
)
).toEqual('div{opacity:0;}');
});
it('does not transform the case of custom CSS variables', () => {
expect(
parse({
'--cP': 'red'
})
).toEqual('--cP:red;');
expect(
parse({
'--c-P': 'red'
})
).toEqual('--c-P:red;');
expect(
parse({
'--cp': 'red'
})
).toEqual('--cp:red;');
expect(
parse({
':root': {
'--cP': 'red'
}
})
).toEqual(':root{--cP:red;}');
});
});

View File

@@ -0,0 +1,17 @@
import { toHash } from '../to-hash';
describe('to-hash', () => {
it('regression', () => {
const res = toHash('goober');
expect(res).toEqual('go1990315141');
expect(toHash('goober')).toEqual('go1990315141');
});
it('collision', () => {
const a = toHash('background:red;color:black;');
const b = toHash('background:black;color:red;');
expect(a === b).toBeFalsy();
});
});

View File

@@ -0,0 +1,64 @@
import { update, extractCss } from '../update';
import { getSheet } from '../get-sheet';
describe('update', () => {
it('regression', () => {
const t = { data: '' };
update('css', t);
expect(t.data).toEqual('css');
});
it('regression: duplicate', () => {
const t = { data: '' };
update('css', t);
update('foo', t);
update('css', t);
expect(t.data).toEqual('cssfoo');
});
it('regression: extract and flush', () => {
update('filled', getSheet());
expect(extractCss()).toEqual(' filled');
expect(extractCss()).toEqual('');
});
it('regression: extract and flush without DOM', () => {
const bkp = global.self;
delete global.self;
update('filled', getSheet());
expect(extractCss()).toEqual('filled');
expect(extractCss()).toEqual('');
global.self = bkp;
});
it('regression: extract and flush from custom target', () => {
const target = document.createElement('div');
update('filled', getSheet());
update('filledbody', getSheet(target));
expect(extractCss(target)).toEqual(' filledbody');
expect(extractCss(target)).toEqual('');
});
it('regression: append or prepend', () => {
extractCss();
update('end', getSheet());
update('start', getSheet(), true);
expect(extractCss()).toEqual('startend');
});
it('regression: global style replacement', () => {
const t = { data: 'html, body { background-color: white; }' };
update(
'html, body { background-color: black; }',
t,
undefined,
'html, body { background-color: white; }'
);
expect(t.data).toEqual('html, body { background-color: black; }');
});
});

28
frontend/node_modules/goober/src/core/astish.js generated vendored Normal file
View File

@@ -0,0 +1,28 @@
let newRule = /(?:([\u0080-\uFFFF\w-%@]+) *:? *([^{;]+?);|([^;}{]*?) *{)|(}\s*)/g;
let ruleClean = /\/\*[^]*?\*\/| +/g;
let ruleNewline = /\n+/g;
let empty = ' ';
/**
* Convert a css style string into a object
* @param {String} val
* @returns {Object}
*/
export let astish = (val) => {
let tree = [{}];
let block, left;
while ((block = newRule.exec(val.replace(ruleClean, '')))) {
// Remove the current entry
if (block[4]) {
tree.shift();
} else if (block[3]) {
left = block[3].replace(ruleNewline, empty).trim();
tree.unshift((tree[0][left] = tree[0][left] || {}));
} else {
tree[0][block[1]] = block[2].replace(ruleNewline, empty).trim();
}
}
return tree[0];
};

41
frontend/node_modules/goober/src/core/compile.js generated vendored Normal file
View File

@@ -0,0 +1,41 @@
import { parse } from './parse';
/**
* Can parse a compiled string, from a tagged template
* @param {String} value
* @param {Object} [props]
*/
export let compile = (str, defs, data) => {
return str.reduce((out, next, i) => {
let tail = defs[i];
// If this is a function we need to:
if (tail && tail.call) {
// 1. Call it with `data`
let res = tail(data);
// 2. Grab the className
let className = res && res.props && res.props.className;
// 3. If there's none, see if this is basically a
// previously styled className by checking the prefix
let end = className || (/^go/.test(res) && res);
if (end) {
// If the `end` is defined means it's a className
tail = '.' + end;
} else if (res && typeof res == 'object') {
// If `res` it's an object, we're either dealing with a vnode
// or an object returned from a function interpolation
tail = res.props ? '' : parse(res, '');
} else {
// Regular value returned. Can be falsy as well.
// Here we check if this is strictly a boolean with false value
// define it as `''` to be picked up as empty, otherwise use
// res value
tail = res === false ? '' : res;
}
}
return out + next + (tail == null ? '' : tail);
}, '');
};

25
frontend/node_modules/goober/src/core/get-sheet.js generated vendored Normal file
View File

@@ -0,0 +1,25 @@
let GOOBER_ID = '_goober';
let ssr = {
data: ''
};
/**
* Returns the _commit_ target
* @param {Object} [target]
* @returns {HTMLStyleElement|{data: ''}}
*/
export let getSheet = (target) => {
if (typeof window === 'object') {
// Querying the existing target for a previously defined <style> tag
// We're doing a querySelector because the <head> element doesn't implemented the getElementById api
return (
(target ? target.querySelector('#' + GOOBER_ID) : window[GOOBER_ID]) ||
Object.assign((target || document.head).appendChild(document.createElement('style')), {
innerHTML: ' ',
id: GOOBER_ID
})
).firstChild;
}
return target || ssr;
};

67
frontend/node_modules/goober/src/core/hash.js generated vendored Normal file
View File

@@ -0,0 +1,67 @@
import { toHash } from './to-hash';
import { update } from './update';
import { astish } from './astish';
import { parse } from './parse';
/**
* In-memory cache.
*/
let cache = {};
/**
* Stringifies a object structure
* @param {Object} data
* @returns {String}
*/
let stringify = (data) => {
if (typeof data == 'object') {
let out = '';
for (let p in data) out += p + stringify(data[p]);
return out;
} else {
return data;
}
};
/**
* Generates the needed className
* @param {String|Object} compiled
* @param {Object} sheet StyleSheet target
* @param {Object} global Global flag
* @param {Boolean} append Append or not
* @param {Boolean} keyframes Keyframes mode. The input is the keyframes body that needs to be wrapped.
* @returns {String}
*/
export let hash = (compiled, sheet, global, append, keyframes) => {
// Get a string representation of the object or the value that is called 'compiled'
let stringifiedCompiled = stringify(compiled);
// Retrieve the className from cache or hash it in place
let className =
cache[stringifiedCompiled] || (cache[stringifiedCompiled] = toHash(stringifiedCompiled));
// If there's no entry for the current className
if (!cache[className]) {
// Build the _ast_-ish structure if needed
let ast = stringifiedCompiled !== compiled ? compiled : astish(compiled);
// Parse it
cache[className] = parse(
// For keyframes
keyframes ? { ['@keyframes ' + className]: ast } : ast,
global ? '' : '.' + className
);
}
// If the global flag is set, save the current stringified and compiled CSS to `cache.g`
// to allow replacing styles in <style /> instead of appending them.
// This is required for using `createGlobalStyles` with themes
let cssToReplace = global && cache.g ? cache.g : null;
if (global) cache.g = cache[className];
// add or update
update(cache[className], sheet, append, cssToReplace);
// return hash
return className;
};

60
frontend/node_modules/goober/src/core/parse.js generated vendored Normal file
View File

@@ -0,0 +1,60 @@
/**
* Parses the object into css, scoped, blocks
* @param {Object} obj
* @param {String} selector
* @param {String} wrapper
*/
export let parse = (obj, selector) => {
let outer = '';
let blocks = '';
let current = '';
for (let key in obj) {
let val = obj[key];
if (key[0] == '@') {
// If these are the `@` rule
if (key[1] == 'i') {
// Handling the `@import`
outer = key + ' ' + val + ';';
} else if (key[1] == 'f') {
// Handling the `@font-face` where the
// block doesn't need the brackets wrapped
blocks += parse(val, key);
} else {
// Regular at rule block
blocks += key + '{' + parse(val, key[1] == 'k' ? '' : selector) + '}';
}
} else if (typeof val == 'object') {
// Call the parse for this block
blocks += parse(
val,
selector
? // Go over the selector and replace the matching multiple selectors if any
selector.replace(/([^,])+/g, (sel) => {
// Return the current selector with the key matching multiple selectors if any
return key.replace(/([^,]*:\S+\([^)]*\))|([^,])+/g, (k) => {
// If the current `k`(key) has a nested selector replace it
if (/&/.test(k)) return k.replace(/&/g, sel);
// If there's a current selector concat it
return sel ? sel + ' ' + k : k;
});
})
: key
);
} else if (val != undefined) {
// Convert all but CSS variables
key = /^--/.test(key) ? key : key.replace(/[A-Z]/g, '-$&').toLowerCase();
// Push the line for this property
current += parse.p
? // We have a prefixer and we need to run this through that
parse.p(key, val)
: // Nope no prefixer just append it
key + ':' + val + ';';
}
}
// If we have properties apply standard rule composition
return outer + (selector && current ? selector + '{' + current + '}' : current) + blocks;
};

15
frontend/node_modules/goober/src/core/to-hash.js generated vendored Normal file
View File

@@ -0,0 +1,15 @@
/**
* Transforms the input into a className.
* The multiplication constant 101 is selected to be a prime,
* as is the initial value of 11.
* The intermediate and final results are truncated into 32-bit
* unsigned integers.
* @param {String} str
* @returns {String}
*/
export let toHash = (str) => {
let i = 0,
out = 11;
while (i < str.length) out = (101 * out + str.charCodeAt(i++)) >>> 0;
return 'go' + out;
};

25
frontend/node_modules/goober/src/core/update.js generated vendored Normal file
View File

@@ -0,0 +1,25 @@
import { getSheet } from './get-sheet';
/**
* Extracts and wipes the cache
* @returns {String}
*/
export let extractCss = (target) => {
let sheet = getSheet(target);
let out = sheet.data;
sheet.data = '';
return out;
};
/**
* Updates the target and keeps a local cache
* @param {String} css
* @param {Object} sheet
* @param {Boolean} append
* @param {?String} cssToReplace
*/
export let update = (css, sheet, append, cssToReplace) => {
cssToReplace
? (sheet.data = sheet.data.replace(cssToReplace, css))
: sheet.data.indexOf(css) === -1 &&
(sheet.data = append ? css + sheet.data : sheet.data + css);
};

40
frontend/node_modules/goober/src/css.js generated vendored Normal file
View File

@@ -0,0 +1,40 @@
import { hash } from './core/hash';
import { compile } from './core/compile';
import { getSheet } from './core/get-sheet';
/**
* css entry
* @param {String|Object|Function} val
*/
function css(val) {
let ctx = this || {};
let _val = val.call ? val(ctx.p) : val;
return hash(
_val.unshift
? _val.raw
? // Tagged templates
compile(_val, [].slice.call(arguments, 1), ctx.p)
: // Regular arrays
_val.reduce((o, i) => Object.assign(o, i && i.call ? i(ctx.p) : i), {})
: _val,
getSheet(ctx.target),
ctx.g,
ctx.o,
ctx.k
);
}
/**
* CSS Global function to declare global styles
* @type {Function}
*/
let glob = css.bind({ g: 1 });
/**
* `keyframes` function for defining animations
* @type {Function}
*/
let keyframes = css.bind({ k: 1 });
export { css, glob, keyframes };

3
frontend/node_modules/goober/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,3 @@
export { styled, setup } from './styled';
export { extractCss } from './core/update';
export { css, glob, keyframes } from './css';

73
frontend/node_modules/goober/src/styled.js generated vendored Normal file
View File

@@ -0,0 +1,73 @@
import { css } from './css';
import { parse } from './core/parse';
let h, useTheme, fwdProp;
function setup(pragma, prefix, theme, forwardProps) {
// This one needs to stay in here, so we won't have cyclic dependencies
parse.p = prefix;
// These are scope to this context
h = pragma;
useTheme = theme;
fwdProp = forwardProps;
}
/**
* styled function
* @param {string} tag
* @param {function} forwardRef
*/
function styled(tag, forwardRef) {
let _ctx = this || {};
return function wrapper() {
let _args = arguments;
function Styled(props, ref) {
// Grab a shallow copy of the props
let _props = Object.assign({}, props);
// Keep a local reference to the previous className
let _previousClassName = _props.className || Styled.className;
// _ctx.p: is the props sent to the context
_ctx.p = Object.assign({ theme: useTheme && useTheme() }, _props);
// Set a flag if the current components had a previous className
// similar to goober. This is the append/prepend flag
// The _empty_ space compresses better than `\s`
_ctx.o = / *go\d+/.test(_previousClassName);
_props.className =
// Define the new className
css.apply(_ctx, _args) + (_previousClassName ? ' ' + _previousClassName : '');
// If the forwardRef fun is defined we have the ref
if (forwardRef) {
_props.ref = ref;
}
// Assign the _as with the provided `tag` value
let _as = tag;
// If this is a string -- checking that is has a first valid char
if (tag[0]) {
// Try to assign the _as with the given _as value if any
_as = _props.as || tag;
// And remove it
delete _props.as;
}
// Handle the forward props filter if defined and _as is a string
if (fwdProp && _as[0]) {
fwdProp(_props);
}
return h(_as, _props);
}
return forwardRef ? forwardRef(Styled) : Styled;
};
}
export { styled, setup };