Skip to content

Commit c1e12ed

Browse files
committed
Extract stimulus screenshot controller
1 parent 331ab16 commit c1e12ed

File tree

6 files changed

+105
-46
lines changed

6 files changed

+105
-46
lines changed

app/javascript/controllers/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import FrameForm from './forms/frame';
1919
import SyntaxHighlightPreview from './syntax-highlight/preview';
2020
import SnippetPreview from './snippets/preview';
2121
import SnippetEditor from './snippets/editor';
22+
import SnippetScreenshot from './snippets/screenshot';
2223

2324
application.register('analytics', AnalyticsCustomEvent);
2425
application.register('code-example', CodeExample);
@@ -31,6 +32,8 @@ application.register('pwa-web-push-demo', PwaWebPushDemo);
3132
application.register('table-of-contents', TableOfContents);
3233

3334
application.register('frame-form', FrameForm);
35+
application.register('syntax-highlight-preview', SyntaxHighlightPreview);
36+
3437
application.register('snippet-preview', SnippetPreview);
3538
application.register('snippet-editor', SnippetEditor);
36-
application.register('syntax-highlight-preview', SyntaxHighlightPreview);
39+
application.register('snippet-screenshot', SnippetScreenshot);

app/javascript/controllers/snippets/editor.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default class extends Controller {
2222
};
2323

