mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Make receiver selection list scrollable/searchable
This commit is contained in:
13
ext/package-lock.json
generated
13
ext/package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"esbuild": "^0.14.38",
|
"esbuild": "^0.14.38",
|
||||||
"esbuild-svelte": "^0.7.1",
|
"esbuild-svelte": "^0.7.1",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
|
"fuzzysort": "^2.0.3",
|
||||||
"semver": "^7.3.7",
|
"semver": "^7.3.7",
|
||||||
"svelte": "^3.48.0",
|
"svelte": "^3.48.0",
|
||||||
"svelte-preprocess": "^4.10.6",
|
"svelte-preprocess": "^4.10.6",
|
||||||
@@ -3359,6 +3360,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fuzzysort": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-ridbZloOhbVCom7MDF//Blt4N6d6L4uWNEiBuYBx4Qr+G55A9qItgsAqrRZGgs8q+mFpyBbwi5FL+FWUQPJCew==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/fx-runner": {
|
"node_modules/fx-runner": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.2.0.tgz",
|
||||||
@@ -10296,6 +10303,12 @@
|
|||||||
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
|
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"fuzzysort": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-ridbZloOhbVCom7MDF//Blt4N6d6L4uWNEiBuYBx4Qr+G55A9qItgsAqrRZGgs8q+mFpyBbwi5FL+FWUQPJCew==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"fx-runner": {
|
"fx-runner": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.2.0.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"esbuild": "^0.14.38",
|
"esbuild": "^0.14.38",
|
||||||
"esbuild-svelte": "^0.7.1",
|
"esbuild-svelte": "^0.7.1",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
|
"fuzzysort": "^2.0.3",
|
||||||
"semver": "^7.3.7",
|
"semver": "^7.3.7",
|
||||||
"svelte": "^3.48.0",
|
"svelte": "^3.48.0",
|
||||||
"svelte-preprocess": "^4.10.6",
|
"svelte-preprocess": "^4.10.6",
|
||||||
|
|||||||
16
ext/src/ui/assets/photon_cancel.svg
Normal file
16
ext/src/ui/assets/photon_cancel.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<style>
|
||||||
|
path {
|
||||||
|
fill: rgba(12, 12, 13, .8);
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
path {
|
||||||
|
fill: rgba(249, 249, 250, .8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<path d="M6.586 8l-2.293 2.293a1 1 0 0 0 1.414 1.414L8 9.414l2.293 2.293a1 1 0 0 0 1.414-1.414L9.414 8l2.293-2.293a1 1 0 1 0-1.414-1.414L8 6.586 5.707 4.293a1 1 0 0 0-1.414 1.414L6.586 8zM8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 761 B |
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { afterUpdate, onDestroy, onMount, tick } from "svelte";
|
import { afterUpdate, onDestroy, onMount, tick } from "svelte";
|
||||||
|
import fuzzysort from "fuzzysort";
|
||||||
|
|
||||||
import messaging, { Message, Port } from "../../messaging";
|
import messaging, { Message, Port } from "../../messaging";
|
||||||
import options, { Options } from "../../lib/options";
|
import options, { Options } from "../../lib/options";
|
||||||
@@ -321,8 +322,47 @@
|
|||||||
function openOptionsPage() {
|
function openOptionsPage() {
|
||||||
browser.runtime.openOptionsPage();
|
browser.runtime.openOptionsPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Search input element. */
|
||||||
|
let searchInput: HTMLInputElement | undefined;
|
||||||
|
/** Current search term. */
|
||||||
|
let searchTerm: string | undefined;
|
||||||
|
let isSearching = false;
|
||||||
|
/** Results of current search term. */
|
||||||
|
let searchResults: Fuzzysort.KeyResults<ReceiverDevice> | undefined;
|
||||||
|
|
||||||
|
async function handleKeyDown(ev: KeyboardEvent) {
|
||||||
|
if (ev.key === "Escape") {
|
||||||
|
handleSearchClear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isSearching && ev.key.length === 1) {
|
||||||
|
isSearching = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
searchInput?.focus();
|
||||||
|
}
|
||||||
|
function handleSearchInput() {
|
||||||
|
// Clear search on empty string
|
||||||
|
if (!searchTerm) {
|
||||||
|
handleSearchClear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchResults = fuzzysort.go(searchTerm, devices, {
|
||||||
|
key: "friendlyName"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSearchClear() {
|
||||||
|
isSearching = false;
|
||||||
|
searchTerm = undefined;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={handleKeyDown} />
|
||||||
|
|
||||||
{#if !isBridgeCompatible}
|
{#if !isBridgeCompatible}
|
||||||
<div class="banner banner--warn">
|
<div class="banner banner--warn">
|
||||||
{_("popupBridgeErrorBanner")}
|
{_("popupBridgeErrorBanner")}
|
||||||
@@ -379,8 +419,56 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if isSearching}
|
||||||
|
<div class="search">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="search-input"
|
||||||
|
bind:this={searchInput}
|
||||||
|
bind:value={searchTerm}
|
||||||
|
on:input={handleSearchInput}
|
||||||
|
title={_("popupSearch")}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="search-clear ghost"
|
||||||
|
title={_("popupSearchClear")}
|
||||||
|
on:click={handleSearchClear}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<ul class="receiver-list">
|
<ul class="receiver-list">
|
||||||
{#if !devices.length}
|
{#if searchTerm && searchResults}
|
||||||
|
{#if !searchResults.length}
|
||||||
|
<div class="receiver-list__not-found">
|
||||||
|
No devices found for "{searchTerm}"
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#each searchResults as result}
|
||||||
|
{@const device = devices.find(
|
||||||
|
device => device.id === result.obj.id
|
||||||
|
)}
|
||||||
|
|
||||||
|
{#if device}
|
||||||
|
<Receiver
|
||||||
|
{opts}
|
||||||
|
{port}
|
||||||
|
{device}
|
||||||
|
{result}
|
||||||
|
{connectedSessionIds}
|
||||||
|
{isMediaTypeAvailable}
|
||||||
|
isAnyMediaTypeAvailable={availableMediaTypes !==
|
||||||
|
ReceiverSelectorMediaType.None &&
|
||||||
|
isDeviceCompatible(mediaType, device)}
|
||||||
|
isAnyConnecting={isConnecting}
|
||||||
|
bind:lastMenuShownDeviceId
|
||||||
|
on:cast={ev => onReceiverCast(ev.detail.device)}
|
||||||
|
on:stop={ev => onReceiverStop(ev.detail.device)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{:else if !devices.length}
|
||||||
<div class="receiver-list__not-found">
|
<div class="receiver-list__not-found">
|
||||||
{_("popupNoReceiversFound")}
|
{_("popupNoReceiversFound")}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onMount } from "svelte";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
|
import fuzzysort from "fuzzysort";
|
||||||
|
|
||||||
import type { Options } from "../../lib/options";
|
import type { Options } from "../../lib/options";
|
||||||
|
|
||||||
@@ -39,6 +40,9 @@
|
|||||||
export let device: ReceiverDevice;
|
export let device: ReceiverDevice;
|
||||||
export let connectedSessionIds: string[];
|
export let connectedSessionIds: string[];
|
||||||
|
|
||||||
|
/** Result object if this receiver is displayed in a search results list. */
|
||||||
|
export let result: Nullable<Fuzzysort.KeyResult<ReceiverDevice>> = null;
|
||||||
|
|
||||||
export let opts: Nullable<Options>;
|
export let opts: Nullable<Options>;
|
||||||
|
|
||||||
/** Current receiver application (if available) */
|
/** Current receiver application (if available) */
|
||||||
@@ -402,7 +406,12 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<li class="receiver" bind:this={receiverElement} on:contextmenu={onContextMenu}>
|
<li
|
||||||
|
class="receiver"
|
||||||
|
class:receiver--result={!!result}
|
||||||
|
bind:this={receiverElement}
|
||||||
|
on:contextmenu={onContextMenu}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
class="receiver__icon"
|
class="receiver__icon"
|
||||||
src="icons/{device.capabilities & ReceiverDeviceCapabilities.VIDEO_OUT
|
src="icons/{device.capabilities & ReceiverDeviceCapabilities.VIDEO_OUT
|
||||||
@@ -414,7 +423,11 @@
|
|||||||
/>
|
/>
|
||||||
<div class="receiver__details">
|
<div class="receiver__details">
|
||||||
<div class="receiver__name">
|
<div class="receiver__name">
|
||||||
{device.friendlyName}
|
{#if result}
|
||||||
|
{@html fuzzysort.highlight(result)}
|
||||||
|
{:else}
|
||||||
|
{device.friendlyName}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if application && !application.isIdleScreen}
|
{#if application && !application.isIdleScreen}
|
||||||
<div class="receiver__status">
|
<div class="receiver__status">
|
||||||
|
|||||||
@@ -83,9 +83,36 @@ body {
|
|||||||
margin-inline-start: 0.5em;
|
margin-inline-start: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
display: flex;
|
||||||
|
padding: 1em;
|
||||||
|
position: relative;
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
.search-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.search-clear {
|
||||||
|
background-color: transparent !important;
|
||||||
|
background-image: url("../../assets/photon_cancel.svg");
|
||||||
|
background-size: 50%;
|
||||||
|
margin-right: 1em;
|
||||||
|
opacity: 0.5;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
.search-clear:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.receiver-list {
|
.receiver-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: initial;
|
margin: initial;
|
||||||
|
height: auto;
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
padding: 0 1em;
|
padding: 0 1em;
|
||||||
padding-bottom: 0.25em;
|
padding-bottom: 0.25em;
|
||||||
}
|
}
|
||||||
@@ -108,6 +135,9 @@ body {
|
|||||||
padding: 0.75em 0;
|
padding: 0.75em 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.receiver--result .receiver__name > b {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
.receiver:not(:last-child) {
|
.receiver:not(:last-child) {
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user