Skip to content

Commit

Permalink
draft: load level batches with IntersectionObserver
Browse files Browse the repository at this point in the history
  • Loading branch information
petschki committed Apr 5, 2024
1 parent 931a064 commit b79cc0c
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 70 deletions.
162 changes: 102 additions & 60 deletions src/pat/contentbrowser/src/ContentBrowser.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
import { resolveIcon } from "./resolveIcon.js";
import Upload from "../../upload/upload";
import {
currentPath,
} from "./stores.js";
import { currentPath } from "./stores.js";
// import Keydown from "svelte-keydown";
animateScroll.setGlobalOptions({
Expand All @@ -21,7 +19,7 @@
// get context stores
const config = getContext("config");
const pathCache = getContext('pathCache');
const pathCache = getContext("pathCache");
const showContentBrowser = getContext("showContentBrowser");
const selectedItems = getContext("selectedItems");
const selectedUids = getContext("selectedUids");
Expand Down Expand Up @@ -54,7 +52,7 @@
allowPathSelection: false,
hiddenInputContainer: ".upload-wrapper",
success: (fileUpload, obj) => {
contentItems.get($currentPath, null, true);
contentItems.get({ path: $currentPath, updateCache: true });
},
});
}
Expand Down Expand Up @@ -110,9 +108,15 @@
}
}
function loadMore(entries, observer) {
entries.forEach(async (entry) => {
if (entry.intersectionRatio === 0 || contentItems.loading) return;
await contentItems.loadMore(entry.target.dataset.path, $currentPath);
});
}
function itemInPath(item) {
const inPath = $currentPath.indexOf(item.getPath) != -1;
return inPath;
return $currentPath.indexOf(item.getPath) != -1;
}
function filterItems() {
Expand All @@ -121,19 +125,26 @@
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
contentItems.get($currentPath, this.value);
contentItems.get({ path: $currentPath, searchTerm: this.value });
}, 300);
}
function initIntersectionObserver() {
for (const el of document.querySelectorAll(".load-more")) {
const observer = new IntersectionObserver(loadMore);
observer.observe(el);
}
}
$: {
contentItems.get($currentPath, null, true);
contentItems.get({ path: $currentPath });
}
$: {
$contentItems;
scrollToRight();
initIntersectionObserver();
}
</script>

