Skip to content

Commit 8040abc

Browse files
committed
feat: add titleUpdated event and split cloud editor features
1 parent 74d8079 commit 8040abc

File tree

12 files changed

+145
-82
lines changed

12 files changed

+145
-82
lines changed

examples/nextjs/app/cloud/page.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use client';
2+
3+
import '@composify/react/style.css';
4+
import '@/components';
5+
6+
import { CloudEditor } from '@composify/react/editor';
7+
8+
export default function Page() {
9+
return <CloudEditor />;
10+
}

examples/nextjs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.1.0",
44
"private": true,
55
"scripts": {
6-
"dev": "next dev --turbopack",
6+
"dev": "next dev --turbopack -p 4000",
77
"build": "next build",
88
"start": "next start",
99
"lint": "next lint"

packages/react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@composify/react",
3-
"version": "0.0.8",
3+
"version": "0.0.9",
44
"homepage": "https://composify.js.org",
55
"repository": {
66
"type": "git",
Lines changed: 20 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,34 @@
1-
import { useCallback, useEffect, useRef, useState, type FC } from 'react';
2-
import { Parser } from '../../renderer';
3-
import { Bridge, getClassNameFactory, GuestEventType, HostEventType } from '../../utils';
1+
import { useState, type FC } from 'react';
2+
import { CloudEditorControl } from '../CloudEditorControl';
3+
import { CloudEditorEventHandler } from '../CloudEditorEventHandler';
4+
import { CloudEditorInitializer } from '../CloudEditorInitializer';
45
import { type EditingRef } from '../EditingContext';
56
import { Editor } from '../Editor';
67
import { type VisualEditorProps } from '../VisualEditor';
7-
import styles from './CloudEditor.module.css';
8-
9-
const getClassName = getClassNameFactory('CloudEditor', styles);
108

119
type Props = VisualEditorProps;
1210

1311
export const CloudEditor: FC<Props> = ({ viewports }) => {
14-
const bridgeRef = useRef<Bridge | null>(null);
15-
1612
const [editingRef, setEditingRef] = useState<EditingRef | null>(null);
1713
const [title, setTitle] = useState('Untitled');
18-
const [initialized, setInitialized] = useState(false);
19-
20-
const handleClickSettings = useCallback(() => {
21-
if (!bridgeRef.current) {
22-
return;
23-
}
24-
25-
bridgeRef.current.emit({ type: GuestEventType.SettingsClicked });
26-
}, []);
27-
28-
const handleClickSave = useCallback(() => {
29-
if (!bridgeRef.current || !editingRef) {
30-
return;
31-
}
32-
33-
bridgeRef.current.emit({
34-
type: GuestEventType.SaveClicked,
35-
content: editingRef.getSource(),
36-
});
37-
}, [editingRef]);
38-
39-
useEffect(() => {
40-
if (!editingRef) {
41-
return;
42-
}
43-
44-
bridgeRef.current = new Bridge(window.parent);
45-
46-
const { replaceRoot, getSource } = editingRef;
47-
const { on, emit, dispose } = bridgeRef.current;
48-
49-
on(HostEventType.Initialize, data => {
50-
setTitle(data.title);
51-
replaceRoot(Parser.parse(data.content));
52-
});
53-
54-
on(HostEventType.ContentRequested, () => {
55-
emit({
56-
type: GuestEventType.ContentProvided,
57-
content: getSource(),
58-
});
59-
});
60-
61-
requestAnimationFrame(() => {
62-
if (!initialized) {
63-
emit({ type: GuestEventType.Ready });
64-
setInitialized(true);
65-
}
66-
});
67-
68-
return dispose;
69-
}, [editingRef, initialized]);
7014

7115
return (
72-
<Editor
73-
ref={setEditingRef}
74-
title={title}
75-
source="<></>"
76-
viewports={viewports}
77-
renderControl={() => (
78-
<div className={getClassName('Controls')}>
79-
<button type="button" className={getClassName('SettingsButton')} onClick={handleClickSettings}>
80-
Settings
81-
</button>
82-
<button type="button" className={getClassName('SaveButton')} onClick={handleClickSave}>
83-
Save
84-
</button>
85-
</div>
16+
<>
17+
<Editor
18+
ref={setEditingRef}
19+
title={title}
20+
source="<></>"
21+
viewports={viewports}
22+
renderControl={() => (editingRef ? <CloudEditorControl getSource={editingRef.getSource} /> : null)}
23+
/>
24+
<CloudEditorInitializer />
25+
{editingRef && (
26+
<CloudEditorEventHandler
27+
setTitle={setTitle}
28+
replaceRoot={editingRef.replaceRoot}
29+
getSource={editingRef.getSource}
30+
/>
8631
)}
87-
/>
32+
</>
8833
);
8934
};

packages/react/src/editor/CloudEditor/CloudEditor.module.css renamed to packages/react/src/editor/CloudEditorControl/CloudEditorControl.module.css

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
.CloudEditor-Controls {
1+
.CloudEditorControl {
22
display: flex;
33
gap: 0.375rem;
44
}
55

6-
.CloudEditor-SettingsButton {
6+
.CloudEditorControl-SettingsButton {
77
padding: 0.4375rem 0.75rem;
88
font-size: 0.75rem;
99
font-weight: 500;
@@ -15,12 +15,12 @@
1515
border: 1px solid var(--composify-palette-outline);
1616
}
1717

18-
.CloudEditor-SettingsButton:hover {
18+
.CloudEditorControl-SettingsButton:hover {
1919
background-color: var(--composify-palette-surface-container);
2020
transition: background-color 0.15s linear;
2121
}
2222

23-
.CloudEditor-SaveButton {
23+
.CloudEditorControl-SaveButton {
2424
padding: 0.4375rem 0.75rem;
2525
font-size: 0.75rem;
2626
font-weight: 500;
@@ -32,7 +32,7 @@
3232
border: 1px solid var(--composify-palette-primary);
3333
}
3434

35-
.CloudEditor-SaveButton:hover {
35+
.CloudEditorControl-SaveButton:hover {
3636
opacity: 0.9;
3737
transition: opacity 0.15s linear;
3838
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { type FC, useCallback, useEffect, useRef } from 'react';
2+
import { Bridge, getClassNameFactory, GuestEventType } from '../../utils';
3+
import styles from './CloudEditorControl.module.css';
4+
5+
const getClassName = getClassNameFactory('CloudEditorControl', styles);
6+
7+
type Props = {
8+
getSource: () => string;
9+
};
10+
11+
export const CloudEditorControl: FC<Props> = ({ getSource }) => {
12+
const bridgeRef = useRef<Bridge | null>(null);
13+
14+
const handleClickSettings = useCallback(() => {
15+
bridgeRef.current?.emit({ type: GuestEventType.SettingsClicked });
16+
}, []);
17+
18+
const handleClickSave = useCallback(() => {
19+
bridgeRef.current?.emit({
20+
type: GuestEventType.SaveClicked,
21+
content: getSource(),
22+
});
23+
}, [getSource]);
24+
25+
useEffect(() => {
26+
bridgeRef.current = new Bridge(window.parent);
27+
28+
return bridgeRef.current.dispose;
29+
}, []);
30+
31+
return (
32+
<div className={getClassName()}>
33+
<button type="button" className={getClassName('SettingsButton')} onClick={handleClickSettings}>
34+
Settings
35+
</button>
36+
<button type="button" className={getClassName('SaveButton')} onClick={handleClickSave}>
37+
Save
38+
</button>
39+
</div>
40+
);
41+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { CloudEditorControl } from './CloudEditorControl';
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { type FC, useEffect } from 'react';
2+
import { Parser, type Node } from '../../renderer';
3+
import { Bridge, GuestEventType, HostEventType } from '../../utils';
4+
5+
type Props = {
6+
setTitle: (title: string) => void;
7+
replaceRoot: (root: Node) => void;
8+
getSource: () => string;
9+
};
10+
11+
export const CloudEditorEventHandler: FC<Props> = ({ setTitle, replaceRoot, getSource }) => {
12+
useEffect(() => {
13+
const bridge = new Bridge(window.parent);
14+
15+
bridge.on(HostEventType.Initialize, data => {
16+
setTitle(data.title);
17+
replaceRoot(Parser.parse(data.content));
18+
});
19+
20+
bridge.on(HostEventType.TitleUpdated, data => {
21+
setTitle(data.title);
22+
});
23+
24+
bridge.on(HostEventType.ContentRequested, () => {
25+
bridge.emit({
26+
type: GuestEventType.ContentProvided,
27+
content: getSource(),
28+
});
29+
});
30+
31+
return bridge.dispose;
32+
}, [setTitle, replaceRoot, getSource]);
33+
34+
return null;
35+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { CloudEditorEventHandler } from './CloudEditorEventHandler';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { type FC, useEffect, useState } from 'react';
2+
import { Bridge, GuestEventType } from '../../utils';
3+
4+
export const CloudEditorInitializer: FC = () => {
5+
const [initialized, setInitialized] = useState(false);
6+
7+
useEffect(() => {
8+
const bridge = new Bridge(window.parent);
9+
10+
requestAnimationFrame(() => {
11+
if (initialized) {
12+
return;
13+
}
14+
15+
bridge.emit({ type: GuestEventType.Ready });
16+
setInitialized(true);
17+
});
18+
19+
return bridge.dispose;
20+
}, [initialized]);
21+
22+
return null;
23+
};

0 commit comments

Comments
 (0)