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
40 changes: 0 additions & 40 deletions apps/roam/src/components/settings/BlockPropFeatureFlagPanel.tsx

This file was deleted.

7 changes: 4 additions & 3 deletions apps/roam/src/components/settings/GeneralSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import TextPanel from "roamjs-components/components/ConfigPanels/TextPanel";
import { getFormattedConfigTree } from "~/utils/discourseConfigRef";
import refreshConfigTree from "~/utils/refreshConfigTree";
import { DEFAULT_CANVAS_PAGE_FORMAT } from "~/index";
import { TOP_LEVEL_BLOCK_PROP_KEYS } from "~/data/blockPropsSettingsConfig";
import { Alert, Intent } from "@blueprintjs/core";
import { BlockPropFeatureFlagPanel } from "./BlockPropFeatureFlagPanel";
import { FlagPanel } from "./block-prop/FlagPanel";

const DiscourseGraphHome = () => {
const settings = useMemo(() => {
Expand Down Expand Up @@ -33,10 +34,10 @@ const DiscourseGraphHome = () => {
value={settings.canvasPageFormat.value}
defaultValue={DEFAULT_CANVAS_PAGE_FORMAT}
/>
<BlockPropFeatureFlagPanel
<FlagPanel
title="(BETA) Left Sidebar"
description="Whether or not to enable the left sidebar."
featureKey="Enable Left Sidebar"
flag={[TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags, "Enable Left Sidebar"]}
/>
<Alert
isOpen={isAlertOpen}
Expand Down
82 changes: 82 additions & 0 deletions apps/roam/src/components/settings/block-prop/FlagPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { useMemo, useState } from "react";
import { Checkbox } from "@blueprintjs/core";
import Description from "roamjs-components/components/Description";
import idToTitle from "roamjs-components/util/idToTitle";
import {
getFeatureFlag,
getGlobalSetting,
setFeatureFlag,
setGlobalSetting,
} from "~/utils/Settings/accessors";
import { type FeatureFlags } from "~/utils/Settings/zodSchema";
import z from "zod";
import { TOP_LEVEL_BLOCK_PROP_KEYS } from "~/data/blockPropsSettingsConfig";

type FeatureFlagPath = [
typeof TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags,
keyof FeatureFlags,
];

type GlobalSettingPath = [typeof TOP_LEVEL_BLOCK_PROP_KEYS.global, ...string[]];

type Props = {
title: string;
description: string;
disabled?: boolean;
flag: FeatureFlagPath | GlobalSettingPath;
};

const getAdapter = (flag: FeatureFlagPath | GlobalSettingPath) => {
const [root, ...rest] = flag;

if (root === TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags) {
const key = rest[0] as keyof FeatureFlags;
return {
getValue: () => getFeatureFlag(key),
setValue: (checked: boolean) => setFeatureFlag(key, checked),
};
}

if (root === TOP_LEVEL_BLOCK_PROP_KEYS.global) {
return {
getValue: () => {
const current = getGlobalSetting(rest);
const parsed = z.boolean().safeParse(current);
return parsed.success ? parsed.data : false;
},
setValue: (checked: boolean) => setGlobalSetting(rest, checked),
};
}

return {
getValue: () => false,
setValue: (checked: boolean) => {
console.warn(`Unknown flag root: ${root} - received value: ${checked}`);
},
};
};

export const FlagPanel = ({ title, description, disabled, flag }: Props) => {
const adapter = useMemo(() => getAdapter(flag), [flag]);
const [value, setLocalValue] = useState<boolean>(() => adapter.getValue());

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { checked } = e.target;
adapter.setValue(checked);
setLocalValue(checked);
};

return (
<Checkbox
checked={value}
disabled={disabled}
onChange={handleChange}
labelElement={
<>
{idToTitle(title)}
<Description description={description} />
</>
}
/>
);
};
41 changes: 37 additions & 4 deletions apps/roam/src/utils/Settings/accessors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import getBlockProps, { type json } from "../getBlockProps";
import getBlockUidByTextOnPage from "roamjs-components/queries/getBlockUidByTextOnPage";
import setBlockProps from "../setBlockProps";
import { DG_BLOCK_PROP_SETTINGS_PAGE_TITLE, TOP_LEVEL_BLOCK_PROP_KEYS } from "~/data/blockPropsSettingsConfig";
import {
DG_BLOCK_PROP_SETTINGS_PAGE_TITLE,
TOP_LEVEL_BLOCK_PROP_KEYS,
} from "~/data/blockPropsSettingsConfig";
import z from "zod";
import { FeatureFlags, FeatureFlagsSchema } from "./zodSchema";
import {
FeatureFlags,
FeatureFlagsSchema,
GlobalSettingsSchema,
} from "./zodSchema";

const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);

export const getBlockPropBasedSettings = ({
keys,
Expand Down Expand Up @@ -103,7 +113,6 @@ export const setBlockPropBasedSettings = ({
setBlockProps(blockUid, updatedProps, true);
};


export const getFeatureFlag = (key: keyof FeatureFlags): boolean => {
const featureFlagKey = TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags;

Expand All @@ -128,4 +137,28 @@ export const setFeatureFlag = (
keys: [featureFlagKey, key],
value: validatedValue,
});
};
};

export const getGlobalSetting = (keys: string[]): unknown => {
const globalKey = TOP_LEVEL_BLOCK_PROP_KEYS.global;

const { blockProps } = getBlockPropBasedSettings({
keys: [globalKey],
});

const settings = GlobalSettingsSchema.parse(blockProps || {});

return keys.reduce<unknown>((current, key) => {
if (!isRecord(current) || !(key in current)) return undefined;
return current[key];
}, settings);
};

export const setGlobalSetting = (keys: string[], value: json): void => {
const globalKey = TOP_LEVEL_BLOCK_PROP_KEYS.global;

void setBlockPropBasedSettings({
keys: [globalKey, ...keys],
value,
});
};
6 changes: 3 additions & 3 deletions apps/roam/src/utils/Settings/zodSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ export const FeatureFlagsSchema = z.object({

export const GlobalSettingsSchema = z.object({
"Left Sidebar": z.object({
Children: z.array(z.string()),
Children: z.array(z.string()).default([]),
Settings: z.object({
Foldable: z.boolean(),
"Truncate at": z.number().int(),
Collapsable: z.boolean().default(false),
Folded: z.boolean().default(false),
}),
}),
});
Expand Down