-
Notifications
You must be signed in to change notification settings - Fork 31
/
editor.js
172 lines (147 loc) · 4.12 KB
/
editor.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import { runScene, clearScene } from './slang';
import context from './helpers/context';
import CodeMirror from 'codemirror';
import * as simpleMode from 'codemirror/addon/mode/simple';
import js from 'codemirror/mode/clojure/clojure';
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/duotone-light.css';
import classMap from './classes/classMap';
import { functionMap } from './functions';
// ------------------------------ EDITOR ------------------------------
const keywords = Object.keys(classMap).concat(Object.keys(functionMap), ['notes', 'rhythm']);
const keywordRegex = new RegExp(`(?:${keywords.join('|')})\\b`);
CodeMirror.defineSimpleMode("slang", {
start: [
{
regex: keywordRegex,
token: "keyword"
},
{
regex: /[a-g](\#|b)?\d+/i,
token: "note"
},
{
regex: /\d+(n|t)/i,
token: "beat"
},
{
regex: /r\d+(n|t)/i,
token: "rest"
},
{
regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i,
token: "number"
},
{
regex: /(\+|\~)/,
token: "pipe"
},
{
regex: /\#.+/,
token: "comment"
},
{
regex: /\@[a-z$][\w$]*/,
token: "variable"
},
],
});
const existingCode = window.localStorage.getItem('code');
const defaultCode = `# Welcome to Slang! Here's an example to get you started.
# Click the Run button above to start playing this code.
# Make a sound called @synth with a triangle wave
@synth (adsr (osc tri) 64n 8n 0.5 8n)
# Play the @synth sound
play @synth
# play a quarter note and then an eighth note
(rhythm [4n 8n])
# play a C major scale
(notes [c3 d3 e3 f3 g3 a3 b3 c4])
`;
const editor = CodeMirror(document.querySelector('#editor'), {
value: window.slangPatch || existingCode || defaultCode,
mode: 'slang',
theme: 'duotone-light',
indentWithTabs: true,
});
function run() {
context.resume();
const value = editor.getValue();
// Any error generated by running the scene should
// be caught here (but not runtime errors like
// a function not existing).
try {
runScene(value);
clearError();
status('Running');
} catch (e) {
console.error(e);
displayError(e);
status('Error');
}
// save the scene to localStorage
window.localStorage.setItem('code', value);
}
function stop() {
clearScene();
status('Stopped');
}
editor.on('keydown', (c, e) => {
if (e.key === 'Enter' && e.metaKey && e.shiftKey) {
stop();
} else if (e.key === 'Enter' && e.metaKey) {
run();
}
});
// ------------------------------ CONTROLS ------------------------------
const $run = document.querySelector('[data-run]');
const $stop = document.querySelector('[data-stop]');
const $status = document.querySelector('[data-status]');
const $url = document.querySelector('[data-url]');
$run.addEventListener('click', run);
$stop.addEventListener('click', stop);
$url.addEventListener('click', createUrl);
function status(str) {
$status.textContent = str;
}
function createUrl() {
const value = editor.getValue();
// The /save route of our express server is
// expecting a JSON blob containing a `text` field.
fetch('/save', {
method: 'POST',
mode: 'cors',
cache: 'default',
body: JSON.stringify({ text: value }),
headers: {
'Content-Type': 'application/json',
'Access-Control-Request-Method': 'post',
'Access-Control-Allow-Credentials': 'true',
},
})
.then(response => response.text())
.then((text) => {
// Redirect the browser to the newly created patch.
window.location.pathname = `/${text}`;
})
.catch((e) => {
console.error(e);
displayError('Oh no! There’s a problem with the server. Try again in a bit.')
});
}
// ------------------------------ ERROR HANDLING ------------------------------
// Stash a few references to elements that we'll use to present
// errors to the user.
const $error = document.querySelector('#error');
const $errorContent = document.querySelector('#error-content');
const $dismiss = document.querySelector('#dismiss');
$dismiss.addEventListener('click', () => {
$error.classList.remove('show');
});
function displayError(message) {
$error.classList.add('show');
$errorContent.textContent = String(message).trim();
}
function clearError() {
$error.classList.remove('show');
}