Skip to content
Open
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
59 changes: 58 additions & 1 deletion src/packages/frontend/account/account-preferences-appearance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import {
LabeledRow,
} from "@cocalc/frontend/components";
import { labels } from "@cocalc/frontend/i18n";
import { DARK_MODE_ICON } from "@cocalc/util/consts/ui";
import {
A11Y,
ACCESSIBILITY_ICON,
DARK_MODE_ICON,
} from "@cocalc/util/consts/ui";
import { DARK_MODE_DEFAULTS } from "@cocalc/util/db-schema/accounts";
import { COLORS } from "@cocalc/util/theme";
import {
Expand Down Expand Up @@ -62,6 +66,18 @@ const DARK_MODE_LABELS = defineMessages({
},
});

const ACCESSIBILITY_MESSAGES = defineMessages({
title: {
id: "account.appearance.accessibility.title",
defaultMessage: "Accessibility",
},
enabled: {
id: "account.appearance.accessibility.enabled",
defaultMessage:
"<strong>Enable Accessibility Mode:</strong> optimize the user interface for accessibility features",
},
});

export function AccountPreferencesAppearance() {
const intl = useIntl();
const other_settings = useTypedRedux("account", "other_settings");
Expand Down Expand Up @@ -107,6 +123,46 @@ export function AccountPreferencesAppearance() {
);
}

function getAccessibilitySettings(): { enabled: boolean } {
const settingsStr = other_settings.get(A11Y);
if (!settingsStr) {
return { enabled: false };
}
try {
return JSON.parse(settingsStr);
} catch {
return { enabled: false };
}
}

function setAccessibilitySettings(settings: { enabled: boolean }): void {
on_change(A11Y, JSON.stringify(settings));
}

function renderAccessibilityPanel(): ReactElement {
const settings = getAccessibilitySettings();
return (
<Panel
size="small"
header={
<>
<Icon unicode={ACCESSIBILITY_ICON} />{" "}
{intl.formatMessage(ACCESSIBILITY_MESSAGES.title)}
</>
}
>
<Switch
checked={settings.enabled}
onChange={(e) =>
setAccessibilitySettings({ ...settings, enabled: e.target.checked })
}
>
<FormattedMessage {...ACCESSIBILITY_MESSAGES.enabled} />
</Switch>
</Panel>
);
}

