Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

web: Implement pasting text using Clipboard API #16692

Merged
merged 5 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
[target.'cfg(all())']
# NOTE that the web build overrides this setting in package.json via the RUSTFLAGS environment variable
rustflags = [
# We need to specify this flag for all targets because Clippy checks all of our code against all targets
# and our web code does not compile without this flag
"--cfg=web_sys_unstable_apis",
]

[target.x86_64-pc-windows-msvc]
# Use the LLD linker, it should be faster than the default.
# See: https://github.com/rust-lang/rust/issues/71520
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
- name: Disable linker start-stop-gc
# Note: We also use `rust-lld` on Windows, see `config.toml`.
if: runner.os != 'macOS'
run: echo RUSTFLAGS=${RUSTFLAGS}\ -Clink-args=-znostart-stop-gc >> $GITHUB_ENV
run: echo RUSTFLAGS=${RUSTFLAGS}\ --cfg=web_sys_unstable_apis\ -Clink-args=-znostart-stop-gc >> $GITHUB_ENV

- name: Cache Cargo output
uses: Swatinem/rust-cache@v2
Expand Down
2 changes: 1 addition & 1 deletion web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ features = [
"EventTarget", "GainNode", "Headers", "HtmlCanvasElement", "HtmlDocument", "HtmlElement", "HtmlFormElement",
"HtmlInputElement", "HtmlTextAreaElement", "KeyboardEvent", "Location", "PointerEvent",
"Request", "RequestInit", "Response", "Storage", "WheelEvent", "Window", "ReadableStream", "RequestCredentials",
"Url",
"Url", "Clipboard",
]

