Rewrite menu from scratch to support mobile screen sizes and keyboard controls

This commit is contained in:
acer 2020-10-12 01:17:56 -04:00
parent bfb85aa8e5
commit a5b84b9d0f
5 changed files with 1161 additions and 441 deletions

View File

@ -84,9 +84,11 @@
</div>
</div>
<div class="mobile_menu">
<button class="right_mobile_menu" id="mobile_menu_button" type="button"></button>
<button class="right_mobile_menu" id="mobile_menu_button" type="button">
<span class="sr_only">Toggle Menu</span>
</button>
</div>
<div class="ddsmoothmenu" id="main_menu"></div>
<nav aria-label="Main Menu" class="main_menu" id="main_menu"></nav>
<div class="hidden" id="tmp"></div>
<div id="popup"></div>
</body>

View File

@ -1,222 +1,193 @@
.mobile_menu{
display:none;
position: absolute;
width:100%;
top: 0;
:root {
--menu-dropdown-background-color: #ffffff;
--menu-dropdown-border-color: #5680C1;
--menu-dropdown-text-color: #2d2b2b;
--menu-dropdown-text-muted-color: #aaaaaa;
--menu-dropdown-hover-background-color: #E4EBF8;
--menu-dropdown-hover-text-color: #2d2d2d;
--menu-dropdown-divider-color: #e5e5e5;
}
.left_mobile_menu, .right_mobile_menu{
position:absolute;
width:50px;
height:50px;
background: url("images/sprites.png") no-repeat 11px -86px;
filter: invert(1);
display:block;
top:0;
z-index:200;
border:0;
outline:0;
cursor: pointer;
}
.left_mobile_menu{left:0;}
.right_mobile_menu{right:0;}
.ddsmoothmenu{
position:fixed;
top:0;
left:0;
width:100%;
font:12px Arial,sans-serif;
background: #2D2D2D;
background: var(--background-color-menu);
width: 100%;
padding-left:10px;
z-index:100;
}
.ddsmoothmenu ul{
z-index:100;
margin: 0;
.sr_only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
list-style-type: none;
height:30px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.ddsmoothmenu ul li{
position: relative;
display: inline-block;
float: left;
color: #2d2b2b;
height:100%;
.main_menu {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
}
.ddsmoothmenu ul ul li a{
width:100%;
.main_menu > ul.menu_bar {
display: flex;
flex-direction: row;
list-style: none;
padding: 0;
margin: 0;
height: 30px;
padding-left: 10px;
background: var(--background-color-menu);
}
.ddsmoothmenu .rightarrowclass{
display:none !important;
.main_menu > ul.menu_bar > li {
padding: 0;
overflow: hidden;
height: 100%;
}
.ddsmoothmenu ul li a{
display: inline-block;
color: #cccccc;
.main_menu > ul.menu_bar > li > a {
display: flex;
align-items: center;
font-size: 12px;
color: var(--text-color-menu);
text-decoration: none;
text-align:center;
padding: 7px 10px 8px 10px !important;
padding: 0 10px;
height: 100%;
}
.ddsmoothmenu ul ul li a{
padding-right: 25px !important;
.main_menu > ul.menu_bar > li > a::-moz-focus-inner {
border: 0;
}
.ddsmoothmenu ul li a.selected{
background-color: #FFFFFF !important;
color: #2d2b2b;
.main_menu > ul.menu_bar > li > a:focus {
outline: none;
box-shadow: 0 -3px var(--menu-dropdown-background-color) inset;
}
.ddsmoothmenu ul li ul li a.selected{
background-color:#E4EBF8 !important;
.main_menu > ul.menu_bar > li > a:hover {
background: var(--menu-dropdown-hover-background-color);
box-shadow: none;
color: var(--menu-dropdown-hover-text-color);
}
.ddsmoothmenu ul li a:hover{
background-color: #E4EBF8;
color: #2D2D2D;
.main_menu > ul.menu_bar > li > a[aria-expanded="true"] {
background: var(--menu-dropdown-background-color);
box-shadow: none;
color: var(--menu-dropdown-text-color);
}
.ddsmoothmenu .hide_ul{
position: absolute;
left: -3000px;
display: none;
visibility: hidden;
border:1px solid #5680C1;
border-top:0px;
.main_menu > ul.menu_bar > li > a > * {
pointer-events: none;
}
.ddsmoothmenu ul li ul{
position: absolute;
left: -3000px;
display: none;
visibility: hidden;
border:1px solid #5680C1;
border-top:0px;
margin-left: -1px;
height:auto;
min-width:140px;
width:auto !important;
top:30px !important;
.main_menu > ul.menu_dropdown {
display: flex;
flex-direction: column;
position: fixed;
top: 0;
left: 0;
list-style: none;
padding: 0;
margin: 0;
overflow-x: hidden;
overflow-y: auto;
max-height: calc(100vh - 60px);
min-width: 150px;
box-shadow: 0 0 0 1px var(--menu-dropdown-border-color);
background: var(--menu-dropdown-background-color);
}
.ddsmoothmenu ul li ul.expanded{
overflow: visible;
max-height: none;
.main_menu > ul.menu_dropdown > li {
padding: 0;
}
.ddsmoothmenu ul li ul li{
display: list-item;
background: #ffffff;
float: none;
height:auto;
width:100%;
}
.ddsmoothmenu ul li ul li a{
text-align:left;
}
.ddsmoothmenu ul li ul li ul{
top: 0;
border-top:1px solid #5680C1;
}
.ddsmoothmenu ul li ul li a{
padding-left: 5px;
padding-right:5px;
.main_menu > ul.menu_dropdown > li > hr {
background: none;
border: 1px solid var(--menu-dropdown-divider-color);
border-bottom: none;
margin: 0;
color: #2D2D2D;
white-space: nowrap;
}
.ddsmoothmenu ul li ul li ul{
top:0 !important;
.main_menu > ul.menu_dropdown > li > a {
display: flex;
flex-direction: row;
align-items: center;
position: relative;
height: 30px;
padding: 0 10px;
font-size: 12px;
line-height: 30px;
text-decoration: none;
color: var(--menu-dropdown-text-color);
}
.ddsmoothmenu .downarrowclass{
.main_menu > ul.menu_dropdown > li > ::-moz-focus-inner {
border: 0;
}
.main_menu > ul.menu_dropdown > li > a:focus {
outline: none;
box-shadow: 0 0 0 2px var(--menu-dropdown-hover-background-color) inset;
}
.main_menu > ul.menu_dropdown > li > a:hover {
background: var(--menu-dropdown-hover-background-color);
box-shadow: none;
color: var(--menu-dropdown-hover-text-color);
}
.main_menu > ul.menu_dropdown > li > a[aria-expanded="true"] {
background: var(--menu-dropdown-hover-background-color);
box-shadow: none;
color: var(--menu-dropdown-hover-text-color);
}
.main_menu > ul.menu_dropdown > li > a[aria-haspopup="true"]::after {
position: absolute;
top: 12px;
right: 7px;
}
.ddsmoothmenu .ddshadow{
position: absolute;
left: 0;
top: 0;
width: 0;
height: 0;
background-color: #ccc;
}
.ddsmoothmenu .mid-line{
background-color:#ff0000;
border-top:1px solid #e5e5e5;
font-size:0;
padding:0 8px 0 8px;
}
.ddsmoothmenu ul li ul li.more > a{
position:relative;
}
.ddsmoothmenu ul li ul li.more > a:before{
position: absolute;
content:">";
content: ">";
right: 9px;
width: 5px;
height: 14px;
transform: scaleY(2);
color: #808080;
}
.ddsmoothmenu ul li ul li ul{
left: calc(100% + 1px) !important;
.main_menu > ul.menu_dropdown > li > a[aria-haspopup="true"] > .name {
margin-right: 8px;
}
.ddsmoothmenu .dots::after{
content: " ...";
.main_menu > ul.menu_dropdown > li > a[target="_blank"]::after {
content: "";
width: 10px;
height: 10px;
margin-left: 5px;
background: url(images/sprites.png) no-repeat -700px 0;
opacity: 0.3;
}
.ddsmoothmenu a[data-key]:after{
position: absolute;
content: attr(data-key) " ";
color: #aaa;
font-size: 12px;
margin-left: 8px;
right:10px;
.main_menu > ul.menu_dropdown > li > a > * {
pointer-events: none;
}
.main_menu > ul.menu_dropdown > li > a > .name {
flex-grow: 1;
overflow: hidden;
white-space: nowrap;
}
.main_menu > ul.menu_dropdown > li > a > .shortcut {
flex-shrink: 1;
color: var(--menu-dropdown-text-muted-color);
}
@media screen and (max-width:700px){
.mobile_menu{
display:block;
}
.left_mobile_menu{
display:none;
}
.ddsmoothmenu{
height:50px;
}
.ddsmoothmenu ul{
width: calc(100% - 50px);
height:50px;
}
.ddsmoothmenu > ul > li > a{
height:50px;
padding-top: 15px !important;
}
.ddsmoothmenu ul li ul{
top:50px !important;
}
.ddsmoothmenu ul li ul li{
height:auto;
}
.ddsmoothmenu ul li ul li a{
height:30px;
}
.mobile_menu {
display: none;
position: absolute;
width: 100%;
top: 0;
}
@media screen and (max-width:550px){
.ddsmoothmenu{
padding-left:0;
}
.ddsmoothmenu ul{
width: calc(100% - 50px);
}
.ddsmoothmenu > ul > li{
width: calc(100% / 7);
}
.ddsmoothmenu > ul > li > a{
width:100%;
padding-left: 3px !important;
padding-right: 3px !important;
overflow: hidden;
}
.left_mobile_menu{
display:block;
}
.left_mobile_menu, .right_mobile_menu {
position: absolute;
width: 50px;
height: 50px;
background: url("images/sprites.png") no-repeat 11px -86px;
filter: invert(1);
display: block;
top: 0;
z-index: 200;
border: 0;
outline: 0;
cursor: pointer;
}
.left_mobile_menu { left:0; }
.right_mobile_menu { right:0; }
@media screen and (max-width:700px) {
.mobile_menu {
display: block;
}
.left_mobile_menu {
display: none;
}
.main_menu > ul.menu_bar {
height: 50px;
padding-left: 0;
padding-right: 50px;
}
}

View File

@ -1,204 +1,630 @@
var menu_template = `
<ul>
<li>
<a class="trn" href="#">File</a>
<ul>
<li><a class="trn" data-target="file/new.new" href="#">New</a></li>
<li><div class="mid-line"></div></li>
<li class="more">
<a class="trn" href="#">Open</a>
<ul>
<li><a class="trn dots" data-target="file/open.open_file" data-key="Drag&Drop" href="#">Open File</a></li>
<li><a class="trn dots" data-target="file/open.open_dir" href="#">Open Directory</a></li>
<li><a class="trn dots" data-target="file/open.open_webcam" href="#">Open from Webcam</a></li>
<li><a class="trn dots" data-target="file/open.open_url" href="#">Open URL</a></li>
<li><a class="trn dots" data-target="file/open.open_data_url" href="#">Open Data URL</a></li>
<li><a class="trn dots" data-target="file/open.open_template_test" href="#">Open test template</a></li>
</ul>
</li>
<li><a class="trn dots" data-target="file/search.search" href="#">Search images</a></li>
<li><div class="mid-line"></div></li>
<li><a class="trn dots" data-target="file/save.save" data-key="S" href="#">Save as</a></li>
<li><a class="trn dots" data-target="file/save.save_data_url" href="#">Save as data URL</a></li>
<li><a class="trn dots" data-target="file/print.print" data-key="Ctrl-P" href="#">Print</a></li>
<li><div class="mid-line"></div></li>
<li><a class="trn" data-target="file/quicksave.quicksave" data-key="F9" href="#">Quick save</a></li>
<li><a class="trn" data-target="file/quickload.quickload" data-key="F10" href="#">Quick load</a></li>
</ul>
</li>
<li>
<a class="trn" href="#">Edit</a>
<ul>
<li><a class="trn" data-target="edit/undo.undo" href="#">Undo</a></li>
<li><div class="mid-line"></div></li>
<li><a class="trn" data-target="edit/selection.delete" data-key="Del" href="#">Delete selection</a></li>
<li><a class="trn" data-target="layer/new.new_selection" href="#">Copy selection</a></li>
<li><a class="trn" data-target="edit/paste.paste" data-key="Ctrl+V" href="#">Paste</a></li>
<li><div class="mid-line"></div></li>
<li><a class="trn" data-target="edit/selection.select_all" href="#">Select all</a></li>
</ul>
</li>
<li>
<a class="trn" href="#">Image</a>
<ul>
<li><a class="trn dots" data-target="image/information.information" href="#">Information</a></li>
<li><a class="trn dots" data-target="image/size.size" href="#">Size</a></li>
<li><a class="trn dots" data-target="image/trim.trim" data-key="T" href="#">Trim</a>
<li class="more">
<a class="trn" href="#">Zoom</a>
<ul>
<li><a class="trn" data-target="image/zoom.in" href="#">Zoom In</a></li>
<li><a class="trn" data-target="image/zoom.out" href="#">Zoom Out</a></li>
<li><div class="mid-line"></div></li>
<li><a class="trn" data-target="image/zoom.original" href="#">Original size</a></li>
<li><a class="trn" data-target="image/zoom.auto" href="#">Fit window</a></li>
</ul>
</li>
<li><div class="mid-line"></div></li>
<li><a class="trn dots" data-target="image/resize.resize" href="#">Resize</a></li>
<li><a class="trn dots" data-target="image/rotate.rotate" href="#">Rotate</a></li>
<li class="more">
<a class="trn" href="#">Flip</a>
<ul>
<li><a class="trn" data-target="image/flip.vertical" href="#">Vertical</a></li>
<li><a class="trn" data-target="image/flip.horizontal" href="#">Horizontal</a></li>
</ul>
</li>
<li><a class="trn dots" data-target="image/translate.translate" href="#">Translate</a></li>
<li><a class="trn dots" data-target="image/opacity.opacity" href="#">Opacity</a></li>
<li><div class="mid-line"></div></li>
<li><a class="trn dots" data-target="image/color_corrections.color_corrections" href="#">Color corrections</a></li>
<li><a class="trn" data-target="image/auto_adjust.auto_adjust" href="#">Auto adjust colors</a></li>
<li><a class="trn" data-target="image/decrease_colors.decrease_colors" href="#">Decrease color depth</a></li>
<li><a class="trn dots" data-target="image/palette.palette" href="#">Color palette</a></li>
<li><a class="trn dots" data-target="image/grid.grid" data-key="G" href="#">Grid</a></li>
<li><div class="mid-line"></div></li>
<li><a class="trn dots" data-target="image/histogram.histogram" data-key="H" href="#">Histogram</a></li>
</ul>
</li>
<li>
<a class="trn" href="#">Layers</a>
<ul>
<li><a class="trn" data-target="layer/new.new" data-key="N" href="#">New</a></li>
<li><a class="trn" data-target="layer/new.new_selection" href="#">New from selection</a></li>
<li><div class="mid-line"></div></li>
<li><a class="trn" data-target="layer/duplicate.duplicate" href="#">Duplicate</a></li>
<li><a class="trn" data-target="layer/visibility.toggle" href="#">Show / Hide</a></li>
<li><a class="trn" data-target="layer/delete.delete" href="#">Delete</a></li>
<li><a class="trn" data-target="layer/raster.raster" href="#">Convert to raster</a></li>
<li><div class="mid-line"></div></li>
<li class="more">
<a class="trn" href="#">Move</a>
<ul>
<li><a class="trn" data-target="layer/move.up" href="#">Up</a></li>
<li><a class="trn" data-target="layer/move.down" href="#">Down</a></li>
</ul>
</li>
<li><a class="trn dots" data-target="layer/composition.composition" href="#">Composition</a></li>
<li><a class="trn dots" data-target="layer/rename.rename" href="#">Rename</a></li>
<li><a class="trn" data-target="layer/clear.clear" href="#">Clear</a></li>
<li><div class="mid-line"></div></li>
<li><a class="trn" data-target="layer/differences.differences" href="#">Differences Down</a></li>
<li><a class="trn" data-target="layer/merge.merge" href="#">Merge Down</a></li>
<li><a class="trn" data-target="layer/flatten.flatten" href="#">Flatten Image</a></li>
</ul>
</li>
<li>
<a class="trn" href="#">Effects</a>
<ul id="effects_list">
<li><div class="mid-line"></div></li>
<li class="more">
<a class="trn" href="#">CSS filters</a>
<ul>
<li><a class="trn dots" data-target="effects/blur.blur" href="#">Gaussian Blur</a>
<li><a class="trn dots" data-target="effects/brightness.brightness" href="#">Brightness</a>
<li><a class="trn dots" data-target="effects/contrast.contrast" href="#">Contrast</a>
<li><a class="trn dots" data-target="effects/grayscale.grayscale" href="#">Grayscale</a>
<li><a class="trn dots" data-target="effects/hue_rotate.hue_rotate" href="#">Hue Rotate</a>
<li><a class="trn dots" data-target="effects/negative.negative" href="#">Negative</a>
<li><a class="trn dots" data-target="effects/saturate.saturate" href="#">Saturate</a>
<li><a class="trn dots" data-target="effects/sepia.sepia" href="#">Sepia</a>
<li><a class="trn dots" data-target="effects/shadow.shadow" href="#">Shadow</a>
</ul>
</li>
<li><a class="trn dots" data-target="effects/black_and_white.black_and_white" href="#">Black and White</a>
<li><a class="trn dots" data-target="effects/blueprint.blueprint" href="#">Blueprint</a>
<li><a class="trn dots" data-target="effects/box_blur.box_blur" href="#">Box Blur</a>
<li><a class="trn dots" data-target="effects/denoise.denoise" href="#">Denoise</a>
<li><a class="trn dots" data-target="effects/dither.dither" href="#">Dither</a>
<li><a class="trn dots" data-target="effects/dot_screen.dot_screen" href="#">Dot Screen</a>
<li><a class="trn dots" data-target="effects/edge.edge" href="#">Edge</a>
<li><a class="trn dots" data-target="effects/emboss.emboss" href="#">Emboss</a>
<li><a class="trn dots" data-target="effects/enrich.enrich" href="#">Enrich</a>
<li><a class="trn dots" data-target="effects/grains.grains" href="#">Grains</a>
<li><a class="trn dots" data-target="effects/heatmap.heatmap" href="#">Heatmap</a>
<li><a class="trn dots" data-target="effects/mosaic.mosaic" href="#">Mosaic</a>
<li><a class="trn dots" data-target="effects/night_vision.night_vision" href="#">Night Vision</a>
<li><a class="trn dots" data-target="effects/oil.oil" href="#">Oil</a>
<li><a class="trn dots" data-target="effects/pencil.pencil" href="#">Pencil</a>
<li><a class="trn dots" data-target="effects/sharpen.sharpen" href="#">Sharpen</a>
<li><a class="trn dots" data-target="effects/solarize.solarize" href="#">Solarize</a>
<li><a class="trn dots" data-target="effects/tilt_shift.tilt_shift" href="#">Tilt Shift</a>
<li><a class="trn dots" data-target="effects/vignette.vignette" href="#">Vignette</a>
<li><a class="trn dots" data-target="effects/vibrance.vibrance" href="#">Vibrance</a>
<li><a class="trn dots" data-target="effects/vintage.vintage" href="#">Vintage</a>
<li><a class="trn dots" data-target="effects/zoom_blur.zoom_blur" href="#">Zoom Blur</a>
</ul>
</li>
<li>
<a class="trn" href="#">Tools</a>
<ul>
<li><div class="mid-line"></div></li>
<li><a class="trn dots" data-target="tools/borders.borders" href="#">Borders</a></li>
<li><a class="trn" data-target="tools/sprites.sprites" href="#">Sprites</a></li>
<li><a class="trn" data-target="tools/keypoints.keypoints" href="#">Key-points</a></li>
<li><a class="trn dots" data-target="tools/content_fill.content_fill" href="#">Content fill</a></li>
<li><div class="mid-line"></div></li>
<li><a class="trn dots" data-target="tools/color_to_alpha.color_to_alpha" href="#">Color to alpha</a></li>
<li><a class="trn dots" data-target="tools/color_zoom.color_zoom" href="#">Color Zoom</a></li>
<li><a class="trn dots" data-target="tools/replace_color.replace_color" href="#">Replace Color</a></li>
<li><a class="trn dots" data-target="tools/restore_alpha.restore_alpha" href="#">Restore alpha</a></li>
<li class="more">
<a class="trn" href="#">External</a>
<ul>
<li><a class="trn external" target="_blank" href="https://tinypng.com">TINYPNG - compress PNG and JPEG</a>
<li><a class="trn external" target="_blank" href="https://www.remove.bg">REMOVE.BG - remove Image Background</a>
<li><a class="trn external" target="_blank" href="https://www.pngtosvg.com">PNGTOSVG - convert image to SVG</a>
<li><a class="trn external" target="_blank" href="https://squoosh.app">SQUOOSH - compress and compare images</a>
</ul>
</li>
<li><div class="mid-line"></div></li>
<li><a class="trn dots" data-target="tools/settings.settings" href="#">Settings</a></li>
</ul>
</li>
<li>
<a class="trn" href="#">Help</a>
<ul>
<li><a class="trn dots" data-target="help/shortcuts.shortcuts" href="#">Keyboard Shortcuts</a></li>
<li><a class="trn external" target="_blank" href="https://github.com/viliusle/miniPaint/issues">Report issues</a></li>
<li class="more">
<a class="trn" href="#">Language</a>
<ul>
<li><a data-target="help/translate.translate.en" href="#">English</a>
<li><div class="mid-line"></div></li>
<li><a data-target="help/translate.translate.zh" href="#">简体中文</a>
<li><a data-target="help/translate.translate.es" href="#">Español</a>
<li><a data-target="help/translate.translate.fr" href="#">French</a>
<li><a data-target="help/translate.translate.de" href="#">German</a>
<li><a data-target="help/translate.translate.it" href="#">Italiano</a>
<li><a data-target="help/translate.translate.ja" href="#">Japanese</a>
<li><a data-target="help/translate.translate.ko" href="#">Korean</a>
<li><a data-target="help/translate.translate.lt" href="#">Lietuvių</a>
<li><a data-target="help/translate.translate.pt" href="#">Portuguese</a>
<li><a data-target="help/translate.translate.ru" href="#">Russian</a>
<li><a data-target="help/translate.translate.tr" href="#">Turkish</a>
</ul>
</li>
<li><div class="mid-line"></div></li>
<li><a class="trn dots" data-target="help/about.about" href="#">About</a></li>
</ul>
</li>
</ul>
`;
const menuDefinition = [
{
name: 'File',
children: [
{
name: 'New',
target: 'file/new.new'
},
{
divider: true
},
{
name: 'Open',
children: [
{
name: 'Open File',
shortcut: 'Drag&Drop',
target: 'file/open.open_file'
},
{
name: 'Open Directory',
target: 'file/open.open_dir'
},
{
name: 'Open from Webcam',
target: 'file/open.open_webcam'
},
{
name: 'Open URL',
target: 'file/open.open_url'
},
{
name: 'Open Data URL',
target: 'file/open.open_data_url'
},
{
name: 'Open Test Template',
target: 'file/open.open_template_test'
}
]
},
{
name: 'Search Images',
ellipsis: true,
target: 'file/search.search'
},
{
divider: true
},
{
name: 'Save As',
ellipsis: true,
shortcut: 'S',
target: 'file/save.save'
},
{
name: 'Save As Data URL',
ellipsis: true,
target: 'file/save.save_data_url'
},
{
name: 'Print',
ellipsis: true,
shortcut: 'Ctrl-P',
target: 'file/print.print'
},
{
divider: true
},
{
name: 'Quick Save',
shortcut: 'F9',
target: 'file/quicksave.quicksave'
},
{
name: 'Quick Load',
shortcut: 'F10',
target: 'file/quickload.quickload'
}
]
},
{
name: 'Edit',
children: [
{
name: 'Undo',
target: 'edit/undo.undo'
},
{
divider: true
},
{
name: 'Delete Selection',
shortcut: 'Del',
target: 'edit/selection.delete'
},
{
name: 'Copy Selection',
target: 'layer/new.new_selection'
},
{
name: 'Paste',
shortcut: 'Ctrl+V',
target: 'edit/paste.paste'
},
{
divider: true
},
{
name: 'Select All',
target: 'edit/selection.select_all'
}
]
},
{
name: 'Image',
children: [
{
name: 'Information',
ellipsis: true,
target: 'image/information.information'
},
{
name: 'Size',
ellipsis: true,
target: 'image/size.size'
},
{
name: 'Trim',
ellipsis: true,
shortcut: 'T',
target: 'image/trim.trim'
},
{
name: 'Zoom',
children: [
{
name: 'Zoom In',
target: 'image/zoom.in'
},
{
name: 'Zoom Out',
target: 'image/zoom.out'
},
{
divider: true
},
{
name: 'Original Size',
target: 'image/zoom.original'
},
{
name: 'Fit Window',
target: 'image/zoom.auto'
}
]
},
{
divider: true
},
{
name: 'Resize',
ellipsis: true,
target: 'image/resize.resize'
},
{
name: 'Rotate',
ellipsis: true,
target: 'image/rotate.rotate'
},
{
name: 'Flip',
children: [
{
name: 'Vertical',
target: 'image/flip.vertical'
},
{
name: 'Horizontal',
target: 'image/flip.horizontal'
}
]
},
{
name: 'Translate',
ellipsis: true,
target: 'image/translate.translate'
},
{
name: 'Opacity',
ellipsis: true,
target: 'image/opacity.opacity'
},
{
divider: true
},
{
name: 'Color Correction',
ellipsis: true,
target: 'image/color_corrections.color_corrections'
},
{
name: 'Auto Adjust Colors',
target: 'image/auto_adjust.auto_adjust'
},
{
name: 'Decrease Color Depth',
target: 'image/decrease_colors.decrease_colors'
},
{
name: 'Color Palette',
ellipsis: true,
target: 'image/palette.palette'
},
{
name: 'Grid',
ellipsis: true,
shortcut: 'G',
target: 'image/grid.grid'
},
{
divider: true
},
{
name: 'Histogram',
ellipsis: true,
shortcut: 'H',
target: 'image/histogram.histogram'
}
]
},
{
name: 'Layers',
children: [
{
name: 'New',
shortcut: 'N',
target: 'layer/new.new'
},
{
name: 'New from Selection',
target: 'layer/new.new_selection'
},
{
divider: true
},
{
name: 'Duplicate',
target: 'layer/duplicate.duplicate'
},
{
name: 'Show / Hide',
target: 'layer/visibility.toggle'
},
{
name: 'Delete',
target: 'layer/delete.delete'
},
{
name: 'Convert to Raster',
target: 'layer/raster.raster'
},
{
divider: true
},
{
name: 'Move',
children: [
{
name: 'Up',
target: 'layer/move.up'
},
{
name: 'Down',
target: 'layer/move.down'
}
]
},
{
name: 'Composition',
ellipsis: true,
target: 'layer/composition.composition'
},
{
name: 'Rename',
ellipsis: true,
target: 'layer/rename.rename'
},
{
name: 'Clear',
target: 'layer/clear.clear'
},
{
divider: true
},
{
name: 'Differences Down',
target: 'layer/differences.differences'
},
{
name: 'Merge Down',
target: 'layer/merge.merge'
},
{
name: 'Flatten Image',
target: 'layer/flatten.flatten'
}
]
},
{
name: 'Effects',
children: [
{
name: 'CSS Filters',
children: [
{
name: 'Gaussian Blur',
ellipsis: true,
target: 'effects/blur.blur'
},
{
name: 'Brightness',
ellipsis: true,
target: 'effects/brightness.brightness'
},
{
name: 'Contrast',
ellipsis: true,
target: 'effects/contrast.contrast'
},
{
name: 'Grayscale',
ellipsis: true,
target: 'effects/grayscale.grayscale'
},
{
name: 'Hue Rotate',
ellipsis: true,
target: 'effects/hue_rotate.hue_rotate'
},
{
name: 'Negative',
ellipsis: true,
target: 'effects/negative.negative'
},
{
name: 'Saturate',
ellipsis: true,
target: 'effects/saturate.saturate'
},
{
name: 'Sepia',
ellipsis: true,
target: 'effects/sepia.sepia'
},
{
name: 'Shadow',
ellipsis: true,
target: 'effects/shadow.shadow'
},
]
},
{
name: 'Black and White',
ellipsis: true,
target: 'effects/black_and_white.black_and_white'
},
{
name: 'Blueprint',
ellipsis: true,
target: 'effects/blueprint.blueprint'
},
{
name: 'Box Blur',
ellipsis: true,
target: 'effects/box_blur.box_blur'
},
{
name: 'Denoise',
ellipsis: true,
target: 'effects/denoise.denoise'
},
{
name: 'Dither',
ellipsis: true,
target: 'effects/dither.dither'
},
{
name: 'Dot Screen',
ellipsis: true,
target: 'effects/dot_screen.dot_screen'
},
{
name: 'Edge',
ellipsis: true,
target: 'effects/edge.edge'
},
{
name: 'Emboss',
ellipsis: true,
target: 'effects/emboss.emboss'
},
{
name: 'Enrich',
ellipsis: true,
target: 'effects/enrich.enrich'
},
{
name: 'Grains',
ellipsis: true,
target: 'effects/grains.grains'
},
{
name: 'Heatmap',
ellipsis: true,
target: 'effects/heatmap.heatmap'
},
{
name: 'Mosaic',
ellipsis: true,
target: 'effects/mosaic.mosaic'
},
{
name: 'Night Vision',
ellipsis: true,
target: 'effects/night_vision.night_vision'
},
{
name: 'Oil',
ellipsis: true,
target: 'effects/oil.oil'
},
{
name: 'Pencil',
ellipsis: true,
target: 'effects/pencil.pencil'
},
{
name: 'Sharpen',
ellipsis: true,
target: 'effects/sharpen.sharpen'
},
{
name: 'Solarize',
ellipsis: true,
target: 'effects/solarize.solarize'
},
{
name: 'Tilt Shift',
ellipsis: true,
target: 'effects/tilt_shift.tilt_shift'
},
{
name: 'Vignette',
ellipsis: true,
target: 'effects/vignette.vignette'
},
{
name: 'Vibrance',
ellipsis: true,
target: 'effects/vibrance.vibrance'
},
{
name: 'Vintage',
ellipsis: true,
target: 'effects/vintage.vintage'
},
{
name: 'Zoom Blur',
ellipsis: true,
target: 'effects/zoom_blur.zoom_blur'
}
]
},
{
name: 'Tools',
children: [
{
name: 'Borders',
ellipsis: true,
target: 'tools/borders.borders'
},
{
name: 'Sprites',
target: 'tools/sprites.sprites'
},
{
name: 'Key-Points',
target: 'tools/keypoints.keypoints'
},
{
name: 'Content Fill',
ellipsis: true,
target: 'tools/content_fill.content_fill'
},
{
divider: true
},
{
name: 'Color to Alpha',
ellipsis: true,
target: 'tools/color_to_alpha.color_to_alpha'
},
{
name: 'Color Zoom',
ellipsis: true,
target: 'tools/color_zoom.color_zoom'
},
{
name: 'Replace Color',
ellipsis: true,
target: 'tools/replace_color.replace_color'
},
{
name: 'Restore Alpha',
ellipsis: true,
target: 'tools/restore_alpha.restore_alpha'
},
{
name: 'External',
children: [
{
name: 'TINYPNG - Compress PNG and JPEG',
href: 'https://tinypng.com'
},
{
name: 'REMOVE.BG - Remove Image Background',
href: 'https://www.remove.bg'
},
{
name: 'PNGTOSVG - Convert Image to SVG',
href: 'https://www.pngtosvg.com'
},
{
name: 'SQUOOSH - Compress and Compare Images',
href: 'https://squoosh.app'
}
]
},
{
divider: true
},
{
name: 'Settings',
ellipsis: true,
target: 'tools/settings.settings'
}
]
},
{
name: 'Help',
children: [
{
name: 'Keyboard Shortcuts',
ellipsis: true,
target: 'help/shortcuts.shortcuts'
},
{
name: 'Report Issues',
href: 'https://github.com/viliusle/miniPaint/issues'
},
{
name: 'Language',
children: [
{
name: 'English',
target: 'help/translate.translate.en'
},
{
divider: true
},
{
name: '简体中文',
target: 'help/translate.translate.zh'
},
{
name: 'Español',
target: 'help/translate.translate.es'
},
{
name: 'Français',
target: 'help/translate.translate.fr'
},
{
name: 'Deutsch',
target: 'help/translate.translate.de'
},
{
name: 'Italiano',
target: 'help/translate.translate.it'
},
{
name: '日本語',
target: 'help/translate.translate.ja'
},
{
name: '한국어',
target: 'help/translate.translate.ko'
},
{
name: 'Lietuvių',
target: 'help/translate.translate.lt'
},
{
name: 'Português',
target: 'help/translate.translate.pt'
},
{
name: 'русский язык',
target: 'help/translate.translate.ru'
},
{
name: 'Türkçe',
target: 'help/translate.translate.tr'
}
]
},
{
divider: true
},
{
name: 'About',
target: 'help/about.about'
}
]
}
];
export default menu_template;
export default menuDefinition;

View File

@ -124,34 +124,23 @@ class Base_gui_class {
var _this = this;
//menu events
var targets = document.querySelectorAll('#main_menu a');
for (var i = 0; i < targets.length; i++) {
if (targets[i].dataset.target == undefined)
continue;
targets[i].addEventListener('click', function (event) {
var parts = this.dataset.target.split('.');
var module = parts[0];
var function_name = parts[1];
var param = parts[2];
this.GUI_menu.on('select_target', (target) => {
var parts = target.split('.');
var module = parts[0];
var function_name = parts[1];
var param = parts[2];
//close menu
var menu = document.querySelector('#main_menu .selected');
if (menu != undefined) {
menu.click();
}
//call module
if (_this.modules[module] == undefined) {
alertify.error('Modules class not found: ' + module);
return;
}
if (_this.modules[module][function_name] == undefined) {
alertify.error('Module function not found. ' + module + '.' + function_name);
return;
}
_this.modules[module][function_name](param);
});
}
//call module
if (this.modules[module] == undefined) {
alertify.error('Modules class not found: ' + module);
return;
}
if (this.modules[module][function_name] == undefined) {
alertify.error('Module function not found. ' + module + '.' + function_name);
return;
}
this.modules[module][function_name](param);
});
//registerToggleAbility
var targets = document.querySelectorAll('.toggle');

View File

@ -4,37 +4,369 @@
*/
import config from './../../config.js';
import menu_template from './../../config-menu.js';
import ddsmoothmenu from './../../libs/menu.js';
import menuDefinition from './../../config-menu.js';
/**
* class responsible for rendering main menu
*/
class GUI_menu_class {
constructor() {
this.eventSubscriptions = {};
this.dropdownMaxHeightMargin = 15;
this.menuContainer = null;
this.menuBarNode = null;
this.lastFocusedMenuBarLink = 0;
this.dropdownStack = [];
}
render_main() {
document.getElementById('main_menu').innerHTML = menu_template;
ddsmoothmenu.init({
mainmenuid: "main_menu",
method: 'toggle', //'hover' (default) or 'toggle'
contentsource: "markup",
this.menuContainer = document.getElementById('main_menu');
let menuTemplate = '<ul class="menu_bar" role="menubar" tabindex="0">';
for (let i = 0; i < menuDefinition.length; i++) {
const item = menuDefinition[i];
menuTemplate += this.generate_menu_bar_item_template(item, i);
}
menuTemplate += '</ul>';
this.menuContainer.innerHTML = menuTemplate;
this.menuBarNode = this.menuContainer.querySelector('[role="menubar"]');
this.menuContainer.addEventListener('click', (event) => { return this.on_click_menu(event); }, true);
this.menuContainer.addEventListener('keydown', (event) => { return this.on_key_down_menu(event); }, true);
this.menuBarNode.addEventListener('focus', (event) => { return this.on_focus_menu_bar(event); });
this.menuBarNode.addEventListener('blur', (event) => { return this.on_blur_menu_bar(event); });
this.menuBarNode.querySelectorAll('a').forEach((link) => {
link.addEventListener('focus', (event) => { return this.on_focus_menu_bar_link(event); });
});
document.body.addEventListener('mousedown', (event) => { return this.on_mouse_down_body(event); }, true);
document.body.addEventListener('touchstart', (event) => { return this.on_mouse_down_body(event); }, true);
window.addEventListener('resize', (event) => { return this.on_resize_window(event); }, true)
}
on(eventName, callback) {
if (!this.eventSubscriptions[eventName]) {
this.eventSubscriptions[eventName] = [];
}
if (!this.eventSubscriptions[eventName].includes(callback)) {
this.eventSubscriptions[eventName].push(callback);
}
}
emit(eventName, payload) {
if (this.eventSubscriptions[eventName]) {
for (let callback of this.eventSubscriptions[eventName]) {
callback(payload);
}
}
}
generate_menu_bar_item_template(definition, index) {
return `
<li>
<a id="main_menu_0_${index}" role="menuitem" tabindex="-1" aria-haspopup="true" aria-expanded="false"
href="javascript:void(0)" data-level="0" data-index="${ index }"><span class="name">${ definition.name }</span></a>
</li>
`.trim();
}
generate_menu_dropdown_item_template(definition, level, index) {
if (definition.divider) {
return `
<li role="presentation">
<hr>
</li>
`.trim();
} else {
return `
<li>
<a id="main_menu_${ level }_${ index }" role="menuitem" aria-haspopup="${ (!!definition.children) + '' }"
href="${ definition.href ? definition.href : 'javascript:void(0)' }"
target="${ definition.href ? '_blank' : '_self' }"
data-level="${ level }" data-index="${ index }">
<span class="name">${ definition.name }${ definition.ellipsis ? ' ...' : '' }</span>
${ !!definition.shortcut ? `
<span class="shortcut"><span class="sr_only">Shortcut Key:</span> ${ definition.shortcut }</span>
` : `` }
</a>
</li>
`.trim();
}
}
on_mouse_down_body(event) {
const target = event.touches ? event.touches[0].target : event.target;
// Clicked outside of menu; close dropdowns.
if (target && !this.menuContainer.contains(target)) {
this.close_child_dropdowns(0);
}
}
on_focus_menu_bar(event) {
if (document.activeElement === this.menuBarNode) {
let lastFocusedLink = this.menuBarNode.querySelector(`[data-index="${ this.lastFocusedMenuBarLink }"]`);
if (!lastFocusedLink) {
lastFocusedLink = this.menuBarNode.querySelector('a');
}
lastFocusedLink.focus();
}
}
on_focus_menu_bar_link(event) {
this.lastFocusedMenuBarLink = parseInt(event.target.getAttribute('data-index'), 10) || 0;
}
on_blur_menu_bar(event) {
// TODO
}
on_key_down_menu(event) {
const key = event.key;
const activeElement = document.activeElement;
if (activeElement && activeElement.tagName === 'A') {
const linkLevel = parseInt(activeElement.getAttribute('data-level'), 10) || 0;
const linkIndex = parseInt(activeElement.getAttribute('data-index'), 10) || 0;
const menuParent = activeElement.closest('ul');
if (linkLevel === 0) {
if (['Right', 'ArrowRight'].includes(event.key)) {
let nextLink = menuParent.querySelector(`[data-index="${ linkIndex + 1 }"]`);
if (!nextLink) {
nextLink = menuParent.querySelector(`[data-index="0"]`);
}
nextLink.focus();
}
else if (['Left', 'ArrowLeft'].includes(event.key)) {
let previousLink = menuParent.querySelector(`[data-index="${ linkIndex - 1 }"]`);
if (!previousLink) {
previousLink = menuParent.querySelector(`[data-index="${ menuParent.querySelectorAll('[data-index]').length - 1 }"]`);
}
previousLink.focus();
}
else if (['Down', 'ArrowDown'].includes(event.key)) {
if (activeElement.getAttribute('aria-haspopup') === 'true') {
event.preventDefault();
activeElement.click();
}
}
else if ([' ', 'Enter'].includes(event.key)) {
event.preventDefault();
activeElement.click();
}
} else {
if (['Up', 'ArrowUp'].includes(event.key)) {
event.preventDefault();
let previousLink = menuParent.querySelector(`[data-index="${ linkIndex - 1 }"]`);
if (!previousLink) {
previousLink = menuParent.querySelector(`[data-index="${ linkIndex - 2 }"]`); // Skip dividers
}
if (!previousLink) {
previousLink = menuParent.querySelector(`[data-index="${ this.dropdownStack[linkLevel - 1].children.length - 1 }"]`);
}
previousLink.focus();
}
else if (['Down', 'ArrowDown'].includes(event.key)) {
event.preventDefault();
let nextLink = menuParent.querySelector(`[data-index="${ linkIndex + 1 }"]`);
if (!nextLink) {
nextLink = menuParent.querySelector(`[data-index="${ linkIndex + 2 }"]`); // Skip dividers
}
if (!nextLink) {
nextLink = menuParent.querySelector(`[data-index="0"]`);
}
nextLink.focus();
}
else if (['Right', 'ArrowRight'].includes(event.key)) {
const menuBarLinkIndex = parseInt(this.dropdownStack[0].opener.getAttribute('data-index'), 10) || 0;
let nextLink = this.menuBarNode.querySelector(`[data-index="${ menuBarLinkIndex + 1 }"]`);
if (!nextLink) {
nextLink = this.menuBarNode.querySelector(`[data-index="0"]`);
}
nextLink.click();
}
else if (['Left', 'ArrowLeft'].includes(event.key)) {
const menuBarLinkIndex = parseInt(this.dropdownStack[0].opener.getAttribute('data-index'), 10) || 0;
let previousLink = this.menuBarNode.querySelector(`[data-index="${ menuBarLinkIndex - 1 }"]`);
if (!previousLink) {
previousLink = this.menuBarNode.querySelector(`[data-index="${ this.menuBarNode.querySelectorAll('[data-index]').length - 1 }"]`);
}
previousLink.click();
}
else if ([' ', 'Enter'].includes(event.key)) {
event.preventDefault();
activeElement.click();
}
else if (['Esc', 'Escape'].includes(event.key)) {
const opener = this.dropdownStack[linkLevel - 1].opener;
opener.click();
opener.focus();
}
}
}
}
on_click_menu(event) {
const target = event.target.closest('a');
// Any link in the menu is clicked.
if (target && target.tagName === 'A') {
const hasPopup = target.getAttribute('aria-haspopup') === 'true';
if (hasPopup) {
this.toggle_dropdown(target, event.isTrusted);
} else {
this.trigger_link(target);
}
} else {
this.close_child_dropdowns(0);
}
}
on_resize_window(event) {
if (this.dropdownStack.length > 0) {
this.position_dropdowns();
}
}
toggle_dropdown(opener, isTrusted) {
const linkLevel = parseInt(opener.getAttribute('data-level'), 10) || 0;
const linkIndex = parseInt(opener.getAttribute('data-index'), 10) || 0;
if (opener.getAttribute('aria-expanded') === 'true') {
this.close_child_dropdowns(linkLevel);
} else {
const parentList = opener.closest('ul');
parentList.querySelectorAll('a').forEach((item) => {
item.setAttribute('aria-expanded', 'false');
});
opener.setAttribute('aria-expanded', true);
this.create_dropdown(opener, linkLevel, linkIndex, !isTrusted);
}
}
trigger_link(link) {
const level = parseInt(link.getAttribute('data-level'), 10) || 0;
const index = parseInt(link.getAttribute('data-index'), 10) || 0;
// Find link definition
let children = menuDefinition;
for (let i = 0; i < level; i++) {
const childIndex = this.dropdownStack[i] != null ? this.dropdownStack[i].index : index;
children = children[childIndex].children;
}
let definition = children[index];
// Close the dropdown
this.close_child_dropdowns(0);
// Emit callback events for triggered links
if (definition.target) {
this.emit('select_target', definition.target);
}
else if (definition.href) {
this.emit('select_href', definition.href);
}
}
close_child_dropdowns(level) {
for (let i = this.dropdownStack.length - 1; i >= 0; i--) {
if (i >= level) {
this.dropdownStack[i].element.parentNode.removeChild(this.dropdownStack[i].element);
this.dropdownStack[i].opener.setAttribute('aria-expanded', false);
}
}
this.dropdownStack = this.dropdownStack.slice(0, level);
}
create_dropdown(opener, level, index, focusAfterCreation) {
this.close_child_dropdowns(level);
// Find child list in the menu definition
let children = menuDefinition;
for (let i = 0; i <= level; i++) {
const childIndex = this.dropdownStack[i] != null ? this.dropdownStack[i].index : index;
children = children[childIndex].children;
}
// Create the dropdown element, place it in DOM & position it
let dropdownElement = document.createElement('ul');
dropdownElement.className = 'menu_dropdown';
dropdownElement.role = 'menu';
dropdownElement.setAttribute('aria-labelledby', 'main_menu_' + level + '_' + index);
let dropdownTemplate = '';
for (let i = 0; i < children.length; i++) {
dropdownTemplate += this.generate_menu_dropdown_item_template(children[i], level + 1, i);
}
dropdownElement.innerHTML = dropdownTemplate;
this.menuContainer.appendChild(dropdownElement);
if (focusAfterCreation) {
dropdownElement.querySelector('a').focus();
}
this.dropdownStack.push({
children,
opener,
index,
element: dropdownElement
});
// Additional logic for ddsmoothmenu library:
// Add CSS class to primary dropdown to identify when to toggle scrolling for mobile.
document.getElementById('main_menu').addEventListener('click', (e) => {
const target = e.target;
if (!target || !target.parentNode) return;
if (target.parentNode.classList.contains('more')) {
var parentList = target.closest('ul');
var wasSelected = target.classList.contains('selected');
setTimeout(() => {
parentList.classList.toggle('expanded', !wasSelected);
}, 1);
} else if (target.tagName === 'A' && target.matches('#main_menu > ul > li > a')) {
target.nextElementSibling.classList.remove('expanded');
this.position_dropdowns();
}
position_dropdowns() {
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
let topNavHeight = 0;
for (let level = 0; level < this.dropdownStack.length; level++) {
const dropdownElement = this.dropdownStack[level].element;
const openerRect = this.dropdownStack[level].opener.getBoundingClientRect();
topNavHeight = openerRect.height;
const dropdownMaxHeight = vh - topNavHeight - this.dropdownMaxHeightMargin;
dropdownElement.style.maxHeight = dropdownMaxHeight + 'px';
const dropdownRect = dropdownElement.getBoundingClientRect();
if (level === 0) {
dropdownElement.style.top = (openerRect.y + openerRect.height) + 'px';
let left = openerRect.x;
if (left + dropdownRect.width > vw) {
left = openerRect.x + openerRect.width - dropdownRect.width;
}
if (left + dropdownRect.width > vw) {
left = vw - dropdownRect.width;
}
if (left < 0) {
left = 0;
}
dropdownElement.style.left = left + 'px';
} else {
let top = openerRect.y;
if (top + dropdownRect.height > vh - this.dropdownMaxHeightMargin) {
top = vh - this.dropdownMaxHeightMargin - dropdownRect.height;
}
dropdownElement.style.top = top + 'px';
let left = openerRect.x + openerRect.width + 1;
if (left + dropdownRect.width > vw) {
left = openerRect.x - dropdownRect.width - 1;
}
if (left < 0) {
if (openerRect.x + (openerRect.width / 2) > vw / 2) {
left = 1;
} else {
left = vw - dropdownRect.width - 1;
if (left < 0) {
left = 1;
}
}
}
dropdownElement.style.left = left + 'px';
}
}, true);
}
}
}