Implement theming support and add MetroMumbleDark theme
This commit is contained in:
parent
0a68f28c38
commit
ca83780109
10
README.md
10
README.md
|
@ -86,8 +86,18 @@ map $http_upgrade $connection_upgrade {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Themes
|
||||||
|
The default theme of mumble-web tries to mimic the excellent [MetroMumble]Light theme.
|
||||||
|
mumble-web also includes a dark version, named MetroMumbleDark, which is heavily inspired by [MetroMumble]'s dark version.
|
||||||
|
|
||||||
|
To select a theme other than the default one, append a `theme=dark` query parameter (where `dark` is the name of the theme) when accessing the mumble-web page.
|
||||||
|
E.g. [this](https://voice.johni0702.de/?address=voice.johni0702.de&port=443/demo&theme=dark)is the live demo linked above but using the dark theme (`dark` is an alias for `MetroMumbleDark`).
|
||||||
|
|
||||||
|
Custom themes can be created by deriving them from the MetroMumbleLight/Dark themes just like the MetroMumbleDark theme is derived from the MetroMumbleLight theme.
|
||||||
|
|
||||||
### License
|
### License
|
||||||
ISC
|
ISC
|
||||||
|
|
||||||
[Mumble]: https://wiki.mumble.info/wiki/Main_Page
|
[Mumble]: https://wiki.mumble.info/wiki/Main_Page
|
||||||
[websockify GitHub page]: https://github.com/novnc/websockify
|
[websockify GitHub page]: https://github.com/novnc/websockify
|
||||||
|
[MetroMumble]: https://github.com/xPoke/MetroMumble
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<meta name="msapplication-config" content="${require('./favicon/browserconfig.xml')}">
|
<meta name="msapplication-config" content="${require('./favicon/browserconfig.xml')}">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="/loading.css">
|
<script src="theme.js"></script>
|
||||||
|
|
||||||
<script src="matrix.js"></script>
|
<script src="matrix.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
@ -340,6 +340,5 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<script src="index.js"></script>
|
<script src="index.js"></script>
|
||||||
</html>
|
</html>
|
||||||
|
|
33
app/theme.js
Normal file
33
app/theme.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import url from 'url'
|
||||||
|
|
||||||
|
var queryParams = url.parse(document.location.href, true).query
|
||||||
|
var theme = queryParams.theme || window.localStorage.getItem('mumble.theme')
|
||||||
|
var themes = {
|
||||||
|
'MetroMumbleLight': 'MetroMumbleLight',
|
||||||
|
'MetroMumbleDark': 'MetroMumbleDark',
|
||||||
|
'light': 'MetroMumbleLight',
|
||||||
|
'dark': 'MetroMumbleDark'
|
||||||
|
}
|
||||||
|
theme = themes[theme] || 'MetroMumbleLight'
|
||||||
|
window.theme = theme
|
||||||
|
|
||||||
|
var [loadingTheme, mainTheme] = {
|
||||||
|
'MetroMumbleLight': [
|
||||||
|
require('../themes/MetroMumbleLight/loading.scss'),
|
||||||
|
require('../themes/MetroMumbleLight/main.scss')
|
||||||
|
],
|
||||||
|
'MetroMumbleDark': [
|
||||||
|
require('../themes/MetroMumbleDark/loading.scss'),
|
||||||
|
require('../themes/MetroMumbleDark/main.scss')
|
||||||
|
]
|
||||||
|
}[theme]
|
||||||
|
|
||||||
|
function useStyle (url) {
|
||||||
|
var style = document.createElement('link')
|
||||||
|
style.rel = 'stylesheet'
|
||||||
|
style.type = 'text/css'
|
||||||
|
style.href = url
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(style)
|
||||||
|
}
|
||||||
|
useStyle(loadingTheme)
|
||||||
|
useStyle(mainTheme)
|
|
@ -36,8 +36,10 @@
|
||||||
"knockout": "^3.4.0",
|
"knockout": "^3.4.0",
|
||||||
"lodash.assign": "^4.2.0",
|
"lodash.assign": "^4.2.0",
|
||||||
"microphone-stream": "^3.0.5",
|
"microphone-stream": "^3.0.5",
|
||||||
|
"node-sass": "^4.9.3",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^0.5.1",
|
||||||
"regexp-replace-loader": "0.0.1",
|
"regexp-replace-loader": "0.0.1",
|
||||||
|
"sass-loader": "^4.1.1",
|
||||||
"stream-chunker": "^1.2.8",
|
"stream-chunker": "^1.2.8",
|
||||||
"transform-loader": "^0.2.3",
|
"transform-loader": "^0.2.3",
|
||||||
"voice-activity-detection": "johni0702/voice-activity-detection#9f8bd90",
|
"voice-activity-detection": "johni0702/voice-activity-detection#9f8bd90",
|
||||||
|
|
5
themes/MetroMumbleDark/loading.scss
Normal file
5
themes/MetroMumbleDark/loading.scss
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
$bg-color: #2c2c2c !default
|
||||||
|
$spinner-color: #888 !default
|
||||||
|
$spinner-bg-color: #222 !default
|
||||||
|
|
||||||
|
@import '../MetroMumbleLight/loading';
|
22
themes/MetroMumbleDark/main.scss
Normal file
22
themes/MetroMumbleDark/main.scss
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
$black: #bbb !default
|
||||||
|
$darkgray: #777 !default
|
||||||
|
$gray: #555 !default
|
||||||
|
$lightgray: #555 !default
|
||||||
|
$bg-color: #2c2c2c !default
|
||||||
|
$white: #1c1c1c !default
|
||||||
|
|
||||||
|
$lightblue: #557 !default
|
||||||
|
|
||||||
|
$toolbar-hover-bg-color: $lightblue !default
|
||||||
|
$toolbar-hover-border-color: $lightblue !default
|
||||||
|
$toolbar-active-bg-color: $gray !default
|
||||||
|
$toolbar-active-border-color: $gray !default
|
||||||
|
$dialog-color: $black !default
|
||||||
|
$channel-selected-bg-color: $lightblue !default
|
||||||
|
|
||||||
|
@import '../MetroMumbleLight/main';
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
color: #000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
|
@ -1,9 +1,13 @@
|
||||||
|
$bg-color: #eee !default
|
||||||
|
$spinner-color: #999 !default
|
||||||
|
$spinner-bg-color: #ddd !default
|
||||||
|
|
||||||
.loading-container {
|
.loading-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #eee;
|
background-color: $bg-color;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +19,8 @@
|
||||||
top: calc(50% - 40px);
|
top: calc(50% - 40px);
|
||||||
left: calc(50% - 40px);
|
left: calc(50% - 40px);
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
border: 10px solid #ddd;
|
border: 10px solid $spinner-bg-color;
|
||||||
border-top-color: #999;
|
border-top-color: $spinner-color;
|
||||||
animation: spin 1s infinite linear;
|
animation: spin 1s infinite linear;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,50 @@
|
||||||
|
$black: #000 !default
|
||||||
|
$darkgray: #888 !default
|
||||||
|
$gray: #a9a9a9 !default
|
||||||
|
$lightgray: #d3d3d3 !default
|
||||||
|
$bg-color: #eee !default
|
||||||
|
$white: #fff !default
|
||||||
|
|
||||||
|
$font-color: $black !default
|
||||||
|
$panel-bg-color: $white !default
|
||||||
|
$panel-border-color: $lightgray !default
|
||||||
|
$channel-tree-color: $lightgray !default
|
||||||
|
$channel-hover-bg-color: $lightgray !default
|
||||||
|
$channel-selected-bg-color: lightblue !default
|
||||||
|
$channel-selected-border-color: $darkgray !default
|
||||||
|
$tooltip-border-color: $darkgray !default
|
||||||
|
$chat-channel-color: orange !default
|
||||||
|
$chat-user-color: green !default
|
||||||
|
$chat-input-color: $font-color !default
|
||||||
|
$mic-volume-border-color: $black !default
|
||||||
|
|
||||||
|
$toolbar-hover-bg-color: $lightgray !default
|
||||||
|
$toolbar-hover-border-color: $gray !default
|
||||||
|
$toolbar-active-bg-color: $white !default
|
||||||
|
$toolbar-active-border-color: $toolbar-hover-bg-color !default
|
||||||
|
$toolbar-divider-color: $lightgray !default
|
||||||
|
$dialog-header-color: $white !default
|
||||||
|
$dialog-header-bg-color: $darkgray !default
|
||||||
|
$dialog-header-border-bottom-color: $gray !default
|
||||||
|
$dialog-bg-color: $bg-color !default
|
||||||
|
$dialog-border-color: $darkgray !default
|
||||||
|
$dialog-color: $font-color !default
|
||||||
|
$dialog-button-border-color: $darkgray !default
|
||||||
|
$dialog-button-bg-color: $white !default
|
||||||
|
$dialog-button-color: $dialog-color !default
|
||||||
|
$dialog-input-border-color: $darkgray !default
|
||||||
|
$dialog-input-bg-color: $white !default
|
||||||
|
$dialog-input-color: $dialog-color !default
|
||||||
|
|
||||||
|
$tooltip-bg-color: $panel-bg-color !default
|
||||||
|
$channels-bg-color: $panel-bg-color !default
|
||||||
|
$channels-border-color: $panel-border-color !default
|
||||||
|
$chat-bg-color: $panel-bg-color !default
|
||||||
|
$chat-border-color: $panel-border-color !default
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
background-color: #eee;
|
background-color: $bg-color;
|
||||||
|
color: $font-color;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%
|
height: 100%
|
||||||
|
@ -10,8 +55,8 @@ html, body {
|
||||||
.channel-root-container {
|
.channel-root-container {
|
||||||
text-size: 16px;
|
text-size: 16px;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
background-color: white;
|
background-color: $channels-bg-color;
|
||||||
border: 1px solid lightgray;
|
border: 1px solid $channels-border-color;
|
||||||
float: left;
|
float: left;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
@ -44,10 +89,10 @@ html, body {
|
||||||
height: calc(98% - 4px);
|
height: calc(98% - 4px);
|
||||||
}
|
}
|
||||||
.log {
|
.log {
|
||||||
background-color: white;
|
background-color: $chat-bg-color;
|
||||||
height: calc(100% - 42px);
|
height: calc(100% - 42px);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border: 1px lightgray solid;
|
border: 1px $chat-border-color solid;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
@ -59,8 +104,7 @@ html, body {
|
||||||
float: left;
|
float: left;
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
background-color: white;
|
background-color: $channels-bg-color;
|
||||||
margin-right:
|
|
||||||
}
|
}
|
||||||
.channel-sub {
|
.channel-sub {
|
||||||
margin-left: 9px;
|
margin-left: 9px;
|
||||||
|
@ -68,7 +112,7 @@ html, body {
|
||||||
padding-left: 9px;
|
padding-left: 9px;
|
||||||
}
|
}
|
||||||
.channel-wrapper:nth-last-child(n + 2) > .branch:not(:empty) + .channel-sub {
|
.channel-wrapper:nth-last-child(n + 2) > .branch:not(:empty) + .channel-sub {
|
||||||
border-left: 1px lightgray solid;
|
border-left: 1px $channel-tree-color solid;
|
||||||
}
|
}
|
||||||
.channel-tree,
|
.channel-tree,
|
||||||
.user-wrapper {
|
.user-wrapper {
|
||||||
|
@ -84,8 +128,8 @@ html, body {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 9px;
|
width: 9px;
|
||||||
border-left: 1px lightgray solid;
|
border-left: 1px $channel-tree-color solid;
|
||||||
border-bottom: 1px lightgray solid;
|
border-bottom: 1px $channel-tree-color solid;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
}
|
}
|
||||||
.channel-wrapper:nth-last-child(n + 2) > .channel-tree:after,
|
.channel-wrapper:nth-last-child(n + 2) > .channel-tree:after,
|
||||||
|
@ -94,7 +138,7 @@ html, body {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 0px;
|
width: 0px;
|
||||||
border-left: 1px lightgray solid;
|
border-left: 1px $channel-tree-color solid;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
}
|
}
|
||||||
.user {
|
.user {
|
||||||
|
@ -109,12 +153,12 @@ html, body {
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
.selected {
|
.selected {
|
||||||
background-color: lightblue !important;
|
background-color: $channel-selected-bg-color !important;
|
||||||
border: 1px solid gray;
|
border: 1px solid $channel-selected-border-color;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
.user:hover,.channel:hover {
|
.user:hover,.channel:hover {
|
||||||
background-color: lightgray;
|
background-color: $channel-hover-bg-color;
|
||||||
}
|
}
|
||||||
.thisClient {
|
.thisClient {
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
|
@ -142,8 +186,8 @@ html, body {
|
||||||
.tooltip {
|
.tooltip {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
height: 0px;
|
height: 0px;
|
||||||
background: white;
|
background: $tooltip-bg-color;
|
||||||
border: 1px solid gray;
|
border: 1px solid $tooltip-border-color;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
margin-left: 30px;
|
margin-left: 30px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
@ -162,12 +206,12 @@ html, body {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
.toolbar img:hover {
|
.toolbar img:hover {
|
||||||
border: 1px solid #bbb;
|
border: 1px solid $toolbar-hover-bg-color;
|
||||||
background-color: #ddd;
|
background-color: $toolbar-hover-border-color;
|
||||||
}
|
}
|
||||||
.toolbar .tb-active {
|
.toolbar .tb-active {
|
||||||
border: 1px solid #bbb;
|
border: 1px solid $toolbar-active-bg-color;
|
||||||
background-color: white;
|
background-color: $toolbar-active-border-color;
|
||||||
}
|
}
|
||||||
.toolbar-horizontal {
|
.toolbar-horizontal {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -195,16 +239,16 @@ html, body {
|
||||||
}
|
}
|
||||||
.toolbar-horizontal .divider {
|
.toolbar-horizontal .divider {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border-left: 1px lightgray solid;
|
border-left: 1px $toolbar-divider-color solid;
|
||||||
}
|
}
|
||||||
.toolbar-vertical .divider {
|
.toolbar-vertical .divider {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
border-top: 1px lightgray solid;
|
border-top: 1px $toolbar-divider-color solid;
|
||||||
}
|
}
|
||||||
.toolbar-horizontal .handle-horizontal {
|
.toolbar-horizontal .handle-horizontal {
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
background-color: #eee !important;
|
background-color: $bg-color !important;
|
||||||
}
|
}
|
||||||
.toolbar-horizontal .handle-vertical {
|
.toolbar-horizontal .handle-vertical {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -212,7 +256,7 @@ html, body {
|
||||||
.toolbar-vertical .handle-vertical {
|
.toolbar-vertical .handle-vertical {
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
background-color: #eee !important;
|
background-color: $bg-color !important;
|
||||||
}
|
}
|
||||||
.toolbar-vertical .handle-horizontal {
|
.toolbar-vertical .handle-horizontal {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -222,16 +266,17 @@ html, body {
|
||||||
}
|
}
|
||||||
.channel-tag {
|
.channel-tag {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: orange;
|
color: $chat-channel-color;
|
||||||
}
|
}
|
||||||
.user-tag {
|
.user-tag {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: green;
|
color: $chat-user-color;
|
||||||
}
|
}
|
||||||
#message-box {
|
#message-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
|
color: $chat-input-color;
|
||||||
margin: 5px 0 5px 0;
|
margin: 5px 0 5px 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
@ -251,9 +296,9 @@ form {
|
||||||
width: calc(100% - 10px);
|
width: calc(100% - 10px);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: white;
|
color: $dialog-header-color;
|
||||||
background-color: gray;
|
background-color: $dialog-header-bg-color;
|
||||||
border-bottom: 1px solid darkgray;
|
border-bottom: 1px solid $dialog-header-border-bottom-color;
|
||||||
}
|
}
|
||||||
.dialog-footer {
|
.dialog-footer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -270,10 +315,10 @@ form {
|
||||||
.dialog-close, .dialog-submit {
|
.dialog-close, .dialog-submit {
|
||||||
width: 45%;
|
width: 45%;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border: 1px gray solid;
|
border: 1px $dialog-button-border-color solid;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: white;
|
background-color: $dialog-button-bg-color;
|
||||||
color: black;
|
color: $dialog-button-color;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
}
|
}
|
||||||
.connect-dialog table {
|
.connect-dialog table {
|
||||||
|
@ -282,8 +327,9 @@ form {
|
||||||
}
|
}
|
||||||
.dialog {
|
.dialog {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #eee;
|
background-color: $dialog-bg-color;
|
||||||
border: 1px gray solid;
|
color: $dialog-color;
|
||||||
|
border: 1px $dialog-border-color solid;
|
||||||
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
|
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
@ -309,7 +355,7 @@ form {
|
||||||
}
|
}
|
||||||
.settings-dialog .mic-volume-container {
|
.settings-dialog .mic-volume-container {
|
||||||
height: 10px;
|
height: 10px;
|
||||||
border: 3px solid black;
|
border: 3px solid $mic-volume-border-color;
|
||||||
}
|
}
|
||||||
.settings-dialog .mic-volume {
|
.settings-dialog .mic-volume {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -335,19 +381,19 @@ form {
|
||||||
}
|
}
|
||||||
.connect-dialog input[type=text] {
|
.connect-dialog input[type=text] {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border: 1px gray solid;
|
border: 1px $dialog-input-border-color solid;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: white;
|
background-color: $dialog-input-bg-color;
|
||||||
color: black;
|
color: $dialog-input-color;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
width: calc(100% - 8px);
|
width: calc(100% - 8px);
|
||||||
}
|
}
|
||||||
.connect-dialog input[type=password] {
|
.connect-dialog input[type=password] {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border: 1px gray solid;
|
border: 1px $dialog-input-border-color solid;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: white;
|
background-color: $dialog-input-bg-color;
|
||||||
color: black;
|
color: $dialog-input-color;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
width: calc(100% - 8px);
|
width: calc(100% - 8px);
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ module.exports = {
|
||||||
'./app/index.js',
|
'./app/index.js',
|
||||||
'./app/index.html'
|
'./app/index.html'
|
||||||
],
|
],
|
||||||
|
theme: './app/theme.js',
|
||||||
matrix: './app/matrix.js'
|
matrix: './app/matrix.js'
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
@ -51,6 +52,15 @@ module.exports = {
|
||||||
'css-loader'
|
'css-loader'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
loaders: [
|
||||||
|
'file-loader?name=[hash].css',
|
||||||
|
'extract-loader',
|
||||||
|
'css-loader',
|
||||||
|
'sass-loader'
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /manifest\.json$|\.xml$/,
|
test: /manifest\.json$|\.xml$/,
|
||||||
loaders: [
|
loaders: [
|
||||||
|
|
Loading…
Reference in a new issue