Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auto-complete to the P5 widget #81

Draft
wants to merge 48 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
bb1743a
slowly upgrade
citelao Dec 28, 2021
8f6144f
imports now compile
citelao Dec 28, 2021
971658e
messing with stuff
citelao Dec 28, 2021
bfb627d
autocomplete
citelao Dec 28, 2021
88f2113
start upgrading webpack etc
citelao Dec 28, 2021
096d7b5
switch to newer tests
citelao Dec 28, 2021
69ec1d6
begin consuming monaco
citelao Dec 28, 2021
8ca7bcf
move stuff and fix query params
citelao Dec 28, 2021
4ea0156
My friends, we have Monaco
citelao Dec 28, 2021
eaf3c0f
js autocomplete mfs
citelao Dec 28, 2021
c548132
import d.ts
citelao Dec 28, 2021
0a604ec
still works!
citelao Dec 28, 2021
97688fb
fix inline highlight
citelao Dec 28, 2021
9cf4159
remove some codemirror refs
citelao Dec 28, 2021
1fe749f
begin theming
citelao Dec 28, 2021
663ddb6
start highlighting string
citelao Dec 28, 2021
3a92f80
start colors
citelao Dec 29, 2021
d58cdf1
remove old dts
citelao Dec 29, 2021
845a435
better theme
citelao Dec 29, 2021
dfb8fa4
trying to do better layout
citelao Dec 29, 2021
ef0b191
gutter bg and remove guides
citelao Dec 29, 2021
a380321
lots more js code complete stuff
citelao Dec 29, 2021
25cdc1e
undo/redo buttons appear
citelao Dec 29, 2021
b30ca13
undo and redo buttons work!
citelao Dec 29, 2021
7cf81df
legit check undefined
citelao Dec 29, 2021
32982d4
ed
citelao Jan 16, 2022
7ad4c0f
fix svg logo
citelao Jan 16, 2022
9bcbbed
port some stuff
citelao Jan 16, 2022
cf33f3d
and that
citelao Jan 16, 2022
19216be
that's the best I can do I think
citelao Jan 16, 2022
4501bd4
note to self
citelao Jan 16, 2022
01d5093
remove codemirror dep
citelao Jan 16, 2022
040733b
fix overscrolling
citelao Jan 16, 2022
e3aca97
tentative changes to update script
citelao Jan 16, 2022
d95d9f3
add non-working full-screen
citelao Jan 16, 2022
037016d
working full-screen page
citelao Jan 16, 2022
7c95d3d
style closely to UI buttons
citelao Jan 16, 2022
24536ca
comment about not fullscreen
citelao Jan 16, 2022
ab22445
fix production builds
citelao Jan 16, 2022
4f6bc01
only ship typescript & js
citelao Jan 16, 2022
eb8d314
size note
citelao Jan 16, 2022
13eaab7
create a basic JS version of the update script
citelao Jan 16, 2022
0b5310f
rebuild
citelao Jan 16, 2022
040b73d
publish to gh-pages
citelao Jan 16, 2022
96b4fd8
add ghpages
citelao Jan 16, 2022
f16e979
make it a devdep
citelao Jan 16, 2022
3328ad4
npm run publish
citelao Jan 16, 2022
c1d1024
you can also specify a percentage
citelao Jan 16, 2022
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
8 changes: 5 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
node_modules
node_modules/
website/
dist/

*.bundle.js
*.bundle.js.map
p5-widget.js
p5-widget.js.map
website
p5-widget.js.map
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ of plugins:
The test suite can be run on the development server at
http://localhost:8080/test/, or on the command-line with `npm test`.

## Publish to GitHub Pages

Run `npm run publish` to build & publish a site.

[website]: https://toolness.github.io/p5.js-widget/
[TypeScript]: http://typescriptlang.org/
[React]: http://facebook.github.io/react/
Expand Down
43 changes: 23 additions & 20 deletions css/p5-widget-codemirror-theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,35 @@
--dark-blueish: #00A1D3;
}

.cm-s-p5-widget span { color: var(--dark-gray); }
/* Commented out if ported */

.cm-s-p5-widget span.cm-meta { color: var(--dark-gray); }
.cm-s-p5-widget span.cm-keyword { line-height: 1em; color: var(--dark-brown); }
.cm-s-p5-widget span.cm-atom { color: var(--pinkish); }
.cm-s-p5-widget span.cm-number { color: var(--pinkish); }
.cm-s-p5-widget span.cm-def { color: var(--dark-blueish); }
.cm-s-p5-widget span.cm-variable { color: var(--dark-blueish); }
.cm-s-p5-widget span.cm-variable-2 { color: var(--almost-black); }
.cm-s-p5-widget span.cm-variable-3 { color: var(--almost-black); }
.cm-s-p5-widget span.cm-property { color: var(--almost-black); }
.cm-s-p5-widget span.cm-operator { color: var(--light-brown); }
.cm-s-p5-widget span.cm-comment { color: var(--light-gray); }
.cm-s-p5-widget span.cm-string { color: var(--dark-blueish); }
.cm-s-p5-widget span.cm-string-2 { color: var(--dark-blueish); }
/* .cm-s-p5-widget span { color: var(--dark-gray); } */