function renderDarkModePanel(): ReactElement {
const checked = !!other_settings.get("dark_mode");
const config = get_dark_mode_config(other_settings.toJS());
Expand Down Expand Up @@ -303,6 +359,7 @@ export function AccountPreferencesAppearance() {
mode="appearance"
/>
{renderDarkModePanel()}
{renderAccessibilityPanel()}
<EditorSettingsColorScheme
size="small"
theme={editor_settings?.get("theme") ?? "default"}
Expand Down
1 change: 1 addition & 0 deletions src/packages/frontend/account/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export interface AccountState {
use_balance_toward_subscriptions?: boolean;
show_symbol_bar_labels?: boolean; // whether to show labels on the menu buttons
[ACTIVITY_BAR_LABELS]?: boolean; // whether to show labels on the vertical activity bar
accessibility?: string; // string is JSON: { enabled: boolean }
}>;
stripe_customer?: TypedMap<{
subscriptions: { data: Map<string, any> };
Expand Down
24 changes: 24 additions & 0 deletions src/packages/frontend/app/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useIntl } from "react-intl";
import { useTypedRedux } from "@cocalc/frontend/app-framework";
import { IntlMessage, isIntlMessage } from "@cocalc/frontend/i18n";
import { ACTIVITY_BAR_LABELS } from "@cocalc/frontend/project/page/activity-bar-consts";
import { A11Y } from "@cocalc/util/consts/ui";
import { COLORS } from "@cocalc/util/theme";
import { getBaseAntdTheme } from "./antd-base-theme";
import { NARROW_THRESHOLD_PX, PageStyle } from "./top-nav-consts";
Expand Down Expand Up @@ -86,6 +87,18 @@ export function useAntdStyleProvider() {
const branded = other_settings?.get("antd_brandcolors", false);
const compact = other_settings?.get("antd_compact", false);

// Parse accessibility settings
const accessibilityStr = other_settings?.get(A11Y);
let accessibilityEnabled = false;
if (accessibilityStr) {
try {
const accessibilitySettings = JSON.parse(accessibilityStr);
accessibilityEnabled = accessibilitySettings.enabled ?? false;
} catch {
// Ignore parse errors
}
}

const borderStyle = rounded
? undefined
: { borderRadius: 0, borderRadiusLG: 0, borderRadiusSM: 0 };
Expand All @@ -96,6 +109,16 @@ export function useAntdStyleProvider() {
? undefined
: { colorPrimary: COLORS.ANTD_LINK_BLUE };

// Accessibility: Set all text to pure black for maximum contrast
const accessibilityTextColor = accessibilityEnabled
? {
colorText: "#000000",
colorTextSecondary: "#000000",
colorTextTertiary: "#000000",
colorTextQuaternary: "#000000",
}
: undefined;

const algorithm = compact ? { algorithm: theme.compactAlgorithm } : undefined;

const antdTheme: ThemeConfig = {
Expand All @@ -106,6 +129,7 @@ export function useAntdStyleProvider() {
...primaryColor,
...borderStyle,
...animationStyle,
...accessibilityTextColor,
},
components: {
Button: {
Expand Down
77 changes: 76 additions & 1 deletion src/packages/frontend/app/query-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import {
set_local_storage,
} from "@cocalc/frontend/misc/local-storage";
import { QueryParams } from "@cocalc/frontend/misc/query-params";
import { A11Y } from "@cocalc/util/consts/ui";
import { is_valid_uuid_string } from "@cocalc/util/misc";

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

function init_api_key(): void {
const actions = redux.getActions("page");
const get_api_key_query_value = QueryParams.get("get_api_key");
if (get_api_key_query_value) {
actions.set_get_api_key(get_api_key_query_value);
actions.set_fullscreen("project");
}
}

function init_session(): void {
const actions = redux.getActions("page");
// configure the session
// This makes it so the default session is 'default' and there is no
// way to NOT have a session, except via session=, which is treated
Expand Down Expand Up @@ -79,5 +86,73 @@ export function init_query_params(): void {
// not have session in the URL, so we can share url's without infected
// other user's session.
QueryParams.remove("session");
}

function parse_accessibility_param(param: string): boolean | null {
if (param === "true" || param === "on" || param === "1") {
return true;
}
if (param === "false" || param === "off" || param === "0") {
return false;
}
return null;
}

async function init_accessibility(): Promise<void> {
// Handle accessibility query parameter
// If ?accessibility=true or =on, enable accessibility mode permanently
// If ?accessibility=false or =off, disable it permanently
// This allows sharing URLs that automatically enable accessibility
const accessibilityParam = QueryParams.get(A11Y);
if (accessibilityParam == null) {
return;
}

const enabled = parse_accessibility_param(accessibilityParam);
QueryParams.remove(A11Y);

if (enabled == null) {
return;
}

try {
// Wait for account store to be ready before setting accessibility
const store = redux.getStore("account");
if (!store || typeof store.async_wait !== "function") {
console.warn("Account store not ready");
return;
}

await store.async_wait({
until: () => store.get_account_id() != null,
timeout: 0,
});

// Preserve existing accessibility settings
const existingSettingsStr = store.getIn(["other_settings", A11Y]);
let existingSettings = { enabled: false };
if (existingSettingsStr) {
try {
existingSettings = JSON.parse(existingSettingsStr);
} catch {
// Ignore parse errors, use default
}
}

// Merge with new enabled value
const settings = { ...existingSettings, enabled };
const accountActions = redux.getActions("account");
accountActions.set_other_settings(A11Y, JSON.stringify(settings));
} catch (err) {
console.warn("Failed to set accessibility from query param:", err);
}
}

export function init_query_params(): void {
init_fullscreen_mode();
init_api_key();
init_session();
// Run accessibility init in background without blocking
// to avoid delaying other store initializations
init_accessibility();
}
1 change: 0 additions & 1 deletion src/packages/frontend/embed/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { init as initMarkdown } from "../markdown/markdown-input/main";
import { init as initCrashBanner } from "../crash-banner";
import { init as initCustomize } from "../customize";


// Do not delete this without first looking at https://github.com/sagemathinc/cocalc/issues/5390
// This import of codemirror forces the initial full load of codemirror
// as part of the main webpack entry point.
Expand Down
2 changes: 1 addition & 1 deletion src/packages/frontend/entry-point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { COCALC_MINIMAL } from "./fullscreen";
// Load/initialize Redux-based react functionality
import { redux } from "./app-framework";

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

// News about the platform, features, etc. – also shown at https://$DNS/news
Expand Down
Loading