mirror of
https://github.com/zadam/trilium.git
synced 2025-12-13 03:39:54 +01:00
New layout: Integrate small ribbon categories + collection properties (#8018)
This commit is contained in:
@@ -2031,7 +2031,7 @@
|
|||||||
"book_properties_config": {
|
"book_properties_config": {
|
||||||
"hide-weekends": "Hide weekends",
|
"hide-weekends": "Hide weekends",
|
||||||
"display-week-numbers": "Display week numbers",
|
"display-week-numbers": "Display week numbers",
|
||||||
"map-style": "Map style:",
|
"map-style": "Map style",
|
||||||
"max-nesting-depth": "Max nesting depth:",
|
"max-nesting-depth": "Max nesting depth:",
|
||||||
"raster": "Raster",
|
"raster": "Raster",
|
||||||
"vector_light": "Vector (Light)",
|
"vector_light": "Vector (Light)",
|
||||||
@@ -2146,6 +2146,12 @@
|
|||||||
"backlinks_one": "{{count}} backlink",
|
"backlinks_one": "{{count}} backlink",
|
||||||
"backlinks_other": "{{count}} backlinks",
|
"backlinks_other": "{{count}} backlinks",
|
||||||
"backlinks_description_one": "This note is linked from {{count}} other note.\n\nClick to view the list of backlinks.",
|
"backlinks_description_one": "This note is linked from {{count}} other note.\n\nClick to view the list of backlinks.",
|
||||||
"backlinks_description_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks."
|
"backlinks_description_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks.",
|
||||||
|
"clipped_note": "Web clip",
|
||||||
|
"clipped_note_description": "This note was originally taken from {{url}}.\n\nClick to navigate to the source webpage.",
|
||||||
|
"execute_script": "Run script",
|
||||||
|
"execute_script_description": "This note is a script note. Click to execute the script.",
|
||||||
|
"execute_sql": "Run SQL",
|
||||||
|
"execute_sql_description": "This note is a SQL note. Click to execute the SQL query."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
&.temporarily-editable-badge { --color: #4fa52b; }
|
&.temporarily-editable-badge { --color: #4fa52b; }
|
||||||
&.read-only-badge { --color: #e33f3b; }
|
&.read-only-badge { --color: #e33f3b; }
|
||||||
&.share-badge { --color: #3b82f6; }
|
&.share-badge { --color: #3b82f6; }
|
||||||
|
&.clipped-note-badge { --color: #57a2a5; }
|
||||||
&.backlinks-badge { color: var(--badge-text-color); }
|
&.backlinks-badge { color: var(--badge-text-color); }
|
||||||
|
&.execute-badge { --color: #f59e0b; }
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { t } from "../services/i18n";
|
|||||||
import { formatDateTime } from "../utils/formatters";
|
import { formatDateTime } from "../utils/formatters";
|
||||||
import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions";
|
import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions";
|
||||||
import Dropdown, { DropdownProps } from "./react/Dropdown";
|
import Dropdown, { DropdownProps } from "./react/Dropdown";
|
||||||
import { useIsNoteReadOnly, useNoteContext, useStaticTooltip } from "./react/hooks";
|
import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useStaticTooltip } from "./react/hooks";
|
||||||
import Icon from "./react/Icon";
|
import Icon from "./react/Icon";
|
||||||
import { NoteSizeWidget, useNoteMetadata } from "./ribbon/NoteInfoTab";
|
import { NoteSizeWidget, useNoteMetadata } from "./ribbon/NoteInfoTab";
|
||||||
import { useShareInfo } from "./shared_info";
|
import { useShareInfo } from "./shared_info";
|
||||||
@@ -20,6 +20,8 @@ export default function BreadcrumbBadges() {
|
|||||||
<ReadOnlyBadge />
|
<ReadOnlyBadge />
|
||||||
<ShareBadge />
|
<ShareBadge />
|
||||||
<BacklinksBadge />
|
<BacklinksBadge />
|
||||||
|
<ClippedNoteBadge />
|
||||||
|
<ExecuteBadge />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -114,6 +116,40 @@ function BacklinksBadge() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ClippedNoteBadge() {
|
||||||
|
const { note } = useNoteContext();
|
||||||
|
const [ pageUrl ] = useNoteLabel(note, "pageUrl");
|
||||||
|
|
||||||
|
return (pageUrl &&
|
||||||
|
<Badge
|
||||||
|
className="clipped-note-badge"
|
||||||
|
icon="bx bx-globe"
|
||||||
|
text={t("breadcrumb_badges.clipped_note")}
|
||||||
|
tooltip={t("breadcrumb_badges.clipped_note_description", { url: pageUrl })}
|
||||||
|
href={pageUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExecuteBadge() {
|
||||||
|
const { note, parentComponent } = useNoteContext();
|
||||||
|
const isScript = note?.isTriliumScript();
|
||||||
|
const isSql = note?.isTriliumSqlite();
|
||||||
|
const isExecutable = isScript || isSql;
|
||||||
|
const [ executeDescription ] = useNoteLabel(note, "executeDescription");
|
||||||
|
const [ executeButton ] = useNoteLabelBoolean(note, "executeButton");
|
||||||
|
|
||||||
|
return (note && isExecutable && (executeDescription || executeButton) &&
|
||||||
|
<Badge
|
||||||
|
className="execute-badge"
|
||||||
|
icon="bx bx-play"
|
||||||
|
text={isScript ? t("breadcrumb_badges.execute_script") : t("breadcrumb_badges.execute_sql")}
|
||||||
|
tooltip={executeDescription || (isScript ? t("breadcrumb_badges.execute_script_description") : t("breadcrumb_badges.execute_sql_description"))}
|
||||||
|
onClick={() => parentComponent.triggerCommand("runActiveNote")}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface BadgeProps {
|
interface BadgeProps {
|
||||||
text?: string;
|
text?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
|||||||
@@ -300,8 +300,9 @@ function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingB
|
|||||||
|
|
||||||
function InAppHelpButton({ note }: FloatingButtonContext) {
|
function InAppHelpButton({ note }: FloatingButtonContext) {
|
||||||
const helpUrl = getHelpUrlForNote(note);
|
const helpUrl = getHelpUrlForNote(note);
|
||||||
|
const isEnabled = !!helpUrl && (!isNewLayout || (note?.type !== "book"));
|
||||||
|
|
||||||
return !!helpUrl && (
|
return isEnabled && (
|
||||||
<FloatingButton
|
<FloatingButton
|
||||||
icon="bx bx-help-circle"
|
icon="bx bx-help-circle"
|
||||||
text={t("help-button.title")}
|
text={t("help-button.title")}
|
||||||
|
|||||||
@@ -1,46 +1,13 @@
|
|||||||
import { type ComponentChild } from "preact";
|
import CollectionProperties from "./note_bars/CollectionProperties";
|
||||||
|
import { useNoteContext, useNoteProperty } from "./react/hooks";
|
||||||
import { formatDateTime } from "../utils/formatters";
|
|
||||||
import { useNoteContext, useStaticTooltip } from "./react/hooks";
|
|
||||||
import { joinElements } from "./react/react_utils";
|
|
||||||
import { useNoteMetadata } from "./ribbon/NoteInfoTab";
|
|
||||||
import { Trans } from "react-i18next";
|
|
||||||
import { useRef } from "preact/hooks";
|
|
||||||
|
|
||||||
export default function NoteTitleDetails() {
|
export default function NoteTitleDetails() {
|
||||||
const { note, noteContext } = useNoteContext();
|
const { note } = useNoteContext();
|
||||||
const isHiddenNote = note?.noteId.startsWith("_");
|
const noteType = useNoteProperty(note, "type");
|
||||||
const isDefaultView = noteContext?.viewScope?.viewMode === "default";
|
|
||||||
|
|
||||||
const items: ComponentChild[] = [].filter(item => !!item);
|
return (
|
||||||
|
|
||||||
return items.length && (
|
|
||||||
<div className="title-details">
|
<div className="title-details">
|
||||||
{joinElements(items, " • ")}
|
{note && noteType === "book" && <CollectionProperties note={note} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextWithValue({ i18nKey, value, valueTooltip }: {
|
|
||||||
i18nKey: string;
|
|
||||||
value: string;
|
|
||||||
valueTooltip: string;
|
|
||||||
}) {
|
|
||||||
const listItemRef = useRef<HTMLLIElement>(null);
|
|
||||||
useStaticTooltip(listItemRef, {
|
|
||||||
selector: "span.value",
|
|
||||||
title: valueTooltip,
|
|
||||||
popperConfig: { placement: "bottom" }
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li ref={listItemRef}>
|
|
||||||
<Trans
|
|
||||||
i18nKey={i18nKey}
|
|
||||||
components={{
|
|
||||||
Value: <span className="value">{value}</span> as React.ReactElement
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
220
apps/client/src/widgets/note_bars/CollectionProperties.tsx
Normal file
220
apps/client/src/widgets/note_bars/CollectionProperties.tsx
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import { t } from "i18next";
|
||||||
|
import { useContext } from "preact/hooks";
|
||||||
|
import { Fragment } from "preact/jsx-runtime";
|
||||||
|
|
||||||
|
import FNote from "../../entities/fnote";
|
||||||
|
import { ViewTypeOptions } from "../collections/interface";
|
||||||
|
import Dropdown from "../react/Dropdown";
|
||||||
|
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList";
|
||||||
|
import FormTextBox from "../react/FormTextBox";
|
||||||
|
import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault } from "../react/hooks";
|
||||||
|
import Icon from "../react/Icon";
|
||||||
|
import { ParentComponent } from "../react/react_utils";
|
||||||
|
import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config";
|
||||||
|
import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab";
|
||||||
|
import ActionButton from "../react/ActionButton";
|
||||||
|
import { getHelpUrlForNote } from "../../services/in_app_help";
|
||||||
|
import { openInAppHelpFromUrl } from "../../services/utils";
|
||||||
|
|
||||||
|
const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
|
||||||
|
grid: "bx bxs-grid",
|
||||||
|
list: "bx bx-list-ul",
|
||||||
|
calendar: "bx bx-calendar",
|
||||||
|
table: "bx bx-table",
|
||||||
|
geoMap: "bx bx-map-alt",
|
||||||
|
board: "bx bx-columns",
|
||||||
|
presentation: "bx bx-rectangle"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CollectionProperties({ note }: { note: FNote }) {
|
||||||
|
const [ viewType, setViewType ] = useViewType(note);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ViewTypeSwitcher viewType={viewType} setViewType={setViewType} />
|
||||||
|
<ViewOptions note={note} viewType={viewType} />
|
||||||
|
<div className="spacer" />
|
||||||
|
<HelpButton note={note} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ViewTypeSwitcher({ viewType, setViewType }: { viewType: ViewTypeOptions, setViewType: (newValue: ViewTypeOptions) => void }) {
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
text={<>
|
||||||
|
<Icon icon={ICON_MAPPINGS[viewType]} />
|
||||||
|
{VIEW_TYPE_MAPPINGS[viewType]}
|
||||||
|
</>}
|
||||||
|
>
|
||||||
|
{Object.entries(VIEW_TYPE_MAPPINGS).map(([ key, label ]) => (
|
||||||
|
<FormListItem
|
||||||
|
key={key}
|
||||||
|
onClick={() => setViewType(key as ViewTypeOptions)}
|
||||||
|
selected={viewType === key}
|
||||||
|
disabled={viewType === key}
|
||||||
|
icon={ICON_MAPPINGS[key as ViewTypeOptions]}
|
||||||
|
>{label}</FormListItem>
|
||||||
|
))}
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ViewOptions({ note, viewType }: { note: FNote, viewType: ViewTypeOptions }) {
|
||||||
|
const properties = bookPropertiesConfig[viewType].properties;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
buttonClassName="bx bx-cog icon-action"
|
||||||
|
hideToggleArrow
|
||||||
|
>
|
||||||
|
{properties.map(property => (
|
||||||
|
<ViewProperty key={property.label} note={note} property={property} />
|
||||||
|
))}
|
||||||
|
{properties.length > 0 && <FormDropdownDivider />}
|
||||||
|
|
||||||
|
<ViewProperty note={note} property={{
|
||||||
|
type: "checkbox",
|
||||||
|
icon: "bx bx-archive",
|
||||||
|
label: t("book_properties.include_archived_notes"),
|
||||||
|
bindToLabel: "includeArchived"
|
||||||
|
} as CheckBoxProperty} />
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ViewProperty({ note, property }: { note: FNote, property: BookProperty }) {
|
||||||
|
switch (property.type) {
|
||||||
|
case "button":
|
||||||
|
return <ButtonPropertyView note={note} property={property} />;
|
||||||
|
case "split-button":
|
||||||
|
return <SplitButtonPropertyView note={note} property={property} />;
|
||||||
|
case "checkbox":
|
||||||
|
return <CheckBoxPropertyView note={note} property={property} />;
|
||||||
|
case "number":
|
||||||
|
return <NumberPropertyView note={note} property={property} />;
|
||||||
|
case "combobox":
|
||||||
|
return <ComboBoxPropertyView note={note} property={property} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ButtonPropertyView({ note, property }: { note: FNote, property: ButtonProperty }) {
|
||||||
|
const parentComponent = useContext(ParentComponent);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormListItem
|
||||||
|
icon={property.icon}
|
||||||
|
title={property.title}
|
||||||
|
onClick={() => {
|
||||||
|
if (!parentComponent) return;
|
||||||
|
property.onClick({
|
||||||
|
note,
|
||||||
|
triggerCommand: parentComponent.triggerCommand.bind(parentComponent)
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>{property.label}</FormListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SplitButtonPropertyView({ note, property }: { note: FNote, property: SplitButtonProperty }) {
|
||||||
|
const parentComponent = useContext(ParentComponent);
|
||||||
|
const ItemsComponent = property.items;
|
||||||
|
const clickContext = parentComponent && {
|
||||||
|
note,
|
||||||
|
triggerCommand: parentComponent.triggerCommand.bind(parentComponent)
|
||||||
|
};
|
||||||
|
|
||||||
|
return (parentComponent &&
|
||||||
|
<FormDropdownSubmenu
|
||||||
|
icon={property.icon ?? "bx bx-empty"}
|
||||||
|
title={property.label}
|
||||||
|
onDropdownToggleClicked={() => clickContext && property.onClick(clickContext)}
|
||||||
|
>
|
||||||
|
<ItemsComponent note={note} parentComponent={parentComponent} />
|
||||||
|
</FormDropdownSubmenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NumberPropertyView({ note, property }: { note: FNote, property: NumberProperty }) {
|
||||||
|
//@ts-expect-error Interop with text box which takes in string values even for numbers.
|
||||||
|
const [ value, setValue ] = useNoteLabel(note, property.bindToLabel);
|
||||||
|
const disabled = property.disabled?.(note);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormListItem
|
||||||
|
icon={property.icon}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{property.label}
|
||||||
|
<FormTextBox
|
||||||
|
type="number"
|
||||||
|
currentValue={value ?? ""} onChange={setValue}
|
||||||
|
style={{ width: (property.width ?? 100) }}
|
||||||
|
min={property.min ?? 0}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</FormListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ComboBoxPropertyView({ note, property }: { note: FNote, property: ComboBoxProperty }) {
|
||||||
|
const [ value, setValue ] = useNoteLabelWithDefault(note, property.bindToLabel, property.defaultValue ?? "");
|
||||||
|
|
||||||
|
function renderItem(option: ComboBoxItem) {
|
||||||
|
return (
|
||||||
|
<FormListItem
|
||||||
|
key={option.value}
|
||||||
|
checked={value === option.value}
|
||||||
|
onClick={() => setValue(option.value)}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</FormListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormDropdownSubmenu
|
||||||
|
title={property.label}
|
||||||
|
icon={property.icon ?? "bx bx-empty"}
|
||||||
|
>
|
||||||
|
{(property.options).map((option, index) => {
|
||||||
|
if ("items" in option) {
|
||||||
|
return (
|
||||||
|
<Fragment key={option.title}>
|
||||||
|
<FormListItem key={option.title} disabled>{option.title}</FormListItem>
|
||||||
|
{option.items.map(renderItem)}
|
||||||
|
{index < property.options.length - 1 && <FormDropdownDivider />}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return renderItem(option);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</FormDropdownSubmenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) {
|
||||||
|
const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel);
|
||||||
|
return (
|
||||||
|
<FormListToggleableItem
|
||||||
|
icon={property.icon}
|
||||||
|
title={property.label}
|
||||||
|
currentValue={value}
|
||||||
|
onChange={setValue}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function HelpButton({ note }: { note: FNote }) {
|
||||||
|
const helpUrl = getHelpUrlForNote(note);
|
||||||
|
|
||||||
|
return (helpUrl && (
|
||||||
|
<ActionButton
|
||||||
|
icon="bx bx-help-circle"
|
||||||
|
onClick={(() => openInAppHelpFromUrl(helpUrl))}
|
||||||
|
text={t("help-button.title")}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
@@ -37,6 +37,21 @@ body.experimental-feature-new-layout {
|
|||||||
padding-inline-start: 24px;
|
padding-inline-start: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title-details {
|
||||||
|
padding-inline-end: 16px;
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
input.form-control {
|
||||||
|
padding: 2px 8px;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.title-row {
|
.title-row {
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
|
|
||||||
@@ -75,7 +90,8 @@ body.experimental-feature-new-layout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)) {
|
.scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)),
|
||||||
|
.note-split.type-book {
|
||||||
.title-row,
|
.title-row,
|
||||||
.title-details {
|
.title-details {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -84,12 +100,11 @@ body.experimental-feature-new-layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title-row {
|
.title-row {
|
||||||
margin-top: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-details {
|
.title-details {
|
||||||
margin-bottom: 0.2em;
|
padding-bottom: 0.2em;
|
||||||
opacity: 0.65;
|
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,10 +206,11 @@ export function FormDropdownDivider() {
|
|||||||
return <div className="dropdown-divider" />;
|
return <div className="dropdown-divider" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FormDropdownSubmenu({ icon, title, children, dropStart }: {
|
export function FormDropdownSubmenu({ icon, title, children, dropStart, onDropdownToggleClicked }: {
|
||||||
icon: string,
|
icon: string,
|
||||||
title: ComponentChildren,
|
title: ComponentChildren,
|
||||||
children: ComponentChildren,
|
children: ComponentChildren,
|
||||||
|
onDropdownToggleClicked?: () => void,
|
||||||
dropStart?: boolean
|
dropStart?: boolean
|
||||||
}) {
|
}) {
|
||||||
const [ openOnMobile, setOpenOnMobile ] = useState(false);
|
const [ openOnMobile, setOpenOnMobile ] = useState(false);
|
||||||
@@ -224,6 +225,10 @@ export function FormDropdownSubmenu({ icon, title, children, dropStart }: {
|
|||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
setOpenOnMobile(!openOnMobile);
|
setOpenOnMobile(!openOnMobile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (onDropdownToggleClicked) {
|
||||||
|
onDropdownToggleClicked();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon icon={icon} />{" "}
|
<Icon icon={icon} />{" "}
|
||||||
|
|||||||
@@ -12,34 +12,41 @@ import FormCheckbox from "../react/FormCheckbox";
|
|||||||
import FormTextBox from "../react/FormTextBox";
|
import FormTextBox from "../react/FormTextBox";
|
||||||
import { ComponentChildren } from "preact";
|
import { ComponentChildren } from "preact";
|
||||||
import { ViewTypeOptions } from "../collections/interface";
|
import { ViewTypeOptions } from "../collections/interface";
|
||||||
import { FormDropdownDivider, FormListItem } from "../react/FormList";
|
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
|
||||||
|
|
||||||
const VIEW_TYPE_MAPPINGS: Record<ViewTypeOptions, string> = {
|
export const VIEW_TYPE_MAPPINGS: Record<ViewTypeOptions, string> = {
|
||||||
grid: t("book_properties.grid"),
|
grid: t("book_properties.grid"),
|
||||||
list: t("book_properties.list"),
|
list: t("book_properties.list"),
|
||||||
calendar: t("book_properties.calendar"),
|
calendar: t("book_properties.calendar"),
|
||||||
table: t("book_properties.table"),
|
table: t("book_properties.table"),
|
||||||
geoMap: t("book_properties.geo-map"),
|
geoMap: t("book_properties.geo-map"),
|
||||||
board: t("book_properties.board"),
|
board: t("book_properties.board"),
|
||||||
presentation: t("book_properties.presentation")
|
presentation: t("book_properties.presentation")
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CollectionPropertiesTab({ note }: TabContext) {
|
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||||
const [ viewType, setViewType ] = useNoteLabel(note, "viewType");
|
|
||||||
const defaultViewType = (note?.type === "search" ? "list" : "grid");
|
|
||||||
const viewTypeWithDefault = (viewType ?? defaultViewType) as ViewTypeOptions;
|
|
||||||
const properties = bookPropertiesConfig[viewTypeWithDefault].properties;
|
|
||||||
|
|
||||||
return (
|
export default function CollectionPropertiesTab({ note }: TabContext) {
|
||||||
<div className="book-properties-widget">
|
const [viewType, setViewType] = useViewType(note);
|
||||||
{note && (
|
const properties = bookPropertiesConfig[viewType].properties;
|
||||||
<>
|
|
||||||
<CollectionTypeSwitcher viewType={viewTypeWithDefault} setViewType={setViewType} />
|
return (
|
||||||
<BookProperties viewType={viewTypeWithDefault} note={note} properties={properties} />
|
<div className="book-properties-widget">
|
||||||
</>
|
{note && (
|
||||||
)}
|
<>
|
||||||
</div>
|
{!isNewLayout && <CollectionTypeSwitcher viewType={viewType} setViewType={setViewType} />}
|
||||||
);
|
<BookProperties viewType={viewType} note={note} properties={properties} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useViewType(note: FNote | null | undefined) {
|
||||||
|
const [ viewType, setViewType ] = useNoteLabel(note, "viewType");
|
||||||
|
const defaultViewType = (note?.type === "search" ? "list" : "grid");
|
||||||
|
const viewTypeWithDefault = (viewType ?? defaultViewType) as ViewTypeOptions;
|
||||||
|
return [ viewTypeWithDefault, setViewType ] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CollectionTypeSwitcher({ viewType, setViewType }: { viewType: string, setViewType: (newValue: string) => void }) {
|
function CollectionTypeSwitcher({ viewType, setViewType }: { viewType: string, setViewType: (newValue: string) => void }) {
|
||||||
@@ -148,7 +155,7 @@ function NumberPropertyView({ note, property }: { note: FNote, property: NumberP
|
|||||||
<FormTextBox
|
<FormTextBox
|
||||||
type="number"
|
type="number"
|
||||||
currentValue={value ?? ""} onChange={setValue}
|
currentValue={value ?? ""} onChange={setValue}
|
||||||
style={{ width: (property.width ?? 100) + "px" }}
|
style={{ width: (property.width ?? 100) }}
|
||||||
min={property.min ?? 0}
|
min={property.min ?? 0}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
|
|||||||
icon: "bx bx-play",
|
icon: "bx bx-play",
|
||||||
content: ScriptTab,
|
content: ScriptTab,
|
||||||
activate: true,
|
activate: true,
|
||||||
show: ({ note }) => note &&
|
show: ({ note }) => note && !isNewLayout &&
|
||||||
(note.isTriliumScript() || note.isTriliumSqlite()) &&
|
(note.isTriliumScript() || note.isTriliumSqlite()) &&
|
||||||
(note.hasLabel("executeDescription") || note.hasLabel("executeButton"))
|
(note.hasLabel("executeDescription") || note.hasLabel("executeButton"))
|
||||||
},
|
},
|
||||||
@@ -60,14 +60,14 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
|
|||||||
title: t("book_properties.book_properties"),
|
title: t("book_properties.book_properties"),
|
||||||
icon: "bx bx-book",
|
icon: "bx bx-book",
|
||||||
content: CollectionPropertiesTab,
|
content: CollectionPropertiesTab,
|
||||||
show: ({ note }) => note?.type === "book" || note?.type === "search",
|
show: ({ note }) => !isNewLayout && note?.type === "book" || note?.type === "search",
|
||||||
toggleCommand: "toggleRibbonTabBookProperties"
|
toggleCommand: "toggleRibbonTabBookProperties"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("note_properties.info"),
|
title: t("note_properties.info"),
|
||||||
icon: "bx bx-info-square",
|
icon: "bx bx-info-square",
|
||||||
content: NotePropertiesTab,
|
content: NotePropertiesTab,
|
||||||
show: ({ note }) => !!note?.getLabelValue("pageUrl"),
|
show: ({ note }) => !isNewLayout && !!note?.getLabelValue("pageUrl"),
|
||||||
activate: true
|
activate: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ interface BookConfig {
|
|||||||
export interface CheckBoxProperty {
|
export interface CheckBoxProperty {
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
label: string;
|
label: string;
|
||||||
bindToLabel: FilterLabelsByType<boolean>
|
bindToLabel: FilterLabelsByType<boolean>;
|
||||||
|
icon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ButtonProperty {
|
export interface ButtonProperty {
|
||||||
@@ -40,10 +41,11 @@ export interface NumberProperty {
|
|||||||
bindToLabel: FilterLabelsByType<number>;
|
bindToLabel: FilterLabelsByType<number>;
|
||||||
width?: number;
|
width?: number;
|
||||||
min?: number;
|
min?: number;
|
||||||
|
icon?: string;
|
||||||
disabled?: (note: FNote) => boolean;
|
disabled?: (note: FNote) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComboBoxItem {
|
export interface ComboBoxItem {
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
@@ -56,6 +58,7 @@ interface ComboBoxGroup {
|
|||||||
export interface ComboBoxProperty {
|
export interface ComboBoxProperty {
|
||||||
type: "combobox",
|
type: "combobox",
|
||||||
label: string;
|
label: string;
|
||||||
|
icon?: string;
|
||||||
bindToLabel: FilterLabelsByType<string>;
|
bindToLabel: FilterLabelsByType<string>;
|
||||||
/**
|
/**
|
||||||
* The default value is used when the label is not set.
|
* The default value is used when the label is not set.
|
||||||
@@ -107,11 +110,13 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
|
|||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
label: t("book_properties_config.hide-weekends"),
|
label: t("book_properties_config.hide-weekends"),
|
||||||
|
icon: "bx bx-calendar-week",
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
bindToLabel: "calendar:hideWeekends"
|
bindToLabel: "calendar:hideWeekends"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("book_properties_config.display-week-numbers"),
|
label: t("book_properties_config.display-week-numbers"),
|
||||||
|
icon: "bx bx-hash",
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
bindToLabel: "calendar:weekNumbers"
|
bindToLabel: "calendar:weekNumbers"
|
||||||
}
|
}
|
||||||
@@ -121,6 +126,7 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
|
|||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
label: t("book_properties_config.map-style"),
|
label: t("book_properties_config.map-style"),
|
||||||
|
icon: "bx bx-palette",
|
||||||
type: "combobox",
|
type: "combobox",
|
||||||
bindToLabel: "map:style",
|
bindToLabel: "map:style",
|
||||||
defaultValue: DEFAULT_MAP_LAYER_NAME,
|
defaultValue: DEFAULT_MAP_LAYER_NAME,
|
||||||
@@ -147,6 +153,7 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("book_properties_config.show-scale"),
|
label: t("book_properties_config.show-scale"),
|
||||||
|
icon: "bx bx-ruler",
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
bindToLabel: "map:scale"
|
bindToLabel: "map:scale"
|
||||||
}
|
}
|
||||||
@@ -156,6 +163,7 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
|
|||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
label: t("book_properties_config.max-nesting-depth"),
|
label: t("book_properties_config.max-nesting-depth"),
|
||||||
|
icon: "bx bx-subdirectory-right",
|
||||||
type: "number",
|
type: "number",
|
||||||
bindToLabel: "maxNestingDepth",
|
bindToLabel: "maxNestingDepth",
|
||||||
width: 65,
|
width: 65,
|
||||||
@@ -171,6 +179,7 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
|
|||||||
{
|
{
|
||||||
label: "Theme",
|
label: "Theme",
|
||||||
type: "combobox",
|
type: "combobox",
|
||||||
|
icon: "bx bx-palette",
|
||||||
bindToLabel: "presentation:theme",
|
bindToLabel: "presentation:theme",
|
||||||
defaultValue: DEFAULT_THEME,
|
defaultValue: DEFAULT_THEME,
|
||||||
options: getPresentationThemes().map(theme => ({
|
options: getPresentationThemes().map(theme => ({
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ type Labels = {
|
|||||||
color: string;
|
color: string;
|
||||||
iconClass: string;
|
iconClass: string;
|
||||||
workspaceIconClass: string;
|
workspaceIconClass: string;
|
||||||
|
executeButton: boolean;
|
||||||
executeDescription: string;
|
executeDescription: string;
|
||||||
executeTitle: string;
|
executeTitle: string;
|
||||||
limit: string; // should be probably be number
|
limit: string; // should be probably be number
|
||||||
|
|||||||
Reference in New Issue
Block a user