Skip to content

Commit ec7f081

Browse files
committed
feat: break up leaf UI into understandable chunks
1 parent 377e3b9 commit ec7f081

1 file changed

Lines changed: 163 additions & 3 deletions

File tree

src/UI.php

Lines changed: 163 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
use Leaf\UI\Component;
66
use Leaf\UI\Core;
7+
use Leaf\UI\Parser;
8+
use Leaf\UI\Utils;
9+
use MatthiasMullie\Minify\CSS;
710

811
/**
912
* Leaf UI
@@ -14,11 +17,168 @@
1417
*/
1518
class UI
1619
{
20+
21+
22+
/**
23+
* Initialize Leaf UI on a page
24+
* @return string
25+
*/
26+
public static function init(): string
27+
{
28+
return (
29+
implode(Core::$scripts) .
30+
static::createElement('script', [
31+
'src' => '/vendor/leafs/ui/client/dist/ui.cjs.production.min.js',
32+
], [''])
33+
);
34+
}
35+
36+
/**
37+
* Render a Leaf UI
38+
*
39+
* @param Component $component The Leaf UI component to render
40+
*/
41+
public static function render($component)
42+
{
43+
$config = json_decode((new \Leaf\Http\Request())->get('_leaf_ui_config', false) ?? '', true) ?? [];
44+
45+
if (!$component->key) {
46+
if (isset($config['component'])) {
47+
$component->key = $config['component'];
48+
} else {
49+
$component->key = Utils::randomId($component::class);
50+
}
51+
}
52+
53+
if (isset($config['payload']['data'])) {
54+
Core::$state = array_merge(Core::$state, $config['payload']['data']);
55+
}
56+
57+
Core::$state[$component->key] = array_merge(get_class_vars($component::class), [
58+
'key' => $component->key,
59+
]);
60+
61+
Core::$componentMethods = array_merge(Core::$componentMethods, get_class_methods($component));
62+
Core::$mappedComponentMethods = array_merge(Core::$mappedComponentMethods, [$component->key => get_class_methods($component)]);
63+
Core::$components = array_merge(Core::$components, [$component->key => $component::class]);
64+
65+
$componentData = Core::buildComponent($component, $config);
66+
67+
if ($componentData['responseType'] === 'json') {
68+
unset($componentData['responseType']);
69+
(new \Leaf\Http\Response())->json($componentData);
70+
} else {
71+
(new \Leaf\Http\Response())->markup($componentData['html']);
72+
}
73+
}
74+
75+
/**
76+
* Render a Leaf UI from a file
77+
*
78+
* @throws \JsonException
79+
*/
80+
public static function view(string $filename): string
81+
{
82+
if (!file_exists($filename)) {
83+
throw new \JsonException("$filename not found!");
84+
}
85+
86+
return Parser::compileTemplate(file_get_contents($filename), Core::mergeState());
87+
}
88+
89+
/**
90+
* Embed a component into a view
91+
*
92+
* @param string $component The component to embed
93+
* @return string
94+
*/
95+
public static function component(string $component, array $props = []): string
96+
{
97+
$component = trim($component, '"\'\`');
98+
99+
if (!class_exists($component)) {
100+
trigger_error($component . ' does not exist', E_USER_ERROR);
101+
}
102+
103+
$component = new $component;
104+
105+
if (!$component->key) {
106+
$component->key = $props['key'] ?? Utils::randomId($component::class);
107+
}
108+
109+
Core::$state[$component->key] = array_merge(get_class_vars($component::class), Core::$state[$component->key] ?? [], ['key' => $component->key], $props);
110+
111+
foreach (Core::$state[$component->key] as $key => $value) {
112+
$component->{$key} = $value;
113+
}
114+
115+
Core::$componentMethods = array_merge(Core::$componentMethods, get_class_methods($component));
116+
Core::$mappedComponentMethods = array_merge(Core::$mappedComponentMethods, [$component->key => get_class_methods($component)]);
117+
Core::$components = array_merge(Core::$components, [$component->key => $component::class]);
118+
119+
$config = [
120+
'type' => 'component',
121+
'payload' => [
122+
'params' => [],
123+
'method' => null,
124+
'methodArgs' => [],
125+
'data' => Core::$state,
126+
],
127+
];
128+
129+
$componentData = Core::buildComponent($component, $config, true);
130+
131+
return $componentData['html'];
132+
}
133+
134+
/**
135+
* Create an HTML element
136+
*
137+
* @param string $element The HTML Element to create
138+
* @param array $props The Element children and attributes eg: `style`
139+
* @param string|array|null $children The component children
140+
*/
141+
public static function createElement(string $element, array $props = [], $children = [])
142+
{
143+
$subs = '';
144+
$attributes = '';
145+
146+
if (isset($props['children']) && (!$children || ($children && empty($children)))) {
147+
$children = $props['children'];
148+
unset($props['children']);
149+
}
150+
151+
if (is_array($children)) {
152+
foreach ($children as $child) {
153+
$subs .= $child;
154+
}
155+
} else {
156+
$subs = $children;
157+
}
158+
159+
if (!empty($props)) {
160+
foreach ($props as $key => $value) {
161+
$attributes .= "$key=\"" . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . "\" ";
162+
}
163+
}
164+
165+
return (!$children || $children && empty($children))
166+
? "<$element $attributes />"
167+
: "<$element $attributes>$subs</$element>";
168+
}
169+
17170
/**
18-
* @inheritDoc
171+
* Map styles to style tag
172+
*
173+
* @param array $styles The styles to apply
174+
* @param array $props Style tag attributes
19175
*/
20-
public static function render(Component $component)
176+
public static function createStyles(array $styles, array $props = [])
21177
{
22-
return Core::render($component);
178+
return self::createElement(
179+
'style',
180+
$props,
181+
(new CSS())->add(Parser::parseStyles($styles))->minify()
182+
);
23183
}
24184
}

0 commit comments

Comments
 (0)