[package.metadata.cargo-machete]
Expand Down
47 changes: 38 additions & 9 deletions web/packages/core/src/ruffle-player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ interface ContextMenuItem {
*
* @param event The mouse event that triggered the click.
*/
onClick: (event: MouseEvent) => void;
onClick: (event: MouseEvent) => Promise<void>;

/**
* Whether this item is clickable.
Expand Down Expand Up @@ -146,6 +146,7 @@ export class RufflePlayer extends HTMLElement {
private readonly volumeControls: HTMLDivElement;
private readonly videoModal: HTMLDivElement;
private readonly hardwareAccelerationModal: HTMLDivElement;
private readonly clipboardModal: HTMLDivElement;

private readonly contextMenuOverlay: HTMLElement;
// Firefox has a read-only "contextMenu" property,
Expand Down Expand Up @@ -271,10 +272,14 @@ export class RufflePlayer extends HTMLElement {
this.volumeControls = <HTMLDivElement>(
this.shadow.getElementById("volume-controls-modal")
);
this.clipboardModal = <HTMLDivElement>(
this.shadow.getElementById("clipboard-modal")
);
this.addModalJavaScript(this.saveManager);
this.addModalJavaScript(this.volumeControls);
this.addModalJavaScript(this.videoModal);
this.addModalJavaScript(this.hardwareAccelerationModal);
this.addModalJavaScript(this.clipboardModal);

this.volumeSettings = new VolumeControls(false, 100);
this.addVolumeControlsJavaScript(this.volumeControls);
Expand Down Expand Up @@ -1380,7 +1385,7 @@ export class RufflePlayer extends HTMLElement {
/**
* Opens the save manager.
*/
private openSaveManager(): void {
private async openSaveManager(): Promise<void> {
this.saveManager.classList.remove("hidden");
}

Expand Down Expand Up @@ -1467,7 +1472,7 @@ export class RufflePlayer extends HTMLElement {
// TODO: better checkboxes
text:
item.caption + (item.checked ? ` (${CHECKMARK})` : ``),
onClick: () =>
onClick: async () =>
this.instance?.run_context_menu_callback(index),
enabled: item.enabled,
});
Expand All @@ -1480,19 +1485,19 @@ export class RufflePlayer extends HTMLElement {
if (this.isFullscreen) {
items.push({
text: text("context-menu-exit-fullscreen"),
onClick: () => this.setFullscreen(false),
onClick: async () => this.setFullscreen(false),
});
} else {
items.push({
text: text("context-menu-enter-fullscreen"),
onClick: () => this.setFullscreen(true),
onClick: async () => this.setFullscreen(true),
});
}
}

items.push({
text: text("context-menu-volume-controls"),
onClick: () => {
onClick: async () => {
this.openVolumeControls();
},
});
Expand Down Expand Up @@ -1533,7 +1538,7 @@ export class RufflePlayer extends HTMLElement {
flavor: isExtension ? "extension" : "",
version: buildInfo.versionName,
}),
onClick() {
async onClick() {
window.open(RUFFLE_ORIGIN, "_blank");
},
});
Expand All @@ -1543,7 +1548,9 @@ export class RufflePlayer extends HTMLElement {
addSeparator();
items.push({
text: text("context-menu-hide"),
onClick: () => (this.contextMenuForceDisabled = true),
onClick: async () => {
this.contextMenuForceDisabled = true;
},
});
}
return items;
Expand Down Expand Up @@ -1663,7 +1670,17 @@ export class RufflePlayer extends HTMLElement {
if (enabled !== false) {
menuItem.addEventListener(
this.contextMenuSupported ? "click" : "pointerup",
onClick,
async (event: MouseEvent) => {
// Prevent the menu from being destroyed.
// It's required when we're dealing with async callbacks,
// as the async callback may still use the menu in the future.
event.stopPropagation();

await onClick(event);

// Then we have to close the context menu manually after the callback finishes.
this.hideContextMenu();
},
);
} else {
menuItem.classList.add("disabled");
Expand Down Expand Up @@ -2354,6 +2371,18 @@ export class RufflePlayer extends HTMLElement {
}
}

protected displayClipboardModal(accessDenied: boolean): void {
const description = this.clipboardModal.querySelector(
"#clipboard-modal-description",
);
if (description) {
description.textContent = text("clipboard-message-description", {
variant: accessDenied ? "access-denied" : "unsupported",
});
this.clipboardModal.classList.remove("hidden");
}
}

protected debugPlayerInfo(): string {
return "";
}
Expand Down
45 changes: 45 additions & 0 deletions web/packages/core/src/shadow-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,36 @@ hardwareModalLink.target = "_blank";
hardwareModalLink.className = "acceleration-link";
hardwareModalLink.textContent = text("enable-hardware-acceleration");

// Clipboard message
const clipboardModal = createElement("div", "clipboard-modal", "modal hidden");
const clipboardModalArea = createElement("div", undefined, "modal-area");
const clipboardModalClose = createElement("span", undefined, "close-modal");
clipboardModalClose.textContent = "\u00D7";
const clipboardModalHeading = createElement("h2", undefined);
clipboardModalHeading.textContent = text("clipboard-message-title");
const clipboardModalTextDescription = createElement(
"p",
"clipboard-modal-description",
);
const shortcutModifier = navigator.userAgent.includes("Mac OS X")
? "Command"
: "Ctrl";
const clipboardModalTextCopy = createElement("p", undefined);
const clipboardModalTextCopyShortcut = createElement("b", undefined);
clipboardModalTextCopyShortcut.textContent = `${shortcutModifier}+C`;
const clipboardModalTextCopyText = createElement("span", undefined);
clipboardModalTextCopyText.textContent = text("clipboard-message-copy");
const clipboardModalTextCut = createElement("p", undefined);
const clipboardModalTextCutShortcut = createElement("b", undefined);
clipboardModalTextCutShortcut.textContent = `${shortcutModifier}+X`;
const clipboardModalTextCutText = createElement("span", undefined);
clipboardModalTextCutText.textContent = text("clipboard-message-cut");
const clipboardModalTextPaste = createElement("p", undefined);
const clipboardModalTextPasteShortcut = createElement("b", undefined);
clipboardModalTextPasteShortcut.textContent = `${shortcutModifier}+V`;
const clipboardModalTextPasteText = createElement("span", undefined);
clipboardModalTextPasteText.textContent = text("clipboard-message-paste");

// Context menu overlay elements
const contextMenuOverlay = createElement(
"div",
Expand Down Expand Up @@ -888,6 +918,21 @@ appendElement(ruffleShadowTemplate.content, hardwareModal);
appendElement(hardwareModal, hardwareModalArea);
appendElement(hardwareModalArea, hardwareModalClose);
appendElement(hardwareModalArea, hardwareModalLink);
// Clipboard modal append
appendElement(ruffleShadowTemplate.content, clipboardModal);
appendElement(clipboardModal, clipboardModalArea);
appendElement(clipboardModalArea, clipboardModalClose);
appendElement(clipboardModalArea, clipboardModalHeading);
appendElement(clipboardModalArea, clipboardModalTextDescription);
appendElement(clipboardModalArea, clipboardModalTextCopy);
appendElement(clipboardModalTextCopy, clipboardModalTextCopyShortcut);
appendElement(clipboardModalTextCopy, clipboardModalTextCopyText);
appendElement(clipboardModalArea, clipboardModalTextCut);
appendElement(clipboardModalTextCut, clipboardModalTextCutShortcut);
appendElement(clipboardModalTextCut, clipboardModalTextCutText);
appendElement(clipboardModalArea, clipboardModalTextPaste);
appendElement(clipboardModalTextPaste, clipboardModalTextPasteShortcut);
appendElement(clipboardModalTextPaste, clipboardModalTextPasteText);
// Context menu overlay append
appendElement(ruffleShadowTemplate.content, contextMenuOverlay);
appendElement(contextMenuOverlay, contextMenu);
9 changes: 9 additions & 0 deletions web/packages/core/texts/en-US/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ enable-hardware-acceleration = It looks like hardware acceleration is not enable
view-error-details = View Error Details
open-in-new-tab = Open in a new tab
click-to-unmute = Click to unmute
clipboard-message-title = Copying and pasting in Ruffle
clipboard-message-description =
{ $variant ->
*[unsupported] Your browser does not support full clipboard access,
[access-denied] Access to the clipboard has been denied,
} but you can always use these shortcuts instead:
clipboard-message-copy = { " " } for copy
clipboard-message-cut = { " " } for cut
clipboard-message-paste = { " " } for paste
error-file-protocol =
It appears you are running Ruffle on the "file:" protocol.
This doesn't work as browsers block many features from working for security reasons.
Expand Down
2 changes: 1 addition & 1 deletion web/packages/core/tools/build_wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ function buildWasm(
extensions: boolean,
wasmSource: string,
) {
const rustFlags = ["-Aunknown_lints"];
const rustFlags = ["--cfg=web_sys_unstable_apis", "-Aunknown_lints"];
const wasmBindgenFlags = [];
const wasmOptFlags = [];
const flavor = extensions ? "extensions" : "vanilla";
Expand Down
Loading