alinea
Version:
[](https://npmjs.org/package/alinea) [](https://packagephobia.com/result?p=alinea)
332 lines (328 loc) • 12.5 kB
JavaScript
import {
useQuery
} from "../../chunks/chunk-DJKGEOOC.js";
import "../../chunks/chunk-U5RRZUYZ.js";
// src/picker/entry/EntryPicker.browser.tsx
import { Picker, Root, createId } from "alinea/core";
import { Entry } from "alinea/core/Entry";
import { workspaceMediaDir } from "alinea/core/EntryFilenames";
import { isMediaRoot } from "alinea/core/media/MediaRoot";
import { and } from "alinea/core/pages/Expr";
import { entries } from "alinea/core/util/Objects";
import { useConfig } from "alinea/dashboard/hook/UseConfig";
import { useFocusList } from "alinea/dashboard/hook/UseFocusList";
import { useGraph } from "alinea/dashboard/hook/UseGraph";
import { useLocale } from "alinea/dashboard/hook/UseLocale";
import { useRoot } from "alinea/dashboard/hook/UseRoot";
import { useWorkspace } from "alinea/dashboard/hook/UseWorkspace";
import { Breadcrumbs, BreadcrumbsItem } from "alinea/dashboard/view/Breadcrumbs";
import { IconButton } from "alinea/dashboard/view/IconButton";
import { Modal } from "alinea/dashboard/view/Modal";
import { Langswitch } from "alinea/dashboard/view/entry/LangSwitch";
import {
Explorer
} from "alinea/dashboard/view/explorer/Explorer";
import { FileUploader } from "alinea/dashboard/view/media/FileUploader";
import {
Button,
HStack,
Icon,
Loader,
Stack,
TextLabel,
VStack,
fromModule
} from "alinea/ui";
import { DropdownMenu } from "alinea/ui/DropdownMenu";
import { IcOutlineGridView } from "alinea/ui/icons/IcOutlineGridView";
import { IcOutlineList } from "alinea/ui/icons/IcOutlineList";
import { IcRoundArrowBack } from "alinea/ui/icons/IcRoundArrowBack";
import { IcRoundSearch } from "alinea/ui/icons/IcRoundSearch";
import { IcRoundUnfoldMore } from "alinea/ui/icons/IcRoundUnfoldMore";
import { Suspense, useCallback, useMemo, useState } from "react";
import {
entryPicker as createEntryPicker
} from "./EntryPicker.js";
// src/picker/entry/EntryPicker.module.scss
var EntryPicker_module_default = {
"root": "alinea-EntryPicker",
"root-footer": "alinea-EntryPicker-footer",
"rootFooter": "alinea-EntryPicker-footer",
"root-header": "alinea-EntryPicker-header",
"rootHeader": "alinea-EntryPicker-header",
"root-search": "alinea-EntryPicker-search",
"rootSearch": "alinea-EntryPicker-search",
"root-search-icon": "alinea-EntryPicker-search-icon",
"rootSearchIcon": "alinea-EntryPicker-search-icon",
"root-search-input": "alinea-EntryPicker-search-input",
"rootSearchInput": "alinea-EntryPicker-search-input",
"root-results": "alinea-EntryPicker-results",
"rootResults": "alinea-EntryPicker-results",
"root-results-list": "alinea-EntryPicker-results-list",
"rootResultsList": "alinea-EntryPicker-results-list",
"root-results-list-item": "alinea-EntryPicker-results-list-item",
"rootResultsListItem": "alinea-EntryPicker-results-list-item"
};
// src/picker/entry/EntryPicker.browser.tsx
import { EntryPickerRow } from "./EntryPickerRow.js";
import { EntryReference } from "./EntryReference.js";
export * from "./EntryPicker.js";
import { jsx, jsxs } from "react/jsx-runtime";
var entryPicker = Picker.withView(createEntryPicker, {
view: EntryPickerModal,
viewRow: EntryPickerRow
});
var styles = fromModule(EntryPicker_module_default);
function mediaRoot(workspace) {
for (const [name, root] of entries(workspace.roots))
if (isMediaRoot(root))
return name;
throw new Error(`Workspace ${workspace.name} has no media root`);
}
function EntryPickerModal({
type,
options,
selection,
onConfirm,
onCancel
}) {
const config = useConfig();
const graph = useGraph();
const {
title,
defaultView,
max,
condition,
withNavigation = true,
showMedia
} = options;
const [search, setSearch] = useState("");
const list = useFocusList({
onClear: () => setSearch("")
});
const [selected, setSelected] = useState(
() => selection || []
);
const workspace = useWorkspace();
const { name: root } = useRoot();
const locale = useLocale();
const [destination, setDestination] = useState({
workspace: workspace.name,
root: showMedia ? mediaRoot(workspace) : root,
locale
});
const destinationRoot = Root.data(workspace.roots[destination.root]);
const destinationLocale = !destinationRoot.i18n ? void 0 : destination.locale ?? destinationRoot.i18n.locales[0];
const { data: parentEntries } = useQuery(
["picker-parents", destination, destinationLocale],
async () => {
if (!destination.parentId)
return [];
const drafts = graph.preferDraft;
const query = destinationLocale ? drafts.locale(destinationLocale) : drafts;
const res = await query.get(
Entry({ entryId: destination.parentId }).select({
title: Entry.title,
parents({ parents }) {
return parents().select({
id: Entry.entryId,
title: Entry.title
});
}
})
);
return res?.parents.concat({
id: destination.parentId,
title: res.title
});
}
);
const cursor = useMemo(() => {
const terms = search.replace(/,/g, " ").split(" ").filter(Boolean);
if (!withNavigation && condition)
return Entry().where(condition).search(...terms);
const rootCondition = and(
Entry.workspace.is(destination.workspace),
Entry.root.is(destination.root)
);
const destinationCondition = terms.length === 0 ? and(rootCondition, Entry.parent.is(destination.parentId ?? null)) : rootCondition;
const translatedCondition = destinationLocale ? and(destinationCondition, Entry.locale.is(destinationLocale)) : destinationCondition;
return Entry().where(translatedCondition).search(...terms);
}, [destination, destinationLocale, search, condition]);
const [view, setView] = useState(defaultView || "row");
const handleSelect = useCallback(
(entry) => {
setSelected((selected2) => {
const index = selected2.findIndex(
(ref) => EntryReference.isEntryReference(ref) && ref.entry === entry.entryId
);
let res = selected2.slice();
if (index === -1) {
res = res.concat({
id: createId(),
type,
entry: entry.entryId
}).slice(-(max || 0));
} else {
res.splice(index, 1);
res = res.slice(-(max || 0));
}
if (max === 1 && res.length === 1)
onConfirm(res);
return res;
});
},
[setSelected, max]
);
function handleConfirm() {
onConfirm(selected);
}
function toRoot() {
setDestination({ ...destination, parentId: void 0 });
}
function goUp() {
if (!destination.parentId)
return onCancel();
const parentIndex = parentEntries?.findIndex(
({ id }) => id === destination.parentId
);
if (parentIndex === void 0)
return;
const parent = parentEntries?.[parentIndex - 1];
if (!parent)
return toRoot();
setDestination({ ...destination, parentId: parent.id });
}
return /* @__PURE__ */ jsx(Modal, { open: true, onClose: onCancel, className: styles.root(), children: /* @__PURE__ */ jsxs(Suspense, { fallback: /* @__PURE__ */ jsx(Loader, { absolute: true }), children: [
/* @__PURE__ */ jsx("div", { className: styles.root.header(), children: /* @__PURE__ */ jsxs(VStack, { gap: 24, children: [
/* @__PURE__ */ jsxs(HStack, { align: "flex-end", gap: 18, children: [
/* @__PURE__ */ jsx(IconButton, { icon: IcRoundArrowBack, onClick: goUp }),
/* @__PURE__ */ jsxs(VStack, { children: [
withNavigation && /* @__PURE__ */ jsxs(Breadcrumbs, { children: [
/* @__PURE__ */ jsx(BreadcrumbsItem, { children: /* @__PURE__ */ jsx("button", { onClick: toRoot, children: workspace.label }) }),
/* @__PURE__ */ jsxs(BreadcrumbsItem, { children: [
/* @__PURE__ */ jsxs(DropdownMenu.Root, { bottom: true, children: [
/* @__PURE__ */ jsx(DropdownMenu.Trigger, { children: /* @__PURE__ */ jsxs(HStack, { center: true, gap: 4, children: [
Root.label(workspace.roots[destination.root]),
/* @__PURE__ */ jsx(Icon, { icon: IcRoundUnfoldMore })
] }) }),
/* @__PURE__ */ jsx(DropdownMenu.Items, { children: entries(workspace.roots).map(([name, root2]) => {
return /* @__PURE__ */ jsx(
DropdownMenu.Item,
{
onClick: () => {
setDestination({
workspace: destination.workspace,
root: name
});
},
children: Root.label(root2)
},
name
);
}) })
] }),
destinationRoot.i18n && /* @__PURE__ */ jsx(
Langswitch,
{
inline: true,
selected: destinationLocale,
locales: destinationRoot.i18n.locales,
onChange: (locale2) => {
setDestination({
...destination,
parentId: void 0,
locale: locale2
});
}
}
)
] }),
!search && parentEntries?.map(({ id, title: title2 }) => {
return /* @__PURE__ */ jsx(BreadcrumbsItem, { children: /* @__PURE__ */ jsx(
"button",
{
onClick: () => {
setDestination({ ...destination, parentId: id });
},
children: title2
}
) }, id);
})
] }),
/* @__PURE__ */ jsx("h2", { children: title ? /* @__PURE__ */ jsx(TextLabel, { label: title }) : "Select a reference" })
] })
] }),
/* @__PURE__ */ jsxs("label", { className: styles.root.search(), children: [
/* @__PURE__ */ jsx(IcRoundSearch, { className: styles.root.search.icon() }),
/* @__PURE__ */ jsx(
"input",
{
type: "text",
placeholder: "Search",
value: search,
onChange: (event) => setSearch(event.target.value),
className: styles.root.search.input(),
...list.focusProps,
autoFocus: true
}
),
/* @__PURE__ */ jsx(Stack.Right, { children: /* @__PURE__ */ jsxs(HStack, { gap: 16, children: [
/* @__PURE__ */ jsx(
IconButton,
{
icon: IcOutlineList,
active: view === "row",
onClick: () => setView("row")
}
),
/* @__PURE__ */ jsx(
IconButton,
{
icon: IcOutlineGridView,
active: view === "thumb",
onClick: () => setView("thumb")
}
)
] }) })
] })
] }) }),
/* @__PURE__ */ jsxs(VStack, { style: { flexGrow: 1, minHeight: 0 }, children: [
/* @__PURE__ */ jsx(list.Container, { children: /* @__PURE__ */ jsx("div", { className: styles.root.results(), children: /* @__PURE__ */ jsx(
Explorer,
{
virtualized: true,
cursor,
type: view,
selectable: showMedia ? ["MediaFile"] : true,
selection: selected,
toggleSelect: handleSelect,
showMedia,
onNavigate: search ? void 0 : (entryId) => {
setDestination({ ...destination, parentId: entryId });
}
}
) }) }),
showMedia && /* @__PURE__ */ jsx(
FileUploader,
{
position: "left",
destination: {
...destination,
directory: workspaceMediaDir(config, destination.workspace)
},
max,
toggleSelect: handleSelect
}
)
] }),
/* @__PURE__ */ jsx(HStack, { as: "footer", className: styles.root.footer(), children: /* @__PURE__ */ jsx(Stack.Right, { children: /* @__PURE__ */ jsxs(HStack, { gap: 16, children: [
/* @__PURE__ */ jsx(Button, { outline: true, type: "button", onClick: onCancel, children: "Cancel" }),
/* @__PURE__ */ jsx(Button, { onClick: handleConfirm, children: "Confirm" })
] }) }) })
] }) });
}
export {
EntryPickerModal,
entryPicker
};