Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pat-contentbrowser component registry #1396

Merged
merged 7 commits into from
Dec 18, 2024
Merged
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
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@11ty/eleventy-upgrade-help": "2",
"@patternslib/pat-code-editor": "4.0.1",
"@patternslib/patternslib": "9.9.16",
"@plone/registry": "^1.7.0",
"@plone/registry": "^2.1.0",
"backbone": "1.4.1",
"backbone.paginator": "2.0.8",
"bootstrap": "5.3.3",
Expand Down Expand Up @@ -134,5 +134,6 @@
],
"publishConfig": {
"access": "public"
}
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
42 changes: 42 additions & 0 deletions src/pat/contentbrowser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Show a widget to select items in an offcanvas miller-column browser.
| recentlyUsed | boolean | false | Show the recently used items dropdown. |
| recentlyUsedKey | integer | | Storage key for saving the recently used items. This is generated with fieldname and username in the patternoptions. |
| recentlyUsedMaxItems | integer | 20 | Maximum items to keep in recently used list. 0: no restriction. |
| customComponentKeys | dict | {} | Register custom components. Currently only "SelectedItem" implemented |


## Default
Expand Down Expand Up @@ -81,3 +82,44 @@ Show a widget to select items in an offcanvas miller-column browser.
data-pat-contentbrowser='{"selectableTypes": ["Image", "File"], "vocabularyUrl": "contentbrowser-test.json", "upload": true}'
/>
```

## Register custom component

Currently only for `SelectedItem` component available.

```html
<input
type="text"
class="pat-contentbrowser"
data-pat-contentbrowser='{
"customComponentKeys": {
"SelectedItem": "pat-contentbrowser.myfield.MySelectedItemComponent",
Copy link
Member

@thet thet Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@petschki @MrTango Regarding the custom component keys - isn't selectedItem the only component key which is supported yet?
I'm a bit restrained to use JSON structures as pattern options - especially JSON objects. Until now I had the impression that JSON objects prevent us from using the Patterns options parser and thus prevent us currently from using the BasePattern (that can be fixed).
But your PR here shows that this is not entirely true - You're using both the Patterns Parser and BasePattern here. However, at least the customComponentKeys is currently not formally defined in the Patterns Parser.
I'd have maybe made a flat single option out for the selectedItem component. Something like selectedItemComponent. Then we can use Patternslib CSS style Pattern option definition like data-pat-contentbrowser="selectedItemComponent: my-component;".

But since this seems to work fine, all is good!

Copy link
Member Author

@petschki petschki Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it is for now, but the idea is to make more components customizable, like the previewColumn (last clicked item) or the toolbar above, or the column itself to tweak the column listings etc ... this is not very complicated, because those are already components inside the pattern ... we only need to implement the corresponding options and then call the customizations instead of the default ones... but this could also be implemented as "flat" options like you suggested ... I'm fine with that.

},
}'
/>
```

Copy the existing component `src/SelectedItem.svelte` to your addon, customize it and register it in your JS bundle as follows:

```javascript
...
import plone_registry from "@plone/registry";
...

async function register_selecteditem_component() {
// we register our component to a custom keyname,
// which later can be used for pattern_options
const SelectedImages = (await import("./MySelectedItemComponent.svelte")).default;
plone_registry.registerComponent({
name: "pat-contentbrowser.myfield.MySelectedItemComponent",
component: SelectedImages,
});
}
register_selecteditem_component();

...

