Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions examples/src/app/components/Example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ErrorBoundary } from './ErrorBoundary.mjs';
import { SelectInput as OverlaySelectInput } from './OverlaySelectInput.mjs';
import { COLOR_NAMES, INLINE_MD_PATTERN, SAFE_URL_PATTERN } from '../../../utils/inline-markdown.mjs';
import { CLOSE_SELECTS_EVENT } from '../constants.mjs';
import { setExampleSnapshotProvider } from '../example-snapshot.mjs';
import { iframe } from '../iframe.mjs';
import { jsx, fragment } from '../jsx.mjs';
import { iframePath } from '../paths.mjs';
Expand Down Expand Up @@ -496,8 +497,28 @@ class Example extends TypedComponent {
controlPanelHeader.onclick = () => this.toggleCollapse();
}

/**
* Captures the example's current control overrides as a flat dot-path map — the same state the
* share feature persists — so the exported project can replay it after the example loads.
*
* @returns {Record<string, any>} The flattened control state.
*/
_captureControls() {
/** @type {Record<string, any>} */
const flat = {};
flattenLeaves(readState().controls ?? {}, '', flat);
return flat;
}

componentDidMount() {
this.setupControlPanel();
setExampleSnapshotProvider(() => ({
files: this.state.files,
category: this.props.match.params.category,
example: this.props.match.params.example,
data: this._captureControls(),
credits: this.state.credits
}));
window.addEventListener('resize', this._onLayoutChange);
window.addEventListener('requestedFiles', this._handleRequestedFiles);
window.addEventListener('orientationchange', this._onLayoutChange);
Expand All @@ -524,6 +545,7 @@ class Example extends TypedComponent {
}

componentWillUnmount() {
setExampleSnapshotProvider(null);
window.dispatchEvent(new Event(CLOSE_SELECTS_EVENT));
this.bindObserver(null);
this._controlPanelScrollRegion?.removeEventListener('scroll', this._handleControlPanelScroll);
Expand Down
1 change: 1 addition & 0 deletions examples/src/app/components/code-editor/CodeEditorBase.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ function getShowMinimap() {
* @property {Record<string, string>} files - The example files.
* @property {string} selectedFile - The selected file.
* @property {boolean} showMinimap - The state of showing the Minimap
* @property {boolean} [downloading] - True while a standalone Vite project is being built.
*/

/** @type {typeof Component<Props, State>} */
Expand Down
50 changes: 49 additions & 1 deletion examples/src/app/components/code-editor/CodeEditorDesktop.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import MonacoEditor, { loader } from '@monaco-editor/react';
import { Button, Container, Panel } from '@playcanvas/pcui/react';

import { CodeEditorBase } from './CodeEditorBase.mjs';
import { downloadExampleProject } from '../../download-project.mjs';
import { iframe } from '../../iframe.mjs';
import { jsx } from '../../jsx.mjs';
import { scriptsPath } from '../../paths.mjs';
Expand All @@ -18,6 +19,18 @@ import { getHashPath, getSelectedFile, patchState, readState } from '../../url-s

loader.config({ paths: { vs: './modules/monaco-editor/min/vs' } });

/**
* @param {() => Promise<any>} task - Async task.
* @returns {Promise<[any, any]>} Error and result tuple.
*/
const tryCatchAsync = async (task) => {
try {
return [null, await task()];
} catch (err) {
return [err, null];
}
};

function getShowMinimap() {
let showMinimap = true;
if (localStorage.getItem('showMinimap')) {
Expand Down Expand Up @@ -98,6 +111,19 @@ class CodeEditorDesktop extends CodeEditorBase {
this._handleExampleHotReload = this._handleExampleHotReload.bind(this);
this._handleExampleError = this._handleExampleError.bind(this);
this._handleRequestedFiles = this._handleRequestedFiles.bind(this);
this._onDownload = this._onDownload.bind(this);
}

async _onDownload() {
if (this.state.downloading) {
return;
}
this.setState({ downloading: true });
const [err] = await tryCatchAsync(downloadExampleProject);
if (err) {
console.error('Failed to download Vite project', err);
}
this.setState({ downloading: false });
}

/**
Expand Down Expand Up @@ -535,7 +561,29 @@ class CodeEditorDesktop extends CodeEditorBase {
`https://github.com/playcanvas/engine/blob/main/examples/src/examples/${examplePath}.example.mjs`
);
}
})
}),
jsx('button', {
type: 'button',
className: 'pcui-button code-editor-download',
'aria-label': 'Download as Vite project',
disabled: this.state.downloading,
onClick: this._onDownload
}, jsx('svg', {
viewBox: '0 0 24 24',
fill: 'none',
stroke: 'currentColor',
strokeWidth: 2,
strokeLinecap: 'round',
strokeLinejoin: 'round',
width: 14,
height: 14
},
jsx('path', { d: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4' }),
jsx('polyline', { points: '7 10 12 15 17 10' }),
jsx('line', { x1: 12, y1: 15, x2: 12, y2: 3 })
), jsx('span', {
className: 'code-editor-download-label'
}, this.state.downloading ? 'Preparing…' : 'Download'))
),
jsx(
Container,
Expand Down
Loading