Skip to content

Commit cc5d1bc

Browse files
committed
feat: enhance CSS editor with clipboard functionality and incorporated UI changes from PR review
1 parent bac7975 commit cc5d1bc

File tree

9 files changed

+190
-21
lines changed

9 files changed

+190
-21
lines changed

packages/studio-web/src/app/editor/editor.component.sass

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
margin-right: 1em
3030
margin-top: 1em
3131
cursor: col-resize
32-
z-index: 1000
32+
z-index: 10
3333
display: block
3434
#styleWindow
3535
margin: 0

packages/studio-web/src/app/shared/wc-styling/wc-styling.component.html

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ <h1 class="title pt-2">
120120
</h3>
121121
</span>
122122

123-
<span class="ms-5 ms-md-0 p-0 pt-3 d-flex justify-content-end">
123+
<span
124+
class="ms-5 ms-md-0 p-0 pt-3 d-flex justify-content-end justify-content-xl-center"
125+
>
124126
<button
125127
i18n="Style download button"
126128
mat-button
@@ -136,6 +138,22 @@ <h1 class="title pt-2">
136138
</button>
137139
</span>
138140
</div>
141+
<div *ngIf="canUseClipBoard" class="row">
142+
<div class="col d-flex justify-content-center">
143+
<button
144+
mat-button
145+
(click)="copyStyle()"
146+
[disabled]="!(styleText$ | async)"
147+
>
148+
<mat-icon class="mat-icon-lg">content_copy</mat-icon
149+
><span i18n="copy">Copy</span>
150+
</button>
151+
<button mat-button (click)="pasteStyle()">
152+
<mat-icon class="mat-icon-lg">content_paste</mat-icon
153+
><span i18n="copy">Paste</span>
154+
</button>
155+
</div>
156+
</div>
139157
<div class="row">
140158
<mat-form-field class="col-12 p-0 b-0 ms-5 ms-md-0">
141159
<textarea

packages/studio-web/src/app/shared/wc-styling/wc-styling.component.sass

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,13 @@ code
4444
#style-section
4545
transform: rotate(0deg) translateX(0%)
4646
height: 70vh
47-
transition: height 1s
4847
width: 100%
4948

5049
#style-section.collapsed
5150
transform: rotate(90deg)
5251
transform-origin: bottom left
5352
width: 85em
5453
max-height: 4.7em
55-
transition: transform 1s
5654
max-width: calc( 55vh + 100px)
5755

5856
div.header > div

packages/studio-web/src/app/shared/wc-styling/wc-styling.component.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class WcStylingComponent implements OnDestroy, OnInit {
3131
@ViewChild("styleInputElement") styleInputElement: ElementRef;
3232
@ViewChild("fontInputElement") fontInputElement: ElementRef;
3333
@ViewChild("styleSection") styleSection: ElementRef;
34+
canUseClipBoard = false;
3435

3536
constructor(
3637
private toastr: ToastrService,
@@ -51,6 +52,20 @@ export class WcStylingComponent implements OnDestroy, OnInit {
5152
//this.collapsed = false;
5253
}
5354
});
55+
//check if clipboard access is allowed
56+
navigator.permissions
57+
.query({ name: "clipboard-write" as PermissionName })
58+
.then((result) => {
59+
if (result.state === "granted" || result.state === "prompt") {
60+
this.canUseClipBoard = true;
61+
} else {
62+
this.canUseClipBoard = false;
63+
}
64+
})
65+
.catch((err) => {
66+
console.error("Failed to query clipboard permissions", err);
67+
this.canUseClipBoard = false;
68+
});
5469
}
5570
onFontSelected(event: any) {
5671
const file: File = event.target.files[0];
@@ -107,6 +122,7 @@ export class WcStylingComponent implements OnDestroy, OnInit {
107122
.text()
108123
.then((val) => {
109124
this.styleText$.next(val);
125+
110126
this.wcStylingService.$wcStyleInput.next(val);
111127
this.inputType = "edit";
112128
this.toastr.success(
@@ -179,7 +195,6 @@ span.theme--dark.sentence__text {
179195
}
180196
toggleStyleInput(event: any) {
181197
this.inputType = event.value;
182-
this.collapsed$.next(false);
183198
}
184199
async ngOnInit() {
185200
this.wcStylingService.$wcStyleInput
@@ -206,6 +221,60 @@ span.theme--dark.sentence__text {
206221
toggleCollapse() {
207222
this.collapsed$.next(!this.collapsed$.getValue());
208223
}
224+
pasteStyle() {
225+
if (this.canUseClipBoard) {
226+
navigator.clipboard
227+
.readText()
228+
.then((text) => {
229+
this.styleText$.next(text);
230+
this.wcStylingService.$wcStyleInput.next(text);
231+
this.inputType = "edit";
232+
this.toastr.success(
233+
$localize`Style sheet pasted from clipboard.`,
234+
undefined,
235+
{ timeOut: 10000 },
236+
);
237+
})
238+
.catch((err) => {
239+
this.toastr.error($localize`Failed to read clipboard content.`, err, {
240+
timeOut: 2000,
241+
});
242+
});
243+
} else {
244+
this.toastr.error(
245+
$localize`Clipboard access is not allowed.`,
246+
$localize`Error`,
247+
);
248+
}
249+
}
250+
251+
copyStyle() {
252+
if (this.canUseClipBoard) {
253+
navigator.clipboard
254+
.writeText(this.styleText$.getValue())
255+
.then(() => {
256+
this.toastr.success(
257+
$localize`Style sheet copied to clipboard.`,
258+
undefined,
259+
{ timeOut: 10000 },
260+
);
261+
})
262+
.catch((err) => {
263+
this.toastr.error(
264+
$localize`Failed to copy style sheet to clipboard.`,
265+
err,
266+
{
267+
timeOut: 2000,
268+
},
269+
);
270+
});
271+
} else {
272+
this.toastr.error(
273+
$localize`Clipboard access is not allowed.`,
274+
$localize`Error`,
275+
);
276+
}
277+
}
209278
}
210279

211280
@Component({

packages/studio-web/src/i18n/messages.es.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
"3796650812518266523": " Escriba o pegue sus hojas de estilos aquí ",
4949
"4934000757746180538": "{$START_TAG_MAT_ICON}save{$CLOSE_TAG_MAT_ICON} Guarde una copia",
5050
"7071695380382454380": "{$START_TAG_MAT_ICON}sync{$CLOSE_TAG_MAT_ICON} Aplicar",
51+
"4323470180912194028": "Copiar",
52+
"8890504809170904482": "Pegar",
5153
"7604046252874749392": "Opcional: utilice una fuente personalizada (.woff2)",
5254
"7719309746449095739": "Fichero ",
5355
"7466581557533667662": " no se pudo procesarlo.",
@@ -56,6 +58,12 @@
5658
"6899344040225872362": "¡Genial!",
5759
"5700078517045291790": " Contenido cargado en el cuadro de texto.",
5860
"6558433540988178003": "No hay texto para descargar.",
61+
"4533795875760195674": "Hojas de estilos pegadas desde el portapapeles.",
62+
"3715050097325047804": "Error al leer el contenido del portapapeles.",
63+
"5048497709983421225": "Acceso al portapapeles no permitido.",
64+
"1519954996184640001": "Error",
65+
"1205008396748653088": "Hojas de estilos copiadas al portapapeles.",
66+
"7492776625383944837": "Error al copiar las hojas de estilos al portapapeles.",
5967
"6731392928829867425": "Bienvenidos al Studio de ReadAlong",
6068
"1309246714146466494": "¡Crear un ReadAlong es fácil! Esta guía le mostrará todas las funcionalidades del Studio.",
6169
"3885497195825665706": "Próximo",

packages/studio-web/src/i18n/messages.fr.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
"3796650812518266523": " Écrivez ou copiez-collez vos feuilles de style ici ",
4949
"4934000757746180538": "{$START_TAG_MAT_ICON}save{$CLOSE_TAG_MAT_ICON} Copie de sauvegarde",
5050
"7071695380382454380": "{$START_TAG_MAT_ICON}sync{$CLOSE_TAG_MAT_ICON} Appliquer",
51+
"4323470180912194028": "Copier",
52+
"8890504809170904482": "Coller",
5153
"7604046252874749392": "Optionnel: utiliser une police personnalisée (.woff2)",
5254
"7719309746449095739": "Fichier ",
5355
"7466581557533667662": " erroné.",
@@ -56,6 +58,12 @@
5658
"6899344040225872362": "Bravo!",
5759
"5700078517045291790": " Contenu chargé dans la zone de texte.",
5860
"6558433540988178003": "Pas de texte à télécharger.",
61+
"4533795875760195674": "Feuilles de style collées du presse-papier.",
62+
"3715050097325047804": "Échec de lecture du contenu du presse-papier.",
63+
"5048497709983421225": "Accès au presse-papier non autorisé.",
64+
"1519954996184640001": "Erreur",
65+
"1205008396748653088": "Feuilles de style copiées dans le presse-papier.",
66+
"7492776625383944837": "Échec de copie des feuilles de style dans le presse-papier.",
5967
"6731392928829867425": "Bienvenue au Studio ReadAlong",
6068
"1309246714146466494": "Il est facile de créer un ReadAlong! Vous trouverez tous les trucs et astuces pour utiliser le Studio dans cette visite guidée.",
6169
"3885497195825665706": "Continuer",

packages/studio-web/src/i18n/messages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
"3796650812518266523": " Write or paste your style sheet here ",
4949
"4934000757746180538": "{$START_TAG_MAT_ICON}save{$CLOSE_TAG_MAT_ICON} Save a copy",
5050
"7071695380382454380": "{$START_TAG_MAT_ICON}sync{$CLOSE_TAG_MAT_ICON} Apply ",
51+
"4323470180912194028": "Copy",
52+
"8890504809170904482": "Paste",
5153
"7604046252874749392": "Optional: use a custom font (.woff2)",
5254
"7719309746449095739": "File ",
5355
"7466581557533667662": " could not be processed.",
@@ -56,6 +58,12 @@
5658
"6899344040225872362": "Great!",
5759
"5700078517045291790": " Content loaded in the text box.",
5860
"6558433540988178003": "No text to download.",
61+
"4533795875760195674": "Style sheet pasted from clipboard.",
62+
"3715050097325047804": "Failed to read clipboard content.",
63+
"5048497709983421225": "Clipboard access is not allowed.",
64+
"1519954996184640001": "Error",
65+
"1205008396748653088": "Style sheet copied to clipboard.",
66+
"7492776625383944837": "Failed to copy style sheet to clipboard.",
5967
"6731392928829867425": "Welcome to ReadAlong Studio",
6068
"1309246714146466494": "Creating a ReadAlong is easy! This guide will show you all the bells and whistles of the Studio.",
6169
"3885497195825665706": "Next",

packages/studio-web/tests/editor/custom-css.spec.ts

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { test, expect } from "@playwright/test";
22
import { testAssetsPath, disablePlausible } from "../test-commands";
33
import fs from "fs";
4-
import { hasUncaughtExceptionCaptureCallback } from "process";
4+
55
test.describe.configure({ mode: "parallel" });
6-
test.beforeEach(async ({ page, isMobile }) => {
6+
test.beforeEach(async ({ page, isMobile, context }) => {
7+
await context.grantPermissions(["clipboard-write", "clipboard-read"]);
78
await page.goto("/", { waitUntil: "load" });
89
disablePlausible(page);
910
if (isMobile) {
@@ -33,21 +34,21 @@ test.beforeEach(async ({ page, isMobile }) => {
3334
page.locator("#t0b0d0"),
3435
"read along has been loaded",
3536
).toHaveCount(1);
37+
await expect(
38+
page.locator("#style-section"),
39+
"css editor to be hidden",
40+
).toHaveClass(/\bcollapsed\b/);
41+
await page.getByTestId("toggle-css-box").click();
42+
await expect(
43+
page.locator("#style-section"),
44+
"css editor to be visible",
45+
).not.toHaveClass(/\bcollapsed\b/);
46+
await expect(page.locator("#styleInput"), "has style data").toHaveValue(
47+
/\.theme--light/,
48+
);
3649
}).toPass();
3750
});
3851
test("should edit css (editor)", async ({ page, isMobile }) => {
39-
await expect(
40-
page.locator("#style-section"),
41-
"css editor to be hidden",
42-
).toHaveClass(/\bcollapsed\b/);
43-
await page.getByTestId("toggle-css-box").click();
44-
await expect(
45-
page.locator("#style-section"),
46-
"css editor to be visible",
47-
).not.toHaveClass(/\bcollapsed\b/);
48-
await expect(page.locator("#styleInput"), "has style data").toHaveValue(
49-
/\.theme--light/,
50-
);
5152
await expect(
5253
page
5354
.locator('[data-test-id="text-container"]')
@@ -91,3 +92,62 @@ test("should edit css (editor)", async ({ page, isMobile }) => {
9192
".theme--light.sentence__word,\n.theme--light.sentence__text {\n color: rgba(180, 170, 70, .9) !important;\n}",
9293
);
9394
});
95+
test("should use with custom font", async ({ page, isMobile }) => {
96+
let fileChooserPromise = page.waitForEvent("filechooser");
97+
await page.locator("#defaultFont").click();
98+
let fileChooser = await fileChooserPromise;
99+
fileChooser.setFiles(testAssetsPath + "cour.ttf");
100+
await expect(
101+
page.getByText("File cour.ttf processed."),
102+
"font successfully loaded",
103+
).toBeVisible();
104+
});
105+
106+
test("should paste in style", async ({ page, context }) => {
107+
const style = fs.readFileSync(
108+
testAssetsPath + "sentence-paragr-cust-css.css",
109+
{ encoding: "utf8", flag: "r" },
110+
);
111+
// Ensure clipboard permissions are granted
112+
await context.grantPermissions(["clipboard-write"]);
113+
await page.evaluate(async (text) => {
114+
await navigator.clipboard.writeText(text);
115+
}, style);
116+
await page.getByRole("button", { name: "Paste" }).click();
117+
// Wait for the style input to be updated after paste
118+
await expect(
119+
page.locator("#styleInput"),
120+
"style input should not be empty",
121+
).not.toBeEmpty();
122+
await expect
123+
.poll(async () => await page.locator("#styleInput").inputValue(), {
124+
message: "check that the style input has been replaced",
125+
})
126+
.toContain(style);
127+
});
128+
test("should load and copy style", async ({ page, context }) => {
129+
await context.grantPermissions(["clipboard-read"]);
130+
let fileChooserPromise = page.waitForEvent("filechooser");
131+
await page.getByRole("radio", { name: "File" }).click();
132+
await page
133+
.locator("#updateStyle")
134+
.waitFor({ state: "visible", timeout: 10000 });
135+
await page.locator("#updateStyle").click();
136+
let fileChooser = await fileChooserPromise;
137+
fileChooser.setFiles(testAssetsPath + "sentence-paragr-cust-css.css");
138+
await expect(
139+
page.getByText(
140+
"File sentence-paragr-cust-css.css processed. Content loaded in the text box.",
141+
),
142+
"css successfully loaded",
143+
).toBeVisible();
144+
await expect(
145+
page.locator("#styleInput"),
146+
"style input should not be empty",
147+
).not.toBeEmpty();
148+
await page.getByRole("button", { name: "Copy" }).click();
149+
const css = await page.evaluate(() => navigator.clipboard.readText());
150+
await expect(css.length, "clipboard css should not be empty").toBeGreaterThan(
151+
0,
152+
);
153+
});

packages/web-component/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ and modify only the UI. Use the web inspector of your browser to find the classe
180180
}
181181
```
182182

183-
Here is a list of minimum classes you want to override:
183+
Here is a list of the minimum classes you want to override:
184184

185185
- .sentence\_\_word.theme--light
186186
- .sentence\_\_word.theme--light.reading
@@ -193,7 +193,7 @@ Here is a list of minimum classes you want to override:
193193
- .paragraph
194194
- .page\_\_container.theme--light (to set page background)
195195

196-
[look at this sample stylesheet for idea](../studio-web/tests/fixtures/sentence-paragr-cust-css.css)
196+
[look at this sample stylesheet to get an idea of what is needed](../studio-web/tests/fixtures/sentence-paragr-cust-css.css)
197197

198198
## XML customizations
199199

0 commit comments

Comments
 (0)