Created framework for showing menus easily on top of a button click

This commit is contained in:
Niko 2022-02-26 02:41:11 +01:00
parent d9420fffca
commit 258f69cc11
7 changed files with 136 additions and 11 deletions

View File

@ -17,7 +17,7 @@
<?php tpl_styles($styles ?? null) ?>
<?php tpl('app.js') ?>
<?php tpl_scripts(['base', 'navmenu', 'carousel', 'app']) ?>
<?php tpl_scripts(['base', 'carousel', 'app']) ?>
<?php tpl_scripts($scripts ?? null) ?>
</head>
<body class="app">
@ -49,7 +49,7 @@
</div>
<?php if ($actor !== null): ?>
<div class="flex icon button noback round">
<a class="center" onclick="app.navmenu.show()">
<a class="center" onclick="app.menu.show(this, 'navprofile')">
<img class="avatar round"
src="<?php echo htmlspecialchars($actor->icon) ?>"/>
</a>

View File

@ -23,4 +23,14 @@ var app = {
collapse_text_size: 80,
},
};
app.menus = {
navprofile: {
position: 'left',
maxwidth: 35,
items: [
{ text: 'Test', onclick: "alert('test')", fa: 'times' },
{ separator: true },
],
},
};
</script>

View File

@ -45,6 +45,17 @@ fieldset label > * {
}
#menu-container.left .component.button {
text-align: right !important;
}
#menu-container.right .component.button {
text-align: left !important;
}
#menu-container #menu .button {
padding: 1em;
}
hr {
color: #00000029;
}

View File

@ -7,6 +7,65 @@ app.script = function(name, cback) {
loadScript(name, file, cback);
}
app.menu = {
show: async function(elem, cfg) {
if (typeof cfg === 'string')
cfg = app.menus[cfg];
if (cfg === undefined)
return false;
const items = cfg.items || [];
const position = cfg.position || 'left';
const maxwidth = em2px(cfg.maxwidth || 35);
const starty = elem.offsetTop + elem.offsetHeight;
var width = (document.body.clientWidth * 80 / 100);
if (width > maxwidth) width = maxwidth;
let left
if (position === 'left')
left = Math.floor((elem.offsetLeft + elem.offsetWidth) - width);
else if (position === 'right')
left = Math.floor(elem.offsetLeft);
if (left === undefined)
return false;
await app.overlay.create('overlay.menu',{
class: position,
top: starty,
width,
left
}, { removable: true }, function(k,v) {
if (k === 'loop:menu') {
var content = '';
for (var i = 0; i < items.length; i++) {
var item = items[i];
item.displayIconRight = 'none';
item.displayIconLeft = 'none';
if (item.fa !== undefined)
if (position === 'left')
item.displayIconLeft = 'initial';
else item.displayIconRight = 'initial';
item.isItem = item.separator === undefined;
item.isSep = item.separator === true;
item.class = item.class || '';
content += app.template.fill(item, v);
}
return content;
}
});
let elems;
elems = document.querySelectorAll('#menu *[select-isitem="false"]');
for (var i = 0; i < elems.length; i++) elems[i].remove();
elems = document.querySelectorAll('#menu *[select-issep="false"]');
for (var i = 0; i < elems.length; i++) elems[i].remove();
return true;
},
};
app.template = {
loadMany: async function(names, cback) {
var templates = [];
@ -297,9 +356,10 @@ app.emoji = {
}
app.overlay = {
create: function(template, data, options) {
create: async function(template, data, options, cback) {
options = options || {};
app.template.load(template, function(tpl) {
await app.template.loadMany([template], function(tpl) {
tpl = tpl[0];
const id = 'overlay-'+uuidv4();
const div = document.createElement('div');
data['oid'] = id;
@ -309,12 +369,13 @@ app.overlay = {
div.className += ' dark';
if (typeof options.css === 'string')
div.className += ` ${options.css}`;
div.innerHTML = app.template.fill(data, tpl);
div.innerHTML = app.template.fill(data, tpl, cback);
if (options.removable)
div.setAttribute('onclick', `app.overlay.remove("${id}")`);
document.body.appendChild(div);
calcHeightMobile();
});
return true;
},
remove: function(id) {
const elem = document.getElementById(id);
@ -494,7 +555,11 @@ window.onload = function(e) {
handle('pages.quiz', /^quiz\/[^\/]+?$/);
}
app.template.loadMany(['toast.confirm','toast.info','toast.warn','toast.error']);
app.template.loadMany([
'toast.confirm', 'toast.info',
'toast.warn', 'toast.error',
'overlay.menu',
]);
if (http.cache.expired('instance_emojis', 30*60)) {
http.cache.del('instance_emojis');
http.get('/api/v1/instance/emojis', {}, function(data) {

View File

@ -167,6 +167,15 @@ function isVisible(element) {
);
}
function em2px(em) {
var div = document.createElement('div');
div.style.width = `${em}em`;
document.body.appendChild(div);
const width = div.offsetWidth;
div.remove();
return width;
}
function text2html(text) {
return text.replaceAll('&gt;','>').replaceAll('&lt;','<')
.replaceAll('&quot;', '"').replaceAll('&amp;', '&');

View File

@ -1,5 +0,0 @@
app.navmenu = {
show: function() {
console.log('TODO: show menu for profile');
},
};

View File

@ -0,0 +1,35 @@
<div id="menu-container" style="
position: fixed;
display: flex;
top: {.top}px;
width: 100%;
" class="{.class}">
<div class="panel" style="
position: relative;
left: {.left}px;
width: {.width}px;
height: 100%;
padding: 0;
background: none;
">
<div id="menu" class="card-item" style="
box-shadow: 0px 1.5em 2em #0000005e;
border: none;
padding: 0;
">
{loop:menu}
<div select-isitem="{.isItem}"
class="component button noback"
onclick="{.onclick}">
<i style="display: {.displayIconRight}"
class="fa fa-{.fa} fa-fw"></i>
<span>{.text}</span>
<i style="display: {.displayIconLeft}"
class="fa fa-{.fa} fa-fw"></i>
</div>
<div select-issep="{.isSep}"
class="separator {.class}"></div>
{/loop}
</div>
</div>
</div>