Skip to content

Commit bddb0ba

Browse files
committed
feature: lock playground actions while actions are executed
- all actions will now trigger an event that other actions are going to listen to - playground will display spinner while action is executed - added missing reset code test - removed some development leftovers - restored flow-php.wip in turnstile captcha config
1 parent a67eb67 commit bddb0ba

16 files changed

+217
-108
lines changed

terraform/cloudflare/turnstile.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
resource "cloudflare_turnstile_widget" "flow_php" {
22
account_id = cloudflare_account.account.id
33
name = "Flow PHP"
4-
domains = ["flow-php.com"]
4+
domains = ["flow-php.com", "flow-php.wip"]
55
mode = "managed"
66
region = "world"
77
}

terraform/cloudflare/workers.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ resource "cloudflare_workers_script" "snippet_upload" {
44
content = file("${path.module}/workers/snippet-upload.js")
55
main_module = "snippet-upload.js"
66

7+
lifecycle {
8+
ignore_changes = all
9+
}
10+
711
compatibility_date = "2024-01-01"
812

913
bindings = [

web/landing/assets/controllers/playground_controller.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'prismjs/components/prism-csv.min.js'
77

88
export default class extends Controller {
99
static outlets = ["wasm", "code-editor", "turnstile", "playground-output"]
10-
static targets = ["loadingMessage", "loadingBar", "loadingPercent", "navigation", "editor", "outputContainer", "storageIndicator", "filePreviewContainer", "filePreviewTitle", "filePreviewContent"]
10+
static targets = ["loadingMessage", "loadingBar", "loadingPercent", "navigation", "editor", "outputContainer", "storageIndicator", "filePreviewContainer", "filePreviewTitle", "filePreviewContent", "actionSpinner"]
1111
static values = {
1212
packageIcon: String,
1313
linkIcon: String
@@ -26,6 +26,28 @@ export default class extends Controller {
2626
}
2727
}
2828

29+
onActionStarted() {
30+
document.querySelectorAll('#action-run, #action-format, #action-share, #action-upload').forEach(btn => btn.disabled = true)
31+
const resetLink = document.getElementById('action-reset')
32+
if (resetLink) {
33+
resetLink.classList.add('disabled')
34+
}
35+
if (this.hasActionSpinnerTarget) {
36+
this.actionSpinnerTarget.classList.remove('hidden')
37+
}
38+
}
39+
40+
onActionFinished() {
41+
document.querySelectorAll('#action-run, #action-format, #action-share, #action-upload').forEach(btn => btn.disabled = false)
42+
const resetLink = document.getElementById('action-reset')
43+
if (resetLink) {
44+
resetLink.classList.remove('disabled')
45+
}
46+
if (this.hasActionSpinnerTarget) {
47+
this.actionSpinnerTarget.classList.add('hidden')
48+
}
49+
}
50+
2951
onStorageLoaded(event) {
3052
this.#log('Code loaded from local storage')
3153
this.#showIndicator('storage')

web/landing/assets/controllers/playground_format_controller.js

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,39 @@ export default class extends Controller {
44
static outlets = ["code-editor", "wasm", "playground-output"]
55

66
async format() {
7-
await Promise.all([
8-
this.codeEditorOutlet.onLoad(),
9-
this.wasmOutlet.onLoad()
10-
])
7+
this.dispatch('action-started', { bubbles: true })
118

12-
const code = this.codeEditorOutlet.getCode()
9+
try {
10+
await Promise.all([
11+
this.codeEditorOutlet.onLoad(),
12+
this.wasmOutlet.onLoad()
13+
])
1314

14-
await this.wasmOutlet.writeFile('/workspace/code.php', code)
15+
const code = this.codeEditorOutlet.getCode()
1516

16-
this.playgroundOutputOutlet.show({ content: 'Formatting code...', type: 'info' })
17-
this.element.querySelectorAll('button').forEach(btn => btn.disabled = true)
17+
await this.wasmOutlet.writeFile('/workspace/code.php', code)
1818

19-
const result = await this.wasmOutlet.format(code)
19+
this.playgroundOutputOutlet.show({ content: 'Formatting code...', type: 'info' })
2020

21-
this.element.querySelectorAll('button').forEach(btn => btn.disabled = false)
21+
const result = await this.wasmOutlet.format(code)
2222

23-
if (result.success) {
24-
this.codeEditorOutlet.setCode(result.code)
25-
this.playgroundOutputOutlet.show({ content: 'Code formatted successfully!', type: 'success' })
23+
if (result.success) {
24+
this.codeEditorOutlet.setCode(result.code)
25+
this.playgroundOutputOutlet.show({ content: 'Code formatted successfully!', type: 'success' })
2626

27-
if (result.fixers && result.fixers.length > 0) {
28-
this.playgroundOutputOutlet.append({
29-
content: `Applied fixers: ${result.fixers.join(', ')}`,
30-
type: 'info'
31-
})
27+
if (result.fixers && result.fixers.length > 0) {
28+
this.playgroundOutputOutlet.append({
29+
content: `Applied fixers: ${result.fixers.join(', ')}`,
30+
type: 'info'
31+
})
32+
}
33+
} else {
34+
this.playgroundOutputOutlet.show({ content: `Format error: ${result.error}`, type: 'error' })
3235
}
33-
} else {
34-
this.playgroundOutputOutlet.show({ content: `Format error: ${result.error}`, type: 'error' })
35-
}
3636

37-
this.dispatch('formatted', { detail: { success: result.success }, bubbles: true })
37+
this.dispatch('formatted', { detail: { success: result.success }, bubbles: true })
38+
} finally {
39+
this.dispatch('action-finished', { bubbles: true })
40+
}
3841
}
3942
}

web/landing/assets/controllers/playground_run_controller.js

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,45 @@ export default class extends Controller {
44
static outlets = ["code-editor", "wasm", "playground-output"]
55

66
async run() {
7-
await Promise.all([
8-
this.codeEditorOutlet.onLoad(),
9-
this.wasmOutlet.onLoad()
10-
])
7+
this.dispatch('action-started', { bubbles: true })
118

12-
const code = this.codeEditorOutlet.getCode()
9+
try {
10+
await Promise.all([
11+
this.codeEditorOutlet.onLoad(),
12+
this.wasmOutlet.onLoad()
13+
])
1314

14-
await this.wasmOutlet.writeFile('/workspace/code.php', code)
15+
const code = this.codeEditorOutlet.getCode()
1516

16-
this.codeEditorOutlet.clearErrors()
17-
this.playgroundOutputOutlet.clear()
18-
this.playgroundOutputOutlet.show({ content: 'Running...', type: 'info' })
17+
await this.wasmOutlet.writeFile('/workspace/code.php', code)
1918

20-
this.element.querySelectorAll('button').forEach(btn => btn.disabled = true)
19+
this.codeEditorOutlet.clearErrors()
20+
this.playgroundOutputOutlet.clear()
21+
this.playgroundOutputOutlet.show({ content: 'Running...', type: 'info' })
2122

22-
const result = await this.wasmOutlet.run(code)
23+
const result = await this.wasmOutlet.run(code)
2324

24-
this.element.querySelectorAll('button').forEach(btn => btn.disabled = false)
25+
if (result.success) {
26+
this.playgroundOutputOutlet.show({ content: "\n" + result.output, type: 'success' })
27+
} else {
28+
this.playgroundOutputOutlet.show({ content: result.error.message, type: 'error' })
2529

26-
if (result.success) {
27-
this.playgroundOutputOutlet.show({ content: "\n" + result.output, type: 'success' })
28-
} else {
29-
this.playgroundOutputOutlet.show({ content: result.error.message, type: 'error' })
30-
31-
if (result.error.line) {
32-
this.codeEditorOutlet.highlightError({
33-
line: result.error.line,
34-
type: result.error.type,
35-
message: result.error.message,
36-
column: result.error.column
37-
})
30+
if (result.error.line) {
31+
this.codeEditorOutlet.highlightError({
32+
line: result.error.line,
33+
type: result.error.type,
34+
message: result.error.message,
35+
column: result.error.column
36+
})
37+
}
3838
}
39-
}
4039

41-
this.dispatch('executed', {
42-
detail: { success: result.success, executionTime: result.executionTime },
43-
bubbles: true
44-
})
40+
this.dispatch('executed', {
41+
detail: { success: result.success, executionTime: result.executionTime },
42+
bubbles: true
43+
})
44+
} finally {
45+
this.dispatch('action-finished', { bubbles: true })
46+
}
4547
}
4648
}

web/landing/assets/controllers/playground_share_controller.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,17 +225,19 @@ export default class extends Controller {
225225
}
226226

227227
async share() {
228-
await Promise.all([
229-
this.codeEditorOutlet.onLoad(),
230-
this.wasmOutlet.onLoad(),
231-
this.turnstileOutlet.onLoad()
232-
])
228+
this.dispatch('action-started', { bubbles: true })
233229

234-
const code = this.codeEditorOutlet.getCode()
230+
try {
231+
await Promise.all([
232+
this.codeEditorOutlet.onLoad(),
233+
this.wasmOutlet.onLoad(),
234+
this.turnstileOutlet.onLoad()
235+
])
235236

236-
await this.wasmOutlet.writeFile('/workspace/code.php', code)
237+
const code = this.codeEditorOutlet.getCode()
238+
239+
await this.wasmOutlet.writeFile('/workspace/code.php', code)
237240

238-
try {
239241
const files = await this.#collectUploadedFiles()
240242
const fileBlobs = files.map(f => f.blob)
241243
const fingerprint = await createFingerprint(code, fileBlobs)
@@ -281,6 +283,8 @@ export default class extends Controller {
281283
console.error('[ShareCode] Error stack:', error.stack)
282284
this.#logError('[ShareCode] Share failed:', error)
283285
this.#showNotification(`Failed to share: ${error.message}`, 'error')
286+
} finally {
287+
this.dispatch('action-finished', { bubbles: true })
284288
}
285289
}
286290

web/landing/assets/controllers/playground_upload_controller.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,18 @@ export default class extends Controller {
4545
}
4646

4747
async handleFileUpload(event) {
48-
const files = Array.from(event.target.files)
49-
for (const file of files) {
50-
await this.uploadFile(file)
51-
}
52-
if (this.hasFileInputTarget) {
53-
this.fileInputTarget.value = ''
48+
this.dispatch('action-started', { bubbles: true })
49+
50+
try {
51+
const files = Array.from(event.target.files)
52+
for (const file of files) {
53+
await this.uploadFile(file)
54+
}
55+
if (this.hasFileInputTarget) {
56+
this.fileInputTarget.value = ''
57+
}
58+
} finally {
59+
this.dispatch('action-finished', { bubbles: true })
5460
}
5561
}
5662

web/landing/assets/controllers/playground_workspace_controller.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ export default class extends Controller {
3333
}
3434
}
3535

36+
onActionStarted() {
37+
if (this.hasTreeTarget) {
38+
this.treeTarget.classList.add('disabled')
39+
}
40+
}
41+
42+
onActionFinished() {
43+
if (this.hasTreeTarget) {
44+
this.treeTarget.classList.remove('disabled')
45+
}
46+
}
47+
3648
async refreshTree() {
3749
await this.playgroundOutlet.onLoad()
3850

web/landing/assets/styles/app.css

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,16 +324,24 @@ code {
324324
}
325325

326326
button {
327-
@apply mr-4;
327+
@apply mr-4 transition-opacity;
328+
}
329+
330+
button:disabled {
331+
@apply opacity-50 cursor-not-allowed;
328332
}
329333

330334
a {
331-
@apply mr-4 no-underline text-white;
335+
@apply mr-4 no-underline text-white transition-opacity;
332336
}
333337

334338
a:hover {
335339
@apply no-underline;
336340
}
341+
342+
a.disabled {
343+
@apply opacity-50 cursor-not-allowed pointer-events-none;
344+
}
337345
}
338346
}
339347

@@ -454,4 +462,20 @@ code {
454462
@apply h-1 bg-blue-600 transition-all duration-300 ease-out;
455463
width: 0;
456464
}
465+
466+
.action-spinner {
467+
@apply inline-flex items-center mr-4;
468+
469+
&.hidden {
470+
display: none;
471+
}
472+
473+
svg {
474+
@apply text-cyan-400;
475+
}
476+
}
477+
478+
.file-tree.disabled {
479+
@apply pointer-events-none opacity-50;
480+
}
457481
}

0 commit comments

Comments
 (0)