Skip to content

Commit 00a0ddd

Browse files
Merge pull request #1841 from NullVoxPopuli/xstate-5
Upgrade to XState 5
2 parents 4069dd9 + 239b90c commit 00a0ddd

File tree

9 files changed

+1025
-536
lines changed

9 files changed

+1025
-536
lines changed

apps/repl/app/app.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'limber-ui/theme.css';
2+
import 'ember-statechart-component';
23

34
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
45
// @ts-ignore
@@ -10,8 +11,6 @@ import { _backburner } from '@ember/runloop';
1011

1112
import loadInitializers from 'ember-load-initializers';
1213
import Resolver from 'ember-resolver';
13-
import { setupComponentMachines } from 'ember-statechart-component';
14-
import { StateNode } from 'xstate';
1514

1615
import config from 'limber/config/environment';
1716

@@ -41,5 +40,3 @@ export default class App extends Application {
4140
}
4241

4342
loadInitializers(App, config.modulePrefix);
44-
45-
setupComponentMachines(StateNode);
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,66 @@
1+
import { waitForPromise } from '@ember/test-waiters';
2+
3+
import { resource, resourceFactory } from 'ember-resources';
4+
import { TrackedObject } from 'tracked-built-ins';
5+
16
import { service } from 'limber-ui';
27

3-
import codemirror from './-code-mirror';
8+
import codemirror, { setupCodeMirror } from './-code-mirror';
49
import Loader from './loader';
510
import { LoadingError } from './loading-error';
611
import { Placeholder } from './placeholder';
7-
import State from './state';
812

913
import type { TOC } from '@ember/component/template-only';
1014