<!-- <Keydown paused={!$showContentBrowser} on:Escape={cancelSelection} /> -->
Expand All @@ -155,14 +166,14 @@
>
</div>
{#if $config.uploadEnabled}
<button
type="button"
class="upload btn btn-secondary btn-sm"
tabindex="0"
on:keydown={upload}
on:click={upload}
><svg use:resolveIcon={{ iconName: "upload" }} /> upload files to {$currentPath}</button
>
<button
type="button"
class="upload btn btn-secondary btn-sm"
tabindex="0"
on:keydown={upload}
on:click={upload}
><svg use:resolveIcon={{ iconName: "upload" }} /> upload files to {$currentPath}</button
>
{/if}
<button
class="btn btn-link text-white"
Expand All @@ -188,7 +199,9 @@
tabindex="0"
on:keydown={() => changePath("/")}
on:click={() => changePath("/")}
><svg use:resolveIcon={{ iconName: "house" }} /></button
><svg
use:resolveIcon={{ iconName: "house" }}
/></button
>
{/if}
{#if i > 0 && level.selectable}
Expand All @@ -202,21 +215,27 @@
{/if}
<div class="levelActions">
{#if !level.gridView}
<button class="btn btn-link btn-sm grid-view"
on:click={() => (level.gridView = true)}
>
<svg use:resolveIcon={{ iconName: "grid" }} />
</button>
<button
class="btn btn-link btn-sm grid-view"
on:click={() => (level.gridView = true)}
>
<svg
use:resolveIcon={{ iconName: "grid" }}
/>
</button>
{:else}
<button class="btn btn-link btn-sm grid-view"
on:click={() => (level.gridView = false)}
>
<svg use:resolveIcon={{ iconName: "list" }} />
</button>
<button
class="btn btn-link btn-sm grid-view"
on:click={() => (level.gridView = false)}
>
<svg
use:resolveIcon={{ iconName: "list" }}
/>
</button>
{/if}
</div>
</div>
{#each (level.items || []) as item, n}
{#each level.items || [] as item, n}
<div
class="contentItem{n % 2 == 0
? ' odd'
Expand All @@ -231,27 +250,30 @@
on:click={() => changePath(item)}
>
{#if level.gridView}
<div class="grid-preview">
{#if item.getIcon}
<img src={`${item.getURL}/@@images/image/thumb`} alt={item.Title}>
{:else}
<svg
use:resolveIcon={{
iconName: `contenttype/${item.portal_type.toLowerCase().replace(/\.| /g, "-")}`,
}}
/>
{/if}
{item.Title}
</div>
<div class="grid-preview">
{#if item.getIcon}
<img
src={`${item.getURL}/@@images/image/thumb`}
alt={item.Title}
/>
{:else}
<svg
use:resolveIcon={{
iconName: `contenttype/${item.portal_type.toLowerCase().replace(/\.| /g, "-")}`,
}}
/>
{/if}
{item.Title}
</div>
{:else}
<div title={item.portal_type}>
<svg
use:resolveIcon={{
iconName: `contenttype/${item.portal_type.toLowerCase().replace(/\.| /g, "-")}`,
}}
/>
{item.Title}
</div>
<div title={item.portal_type}>
<svg
use:resolveIcon={{
iconName: `contenttype/${item.portal_type.toLowerCase().replace(/\.| /g, "-")}`,
}}
/>
{item.Title}
</div>
{/if}
{#if item.is_folderish}
<svg
Expand All @@ -262,10 +284,22 @@
{/if}
</div>
{/each}
{#if level.items_total == 0}
<div class="contentItem">
<p>no items found.</p>
</div>
{#if level.batching}
<div class="load-more" data-path={level.path}>
<div
class="spinner-border {!contentItems.loading
? 'd-none'
: ''}"
role="status"
>
<span class="visually-hidden">Loading...</span>
</div>
</div>
{/if}
{#if level.items.length == 0}
<div class="contentItem">
<p>no items found.</p>
</div>
{/if}
</div>
{/each}
Expand All @@ -275,8 +309,11 @@
<button
class="btn btn-primary btn-sm"
disabled={!isSelectable(previewItem)}
on:click|preventDefault={() => selectItem(previewItem)}
>select "{previewItem.getPath.split("/").pop()}"</button
on:click|preventDefault={() =>
selectItem(previewItem)}
>select "{previewItem.getPath
.split("/")
.pop()}"</button
>
</div>
<div class="info">
Expand Down Expand Up @@ -344,7 +381,7 @@
display: flex;
flex-wrap: nowrap;
width: 100%;
overflow:hidden;
overflow: hidden;
flex-grow: 3;
border-left: var(--bs-border-style) var(--bs-border-color) var(--bs-border-width);
}
Expand Down Expand Up @@ -408,9 +445,9 @@
}
.contentItem .grid-preview > img {
width:95px;
height:95px;
object-fit:cover;
width: 95px;
height: 95px;
object-fit: cover;
float: left;
margin-right: 1rem;
}
Expand Down Expand Up @@ -440,4 +477,9 @@
width: 590px;
overflow-x: auto;
}
.load-more {
text-align: center;
min-height: 1rem;
}
</style>
65 changes: 60 additions & 5 deletions src/pat/contentbrowser/src/ContentStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import { request } from "./api.js";
export default function (config, pathCache) {
const store = writable([]);

store.get = async (path, searchTerm, updateCache) => {
store.loading = false;

store.get = async ({
path = "",
searchTerm = "",
updateCache = false,
}) => {
const base_url = new URL(config.base_url);
const portalPath = base_url.pathname;
path = path.replace(new RegExp(`^${portalPath}`), "");
Expand All @@ -20,7 +26,7 @@ export default function (config, pathCache) {
while (partsToShow.length > 0) {
let sub_path = partsToShow.join("/").replace(/^\//, "");
const poped = partsToShow.pop();
sub_path = pathPrefix + ((poped != "") ? `/${sub_path}`: "");
sub_path = pathPrefix + ((poped != "") ? `/${sub_path}` : "");
if (paths.indexOf(sub_path) === -1) paths.push(sub_path);
}

Expand All @@ -34,22 +40,23 @@ export default function (config, pathCache) {
let level = {};
const c = get(pathCache);
if (Object.keys(c).indexOf(p) === -1 || skipCache) {
console.log(`uncached lookup of ${p} (${isFirstPath}, ${searchTerm}, ${updateCache})`);
let query = {
base_url: config.base_url,
path: p,
};

if(isFirstPath && searchTerm){
if (isFirstPath && searchTerm) {
query["searchTerm"] = "*" + searchTerm + "*";
}
if(config.selectableTypes.length) {
if (config.selectableTypes.length) {
query["selectableTypes"] = config.selectableTypes;
}

level = await request(query);

// do not update cache when searching
if(!searchTerm) {
if (!searchTerm) {
const levelInfo = await request({
base_url: config.base_url,
levelInfoPath: p,
Expand All @@ -68,12 +75,60 @@ export default function (config, pathCache) {
});
}
} else {
console.log(`get path ${p} from cache`);
level = c[p];
}
levels = [level, ...levels];
}
store.set(levels);
};

store.loadMore = async (path, current_path) => {
store.loading = true;

const c = get(pathCache);
if (Object.keys(c).indexOf(path) === -1) {
console.log(`path not found in cache ${path}`);
return;
};
const level = c[path];

if (!level.batching) {
console.log("nothing to load");
}

const url = level.batching.next;

const response = await fetch(url, {
headers: {
"Accept": "application/json"
}
});
const json = await response.json();

if (!response.ok) {
console.log(`could not load url ${url}`);
return;
}

console.log(`loading ${url} for ${path}`);

const batch_url = new URL(url);
const b_start = parseInt(batch_url.searchParams.get("b_start"));

for (const [idx, item] of Object.entries(json.items)) {
level.items[parseInt(idx) + b_start] = item;
}
level.batching = json.batching;

pathCache.update((n) => {
n[path] = level;
return n;
});

// use store.get to update levels
store.get({path: current_path});
};

return store;
}
Loading

0 comments on commit b79cc0c

Please sign in to comment.