Skip to content
Open
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
# faust-web-component

> :warning: This is a fork !
>
> Features in fork: `readonly` boolean attribute for read-only editor
> and `min-height` for setting a minimum height as CSS prop to the editor element
> Additional variant \<faust-editor-basic\> without buttons for dynamic editor
> Aims to bring a lightweight variant, see [the demo](https://synthe.tiseur.fr/faust-web-component/#readonlyandbasic)
> or an usecase in [my other project](https://github.com/Simon-L/pasfa)
> `dist/faust-web-component-basic.js` contains only the basic variant, it's much smaller in size but obviously doesn't allow compiling DSP.

This package provides two [web components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) for embedding interactive [Faust](https://faust.grame.fr) snippets in web pages.

- `<faust-editor>` displays an editor (using [CodeMirror 6](https://codemirror.net/)) with executable, editable Faust code, along with some bells & whistles (controls, block diagram, plots) in a side pane.
This component is ideal for demonstrating some code in Faust and allowing the reader to try it out and tweak it themselves without having to leave the page. (For more extensive work, it also includes a button to open the code in the Faust IDE.)
* `readonly` : attribute disables playback and makes editor read-only.
* `min-height` : attribute to set the min-height CSS property, the value must be valid CSS value eg. "42px", "1.5em".

- `<faust-widget>` just shows the controls and does not allow editing, so it serves simply as a way to embed interactive DSP.

Expand Down
41 changes: 41 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</head>
<body>
<div id="content">
<a href="#readonlyandbasic">Readonly and basic editor examples</a>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
<faust-editor>
import("stdfaust.lib");
Expand Down Expand Up @@ -53,6 +54,46 @@
dm.zita_light; // stereo reverb
-->
</faust-editor>
<a id="readonlyandbasic"></a>
<p>Read-only editor</p>
<faust-editor readonly>
<!--
// This editor is readonly
import("stdfaust.lib");
process =
dm.cubicnl_demo : // distortion
dm.wah4_demo <: // wah pedal
dm.phaser2_demo : // stereo phaser
dm.compressor_demo : // stereo compressor
dm.zita_light; // stereo reverb
-->
</faust-editor>
<p>Basic editor without playback</p>
<faust-editor-basic>
<!--
// This editor is "basic", it doesn't offer playback
import("stdfaust.lib");
process =
dm.cubicnl_demo : // distortion
dm.wah4_demo <: // wah pedal
dm.phaser2_demo : // stereo phaser
dm.compressor_demo : // stereo compressor
dm.zita_light; // stereo reverb
-->
</faust-editor-basic>
<p>Read-only basic editor without playback</p>
<faust-editor-basic readonly>
<!--
// This editor is "basic", it doesn't offer playback
import("stdfaust.lib");
process =
dm.cubicnl_demo : // distortion
dm.wah4_demo <: // wah pedal
dm.phaser2_demo : // stereo phaser
dm.compressor_demo : // stereo compressor
dm.zita_light; // stereo reverb
-->
</faust-editor-basic>
<p>Minus, ducimus consequuntur? Corrupti animi aut magni nihil, dolor eos tenetur, autem deserunt et iure culpa, suscipit minus quia velit laudantium asperiores.</p>
<faust-widget>
<!--
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
"tsconfig.json",
"vite.config.js",
"dist/faust-web-component.js",
"dist/faust-web-component-basic.js",
"dist/02-XYLO1.mp3"
],
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "vite build && vite build --mode basic",
"preview": "vite preview"
},
"repository": {
Expand Down
4 changes: 2 additions & 2 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import jsURL from "@grame/faustwasm/libfaust-wasm/libfaust-wasm.js?url"
import dataURL from "@grame/faustwasm/libfaust-wasm/libfaust-wasm.data?url"
import wasmURL from "@grame/faustwasm/libfaust-wasm/libfaust-wasm.wasm?url"
import { library } from "@fortawesome/fontawesome-svg-core"
import { faPlay, faStop, faUpRightFromSquare, faSquareCaretLeft, faAnglesLeft, faAnglesRight, faSliders, faDiagramProject, faWaveSquare, faChartLine, faPowerOff } from "@fortawesome/free-solid-svg-icons"
import { faPlay, faStop, faUpRightFromSquare, faSquareCaretLeft, faAnglesLeft, faAnglesRight, faSliders, faDiagramProject, faWaveSquare, faChartLine, faPowerOff, faCopy } from "@fortawesome/free-solid-svg-icons"

for (const icon of [faPlay, faStop, faUpRightFromSquare, faSquareCaretLeft, faAnglesLeft, faAnglesRight, faSliders, faDiagramProject, faWaveSquare, faChartLine, faPowerOff]) {
for (const icon of [faPlay, faStop, faUpRightFromSquare, faSquareCaretLeft, faAnglesLeft, faAnglesRight, faSliders, faDiagramProject, faWaveSquare, faChartLine, faPowerOff, faCopy]) {
library.add(icon)
}

Expand Down
3 changes: 2 additions & 1 deletion src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const faustLanguage = StreamLanguage.define(clike({
}
}))

export function createEditor(parent: HTMLElement, doc: string) {
export function createEditor(parent: HTMLElement, doc: string, editable: boolean = true) {
return new EditorView({
parent,
doc,
Expand Down Expand Up @@ -63,6 +63,7 @@ export function createEditor(parent: HTMLElement, doc: string) {
...lintKeymap
]),
faustLanguage,
EditorView.editable.of(editable)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want the readOnly facet instead of (or perhaps in addition to) the editable facet.
editable prevents the editor from getting focus but doesn't actually prevent the contents from being modified, so it's currently possible to modify the contents of a readonly editor via e.g. cut & paste shortcuts.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 476466a

],
})
}
Expand Down
169 changes: 169 additions & 0 deletions src/faust-editor-basic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { icon } from "@fortawesome/fontawesome-svg-core"
import faustCSS from "@shren/faust-ui/dist/esm/index.css?inline"
import { createEditor, setError, clearError } from "./editor"
import faustSvg from "./faustText.svg"

function editorTemplate(readonly: boolean = false, minHeight: string = "") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like readonly is unused in this function.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 26f6753

const editorMinHeight = minHeight != "" ? `min-height: ${minHeight};` : ""
const template = document.createElement("template")
template.innerHTML = `
<div id="root">
<div id="controls">
<a title="Open in Faust IDE" id="ide" href="https://faustide.grame.fr/" class="button" target="_blank">${icon({ prefix: "fas", iconName: "up-right-from-square" }).html[0]}</a>
<button title="Copy" class="button" id="copy">${icon({ prefix: "fas", iconName: "copy" }).html[0]}</button>
<a title="Faust website" id="faust" href="https://faust.grame.fr/" target="_blank"><img src="${faustSvg}" height="15px" /></a>
</div>
<div id="content">
<div id="editor"></div>
</div>
</div>
<style>
#root {
overflow:hidden;
border: 1px solid black;
border-radius: 5px;
box-sizing: border-box;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to reduce duplication between this module and faust-editor.ts. Perhaps the shared CSS could be moved out to a common module.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to keep that for a later task/PR

*, *:before, *:after {
box-sizing: inherit;
}
#controls {
background-color: #384d64;
border-bottom: 1px solid black;
display: flex;
}
#faust {
margin-left: auto;
margin-right: 10px;
display: flex;
align-items: center;
}
#content {
display: flex;
}
#editor {
flex-grow: 1;
overflow-y: auto;
}
#editor .cm-editor {
height: 100%;
${minHeight != "" ? `min-height: ${minHeight};` : ""}
}
.cm-diagnostic {
font-family: monospace;
}
.cm-diagnostic-error {
background-color: #fdf2f5 !important;
color: #a4000f !important;
border-color: #a4000f !important;
}
a.button {
appearance: button;
}
.button {
background-color: #384d64;
border: 0;
padding: 5px;
width: 25px;
height: 25px;
color: #fff;
}
.button:hover {
background-color: #4b71a1;
}
.button:active {
background-color: #373736;
}
.button:disabled {
opacity: 0.65;
cursor: not-allowed;
pointer-events: none;
}
#controls > .button > svg {
width: 15px;
height: 15px;
vertical-align: top;
}
${faustCSS}
</style>
`
return template
}

export default class FaustEditorBasic extends HTMLElement {
constructor() {
super()
}

readonly = false
minHeight = null
editor = null

getCodeString() {
return this.editor.state.doc.toString()
}

setCode(code) {
this.editor.dispatch({
changes: {from: 0, to: this.editor.state.doc.length, insert: code}
})
}

connectedCallback() {
const code = this.innerHTML.replace("<!--", "").replace("-->", "").trim()
console.log("connectedCallback: Got %s for min-height", this.minHeight);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let's remove the debug logging before this is merged

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 26f6753

this.attachShadow({ mode: "open" }).appendChild(editorTemplate(this.readonly, this.minHeight).content.cloneNode(true))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generating a new template node every time defeats the purpose of using a template for cloning; we should just do one or the other.
That is, either generate the innerHTML of the shadow DOM directly without first generating and cloning and template, or clone a fixed template and then tweak it to apply min-height. (I'd prefer the latter.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! To be honest I had no prior experience with writing custom elements, shadow DOM and templates used in that way.
I think if we can get the min-height to be set by the user entirely from the outer document, then I should be able to revert to the initial template.
Afaik though, unnecessary code left aside, the improvement should not be significant unless showing hundreds of editors.


const ideLink = this.shadowRoot!.querySelector("#ide") as HTMLAnchorElement
ideLink.onfocus = () => {
// Open current contents of editor in IDE
const urlParams = new URLSearchParams()
urlParams.set("inline", btoa(editor.state.doc.toString()).replace("+", "-").replace("/", "_"))
ideLink.href = `https://faustide.grame.fr/?${urlParams.toString()}`
}

const editorEl = this.shadowRoot!.querySelector("#editor") as HTMLDivElement
const editor = createEditor(editorEl, code, !this.readonly)

const copyButton = this.shadowRoot!.querySelector("#copy") as HTMLButtonElement

copyButton.onclick = () => {
if (navigator.clipboard) {
navigator.clipboard.writeText(editor.state.doc.toString())
} else {
console.log("Unable to use clipboard")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could check navigator.clipboard at template-generation time to avoid creating the copy button in the first place.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 476466a

}
}

this.editor = editor
}

attributeChangedCallback(name, oldValue, newValue) {
if ((name == "readonly") && (newValue != null)) {
this.readonly = true
}
if ((name == "min-height") && (newValue != "")) {
this.minHeight = newValue
}
}

static get observedAttributes() {
return ["readonly", "min-height"];
}

}
Loading