Compare commits
2 Commits
4e4b0e428d
...
3ea6aa8fb3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ea6aa8fb3 | ||
|
|
b252a80a8e |
@ -563,6 +563,7 @@ export function EditorApp() {
|
|||||||
|
|
||||||
<Inspector
|
<Inspector
|
||||||
selected={selected}
|
selected={selected}
|
||||||
|
onUpdateRect={(id, patch) => dispatch({ type: 'updateWidgetRect', id, rect: patch })}
|
||||||
onUpdateTextProps={(id, props) =>
|
onUpdateTextProps={(id, props) =>
|
||||||
dispatch({ type: 'updateWidgetProps', widgetType: 'text', id, props })
|
dispatch({ type: 'updateWidgetProps', widgetType: 'text', id, props })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Input, InputNumber, Select, Space, Typography } from 'antd';
|
import { Divider, Input, InputNumber, Select, Space, Typography } from 'antd';
|
||||||
import { assertNever, type WidgetNode, type TextWidgetNode, type WidgetNodeByType } from '@astralview/sdk';
|
import { assertNever, type WidgetNode, type TextWidgetNode, type WidgetNodeByType } from '@astralview/sdk';
|
||||||
|
|
||||||
type ImageWidgetNode = WidgetNodeByType['image'];
|
type ImageWidgetNode = WidgetNodeByType['image'];
|
||||||
@ -7,6 +7,7 @@ type VideoWidgetNode = WidgetNodeByType['video'];
|
|||||||
|
|
||||||
export function Inspector(props: {
|
export function Inspector(props: {
|
||||||
selected?: WidgetNode;
|
selected?: WidgetNode;
|
||||||
|
onUpdateRect: (id: string, patch: Partial<WidgetNode['rect']>) => void;
|
||||||
onUpdateTextProps: (id: string, patch: Partial<TextWidgetNode['props']>) => void;
|
onUpdateTextProps: (id: string, patch: Partial<TextWidgetNode['props']>) => void;
|
||||||
onUpdateImageProps: (id: string, patch: Partial<ImageWidgetNode['props']>) => void;
|
onUpdateImageProps: (id: string, patch: Partial<ImageWidgetNode['props']>) => void;
|
||||||
onUpdateIframeProps: (id: string, patch: Partial<IframeWidgetNode['props']>) => void;
|
onUpdateIframeProps: (id: string, patch: Partial<IframeWidgetNode['props']>) => void;
|
||||||
@ -18,6 +19,60 @@ export function Inspector(props: {
|
|||||||
return <Typography.Paragraph style={{ color: '#666' }}>No selection.</Typography.Paragraph>;
|
return <Typography.Paragraph style={{ color: '#666' }}>No selection.</Typography.Paragraph>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rectDisabled = !!node.locked;
|
||||||
|
|
||||||
|
const RectSection = (
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Typography.Text type="secondary">Rect</Typography.Text>
|
||||||
|
<Space style={{ width: '100%', marginTop: 6 }} size={8} wrap>
|
||||||
|
<InputNumber
|
||||||
|
size="small"
|
||||||
|
value={node.rect.x}
|
||||||
|
controls={false}
|
||||||
|
disabled={rectDisabled}
|
||||||
|
onChange={(v) => props.onUpdateRect(node.id, { x: typeof v === 'number' ? v : node.rect.x })}
|
||||||
|
style={{ width: 90 }}
|
||||||
|
addonBefore="X"
|
||||||
|
/>
|
||||||
|
<InputNumber
|
||||||
|
size="small"
|
||||||
|
value={node.rect.y}
|
||||||
|
controls={false}
|
||||||
|
disabled={rectDisabled}
|
||||||
|
onChange={(v) => props.onUpdateRect(node.id, { y: typeof v === 'number' ? v : node.rect.y })}
|
||||||
|
style={{ width: 90 }}
|
||||||
|
addonBefore="Y"
|
||||||
|
/>
|
||||||
|
<InputNumber
|
||||||
|
size="small"
|
||||||
|
value={node.rect.w}
|
||||||
|
controls={false}
|
||||||
|
disabled={rectDisabled}
|
||||||
|
min={1}
|
||||||
|
onChange={(v) => props.onUpdateRect(node.id, { w: typeof v === 'number' ? v : node.rect.w })}
|
||||||
|
style={{ width: 96 }}
|
||||||
|
addonBefore="W"
|
||||||
|
/>
|
||||||
|
<InputNumber
|
||||||
|
size="small"
|
||||||
|
value={node.rect.h}
|
||||||
|
controls={false}
|
||||||
|
disabled={rectDisabled}
|
||||||
|
min={1}
|
||||||
|
onChange={(v) => props.onUpdateRect(node.id, { h: typeof v === 'number' ? v : node.rect.h })}
|
||||||
|
style={{ width: 96 }}
|
||||||
|
addonBefore="H"
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
{rectDisabled ? (
|
||||||
|
<Typography.Text style={{ display: 'block', marginTop: 4, color: '#64748b', fontSize: 12 }}>
|
||||||
|
Locked layer: rect editing disabled.
|
||||||
|
</Typography.Text>
|
||||||
|
) : null}
|
||||||
|
<Divider style={{ margin: '12px 0', borderColor: 'rgba(255,255,255,0.08)' }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'image':
|
case 'image':
|
||||||
return (
|
return (
|
||||||
@ -26,6 +81,8 @@ export function Inspector(props: {
|
|||||||
Image
|
Image
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
|
|
||||||
|
{RectSection}
|
||||||
|
|
||||||
<Typography.Text type="secondary">Source</Typography.Text>
|
<Typography.Text type="secondary">Source</Typography.Text>
|
||||||
<Input
|
<Input
|
||||||
value={node.props.src}
|
value={node.props.src}
|
||||||
@ -65,6 +122,8 @@ export function Inspector(props: {
|
|||||||
Iframe
|
Iframe
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
|
|
||||||
|
{RectSection}
|
||||||
|
|
||||||
<Typography.Text type="secondary">Source</Typography.Text>
|
<Typography.Text type="secondary">Source</Typography.Text>
|
||||||
<Input
|
<Input
|
||||||
value={node.props.src}
|
value={node.props.src}
|
||||||
@ -90,6 +149,8 @@ export function Inspector(props: {
|
|||||||
Video
|
Video
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
|
|
||||||
|
{RectSection}
|
||||||
|
|
||||||
<Typography.Text type="secondary">Source</Typography.Text>
|
<Typography.Text type="secondary">Source</Typography.Text>
|
||||||
<Input
|
<Input
|
||||||
value={node.props.src}
|
value={node.props.src}
|
||||||
@ -173,6 +234,8 @@ export function Inspector(props: {
|
|||||||
Text
|
Text
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
|
|
||||||
|
{RectSection}
|
||||||
|
|
||||||
<Typography.Text type="secondary">Content</Typography.Text>
|
<Typography.Text type="secondary">Content</Typography.Text>
|
||||||
<Input
|
<Input
|
||||||
value={node.props.text}
|
value={node.props.text}
|
||||||
|
|||||||
@ -21,15 +21,16 @@ export function bindEditorHotkeys(getShift: () => boolean, dispatch: (a: EditorA
|
|||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
const ctrl = e.ctrlKey || e.metaKey;
|
const ctrl = e.ctrlKey || e.metaKey;
|
||||||
|
|
||||||
|
// Do not steal common hotkeys while typing in an input/editor.
|
||||||
|
// (Including Escape: inputs often use it to revert/blur.)
|
||||||
|
if (isEditableTarget(e.target)) return;
|
||||||
|
|
||||||
// Esc: clear selection (and closes context menu via selection parity effect).
|
// Esc: clear selection (and closes context menu via selection parity effect).
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
dispatch({ type: 'selectSingle', id: undefined });
|
dispatch({ type: 'selectSingle', id: undefined });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not steal common hotkeys while typing in an input/editor.
|
|
||||||
if (isEditableTarget(e.target)) return;
|
|
||||||
|
|
||||||
// Undo/redo
|
// Undo/redo
|
||||||
if (ctrl && e.key.toLowerCase() === 'z') {
|
if (ctrl && e.key.toLowerCase() === 'z') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user