Introducing new bookmark and connection profile system
parent
1dbad341b9
commit
ac236d1646
|
@ -62,17 +62,6 @@ $background:lightgray;
|
|||
align-items: center;
|
||||
border: 2px solid rgba(0, 0, 0, 0);
|
||||
border-left: 0;
|
||||
|
||||
.arrow {
|
||||
border: solid black;
|
||||
border-width: 0 3px 3px 0;
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
@ -149,5 +138,62 @@ $background:lightgray;
|
|||
hr:last-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
|
||||
}
|
||||
|
||||
.bookmark, .directory {
|
||||
display: flex!important;
|
||||
flex-direction: row;
|
||||
|
||||
align-items: center;
|
||||
justify-content: stretch;
|
||||
|
||||
.name {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.icon, .arrow {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.directory {
|
||||
&:hover {
|
||||
> .sub-container, > .sub-container .sub-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:hover) {
|
||||
.sub-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-container {
|
||||
padding-right: 3px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sub-menu {
|
||||
display: none;
|
||||
left: 100%;
|
||||
top: -13px;
|
||||
position: absolute;
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
.modal .modal-bookmarks {
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
.bookmark-list {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
.entry {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
> .name {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.bookmark {
|
||||
&.selected {
|
||||
background-color: #0000FF77;
|
||||
}
|
||||
}
|
||||
|
||||
&.directory {
|
||||
&.selected {
|
||||
> .name {
|
||||
background-color: #0000FF77;
|
||||
}
|
||||
}
|
||||
|
||||
> .name {
|
||||
border: 0 solid gray;
|
||||
border-bottom: 1px solid #ad9d9d33;
|
||||
}
|
||||
.members {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
margin-top: 5px;
|
||||
text-align: right;
|
||||
|
||||
button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.group_box:not(:first-of-type) {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bookmark-setting {
|
||||
.group_box {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.property {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
input, select, .default-channel-container {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.default-channel-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
button {
|
||||
margin-left: 5px;
|
||||
max-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.key {
|
||||
width: 160px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal .modal-bookmark-create {
|
||||
.property {
|
||||
margin-top: 5px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
.key {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
select, input {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
text-align: right;
|
||||
|
||||
button {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
.modal .modal-connect {
|
||||
margin-top: 5px;
|
||||
|
||||
> div:not(:first-of-type) {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.profile-select-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
select {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-invalid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
|
||||
> div {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -245,3 +245,137 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal .settings-profiles {
|
||||
margin: 5px;
|
||||
> div:not(:first-of-type) {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.profile-status-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.profile-list {
|
||||
user-select: none;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
border: solid 1px lightgray;
|
||||
padding: 2px;
|
||||
background: #33333318;
|
||||
|
||||
height: 50%;
|
||||
min-height: 50%;
|
||||
max-height: 50%;
|
||||
|
||||
.entry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
cursor: pointer;
|
||||
|
||||
&.default {
|
||||
.name {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: #0000FF77;
|
||||
}
|
||||
|
||||
|
||||
.button {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #00000010;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.management {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
margin-top: 5px;
|
||||
float: right;
|
||||
|
||||
.space {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
button:not(:first-of-type) {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.general-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
|
||||
.setting {
|
||||
&:not(:first-of-type) {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
.key {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
input, div {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.select-container {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.identity-settings {
|
||||
display: none;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.identity-settings-teaforo {
|
||||
/*
|
||||
.connected, .disconnected {
|
||||
display: none
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
|
@ -101,15 +101,21 @@
|
|||
|
||||
|
||||
.group_box {
|
||||
display: grid;
|
||||
grid-template-rows: min-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
.header {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
float: left;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
background: rgba(0, 0, 0, .035);
|
||||
border: lightgray solid 1px;
|
||||
border-radius: 0 2px;
|
||||
|
@ -372,6 +378,7 @@
|
|||
border: solid black;
|
||||
border-width: 0 3px 3px 0;
|
||||
padding: 3px;
|
||||
height: 10px;
|
||||
|
||||
&.right {
|
||||
transform: rotate(-45deg);
|
||||
|
|
|
@ -49,6 +49,8 @@
|
|||
<link rel="stylesheet" href="css/ts/icons.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/general.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/modals.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/modal-bookmarks.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/modal-connect.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/modal-query.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/modal-banlist.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/modal-bancreate.css" type="text/css">
|
||||
|
|
|
@ -20,15 +20,14 @@
|
|||
<div class="icon_x32 client-disconnect"></div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="button-dropdown btn_bookmark" title="{{tr 'Bookmarks' /}}">
|
||||
<div class="buttons">
|
||||
<div class="button icon_x32 client-bookmark_manager btn_bookmark_list"></div>
|
||||
<div class="button-dropdown">
|
||||
<div class="arrow"></div>
|
||||
<div class="arrow down"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown bookmark-dropdown" style="width: 300px">
|
||||
<div class="dropdown bookmark-dropdown" style="width: 350px">
|
||||
<div class="btn_bookmark_list"><div class="icon client-bookmark_manager"></div><a>{{tr "Manage bookmarks" /}}</a></div>
|
||||
<div class="btn_bookmark_add"><div class="icon client-bookmark_add"></div><a>{{tr "Add current server to bookmarks" /}}</a></div>
|
||||
<div class="btn_bookmark_remove"><div class="icon client-bookmark_remove"></div><a>{{tr "Remove current server to bookmarks" /}}</a></div>
|
||||
|
@ -36,14 +35,14 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="button-dropdown btn_away" title="{{tr 'Toggle away status' /}}">
|
||||
<div class="buttons">
|
||||
<div class="button icon_x32 client-away btn_away_toggle"></div>
|
||||
<div class="button-dropdown">
|
||||
<div class="arrow"></div>
|
||||
<div class="arrow down"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
|
@ -63,7 +62,7 @@
|
|||
<div class="buttons">
|
||||
<div class="button icon_x32 client-token btn_token_use"></div>
|
||||
<div class="button-dropdown">
|
||||
<div class="arrow"></div>
|
||||
<div class="arrow down"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
|
@ -85,7 +84,7 @@
|
|||
<div class="buttons">
|
||||
<div class="button icon_x32 client-server_query btn_query_toggle"></div>
|
||||
<div class="button-dropdown">
|
||||
<div class="arrow"></div>
|
||||
<div class="arrow down"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown display_left">
|
||||
|
@ -134,7 +133,7 @@
|
|||
</script>
|
||||
<!-- Template for the connect modal -->
|
||||
<script class="jsrender-template" id="tmpl_connect" type="text/html">
|
||||
<div style="margin-top: 5px;">
|
||||
<div class="modal-connect">
|
||||
<div style="display: flex; flex-direction: row; width: 100%; justify-content: space-between">
|
||||
<div style="width: 68%; margin-bottom: 5px">
|
||||
<div>{{tr "Remote address and port:" /}}</div>
|
||||
|
@ -151,41 +150,15 @@
|
|||
<div>{{tr "Nickname:" /}}</div>
|
||||
<input type="text" style="width: 100%" class="connect_nickname" value="">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="group_box">
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<div style="text-align: right;">{{tr "Identity Settings" /}}</div>
|
||||
<select class="identity_select">
|
||||
<option name="identity_type" value="TEAFORO">{{tr "Forum Account" /}}</option>
|
||||
<option name="identity_type" value="TEAMSPEAK">{{tr "TeamSpeak" /}}</option>
|
||||
<option name="identity_type" value="NICKNAME">{{tr "Nickname (Debug purposes only!)" /}}</option> <!-- Only available on localhost for debug -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="profile-select-container">
|
||||
<div style="text-align: right;">{{tr "Connection profile:" /}}</div>
|
||||
<select class="profile-select"> </select>
|
||||
</div>
|
||||
<div class="profile-invalid">
|
||||
<hr>
|
||||
<div class="identity_config_TEAMSPEAK identity_config">
|
||||
{{tr "Please enter your exported TS3 Identity string bellow or select your exported Identity"/}}<br>
|
||||
<div style="width: 100%; display: flex; justify-content: stretch; flex-direction: row">
|
||||
<input placeholder="Identity string" style="min-width: 60%; flex-shrink: 1; flex-grow: 2; margin: 5px;" class="identity_string">
|
||||
<div style="max-width: 200px; flex-grow: 1; flex-shrink: 4; margin: 5px"><input style="display: flex; width: 100%;" class="identity_file" type="file"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="identity_config_TEAFORO identity_config">
|
||||
<div class="connected">
|
||||
{{tr "You're using your forum account as verification"/}}
|
||||
</div>
|
||||
<div class="disconnected">
|
||||
<!-- TODO tr -->
|
||||
You cant use your TeaSpeak forum account.<br>
|
||||
You're not connected!<br>
|
||||
Click {{if !client}}<a href="{{:forum_path}}login.php">here</a>{{else}}<a href="#" class="native-teaforo-login">here</a>{{/if}} to login via the TeaSpeak forum.
|
||||
</div>
|
||||
</div>
|
||||
<div class="identity_config_NICKNAME identity_config">
|
||||
{{tr "This is just for debug and uses the name as unique identifier" /}}
|
||||
</div>
|
||||
|
||||
<div style="background-color: red; border-radius: 1px; display: none" class="error_message"></div>
|
||||
</div> <!-- <a href="<?php echo authPath() . "login.php"; ?>">Login</a> via the TeaSpeak forum. -->
|
||||
<div>The selected connect profile is invalid</div>
|
||||
<div><div>Click </div><a href="#" class="button-manage-profiles">here</a><div> to manage your connect profiles</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
@ -701,7 +674,7 @@
|
|||
<div class="settings-translations">
|
||||
<div class="group_box">
|
||||
<div class="header">{{tr "Available translations" /}}</div>
|
||||
<div class="content settings-microphone">
|
||||
<div class="content">
|
||||
<div class="setting-list">
|
||||
<div class="list">
|
||||
<!--
|
||||
|
@ -732,9 +705,115 @@
|
|||
</div>
|
||||
</x-content>
|
||||
</x-entry>
|
||||
<x-entry>
|
||||
<x-tag class="tab-profiles">
|
||||
{{tr "Profiles" /}}
|
||||
</x-tag>
|
||||
<x-content>
|
||||
<div class="settings-profiles">
|
||||
<div class="group_box">
|
||||
<div class="header">{{tr "Available profiles" /}}</div>
|
||||
<div class="content">
|
||||
<div class="profile-list">
|
||||
<div class="list">
|
||||
<!--
|
||||
<div class="entry default">{{tr "English (Default / Fallback)" /}}</div>
|
||||
<div class="entry repository">
|
||||
<div class="name">TeaSpeak Official</div>
|
||||
<div class="button button-delete"><div class="icon client-delete"></div></div>
|
||||
<div class="button button-info"><div class="icon client-about"></div></div>
|
||||
</div>
|
||||
<div class="entry translation selected">
|
||||
<div class="name">German (Google Translate)</div>
|
||||
<div class="button button-info"><div class="icon client-about"></div></div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<div class="management">
|
||||
<button class="button-set-default">{{tr "Set selected as default" /}}</button>
|
||||
<button class="button-delete">{{tr "Delete selected" /}}</button>
|
||||
<div class="space"></div>
|
||||
<button class="button-add-profile">{{tr "Create profile" /}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group_box">
|
||||
<div class="header">{{tr "Profile settings" /}}</div>
|
||||
<div class="content">
|
||||
<div class="profile-settings">
|
||||
<div class="general-settings">
|
||||
<div class="setting">
|
||||
<div class="key">{{tr "Profile name:" /}}</div>
|
||||
<input class="value setting-name">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="key">{{tr "Default nickname:" /}}</div>
|
||||
<input class="value setting-default-nickname">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="key">{{tr "Default server password:" /}}</div>
|
||||
<input class="value setting-default-password">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="key">{{tr "Identify Type:" /}}</div>
|
||||
<div class="select-container">
|
||||
<select>
|
||||
<option name="identity-type" value="teaforo">{{tr "Forum Account" /}}</option>
|
||||
<option name="identity-type" value="teamspeak">{{tr "TeamSpeak" /}}</option>
|
||||
<option name="identity-type" value="nickname">{{tr "Nickname (Debug purposes only!)" /}}</option> <!-- Only available on localhost for debug -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="identity-settings identity-settings-teamspeak">
|
||||
{{tr "Please enter your exported TS3 Identity string bellow or select your exported Identity"/}}<br>
|
||||
<div style="width: 100%; display: flex; justify-content: stretch; flex-direction: row">
|
||||
<input placeholder="Identity string" style="min-width: 60%; flex-shrink: 1; flex-grow: 2; margin: 5px;" class="identity_string">
|
||||
<div style="max-width: 200px; flex-grow: 1; flex-shrink: 4; margin: 5px"><input style="display: flex; width: 100%;" class="identity_file" type="file"></div>
|
||||
</div>
|
||||
<div class="error-message">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="identity-settings identity-settings-teaforo">
|
||||
<div class="connected">
|
||||
{{tr "You're using your forum account as verification"/}}
|
||||
</div>
|
||||
<div class="disconnected">
|
||||
<!-- TODO tr -->
|
||||
You cant use your TeaSpeak forum account. You're not connected!<br>
|
||||
Click {{if !client}}<a href="{{:forum_path}}login.php">here</a>{{else}}<a href="#" class="native-teaforo-login">here</a>{{/if}} to login via the TeaSpeak forum.
|
||||
</div>
|
||||
</div>
|
||||
<div class="identity-settings identity-settings-nickname">
|
||||
<a>
|
||||
{{tr "This is just for debug and uses the name as unique identifier" /}}
|
||||
</a>
|
||||
<div>
|
||||
<a>{{tr "Username:" /}}</a>
|
||||
<input class="setting-name" placeholder="WolverinDEV">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-content>
|
||||
</x-entry>
|
||||
</x-tab>
|
||||
</script>
|
||||
|
||||
<script class="jsrender-template" id="settings-profile-list-entry" type="text/html">
|
||||
<div class="entry profile {{if id == 'default'}}default{{/if}}">
|
||||
<div class="name">{{>profile_name}}</div>
|
||||
<!-- <div class="button button-delete"><div class="icon client-delete"></div></div> -->
|
||||
<div title="{{tr 'Profile is valid' /}}" class="icon client-delete status status-invalid"></div>
|
||||
<div title="{{tr 'Profile is invalid' /}}" class="icon client-apply status status-valid"></div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script class="jsrender-template" id="settings-translations-list-entry" type="text/html">
|
||||
{{if type == "repository" }}
|
||||
<div class="entry repository" repository="{{:id}}">
|
||||
|
@ -1106,7 +1185,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</script>
|
||||
9
|
||||
|
||||
<script class="jsrender-template" id="tmpl_client_ban" type="text/html">
|
||||
<div class="align_column">
|
||||
<div class="align_column" style="margin: 5px">
|
||||
|
@ -1767,5 +1846,123 @@
|
|||
<div class="column column-bound-server">{{>bounded_server}}</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script class="jsrender-template" id="tmpl_manage_bookmarks" type="text/html">
|
||||
<div class="modal-bookmarks">
|
||||
<div class="group_box">
|
||||
<div class="header">{{tr "Bookmarks" /}}</div>
|
||||
<div class="content bookmark-list">
|
||||
<div class="list">
|
||||
<div class="entry bookmark">
|
||||
<div class="name">TeaSpeak official</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button class="button-create">{{tr "Create new bookmark/directory" /}}</button>
|
||||
<button class="button-delete">{{tr "Delete selected bookmark/directory" /}}</button>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="group_box">
|
||||
<div class="header">{{tr "Bookmark settings" /}}</div>
|
||||
<div class="content">
|
||||
<div class="bookmark-setting bookmark-setting-bookmark">
|
||||
<div class="property">
|
||||
<div class="key">{{tr "Bookmark name:" /}}</div>
|
||||
<input class="setting-bookmark-name">
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="key">{{tr "Connect profile:" /}}</div>
|
||||
<select class="setting-bookmark-profile"></select>
|
||||
</div>
|
||||
<div class="group_box">
|
||||
<div class="header">{{tr "Server Properties" /}}</div>
|
||||
<div class="content">
|
||||
<div class="property">
|
||||
<div class="key">{{tr "Server address:" /}}</div>
|
||||
<input class="setting-server-host">
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="key">{{tr "Server Port:" /}}</div>
|
||||
<input class="setting-server-port" type="number">
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="key">{{tr "Server Password:" /}}</div>
|
||||
<input class="setting-server-password" type="password" id="bookmark_server_password_{{rnd '0~13377331'/}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group_box">
|
||||
<div class="header">{{tr "Connect Properties (Not yet supported)" /}}</div>
|
||||
<div class="content">
|
||||
<div class="property">
|
||||
<div class="key">{{tr "Username:" /}}</div>
|
||||
<input class="setting-username" disabled>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="key">{{tr "Default channel:" /}}</div>
|
||||
<div class="default-channel-container">
|
||||
<input class="setting-channel" disabled>
|
||||
<button class="button-set-to-current" disabled>{{tr "current channel" /}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="key">{{tr "Channel password:" /}}</div>
|
||||
<input class="setting-channel-password" type="password" id="bookmark_channel_password_{{rnd '0~13377331'/}}" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bookmark-setting bookmark-setting-directory">
|
||||
<div class="property">
|
||||
<div class="key">{{tr "Directory name:" /}}</div>
|
||||
<input class="setting-bookmark-name">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script class="jsrender-template" id="tmpl_manage_bookmarks-list_entry" type="text/html">
|
||||
{{if type == "bookmark" }}
|
||||
<div class="entry bookmark">
|
||||
<div class="name">{{>name}}</div>
|
||||
</div>
|
||||
{{else type == "directory" }}
|
||||
<div class="entry directory">
|
||||
<div class="name">{{>name}}</div>
|
||||
<div class="members"> </div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</script>
|
||||
|
||||
|
||||
<script class="jsrender-template" id="tmpl_manage_bookmarks-create" type="text/html">
|
||||
<div class="modal-bookmark-create">
|
||||
<div class="property">
|
||||
<div class="key">Bookmark Type:</div>
|
||||
<select class="bookmark-type">
|
||||
<option value="bookmark">Bookmark</option>
|
||||
<option value="directory">Directory</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="key">Parent Directory:</div>
|
||||
<select class="bookmark-parent">
|
||||
<option bookmark-uuid=""></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="key">Bookmark Name:</div>
|
||||
<input class="bookmark-name">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="buttons">
|
||||
<button class="button-create">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,159 +0,0 @@
|
|||
enum IdentitifyType {
|
||||
TEAFORO,
|
||||
TEAMSPEAK,
|
||||
NICKNAME
|
||||
}
|
||||
|
||||
namespace TSIdentityHelper {
|
||||
export let funcationParseIdentity: any;
|
||||
export let funcationParseIdentityByFile: any;
|
||||
export let funcationCalculateSecurityLevel: any;
|
||||
export let functionUid: any;
|
||||
export let funcationExportIdentity: any;
|
||||
export let funcationPublicKey: any;
|
||||
export let funcationSignMessage: any;
|
||||
|
||||
let functionLastError: any;
|
||||
let functionClearLastError: any;
|
||||
|
||||
let functionDestroyString: any;
|
||||
let functionDestroyIdentity: any;
|
||||
|
||||
export function setup() : boolean {
|
||||
functionDestroyString = Module.cwrap("destroy_string", "pointer", []);
|
||||
functionLastError = Module.cwrap("last_error_message", null, ["string"]);
|
||||
funcationParseIdentity = Module.cwrap("parse_identity", "pointer", ["string"]);
|
||||
funcationParseIdentityByFile = Module.cwrap("parse_identity_file", "pointer", ["string"]);
|
||||
functionDestroyIdentity = Module.cwrap("delete_identity", null, ["pointer"]);
|
||||
|
||||
funcationCalculateSecurityLevel = Module.cwrap("identity_security_level", "pointer", ["pointer"]);
|
||||
funcationExportIdentity = Module.cwrap("identity_export", "pointer", ["pointer"]);
|
||||
funcationPublicKey = Module.cwrap("identity_key_public", "pointer", ["pointer"]);
|
||||
funcationSignMessage = Module.cwrap("identity_sign", "pointer", ["pointer", "string", "number"]);
|
||||
functionUid = Module.cwrap("identity_uid", "pointer", ["pointer"]);
|
||||
|
||||
return Module.cwrap("tomcrypt_initialize", "number", [])() == 0;
|
||||
}
|
||||
|
||||
export function last_error() : string {
|
||||
return unwarpString(functionLastError());
|
||||
}
|
||||
|
||||
export function unwarpString(str) : string {
|
||||
if(str == "") return "";
|
||||
try {
|
||||
if(!$.isFunction(window.Pointer_stringify)) {
|
||||
displayCriticalError(tr("Missing required wasm function!<br>Please reload the page!"));
|
||||
}
|
||||
let message: string = window.Pointer_stringify(str);
|
||||
functionDestroyString(str);
|
||||
return message;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export function loadIdentity(key: string) : TeamSpeakIdentity {
|
||||
let handle = funcationParseIdentity(key);
|
||||
if(!handle) return undefined;
|
||||
return new TeamSpeakIdentity(handle, "TeaWeb user");
|
||||
}
|
||||
|
||||
export function loadIdentityFromFileContains(contains: string) : TeamSpeakIdentity {
|
||||
let handle = funcationParseIdentityByFile(contains);
|
||||
if(!handle) return undefined;
|
||||
return new TeamSpeakIdentity(handle, "TeaWeb user");
|
||||
}
|
||||
}
|
||||
|
||||
interface Identity {
|
||||
name() : string;
|
||||
uid() : string;
|
||||
type() : IdentitifyType;
|
||||
|
||||
valid() : boolean;
|
||||
}
|
||||
|
||||
class NameIdentity implements Identity {
|
||||
private _name: string;
|
||||
|
||||
constructor(name: string) {
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
set_name(name: string) { this._name = name; }
|
||||
|
||||
name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
uid(): string {
|
||||
return btoa(this._name); //FIXME hash!
|
||||
}
|
||||
|
||||
type(): IdentitifyType {
|
||||
return IdentitifyType.NICKNAME;
|
||||
}
|
||||
|
||||
valid(): boolean {
|
||||
return this._name != undefined && this._name != "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TeamSpeakIdentity implements Identity {
|
||||
private handle: any;
|
||||
private _name: string;
|
||||
|
||||
constructor(handle: any, name: string) {
|
||||
this.handle = handle;
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
securityLevel() : number | undefined {
|
||||
return parseInt(TSIdentityHelper.unwarpString(TSIdentityHelper.funcationCalculateSecurityLevel(this.handle)));
|
||||
}
|
||||
|
||||
name() : string { return this._name; }
|
||||
|
||||
uid() : string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.functionUid(this.handle));
|
||||
}
|
||||
|
||||
type() : IdentitifyType { return IdentitifyType.TEAMSPEAK; }
|
||||
|
||||
signMessage(message: string): string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.funcationSignMessage(this.handle, message, message.length));
|
||||
}
|
||||
|
||||
exported() : string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.funcationExportIdentity(this.handle));
|
||||
}
|
||||
|
||||
publicKey() : string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.funcationPublicKey(this.handle));
|
||||
}
|
||||
|
||||
valid() : boolean { return true; }
|
||||
}
|
||||
|
||||
class TeaForumIdentity implements Identity {
|
||||
readonly identityData: string;
|
||||
readonly identityDataJson: string;
|
||||
readonly identitySign: string;
|
||||
|
||||
valid() : boolean {
|
||||
return this.identityData.length > 0 && this.identityDataJson.length > 0 && this.identitySign.length > 0;
|
||||
}
|
||||
|
||||
constructor(data: string, sign: string) {
|
||||
this.identityDataJson = data;
|
||||
this.identityData = JSON.parse(this.identityDataJson);
|
||||
this.identitySign = sign;
|
||||
}
|
||||
|
||||
name() : string { return this.identityData["user_name"]; }
|
||||
uid() : string { return "TeaForo#" + this.identityData["user_id"]; }
|
||||
type() : IdentitifyType { return IdentitifyType.TEAFORO; }
|
||||
}
|
|
@ -1,21 +1,14 @@
|
|||
namespace bookmarks {
|
||||
function guid() {
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
return Math
|
||||
.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
|
||||
}
|
||||
|
||||
export interface ConnectIdentity {
|
||||
identity_type: IdentitifyType;
|
||||
}
|
||||
|
||||
export interface ForumConnectIdentity extends ConnectIdentity { }
|
||||
export interface NicknameConnectIdentity extends ConnectIdentity { }
|
||||
export interface TeamSpeakConnectIdentity extends ConnectIdentity { }
|
||||
|
||||
export interface ServerProperties {
|
||||
server_address: string;
|
||||
server_port: number;
|
||||
|
@ -41,6 +34,7 @@ namespace bookmarks {
|
|||
default_channel_password_hash?: string;
|
||||
default_channel_password?: string;
|
||||
|
||||
connect_profile: string;
|
||||
}
|
||||
|
||||
export interface DirectoryBookmark {
|
||||
|
@ -104,12 +98,28 @@ namespace bookmarks {
|
|||
return find_bookmark_recursive(bookmarks(), uuid);
|
||||
}
|
||||
|
||||
export function parent_bookmark(bookmark: Bookmark) : DirectoryBookmark {
|
||||
const books: (DirectoryBookmark | Bookmark)[] = [bookmarks()];
|
||||
while(!books.length) {
|
||||
const directory = books.pop_front();
|
||||
if(directory.type == BookmarkType.DIRECTORY) {
|
||||
const cast = <DirectoryBookmark>directory;
|
||||
|
||||
if(cast.content.indexOf(bookmark) != -1)
|
||||
return cast;
|
||||
books.push(...cast.content);
|
||||
}
|
||||
}
|
||||
return bookmarks();
|
||||
}
|
||||
|
||||
export function create_bookmark(display_name: string, directory: DirectoryBookmark, server_properties: ServerProperties, nickname: string) : Bookmark {
|
||||
const bookmark = {
|
||||
display_name: display_name,
|
||||
server_properties: server_properties,
|
||||
nickname: nickname,
|
||||
type: BookmarkType.ENTRY,
|
||||
connect_profile: "default",
|
||||
unique_id: guid()
|
||||
} as Bookmark;
|
||||
|
||||
|
@ -132,7 +142,7 @@ namespace bookmarks {
|
|||
|
||||
//TODO test if the new parent is within the old bookmark
|
||||
export function change_directory(parent: DirectoryBookmark, bookmark: Bookmark | DirectoryBookmark) {
|
||||
delete_bookmark(bookmark)
|
||||
delete_bookmark(bookmark);
|
||||
parent.content.push(bookmark)
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace MessageHelper {
|
|||
return this.formatElement("<unknwon object>");
|
||||
} else if(typeof(object) === "function") return this.formatElement(object());
|
||||
else if(typeof(object) === "undefined") return this.formatElement("<undefined>");
|
||||
else if(typeof(object) === "number") return [$.spawn("a").text(object)];
|
||||
return this.formatElement("<unknown object type " + typeof object + ">");
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ enum DisconnectReason {
|
|||
CONNECTION_PING_TIMEOUT,
|
||||
CLIENT_KICKED,
|
||||
CLIENT_BANNED,
|
||||
HANDSHAKE_FAILED,
|
||||
SERVER_CLOSED,
|
||||
SERVER_REQUIRES_PASSWORD,
|
||||
UNKNOWN
|
||||
|
@ -76,7 +77,7 @@ class TSClient {
|
|||
this.controlBar.initialise();
|
||||
}
|
||||
|
||||
startConnection(addr: string, identity: Identity, name?: string, password?: {password: string, hashed: boolean}) {
|
||||
startConnection(addr: string, profile: profiles.ConnectionProfile, name?: string, password?: {password: string, hashed: boolean}) {
|
||||
if(this.serverConnection)
|
||||
this.handleDisconnect(DisconnectReason.REQUESTED);
|
||||
|
||||
|
@ -96,17 +97,17 @@ class TSClient {
|
|||
|
||||
if(password && !password.hashed) {
|
||||
helpers.hashPassword(password.password).then(password => {
|
||||
this.serverConnection.startConnection({host, port}, new HandshakeHandler(identity, name, password));
|
||||
this.serverConnection.startConnection({host, port}, new HandshakeHandler(profile, name, password));
|
||||
}).catch(error => {
|
||||
createErrorModal(tr("Error while hashing password"), tr("Failed to hash server password!<br>") + error).open();
|
||||
})
|
||||
} else
|
||||
this.serverConnection.startConnection({host, port}, new HandshakeHandler(identity, name, password ? password.password : undefined));
|
||||
this.serverConnection.startConnection({host, port}, new HandshakeHandler(profile, name, password ? password.password : undefined));
|
||||
}
|
||||
|
||||
|
||||
getClient() : LocalClientEntry { return this._ownEntry; }
|
||||
getClientId(){ return this._clientId; } //TODO here
|
||||
getClientId() { return this._clientId; } //TODO here
|
||||
|
||||
set clientId(id: number) {
|
||||
this._clientId = id;
|
||||
|
@ -141,6 +142,13 @@ class TSClient {
|
|||
}
|
||||
|
||||
private certAcceptUrl() {
|
||||
//TODO here
|
||||
const properties = {
|
||||
connect_direct: true,
|
||||
connect_profile: this.serverConnection._handshakeHandler.profile.id,
|
||||
connect_url: this.serverConnection._remote_address.host + ":" + this.serverConnection._remote_address.port
|
||||
};
|
||||
|
||||
// document.URL
|
||||
let callback = document.URL;
|
||||
if(document.location.search.length == 0)
|
||||
|
@ -148,7 +156,11 @@ class TSClient {
|
|||
else
|
||||
callback += "&default_connect_url=true";
|
||||
//
|
||||
switch (this.serverConnection._handshakeHandler.identity.type()) {
|
||||
|
||||
//this.serverConnection._handshakeHandler.profile
|
||||
callback += "&connect_profile=" + encodeURIComponent(this.serverConnection._handshakeHandler.profile.id);
|
||||
/*
|
||||
switch (this.serverConnection._handshakeHandler.profile.type()) {
|
||||
case IdentitifyType.TEAFORO:
|
||||
callback += "&default_connect_type=teaforo";
|
||||
break;
|
||||
|
@ -156,6 +168,7 @@ class TSClient {
|
|||
callback += "&default_connect_type=teamspeak";
|
||||
break;
|
||||
}
|
||||
*/
|
||||
callback += "&default_connect_url=" + encodeURIComponent(this.serverConnection._remote_address.host + ":" + this.serverConnection._remote_address.port);
|
||||
|
||||
return "https://" + this.serverConnection._remote_address.host + ":" + this.serverConnection._remote_address.port + "/?forward_url=" + encodeURIComponent(callback);
|
||||
|
@ -166,8 +179,7 @@ class TSClient {
|
|||
case DisconnectReason.REQUESTED:
|
||||
break;
|
||||
case DisconnectReason.CONNECT_FAILURE:
|
||||
console.error(tr("Could not connect to remote host! Exception"));
|
||||
console.error(data);
|
||||
console.error(tr("Could not connect to remote host! Exception: %o"), data);
|
||||
|
||||
if(native_client) {
|
||||
createErrorModal(
|
||||
|
@ -185,6 +197,14 @@ class TSClient {
|
|||
}
|
||||
sound.play(Sound.CONNECTION_REFUSED);
|
||||
break;
|
||||
case DisconnectReason.HANDSHAKE_FAILED:
|
||||
//TODO sound
|
||||
console.error(tr("Failed to process handshake: %o"), data);
|
||||
createErrorModal(
|
||||
tr("Could not connect"),
|
||||
tr("Failed to process handshake: ") + data as string
|
||||
).open();
|
||||
break;
|
||||
case DisconnectReason.CONNECTION_CLOSED:
|
||||
console.error(tr("Lost connection to remote server!"));
|
||||
createErrorModal(
|
||||
|
@ -215,7 +235,7 @@ class TSClient {
|
|||
createInputModal(tr("Server password"), tr("Enter server password:"), password => password.length != 0, password => {
|
||||
if(!(typeof password === "string")) return;
|
||||
this.startConnection(this.serverConnection._remote_address.host + ":" + this.serverConnection._remote_address.port,
|
||||
this.serverConnection._handshakeHandler.identity,
|
||||
this.serverConnection._handshakeHandler.profile,
|
||||
this.serverConnection._handshakeHandler.name,
|
||||
{password: password as string, hashed: false});
|
||||
}).open();
|
||||
|
|
|
@ -277,59 +277,50 @@ class ServerConnection {
|
|||
}
|
||||
}
|
||||
|
||||
class HandshakeHandler {
|
||||
readonly identity: Identity;
|
||||
readonly name?: string;
|
||||
private connection: ServerConnection;
|
||||
server_password: string;
|
||||
interface HandshakeIdentityHandler {
|
||||
connection: ServerConnection;
|
||||
|
||||
constructor(identity: Identity, name?: string, password?: string) {
|
||||
this.identity = identity;
|
||||
start_handshake();
|
||||
register_callback(callback: (success: boolean, message?: string) => any);
|
||||
}
|
||||
|
||||
class HandshakeHandler {
|
||||
private connection: ServerConnection;
|
||||
private handshake_handler: HandshakeIdentityHandler;
|
||||
|
||||
readonly profile: profiles.ConnectionProfile;
|
||||
readonly name: string;
|
||||
readonly server_password: string;
|
||||
|
||||
constructor(profile: profiles.ConnectionProfile, name: string, password: string) {
|
||||
this.profile = profile;
|
||||
this.server_password = password;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
setConnection(con: ServerConnection) {
|
||||
this.connection = con;
|
||||
this.connection.commandHandler["handshakeidentityproof"] = this.handleCommandHandshakeIdentityProof.bind(this);
|
||||
}
|
||||
|
||||
startHandshake() {
|
||||
let data: any = {
|
||||
intention: 0,
|
||||
authentication_method: this.identity.type()
|
||||
};
|
||||
if(this.identity.type() == IdentitifyType.TEAMSPEAK) {
|
||||
data.publicKey = (this.identity as TeamSpeakIdentity).publicKey();
|
||||
} else if(this.identity.type() == IdentitifyType.TEAFORO) {
|
||||
data.data = (this.identity as TeaForumIdentity).identityDataJson;
|
||||
} else if(this.identity.type() == IdentitifyType.NICKNAME) {
|
||||
data["client_nickname"] = this.identity.name();
|
||||
this.handshake_handler = this.profile.spawn_identity_handshake_handler(this.connection);
|
||||
if(!this.handshake_handler) {
|
||||
this.handshake_failed("failed to create identity handler");
|
||||
return;
|
||||
}
|
||||
|
||||
this.connection.sendCommand("handshakebegin", data).catch(error => {
|
||||
console.log(error);
|
||||
//TODO here
|
||||
}).then(() => {
|
||||
if(this.identity.type() == IdentitifyType.NICKNAME) {
|
||||
this.handshake_handler.register_callback((flag, message) => {
|
||||
if(flag)
|
||||
this.handshake_finished();
|
||||
}
|
||||
else
|
||||
this.handshake_failed(message);
|
||||
});
|
||||
|
||||
this.handshake_handler.start_handshake();
|
||||
}
|
||||
|
||||
private handleCommandHandshakeIdentityProof(json) {
|
||||
let proof: string;
|
||||
if(this.identity.type() == IdentitifyType.TEAMSPEAK) {
|
||||
proof = (this.identity as TeamSpeakIdentity).signMessage(json[0]["message"]);
|
||||
} else if(this.identity.type() == IdentitifyType.TEAFORO) {
|
||||
proof = (this.identity as TeaForumIdentity).identitySign;
|
||||
} else if(this.identity.type() == IdentitifyType.NICKNAME) {
|
||||
//FIXME handle error this should never happen!
|
||||
}
|
||||
this.connection.sendCommand("handshakeindentityproof", {proof: proof}).catch(error => {
|
||||
console.error(tr("Got login error"));
|
||||
console.log(error);
|
||||
}).then(() => this.handshake_finished()); //TODO handle error
|
||||
private handshake_failed(message: string) {
|
||||
this.connection._client.handleDisconnect(DisconnectReason.HANDSHAKE_FAILED, message);
|
||||
}
|
||||
|
||||
private handshake_finished(version?: string) {
|
||||
|
@ -348,7 +339,7 @@ class HandshakeHandler {
|
|||
const browser_name = (navigator.browserSpecs || {})["name"] || " ";
|
||||
let data = {
|
||||
//TODO variables!
|
||||
client_nickname: this.name ? this.name : this.identity.name(),
|
||||
client_nickname: this.name,
|
||||
client_platform: (browser_name ? browser_name + " " : "") + navigator.platform,
|
||||
client_version: "TeaWeb " + git_version + " (" + navigator.userAgent + ")",
|
||||
|
||||
|
|
|
@ -110,14 +110,19 @@ function load_script(path: string | string[]) : Promise<Boolean> {
|
|||
});
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tag = document.createElement("script");
|
||||
const tag: HTMLScriptElement = document.createElement("script");
|
||||
tag.type = "application/javascript";
|
||||
tag.async = true;
|
||||
tag.defer = true;
|
||||
tag.onerror = error => {
|
||||
console.log(error);
|
||||
tag.remove();
|
||||
reject(error);
|
||||
};
|
||||
tag.onload = () => resolve();
|
||||
tag.onload = () => {
|
||||
console.debug("Script %o loaded", path);
|
||||
resolve();
|
||||
};
|
||||
document.getElementById("scripts").appendChild(tag);
|
||||
tag.src = path;
|
||||
});
|
||||
|
@ -179,9 +184,14 @@ function loadDebug() {
|
|||
"js/crypto/sha.js",
|
||||
"js/crypto/hex.js",
|
||||
|
||||
//load the profiles
|
||||
"js/profiles/ConnectionProfile.js",
|
||||
"js/profiles/Identity.js",
|
||||
|
||||
//Load UI
|
||||
"js/ui/modal/ModalQuery.js",
|
||||
"js/ui/modal/ModalQueryManage.js",
|
||||
"js/ui/modal/ModalBookmarks.js",
|
||||
"js/ui/modal/ModalConnect.js",
|
||||
"js/ui/modal/ModalSettings.js",
|
||||
"js/ui/modal/ModalCreateChannel.js",
|
||||
|
@ -227,12 +237,14 @@ function loadDebug() {
|
|||
"js/FileManager.js",
|
||||
"js/client.js",
|
||||
"js/chat.js",
|
||||
"js/Identity.js",
|
||||
|
||||
"js/PPTListener.js",
|
||||
...custom_scripts
|
||||
]).then(() => load_wait_scripts([
|
||||
"js/codec/CodecWrapperWorker.js"
|
||||
"js/codec/CodecWrapperWorker.js",
|
||||
"js/profiles/identities/NameIdentity.js", //Depends on Identity
|
||||
"js/profiles/identities/TeaForumIdentity.js", //Depends on Identity
|
||||
"js/profiles/identities/TeamSpeakIdentity.js", //Depends on Identity
|
||||
])).then(() => load_wait_scripts([
|
||||
"js/main.js"
|
||||
])).then(() => {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/// <reference path="chat.ts" />
|
||||
/// <reference path="client.ts" />
|
||||
/// <reference path="Identity.ts" />
|
||||
/// <reference path="utils/modal.ts" />
|
||||
/// <reference path="ui/modal/ModalConnect.ts" />
|
||||
/// <reference path="ui/modal/ModalCreateChannel.ts" />
|
||||
|
@ -15,8 +14,6 @@ let settings: Settings;
|
|||
let globalClient: TSClient;
|
||||
let chat: ChatBox;
|
||||
|
||||
let forumIdentity: TeaForumIdentity;
|
||||
|
||||
const js_render = window.jsrender || $;
|
||||
const native_client = window.require !== undefined;
|
||||
|
||||
|
@ -27,31 +24,34 @@ function getUserMediaFunction() {
|
|||
}
|
||||
|
||||
function setup_close() {
|
||||
if(settings.static(Settings.KEY_DISABLE_UNLOAD_DIALOG, false)) return;
|
||||
|
||||
window.onbeforeunload = event => {
|
||||
if(!globalClient.serverConnection || !globalClient.serverConnection.connected) return;
|
||||
if(profiles.requires_save())
|
||||
profiles.save();
|
||||
|
||||
if(!native_client) {
|
||||
event.returnValue = "Are you really sure?<br>You're still connected!";
|
||||
} else {
|
||||
event.preventDefault();
|
||||
event.returnValue = "question";
|
||||
if(!settings.static(Settings.KEY_DISABLE_UNLOAD_DIALOG, false)) {
|
||||
if(!globalClient.serverConnection || !globalClient.serverConnection.connected) return;
|
||||
|
||||
const {remote} = require('electron');
|
||||
const dialog = remote.dialog;
|
||||
if(!native_client) {
|
||||
event.returnValue = "Are you really sure?<br>You're still connected!";
|
||||
} else {
|
||||
event.preventDefault();
|
||||
event.returnValue = "question";
|
||||
|
||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
const {remote} = require('electron');
|
||||
const dialog = remote.dialog;
|
||||
|
||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'question',
|
||||
buttons: ['Yes', 'No'],
|
||||
title: 'Confirm',
|
||||
message: 'Are you really sure?\nYou\'re still connected!'
|
||||
}, choice => {
|
||||
if(choice === 0) {
|
||||
window.onbeforeunload = undefined;
|
||||
remote.getCurrentWindow().close();
|
||||
}
|
||||
});
|
||||
}, choice => {
|
||||
if(choice === 0) {
|
||||
window.onbeforeunload = undefined;
|
||||
remote.getCurrentWindow().close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -125,10 +125,11 @@ async function initialize() {
|
|||
}
|
||||
|
||||
AudioController.initializeAudioController();
|
||||
if(!TSIdentityHelper.setup()) {
|
||||
if(!profiles.identities.setup_teamspeak()) {
|
||||
console.error(tr("Could not setup the TeamSpeak identity parser!"));
|
||||
return;
|
||||
}
|
||||
profiles.load();
|
||||
|
||||
try {
|
||||
await ppt.initialize();
|
||||
|
@ -145,9 +146,7 @@ function main() {
|
|||
settings = new Settings();
|
||||
globalClient = new TSClient();
|
||||
/** Setup the XF forum identity **/
|
||||
if(settings.static("forum_user_data")) {
|
||||
forumIdentity = new TeaForumIdentity(settings.static("forum_user_data"), settings.static("forum_user_sign"));
|
||||
}
|
||||
profiles.identities.setup_forum();
|
||||
|
||||
chat = new ChatBox($("#chat"));
|
||||
globalClient.setup();
|
||||
|
@ -160,6 +159,8 @@ function main() {
|
|||
//Modals.spawnSettingsModal();
|
||||
//Modals.createChannelModal(undefined);
|
||||
|
||||
/*
|
||||
//FIXME
|
||||
if(settings.static("default_connect_url")) {
|
||||
switch (settings.static("default_connect_type")) {
|
||||
case "teaforo":
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
namespace profiles {
|
||||
|
||||
export class ConnectionProfile {
|
||||
id: string;
|
||||
|
||||
profile_name: string;
|
||||
default_username: string;
|
||||
default_password: string;
|
||||
|
||||
selected_identity_type: string = "unset";
|
||||
identities: {[key:string]:identities.Identity} = {};
|
||||
|
||||
constructor(id: string) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
selected_identity() : identities.Identity {
|
||||
const current_type = this.selected_type();
|
||||
if(current_type === undefined)
|
||||
return undefined;
|
||||
|
||||
if(current_type == identities.IdentitifyType.TEAFORO) {
|
||||
return identities.static_forum_identity();
|
||||
} else if(current_type == identities.IdentitifyType.TEAMSPEAK || current_type == identities.IdentitifyType.NICKNAME) {
|
||||
return this.identities[this.selected_identity_type.toLowerCase()];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
selected_type?() : identities.IdentitifyType {
|
||||
return identities.IdentitifyType[this.selected_identity_type.toUpperCase()];
|
||||
}
|
||||
|
||||
set_identity(type: identities.IdentitifyType, identity: identities.Identity) {
|
||||
this.identities[identities.IdentitifyType[type].toLowerCase()] = identity;
|
||||
}
|
||||
|
||||
spawn_identity_handshake_handler?(connection: ServerConnection) : HandshakeIdentityHandler {
|
||||
const identity = this.selected_identity();
|
||||
if(!identity)
|
||||
return undefined;
|
||||
return identity.spawn_identity_handshake_handler(connection);
|
||||
}
|
||||
|
||||
encode?() : string {
|
||||
const identity_data = {};
|
||||
for(const key in this.identities)
|
||||
if(this.identities[key])
|
||||
identity_data[key] = this.identities[key].encode();
|
||||
|
||||
return JSON.stringify({
|
||||
version: 1,
|
||||
username: this.default_username,
|
||||
password: this.default_password,
|
||||
profile_name: this.profile_name,
|
||||
identity_type: this.selected_identity_type,
|
||||
identity_data: identity_data,
|
||||
id: this.id
|
||||
});
|
||||
}
|
||||
|
||||
valid() : boolean {
|
||||
return this.selected_identity() !== undefined && this.default_username !== undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function decode_profile(data) : ConnectionProfile | string {
|
||||
data = JSON.parse(data);
|
||||
if(data.version !== 1)
|
||||
return "invalid version";
|
||||
|
||||
const result: ConnectionProfile = new ConnectionProfile(data.id);
|
||||
result.default_username = data.username;
|
||||
result.default_password = data.password;
|
||||
result.profile_name = data.profile_name;
|
||||
result.selected_identity_type = (data.identity_type || "").toLowerCase();
|
||||
|
||||
if(data.identity_data) {
|
||||
for(const key in data.identity_data) {
|
||||
const type = identities.IdentitifyType[key.toUpperCase() as string];
|
||||
const _data = data.identity_data[key];
|
||||
if(type == undefined) continue;
|
||||
|
||||
const identity = identities.decode_identity(type, _data);
|
||||
if(identity == undefined) continue;
|
||||
|
||||
result.identities[key.toLowerCase()] = identity;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
interface ProfilesData {
|
||||
version: number;
|
||||
profiles: string[];
|
||||
}
|
||||
|
||||
let available_profiles: ConnectionProfile[] = [];
|
||||
export function load() {
|
||||
available_profiles = [];
|
||||
|
||||
const profiles_json = localStorage.getItem("profiles");
|
||||
let profiles_data: ProfilesData = profiles_json ? JSON.parse(profiles_json) : {version: 0} as any;
|
||||
|
||||
if(profiles_data.version === 0) {
|
||||
profiles_data = {
|
||||
version: 1,
|
||||
profiles: []
|
||||
};
|
||||
}
|
||||
if(profiles_data.version == 1) {
|
||||
for(const profile_data of profiles_data.profiles) {
|
||||
const profile = decode_profile(profile_data);
|
||||
if(typeof(profile) === 'string') {
|
||||
console.error(tr("Failed to load profile. Reason: %s, Profile data: %s"), profile, profiles_data);
|
||||
continue;
|
||||
}
|
||||
available_profiles.push(profile);
|
||||
}
|
||||
}
|
||||
|
||||
if(!find_profile("default")) { //Create a default profile
|
||||
const profile = create_new_profile("default","default");
|
||||
profile.default_password = "";
|
||||
profile.default_username = "Another TeaSpeak user";
|
||||
profile.profile_name = "Default Profile";
|
||||
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
export function create_new_profile(name: string, id?: string) : ConnectionProfile {
|
||||
const profile = new ConnectionProfile(id || guid());
|
||||
profile.profile_name = name;
|
||||
profile.default_username = "Another TeaSpeak user";
|
||||
available_profiles.push(profile);
|
||||
return profile;
|
||||
}
|
||||
|
||||
let _requires_save = false;
|
||||
export function save() {
|
||||
const profiles: string[] = [];
|
||||
for(const profile of available_profiles)
|
||||
profiles.push(profile.encode());
|
||||
|
||||
const data = JSON.stringify({
|
||||
version: 1,
|
||||
profiles: profiles
|
||||
});
|
||||
localStorage.setItem("profiles", data);
|
||||
}
|
||||
|
||||
export function mark_need_save() {
|
||||
_requires_save = true;
|
||||
}
|
||||
|
||||
export function requires_save() : boolean {
|
||||
return _requires_save;
|
||||
}
|
||||
|
||||
export function profiles() : ConnectionProfile[] {
|
||||
return available_profiles;
|
||||
}
|
||||
|
||||
export function find_profile(id: string) : ConnectionProfile | undefined {
|
||||
for(const profile of profiles())
|
||||
if(profile.id == id)
|
||||
return profile;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function find_profile_by_name(name: string) : ConnectionProfile | undefined {
|
||||
name = name.toLowerCase();
|
||||
for(const profile of profiles())
|
||||
if((profile.profile_name || "").toLowerCase() == name)
|
||||
return profile;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
export function default_profile() : ConnectionProfile {
|
||||
return find_profile("default");
|
||||
}
|
||||
|
||||
export function set_default_profile(profile: ConnectionProfile) {
|
||||
const old_default = default_profile();
|
||||
if(old_default && old_default != profile) {
|
||||
old_default.id = guid();
|
||||
}
|
||||
profile.id = "default";
|
||||
}
|
||||
|
||||
export function delete_profile(profile: ConnectionProfile) {
|
||||
available_profiles.remove(profile);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
namespace profiles.identities {
|
||||
export enum IdentitifyType {
|
||||
TEAFORO,
|
||||
TEAMSPEAK,
|
||||
NICKNAME
|
||||
}
|
||||
|
||||
export interface Identity {
|
||||
name() : string;
|
||||
uid() : string;
|
||||
type() : IdentitifyType;
|
||||
|
||||
valid() : boolean;
|
||||
|
||||
encode?() : string;
|
||||
decode(data: string) : boolean;
|
||||
|
||||
spawn_identity_handshake_handler(connection: ServerConnection) : HandshakeIdentityHandler;
|
||||
}
|
||||
|
||||
export function decode_identity(type: IdentitifyType, data: string) : Identity {
|
||||
let identity: Identity;
|
||||
switch (type) {
|
||||
case IdentitifyType.NICKNAME:
|
||||
identity = new NameIdentity();
|
||||
break;
|
||||
case IdentitifyType.TEAFORO:
|
||||
identity = new TeaForumIdentity(undefined, undefined);
|
||||
break;
|
||||
case IdentitifyType.TEAMSPEAK:
|
||||
identity = new TeamSpeakIdentity(undefined, undefined);
|
||||
break;
|
||||
}
|
||||
if(!identity)
|
||||
return undefined;
|
||||
|
||||
if(!identity.decode(data))
|
||||
return undefined;
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
||||
export function create_identity(type: IdentitifyType) {
|
||||
let identity: Identity;
|
||||
switch (type) {
|
||||
case IdentitifyType.NICKNAME:
|
||||
identity = new NameIdentity();
|
||||
break;
|
||||
case IdentitifyType.TEAFORO:
|
||||
identity = new TeaForumIdentity(undefined, undefined);
|
||||
break;
|
||||
case IdentitifyType.TEAMSPEAK:
|
||||
identity = new TeamSpeakIdentity(undefined, undefined);
|
||||
break;
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
export abstract class AbstractHandshakeIdentityHandler implements HandshakeIdentityHandler {
|
||||
connection: ServerConnection;
|
||||
|
||||
protected callbacks: ((success: boolean, message?: string) => any)[] = [];
|
||||
|
||||
protected constructor(connection: ServerConnection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
register_callback(callback: (success: boolean, message?: string) => any) {
|
||||
this.callbacks.push(callback);
|
||||
}
|
||||
|
||||
abstract start_handshake();
|
||||
|
||||
protected trigger_success() {
|
||||
for(const callback of this.callbacks)
|
||||
callback(true);
|
||||
}
|
||||
|
||||
protected trigger_fail(message: string) {
|
||||
for(const callback of this.callbacks)
|
||||
callback(false, message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/// <reference path="../Identity.ts" />
|
||||
|
||||
namespace profiles.identities {
|
||||
class NameHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
||||
readonly identity: NameIdentity;
|
||||
|
||||
constructor(connection: ServerConnection, identity: profiles.identities.NameIdentity) {
|
||||
super(connection);
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
start_handshake() {
|
||||
this.connection.commandHandler["handshakeidentityproof"] = () => this.trigger_fail("server requested unexpected proof");
|
||||
|
||||
this.connection.sendCommand("handshakebegin", {
|
||||
intention: 0,
|
||||
authentication_method: this.identity.type(),
|
||||
client_nickname: this.identity.name()
|
||||
}).catch(error => {
|
||||
console.error(tr("Failed to initialize name based handshake. Error: %o"), error);
|
||||
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute begin (" + error + ")");
|
||||
}).then(() => this.trigger_success());
|
||||
}
|
||||
}
|
||||
|
||||
export class NameIdentity implements Identity {
|
||||
private _name: string;
|
||||
|
||||
constructor(name?: string) {
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
set_name(name: string) { this._name = name; }
|
||||
|
||||
name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
uid(): string {
|
||||
return btoa(this._name); //FIXME hash!
|
||||
}
|
||||
|
||||
type(): IdentitifyType {
|
||||
return IdentitifyType.NICKNAME;
|
||||
}
|
||||
|
||||
valid(): boolean {
|
||||
return this._name != undefined && this._name != "";
|
||||
}
|
||||
|
||||
decode(data) {
|
||||
data = JSON.parse(data);
|
||||
if(data.version !== 1)
|
||||
return false;
|
||||
|
||||
this._name = data["name"];
|
||||
return true;
|
||||
}
|
||||
|
||||
encode?() : string {
|
||||
return JSON.stringify({
|
||||
version: 1,
|
||||
name: this._name
|
||||
});
|
||||
}
|
||||
|
||||
spawn_identity_handshake_handler(connection: ServerConnection) : HandshakeIdentityHandler {
|
||||
return new NameHandshakeHandler(connection, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/// <reference path="../Identity.ts" />
|
||||
|
||||
namespace profiles.identities {
|
||||
class TeaForumHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
||||
readonly identity: TeaForumIdentity;
|
||||
|
||||
constructor(connection: ServerConnection, identity: profiles.identities.TeaForumIdentity) {
|
||||
super(connection);
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
start_handshake() {
|
||||
this.connection.commandHandler["handshakeidentityproof"] = this.handle_proof.bind(this);
|
||||
|
||||
this.connection.sendCommand("handshakebegin", {
|
||||
intention: 0,
|
||||
authentication_method: this.identity.type(),
|
||||
data: this.identity.data_json()
|
||||
}).catch(error => {
|
||||
console.error(tr("Failed to initialize TeaForum based handshake. Error: %o"), error);
|
||||
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute begin (" + error + ")");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private handle_proof(json) {
|
||||
this.connection.sendCommand("handshakeindentityproof", {
|
||||
proof: this.identity.data_sign()
|
||||
}).catch(error => {
|
||||
console.error(tr("Failed to proof the identity. Error: %o"), error);
|
||||
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute proof (" + error + ")");
|
||||
}).then(() => this.trigger_success());
|
||||
}
|
||||
}
|
||||
|
||||
export class TeaForumIdentity implements Identity {
|
||||
private identityData: string;
|
||||
private identityDataJson: string;
|
||||
private identitySign: string;
|
||||
|
||||
valid() : boolean {
|
||||
return this.identityData.length > 0 && this.identityDataJson.length > 0 && this.identitySign.length > 0;
|
||||
}
|
||||
|
||||
constructor(data: string, sign: string) {
|
||||
this.identityDataJson = data;
|
||||
this.identityData = data ? JSON.parse(this.identityDataJson) : undefined;
|
||||
this.identitySign = sign;
|
||||
}
|
||||
|
||||
data_json() : string { return this.identityDataJson; }
|
||||
data_sign() : string { return this.identitySign; }
|
||||
|
||||
name() : string { return this.identityData["user_name"]; }
|
||||
uid() : string { return "TeaForo#" + this.identityData["user_id"]; }
|
||||
type() : IdentitifyType { return IdentitifyType.TEAFORO; }
|
||||
|
||||
decode(data) {
|
||||
data = JSON.parse(data);
|
||||
if(data.version !== 1)
|
||||
return false;
|
||||
|
||||
this.identityDataJson = data["identity_data"];
|
||||
this.identitySign = data["identity_sign"];
|
||||
this.identityData = JSON.parse(this.identityData);
|
||||
return true;
|
||||
}
|
||||
|
||||
encode?() : string {
|
||||
return JSON.stringify({
|
||||
version: 1,
|
||||
identity_data: this.identityDataJson,
|
||||
identity_sign: this.identitySign
|
||||
});
|
||||
}
|
||||
|
||||
spawn_identity_handshake_handler(connection: ServerConnection) : HandshakeIdentityHandler {
|
||||
return new TeaForumHandshakeHandler(connection, this);
|
||||
}
|
||||
}
|
||||
|
||||
let static_identity: TeaForumIdentity;
|
||||
|
||||
export function setup_forum() {
|
||||
const user_data = settings.static("forum_user_data") as string;
|
||||
const user_sign = settings.static("forum_user_sign") as string;
|
||||
|
||||
if(user_data && user_sign)
|
||||
static_identity = new TeaForumIdentity(user_data, user_sign);
|
||||
}
|
||||
|
||||
export function valid_static_forum_identity() : boolean {
|
||||
return static_identity && static_identity.valid();
|
||||
}
|
||||
|
||||
export function static_forum_identity() : TeaForumIdentity | undefined {
|
||||
return static_identity;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
/// <reference path="../Identity.ts" />
|
||||
|
||||
namespace profiles.identities {
|
||||
export namespace TSIdentityHelper {
|
||||
export let funcationParseIdentity: any;
|
||||
export let funcationParseIdentityByFile: any;
|
||||
export let funcationCalculateSecurityLevel: any;
|
||||
export let functionUid: any;
|
||||
export let funcationExportIdentity: any;
|
||||
export let funcationPublicKey: any;
|
||||
export let funcationSignMessage: any;
|
||||
|
||||
let functionLastError: any;
|
||||
let functionClearLastError: any;
|
||||
|
||||
let functionDestroyString: any;
|
||||
let functionDestroyIdentity: any;
|
||||
|
||||
export function setup() : boolean {
|
||||
functionDestroyString = Module.cwrap("destroy_string", "pointer", []);
|
||||
functionLastError = Module.cwrap("last_error_message", null, ["string"]);
|
||||
funcationParseIdentity = Module.cwrap("parse_identity", "pointer", ["string"]);
|
||||
funcationParseIdentityByFile = Module.cwrap("parse_identity_file", "pointer", ["string"]);
|
||||
functionDestroyIdentity = Module.cwrap("delete_identity", null, ["pointer"]);
|
||||
|
||||
funcationCalculateSecurityLevel = Module.cwrap("identity_security_level", "pointer", ["pointer"]);
|
||||
funcationExportIdentity = Module.cwrap("identity_export", "pointer", ["pointer"]);
|
||||
funcationPublicKey = Module.cwrap("identity_key_public", "pointer", ["pointer"]);
|
||||
funcationSignMessage = Module.cwrap("identity_sign", "pointer", ["pointer", "string", "number"]);
|
||||
functionUid = Module.cwrap("identity_uid", "pointer", ["pointer"]);
|
||||
|
||||
return Module.cwrap("tomcrypt_initialize", "number", [])() == 0;
|
||||
}
|
||||
|
||||
export function last_error() : string {
|
||||
return unwarpString(functionLastError());
|
||||
}
|
||||
|
||||
export function unwarpString(str) : string {
|
||||
if(str == "") return "";
|
||||
try {
|
||||
if(!$.isFunction(window.Pointer_stringify)) {
|
||||
displayCriticalError(tr("Missing required wasm function!<br>Please reload the page!"));
|
||||
}
|
||||
let message: string = window.Pointer_stringify(str);
|
||||
functionDestroyString(str);
|
||||
return message;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export function loadIdentity(key: string) : TeamSpeakIdentity {
|
||||
let handle = funcationParseIdentity(key);
|
||||
if(!handle) return undefined;
|
||||
return new TeamSpeakIdentity(handle, "TeaWeb user");
|
||||
}
|
||||
|
||||
export function loadIdentityFromFileContains(contains: string) : TeamSpeakIdentity {
|
||||
let handle = funcationParseIdentityByFile(contains);
|
||||
if(!handle) return undefined;
|
||||
return new TeamSpeakIdentity(handle, "TeaWeb user");
|
||||
}
|
||||
|
||||
export function load_identity(handle: TeamSpeakIdentity, key) : boolean {
|
||||
let native_handle = funcationParseIdentity(key);
|
||||
if(!native_handle) return false;
|
||||
|
||||
handle["handle"] = native_handle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class TeamSpeakHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
||||
identity: TeamSpeakIdentity;
|
||||
|
||||
constructor(connection: ServerConnection, identity: TeamSpeakIdentity) {
|
||||
super(connection);
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
start_handshake() {
|
||||
this.connection.commandHandler["handshakeidentityproof"] = this.handle_proof.bind(this);
|
||||
|
||||
this.connection.sendCommand("handshakebegin", {
|
||||
intention: 0,
|
||||
authentication_method: this.identity.type(),
|
||||
publicKey: this.identity.publicKey()
|
||||
}).catch(error => {
|
||||
console.error(tr("Failed to initialize TeamSpeak based handshake. Error: %o"), error);
|
||||
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute begin (" + error + ")");
|
||||
});
|
||||
}
|
||||
|
||||
private handle_proof(json) {
|
||||
const proof = this.identity.signMessage(json[0]["message"]);
|
||||
|
||||
this.connection.sendCommand("handshakeindentityproof", {proof: proof}).catch(error => {
|
||||
console.error(tr("Failed to proof the identity. Error: %o"), error);
|
||||
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute proof (" + error + ")");
|
||||
}).then(() => this.trigger_success());
|
||||
}
|
||||
}
|
||||
|
||||
export class TeamSpeakIdentity implements Identity {
|
||||
private handle: any;
|
||||
private _name: string;
|
||||
|
||||
constructor(handle: any, name: string) {
|
||||
this.handle = handle;
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
securityLevel() : number | undefined {
|
||||
return parseInt(TSIdentityHelper.unwarpString(TSIdentityHelper.funcationCalculateSecurityLevel(this.handle)));
|
||||
}
|
||||
|
||||
name() : string { return this._name; }
|
||||
|
||||
uid() : string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.functionUid(this.handle));
|
||||
}
|
||||
|
||||
type() : IdentitifyType { return IdentitifyType.TEAMSPEAK; }
|
||||
|
||||
signMessage(message: string): string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.funcationSignMessage(this.handle, message, message.length));
|
||||
}
|
||||
|
||||
exported() : string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.funcationExportIdentity(this.handle));
|
||||
}
|
||||
|
||||
publicKey() : string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.funcationPublicKey(this.handle));
|
||||
}
|
||||
|
||||
valid() : boolean { return this.handle !== undefined; }
|
||||
|
||||
decode(data) : boolean {
|
||||
data = JSON.parse(data);
|
||||
if(data.version != 1) return false;
|
||||
|
||||
if(!TSIdentityHelper.load_identity(this, data["key"]))
|
||||
return false;
|
||||
this._name = data["name"];
|
||||
return true;
|
||||
}
|
||||
|
||||
encode?() : string {
|
||||
if(!this.handle) return undefined;
|
||||
|
||||
const key = this.exported();
|
||||
if(!key) return undefined;
|
||||
|
||||
return JSON.stringify({
|
||||
key: key,
|
||||
name: this._name,
|
||||
version: 1
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
spawn_identity_handshake_handler(connection: ServerConnection) : HandshakeIdentityHandler {
|
||||
return new TeamSpeakHandshakeHandler(connection, this);
|
||||
}
|
||||
}
|
||||
|
||||
export function setup_teamspeak() : boolean {
|
||||
return TSIdentityHelper.setup();
|
||||
}
|
||||
}
|
|
@ -69,13 +69,16 @@ class ControlBar {
|
|||
{
|
||||
let bookmark = this.htmlTag.find(".btn_bookmark");
|
||||
bookmark.find(".button-dropdown").on('click', () => {
|
||||
bookmark.find(".dropdown").addClass("displayed");
|
||||
bookmark.find("> .dropdown").addClass("displayed");
|
||||
});
|
||||
bookmark.on('mouseleave', () => {
|
||||
bookmark.find(".dropdown").removeClass("displayed");
|
||||
bookmark.find("> .dropdown").removeClass("displayed");
|
||||
});
|
||||
bookmark.find(".btn_bookmark_list").on('click', this.on_bookmark_manage.bind(this));
|
||||
bookmark.find(".btn_bookmark_add").on('click', this.on_bookmark_server_add.bind(this));
|
||||
|
||||
this.update_bookmarks()
|
||||
this.update_bookmarks();
|
||||
this.update_bookmark_status();
|
||||
}
|
||||
{
|
||||
let query = this.htmlTag.find(".btn_query");
|
||||
|
@ -304,28 +307,99 @@ class ControlBar {
|
|||
}
|
||||
}
|
||||
|
||||
private on_bookmark_server_add() {
|
||||
if(globalClient && globalClient.connected) {
|
||||
createInputModal(tr("Enter bookmarks name"), tr("Please enter the bookmarks name:<br>"), text => true, result => {
|
||||
if(result) {
|
||||
const bookmark = bookmarks.create_bookmark(result as string, bookmarks.bookmarks(), {
|
||||
server_port: globalClient.serverConnection._remote_address.port,
|
||||
server_address: globalClient.serverConnection._remote_address.host,
|
||||
|
||||
server_password: "",
|
||||
server_password_hash: ""
|
||||
}, globalClient.getClient().clientNickName());
|
||||
bookmarks.save_bookmark(bookmark);
|
||||
this.update_bookmarks()
|
||||
}
|
||||
}).open();
|
||||
} else {
|
||||
createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open();
|
||||
}
|
||||
}
|
||||
|
||||
update_bookmark_status() {
|
||||
this.htmlTag.find(".btn_bookmark_add").removeClass("hidden").addClass("disabled");
|
||||
this.htmlTag.find(".btn_bookmark_remove").addClass("hidden");
|
||||
}
|
||||
|
||||
|
||||
update_bookmarks() {
|
||||
//<div class="btn_bookmark_connect" target="localhost"><a>Localhost</a></div>
|
||||
let tag_bookmark = this.htmlTag.find(".btn_bookmark .dropdown");
|
||||
tag_bookmark.find(".bookmark, .bookmark_directory").detach();
|
||||
tag_bookmark.find(".bookmark, .directory").detach();
|
||||
|
||||
const build_entry = (bookmark: bookmarks.DirectoryBookmark | bookmarks.Bookmark) => {
|
||||
if(bookmark.type == bookmarks.BookmarkType.ENTRY) {
|
||||
const mark = <bookmarks.Bookmark>bookmark;
|
||||
return $.spawn("div")
|
||||
.addClass("bookmark")
|
||||
.append(
|
||||
$.spawn("div").addClass("icon client-server")
|
||||
)
|
||||
.append(
|
||||
$.spawn("div")
|
||||
.addClass("name")
|
||||
.text(bookmark.display_name)
|
||||
.on('click', event => {
|
||||
this.htmlTag.find(".btn_bookmark").find(".dropdown").removeClass("displayed");
|
||||
this.handle.startConnection(
|
||||
mark.server_properties.server_address + ":" + mark.server_properties.server_port,
|
||||
profiles.find_profile(mark.connect_profile) || profiles.default_profile(),
|
||||
mark.nickname,
|
||||
{
|
||||
password: mark.server_properties.server_password_hash,
|
||||
hashed: true
|
||||
}
|
||||
);
|
||||
})
|
||||
)
|
||||
} else {
|
||||
const mark = <bookmarks.DirectoryBookmark>bookmark;
|
||||
const container = $.spawn("div").addClass("sub-menu dropdown");
|
||||
|
||||
for(const member of mark.content)
|
||||
container.append(build_entry(member));
|
||||
|
||||
return $.spawn("div")
|
||||
.addClass("directory")
|
||||
.append(
|
||||
$.spawn("div").addClass("icon client-folder")
|
||||
)
|
||||
.append(
|
||||
$.spawn("div")
|
||||
.addClass("name")
|
||||
.text(bookmark.display_name)
|
||||
)
|
||||
.append(
|
||||
$.spawn("div").addClass("arrow right")
|
||||
)
|
||||
.append(
|
||||
$.spawn("div").addClass("sub-container")
|
||||
.append(container)
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
for(const bookmark of bookmarks.bookmarks().content) {
|
||||
if(bookmark.type == bookmarks.BookmarkType.ENTRY) {
|
||||
tag_bookmark.append(
|
||||
$.spawn("div")
|
||||
.addClass("bookmark")
|
||||
/* /.attr("bookmark-uuid", bookmark.unique_id) */
|
||||
.text(bookmark.display_name)
|
||||
.on('click', event => {
|
||||
spawnConnectModal()
|
||||
|
||||
})
|
||||
)
|
||||
}
|
||||
//TODO add bookmark directories here
|
||||
const entry = build_entry(bookmark);
|
||||
tag_bookmark.append(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private on_bookmark_manage() {
|
||||
Modals.spawnBookmarkModal();
|
||||
}
|
||||
|
||||
get query_visibility() {
|
||||
return this._query_visible;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
function bookmark_tag(callback_select: (entry, tag) => any, bookmark: bookmarks.Bookmark | bookmarks.DirectoryBookmark) {
|
||||
const tag = $("#tmpl_manage_bookmarks-list_entry").renderTag({
|
||||
name: bookmark.display_name,
|
||||
type: bookmark.type == bookmarks.BookmarkType.DIRECTORY ? "directory" : "bookmark"
|
||||
});
|
||||
tag.find(".name").on('click', () => {
|
||||
callback_select(bookmark, tag);
|
||||
tag.addClass("selected");
|
||||
});
|
||||
|
||||
if(bookmark.type == bookmarks.BookmarkType.DIRECTORY) {
|
||||
const casted = <bookmarks.DirectoryBookmark>bookmark;
|
||||
for(const member of casted.content)
|
||||
tag.find("> .members").append(bookmark_tag(callback_select, member));
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
function parent_tag(select_tag: JQuery, prefix: string, bookmark: bookmarks.Bookmark | bookmarks.DirectoryBookmark) {
|
||||
if(bookmark.type == bookmarks.BookmarkType.DIRECTORY) {
|
||||
const casted = <bookmarks.DirectoryBookmark>bookmark;
|
||||
|
||||
select_tag.append(
|
||||
$.spawn("option")
|
||||
.val(casted.unique_id)
|
||||
.text(prefix + casted.display_name)
|
||||
);
|
||||
|
||||
for(const member of casted.content)
|
||||
parent_tag(select_tag, prefix + " ", member);
|
||||
}
|
||||
}
|
||||
|
||||
export function spawnBookmarkModal() {
|
||||
let modal: Modal;
|
||||
modal = createModal({
|
||||
header: tr("Manage bookmarks"),
|
||||
body: () => {
|
||||
let template = $("#tmpl_manage_bookmarks").renderTag({ });
|
||||
template = $.spawn("div").append(template);
|
||||
let selected_bookmark: bookmarks.Bookmark | bookmarks.DirectoryBookmark | undefined;
|
||||
let update_name: () => any;
|
||||
|
||||
const update_bookmarks = () => { //list bookmarks
|
||||
template.find(".list").empty();
|
||||
|
||||
const callback_selected = (entry: bookmarks.Bookmark | bookmarks.DirectoryBookmark, tag: JQuery) => {
|
||||
template.find(".selected").removeClass("selected");
|
||||
if(selected_bookmark == entry) return;
|
||||
|
||||
selected_bookmark = entry;
|
||||
update_name = () => tag.find("> .name").text(entry.display_name);
|
||||
|
||||
template.find(".bookmark-setting").hide();
|
||||
template.find(".setting-bookmark-name").val(selected_bookmark.display_name);
|
||||
|
||||
if(selected_bookmark.type == bookmarks.BookmarkType.ENTRY) {
|
||||
template.find(".bookmark-setting-bookmark").show();
|
||||
|
||||
const casted = <bookmarks.Bookmark>selected_bookmark;
|
||||
const profile = profiles.find_profile(casted.connect_profile) || profiles.default_profile();
|
||||
template.find(".setting-bookmark-profile").val(profile.id);
|
||||
|
||||
template.find(".setting-server-host").val(casted.server_properties.server_address);
|
||||
template.find(".setting-server-port").val(casted.server_properties.server_port);
|
||||
template.find(".setting-server-password").val(casted.server_properties.server_password_hash || casted.server_properties.server_password);
|
||||
|
||||
template.find(".setting-username").val(casted.nickname);
|
||||
template.find(".setting-channel").val(casted.default_channel);
|
||||
template.find(".setting-channel-password").val(casted.default_channel_password_hash || casted.default_channel_password);
|
||||
} else {
|
||||
template.find(".bookmark-setting-directory").show();
|
||||
}
|
||||
};
|
||||
|
||||
for(const bookmark of bookmarks.bookmarks().content) {
|
||||
template.find(".list").append(bookmark_tag(callback_selected, bookmark));
|
||||
}
|
||||
console.log( template.find(".list").find(".bookmark, .directory"));
|
||||
template.find(".list").find(".bookmark, .directory").eq(0).find("> .name").trigger('click');
|
||||
};
|
||||
|
||||
{ //General buttons
|
||||
template.find(".button-create").on('click', event => {
|
||||
let create_modal: Modal;
|
||||
create_modal = createModal({
|
||||
header: tr("Create a new entry"),
|
||||
body: () => {
|
||||
let template = $("#tmpl_manage_bookmarks-create").renderTag({ });
|
||||
template = $.spawn("div").append(template);
|
||||
|
||||
for(const bookmark of bookmarks.bookmarks().content)
|
||||
parent_tag(template.find(".bookmark-parent"), "", bookmark);
|
||||
|
||||
if(selected_bookmark) {
|
||||
const parent = selected_bookmark.type == bookmarks.BookmarkType.ENTRY ?
|
||||
bookmarks.parent_bookmark(selected_bookmark as bookmarks.Bookmark) :
|
||||
selected_bookmark;
|
||||
if(parent)
|
||||
template.find(".bookmark-parent").val(parent.unique_id);
|
||||
}
|
||||
|
||||
template.find(".bookmark-name").on('change, keyup', event => {
|
||||
template.find(".button-create").prop("disabled", (<HTMLInputElement>event.target).value.length < 3);
|
||||
});
|
||||
|
||||
template.find(".button-create").prop("disabled", true).on('click', event => {
|
||||
const name = template.find(".bookmark-name").val() as string;
|
||||
const parent_uuid = template.find(".bookmark-parent").val() as string;
|
||||
|
||||
const parent = bookmarks.find_bookmark(parent_uuid);
|
||||
|
||||
let bookmark;
|
||||
if(template.find(".bookmark-type").val() == "directory") {
|
||||
bookmark = bookmarks.create_bookmark_directory(parent as bookmarks.DirectoryBookmark || bookmarks.bookmarks(), name);
|
||||
} else {
|
||||
bookmark = bookmarks.create_bookmark(name, parent as bookmarks.DirectoryBookmark || bookmarks.bookmarks(), {
|
||||
server_port: 9987,
|
||||
server_address: "ts.teaspeak.de"
|
||||
}, "Another TeaSpeak user");
|
||||
}
|
||||
bookmarks.save_bookmark(bookmark);
|
||||
create_modal.close();
|
||||
update_bookmarks();
|
||||
});
|
||||
|
||||
return template;
|
||||
},
|
||||
footer: 400
|
||||
});
|
||||
|
||||
create_modal.open();
|
||||
});
|
||||
|
||||
template.find(".button-delete").on('click', event => {
|
||||
if(!selected_bookmark) return;
|
||||
|
||||
spawnYesNo(tr("Are you sure?"), tr("Do you really want to delete this entry?"), result => {
|
||||
if(result) {
|
||||
bookmarks.delete_bookmark(selected_bookmark);
|
||||
bookmarks.save_bookmark(selected_bookmark); /* save the deleted state */
|
||||
update_bookmarks();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* bookmark listener */
|
||||
{
|
||||
template.find(".setting-bookmark-profile").on('change', event => {
|
||||
if(!selected_bookmark || selected_bookmark.type != bookmarks.BookmarkType.ENTRY) return;
|
||||
const casted = <bookmarks.Bookmark>selected_bookmark;
|
||||
const element = <HTMLInputElement>event.target;
|
||||
|
||||
casted.connect_profile = element.value;
|
||||
bookmarks.save_bookmark(selected_bookmark);
|
||||
});
|
||||
|
||||
template.find(".setting-server-host").on('change', event => {
|
||||
if(!selected_bookmark || selected_bookmark.type != bookmarks.BookmarkType.ENTRY) return;
|
||||
const casted = <bookmarks.Bookmark>selected_bookmark;
|
||||
const element = <HTMLInputElement>event.target;
|
||||
|
||||
casted.server_properties.server_address = element.value;
|
||||
bookmarks.save_bookmark(selected_bookmark);
|
||||
});
|
||||
|
||||
template.find(".setting-server-port").on('change', event => {
|
||||
if(!selected_bookmark || selected_bookmark.type != bookmarks.BookmarkType.ENTRY) return;
|
||||
const casted = <bookmarks.Bookmark>selected_bookmark;
|
||||
const element = <HTMLInputElement>event.target;
|
||||
|
||||
casted.server_properties.server_port = parseInt(element.value);
|
||||
bookmarks.save_bookmark(selected_bookmark);
|
||||
});
|
||||
|
||||
template.find(".setting-server-password").on('change', event => {
|
||||
if(!selected_bookmark || selected_bookmark.type != bookmarks.BookmarkType.ENTRY) return;
|
||||
const casted = <bookmarks.Bookmark>selected_bookmark;
|
||||
const element = <HTMLInputElement>event.target;
|
||||
|
||||
casted.server_properties.server_password = element.value;
|
||||
bookmarks.save_bookmark(selected_bookmark);
|
||||
});
|
||||
|
||||
|
||||
template.find(".setting-username").on('change', event => {
|
||||
if(!selected_bookmark || selected_bookmark.type != bookmarks.BookmarkType.ENTRY) return;
|
||||
const casted = <bookmarks.Bookmark>selected_bookmark;
|
||||
const element = <HTMLInputElement>event.target;
|
||||
|
||||
casted.nickname = element.value;
|
||||
bookmarks.save_bookmark(selected_bookmark);
|
||||
});
|
||||
|
||||
template.find(".setting-channel").on('change', event => {
|
||||
if(!selected_bookmark || selected_bookmark.type != bookmarks.BookmarkType.ENTRY) return;
|
||||
const casted = <bookmarks.Bookmark>selected_bookmark;
|
||||
const element = <HTMLInputElement>event.target;
|
||||
|
||||
casted.default_channel = element.value;
|
||||
bookmarks.save_bookmark(selected_bookmark);
|
||||
});
|
||||
|
||||
template.find(".setting-channel-password").on('change', event => {
|
||||
if(!selected_bookmark || selected_bookmark.type != bookmarks.BookmarkType.ENTRY) return;
|
||||
const casted = <bookmarks.Bookmark>selected_bookmark;
|
||||
const element = <HTMLInputElement>event.target;
|
||||
|
||||
casted.default_channel_password = element.value;
|
||||
bookmarks.save_bookmark(selected_bookmark);
|
||||
});
|
||||
}
|
||||
|
||||
/* listener for both */
|
||||
{
|
||||
template.find(".setting-bookmark-name").on('change', event => {
|
||||
if(!selected_bookmark) return;
|
||||
const element = <HTMLInputElement>event.target;
|
||||
|
||||
if(element.value.length >= 3) {
|
||||
selected_bookmark.display_name = element.value;
|
||||
bookmarks.save_bookmark(selected_bookmark);
|
||||
if(update_name)
|
||||
update_name();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* connect profile initialisation */
|
||||
{
|
||||
const list = template.find(".setting-bookmark-profile");
|
||||
for(const profile of profiles.profiles()) {
|
||||
const tag = $.spawn("option").val(profile.id).text(profile.profile_name);
|
||||
if(profile.id == "default")
|
||||
tag.css("font-weight", "bold");
|
||||
|
||||
list.append(tag);
|
||||
}
|
||||
}
|
||||
|
||||
update_bookmarks();
|
||||
template.find(".button-close").on('click', event => modal.close());
|
||||
return template;
|
||||
},
|
||||
footer: undefined,
|
||||
width: 750
|
||||
});
|
||||
|
||||
modal.close_listener.push(() => globalClient.controlBar.update_bookmarks());
|
||||
modal.open();
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function spawnConnectModal(defaultHost: { url: string, enforce: boolean} = { url: "ts.TeaSpeak.de", enforce: false}, def_connect_type?: { identity: IdentitifyType, enforce: boolean}) {
|
||||
let connectIdentity: Identity;
|
||||
export function spawnConnectModal(defaultHost: { url: string, enforce: boolean} = { url: "ts.TeaSpeak.de", enforce: false}, connect_profile?: { profile: profiles.ConnectionProfile, enforce: boolean}) {
|
||||
let selected_profile: profiles.ConnectionProfile;
|
||||
const connectModal = createModal({
|
||||
header: function() {
|
||||
let header = $.spawn("div");
|
||||
|
@ -12,13 +12,13 @@ namespace Modals {
|
|||
body: function () {
|
||||
let tag = $("#tmpl_connect").renderTag({
|
||||
client: native_client,
|
||||
forum_path: settings.static("forum_path"),
|
||||
forum_valid: !!forumIdentity
|
||||
forum_path: settings.static("forum_path")
|
||||
});
|
||||
|
||||
let updateFields = function () {
|
||||
if(connectIdentity) tag.find(".connect_nickname").attr("placeholder", connectIdentity.name());
|
||||
else tag.find(".connect_nickname").attr("");
|
||||
if(selected_profile) tag.find(".connect_nickname").attr("placeholder", selected_profile.default_username);
|
||||
else
|
||||
tag.find(".connect_nickname").attr("");
|
||||
|
||||
let button = tag.parents(".modal-content").find(".connect_connect_button");
|
||||
|
||||
|
@ -30,7 +30,7 @@ namespace Modals {
|
|||
let field_nickname = tag.find(".connect_nickname");
|
||||
let nickname = field_nickname.val().toString();
|
||||
settings.changeGlobal("connect_name", nickname);
|
||||
let flag_nickname = (nickname.length == 0 && connectIdentity && connectIdentity.name().length > 0) || nickname.length >= 3 && nickname.length <= 32;
|
||||
let flag_nickname = (nickname.length == 0 && selected_profile && selected_profile.default_username.length > 0) || nickname.length >= 3 && nickname.length <= 32;
|
||||
|
||||
if(flag_address) {
|
||||
if(field_address.hasClass("invalid_input"))
|
||||
|
@ -48,7 +48,7 @@ namespace Modals {
|
|||
field_nickname.addClass("invalid_input");
|
||||
}
|
||||
|
||||
if(!flag_nickname || !flag_address || !connectIdentity) {
|
||||
if(!flag_nickname || !flag_address || !selected_profile || !selected_profile.valid()) {
|
||||
button.prop("disabled", true);
|
||||
} else {
|
||||
button.prop("disabled", false);
|
||||
|
@ -63,109 +63,38 @@ namespace Modals {
|
|||
if(event.keyCode == JQuery.Key.Enter && !event.shiftKey)
|
||||
tag.parents(".modal-content").find(".connect_connect_button").trigger('click');
|
||||
});
|
||||
tag.find(".connect_nickname").on("keyup", () => updateFields());
|
||||
|
||||
tag.find(".identity_select").on('change', function (this: HTMLSelectElement) {
|
||||
settings.changeGlobal("connect_identity_type", IdentitifyType[this.value]);
|
||||
tag.find(".error_message").hide();
|
||||
tag.find(".identity_config:not(" + ".identity_config_" + this.value + ")").hide();
|
||||
tag.find(".identity_config_" + this.value).show().trigger('shown');
|
||||
tag.find(".button-manage-profiles").on('click', event => {
|
||||
const modal = Modals.spawnSettingsModal();
|
||||
setTimeout(() => {
|
||||
modal.htmlTag.find(".tab-profiles").parent(".entry").trigger('click');
|
||||
}, 100);
|
||||
return true;
|
||||
});
|
||||
tag.find(".identity_select").val(IdentitifyType[def_connect_type && def_connect_type.enforce ? def_connect_type.identity : settings.global("connect_identity_type", (def_connect_type || { identity: undefined }).identity || IdentitifyType.TEAFORO)]);
|
||||
setTimeout(() => tag.find(".identity_select").trigger('change'), 0); //For some reason could not be run instantly
|
||||
|
||||
{
|
||||
tag.find(".identity_file").change(function (this: HTMLInputElement) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
connectIdentity = TSIdentityHelper.loadIdentityFromFileContains(reader.result as string);
|
||||
const select_tag = tag.find(".profile-select-container select");
|
||||
const select_invalid_tag = tag.find(".profile-invalid");
|
||||
|
||||
console.log(connectIdentity.uid());
|
||||
if(!connectIdentity) tag.find(".error_message").text(tr("Could not read identity! ") + TSIdentityHelper.last_error());
|
||||
else {
|
||||
tag.find(".identity_string").val((connectIdentity as TeamSpeakIdentity).exported());
|
||||
settings.changeGlobal("connect_identity_teamspeak_identity", (connectIdentity as TeamSpeakIdentity).exported());
|
||||
}
|
||||
|
||||
(!!connectIdentity ? tag.hide : tag.show).apply(tag.find(".error_message"));
|
||||
updateFields();
|
||||
};
|
||||
reader.onerror = ev => {
|
||||
tag.find(".error_message").text(tr("Could not read identity file!")).show();
|
||||
updateFields();
|
||||
};
|
||||
reader.readAsText(this.files[0]);
|
||||
});
|
||||
|
||||
tag.find(".identity_string").on('change', function (this: HTMLInputElement) {
|
||||
if(this.value.length == 0){
|
||||
tag.find(".error_message").text(tr("Please select an identity!"));
|
||||
connectIdentity = undefined;
|
||||
} else {
|
||||
connectIdentity = TSIdentityHelper.loadIdentity(this.value);
|
||||
if(!connectIdentity) tag.find(".error_message").text("Could not parse identity! " + TSIdentityHelper.last_error());
|
||||
else settings.changeGlobal("connect_identity_teamspeak_identity", this.value);
|
||||
}
|
||||
(!!connectIdentity ? tag.hide : tag.show).apply(tag.find(".error_message"));
|
||||
tag.find(".identity_file").val("");
|
||||
updateFields();
|
||||
});
|
||||
tag.find(".identity_string").val(settings.global("connect_identity_teamspeak_identity", ""));
|
||||
tag.find(".identity_config_" + IdentitifyType[IdentitifyType.TEAMSPEAK]).on('shown', ev => {
|
||||
tag.find(".identity_string").trigger('change');
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const element = tag.find(".identity_config_" + IdentitifyType[IdentitifyType.TEAFORO]);
|
||||
element.on('shown', ev => {
|
||||
console.log(tr("Updating via shown"));
|
||||
connectIdentity = forumIdentity;
|
||||
|
||||
if(connectIdentity) {
|
||||
element.find(".connected").show();
|
||||
element.find(".disconnected").hide();
|
||||
} else {
|
||||
element.find(".connected").hide();
|
||||
element.find(".disconnected").show();
|
||||
}
|
||||
updateFields();
|
||||
});
|
||||
|
||||
if(native_client) {
|
||||
tag.find(".native-teaforo-login").on('click', event => {
|
||||
setTimeout(() => {
|
||||
const forum = require("teaforo.js");
|
||||
const call = () => {
|
||||
try {
|
||||
console.log("Trigger update!");
|
||||
element.trigger('shown');
|
||||
} catch ($) { console.log($); }
|
||||
if(connectModal.shown)
|
||||
forum.register_callback(call);
|
||||
};
|
||||
forum.register_callback(call);
|
||||
forum.open();
|
||||
}, 0);
|
||||
});
|
||||
for(const profile of profiles.profiles()) {
|
||||
select_tag.append(
|
||||
$.spawn("option").text(profile.profile_name).val(profile.id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
tag.find(".identity_config_" + IdentitifyType[IdentitifyType.NICKNAME]).on('shown', ev => {
|
||||
connectIdentity = new NameIdentity(tag.find(".connect_nickname").val() as string);
|
||||
select_tag.on('change', event => {
|
||||
selected_profile = profiles.find_profile(select_tag.val() as string);
|
||||
if(!selected_profile || !selected_profile.valid())
|
||||
select_invalid_tag.show();
|
||||
else
|
||||
select_invalid_tag.hide();
|
||||
updateFields();
|
||||
});
|
||||
tag.find(".connect_nickname").on("keyup", () => {
|
||||
if(connectIdentity instanceof NameIdentity)
|
||||
connectIdentity.set_name(tag.find(".connect_nickname").val() as string);
|
||||
});
|
||||
|
||||
if(!settings.static("localhost_debug", false)) {
|
||||
tag.find(".identity_select option[value=" + IdentitifyType[IdentitifyType.NICKNAME] + "]").remove();
|
||||
}
|
||||
select_tag.val('default').trigger('change');
|
||||
}
|
||||
|
||||
tag.find(".connect_nickname").on("keyup", () => updateFields());
|
||||
|
||||
//connect_address
|
||||
return tag;
|
||||
},
|
||||
|
@ -186,8 +115,8 @@ namespace Modals {
|
|||
let address = field_address.val().toString();
|
||||
globalClient.startConnection(
|
||||
address,
|
||||
connectIdentity,
|
||||
tag.parents(".modal-content").find(".connect_nickname").val().toString(),
|
||||
selected_profile,
|
||||
tag.parents(".modal-content").find(".connect_nickname").val().toString() || selected_profile.default_username,
|
||||
{password: tag.parents(".modal-content").find(".connect_password").val().toString(), hashed: false}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -4,18 +4,26 @@
|
|||
/// <reference path="../../voice/AudioController.ts" />
|
||||
|
||||
namespace Modals {
|
||||
import info = log.info;
|
||||
import TranslationRepository = i18n.TranslationRepository;
|
||||
import ConnectionProfile = profiles.ConnectionProfile;
|
||||
import IdentitifyType = profiles.identities.IdentitifyType;
|
||||
|
||||
export function spawnSettingsModal() {
|
||||
export function spawnSettingsModal() : Modal{
|
||||
let modal;
|
||||
modal = createModal({
|
||||
header: tr("Settings"),
|
||||
body: () => {
|
||||
let template = $("#tmpl_settings").renderTag();
|
||||
let template = $("#tmpl_settings").renderTag({
|
||||
client: native_client,
|
||||
valid_forum_identity: profiles.identities.valid_static_forum_identity(),
|
||||
forum_path: settings.static("forum_path"),
|
||||
});
|
||||
|
||||
template = $.spawn("div").append(template);
|
||||
initialiseSettingListeners(modal,template = template.tabify());
|
||||
initialise_translations(template.find(".settings-translations"));
|
||||
initialise_profiles(modal, template.find(".settings-profiles"));
|
||||
|
||||
return template;
|
||||
},
|
||||
footer: () => {
|
||||
|
@ -35,6 +43,7 @@ namespace Modals {
|
|||
width: 750
|
||||
});
|
||||
modal.open();
|
||||
return modal;
|
||||
}
|
||||
|
||||
function initialiseSettingListeners(modal: Modal, tag: JQuery) {
|
||||
|
@ -275,172 +284,418 @@ namespace Modals {
|
|||
|
||||
}
|
||||
|
||||
function initialise_translations(tag: JQuery) {
|
||||
{ //Initialize the list
|
||||
const tag_list = tag.find(".setting-list .list");
|
||||
const tag_loading = tag.find(".setting-list .loading");
|
||||
const template = $("#settings-translations-list-entry");
|
||||
const restart_hint = tag.find(".setting-list .restart-note");
|
||||
restart_hint.hide();
|
||||
function initialise_translations(tag: JQuery) {
|
||||
{ //Initialize the list
|
||||
const tag_list = tag.find(".setting-list .list");
|
||||
const tag_loading = tag.find(".setting-list .loading");
|
||||
const template = $("#settings-translations-list-entry");
|
||||
const restart_hint = tag.find(".setting-list .restart-note");
|
||||
restart_hint.hide();
|
||||
|
||||
const update_list = () => {
|
||||
tag_list.empty();
|
||||
const update_list = () => {
|
||||
tag_list.empty();
|
||||
|
||||
const currently_selected = i18n.config.translation_config().current_translation_url;
|
||||
{ //Default translation
|
||||
const tag = template.renderTag({
|
||||
type: "default",
|
||||
selected: !currently_selected || currently_selected == "default"
|
||||
});
|
||||
tag.on('click', () => {
|
||||
i18n.select_translation(undefined, undefined);
|
||||
tag_list.find(".selected").removeClass("selected");
|
||||
tag.addClass("selected");
|
||||
|
||||
restart_hint.show();
|
||||
});
|
||||
tag.appendTo(tag_list);
|
||||
}
|
||||
|
||||
{
|
||||
const display_repository_info = (repository: TranslationRepository) => {
|
||||
const info_modal = createModal({
|
||||
header: tr("Repository info"),
|
||||
body: () => {
|
||||
return $("#settings-translations-list-entry-info").renderTag({
|
||||
type: "repository",
|
||||
name: repository.name,
|
||||
url: repository.url,
|
||||
contact: repository.contact,
|
||||
translations: repository.translations || []
|
||||
});
|
||||
},
|
||||
footer: () => {
|
||||
let footer = $.spawn("div");
|
||||
footer.addClass("modal-button-group");
|
||||
footer.css("margin-top", "5px");
|
||||
footer.css("margin-bottom", "5px");
|
||||
footer.css("text-align", "right");
|
||||
|
||||
let buttonOk = $.spawn("button");
|
||||
buttonOk.text(tr("Close"));
|
||||
buttonOk.click(() => info_modal.close());
|
||||
footer.append(buttonOk);
|
||||
|
||||
return footer;
|
||||
}
|
||||
});
|
||||
info_modal.open()
|
||||
};
|
||||
|
||||
tag_loading.show();
|
||||
i18n.iterate_translations((repo, entry) => {
|
||||
let repo_tag = tag_list.find("[repository=\"" + repo.unique_id + "\"]");
|
||||
if(repo_tag.length == 0) {
|
||||
repo_tag = template.renderTag({
|
||||
type: "repository",
|
||||
name: repo.name || repo.url,
|
||||
id: repo.unique_id
|
||||
});
|
||||
|
||||
repo_tag.find(".button-delete").on('click', e => {
|
||||
e.preventDefault();
|
||||
|
||||
Modals.spawnYesNo(tr("Are you sure?"), tr("Do you really want to delete this repository?"), answer => {
|
||||
if(answer) {
|
||||
i18n.delete_repository(repo);
|
||||
update_list();
|
||||
}
|
||||
});
|
||||
});
|
||||
repo_tag.find(".button-info").on('click', e => {
|
||||
e.preventDefault();
|
||||
|
||||
display_repository_info(repo);
|
||||
});
|
||||
|
||||
tag_list.append(repo_tag);
|
||||
}
|
||||
|
||||
const currently_selected = i18n.config.translation_config().current_translation_url;
|
||||
{ //Default translation
|
||||
const tag = template.renderTag({
|
||||
type: "default",
|
||||
selected: !currently_selected || currently_selected == "default"
|
||||
type: "translation",
|
||||
name: entry.info.name || entry.url,
|
||||
id: repo.unique_id,
|
||||
selected: i18n.config.translation_config().current_translation_url == entry.url
|
||||
});
|
||||
tag.on('click', () => {
|
||||
i18n.select_translation(undefined, undefined);
|
||||
tag_list.find(".selected").removeClass("selected");
|
||||
tag.addClass("selected");
|
||||
tag.find(".button-info").on('click', e => {
|
||||
e.preventDefault();
|
||||
|
||||
const info_modal = createModal({
|
||||
header: tr("Translation info"),
|
||||
body: () => {
|
||||
const tag = $("#settings-translations-list-entry-info").renderTag({
|
||||
type: "translation",
|
||||
name: entry.info.name,
|
||||
url: entry.url,
|
||||
repository_name: repo.name,
|
||||
contributors: entry.info.contributors || []
|
||||
});
|
||||
|
||||
tag.find(".button-info").on('click', () => display_repository_info(repo));
|
||||
|
||||
return tag;
|
||||
},
|
||||
footer: () => {
|
||||
let footer = $.spawn("div");
|
||||
footer.addClass("modal-button-group");
|
||||
footer.css("margin-top", "5px");
|
||||
footer.css("margin-bottom", "5px");
|
||||
footer.css("text-align", "right");
|
||||
|
||||
let buttonOk = $.spawn("button");
|
||||
buttonOk.text(tr("Close"));
|
||||
buttonOk.click(() => info_modal.close());
|
||||
footer.append(buttonOk);
|
||||
|
||||
return footer;
|
||||
}
|
||||
});
|
||||
info_modal.open()
|
||||
});
|
||||
tag.on('click', e => {
|
||||
if(e.isDefaultPrevented()) return;
|
||||
i18n.select_translation(repo, entry);
|
||||
tag_list.find(".selected").removeClass("selected");
|
||||
tag.addClass("selected");
|
||||
|
||||
restart_hint.show();
|
||||
});
|
||||
tag.appendTo(tag_list);
|
||||
}
|
||||
|
||||
{
|
||||
const display_repository_info = (repository: TranslationRepository) => {
|
||||
const info_modal = createModal({
|
||||
header: tr("Repository info"),
|
||||
body: () => {
|
||||
return $("#settings-translations-list-entry-info").renderTag({
|
||||
type: "repository",
|
||||
name: repository.name,
|
||||
url: repository.url,
|
||||
contact: repository.contact,
|
||||
translations: repository.translations || []
|
||||
});
|
||||
},
|
||||
footer: () => {
|
||||
let footer = $.spawn("div");
|
||||
footer.addClass("modal-button-group");
|
||||
footer.css("margin-top", "5px");
|
||||
footer.css("margin-bottom", "5px");
|
||||
footer.css("text-align", "right");
|
||||
|
||||
let buttonOk = $.spawn("button");
|
||||
buttonOk.text(tr("Close"));
|
||||
buttonOk.click(() => info_modal.close());
|
||||
footer.append(buttonOk);
|
||||
|
||||
return footer;
|
||||
}
|
||||
});
|
||||
info_modal.open()
|
||||
};
|
||||
|
||||
tag_loading.show();
|
||||
i18n.iterate_translations((repo, entry) => {
|
||||
let repo_tag = tag_list.find("[repository=\"" + repo.unique_id + "\"]");
|
||||
if(repo_tag.length == 0) {
|
||||
repo_tag = template.renderTag({
|
||||
type: "repository",
|
||||
name: repo.name || repo.url,
|
||||
id: repo.unique_id
|
||||
});
|
||||
|
||||
repo_tag.find(".button-delete").on('click', e => {
|
||||
e.preventDefault();
|
||||
|
||||
Modals.spawnYesNo(tr("Are you sure?"), tr("Do you really want to delete this repository?"), answer => {
|
||||
if(answer) {
|
||||
i18n.delete_repository(repo);
|
||||
update_list();
|
||||
}
|
||||
});
|
||||
});
|
||||
repo_tag.find(".button-info").on('click', e => {
|
||||
e.preventDefault();
|
||||
|
||||
display_repository_info(repo);
|
||||
});
|
||||
|
||||
tag_list.append(repo_tag);
|
||||
}
|
||||
|
||||
const tag = template.renderTag({
|
||||
type: "translation",
|
||||
name: entry.info.name || entry.url,
|
||||
id: repo.unique_id,
|
||||
selected: i18n.config.translation_config().current_translation_url == entry.url
|
||||
});
|
||||
tag.find(".button-info").on('click', e => {
|
||||
e.preventDefault();
|
||||
|
||||
const info_modal = createModal({
|
||||
header: tr("Translation info"),
|
||||
body: () => {
|
||||
const tag = $("#settings-translations-list-entry-info").renderTag({
|
||||
type: "translation",
|
||||
name: entry.info.name,
|
||||
url: entry.url,
|
||||
repository_name: repo.name,
|
||||
contributors: entry.info.contributors || []
|
||||
});
|
||||
|
||||
tag.find(".button-info").on('click', () => display_repository_info(repo));
|
||||
|
||||
return tag;
|
||||
},
|
||||
footer: () => {
|
||||
let footer = $.spawn("div");
|
||||
footer.addClass("modal-button-group");
|
||||
footer.css("margin-top", "5px");
|
||||
footer.css("margin-bottom", "5px");
|
||||
footer.css("text-align", "right");
|
||||
|
||||
let buttonOk = $.spawn("button");
|
||||
buttonOk.text(tr("Close"));
|
||||
buttonOk.click(() => info_modal.close());
|
||||
footer.append(buttonOk);
|
||||
|
||||
return footer;
|
||||
}
|
||||
});
|
||||
info_modal.open()
|
||||
});
|
||||
tag.on('click', e => {
|
||||
if(e.isDefaultPrevented()) return;
|
||||
i18n.select_translation(repo, entry);
|
||||
tag_list.find(".selected").removeClass("selected");
|
||||
tag.addClass("selected");
|
||||
|
||||
restart_hint.show();
|
||||
});
|
||||
tag.insertAfter(repo_tag)
|
||||
}, () => {
|
||||
tag_loading.hide();
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
{
|
||||
tag.find(".button-add-repository").on('click', () => {
|
||||
createInputModal("Enter URL", tr("Enter repository URL:<br>"), text => true, url => { //FIXME test valid url
|
||||
if(!url) return;
|
||||
|
||||
tag_loading.show();
|
||||
i18n.load_repository(url as string).then(repository => {
|
||||
i18n.register_repository(repository);
|
||||
update_list();
|
||||
}).catch(error => {
|
||||
tag_loading.hide();
|
||||
createErrorModal("Failed to load repository", tr("Failed to query repository.<br>Ensure that this repository is valid and reachable.<br>Error: ") + error).open();
|
||||
})
|
||||
}).open();
|
||||
tag.insertAfter(repo_tag)
|
||||
}, () => {
|
||||
tag_loading.hide();
|
||||
});
|
||||
}
|
||||
|
||||
restart_hint.find(".button-reload").on('click', () => {
|
||||
location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
update_list();
|
||||
{
|
||||
tag.find(".button-add-repository").on('click', () => {
|
||||
createInputModal("Enter URL", tr("Enter repository URL:<br>"), text => true, url => { //FIXME test valid url
|
||||
if(!url) return;
|
||||
|
||||
tag_loading.show();
|
||||
i18n.load_repository(url as string).then(repository => {
|
||||
i18n.register_repository(repository);
|
||||
update_list();
|
||||
}).catch(error => {
|
||||
tag_loading.hide();
|
||||
createErrorModal("Failed to load repository", tr("Failed to query repository.<br>Ensure that this repository is valid and reachable.<br>Error: ") + error).open();
|
||||
})
|
||||
}).open();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
restart_hint.find(".button-reload").on('click', () => {
|
||||
location.reload();
|
||||
});
|
||||
|
||||
update_list();
|
||||
}
|
||||
}
|
||||
|
||||
function initialise_profiles(modal: Modal, tag: JQuery) {
|
||||
const settings_tag = tag.find(".profile-settings");
|
||||
let selected_profile: ConnectionProfile;
|
||||
let nickname_listener: () => any;
|
||||
let status_listener: () => any;
|
||||
|
||||
const display_settings = (profile: ConnectionProfile) => {
|
||||
selected_profile = profile;
|
||||
|
||||
settings_tag.find(".setting-name").val(profile.profile_name);
|
||||
settings_tag.find(".setting-default-nickname").val(profile.default_username);
|
||||
settings_tag.find(".setting-default-password").val(profile.default_password);
|
||||
|
||||
{
|
||||
//change listener
|
||||
const select_tag = settings_tag.find(".select-container select")[0] as HTMLSelectElement;
|
||||
const type = profile.selected_identity_type.toLowerCase();
|
||||
|
||||
select_tag.onchange = () => {
|
||||
console.log("Selected: " + select_tag.value);
|
||||
settings_tag.find(".identity-settings.active").removeClass("active");
|
||||
settings_tag.find(".identity-settings-" + select_tag.value).addClass("active");
|
||||
|
||||
profile.selected_identity_type = select_tag.value.toLowerCase();
|
||||
const selected_type = profile.selected_type();
|
||||
const identity = profile.selected_identity();
|
||||
|
||||
profiles.mark_need_save();
|
||||
|
||||
if(selected_type == IdentitifyType.TEAFORO) {
|
||||
const forum_tag = settings_tag.find(".identity-settings-teaforo");
|
||||
|
||||
forum_tag.find(".connected .disconnected").hide();
|
||||
if(identity && identity.valid()) {
|
||||
forum_tag.find(".connected").show();
|
||||
} else {
|
||||
forum_tag.find(".disconnected").show();
|
||||
}
|
||||
} else if(selected_type == IdentitifyType.TEAMSPEAK) {
|
||||
console.log("Set: " + identity);
|
||||
const teamspeak_tag = settings_tag.find(".identity-settings-teamspeak");
|
||||
if(identity)
|
||||
teamspeak_tag.find(".identity_string").val((identity as profiles.identities.TeamSpeakIdentity).exported());
|
||||
else
|
||||
teamspeak_tag.find(".identity_string").val("");
|
||||
} else if(selected_type == IdentitifyType.NICKNAME) {
|
||||
const name_tag = settings_tag.find(".identity-settings-nickname");
|
||||
if(identity)
|
||||
name_tag.find("input").val(identity.name());
|
||||
else
|
||||
name_tag.find("input").val("");
|
||||
}
|
||||
};
|
||||
|
||||
select_tag.value = type;
|
||||
select_tag.onchange(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const update_profile_list = () => {
|
||||
const profile_list = tag.find(".profile-list .list").empty();
|
||||
const profile_template = $("#settings-profile-list-entry");
|
||||
for(const profile of profiles.profiles()) {
|
||||
const list_tag = profile_template.renderTag({
|
||||
profile_name: profile.profile_name,
|
||||
id: profile.id
|
||||
});
|
||||
|
||||
const profile_status_update = () => {
|
||||
list_tag.find(".status").hide();
|
||||
if(profile.valid())
|
||||
list_tag.find(".status-valid").show();
|
||||
else
|
||||
list_tag.find(".status-invalid").show();
|
||||
};
|
||||
list_tag.on('click', event => {
|
||||
/* update ui */
|
||||
profile_list.find(".selected").removeClass("selected");
|
||||
list_tag.addClass("selected");
|
||||
|
||||
if(profile == selected_profile) return;
|
||||
nickname_listener = () => list_tag.find(".name").text(profile.profile_name);
|
||||
status_listener = profile_status_update;
|
||||
|
||||
display_settings(profile);
|
||||
});
|
||||
|
||||
|
||||
profile_list.append(list_tag);
|
||||
if((!selected_profile && profile.id == "default") || selected_profile == profile)
|
||||
setTimeout(() => list_tag.trigger('click'), 1);
|
||||
profile_status_update();
|
||||
}
|
||||
};
|
||||
|
||||
/* identity settings */
|
||||
{
|
||||
{ //TeamSpeak change listener
|
||||
const teamspeak_tag = settings_tag.find(".identity-settings-teamspeak");
|
||||
const display_error = (error?: string) => {
|
||||
if(error){
|
||||
teamspeak_tag.find(".error-message").show().html(error);
|
||||
} else
|
||||
teamspeak_tag.find(".error-message").hide();
|
||||
status_listener();
|
||||
};
|
||||
|
||||
teamspeak_tag.find(".identity_file").on('change', event => {
|
||||
if(!selected_profile) return;
|
||||
|
||||
const element = event.target as HTMLInputElement;
|
||||
const file_reader = new FileReader();
|
||||
file_reader.onload = function() {
|
||||
const identity = profiles.identities.TSIdentityHelper.loadIdentityFromFileContains(file_reader.result as string);
|
||||
if(!identity) {
|
||||
display_error(tr("Failed to parse identity.<br>Reason: ") + profiles.identities.TSIdentityHelper.last_error());
|
||||
return;
|
||||
} else {
|
||||
teamspeak_tag.find(".identity_string").val(identity.exported());
|
||||
selected_profile.set_identity(IdentitifyType.TEAMSPEAK, identity as any);
|
||||
profiles.mark_need_save();
|
||||
display_error(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
file_reader.onerror = ev => {
|
||||
console.error(tr("Failed to read give identity file: %o"), ev);
|
||||
display_error(tr("Failed to read file!"));
|
||||
return;
|
||||
};
|
||||
|
||||
if(element.files && element.files.length > 0)
|
||||
file_reader.readAsText(element.files[0]);
|
||||
});
|
||||
|
||||
teamspeak_tag.find(".identity_string").on('change', event => {
|
||||
if(!selected_profile) return;
|
||||
|
||||
const element = event.target as HTMLInputElement;
|
||||
if(element.value.length == 0) {
|
||||
display_error("Please provide an identity");
|
||||
} else {
|
||||
const identity = profiles.identities.TSIdentityHelper.loadIdentity(element.value);
|
||||
if(!identity) {
|
||||
display_error("Failed to parse identity string!");
|
||||
return;
|
||||
}
|
||||
|
||||
selected_profile.set_identity(IdentitifyType.TEAMSPEAK, identity as any);
|
||||
profiles.mark_need_save();
|
||||
display_error(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{ //The forum
|
||||
const teaforo_tag = settings_tag.find(".identity-settings-teaforo");
|
||||
if(native_client) {
|
||||
teaforo_tag.find(".native-teaforo-login").on('click', event => {
|
||||
setTimeout(() => {
|
||||
const forum = require("teaforo.js");
|
||||
const call = () => {
|
||||
if(modal.shown) {
|
||||
display_settings(selected_profile);
|
||||
status_listener();
|
||||
}
|
||||
};
|
||||
forum.register_callback(call);
|
||||
forum.open();
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
//TODO add the name!
|
||||
}
|
||||
|
||||
/* general settings */
|
||||
{
|
||||
settings_tag.find(".setting-name").on('change', event => {
|
||||
const value = settings_tag.find(".setting-name").val() as string;
|
||||
if(value && selected_profile) {
|
||||
selected_profile.profile_name = value;
|
||||
if(nickname_listener)
|
||||
nickname_listener();
|
||||
profiles.mark_need_save();
|
||||
status_listener();
|
||||
}
|
||||
});
|
||||
settings_tag.find(".setting-default-nickname").on('change', event => {
|
||||
const value = settings_tag.find(".setting-default-nickname").val() as string;
|
||||
if(value && selected_profile) {
|
||||
selected_profile.default_username = value;
|
||||
profiles.mark_need_save();
|
||||
status_listener();
|
||||
}
|
||||
});
|
||||
settings_tag.find(".setting-default-password").on('change', event => {
|
||||
const value = settings_tag.find(".setting-default-password").val() as string;
|
||||
if(value && selected_profile) {
|
||||
selected_profile.default_username = value;
|
||||
profiles.mark_need_save();
|
||||
status_listener();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* general buttons */
|
||||
{
|
||||
tag.find(".button-add-profile").on('click', event => {
|
||||
createInputModal(tr("Please enter a name"), tr("Please enter a name for the new profile:<br>"), text => text.length > 0 && !profiles.find_profile_by_name(text), value => {
|
||||
if(value) {
|
||||
display_settings(profiles.create_new_profile(value as string));
|
||||
update_profile_list();
|
||||
profiles.mark_need_save();
|
||||
}
|
||||
}).open();
|
||||
});
|
||||
|
||||
tag.find(".button-set-default").on('click', event => {
|
||||
if(selected_profile && selected_profile.id != 'default') {
|
||||
profiles.set_default_profile(selected_profile);
|
||||
update_profile_list();
|
||||
profiles.mark_need_save();
|
||||
}
|
||||
});
|
||||
|
||||
tag.find(".button-delete").on('click', event => {
|
||||
if(selected_profile && selected_profile.id != 'default') {
|
||||
event.preventDefault();
|
||||
spawnYesNo(tr("Are you sure?"), tr ("Do you really want to delete this profile?"), result => {
|
||||
if(result) {
|
||||
profiles.delete_profile(selected_profile);
|
||||
update_profile_list();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
modal.close_listener.push(() => {
|
||||
if(profiles.requires_save())
|
||||
profiles.save();
|
||||
});
|
||||
update_profile_list();
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
Subproject commit 0221bd137ef5bbc846018ff86deda0aca38aed26
|
||||
Subproject commit 86dae7fae51db65d63febf4c3e15b8dc629a5732
|
Loading…
Reference in New Issue