```

Note: this needs the `svelte-loader` plugin in your webpack.config.js ... see mockups webpack config for info.

9 changes: 9 additions & 0 deletions src/pat/contentbrowser/contentbrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BasePattern } from "@patternslib/patternslib/src/core/basepattern";
import Parser from "@patternslib/patternslib/src/core/parser";
import registry from "@patternslib/patternslib/src/core/registry";
import utils from "../../core/utils";
import plone_registry from "@plone/registry";

// Contentbrowser pattern

Expand Down Expand Up @@ -48,6 +49,14 @@ class Pattern extends BasePattern {
async init() {
this.el.style.display = "none";

// register default components in @plone/registry
const SelectedItem = (await import("./src/SelectedItem.svelte")).default;

plone_registry.registerComponent({
name: "pat-contentbrowser.SelectedItem",
component: SelectedItem,
});

// ensure an id on our element (TinyMCE doesn't have one)
let nodeId = this.el.getAttribute("id");
if (!nodeId) {
Expand Down
2 changes: 2 additions & 0 deletions src/pat/contentbrowser/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
export let recentlyUsedKey;
export let recentlyUsedMaxItems;
export let bSize = 20;
export let componentRegistryKeys = {};

const log = logger.getLogger("pat-contentbrowser");

Expand Down Expand Up @@ -83,6 +84,7 @@
recentlyUsedKey: recentlyUsedKey,
recentlyUsedMaxItems: recentlyUsedMaxItems,
pageSize: bSize,
componentRegistryKeys: componentRegistryKeys,
};

log.debug(`Initialized App<${fieldId}> with config ${JSON.stringify($config)}`);
Expand Down
2 changes: 1 addition & 1 deletion src/pat/contentbrowser/src/RecentlyUsed.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
{#each items.reverse() as recentlyUsed}
<li>
<a
href={recentlyUsed.getURL}
href={recentlyUsed.getURL || "#"}
petschki marked this conversation as resolved.
Show resolved Hide resolved
on:click|preventDefault={() => select(recentlyUsed)}
class="dropdown-item"
>
Expand Down
59 changes: 59 additions & 0 deletions src/pat/contentbrowser/src/SelectedItem.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script>
import { getContext } from "svelte";
import { resolveIcon } from "./utils";

// current item index of parent iteration
export let idx;
// item data
export let item;

// parent method to remove selected item from list
const unselectItem = getContext("unselectItem");

</script>

<div class="selected-item border border-secondary-subtle rounded p-2 mb-1 bg-body-tertiary" data-uuid={item.UID}>
<div class="item-info">
<!-- svelte-ignore a11y-missing-attribute -->
<button
class="btn btn-link btn-sm link-secondary"
on:click={() => unselectItem(idx)}
><svg use:resolveIcon={{ iconName: "x-circle" }} /></button
>
<div>
<span class="item-title">{item.Title}</span><br />
<span class="small">{item.path}</span>
</div>
</div>
{#if item.getURL && (item.getIcon || item.portal_type === "Image")}<img
src="{item.getURL}/@@images/image/mini"
alt={item.Title}
/>{/if}
</div>

<style>
.selected-item {
display: flex;
flex-wrap: nowrap;
align-items: start;
justify-content: space-between;
cursor: move;
}
.selected-item > * {
margin-right: 0.5rem;
display: block;
}
.selected-item button {
cursor: pointer;
padding: 0 0.375rem 0.374rem 0;
}
.selected-item .item-info {
display: flex;
align-items: start;
}
.selected-item > img {
object-fit: cover;
width: 95px;
height: 95px;
}
</style>
72 changes: 19 additions & 53 deletions src/pat/contentbrowser/src/SelectedItems.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script>
import { getContext, onMount } from "svelte";
import { flip } from "svelte/animate";
import { get_items_from_uids, resolveIcon } from "./utils.js";
import { getContext, onMount, setContext } from "svelte";
import { get_items_from_uids } from "./utils.js";
import Sortable from "sortablejs";
import _t from "../../../core/i18n-wrapper";
import events from "@patternslib/patternslib/src/core/events";
import plone_registry from "@plone/registry";

let ref;
let initializing = true;
Expand All @@ -21,6 +21,13 @@
// showContentBrowser reactive state
const showContentBrowser = getContext("showContentBrowser");

// get selectedItem component from registry.
// the registry key can be customized with pattern_options
// if an addon registers a custom component to a custom key
const RegisteredSelectedItem = plone_registry.getComponent(
$config.componentRegistryKeys?.selectedItem || "pat-contentbrowser.SelectedItem"
);

onMount(async () => {
await initializeSelectedItemsStore();
initializeSorting();
Expand All @@ -35,6 +42,10 @@
selectedUids.update(() => $selectedItems.map((x) => x.UID));
}

// use this function in "SelectedItem" component with
// const unselectItem = getContext("unselectItem")
setContext("unselectItem", unselectItem);

async function initializeSelectedItemsStore() {
const initialValue = $config.selection.length
? $config.selection
Expand Down Expand Up @@ -85,6 +96,10 @@
selectedItemsNode.dispatchEvent(events.change_event());
}

function LoadSelectedItemComponent(node, props) {
const component = new RegisteredSelectedItem.component({target: node, props: props});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const component = new RegisteredSelectedItem.component({target: node, props: props});
const component = plone_registry.getComponent({
name: "pat-contentbrowser.SelectedItem",
dependencies: [props.item["@type"]]
}).component;
return component({target: node, props: props});

NOTE: untested pseudo code

}

$: {
$selectedItems;
if ($selectedItems.length || !initializing) {
Expand All @@ -105,27 +120,7 @@
on:click={() => $showContentBrowser = $selectedItems.length ? false : true }>
{#if $selectedItems}
{#each $selectedItems as selItem, i (selItem.UID)}
<div
class="selected-item"
animate:flip={{ duration: 500 }}
data-uuid={selItem.UID}
>
<div class="item-info">
<button
class="btn btn-link btn-sm link-secondary"
on:click|stopPropagation={() => unselectItem(i)}
><svg use:resolveIcon={{ iconName: "x-circle" }} /></button
>
<div>
<span class="item-title">{selItem.Title}</span><br />
<span class="small">{selItem.path}</span>
</div>
</div>
{#if selItem.getURL && (selItem.getIcon || selItem.portal_type === "Image")}<img
src="{selItem.getURL}/@@images/image/mini"
alt={selItem.Title}
/>{/if}
</div>
<div use:LoadSelectedItemComponent={{idx:i, item:selItem}} />
{/each}
{/if}
{#if !$selectedItems}
Expand Down Expand Up @@ -155,33 +150,4 @@
padding: 0.5rem 0.5rem 0 0.5rem;
flex: 1 1 auto;
}
.content-browser-selected-items .selected-item {
border-radius: var(--bs-border-radius);
background-color: var(--bs-tertiary-bg);
border: var(--bs-border-style) var(--bs-border-color) var(--bs-border-width);
padding: 0.5rem;
margin-bottom: 0.5rem;
display: flex;
flex-wrap: nowrap;
align-items: start;
justify-content: space-between;
cursor: move;
}
.content-browser-selected-items .selected-item > * {
margin-right: 0.5rem;
display: block;
}
.content-browser-selected-items .selected-item button {
cursor: pointer;
padding: 0 0.375rem 0.374rem 0;
}
.content-browser-selected-items .selected-item .item-info {
display: flex;
align-items: start;
}
.content-browser-selected-items .selected-item > img {
object-fit: cover;
width: 95px;
height: 95px;
}
</style>
Loading
Loading