You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nostrweb/src/view.ts

130 lines
3.4 KiB
TypeScript

import {nip19} from 'nostr-tools';
import {elem} from './utils/dom';
type ViewOptions = {
type: 'feed'
} | {
type: 'note';
id: string;
} | {
type: 'profile';
id: string;
} | {
type: 'event';
id: string;
};
type DOMMap = {
[id: string]: HTMLElement
};
type Container = {
id: string;
options: ViewOptions,
view: HTMLElement;
content: HTMLDivElement;
dom: DOMMap;
};
const containers: Array<Container> = [];
let activeContainerIndex = -1;
export const getViewContent = () => containers[activeContainerIndex]?.content;
export const clearView = () => {
// TODO: this is clears the current view, but it should probably do this for all views
const domMap = containers[activeContainerIndex]?.dom;
Object.keys(domMap).forEach(eventId => delete domMap[eventId]);
getViewContent().replaceChildren();
};
export const getViewElem = (id: string) => {
return containers[activeContainerIndex]?.dom[id];
};
export const setViewElem = (id: string, node: HTMLElement) => {
const container = containers[activeContainerIndex];
if (container) {
container.dom[id] = node;
}
return node;
};
const mainContainer = document.querySelector('main') as HTMLElement;
const renderView = (options: ViewOptions) => {
const content = elem('div', {className: 'content'});
const dom: DOMMap = {};
switch (options.type) {
case 'profile':
const pubkey = options.id;
const detail = elem('p', {data: {'profileDetails': pubkey}});
dom[`detail-${pubkey}`] = detail;
const header = elem('header', {className: 'hero'}, [
elem('small', {}, nip19.npubEncode(pubkey)),
elem('h1', {}, pubkey),
detail,
elem('footer', {}, [
elem('span', {data:{following: pubkey}})
])
]);
dom[pubkey] = header;
content.append(header);
document.title = pubkey;
break;
case 'note':
break;
case 'feed':
break;
case 'event':
const id = options.id;
const eventHeader = elem('header', {className: 'hero'}, [
elem('h1', {}, id),
]);
dom[id] = eventHeader;
content.append(eventHeader);
document.title = id;
break;
}
const view = elem('section', {className: 'view'}, [content]);
return {content, dom, view};
};
const createContainer = (
route: string,
options: ViewOptions,
) => {
const {content, dom, view} = renderView(options);
const container = {id: route, options, view, content, dom};
mainContainer.append(view);
containers.push(container);
return container;
};
type GetViewOptions = () => ViewOptions;
export const getViewOptions: GetViewOptions = () => containers[activeContainerIndex]?.options || {type: 'feed'};
export const view = (
route: string,
options: ViewOptions,
) => {
const active = containers[activeContainerIndex];
const nextContainer = containers.find(c => c.id === route) || createContainer(route, options);
const nextContainerIndex = containers.indexOf(nextContainer);
if (nextContainerIndex === activeContainerIndex) {
return;
}
if (active) {
nextContainer.view.classList.add('view-next');
}
requestAnimationFrame(() => {
requestAnimationFrame(() => {
nextContainer.view.classList.remove('view-next', 'view-prev');
});
active?.view.classList.add(nextContainerIndex < activeContainerIndex ? 'view-next' : 'view-prev');
activeContainerIndex = nextContainerIndex;
});
};