.cm-s-p5-widget span.cm-error { color: #f00; }
/* .cm-s-p5-widget span.cm-meta { color: var(--dark-gray); } */
/* .cm-s-p5-widget span.cm-keyword { line-height: 1em; color: var(--dark-brown); } */
.cm-s-p5-widget span.cm-atom { color: var(--pinkish); } /* Unsure what this is */
/* .cm-s-p5-widget span.cm-number { color: var(--pinkish); } */
/* .cm-s-p5-widget span.cm-def { color: var(--dark-blueish); }
.cm-s-p5-widget span.cm-variable { color: var(--dark-blueish); } */
.cm-s-p5-widget span.cm-variable-2 { color: var(--almost-black); } /* Unsure what this is */
.cm-s-p5-widget span.cm-variable-3 { color: var(--almost-black); } /* Unsure what this is */
/* .cm-s-p5-widget span.cm-property { color: var(--almost-black); } */ /* Not supported */
/* .cm-s-p5-widget span.cm-operator { color: var(--light-brown); } */ /* not supported */
/* .cm-s-p5-widget span.cm-comment { color: var(--light-gray); } */
/* .cm-s-p5-widget span.cm-string { color: var(--dark-blueish); }
.cm-s-p5-widget span.cm-string-2 { color: var(--dark-blueish); } */

.cm-s-p5-widget .CodeMirror-activeline-background { background: #e8f2ff; }
.cm-s-p5-widget .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }
/* .cm-s-p5-widget span.cm-error { color: #f00; } */

/* These styles don't seem to be set by CodeMirror's javascript mode. */
/* Adjusted slightly. */
/* .cm-s-p5-widget .CodeMirror-activeline-background { background: #e8f2ff; }
.cm-s-p5-widget .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } */

.cm-s-p5-widget span.cm-qualifier { color: #555; }
/* These styles don't seem to be set by CodeMirror's javascript mode. */
/* OK, won't port */
/* .cm-s-p5-widget span.cm-qualifier { color: #555; }
.cm-s-p5-widget span.cm-builtin { color: #30a; }
.cm-s-p5-widget span.cm-bracket { color: #cc7; }
.cm-s-p5-widget span.cm-tag { color: #170; }
.cm-s-p5-widget span.cm-attribute { color: #00c; }
.cm-s-p5-widget span.cm-link { color: #219; }
.cm-s-p5-widget span.cm-link { color: #219; } */
5 changes: 0 additions & 5 deletions css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,6 @@ a.p5-logo img {
padding-right: 1em;
}

.CodeMirror-gutters {
border-right: none;
background: var(--very-light-gray);
}

.error-line {
background: var(--light-pink);
}
129 changes: 95 additions & 34 deletions lib/editor.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import React = require("react");

import CodeMirror = require("codemirror");

import "codemirror/mode/javascript/javascript.js";
import * as Monaco from 'monaco-editor';

import PureComponent from "./pure-component";
import MonacoTheme from "./monaco-theme";
import UndoRedoHelper from "./undo-redo-helper";

// TODO: versions
const p5dts = require("!!raw-loader!@types/p5/global.d.ts") as string;
const p5Uri = "ts:p5.d.ts";
// const p5domdts = require("raw-loader!@types/p5/lib/addons/p5.dom.d.ts") as string;
// const p5sounddts = require("raw-loader!@types/p5/lib/addons/p5.sound.d.ts") as string;

// It seems like CodeMirror behaves oddly with a flexbox layout, so
// we will manually size it. However, Chrome seems to have a bug
Expand All @@ -22,43 +27,93 @@ interface State {
}

export default class Editor extends PureComponent<Props, State> {
_cm: CodeMirror.Editor
_ed: Monaco.editor.IStandaloneCodeEditor
_resizeTimeout: number
_errorLineHandle: any

componentDidUpdate(prevProps: Props) {
if (this.props.content !== prevProps.content &&
this.props.content !== this._cm.getValue()) {
this._cm.setValue(this.props.content);
this.props.content !== this._ed.getValue()) {
this._ed.setValue(this.props.content);
}
if (this.props.errorLine !== prevProps.errorLine) {
if (this._errorLineHandle) {
this._cm.removeLineClass(this._errorLineHandle, 'background',
'error-line');
this._ed.deltaDecorations(this._errorLineHandle, []);
this._errorLineHandle = null;
}
if (this.props.errorLine) {
this._errorLineHandle = this._cm.addLineClass(
this.props.errorLine - 1,
'background',
'error-line'
);
this._errorLineHandle = this._ed.deltaDecorations([], [
{
range: {
startColumn: 0,
startLineNumber: this.props.errorLine,
endColumn: 0,
endLineNumber: this.props.errorLine,
},
options: {
className: 'error-line',
isWholeLine: true,
}
}
]);
}
}
}

componentDidMount() {
this._cm = CodeMirror(this.refs.container, {
theme: 'p5-widget',
Monaco.editor.defineTheme("p5-widget", MonacoTheme);
this._ed = Monaco.editor.create(this.refs.container, {
value: this.props.content,
lineNumbers: true,
mode: 'javascript'
language: "javascript",

// Ensure we don't have scrolling by default.
scrollBeyondLastLine: false,

// Style:
theme: 'p5-widget',
fontSize: 16,
fontFamily: '"Monaco", "Menlo", "Ubuntu Mono", "Consolas", "source-code-pro", monospace',
lineNumbersMinChars: 2,
lineNumbers: "on",
folding: false,
minimap: {
enabled: false,
},
guides: {
indentation: false,
},

// Unclear what we need to do here.
automaticLayout: true,
fixedOverflowWidgets: true,
});
this._cm.on('change', () => {

Monaco.languages.typescript.javascriptDefaults.addExtraLib(p5dts, p5Uri);
// When resolving definitions and references, the editor will try to use created models.
// Creating a model for the library allows "peek definition/references" commands to work with the library.
Monaco.editor.createModel(p5dts, 'typescript', Monaco.Uri.parse(p5Uri));

// This could adapt to the browser---detect what features are supported and
// add those libraries to the `lib`s, beyond just ES2015, which
// https://www.typescriptlang.org/tsconfig#target recommends for browsers in
// general.
//
// We get errors like `Unhandled Promise Rejection: Error: Could not find
// source file: 'inmemory://model/1'.` if we don't Object.assign here.
const currentOptions = Monaco.languages.typescript.javascriptDefaults.getCompilerOptions();
Monaco.languages.typescript.javascriptDefaults.setCompilerOptions(Object.assign({
lib: ["dom", "es2015"],
module: Monaco.languages.typescript.ModuleKind.None,
}, currentOptions));

this._ed.onDidChangeModelContent(() => {
if (this.props.onChange) {
let size = this._cm.getDoc().historySize();
this.props.onChange(this._cm.getValue(),
size.undo > 0, size.redo > 0);
// TODO: extract to helper?
const helper = new UndoRedoHelper(this._ed);
this.props.onChange(
this._ed.getValue(),
helper.canUndo(),
helper.canRedo());
}
});
this.resizeEditor();
Expand All @@ -70,33 +125,39 @@ export default class Editor extends PureComponent<Props, State> {
componentWillUnmount() {
// CodeMirror instances have no remove/destroy methods, so we
// don't need to do anything: http://stackoverflow.com/a/18890324/2422398
this._cm = null;
this._ed = null;
clearTimeout(this._resizeTimeout);
window.removeEventListener('resize', this.resizeEditor, false);
}

undo() {
this._cm.getDoc().undo();
const helper = new UndoRedoHelper(this._ed);
helper.undo();
}

redo() {
this._cm.getDoc().redo();
const helper = new UndoRedoHelper(this._ed);
helper.redo();
}

resizeEditor = () => {
let wrapper = this._cm.getWrapperElement();
let oldDisplay = wrapper.style.display;
// TODO: we can use auto-layout, but it looks like the context menu gets
// clipped for small iframes.

// let wrapper = this._cm.getContainerDomNode();
// let oldDisplay = wrapper.style.display;

// We need to get the size of our container when it's
// "uncorrupted" by the height of our codemirror widget, so
// temporarily hide the widget.
wrapper.style.display = 'none';
// // We need to get the size of our container when it's
// // "uncorrupted" by the height of our codemirror widget, so
// // temporarily hide the widget.
// wrapper.style.display = 'none';

let rectHeight = this.refs.container.getBoundingClientRect().height;
// let rectHeight = this.refs.container.getBoundingClientRect().height;
// console.log(rectHeight);

wrapper.style.display = oldDisplay;
// wrapper.style.display = oldDisplay;

this._cm.setSize(null, rectHeight);
// this._cm.layout();
}

// http://stackoverflow.com/a/33826399/2422398
Expand Down
22 changes: 9 additions & 13 deletions lib/main.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
import React = require("react");
import ReactDOM = require("react-dom");

import url = require("url");

import * as defaults from "./defaults";
import { SessionStorageAutosaver } from "./autosaver";
import App from "./app";

let defaultSketchJS = require("raw!./default-sketch.js") as string;
let defaultSketchJS = require("raw-loader!./default-sketch.js") as string;

require("../node_modules/codemirror/lib/codemirror.css");
require("../css/style.css");
require("../css/p5-widget-codemirror-theme.css");

function start() {
let embeddingPageURL = document.referrer;
let qs = url.parse(window.location.search, true).query;
let id = embeddingPageURL + '_' + qs['id'];
let baseSketchURL = qs['baseSketchURL'] || embeddingPageURL;
let autoplay = (qs['autoplay'] === 'on');
let initialContent = qs['sketch'] || defaultSketchJS;
let p5version = qs['p5version'] || defaults.P5_VERSION;
let previewWidth = parseInt(qs['previewWidth']);
let maxRunTime = parseInt(qs['maxRunTime'])
let qs = new URLSearchParams(window.location.search);
let id = embeddingPageURL + '_' + qs.get('id');
let baseSketchURL = qs.get('baseSketchURL') || embeddingPageURL;
let autoplay = (qs.get('autoplay') === 'on');
let initialContent = qs.get('sketch') || defaultSketchJS;
let p5version = qs.get('p5version') || defaults.P5_VERSION;
let previewWidth = parseInt(qs.get('previewWidth'));
let maxRunTime = parseInt(qs.get('maxRunTime'))
if (isNaN(previewWidth)) {
previewWidth = defaults.PREVIEW_WIDTH;
}
Expand Down
47 changes: 47 additions & 0 deletions lib/monaco-theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as Monaco from 'monaco-editor';

const colors = {
"very-light-gray": "#f0f0f0",
"light-gray": "#A0A0A0",
"dark-gray": "#666666",
"almost-black": "#222222",
"dark-brown": "#704F21",
"light-brown": "#a67f59",
"pinkish": "#DC3787", /* not p5 pink, but related */
"dark-blueish": "#00A1D3",
"white": "#ffffff"
}

// See the official themes for inspiration, but note that they use a different tokenizer:
//
// https://github.com/Microsoft/vscode/blob/main/src/vs/editor/standalone/common/themes.ts#L13
const MonacoTheme: Monaco.editor.IStandaloneThemeData = {
base: 'vs',
inherit: true,
rules: [
{ token: "meta", foreground: colors["dark-gray"] },
{ token: "storage", foreground: colors["dark-brown"] },
{ token: "keyword", foreground: colors["dark-brown"] },
{ token: "number", foreground: colors["pinkish"] },
{ token: "identifier", foreground: colors["dark-blueish"] },
{ token: "function", foreground: colors["dark-blueish"] },
{ token: "delimiter", foreground: colors["dark-gray"] },
{ token: "operator", foreground: colors["light-brown"] },
{ token: "comment", foreground: colors["light-gray"] },
{ token: "string", foreground: colors["dark-blueish"] }
],
colors: {
"editor.foreground": colors["dark-gray"],
"editorLineNumber.foreground": colors['light-gray'],

// Took me a long time to find:
// https://stackoverflow.com/questions/65659354/what-is-the-name-of-configuration-to-change-the-background-of-line-number-vscode
"editorGutter.background": colors['very-light-gray'],

"editorBracketMatch.background": colors['white'],
"editorBracketMatch.border": colors['dark-gray'],

"errorForeground": "#ff0000",
}
};
export default MonacoTheme;
13 changes: 11 additions & 2 deletions lib/p5-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,12 @@ function isElementInViewport(el: HTMLElement) {
}

function getDataHeight(el: HTMLScriptElement) {
let height = parseInt(el.getAttribute('data-height'));
const dataHeight = el.getAttribute('data-height') || "";
if (dataHeight.indexOf("%") !== -1) {
return dataHeight;
}

let height = parseInt(dataHeight);
if (isNaN(height)) height = defaults.HEIGHT;

return height;
Expand Down Expand Up @@ -110,7 +114,12 @@ function replaceScriptWithWidget(el: HTMLScriptElement) {

function makeWidget(sketch: string) {
qsArgs.push('sketch=' + encodeURIComponent(sketch));
style.push('min-height: ' + height + 'px');

if (typeof height === "number") {
style.push('min-height: ' + height + 'px');
} else {
style.push('height: ' + height);
}
url = myBaseURL + IFRAME_FILENAME + '?' + qsArgs.join('&');
iframe.setAttribute('src', url);
iframe.setAttribute('style', style.join('; '));
Expand Down
2 changes: 1 addition & 1 deletion lib/preview-frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface PreviewFrameWindow extends PreviewFrame.Runner {
p5: (sketch?: Function, node?: HTMLElement, sync?: boolean) => void;
}

let global = window as PreviewFrameWindow;
let global = window as unknown as PreviewFrameWindow;

function loadScript(url, cb?: () => void) {
let script = document.createElement('script');
Expand Down
Loading