mirror of
https://github.com/novnc/noVNC.git
synced 2026-05-26 07:08:06 +00:00
Merge pull request #2043 from tobfah/enable-horizontal-control-bar
Enable attaching the control bar to top and bottom
This commit is contained in:
98
app/images/icons/novnc-icon-35x21.svg
Normal file
98
app/images/icons/novnc-icon-35x21.svg
Normal file
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="35"
|
||||
height="21"
|
||||
viewBox="0 0 35 21.000001"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(0,-1004.3621)">
|
||||
<g
|
||||
id="g4300"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.10948"
|
||||
transform="matrix(0.90909091,0,0,0.893617,-4.0909091,96.570459)">
|
||||
<g
|
||||
id="g4302"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.10948">
|
||||
<path
|
||||
d="m 11.986926,1016.3621 c 0.554325,0 1.025987,0.2121 1.414987,0.6362 0.398725,0.4138 0.600909,0.9155 0.598087,1.5052 v 6.8586 h -2 v -6.8914 c 0,-0.072 -0.03404,-0.1086 -0.102113,-0.1086 H 7.1021125 C 7.0340375,1018.3621 7,1018.3983 7,1018.4707 v 6.8914 H 5 v -9 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.10948px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="path4304" />
|
||||
<path
|
||||
d="m 17.013073,1016.3621 h 4.973854 c 0.554325,0 1.025987,0.2121 1.414986,0.6362 0.398725,0.4138 0.598087,0.9155 0.598087,1.5052 v 4.7172 c 0,0.5897 -0.199362,1.0966 -0.598087,1.5207 -0.388999,0.4138 -0.860661,0.6207 -1.414986,0.6207 h -4.973854 c -0.554325,0 -1.030849,-0.2069 -1.429574,-0.6207 C 15.1945,1024.3173 15,1023.8104 15,1023.2207 v -4.7172 c 0,-0.5897 0.1945,-1.0914 0.583499,-1.5052 0.398725,-0.4241 0.875249,-0.6362 1.429574,-0.6362 z m 4.884815,2 h -4.795776 c -0.06808,0 -0.102112,0.036 -0.102112,0.1086 v 4.7828 c 0,0.072 0.03404,0.1086 0.102112,0.1086 h 4.795776 c 0.06807,0 0.102112,-0.036 0.102112,-0.1086 v -4.7828 c 0,-0.072 -0.03404,-0.1086 -0.102112,-0.1086 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.10948px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="path4306" />
|
||||
</g>
|
||||
<g
|
||||
id="g4308"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.10948">
|
||||
<path
|
||||
d="m 12,1036.9177 4.768114,-8.5556 H 19 l -6,11 h -2 l -6,-11 h 2.2318854 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.10948px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="path4310" />
|
||||
<path
|
||||
d="m 29,1036.3621 v -8 h 2 v 11 h -2 l -7,-8 v 8 h -2 v -11 h 2 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.10948px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="path4312" />
|
||||
<path
|
||||
d="m 43,1030.3621 h -8.897887 c -0.06808,0 -0.102113,0.036 -0.102113,0.1069 v 6.7862 c 0,0.071 0.03404,0.1069 0.102113,0.1069 H 43 v 2 h -8.972339 c -0.56405,0 -1.045437,-0.2037 -1.444162,-0.6111 C 32.1945,1038.3334 32,1037.8292 32,1037.2385 v -6.7528 c 0,-0.5907 0.1945,-1.0898 0.583499,-1.4972 0.398725,-0.4176 0.880112,-0.6264 1.444162,-0.6264 H 43 Z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.10948px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="path4314" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g4291"
|
||||
style="stroke:none;stroke-width:1.10948"
|
||||
transform="matrix(0.90909091,0,0,0.893617,-4.5454545,96.123649)">
|
||||
<g
|
||||
id="g4282"
|
||||
style="stroke:none;stroke-width:1.10948">
|
||||
<path
|
||||
id="path4143"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1.10948px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 11.986926,1016.3621 c 0.554325,0 1.025987,0.2121 1.414987,0.6362 0.398725,0.4138 0.600909,0.9155 0.598087,1.5052 v 6.8586 h -2 v -6.8914 c 0,-0.072 -0.03404,-0.1086 -0.102113,-0.1086 H 7.1021125 C 7.0340375,1018.3621 7,1018.3983 7,1018.4707 v 6.8914 H 5 v -9 z" />
|
||||
<path
|
||||
id="path4145"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1.10948px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 17.013073,1016.3621 h 4.973854 c 0.554325,0 1.025987,0.2121 1.414986,0.6362 0.398725,0.4138 0.598087,0.9155 0.598087,1.5052 v 4.7172 c 0,0.5897 -0.199362,1.0966 -0.598087,1.5207 -0.388999,0.4138 -0.860661,0.6207 -1.414986,0.6207 h -4.973854 c -0.554325,0 -1.030849,-0.2069 -1.429574,-0.6207 C 15.1945,1024.3173 15,1023.8104 15,1023.2207 v -4.7172 c 0,-0.5897 0.1945,-1.0914 0.583499,-1.5052 0.398725,-0.4241 0.875249,-0.6362 1.429574,-0.6362 z m 4.884815,2 h -4.795776 c -0.06808,0 -0.102112,0.036 -0.102112,0.1086 v 4.7828 c 0,0.072 0.03404,0.1086 0.102112,0.1086 h 4.795776 c 0.06807,0 0.102112,-0.036 0.102112,-0.1086 v -4.7828 c 0,-0.072 -0.03404,-0.1086 -0.102112,-0.1086 z" />
|
||||
</g>
|
||||
<g
|
||||
id="g4286"
|
||||
style="stroke:none;stroke-width:1.10948">
|
||||
<path
|
||||
id="path4147"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1.10948px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 12,1036.9177 4.768114,-8.5556 H 19 l -6,11 h -2 l -6,-11 h 2.2318854 z" />
|
||||
<path
|
||||
id="path4149"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1.10948px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 29,1036.3621 v -8 h 2 v 11 h -2 l -7,-8 v 8 h -2 v -11 h 2 z" />
|
||||
<path
|
||||
id="path4151"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1.10948px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 43,1030.3621 h -8.897887 c -0.06808,0 -0.102113,0.036 -0.102113,0.1069 v 6.7862 c 0,0.071 0.03404,0.1069 0.102113,0.1069 H 43 v 2 h -8.972339 c -0.56405,0 -1.045437,-0.2037 -1.444162,-0.6111 C 32.1945,1038.3334 32,1037.8292 32,1037.2385 v -6.7528 c 0,-0.5907 0.1945,-1.0898 0.583499,-1.4972 0.398725,-0.4176 0.880112,-0.6264 1.444162,-0.6264 H 43 Z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
@@ -117,7 +117,8 @@ html {
|
||||
.noVNC_center > * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.noVNC_vcenter {
|
||||
|
||||
.noVNC_crosscenter {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@@ -129,9 +130,29 @@ html {
|
||||
padding: 0 !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
.noVNC_vcenter > * {
|
||||
.noVNC_crosscenter > * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.noVNC_right .noVNC_crosscenter {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.noVNC_top.noVNC_crosscenter,
|
||||
.noVNC_top .noVNC_crosscenter {
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.noVNC_bottom.noVNC_crosscenter,
|
||||
.noVNC_bottom .noVNC_crosscenter {
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.noVNC_bottom .noVNC_crosscenter {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
* Layering
|
||||
@@ -231,10 +252,18 @@ html {
|
||||
:root.noVNC_connected #noVNC_control_bar_anchor.noVNC_idle {
|
||||
opacity: 0.8;
|
||||
}
|
||||
#noVNC_control_bar_anchor:is(.noVNC_top, .noVNC_bottom) {
|
||||
/* Edge misrenders animations wihthout this */
|
||||
transform: translateY(0);
|
||||
}
|
||||
#noVNC_control_bar_anchor.noVNC_right {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
#noVNC_control_bar_anchor.noVNC_bottom {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#noVNC_control_bar {
|
||||
position: relative;
|
||||
@@ -249,10 +278,34 @@ html {
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none; /* Disable iOS image long-press popup */
|
||||
}
|
||||
.noVNC_right #noVNC_control_bar {
|
||||
left: 100%;
|
||||
border-radius: 12px 0 0 12px;
|
||||
}
|
||||
.noVNC_top #noVNC_control_bar {
|
||||
left: auto;
|
||||
/* FIXME: We want to mirror the left and right modes here and use a
|
||||
relative top offset (-100%), but it doesn't resolve
|
||||
correctly against the anchor height reference */
|
||||
top: -55px;
|
||||
border-radius: 0 0 12px 12px;
|
||||
}
|
||||
.noVNC_bottom #noVNC_control_bar {
|
||||
left: auto;
|
||||
/* FIXME: We want to mirror the left and right modes here and use a
|
||||
relative top offset (100%), but it doesn't resolve
|
||||
correctly against the anchor height reference */
|
||||
top: 55px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
#noVNC_control_bar.noVNC_open {
|
||||
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
|
||||
left: 0;
|
||||
}
|
||||
:is(.noVNC_top, .noVNC_bottom) #noVNC_control_bar.noVNC_open {
|
||||
left: auto;
|
||||
top: 0;
|
||||
}
|
||||
#noVNC_control_bar::before {
|
||||
/* This extra element is to get a proper shadow */
|
||||
content: "";
|
||||
@@ -263,19 +316,22 @@ html {
|
||||
left: -30px;
|
||||
transition: box-shadow 0.5s ease-in-out;
|
||||
}
|
||||
#noVNC_control_bar.noVNC_open::before {
|
||||
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.noVNC_right #noVNC_control_bar {
|
||||
left: 100%;
|
||||
border-radius: 12px 0 0 12px;
|
||||
}
|
||||
.noVNC_right #noVNC_control_bar.noVNC_open {
|
||||
left: 0;
|
||||
}
|
||||
.noVNC_right #noVNC_control_bar::before {
|
||||
visibility: hidden;
|
||||
}
|
||||
.noVNC_top #noVNC_control_bar::before {
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
top: -30px;
|
||||
bottom: auto;
|
||||
}
|
||||
.noVNC_bottom #noVNC_control_bar::before {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#noVNC_control_bar.noVNC_open::before {
|
||||
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
#noVNC_control_bar_handle {
|
||||
position: absolute;
|
||||
@@ -288,41 +344,96 @@ html {
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
background-color: var(--novnc-darkblue);
|
||||
background-image: url("../images/handle_bg.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right;
|
||||
box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
#noVNC_control_bar_handle:after {
|
||||
content: "";
|
||||
transition: transform 0.5s ease-in-out;
|
||||
background: url("../images/handle.svg");
|
||||
position: absolute;
|
||||
top: 22px; /* (50px-6px)/2 */
|
||||
right: 5px;
|
||||
width: 5px;
|
||||
height: 6px;
|
||||
}
|
||||
#noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
|
||||
transform: translateX(1px) rotate(180deg);
|
||||
}
|
||||
:root:not(.noVNC_connected) #noVNC_control_bar_handle {
|
||||
display: none;
|
||||
}
|
||||
.noVNC_right #noVNC_control_bar_handle {
|
||||
background-position: left;
|
||||
:is(.noVNC_top, .noVNC_bottom) #noVNC_control_bar_handle {
|
||||
transform: translateX(35px);
|
||||
top: -15px;
|
||||
left: 0;
|
||||
width: 50px;
|
||||
height: calc(100% + 30px);
|
||||
}
|
||||
|
||||
#noVNC_control_bar_handle::before {
|
||||
content: "";
|
||||
background: url("../images/handle_bg.svg");
|
||||
background-repeat: no-repeat;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 15px;
|
||||
height: 50px;
|
||||
}
|
||||
.noVNC_right #noVNC_control_bar_handle::before {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
.noVNC_top #noVNC_control_bar_handle::before {
|
||||
left: 0;
|
||||
right: auto;
|
||||
transform-origin: bottom left;
|
||||
transform: rotate(90deg) translateX(20px);
|
||||
}
|
||||
.noVNC_bottom #noVNC_control_bar_handle::before {
|
||||
left: 0;
|
||||
right: auto;
|
||||
transform-origin: bottom left;
|
||||
transform: rotate(90deg) translateX(-50px);
|
||||
}
|
||||
|
||||
#noVNC_control_bar_handle:after {
|
||||
content: "";
|
||||
transition: transform 0.5s ease-in-out;
|
||||
background: url("../images/handle.svg") no-repeat center;
|
||||
background-size: 5px 6px;
|
||||
position: absolute;
|
||||
top: 20px; /* (50px-10px)/2 */
|
||||
right: 3px;
|
||||
transform: none;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
transform-origin: center;
|
||||
}
|
||||
.noVNC_right #noVNC_control_bar_handle:after {
|
||||
left: 5px;
|
||||
right: 0;
|
||||
left: 3px;
|
||||
right: auto;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.noVNC_top #noVNC_control_bar_handle:after {
|
||||
left: 20px;
|
||||
right: auto;
|
||||
top: auto;
|
||||
bottom: 3px;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.noVNC_bottom #noVNC_control_bar_handle:after {
|
||||
left: 20px;
|
||||
right: auto;
|
||||
top: 3px;
|
||||
bottom: auto;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
#noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
|
||||
transform: translateX(1px) rotate(180deg);
|
||||
}
|
||||
.noVNC_right #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
|
||||
transform: none;
|
||||
transform: translateX(-1px);
|
||||
}
|
||||
.noVNC_top #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
|
||||
transform: translateY(1px) rotate(-90deg);
|
||||
}
|
||||
.noVNC_bottom #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
|
||||
transform: translateY(-1px) rotate(90deg);
|
||||
}
|
||||
|
||||
/* Larger touch area for the handle, used when a touch screen is available */
|
||||
#noVNC_control_bar_handle div {
|
||||
position: absolute;
|
||||
left: auto;
|
||||
right: -35px;
|
||||
top: 0;
|
||||
width: 50px;
|
||||
@@ -338,35 +449,66 @@ html {
|
||||
left: -35px;
|
||||
right: auto;
|
||||
}
|
||||
.noVNC_top #noVNC_control_bar_handle div {
|
||||
left: 0;
|
||||
right: auto;
|
||||
top: auto;
|
||||
bottom: -35px;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
.noVNC_bottom #noVNC_control_bar_handle div {
|
||||
left: 0;
|
||||
right: auto;
|
||||
top: -35px;
|
||||
bottom: auto;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
#noVNC_control_bar > .noVNC_scroll {
|
||||
max-height: 100vh; /* Chrome is buggy with 100% */
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 0 10px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px 0;
|
||||
}
|
||||
|
||||
#noVNC_control_bar > .noVNC_scroll > * {
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
:is(.noVNC_top, .noVNC_bottom) > #noVNC_control_bar > .noVNC_scroll {
|
||||
max-width: 100vw; /* Chrome is buggy with 100% */
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
flex-direction: row;
|
||||
gap: 0 10px;
|
||||
}
|
||||
|
||||
/* Control bar hint */
|
||||
#noVNC_hint_anchor {
|
||||
.noVNC_hint_anchor {
|
||||
position: fixed;
|
||||
right: -50px;
|
||||
left: auto;
|
||||
}
|
||||
#noVNC_control_bar_anchor.noVNC_right + #noVNC_hint_anchor {
|
||||
left: -50px;
|
||||
right: auto;
|
||||
}
|
||||
#noVNC_control_bar_hint {
|
||||
position: relative;
|
||||
transform: scale(0);
|
||||
.noVNC_hint_anchor.noVNC_right {
|
||||
left: auto;
|
||||
right: -50px;
|
||||
}
|
||||
.noVNC_hint_anchor.noVNC_top {
|
||||
left: auto;
|
||||
top: -50px;
|
||||
}
|
||||
.noVNC_hint_anchor.noVNC_bottom {
|
||||
left: auto;
|
||||
top: auto;
|
||||
bottom: -50px;
|
||||
}
|
||||
.noVNC_control_bar_hint {
|
||||
width: 100px;
|
||||
height: 50%;
|
||||
max-height: 600px;
|
||||
position: relative;
|
||||
transform: scale(0);
|
||||
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
@@ -376,13 +518,19 @@ html {
|
||||
border-radius: 12px;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
#noVNC_control_bar_hint.noVNC_active {
|
||||
:is(.noVNC_top, .noVNC_bottom) .noVNC_control_bar_hint {
|
||||
width: 50%;
|
||||
height: 100px;
|
||||
max-width: 600px;
|
||||
max-height: none;
|
||||
}
|
||||
.noVNC_control_bar_hint.noVNC_active {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition-delay: 0.2s;
|
||||
transform: scale(1);
|
||||
}
|
||||
#noVNC_control_bar_hint.noVNC_notransition {
|
||||
.noVNC_control_bar_hint.noVNC_notransition {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
@@ -390,7 +538,6 @@ html {
|
||||
#noVNC_control_bar .noVNC_button {
|
||||
min-width: unset;
|
||||
padding: 4px 4px;
|
||||
vertical-align: middle;
|
||||
border:1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
background-color: transparent;
|
||||
@@ -411,7 +558,7 @@ html {
|
||||
|
||||
box-sizing: border-box; /* so max-width don't have to care about padding */
|
||||
max-width: calc(100vw - 75px - 25px); /* minus left and right margins */
|
||||
max-height: 100vh; /* Chrome is buggy with 100% */
|
||||
max-height: calc(100vh - 75px - 25px); /* minus top and bottom margins */
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -431,16 +578,24 @@ html {
|
||||
opacity: 1;
|
||||
transform: translateX(75px);
|
||||
}
|
||||
.noVNC_right .noVNC_vcenter {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.noVNC_right .noVNC_panel {
|
||||
transform: translateX(-25px);
|
||||
}
|
||||
.noVNC_right .noVNC_panel.noVNC_open {
|
||||
transform: translateX(-75px);
|
||||
}
|
||||
.noVNC_top .noVNC_panel {
|
||||
transform: translateY(25px);
|
||||
}
|
||||
.noVNC_top .noVNC_panel.noVNC_open {
|
||||
transform: translateY(75px);
|
||||
}
|
||||
.noVNC_bottom .noVNC_panel {
|
||||
transform: translateY(-25px);
|
||||
}
|
||||
.noVNC_bottom .noVNC_panel.noVNC_open {
|
||||
transform: translateY(-75px);
|
||||
}
|
||||
|
||||
.noVNC_panel > * {
|
||||
display: block;
|
||||
@@ -536,13 +691,26 @@ html {
|
||||
/* Control bar content */
|
||||
|
||||
#noVNC_control_bar .noVNC_logo {
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
max-width: 35px;
|
||||
max-height: 35px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.noVNC_logo + hr {
|
||||
/* Remove all but top border */
|
||||
border: none;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
width: 35px;
|
||||
height: 1px;
|
||||
margin: 0;
|
||||
}
|
||||
:is(.noVNC_top, .noVNC_bottom) .noVNC_logo + hr {
|
||||
/* Remove all but left border */
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-top: none;
|
||||
width: 1px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
:root:not(.noVNC_connected) #noVNC_view_drag_button {
|
||||
@@ -550,16 +718,15 @@ html {
|
||||
}
|
||||
|
||||
/* noVNC Touch Device only buttons */
|
||||
:root:not(.noVNC_connected) #noVNC_mobile_buttons {
|
||||
:root:not(.noVNC_connected) #noVNC_keyboard_button {
|
||||
display: none;
|
||||
}
|
||||
@media not all and (any-pointer: coarse) {
|
||||
/* FIXME: The button for the virtual keyboard is the only button in this
|
||||
group of "mobile buttons". It is bad to assume that no touch
|
||||
devices have physical keyboards available. Hopefully we can get
|
||||
a media query for this:
|
||||
/* FIXME: It is bad to assume that no touch devices have physical
|
||||
keyboards available. Hopefully we can get a media query
|
||||
for this:
|
||||
https://github.com/w3c/csswg-drafts/issues/3871 */
|
||||
:root.noVNC_connected #noVNC_mobile_buttons {
|
||||
:root.noVNC_connected #noVNC_keyboard_button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -573,6 +740,18 @@ html {
|
||||
background-color: var(--novnc-darkgrey);
|
||||
border: none;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px 0;
|
||||
}
|
||||
#noVNC_modifiers > * {
|
||||
margin: 0;
|
||||
}
|
||||
:is(.noVNC_top, .noVNC_bottom) #noVNC_modifiers {
|
||||
flex-direction: row;
|
||||
gap: 0 10px;
|
||||
}
|
||||
|
||||
/* Shutdown/Reboot */
|
||||
|
||||
169
app/ui.js
169
app/ui.js
@@ -37,6 +37,8 @@ const UI = {
|
||||
|
||||
controlbarGrabbed: false,
|
||||
controlbarDrag: false,
|
||||
controlbarMouseDownClientX: 0,
|
||||
controlbarMouseDownOffsetX: 0,
|
||||
controlbarMouseDownClientY: 0,
|
||||
controlbarMouseDownOffsetY: 0,
|
||||
|
||||
@@ -110,8 +112,11 @@ const UI = {
|
||||
}
|
||||
|
||||
// Restore control bar position
|
||||
if (WebUtil.readSetting('controlbar_pos') === 'right') {
|
||||
UI.toggleControlbarSide();
|
||||
const pos = WebUtil.readSetting('controlbar_pos');
|
||||
if (['left', 'right', 'top', 'bottom'].includes(pos)) {
|
||||
UI.toggleControlbarSide(pos);
|
||||
} else {
|
||||
UI.toggleControlbarSide('left');
|
||||
}
|
||||
|
||||
UI.initFullscreen();
|
||||
@@ -575,7 +580,15 @@ const UI = {
|
||||
}
|
||||
},
|
||||
|
||||
toggleControlbarSide() {
|
||||
getControlbarPos() {
|
||||
const anchor = document.getElementById('noVNC_control_bar_anchor');
|
||||
if (anchor.classList.contains('noVNC_right')) return 'right';
|
||||
if (anchor.classList.contains('noVNC_top')) return 'top';
|
||||
if (anchor.classList.contains('noVNC_bottom')) return 'bottom';
|
||||
return 'left';
|
||||
},
|
||||
|
||||
toggleControlbarSide(pos) {
|
||||
// Temporarily disable animation, if bar is displayed, to avoid weird
|
||||
// movement. The transitionend-event will not fire when display=none.
|
||||
const bar = document.getElementById('noVNC_control_bar');
|
||||
@@ -586,13 +599,12 @@ const UI = {
|
||||
}
|
||||
|
||||
const anchor = document.getElementById('noVNC_control_bar_anchor');
|
||||
if (anchor.classList.contains("noVNC_right")) {
|
||||
WebUtil.writeSetting('controlbar_pos', 'left');
|
||||
anchor.classList.remove("noVNC_right");
|
||||
} else {
|
||||
WebUtil.writeSetting('controlbar_pos', 'right');
|
||||
anchor.classList.add("noVNC_right");
|
||||
|
||||
anchor.classList.remove('noVNC_right', 'noVNC_top', 'noVNC_bottom');
|
||||
if (['right', 'top', 'bottom'].includes(pos)) {
|
||||
anchor.classList.add(`noVNC_${pos}`);
|
||||
}
|
||||
WebUtil.writeSetting('controlbar_pos', pos);
|
||||
|
||||
// Consider this a movement of the handle
|
||||
UI.controlbarDrag = true;
|
||||
@@ -602,19 +614,21 @@ const UI = {
|
||||
},
|
||||
|
||||
showControlbarHint(show, animate=true) {
|
||||
const hint = document.getElementById('noVNC_control_bar_hint');
|
||||
const getPos = element =>
|
||||
['right', 'top', 'bottom'].find(pos =>
|
||||
element.classList.contains(`noVNC_${pos}`)
|
||||
) ?? 'left';
|
||||
|
||||
if (animate) {
|
||||
hint.classList.remove("noVNC_notransition");
|
||||
} else {
|
||||
hint.classList.add("noVNC_notransition");
|
||||
}
|
||||
const anchor = document.getElementById('noVNC_control_bar_anchor');
|
||||
const anchorPos = getPos(anchor);
|
||||
|
||||
if (show) {
|
||||
hint.classList.add("noVNC_active");
|
||||
} else {
|
||||
hint.classList.remove("noVNC_active");
|
||||
}
|
||||
document.querySelectorAll('.noVNC_control_bar_hint').forEach((hint) => {
|
||||
const hintPos = getPos(hint.parentElement);
|
||||
const shouldShow = show && (hintPos !== anchorPos);
|
||||
|
||||
hint.classList.toggle('noVNC_active', shouldShow);
|
||||
hint.classList.toggle('noVNC_notransition', !animate || !shouldShow);
|
||||
});
|
||||
},
|
||||
|
||||
dragControlbarHandle(e) {
|
||||
@@ -622,28 +636,62 @@ const UI = {
|
||||
|
||||
const ptr = getPointerEvent(e);
|
||||
|
||||
const anchor = document.getElementById('noVNC_control_bar_anchor');
|
||||
if (ptr.clientX < (window.innerWidth * 0.1)) {
|
||||
if (anchor.classList.contains("noVNC_right")) {
|
||||
UI.toggleControlbarSide();
|
||||
let controlBarPos = UI.getControlbarPos();
|
||||
|
||||
if (ptr.clientX < (window.innerWidth * 0.1) &&
|
||||
ptr.clientY > (window.innerHeight * 0.25) &&
|
||||
ptr.clientY < (window.innerHeight * 0.75)) {
|
||||
if (controlBarPos !== 'left') {
|
||||
UI.toggleControlbarSide('left');
|
||||
controlBarPos = 'left';
|
||||
}
|
||||
} else if (ptr.clientX > (window.innerWidth * 0.9)) {
|
||||
if (!anchor.classList.contains("noVNC_right")) {
|
||||
UI.toggleControlbarSide();
|
||||
|
||||
} else if (ptr.clientX > (window.innerWidth * 0.9) &&
|
||||
ptr.clientY > (window.innerHeight * 0.25) &&
|
||||
ptr.clientY < (window.innerHeight * 0.75)) {
|
||||
if (controlBarPos !== 'right') {
|
||||
UI.toggleControlbarSide('right');
|
||||
controlBarPos = 'right';
|
||||
}
|
||||
|
||||
// Slightly increased height thresholds since 10% of the
|
||||
// height proved small in practice
|
||||
} else if (ptr.clientX > (window.innerWidth * 0.25) &&
|
||||
ptr.clientX < (window.innerWidth * 0.75) &&
|
||||
ptr.clientY < (window.innerHeight * 0.2)) {
|
||||
if (controlBarPos !== 'top') {
|
||||
UI.toggleControlbarSide('top');
|
||||
controlBarPos = 'top';
|
||||
}
|
||||
|
||||
} else if (ptr.clientX > (window.innerWidth * 0.25) &&
|
||||
ptr.clientX < (window.innerWidth * 0.75) &&
|
||||
ptr.clientY > (window.innerHeight * 0.8)) {
|
||||
if (controlBarPos !== 'bottom') {
|
||||
UI.toggleControlbarSide("bottom");
|
||||
controlBarPos = 'bottom';
|
||||
}
|
||||
}
|
||||
|
||||
const isVertical = controlBarPos === 'left' || controlBarPos === 'right';
|
||||
|
||||
if (!UI.controlbarDrag) {
|
||||
const dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY);
|
||||
const dragDistance = isVertical
|
||||
? Math.abs(ptr.clientY - UI.controlbarMouseDownClientY)
|
||||
: Math.abs(ptr.clientX - UI.controlbarMouseDownClientX);
|
||||
|
||||
if (dragDistance < dragThreshold) return;
|
||||
|
||||
UI.controlbarDrag = true;
|
||||
}
|
||||
|
||||
if (isVertical) {
|
||||
const eventY = ptr.clientY - UI.controlbarMouseDownOffsetY;
|
||||
|
||||
UI.moveControlbarHandle(eventY);
|
||||
UI.moveControlbarHandle(eventY, true);
|
||||
} else {
|
||||
const eventX = ptr.clientX - UI.controlbarMouseDownOffsetX;
|
||||
UI.moveControlbarHandle(eventX, false);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -652,41 +700,56 @@ const UI = {
|
||||
},
|
||||
|
||||
// Move the handle but don't allow any position outside the bounds
|
||||
moveControlbarHandle(viewportRelativeY) {
|
||||
moveControlbarHandle(viewportRelativeCoord, isVertical) {
|
||||
const handle = document.getElementById("noVNC_control_bar_handle");
|
||||
const handleHeight = handle.getBoundingClientRect().height;
|
||||
|
||||
const handleSpan = isVertical
|
||||
? handle.getBoundingClientRect().height
|
||||
: handle.getBoundingClientRect().width;
|
||||
|
||||
const controlbarBounds = document.getElementById("noVNC_control_bar")
|
||||
.getBoundingClientRect();
|
||||
const controlbarBoundsStart = isVertical
|
||||
? controlbarBounds.top
|
||||
: controlbarBounds.left;
|
||||
const controlbarBoundsSpan = isVertical
|
||||
? controlbarBounds.height
|
||||
: controlbarBounds.width;
|
||||
|
||||
const margin = 10;
|
||||
|
||||
// These heights need to be non-zero for the below logic to work
|
||||
if (handleHeight === 0 || controlbarBounds.height === 0) {
|
||||
if (handleSpan === 0 || controlbarBoundsSpan === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newY = viewportRelativeY;
|
||||
let newCoord = viewportRelativeCoord;
|
||||
|
||||
// Check if the coordinates are outside the control bar
|
||||
if (newY < controlbarBounds.top + margin) {
|
||||
// Force coordinates to be below the top of the control bar
|
||||
newY = controlbarBounds.top + margin;
|
||||
if (newCoord < controlbarBoundsStart + margin) {
|
||||
// Force coordinates to be below the start of the control bar
|
||||
newCoord = controlbarBoundsStart + margin;
|
||||
|
||||
} else if (newY > controlbarBounds.top +
|
||||
controlbarBounds.height - handleHeight - margin) {
|
||||
// Force coordinates to be above the bottom of the control bar
|
||||
newY = controlbarBounds.top +
|
||||
controlbarBounds.height - handleHeight - margin;
|
||||
} else if (newCoord > controlbarBoundsStart +
|
||||
controlbarBoundsSpan - handleSpan - margin) {
|
||||
// Force coordinates to be before the end of the control bar
|
||||
newCoord = controlbarBoundsStart +
|
||||
controlbarBoundsSpan - handleSpan - margin;
|
||||
}
|
||||
|
||||
// Corner case: control bar too small for stable position
|
||||
if (controlbarBounds.height < (handleHeight + margin * 2)) {
|
||||
newY = controlbarBounds.top +
|
||||
(controlbarBounds.height - handleHeight) / 2;
|
||||
if (controlbarBoundsSpan < (handleSpan + margin * 2)) {
|
||||
newCoord = controlbarBoundsStart +
|
||||
(controlbarBoundsSpan - handleSpan) / 2;
|
||||
}
|
||||
|
||||
// The transform needs coordinates that are relative to the parent
|
||||
const parentRelativeY = newY - controlbarBounds.top;
|
||||
handle.style.transform = "translateY(" + parentRelativeY + "px)";
|
||||
const parentRelativeCoord = newCoord - controlbarBoundsStart;
|
||||
if (isVertical) {
|
||||
handle.style.transform = "translateY(" + parentRelativeCoord + "px)";
|
||||
} else {
|
||||
handle.style.transform = "translateX(" + parentRelativeCoord + "px)";
|
||||
}
|
||||
},
|
||||
|
||||
updateControlbarHandle() {
|
||||
@@ -694,7 +757,15 @@ const UI = {
|
||||
// the move function expects coordinates relative the the viewport.
|
||||
const handle = document.getElementById("noVNC_control_bar_handle");
|
||||
const handleBounds = handle.getBoundingClientRect();
|
||||
UI.moveControlbarHandle(handleBounds.top);
|
||||
|
||||
const controlBarPos = UI.getControlbarPos();
|
||||
const isVertical = controlBarPos === 'left' || controlBarPos === 'right';
|
||||
|
||||
if (isVertical) {
|
||||
UI.moveControlbarHandle(handleBounds.top, true);
|
||||
} else {
|
||||
UI.moveControlbarHandle(handleBounds.left, false);
|
||||
}
|
||||
},
|
||||
|
||||
controlbarHandleMouseUp(e) {
|
||||
@@ -732,6 +803,8 @@ const UI = {
|
||||
|
||||
UI.controlbarMouseDownClientY = ptr.clientY;
|
||||
UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top;
|
||||
UI.controlbarMouseDownClientX = ptr.clientX;
|
||||
UI.controlbarMouseDownOffsetX = ptr.clientX - bounds.left;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
UI.keepControlbar();
|
||||
|
||||
32
vnc.html
32
vnc.html
@@ -110,14 +110,14 @@
|
||||
</div>
|
||||
|
||||
<!-- noVNC control bar -->
|
||||
<div id="noVNC_control_bar_anchor" class="noVNC_vcenter">
|
||||
|
||||
<div id="noVNC_control_bar_anchor" class="noVNC_crosscenter">
|
||||
<div id="noVNC_control_bar">
|
||||
<div id="noVNC_control_bar_handle" title="Hide/Show the control bar"><div></div></div>
|
||||
|
||||
<div class="noVNC_scroll">
|
||||
|
||||
<h1 class="noVNC_logo" translate="no"><span>no</span><br>VNC</h1>
|
||||
<img class="noVNC_logo" src="app/images/icons/novnc-icon-35x21.svg"
|
||||
alt="noVNC">
|
||||
|
||||
<hr>
|
||||
|
||||
@@ -127,16 +127,14 @@
|
||||
title="Move/Drag viewport">
|
||||
|
||||
<!--noVNC touch device only buttons-->
|
||||
<div id="noVNC_mobile_buttons">
|
||||
<input type="image" alt="Keyboard" src="app/images/keyboard.svg"
|
||||
id="noVNC_keyboard_button" class="noVNC_button" title="Show keyboard">
|
||||
</div>
|
||||
|
||||
<!-- Extra manual keys -->
|
||||
<input type="image" alt="Extra keys" src="app/images/toggleextrakeys.svg"
|
||||
id="noVNC_toggle_extra_keys_button" class="noVNC_button"
|
||||
title="Show extra keys">
|
||||
<div class="noVNC_vcenter">
|
||||
<div class="noVNC_crosscenter">
|
||||
<div id="noVNC_modifiers" class="noVNC_panel">
|
||||
<input type="image" alt="Ctrl" src="app/images/ctrl.svg"
|
||||
id="noVNC_toggle_ctrl_button" class="noVNC_button"
|
||||
@@ -163,7 +161,7 @@
|
||||
<input type="image" alt="Shutdown/Reboot" src="app/images/power.svg"
|
||||
id="noVNC_power_button" class="noVNC_button"
|
||||
title="Shutdown/Reboot...">
|
||||
<div class="noVNC_vcenter">
|
||||
<div class="noVNC_crosscenter">
|
||||
<div id="noVNC_power" class="noVNC_panel">
|
||||
<div class="noVNC_heading">
|
||||
<img alt="" src="app/images/power.svg"> Power
|
||||
@@ -178,7 +176,7 @@
|
||||
<input type="image" alt="Clipboard" src="app/images/clipboard.svg"
|
||||
id="noVNC_clipboard_button" class="noVNC_button"
|
||||
title="Clipboard">
|
||||
<div class="noVNC_vcenter">
|
||||
<div class="noVNC_crosscenter">
|
||||
<div id="noVNC_clipboard" class="noVNC_panel">
|
||||
<div class="noVNC_heading">
|
||||
<img alt="" src="app/images/clipboard.svg"> Clipboard
|
||||
@@ -199,7 +197,7 @@
|
||||
<input type="image" alt="Settings" src="app/images/settings.svg"
|
||||
id="noVNC_settings_button" class="noVNC_button"
|
||||
title="Settings">
|
||||
<div class="noVNC_vcenter">
|
||||
<div class="noVNC_crosscenter">
|
||||
<div id="noVNC_settings" class="noVNC_panel">
|
||||
<div class="noVNC_heading">
|
||||
<img alt="" src="app/images/settings.svg"> Settings
|
||||
@@ -332,8 +330,20 @@
|
||||
|
||||
</div> <!-- End of noVNC_control_bar -->
|
||||
|
||||
<div id="noVNC_hint_anchor" class="noVNC_vcenter">
|
||||
<div id="noVNC_control_bar_hint">
|
||||
<div class="noVNC_hint_anchor noVNC_crosscenter">
|
||||
<div class="noVNC_control_bar_hint">
|
||||
</div>
|
||||
</div>
|
||||
<div class="noVNC_hint_anchor noVNC_right noVNC_crosscenter">
|
||||
<div class="noVNC_control_bar_hint">
|
||||
</div>
|
||||
</div>
|
||||
<div class="noVNC_hint_anchor noVNC_top noVNC_crosscenter">
|
||||
<div class="noVNC_control_bar_hint">
|
||||
</div>
|
||||
</div>
|
||||
<div class="noVNC_hint_anchor noVNC_bottom noVNC_crosscenter">
|
||||
<div class="noVNC_control_bar_hint">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user