mami/src/mami.js/controls/tabs.js

174 lines
4.9 KiB
JavaScript

#include args.js
#include uniqstr.js
const MamiTabsControl = function(options) {
options = MamiArgs('options', options, define => {
define('onAdd').required().type('function').done();
define('onRemove').required().type('function').done();
define('onSwitch').required().type('function').done();
});
const onAdd = options.onAdd,
onRemove = options.onRemove,
onSwitch = options.onSwitch;
const tabs = new Map;
let currentTab;
const extractElement = elementInfo => {
let element;
if(elementInfo instanceof Element) {
element = elementInfo;
} else if('getElement' in elementInfo) {
element = elementInfo.getElement();
} else throw 'elementInfo is not a valid type';
if(!(element instanceof Element))
throw 'element is not an instance of Element';
return element;
};
const doTransition = async (transition, ctx) => {
if(transition === undefined)
return;
if(typeof transition !== 'function')
return;
await transition(ctx);
};
const getTabInfoAndId = tabInfoOrId => {
const result = {
id: undefined,
info: undefined,
};
const type = typeof tabInfoOrId;
if(type === 'string') {
result.id = tabInfoOrId;
result.info = tabs.get(result.id);
} else if(type === 'object') {
result.info = tabInfoOrId;
result.id = getExistingTabId(result.info);
} else throw 'tabInfoOrId must be an object or a string';
if(result.id === undefined || result.info === undefined)
throw 'tab not found';
return result;
};
const switchTab = async (tabInfoOrId, transition) => {
const result = getTabInfoAndId(tabInfoOrId);
const prevTab = currentTab;
currentTab = result.info;
const cbCtx = {
id: result.id,
info: result.info,
elem: extractElement(result.info),
from: undefined,
};
if(prevTab !== undefined) {
const prev = getTabInfoAndId(prevTab);
cbCtx.from = {
id: prev.id,
info: prev.info,
elem: extractElement(prev.info),
};
}
await onSwitch(cbCtx);
if(typeof result.info.onTabForeground === 'function')
await result.info.onTabForeground();
if(prevTab !== undefined) {
if(typeof prevTab.onTabBackground === 'function')
await prevTab.onTabBackground();
await doTransition(transition, cbCtx);
}
};
const getExistingTabId = tabInfo => {
if(tabInfo !== undefined)
for(const [key, value] of tabs.entries())
if(value === tabInfo)
return key;
return undefined;
};
return {
switch: switchTab,
current: () => currentTab,
currentId: () => getExistingTabId(currentTab),
currentElement: () => {
if(currentTab === undefined)
return undefined;
return extractElement(currentTab);
},
count: () => tabs.size,
has: tabInfo => getExistingTabId(tabInfo) !== undefined,
add: async (tabInfo, title) => {
if(typeof tabInfo !== 'object')
throw 'tabInfo must be an object';
let tabId = getExistingTabId(tabInfo);
if(tabId !== undefined)
throw 'tabInfo has already been added';
tabId = MamiUniqueStr(8);
tabs.set(tabId, tabInfo);
if(title !== 'string' && 'getTitle' in tabInfo)
title = tabInfo.getTitle();
const element = extractElement(tabInfo);
await onAdd({
id: tabId,
info: tabInfo,
elem: element,
title: title,
onClick: () => switchTab(tabInfo),
});
if(typeof tabInfo.onTabAdd === 'function')
await tabInfo.onTabAdd();
if(currentTab === undefined)
await switchTab(tabInfo);
return tabId;
},
remove: async tabInfoOrId => {
const result = getTabInfoAndId(tabInfoOrId);
if(!tabs.has(result.id))
throw 'this tab does not exist';
const element = extractElement(result.info);
tabs.delete(result.id);
await onRemove({
id: result.id,
info: result.info,
elem: element,
});
if(typeof tabInfo.onTabRemove === 'function')
await tabInfo.onTabRemove();
if(currentTab === result.info)
await switchTab(tabs.size > 0 ? tabs.keys().next().value : undefined);
},
};
};