15+
function deferCodemirror() {
16+
let state = new TrackedObject({ isLoading: false, isDone: false, error: null });
17+
18+
function getEditor() {
19+
state.isLoading = true;
20+
waitForPromise(setupCodeMirror())
21+
.then(() => {
22+
state.isDone = true;
23+
})
24+
.catch((error) => (state.error = error))
25+
.finally(() => {
26+
state.isLoading = false;
27+
});
28+
}
29+
30+
function cleanup() {
31+
window.removeEventListener('mousemove', load);
32+
window.removeEventListener('keydown', load);
33+
window.removeEventListener('touchstart', load);
34+
}
35+
36+
let load = () => {
37+
getEditor();
38+
cleanup();
39+
};
40+
41+
return resource(({ on, owner }) => {
42+
if (owner.lookup('service:router').currentRoute?.queryParams?.['forceEditor'] === 'true') {
43+
load();
44+
}
45+
46+
on.cleanup(() => cleanup());
47+
48+
window.addEventListener('mousemove', load, { passive: true });
49+
window.addEventListener('keydown', load, { passive: true });
50+
window.addEventListener('touchstart', load, { passive: true });
51+
52+
return state;
53+
});
54+
}
55+
56+
resourceFactory(deferCodemirror);
57+
1158
export const Editor: TOC<{
1259
Element: HTMLDivElement;
1360
}> = <template>
14-
<State as |state|>
61+
{{#let (deferCodemirror) as |state|}}
1562

16-
{{#if (state.matches "editingWithCodeMirror")}}
63+
{{#if state.isDone}}
1764

1865
{{#let (service "editor") as |context|}}
1966
<div class="overflow-hidden overflow-y-auto">
@@ -28,19 +75,19 @@ export const Editor: TOC<{
2875
...attributes
2976
>
3077

31-
{{#if (state.matches "loadCodeMirror")}}
78+
{{#if state.isLoading}}
3279
<Loader />
3380
{{/if}}
3481

35-
{{#if (state.matches "error")}}
36-
<LoadingError @error={{state.context.error}} />
82+
{{#if state.error}}
83+
<LoadingError @error={{state.error}} />
3784
{{/if}}
3885

3986
<Placeholder />
4087
</div>
4188
{{/if}}
4289

43-
</State>
90+
{{/let}}
4491
</template>;
4592

4693
export default Editor;

apps/repl/app/components/limber/editor/state.ts

-67
This file was deleted.

apps/repl/app/components/limber/layout/index.gts

+30-17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
// need to Fix something in ember-statechart-component
2-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
3-
// @ts-nocheck
41
import { assert } from '@ember/debug';
5-
import { fn, hash } from '@ember/helper';
2+
import { fn } from '@ember/helper';
63

74
import { modifier } from 'ember-modifier';
85

@@ -11,17 +8,21 @@ import { EditorContainer, OutputContainer } from './containers';
118
import { Controls } from './controls';
129
import { Orientation } from './orientation';
1310
import { ResizeHandle } from './resize-handle';
14-
import State, { isHorizontalSplit, setupResizeObserver } from './state';
11+
import { isHorizontalSplit, LayoutState, setupResizeObserver } from './state';
1512

1613
import type { TOC } from '@ember/component/template-only';
17-
import type { Send, State as StateFor } from 'ember-statechart-component/glint';
14+
import type { ReactiveActorFrom } from 'ember-statechart-component';
1815

19-
const setupState = modifier((element: Element, [send]: [Send<unknown>]) => {
16+
type ReactiveActor = ReactiveActorFrom<typeof LayoutState>;
17+
18+
const setupState = modifier((element: Element, [send]: [(event: string) => void]) => {
2019
assert(`Element is not resizable`, element instanceof HTMLElement);
2120

2221
let observer = setupResizeObserver(() => send('RESIZE'));
2322

24-
send('CONTAINER_FOUND', {
23+
// @ts-expect-error need to fix the type of this for ember-statechart-component
24+
send({
25+
type: 'CONTAINER_FOUND',
2526
container: element,
2627
observer,
2728
maximize: () => send('MAXIMIZE'),
@@ -37,46 +38,58 @@ const effect = (fn: (...args: unknown[]) => void) => {
3738
fn();
3839
};
3940

40-
const isResizable = (state: StateFor<typeof State>) => {
41+
const isResizable = (state: ReactiveActor) => {
4142
return !(state.matches('hasContainer.minimized') || state.matches('hasContainer.maximized'));
4243
};
4344

4445
/**
4546
* true for horizontally split
4647
* false for vertically split
4748
*/
48-
const containerDirection = (state: StateFor<typeof State>) => {
49+
const containerDirection = (state: ReactiveActor) => {
4950
if (state.matches('hasContainer.default.horizontallySplit')) {
5051
return true;
5152
}
5253

53-
return isHorizontalSplit(state.context);
54+
return isHorizontalSplit(state.snapshot);
5455
};
5556

57+
function updateOrientation(isVertical: boolean) {
58+
return {
59+
type: 'ORIENTATION',
60+
isVertical,
61+
};
62+
}
63+
5664
export const Layout: TOC<{
5765
Blocks: {
5866
editor: [];
5967
output: [];
6068
};
6169
}> = <template>
62-
<State as |state send|>
70+
<LayoutState as |state|>
6371
{{#let (containerDirection state) as |horizontallySplit|}}
6472
<Orientation as |isVertical|>
65-
{{effect (fn send "ORIENTATION" (hash isVertical=isVertical))}}
73+
{{! Normally we don't do effects in app code,
74+
because we can derive all state.
75+
76+
But XState is an *evented* system, so we have to send events.
77+
}}
78+
{{effect (fn state.send (updateOrientation isVertical))}}
6679

6780
<div
6881
{{! row = left to right, col = top to bottom }}
6982
class="{{if horizontallySplit 'flex-col' 'flex-row'}} flex overflow-hidden"
7083
>
7184

72-
<EditorContainer @splitHorizontally={{horizontallySplit}} {{setupState send}}>
85+
<EditorContainer @splitHorizontally={{horizontallySplit}} {{setupState state.send}}>
7386
<Save />
7487
<Controls
7588
@isMinimized={{state.matches "hasContainer.minimized"}}
7689
@isMaximized={{state.matches "hasContainer.maximized"}}
77-
@needsControls={{toBoolean state.context.container}}
90+
@needsControls={{toBoolean state.snapshot.context.container}}
7891
@splitHorizontally={{horizontallySplit}}
79-
@send={{send}}
92+
@send={{state.send}}
8093
/>
8194

8295
{{yield to="editor"}}
@@ -100,7 +113,7 @@ export const Layout: TOC<{
100113
</div>
101114
</Orientation>
102115
{{/let}}
103-
</State>
116+
</LayoutState>
104117
</template>;
105118

106119
export default Layout;

0 commit comments

Comments
 (0)