2424
connect() {
25-
console.log('Stimulus controller connected');
25+
console.log('Connect!');
2626

2727
this.disableEditMode();
2828

@@ -35,19 +35,24 @@ export default class extends Controller {
3535

3636
this.element.addEventListener('click', this.enableEditMode);
3737

38-
this.textareaTarget.addEventListener('blur', this.preview);
38+
this.textareaTarget.addEventListener('blur', this.finishedEditing);
3939
this.textareaTarget.addEventListener('input', this.autogrow);
4040
window.addEventListener('resize', this.onResize);
41+
window.addEventListener('click', this.checkIfFinishedEditing);
4142
}
4243

4344
disconnect() {
4445
window.removeEventListener('resize', this.onResize);
46+
window.removeEventListener('click', this.checkIfFinishedEditing);
4547
}
4648

4749
enableEditMode = () => {
4850
this.textareaTarget.disabled = false;
4951
this.textareaTarget.style.visibility = 'visible';
5052
this.sourceTarget.style.visibility = 'hidden';
53+
this.textareaTarget.focus();
54+
55+
this.startedEditing();
5156
};
5257

5358
disableEditMode = () => {
@@ -62,9 +67,31 @@ export default class extends Controller {
6267
}
6368
};
6469

65-
preview = () => {
66-
this.dispatch('changed', {
70+
startedEditing = () => {
71+
console.log('Started editing!');
72+
this.dispatch('edit-start');
73+
};
74+
75+
finishedEditing = () => {
76+
console.log('Finished editing!');
77+
this.dispatch('edit-finish', {
6778
detail: { content: this.textareaTarget.value },
6879
});
6980
};
81+
82+
checkIfFinishedEditing = (event: Event) => {
83+
if (document.activeElement !== this.textareaTarget) {
84+
return;
85+
}
86+
87+
if (
88+
this.textareaTarget === event.target ||
89+
this.textareaTarget.contains(event.target as Node) ||
90+
this.textareaTarget.parentNode === event.target
91+
) {
92+
return;
93+
}
94+
95+
this.finishedEditing();
96+
};
7097
}
Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Controller } from '@hotwired/stimulus';
2-
import * as htmlToImage from 'html-to-image';
32

43
import debug from '../../utils/debug';
54

@@ -10,55 +9,45 @@ export default class extends Controller<HTMLFormElement> {
109
// based on https://donatstudios.com/TypeScriptTimeoutTrouble
1110
private idleTimeout?: ReturnType<typeof setTimeout>;
1211

13-
static targets = ['previewButton', 'snippet'];
12+
static targets = ['previewButton'];
1413

1514
declare readonly hasPreviewButtonTarget: boolean;
1615
declare readonly previewButtonTarget: HTMLInputElement;
1716

18-
declare readonly hasSnippetTarget: boolean;
19-
declare readonly snippetTarget: HTMLInputElement;
20-
2117
connect(): void {
2218
console.log('Connect!');
2319

24-
this.element.addEventListener(
25-
'turbo:before-fetch-request',
26-
this.prepareScreenshot,
27-
);
28-
}
29-
30-
disconnect(): void {
31-
this.clearIdleTimeout();
32-
}
33-
34-
clearIdleTimeout(): void {
35-
if (this.idleTimeout) clearTimeout(this.idleTimeout);
20+
this.element.addEventListener('turbo:submit-start', (event) => {
21+
if (
22+
(event as CustomEvent).detail.formSubmission.submitter !==
23+
this.previewButtonTarget
24+
) {
25+
return;
26+
}
27+
28+
if (event.target instanceof HTMLFormElement) {
29+
for (const field of event.target.elements) {
30+
(field as HTMLInputElement).disabled = true;
31+
}
32+
}
33+
});
34+
35+
this.element.addEventListener('turbo:submit-end', (event) => {
36+
if (event.target instanceof HTMLFormElement) {
37+
for (const field of event.target.elements) {
38+
(field as HTMLInputElement).disabled = false;
39+
}
40+
}
41+
});
3642
}
3743

3844
preview = (): void => {
39-
console.log('Start preview timer!');
40-
this.clearIdleTimeout();
41-
this.idleTimeout = setTimeout(this.clickPreviewButton, 500);
45+
console.log('Start preview!');
46+
this.clickPreviewButton();
4247
};
4348

4449
clickPreviewButton = (): void => {
4550
console.log('Click preview button!');
4651
this.previewButtonTarget.click();
4752
};
48-
49-
prepareScreenshot = async (event) => {
50-
event.preventDefault();
51-
52-
if (event.detail.fetchOptions.body instanceof URLSearchParams) {
53-
const data = await this.drawScreenshot();
54-
55-
event.detail.fetchOptions.body.append('screenshot', data);
56-
}
57-
58-
event.detail.resume();
59-
};
60-
61-
drawScreenshot = async () => {
62-
return htmlToImage.toPng(this.snippetTarget);
63-
};
6453
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
import * as htmlToImage from 'html-to-image';
3+
4+
import debug from '../../utils/debug';
5+
6+
const console = debug('app:javascript:controllers:snippets:screenshot');
7+
8+
export default class extends Controller<HTMLFormElement> {
9+
static targets = ['snippet'];
10+
11+
declare readonly hasSnippetTarget: boolean;
12+
declare readonly snippetTarget: HTMLInputElement;
13+
14+
connect(): void {
15+
console.log('Connect!');
16+
17+
this.element.addEventListener(
18+
'turbo:before-fetch-request',
19+
this.prepareScreenshot,
20+
);
21+
}
22+
23+
prepareScreenshot = async (event) => {
24+
event.preventDefault();
25+
26+
if (event.detail.fetchOptions.body instanceof URLSearchParams) {
27+
console.log('Prepare screenshot!');
28+
29+
const data = await this.drawScreenshot();
30+
31+
event.detail.fetchOptions.body.append('screenshot', data);
32+
}
33+
34+
event.detail.resume();
35+
};
36+
37+
drawScreenshot = async () => {
38+
return htmlToImage.toPng(this.snippetTarget);
39+
};
40+
}

app/javascript/css/components/code.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
& pre {
3636
border-radius: 0.5rem;
37-
padding-top: var(--space-m);
37+
padding-top: var(--space-xs);
3838
padding-inline-end: var(--space-m);
3939
padding-bottom: var(--space-m);
4040
padding-inline-start: var(--space-m);
@@ -196,7 +196,7 @@
196196
}
197197

198198
& .code-editor {
199-
padding-top: var(--space-m);
199+
padding-top: var(--space-xs);
200200
padding-inline-end: var(--space-m);
201201
padding-bottom: var(--space-m);
202202
padding-inline-start: var(--space-m);

app/views/snippets/form.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ def view_template
1515
model: snippet,
1616
class: "grid-content",
1717
data: {
18-
controller: "snippet-preview",
19-
action: "snippet-editor:changed->snippet-preview#preview"
18+
controller: "snippet-preview snippet-screenshot",
19+
action: "snippet-editor:edit-finish->snippet-preview#preview"
2020
}
2121
) do |form|
2222
errors
2323

2424
language_select(form, data: {action: "change->snippet-preview#preview"})
2525

2626
turbo_frame_tag dom_id(snippet, :code_block), class: "snippet-frame grid-cols-12" do
27-
div(class: "snippet-background", data: {snippet_preview_target: "snippet"}) do
27+
div(class: "snippet-background", data: {snippet_screenshot_target: "snippet"}) do
2828
render CodeBlock::Container.new(language: language, class: "snippet") do
2929
render CodeBlock::Header.new do
3030
label(class: "sr-only", for: "snippet[filename]") { "Filename" }

0 commit comments

Comments
 (0)