Skip to content

Commit da07707

Browse files
Add a settings link to the footer with i18n options & pwa instructions (#10254)
* changes * add changeset * changes * changes * restore * changes * changes * changes * changes * add changeset * changes * changes * changes * format * changes * changes * changes * changes * add guard * changes * more changes * format frontend * add changeset * add more translations * format * changes * spacing * changes * format --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
1 parent f3bedd4 commit da07707

36 files changed

+2359
-200
lines changed

.changeset/orange-bugs-send.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@gradio/client": minor
3+
"@gradio/core": minor
4+
"gradio": minor
5+
---
6+
7+
feat:Add a `settings` link to the footer with i18n options & pwa instructions

client/js/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ export interface Config {
186186
api_prefix?: string;
187187
fill_height?: boolean;
188188
fill_width?: boolean;
189+
pwa?: boolean;
189190
}
190191

191192
// todo: DRY up types

gradio/blocks.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,7 @@ def __init__(
10831083
self.renderables: list[Renderable] = []
10841084
self.state_holder: StateHolder
10851085
self.custom_mount_path: str | None = None
1086+
self.pwa = False
10861087

10871088
# For analytics_enabled and allow_flagging: (1) first check for
10881089
# parameter, (2) check for env variable, (3) default to True/"manual"
@@ -2171,6 +2172,7 @@ def get_config_file(self) -> BlocksConfigDict:
21712172
"fill_height": self.fill_height,
21722173
"fill_width": self.fill_width,
21732174
"theme_hash": self.theme_hash,
2175+
"pwa": self.pwa,
21742176
}
21752177
config.update(self.default_config.get_config()) # type: ignore
21762178
config["connect_heartbeat"] = utils.connect_heartbeat(
@@ -2450,9 +2452,10 @@ def reverse(text):
24502452
if block.key is None:
24512453
block.key = f"__{block._id}__"
24522454

2453-
self.config = self.get_config_file()
2455+
self.pwa = utils.get_space() is not None if pwa is None else pwa
24542456
self.max_threads = max_threads
24552457
self._queue.max_thread_count = max_threads
2458+
self.config = self.get_config_file()
24562459

24572460
self.ssr_mode = (
24582461
False
@@ -2532,7 +2535,6 @@ def reverse(text):
25322535
"http" if share_server_address is not None else "https"
25332536
)
25342537
self.has_launched = True
2535-
self.pwa = utils.get_space() is not None if pwa is None else pwa
25362538

25372539
self.protocol = (
25382540
"https"

gradio/data_classes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ class BlocksConfigDict(TypedDict):
383383
root: NotRequired[str | None]
384384
username: NotRequired[str | None]
385385
api_prefix: str
386+
pwa: NotRequired[bool]
386387

387388

388389
class MediaStreamChunk(TypedDict):

js/core/src/Blocks.svelte

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import type { ComponentMeta, Dependency, LayoutNode } from "./types";
99
import type { UpdateTransaction } from "./init";
1010
import { setupi18n } from "./i18n";
11-
import { ApiDocs, ApiRecorder } from "./api_docs/";
11+
import { ApiDocs, ApiRecorder, Settings } from "./api_docs/";
1212
import type { ThemeMode, Payload } from "./types";
1313
import { Toast } from "@gradio/statustracker";
1414
import type { ToastMessage } from "@gradio/statustracker";
@@ -18,6 +18,7 @@
1818
1919
import logo from "./images/logo.svg";
2020
import api_logo from "./api_docs/img/api-logo.svg";
21+
import settings_logo from "./api_docs/img/settings-logo.svg";
2122
import { create_components, AsyncFunction } from "./init";
2223
import type {
2324
LogMessage,
@@ -85,8 +86,10 @@
8586
8687
export let search_params: URLSearchParams;
8788
let api_docs_visible = search_params.get("view") === "api" && show_api;
89+
let settings_visible = search_params.get("view") === "settings";
8890
let api_recorder_visible =
8991
search_params.get("view") === "api-recorder" && show_api;
92+
9093
function set_api_docs_visible(visible: boolean): void {
9194
api_recorder_visible = false;
9295
api_docs_visible = visible;
@@ -98,6 +101,18 @@
98101
}
99102
history.replaceState(null, "", "?" + params.toString());
100103
}
104+
105+
function set_settings_visible(visible: boolean): void {
106+
let params = new URLSearchParams(window.location.search);
107+
if (visible) {
108+
params.set("view", "settings");
109+
} else {
110+
params.delete("view");
111+
}
112+
history.replaceState(null, "", "?" + params.toString());
113+
settings_visible = !settings_visible;
114+
}
115+
101116
let api_calls: Payload[] = [];
102117
103118
export let render_complete = false;
@@ -758,8 +773,8 @@
758773
>
759774
{$_("errors.use_via_api")}
760775
<img src={api_logo} alt={$_("common.logo")} />
776+
<div>&nbsp;·</div>
761777
</button>
762-
<div>·</div>
763778
{/if}
764779
<a
765780
href="https://gradio.app"
@@ -770,6 +785,16 @@
770785
{$_("common.built_with_gradio")}
771786
<img src={logo} alt={$_("common.logo")} />
772787
</a>
788+
<button
789+
on:click={() => {
790+
set_settings_visible(!settings_visible);
791+
}}
792+
class="settings"
793+
>
794+
<div>· &nbsp;</div>
795+
{$_("common.settings")}
796+
<img src={settings_logo} alt={$_("common.settings")} />
797+
</button>
773798
</footer>
774799
{/if}
775800
</div>
@@ -819,6 +844,30 @@
819844
</div>
820845
{/if}
821846

847+
{#if settings_visible && $_layout && app.config}
848+
<div class="api-docs">
849+
<!-- TODO: fix -->
850+
<!-- svelte-ignore a11y-click-events-have-key-events-->
851+
<!-- svelte-ignore a11y-no-static-element-interactions-->
852+
<div
853+
class="backdrop"
854+
on:click={() => {
855+
set_settings_visible(false);
856+
}}
857+
/>
858+
<div class="api-docs-wrap">
859+
<Settings
860+
on:close={(event) => {
861+
set_settings_visible(false);
862+
}}
863+
pwa_enabled={app.config.pwa}
864+
{root}
865+
{space_id}
866+
/>
867+
</div>
868+
</div>
869+
{/if}
870+
822871
{#if messages}
823872
<Toast {messages} on:close={handle_error_close} />
824873
{/if}
@@ -849,7 +898,8 @@
849898
margin-left: var(--size-2);
850899
}
851900
852-
.show-api {
901+
.show-api,
902+
.settings {
853903
display: flex;
854904
align-items: center;
855905
}
@@ -863,12 +913,19 @@
863913
width: var(--size-3);
864914
}
865915
916+
.settings img {
917+
margin-right: var(--size-1);
918+
margin-left: var(--size-1);
919+
width: var(--size-4);
920+
}
921+
866922
.built-with {
867923
display: flex;
868924
align-items: center;
869925
}
870926
871-
.built-with:hover {
927+
.built-with:hover,
928+
.settings:hover {
872929
color: var(--body-text-color);
873930
}
874931
@@ -923,4 +980,19 @@
923980
bottom: 10px;
924981
z-index: 1000;
925982
}
983+
984+
.show-api {
985+
display: flex;
986+
align-items: center;
987+
}
988+
989+
@media (max-width: 640px) {
990+
.show-api {
991+
display: none;
992+
}
993+
}
994+
995+
.show-api:hover {
996+
color: var(--body-text-color);
997+
}
926998
</style>
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<script lang="ts">
2+
/* eslint-disable */
3+
import { onMount } from "svelte";
4+
import SettingsBanner from "./SettingsBanner.svelte";
5+
export let root: string;
6+
export let space_id: string | null;
7+
export let pwa_enabled: boolean | undefined;
8+
import { BaseDropdown as Dropdown } from "@gradio/dropdown";
9+
import { language_choices, changeLocale } from "../i18n";
10+
import { locale, _ } from "svelte-i18n";
11+
import { setupi18n } from "../i18n";
12+
13+
if (root === "") {
14+
root = location.protocol + "//" + location.host + location.pathname;
15+
}
16+
if (!root.endsWith("/")) {
17+
root += "/";
18+
}
19+
20+
function setTheme(theme: "light" | "dark" | "system") {
21+
const url = new URL(window.location.href);
22+
if (theme === "system") {
23+
url.searchParams.delete("__theme");
24+
current_theme = "system";
25+
} else {
26+
url.searchParams.set("__theme", theme);
27+
current_theme = theme;
28+
}
29+
window.location.href = url.toString();
30+
}
31+
32+
onMount(() => {
33+
document.body.style.overflow = "hidden";
34+
if ("parentIFrame" in window) {
35+
window.parentIFrame?.scrollTo(0, 0);
36+
}
37+
const url = new URL(window.location.href);
38+
const theme = url.searchParams.get("__theme");
39+
current_theme = (theme as "light" | "dark" | "system") || "system";
40+
return () => {
41+
document.body.style.overflow = "auto";
42+
};
43+
});
44+
45+
let current_locale: string;
46+
let current_theme: "light" | "dark" | "system" = "system";
47+
48+
locale.subscribe((value) => {
49+
if (value) {
50+
current_locale = value;
51+
}
52+
});
53+
54+
function handleLanguageChange(e: CustomEvent): void {
55+
const new_locale = e.detail;
56+
changeLocale(new_locale);
57+
}
58+
setupi18n();
59+
</script>
60+
61+
<div class="banner-wrap">
62+
<SettingsBanner on:close {root} />
63+
</div>
64+
{#if space_id === null}
65+
<!-- on Spaces, the theme is set in HF settings -->
66+
<div class="banner-wrap">
67+
<h2>{$_("common.display_theme")}</h2>
68+
<p class="padded theme-buttons">
69+
<li
70+
class="theme-button {current_theme === 'light'
71+
? 'current-theme'
72+
: 'inactive-theme'}"
73+
on:click={() => setTheme("light")}
74+
>
75+
<button>☀︎ &nbsp;Light</button>
76+
</li>
77+
<li
78+
class="theme-button {current_theme === 'dark'
79+
? 'current-theme'
80+
: 'inactive-theme'}"
81+
on:click={() => setTheme("dark")}
82+
>
83+
<button>⏾ &nbsp; Dark</button>
84+
</li>
85+
<li
86+
class="theme-button {current_theme === 'system'
87+
? 'current-theme'
88+
: 'inactive-theme'}"
89+
on:click={() => setTheme("system")}
90+
>
91+
<button>🖥︎ &nbsp;System</button>
92+
</li>
93+
</p>
94+
</div>
95+
{/if}
96+
<div class="banner-wrap">
97+
<h2>{$_("common.language")}</h2>
98+
<p class="padded">
99+
<Dropdown
100+
label="Language"
101+
choices={language_choices}
102+
show_label={false}
103+
{root}
104+
value={current_locale}
105+
on:change={handleLanguageChange}
106+
/>
107+
</p>
108+
</div>
109+
<div class="banner-wrap">
110+
<h2>{$_("common.pwa")}</h2>
111+
<p class="padded">
112+
{#if pwa_enabled}
113+
You can install this app as a Progressive Web App on your device. Visit <a
114+
href={root}>{root}</a
115+
> and click the install button in the URL address bar of your browser.
116+
{:else}
117+
Progressive Web App is not enabled for this app. To enable it, start your
118+
Gradio app with <code>launch(pwa=True)</code>.
119+
{/if}
120+
</p>
121+
</div>
122+
123+
<style>
124+
.banner-wrap {
125+
position: relative;
126+
border-bottom: 1px solid var(--border-color-primary);
127+
padding: var(--size-4) var(--size-6);
128+
font-size: var(--text-md);
129+
}
130+
131+
.banner-wrap h2 {
132+
font-size: var(--text-xl);
133+
}
134+
135+
a {
136+
text-decoration: underline;
137+
}
138+
139+
p.padded {
140+
padding: 15px 0px;
141+
}
142+
143+
.theme-buttons {
144+
display: flex;
145+
align-items: center;
146+
}
147+
148+
.theme-buttons > * + * {
149+
margin-left: var(--size-2);
150+
}
151+
152+
.theme-button {
153+
display: flex;
154+
align-items: center;
155+
border: 1px solid var(--border-color-primary);
156+
border-radius: var(--radius-md);
157+
padding: var(--size-2) var(--size-2-5);
158+
line-height: 1;
159+
user-select: none;
160+
text-transform: capitalize;
161+
cursor: pointer;
162+
}
163+
164+
.current-theme {
165+
border: 1px solid var(--body-text-color-subdued);
166+
color: var(--body-text-color);
167+
}
168+
169+
.inactive-theme {
170+
color: var(--body-text-color-subdued);
171+
}
172+
173+
.inactive-theme:hover,
174+
.inactive-theme:focus {
175+
box-shadow: var(--shadow-drop);
176+
color: var(--body-text-color);
177+
}
178+
179+
.theme-button button {
180+
all: unset;
181+
cursor: pointer;
182+
}
183+
</style>

0 commit comments

Comments
 (0)