Skip to content

Commit

Permalink
Add menu item for opening dedicated window (#1589)
Browse files Browse the repository at this point in the history
Part of #728.

This PR adds an item to the “View” menu, which is labelled “Dedicated
Window”, and which opens the remote screen in “standalone” view mode in
a popup window.


https://github.com/tiny-pilot/tinypilot/assets/83721279/f6bdbdf5-5387-4f6a-8401-9b33f92fb5cb

## Notes

- As discussed in [this issue
comment](#728 (comment)),
we decided to use a rather pragmatic and slightly unconventional
approach for hiding the remote screen from the main (parent) window, by
issuing an external redirect to a separate page. A “clean” and proper
teardown mechanism, which would allow us to stay on the main page, would
probably be rather complex to build, and also brittle to maintain.
- I’ve debated whether to use an `btn-action` (blue) or a `btn-success`
(green) for the “Refresh Page” button. I eventually went with the
former, since the action is optional, and the button is only a
convenience.
<a data-ca-tag
href="https://codeapprove.com/pr/tiny-pilot/tinypilot/1589"><img
src="https://codeapprove.com/external/github-tag-allbg.png" alt="Review
on CodeApprove" /></a>
  • Loading branch information
jotaen4tinypilot authored Aug 28, 2023
1 parent c0d8e10 commit fe10492
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 0 deletions.
12 changes: 12 additions & 0 deletions app/static/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,18 @@ menuBar.addEventListener("keystroke-history-toggled", () => {
menuBar.addEventListener("keyboard-visibility-toggled", () => {
onScreenKeyboard.show(!onScreenKeyboard.isShown());
});
menuBar.addEventListener("dedicated-window-requested", () => {
// Open popup window in standalone view mode (without menu bar or status bar).
window.open("/?viewMode=standalone", undefined, "popup=true");

// Redirect the user to a placeholder page. We can’t keep the main window
// open as is, because then we’d have a duplicate video stream (which would
// result in twice the bandwidth consumption), or we’d risk inconsistent view
// state, or ambiguous control flows between the two windows.
// Leaving the main page via an external redirect is a rather pragmatic yet
// effective approach to ensure proper teardown of the main window resources.
window.location = "/dedicated-window-placeholder";
});
menuBar.addEventListener("shutdown-dialog-requested", () => {
document.getElementById("shutdown-overlay").show();
});
Expand Down
9 changes: 9 additions & 0 deletions app/templates/custom-elements/menu-bar.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
user-select: none;
}

:host([logo-only]) ul {
display: none;
}

.logo {
position: relative;
box-sizing: border-box;
Expand Down Expand Up @@ -265,6 +269,11 @@
>Full Screen</a
>
</li>
<li class="item" role="presentation">
<a data-onclick-event="dedicated-window-requested" role="menuitem"
>Dedicated Window</a
>
</li>
</ul>
</li>

Expand Down
56 changes: 56 additions & 0 deletions app/templates/dedicated-window-placeholder.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script>
// Reset the actual URL path of this page to `/`, to make it look and feel
// as if the user was still on the main app page. The fact that we issued
// an external redirect should be kept away from the user as good as
// possible, so e.g. the real path of this page also shouldn’t appear in
// the browser history. As an additional benefit, the user is able to
// restore the main app UI by simply triggering a page refresh.
// In order to avoid initial flickering in the address bar, the URL
// replacement operation has to happen as first thing while the browser
// parses this HTML document (in any event prior to fetching assets).
window.history.replaceState(null, null, "/");
</script>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ page_title_prefix }}TinyPilot</title>
<link rel="stylesheet" type="text/css" href="/css/style.css" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
text-align: center;
}

@media (prefers-color-scheme: dark) {
body {
color: var(--brand-creme-light);
}
}
</style>
</head>
<body>
{% include 'custom-elements/menu-bar.html' %}

<div class="header-bar">
<!-- Display a non-interactive, stripped down version of the menu bar,
to make the page look like the main app UI. -->
<menu-bar id="menu-bar" logo-only></menu-bar>
</div>

<p>
The remote screen is displaying in another browser window.<br />
To restore the remote screen in this window, please refresh the page.
</p>
<button class="btn-action" onclick="window.location.reload()">
Refresh Page
</button>
</body>
</html>
6 changes: 6 additions & 0 deletions app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ def styleguide_get():
return flask.abort(404)


@views_blueprint.route('/dedicated-window-placeholder', methods=['GET'])
def dedicated_window_placeholder_get():
return flask.render_template('dedicated-window-placeholder.html',
page_title_prefix=_page_title_prefix())


# On a real install, nginx redirects the /stream route to uStreamer, so a real
# user should never hit this route in production. In development, show a fake
# still image to give a better sense of how the TinyPilot UI looks.
Expand Down

0 comments on commit fe10492

Please sign in to comment.