diff --git a/apps/client/src/widgets/type_widgets/options/appearance.css b/apps/client/src/widgets/type_widgets/options/appearance.css
new file mode 100644
index 000000000..4cfaa3450
--- /dev/null
+++ b/apps/client/src/widgets/type_widgets/options/appearance.css
@@ -0,0 +1,91 @@
+.old-layout-illustration {
+ width: 170px;
+ height: 130px;
+ border: 1px solid var(--main-border-color);
+ border-radius: 6px;
+ display: flex;
+ background: var(--root-background);
+ overflow: hidden;
+
+ .launcher-pane {
+ width: 10%;
+ background: var(--launcher-pane-vert-background-color);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 1px 0;
+
+ svg {
+ margin-top: 1px;
+ margin-bottom: 5px;
+ }
+
+ .bx {
+ margin: 4px 0;
+ font-size: 12px;
+ opacity: 0.5;
+ }
+ }
+
+ .tree {
+ width: 20%;
+ font-size: 4px;
+ padding: 12px 5px;
+ overflow: hidden;
+ flex-shrink: 0;
+
+ ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ }
+ }
+
+ .main {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+
+ .tab-bar {
+ height: 20px;
+ }
+
+ .content {
+ background-color: var(--main-background-color);
+ flex-grow: 1;
+ border-top-left-radius: 6px;
+ padding: 5px;
+
+ .title-bar {
+ display: flex;
+ align-items: center;
+ font-size: 14px;
+
+ .title {
+ flex-grow: 1;
+ }
+ }
+
+ .ribbon {
+ .bx {
+ font-size: 10px;
+ }
+
+ .ribbon-header {
+ display: flex;
+ }
+
+ .ribbon-body {
+ height: 20px;
+ background-color: rgba(0, 0, 0, 0.05);
+ border-radius: 6px;
+ margin: 1px 0;
+ }
+ }
+
+ .content-inner {
+ font-size: 6px;
+ }
+ }
+ }
+}
diff --git a/apps/client/src/widgets/type_widgets/options/appearance.tsx b/apps/client/src/widgets/type_widgets/options/appearance.tsx
index 809d05e9e..fe6c54746 100644
--- a/apps/client/src/widgets/type_widgets/options/appearance.tsx
+++ b/apps/client/src/widgets/type_widgets/options/appearance.tsx
@@ -1,18 +1,23 @@
+import "./appearance.css";
+
+import { FontFamily, OptionNames } from "@triliumnext/commons";
import { useEffect, useState } from "preact/hooks";
+
import { t } from "../../../services/i18n";
-import { isElectron, isMobile, reloadFrontendApp, restartDesktopApp } from "../../../services/utils";
-import Column from "../../react/Column";
-import FormRadioGroup from "../../react/FormRadioGroup";
-import FormSelect, { FormSelectWithGroups } from "../../react/FormSelect";
-import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
-import OptionsSection from "./components/OptionsSection";
import server from "../../../services/server";
+import { isElectron, isMobile, reloadFrontendApp, restartDesktopApp } from "../../../services/utils";
+import Button from "../../react/Button";
+import Column from "../../react/Column";
import FormCheckbox from "../../react/FormCheckbox";
import FormGroup from "../../react/FormGroup";
-import { FontFamily, OptionNames } from "@triliumnext/commons";
-import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox";
+import FormRadioGroup from "../../react/FormRadioGroup";
+import FormSelect, { FormSelectWithGroups } from "../../react/FormSelect";
import FormText from "../../react/FormText";
-import Button from "../../react/Button";
+import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox";
+import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
+import Icon from "../../react/Icon";
+import OptionsSection from "./components/OptionsSection";
+import RadioWithIllustration from "./components/RadioWithIllustration";
import RelatedSettings from "./components/RelatedSettings";
const MIN_CONTENT_WIDTH = 640;
@@ -30,7 +35,7 @@ const BUILTIN_THEMES: Theme[] = [
{ val: "auto", title: t("theme.auto_theme") },
{ val: "light", title: t("theme.light_theme") },
{ val: "dark", title: t("theme.dark_theme") }
-]
+];
interface FontFamilyEntry {
value: FontFamily;
@@ -84,6 +89,7 @@ export default function AppearanceSettings() {
return (
+ {!isMobile() &&
}
{!isMobile() &&
}
{overrideThemeFonts === "true" &&
}
@@ -102,7 +108,73 @@ export default function AppearanceSettings() {
}
]} />
- )
+ );
+}
+
+function LayoutSwitcher() {
+ return (
+
+ },
+ { key: "new-layout", text: "New layout", illustration: }
+ ]}
+ />
+
+ );
+}
+
+function LayoutIllustration({ isNewLayout }: { isNewLayout: boolean }) {
+ return (
+
+
+
+
+
+ Options
+
+ Appearance
+ Shortcuts
+ Text Notes
+ Code Notes
+ Images
+
+
+
+
+
+
+
+
+
+
+ Title
+
+
+
+ {!isNewLayout &&
}
+
+
+ This is a "demo" document packaged with Trilium to showcase some of its features and also give you some ideas on how you might structure your notes. You can play with it, and modify the note content and tree structure as you wish.
+
+
+
+
+ );
}
function LayoutOrientation() {
@@ -141,7 +213,7 @@ function ApplicationTheme() {
setThemes([
...BUILTIN_THEMES,
...userThemes
- ])
+ ]);
});
}, []);
@@ -162,7 +234,7 @@ function ApplicationTheme() {
- )
+ );
}
function Fonts() {
@@ -245,7 +317,7 @@ function ElectronIntegration() {
- )
+ );
}
function Performance() {
@@ -271,7 +343,7 @@ function Performance() {
{isElectron() && }
-
+ ;
}
function SmoothScrollEnabledOption() {
@@ -280,7 +352,7 @@ function SmoothScrollEnabledOption() {
return
+ />;
}
function MaxContentWidth() {
@@ -302,10 +374,10 @@ function MaxContentWidth() {
+ currentValue={centerContent}
+ onChange={setCenterContent} />
- )
+ );
}
function RibbonOptions() {
@@ -318,5 +390,5 @@ function RibbonOptions() {
currentValue={editedNotesOpenInRibbon} onChange={setEditedNotesOpenInRibbon}
/>
- )
+ );
}
diff --git a/apps/client/src/widgets/type_widgets/options/components/RadioWithIllustration.css b/apps/client/src/widgets/type_widgets/options/components/RadioWithIllustration.css
new file mode 100644
index 000000000..fcec83eff
--- /dev/null
+++ b/apps/client/src/widgets/type_widgets/options/components/RadioWithIllustration.css
@@ -0,0 +1,8 @@
+.radio-with-illustration {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ gap: 1em;
+ justify-content: center;
+}
diff --git a/apps/client/src/widgets/type_widgets/options/components/RadioWithIllustration.tsx b/apps/client/src/widgets/type_widgets/options/components/RadioWithIllustration.tsx
new file mode 100644
index 000000000..b5bdfa4b2
--- /dev/null
+++ b/apps/client/src/widgets/type_widgets/options/components/RadioWithIllustration.tsx
@@ -0,0 +1,28 @@
+import "./RadioWithIllustration.css";
+
+import { ComponentChild } from "preact";
+
+interface RadioWithIllustrationProps {
+ values: {
+ key: string;
+ text: string;
+ illustration: ComponentChild;
+ }[];
+ currentValue: string;
+ onChange(newValue: string);
+}
+
+export default function RadioWithIllustration({ values }: RadioWithIllustrationProps) {
+ return (
+
+ {values.map(value => (
+
+
+ {value.illustration}
+ {value.text}
+
+
+ ))}
+
+ );
+}
diff --git a/apps/server/src/routes/api/options.ts b/apps/server/src/routes/api/options.ts
index 6e38cc615..0a6940cb7 100644
--- a/apps/server/src/routes/api/options.ts
+++ b/apps/server/src/routes/api/options.ts
@@ -1,13 +1,14 @@
-"use strict";
-import optionService from "../../services/options.js";
-import log from "../../services/log.js";
-import searchService from "../../services/search/services/search.js";
-import ValidationError from "../../errors/validation_error.js";
-import type { Request } from "express";
-import { changeLanguage, getLocales } from "../../services/i18n.js";
+
import type { OptionNames } from "@triliumnext/commons";
+import type { Request } from "express";
+
+import ValidationError from "../../errors/validation_error.js";
import config from "../../services/config.js";
+import { changeLanguage, getLocales } from "../../services/i18n.js";
+import log from "../../services/log.js";
+import optionService from "../../services/options.js";
+import searchService from "../../services/search/services/search.js";
interface UserTheme {
val: string; // value of the theme, used in the URL
@@ -100,6 +101,7 @@ const ALLOWED_OPTIONS = new Set([
"splitEditorOrientation",
"seenCallToActions",
"experimentalFeatures",
+ "newLayout",
// AI/LLM integration options
"aiEnabled",
diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts
index d89dfe0d4..30754ab11 100644
--- a/apps/server/src/services/options_init.ts
+++ b/apps/server/src/services/options_init.ts
@@ -1,10 +1,11 @@
-import optionService from "./options.js";
+import { type KeyboardShortcutWithRequiredActionName, type OptionMap, type OptionNames, SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons";
+
import appInfo from "./app_info.js";
-import { randomSecureToken, isWindows } from "./utils.js";
-import log from "./log.js";
import dateUtils from "./date_utils.js";
import keyboardActions from "./keyboard_actions.js";
-import { SANITIZER_DEFAULT_ALLOWED_TAGS, type KeyboardShortcutWithRequiredActionName, type OptionMap, type OptionNames } from "@triliumnext/commons";
+import log from "./log.js";
+import optionService from "./options.js";
+import { isWindows,randomSecureToken } from "./utils.js";
function initDocumentOptions() {
optionService.createOption("documentId", randomSecureToken(16), false);
@@ -156,6 +157,7 @@ const defaultOptions: DefaultOption[] = [
{ name: "shadowsEnabled", value: "true", isSynced: false },
{ name: "backdropEffectsEnabled", value: "true", isSynced: false },
{ name: "smoothScrollEnabled", value: "true", isSynced: false },
+ { name: "newLayout", value: "true", isSynced: true },
// Internationalization
{ name: "locale", value: "en", isSynced: true },
@@ -171,9 +173,9 @@ const defaultOptions: DefaultOption[] = [
value: (optionsMap) => {
if (optionsMap.theme === "light") {
return "default:stackoverflow-light";
- } else {
- return "default:stackoverflow-dark";
}
+ return "default:stackoverflow-dark";
+
},
isSynced: false
},
diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts
index 9dd1df9f0..4bf445c12 100644
--- a/packages/commons/src/lib/options_interface.ts
+++ b/packages/commons/src/lib/options_interface.ts
@@ -131,6 +131,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions