Skip to content

Commit bcc904c

Browse files
committed
frontend/account: add "Accessibility" setting (for now just boolean) and a query parameter.
1 parent f5f0ddf commit bcc904c

File tree

8 files changed

+162
-10
lines changed

8 files changed

+162
-10
lines changed

src/packages/frontend/account/account-preferences-appearance.tsx

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
LabeledRow,
1919
} from "@cocalc/frontend/components";
2020
import { labels } from "@cocalc/frontend/i18n";
21-
import { DARK_MODE_ICON } from "@cocalc/util/consts/ui";
21+
import { ACCESSIBILITY_ICON, DARK_MODE_ICON } from "@cocalc/util/consts/ui";
2222
import { DARK_MODE_DEFAULTS } from "@cocalc/util/db-schema/accounts";
2323
import { COLORS } from "@cocalc/util/theme";
2424
import {
@@ -62,6 +62,18 @@ const DARK_MODE_LABELS = defineMessages({
6262
},
6363
});
6464

65+
const ACCESSIBILITY_MESSAGES = defineMessages({
66+
title: {
67+
id: "account.appearance.accessibility.title",
68+
defaultMessage: "Accessibility",
69+
},
70+
enabled: {
71+
id: "account.appearance.accessibility.enabled",
72+
defaultMessage:
73+
"<strong>Enable Accessibility Mode:</strong> optimize the user interface for accessibility features",
74+
},
75+
});
76+
6577
export function AccountPreferencesAppearance() {
6678
const intl = useIntl();
6779
const other_settings = useTypedRedux("account", "other_settings");
@@ -107,6 +119,46 @@ export function AccountPreferencesAppearance() {
107119
);
108120
}
109121

122+
function getAccessibilitySettings(): { enabled: boolean } {
123+
const settingsStr = other_settings.get("accessibility");
124+
if (!settingsStr) {
125+
return { enabled: false };
126+
}
127+
try {
128+
return JSON.parse(settingsStr);
129+
} catch {
130+
return { enabled: false };
131+
}
132+
}
133+
134+
function setAccessibilitySettings(settings: { enabled: boolean }): void {
135+
on_change("accessibility", JSON.stringify(settings));
136+
}
137+
138+
function renderAccessibilityPanel(): ReactElement {
139+
const settings = getAccessibilitySettings();
140+
return (
141+
<Panel
142+
size="small"
143+
header={
144+
<>
145+
<Icon unicode={ACCESSIBILITY_ICON} />{" "}
146+
{intl.formatMessage(ACCESSIBILITY_MESSAGES.title)}
147+
</>
148+
}
149+
>
150+
<Switch
151+
checked={settings.enabled}
152+
onChange={(e) =>
153+
setAccessibilitySettings({ ...settings, enabled: e.target.checked })
154+
}
155+
>
156+
<FormattedMessage {...ACCESSIBILITY_MESSAGES.enabled} />
157+
</Switch>
158+
</Panel>
159+
);
160+
}
161+
110162
function renderDarkModePanel(): ReactElement {
111163
const checked = !!other_settings.get("dark_mode");
112164
const config = get_dark_mode_config(other_settings.toJS());
@@ -303,6 +355,7 @@ export function AccountPreferencesAppearance() {
303355
mode="appearance"
304356
/>
305357
{renderDarkModePanel()}
358+
{renderAccessibilityPanel()}
306359
<EditorSettingsColorScheme
307360
size="small"
308361
theme={editor_settings?.get("theme") ?? "default"}

src/packages/frontend/account/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export interface AccountState {
6868
use_balance_toward_subscriptions?: boolean;
6969
show_symbol_bar_labels?: boolean; // whether to show labels on the menu buttons
7070
[ACTIVITY_BAR_LABELS]?: boolean; // whether to show labels on the vertical activity bar
71+
accessibility?: string; // string is JSON: { enabled: boolean }
7172
}>;
7273
stripe_customer?: TypedMap<{
7374
subscriptions: { data: Map<string, any> };

src/packages/frontend/app/context.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,18 @@ export function useAntdStyleProvider() {
8686
const branded = other_settings?.get("antd_brandcolors", false);
8787
const compact = other_settings?.get("antd_compact", false);
8888

89+
// Parse accessibility settings
90+
const accessibilityStr = other_settings?.get("accessibility");
91+
let accessibilityEnabled = false;
92+
if (accessibilityStr) {
93+
try {
94+
const accessibilitySettings = JSON.parse(accessibilityStr);
95+
accessibilityEnabled = accessibilitySettings.enabled ?? false;
96+
} catch {
97+
// Ignore parse errors
98+
}
99+
}
100+
89101
const borderStyle = rounded
90102
? undefined
91103
: { borderRadius: 0, borderRadiusLG: 0, borderRadiusSM: 0 };
@@ -96,6 +108,16 @@ export function useAntdStyleProvider() {
96108
? undefined
97109
: { colorPrimary: COLORS.ANTD_LINK_BLUE };
98110

111+
// Accessibility: Set all text to pure black for maximum contrast
112+
const accessibilityTextColor = accessibilityEnabled
113+
? {
114+
colorText: "#000000",
115+
colorTextSecondary: "#000000",
116+
colorTextTertiary: "#000000",
117+
colorTextQuaternary: "#000000",
118+
}
119+
: undefined;
120+
99121
const algorithm = compact ? { algorithm: theme.compactAlgorithm } : undefined;
100122

101123
const antdTheme: ThemeConfig = {
@@ -106,6 +128,7 @@ export function useAntdStyleProvider() {
106128
...primaryColor,
107129
...borderStyle,
108130
...animationStyle,
131+
...accessibilityTextColor,
109132
},
110133
components: {
111134
Button: {

src/packages/frontend/app/init.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { init_ping } from "./monitor-pings";
44
import { init_connection } from "./monitor-connection";
55
import { init_query_params } from "./query-params";
66

7-
export function init() {
7+
export async function init() {
88
init_actions();
99
init_store();
1010
init_ping();
1111
init_connection();
12-
init_query_params();
12+
await init_query_params();
1313
}

src/packages/frontend/app/query-params.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import { QueryParams } from "@cocalc/frontend/misc/query-params";
1717
import { is_valid_uuid_string } from "@cocalc/util/misc";
1818

19-
export function init_query_params(): void {
19+
function init_fullscreen_mode(): void {
2020
const actions = redux.getActions("page");
2121
// enable fullscreen mode upon loading a URL like /app?fullscreen and
2222
// additionally kiosk-mode upon /app?fullscreen=kiosk
@@ -40,13 +40,19 @@ export function init_query_params(): void {
4040
} else if (COCALC_FULLSCREEN === "project") {
4141
actions.set_fullscreen("project");
4242
}
43+
}
4344

45+
function init_api_key(): void {
46+
const actions = redux.getActions("page");
4447
const get_api_key_query_value = QueryParams.get("get_api_key");
4548
if (get_api_key_query_value) {
4649
actions.set_get_api_key(get_api_key_query_value);
4750
actions.set_fullscreen("project");
4851
}
52+
}
4953

54+
function init_session(): void {
55+
const actions = redux.getActions("page");
5056
// configure the session
5157
// This makes it so the default session is 'default' and there is no
5258
// way to NOT have a session, except via session=, which is treated
@@ -79,5 +85,72 @@ export function init_query_params(): void {
7985
// not have session in the URL, so we can share url's without infected
8086
// other user's session.
8187
QueryParams.remove("session");
88+
}
89+
90+
function parse_accessibility_param(param: string): boolean | null {
91+
if (param === "true" || param === "on" || param === "1") {
92+
return true;
93+
}
94+
if (param === "false" || param === "off" || param === "0") {
95+
return false;
96+
}
97+
return null;
98+
}
99+
100+
async function init_accessibility(): Promise<void> {
101+
// Handle accessibility query parameter
102+
// If ?accessibility=true or =on, enable accessibility mode permanently
103+
// If ?accessibility=false or =off, disable it permanently
104+
// This allows sharing URLs that automatically enable accessibility
105+
const accessibilityParam = QueryParams.get("accessibility");
106+
if (accessibilityParam == null) {
107+
return;
108+
}
109+
110+
const enabled = parse_accessibility_param(accessibilityParam);
111+
QueryParams.remove("accessibility");
112+
113+
if (enabled == null) {
114+
return;
115+
}
116+
117+
try {
118+
// Wait for account to be ready before setting accessibility
119+
const store = redux.getStore("account");
120+
await store.async_wait({
121+
until: () => store.get_account_id() != null,
122+
timeout: 0,
123+
});
124+
125+
// Preserve existing accessibility settings
126+
const existingSettingsStr = store.getIn([
127+
"other_settings",
128+
"accessibility",
129+
]);
130+
let existingSettings = { enabled: false };
131+
if (existingSettingsStr) {
132+
try {
133+
existingSettings = JSON.parse(existingSettingsStr);
134+
} catch {
135+
// Ignore parse errors, use default
136+
}
137+
}
138+
139+
// Merge with new enabled value
140+
const settings = { ...existingSettings, enabled };
141+
const accountActions = redux.getActions("account");
142+
accountActions.set_other_settings(
143+
"accessibility",
144+
JSON.stringify(settings),
145+
);
146+
} catch (err) {
147+
console.warn("Failed to set accessibility from query param:", err);
148+
}
149+
}
82150

151+
export async function init_query_params(): Promise<void> {
152+
init_fullscreen_mode();
153+
init_api_key();
154+
init_session();
155+
await init_accessibility();
83156
}

src/packages/frontend/embed/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import { init as initMarkdown } from "../markdown/markdown-input/main";
2323
import { init as initCrashBanner } from "../crash-banner";
2424
import { init as initCustomize } from "../customize";
2525

26-
2726
// Do not delete this without first looking at https://github.com/sagemathinc/cocalc/issues/5390
2827
// This import of codemirror forces the initial full load of codemirror
2928
// as part of the main webpack entry point.
@@ -34,7 +33,7 @@ import { render } from "../app/render";
3433

3534
export async function init() {
3635
initAccount(redux);
37-
initApp();
36+
await initApp();
3837
initProjects();
3938
initMarkdown();
4039
initCustomize();

src/packages/frontend/entry-point.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { COCALC_MINIMAL } from "./fullscreen";
1515
// Load/initialize Redux-based react functionality
1616
import { redux } from "./app-framework";
1717

18-
// Systemwide notifications that are broadcast to all users (and set by admins)
18+
// system-wide notifications that are broadcast to all users (and set by admins)
1919
import "./system-notifications";
2020

2121
// News about the platform, features, etc. – also shown at https://$DNS/news
@@ -56,7 +56,7 @@ import { render } from "./app/render";
5656
export async function init() {
5757
initJqueryPlugins();
5858
initAccount(redux);
59-
initApp();
59+
await initApp();
6060
initProjects();
6161
initCustomSoftware();
6262
initFileUse();

src/packages/util/consts/ui.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ export const R_IDE = "R IDE";
3333
// Default font size for account settings and UI elements
3434
export const DEFAULT_FONT_SIZE = 14;
3535

36-
// Icon unicode character for dark mode toggle (◑ - circle with right half black)
37-
export const DARK_MODE_ICON = 0x25d1;
36+
// Icon unicode character for dark mode toggle (☽ - first quarter moon)
37+
export const DARK_MODE_ICON = 0x263d;
38+
39+
// Icon unicode character for accessibility (♿ - wheelchair symbol)
40+
export const ACCESSIBILITY_ICON = 0x267f;
3841

3942
// Icon unicode characters for auto-sync arrows in LaTeX editor
4043
export const SYNC_FORWARD_ICON = 0x21a6; // ↦ - rightwards arrow $mapto$

0 commit comments

Comments
 (0)