Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e86d0a0fc1 | ||
|
|
cf13957b45 | ||
|
|
2a18d7f77d | ||
|
|
7dadf3da7a | ||
|
|
de67e7967a | ||
|
|
face49de00 | ||
|
|
365781a0c0 | ||
|
|
ae9c4e5995 | ||
|
|
7bc7ef383a | ||
|
|
54b37862f1 | ||
|
|
0409d8f761 | ||
|
|
b669bdd8d5 | ||
|
|
b41658138a | ||
|
|
d68edb9e1a |
283
src/ui/ab.js
Normal file
283
src/ui/ab.js
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
import { handler, string2RGB, platformSvg, msgbox,translate,is_win,OS } from "./common.js";
|
||||||
|
import { app, formatId, createNewConnect,svg_menu } from "./index.js"; // TODO check app obj
|
||||||
|
// TODO transform
|
||||||
|
const svg_tile = <svg id="session-tile" viewBox="0 0 158.6 158.6"><path style="stroke-width:.309756" d="M5.4 157.7c-1-.3-2-1-3.2-2.1-2.8-2.8-2.6-1-2.5-32 0-26.7 0-27 .7-28.3a9.3 9.3 0 0 1 4-4.2c1.2-.6 2.3-.6 29-.7 27.5 0 27.6 0 29.1.6.8.4 2 1.2 2.7 2 2.4 2.5 2.3.7 2.2 31.6-.1 26.5-.1 27.6-.7 28.8a9.3 9.3 0 0 1-4.2 4c-1.4.6-1.6.6-28.5.7a235 235 0 0 1-28.6-.4zm91 0a8.5 8.5 0 0 1-5.7-5.4c-.2-.7-.3-8.3-.3-28.3V96.7l.7-1.6a8.9 8.9 0 0 1 4.6-4.3c1.2-.4 3.8-.5 28.9-.4 26.6.1 27.6.1 28.8.7 1.6.8 3.2 2.5 4 4.2.7 1.4.7 1.6.7 28.3.1 31 .3 29.2-2.5 32-2.8 2.7-1 2.6-31.4 2.6-21.4 0-26.8-.1-27.9-.5zM5.3 67a8.7 8.7 0 0 1-4-3C-.5 61.6-.5 62.3-.5 33.6-.4 3.2-.5 5 2.2 2.2 5-.6 3.2-.4 34.2-.3c26.7 0 27 0 28.3.7 1.7.8 3.4 2.4 4.2 4 .6 1.2.6 2.2.7 28.8 0 25.1 0 27.7-.4 29a9 9 0 0 1-4.3 4.5l-1.6.7H33.7c-20.2 0-27.7-.1-28.4-.4Zm89.8-.3a9 9 0 0 1-4.3-4.6c-.5-1.2-.5-3.8-.5-28.9.1-26.6.2-27.6.7-28.8a9.3 9.3 0 0 1 4.2-4c1.4-.7 1.6-.7 28.3-.7 31-.1 29.2-.3 32 2.5 2.8 2.8 2.6 1 2.5 32 0 26.7 0 26.9-.7 28.3a9.3 9.3 0 0 1-4 4.2c-1.2.5-2.2.6-29 .6l-27.7.1z" transform="translate(.4 .4)" /></svg>;
|
||||||
|
const svg_list = <svg id="session-list" viewBox="0 0 246.8 185.8"><path style="stroke-width:.482473" d="M-69.2 102.7A16.5 16.5 0 0 1-67 70.4c7.3-1 15 4 17.3 11 1 3 1 8 0 10.8a16.7 16.7 0 0 1-19.5 10.5zm53-3.4a12.3 12.3 0 0 1-7-16.8c1.3-3 3.1-4.7 6-6 2.2-1 2.8-1 87.2-1 92.4 0 87-.2 90.6 2.6.9.7 2.2 2.4 3 3.7 1.2 2.2 1.4 3.1 1.4 6 0 4.8-2.3 8.6-6.8 11l-1.9 1-85.2.1c-71.9 0-85.5 0-87.3-.6zm-53.5-73c-4.7-1.5-8.6-5-10.6-9.1-1.8-4-1.8-9.8 0-13.7 1.6-3.3 4.4-6.2 7.8-8 2.2-1.2 3-1.3 7.1-1.3 4 0 5 .1 7.3 1.3a16.6 16.6 0 0 1 0 29.6c-2 1-3.4 1.4-6.5 1.5-2.2 0-4.5 0-5.1-.3zm52.3-4.8c-2.4-1.1-5.3-4-6.2-6.5-1-2.4-1-7.3.1-9.7.5-1.1 1.8-2.8 2.8-3.8 3.7-3.5-4-3.2 91-3.2h85.5l2.5 1.1a12 12 0 0 1 0 21.8l-2.5 1.2H70.2c-82.5 0-85.7 0-87.6-1zm-52.1-71.6a18 18 0 0 1-10-7.7 17 17 0 0 1-.7-15c2.3-5 5.8-7.9 11.4-9.3 9-2.3 18.3 4 19.8 13.4a16.4 16.4 0 0 1-15.2 19c-2.1.1-4.1 0-5.3-.4zm52.1-5.9c-1.3-.6-3-1.7-3.7-2.5-4.7-5-4.2-13.7 1-18 3.7-3.1-1.8-3 91.5-2.8l84.9.1 2 1a12 12 0 0 1 6.7 11c0 3-.2 3.9-1.4 6-.8 1.4-2.1 3-3 3.8-3.7 2.7 1.8 2.6-90.6 2.6h-85l-2.4-1.2z" transform="translate(81.7 82.6)" /></svg>;
|
||||||
|
const search_icon = <svg viewBox="0 0 655.278 655.024"><g transform="translate(-24.497 -195.01)"><path d="m649.96 847.92c-2.9592-1.3629-27.183-24.243-63.36-59.846-32.213-31.702-70.814-69.663-85.78-84.357l-27.21-26.717-4.7897 3.5287c-66.337 48.872-145.32 66.878-224.31 51.138-72.966-14.539-136.58-58.184-178.47-122.44-15.945-24.462-30.723-61.471-36.413-91.191-8.9404-46.696-6.2422-90.39 8.3388-135.04 13.39-41.003 34.756-75.42 66.479-107.09 74.506-74.377 183.71-99.89 284.22-66.397 62.352 20.777 117.67 65.579 150.79 122.12 38.716 66.101 46.59 147.55 21.43 221.66-9.9038 29.171-29.788 63.725-49.916 86.743l-7.0583 8.0717 3.0992 2.919c1.7046 1.6054 40.675 39.928 86.602 85.161 89.007 87.664 87.558 86.034 85.619 96.293-1.2888 6.8209-5.2313 12.041-11.321 14.989-6.7901 3.287-11.55 3.4093-17.952 0.46117zm-316.64-154.63c32.373-5.0481 61.075-15.115 86.553-30.358 47.942-28.683 83.505-72.09 100.89-123.14 35.043-102.91-6.4362-214.07-100.89-270.37-52.514-31.302-117.76-40.564-178.06-25.277-81.183 20.579-145.19 82.918-166.86 162.52-5.5757 20.478-7.445 35.423-7.445 59.52s1.8693 39.042 7.445 59.52c21.409 78.63 85.366 141.52 164.81 162.05 29.22 7.5511 66.493 9.756 93.564 5.5347z" stroke-width="1.28" /></g></svg>;
|
||||||
|
const clear_icon = <svg viewBox="0 0 478.94 479.03"><path d="M217.488 478.45c-30.264-3.146-55.348-10.265-82.714-23.477C62.54 420.1 14.214 353.763 1.824 272.463c-2.412-15.82-2.434-50.027-.043-66.058 16.004-107.32 97.008-188.28 204.71-204.6 14.33-2.172 49.054-2.447 63-.498C323.95 8.915 371.3 32.2 409.03 69.927c37.697 37.698 61.125 85.349 68.605 139.54 1.943 14.08 1.68 48.804-.478 63-6.616 43.533-24.01 83.859-50.468 117-37.556 47.046-92.812 78.608-153.26 87.54-12.553 1.855-44.144 2.671-55.936 1.445zm42.144-32.045c15.649-1.602 29.895-4.63 44.856-9.531 78.146-25.604 133.49-94.718 141.94-177.26 6.245-60.993-16.1-123.3-59.94-167.14-55.797-55.797-139.4-75.365-213.52-49.98-77.69 26.609-131.51 94.14-140.42 176.19-4.761 43.843 6.392 91.899 30.274 130.44 41.468 66.926 119.01 105.26 196.82 97.29zm-138.69-80.346c-4.096-1.784-8.225-6.874-9.022-11.123-1.676-8.935-3.495-6.761 52.877-63.221l52.17-52.25-52.17-52.25c-56.544-56.632-54.56-54.249-52.834-63.451.924-4.923 6.905-10.904 11.828-11.828 9.201-1.726 6.819-3.71 63.451 52.834l52.25 52.169 52.25-52.169c56.632-56.544 54.25-54.56 63.451-52.834 4.923.923 10.904 6.905 11.828 11.828 1.726 9.201 3.71 6.818-52.834 63.451l-52.169 52.25 52.17 52.25c56.543 56.632 54.56 54.249 52.833 63.451-.923 4.923-6.905 10.904-11.828 11.828-9.201 1.726-6.818 3.71-63.455-52.838l-52.255-52.173-51.745 51.696c-28.496 28.469-53.01 52.166-54.56 52.742-3.766 1.4-8.515 1.26-12.234-.36z" /></svg>;
|
||||||
|
|
||||||
|
function getSessionsStyleOption(type) {
|
||||||
|
return (type || "recent") + "-sessions-style";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSessionsStyle(type) {
|
||||||
|
var v = handler.xcall("get_local_option",getSessionsStyleOption(type));
|
||||||
|
if (!v) v = type == "ab" ? "list" : "tile";
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchPatterns = {};
|
||||||
|
|
||||||
|
export class SearchBar extends Element {
|
||||||
|
|
||||||
|
this(props) {
|
||||||
|
this.type = (props || {}).type || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
let value = searchPatterns[this.type] || "";
|
||||||
|
setTimeout(() => { this.$("input").value = value; }, 1);
|
||||||
|
return (<div class="search-id" >
|
||||||
|
<span class="search-icon">{search_icon}</span>
|
||||||
|
<input type="text" novalue={translate("Search ID")} />
|
||||||
|
{value && <span class="clear-input">{clear_icon}</span>}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at span.clear-input"](_) {
|
||||||
|
this.$("input").value = '';
|
||||||
|
this.onChange('');
|
||||||
|
}
|
||||||
|
|
||||||
|
["on change at input"](_, el) {
|
||||||
|
this.onChange(el.value.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(v) {
|
||||||
|
searchPatterns[this.type] = v;
|
||||||
|
app.multipleSessions.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SessionStyle extends Element {
|
||||||
|
type = "";
|
||||||
|
|
||||||
|
this(props) {
|
||||||
|
this.type = (props || {}).type || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let sessionsStyle = getSessionsStyle(this.type);
|
||||||
|
return (<div class="sessions-tab" style="margin-left: 0.5em;">
|
||||||
|
<span class={sessionsStyle == "tile" ? "active" : "inactive"}>{svg_tile}</span>
|
||||||
|
<span class={sessionsStyle != "tile" ? "active" : "inactive"}>{svg_list}</span>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at span.inactive"](_) {
|
||||||
|
let option = getSessionsStyleOption(this.type);
|
||||||
|
let sessionsStyle = getSessionsStyle(this.type);
|
||||||
|
handler.xcall("set_option", option, sessionsStyle == "tile" ? "list" : "tile");
|
||||||
|
app.componentUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SessionList extends Element {
|
||||||
|
sessions = [];
|
||||||
|
type = "";
|
||||||
|
style;
|
||||||
|
|
||||||
|
|
||||||
|
this(props) {
|
||||||
|
this.sessions = props.sessions;
|
||||||
|
this.type = props.type;
|
||||||
|
this.style = getSessionsStyle(props.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSessions() {
|
||||||
|
let p = searchPatterns[this.type];
|
||||||
|
if (!p) return this.sessions;
|
||||||
|
let tmp = [];
|
||||||
|
this.sessions.map( (s) => {
|
||||||
|
let name = s[4] || s.alias || s[0] || s.id || "";
|
||||||
|
if (name.indexOf(p) >= 0) tmp.push(s);
|
||||||
|
});
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let sessions = this.getSessions();
|
||||||
|
if (sessions.length == 0) return <div style="margin: *; font-size: 1.6em;">{translate("Empty")}</div>;
|
||||||
|
sessions = sessions.map((x) => this.getSession(x));
|
||||||
|
return <div class="recent-sessions-content" key={sessions.length} >
|
||||||
|
<popup>
|
||||||
|
<menu class="context" id="remote-context">
|
||||||
|
<li id="connect">{translate('Connect')}</li>
|
||||||
|
<li id="transfer">{translate('Transfer File')}</li>
|
||||||
|
<li id="tunnel">{translate('TCP Tunneling')}</li>
|
||||||
|
<li id="rdp">RDP</li>
|
||||||
|
<div class="separator" />
|
||||||
|
<li id="rename">{translate('Rename')}</li>
|
||||||
|
{this.type != "fav" && <li id="remove">{translate('Remove')}</li>}
|
||||||
|
{is_win && <li di="shortcut">{translate('Create Desktop Shortcut')}</li>}
|
||||||
|
<li id="forget-password">{translate('Unremember Password')}</li>
|
||||||
|
{(!this.type || this.type == "fav") && <li id="add-fav">{translate('Add to Favorites')}</li>}
|
||||||
|
{(!this.type || this.type == "fav") && <li id="remove-fav">{translate('Remove from Favorites')}</li>}
|
||||||
|
</menu>
|
||||||
|
</popup>
|
||||||
|
{sessions}
|
||||||
|
</div >;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSession(s) {
|
||||||
|
let id = s[0] || s.id || "";
|
||||||
|
let username = s[1] || s.username || "";
|
||||||
|
let hostname = s[2] || s.hostname || "";
|
||||||
|
let platform = s[3] || s.platform || "";
|
||||||
|
let alias = s[4] || s.alias || "";
|
||||||
|
if (this.style == "list") {
|
||||||
|
return (<div class="remote-session-link remote-session-list" id={id} platform={platform} title={alias ? "ID: " + id : ""} >
|
||||||
|
<div class="platform" style={"background:" + string2RGB(id + platform, 0.5)}>
|
||||||
|
{platform && platformSvg(platform, "white")}
|
||||||
|
</div>
|
||||||
|
<div class="name">
|
||||||
|
<div>
|
||||||
|
<div id="alias" class="ellipsis">{alias ? alias : formatId(id)}</div>
|
||||||
|
<div class="username ellipsis">{username}@{hostname}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{svg_menu}
|
||||||
|
</div>
|
||||||
|
</div >);
|
||||||
|
}
|
||||||
|
return (<div class="remote-session-link remote-session" id={id} platform={platform} title={alias ? "ID: " + id : ""} style={"background:" + string2RGB(id + platform, 0.5)} >
|
||||||
|
<div class="platform">
|
||||||
|
{platform && platformSvg(platform, "white")}
|
||||||
|
<div class="username ellipsis">{username}@{hostname}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<div id="alias" class="ellipsis">{alias ? alias : formatId(id)}</div>
|
||||||
|
{svg_menu}
|
||||||
|
</div >
|
||||||
|
</div >);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on doubleclick at div.remote-session-link"](evt, me) {
|
||||||
|
createNewConnect(me.id, "connect");
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #menu"](_, me) {
|
||||||
|
let id = me.parentElement.parentElement.id;
|
||||||
|
let platform = me.parentElement.parentElement.getAttribute("platform");
|
||||||
|
this.$("#rdp").style.setProperty(
|
||||||
|
"display", (platform == "Windows" && is_win) ? "block" : "none",
|
||||||
|
);
|
||||||
|
this.$("#forget-password").style.setProperty(
|
||||||
|
"display", handler.xcall("peer_has_password", id) ? "block" : "none",
|
||||||
|
);
|
||||||
|
if (!this.type || this.type == "fav") {
|
||||||
|
let in_fav = handler.xcall("get_fav").indexOf(id) >= 0;
|
||||||
|
let el = this.$("add-fav");
|
||||||
|
if (el) el.style.setProperty(
|
||||||
|
"display", in_fav ? "none" : "block",
|
||||||
|
);
|
||||||
|
el = this.$("remove-fav");
|
||||||
|
if (el) el.style.setProperty(
|
||||||
|
"display", in_fav ? "block" : "none",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// https://sciter.com/forums/topic/replacecustomize-context-menu/
|
||||||
|
let menu = this.$("menu#remote-context");
|
||||||
|
menu.setAttribute("remote-id",id);
|
||||||
|
me.popup(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at menu#remote-context li"](evt, me) {
|
||||||
|
let action = me.id;
|
||||||
|
let id = me.parentElement.getAttribute("remote-id");
|
||||||
|
if (action == "connect") {
|
||||||
|
createNewConnect(id, "connect");
|
||||||
|
} else if (action == "transfer") {
|
||||||
|
createNewConnect(id, "file-transfer");
|
||||||
|
} else if (action == "remove") {
|
||||||
|
if (!this.type) {
|
||||||
|
handler.xcall("remove_peer", id);
|
||||||
|
app.componentUpdate();
|
||||||
|
}
|
||||||
|
} else if (action == "forget-password") {
|
||||||
|
handler.forget_password(id);
|
||||||
|
} else if (action == "shortcut") {
|
||||||
|
handler.xcall("create_shortcut", id);
|
||||||
|
} else if (action == "rdp") {
|
||||||
|
createNewConnect(id, "rdp");
|
||||||
|
} else if (action == "add-fav") {
|
||||||
|
var favs = handler.get_fav();
|
||||||
|
if (favs.indexOf(id) < 0) {
|
||||||
|
favs = [id].concat(favs);
|
||||||
|
handler.store_fav(favs);
|
||||||
|
}
|
||||||
|
app.multipleSessions.update();
|
||||||
|
app.update();
|
||||||
|
} else if (action == "remove-fav") {
|
||||||
|
var favs = handler.get_fav();
|
||||||
|
var i = favs.indexOf(id);
|
||||||
|
favs.splice(i, 1);
|
||||||
|
handler.store_fav(favs);
|
||||||
|
app.multipleSessions.update();
|
||||||
|
} else if (action == "tunnel") {
|
||||||
|
createNewConnect(id, "port-forward");
|
||||||
|
} else if (action == "rename") {
|
||||||
|
let old_name = handler.xcall("get_peer_option", id, "alias");
|
||||||
|
msgbox("custom-rename", "Rename", "<div class='form'> \
|
||||||
|
<div><input name='name' class='outline-focus' style='width: *; height: 23px', value='" + old_name + "' /></div> \
|
||||||
|
</div> \
|
||||||
|
",
|
||||||
|
function (res = null) {
|
||||||
|
if (!res) return;
|
||||||
|
let name = (res.name || "").trim();
|
||||||
|
if (name != old_name) {
|
||||||
|
handler.xcall("set_peer_option", id, "alias", name);
|
||||||
|
}
|
||||||
|
app.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSessionsType() {
|
||||||
|
return handler.xcall("get_local_option", "show-sessions-type");
|
||||||
|
}
|
||||||
|
|
||||||
|
class Favorites extends Element {
|
||||||
|
render() {
|
||||||
|
var sessions = handler.xcall("get_fav").map(function(f) {
|
||||||
|
return handler.xcall("get_peer", f);
|
||||||
|
});
|
||||||
|
return <SessionList sessions={sessions} type="fav" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MultipleSessions extends Element {
|
||||||
|
render() {
|
||||||
|
var type = getSessionsType();
|
||||||
|
return <div style="size: *">
|
||||||
|
<div class="sessions-bar">
|
||||||
|
<div style="width:*" class="sessions-tab" id="sessions-type">
|
||||||
|
<span class={!type ? 'active' : 'inactive'}>{translate('Recent Sessions')}</span>
|
||||||
|
<span id="fav" class={type == "fav" ? 'active' : 'inactive'}>{translate('Favorites')}</span>
|
||||||
|
</div>
|
||||||
|
{!this.hidden && <SearchBar type={type} />}
|
||||||
|
{!this.hidden && <SessionStyle type={type} />}
|
||||||
|
</div>
|
||||||
|
{!this.hidden &&
|
||||||
|
((type == "fav" && <Favorites />) ||
|
||||||
|
<SessionList sessions={handler.xcall("get_recent_sessions")} />)}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at div#sessions-type span.inactive"] (_, el) {
|
||||||
|
handler.xcall("set_option", 'show-sessions-type', el.id || "");
|
||||||
|
this.componentUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSize() {
|
||||||
|
let w = this.$(".sessions-bar").state.box("width") - 220;
|
||||||
|
this.$("#sessions-type span").style.setProperty(
|
||||||
|
"max-width", (w / 2) + "px",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.onsizechange = () => { if (app && app.multipleSessions) app.multipleSessions.onSize(); }
|
||||||
290
src/ui/ab.tis
290
src/ui/ab.tis
@@ -1,290 +0,0 @@
|
|||||||
var svg_tile = <svg #session-tile viewBox="0 0 158.6 158.6"><path style="stroke-width:.309756" d="M5.4 157.7c-1-.3-2-1-3.2-2.1-2.8-2.8-2.6-1-2.5-32 0-26.7 0-27 .7-28.3a9.3 9.3 0 0 1 4-4.2c1.2-.6 2.3-.6 29-.7 27.5 0 27.6 0 29.1.6.8.4 2 1.2 2.7 2 2.4 2.5 2.3.7 2.2 31.6-.1 26.5-.1 27.6-.7 28.8a9.3 9.3 0 0 1-4.2 4c-1.4.6-1.6.6-28.5.7a235 235 0 0 1-28.6-.4zm91 0a8.5 8.5 0 0 1-5.7-5.4c-.2-.7-.3-8.3-.3-28.3V96.7l.7-1.6a8.9 8.9 0 0 1 4.6-4.3c1.2-.4 3.8-.5 28.9-.4 26.6.1 27.6.1 28.8.7 1.6.8 3.2 2.5 4 4.2.7 1.4.7 1.6.7 28.3.1 31 .3 29.2-2.5 32-2.8 2.7-1 2.6-31.4 2.6-21.4 0-26.8-.1-27.9-.5zM5.3 67a8.7 8.7 0 0 1-4-3C-.5 61.6-.5 62.3-.5 33.6-.4 3.2-.5 5 2.2 2.2 5-.6 3.2-.4 34.2-.3c26.7 0 27 0 28.3.7 1.7.8 3.4 2.4 4.2 4 .6 1.2.6 2.2.7 28.8 0 25.1 0 27.7-.4 29a9 9 0 0 1-4.3 4.5l-1.6.7H33.7c-20.2 0-27.7-.1-28.4-.4Zm89.8-.3a9 9 0 0 1-4.3-4.6c-.5-1.2-.5-3.8-.5-28.9.1-26.6.2-27.6.7-28.8a9.3 9.3 0 0 1 4.2-4c1.4-.7 1.6-.7 28.3-.7 31-.1 29.2-.3 32 2.5 2.8 2.8 2.6 1 2.5 32 0 26.7 0 26.9-.7 28.3a9.3 9.3 0 0 1-4 4.2c-1.2.5-2.2.6-29 .6l-27.7.1z" transform="translate(.4 .4)"/></svg>;
|
|
||||||
var svg_list = <svg #session-list viewBox="0 0 246.8 185.8"><path style="stroke-width:.482473" d="M-69.2 102.7A16.5 16.5 0 0 1-67 70.4c7.3-1 15 4 17.3 11 1 3 1 8 0 10.8a16.7 16.7 0 0 1-19.5 10.5zm53-3.4a12.3 12.3 0 0 1-7-16.8c1.3-3 3.1-4.7 6-6 2.2-1 2.8-1 87.2-1 92.4 0 87-.2 90.6 2.6.9.7 2.2 2.4 3 3.7 1.2 2.2 1.4 3.1 1.4 6 0 4.8-2.3 8.6-6.8 11l-1.9 1-85.2.1c-71.9 0-85.5 0-87.3-.6zm-53.5-73c-4.7-1.5-8.6-5-10.6-9.1-1.8-4-1.8-9.8 0-13.7 1.6-3.3 4.4-6.2 7.8-8 2.2-1.2 3-1.3 7.1-1.3 4 0 5 .1 7.3 1.3a16.6 16.6 0 0 1 0 29.6c-2 1-3.4 1.4-6.5 1.5-2.2 0-4.5 0-5.1-.3zm52.3-4.8c-2.4-1.1-5.3-4-6.2-6.5-1-2.4-1-7.3.1-9.7.5-1.1 1.8-2.8 2.8-3.8 3.7-3.5-4-3.2 91-3.2h85.5l2.5 1.1a12 12 0 0 1 0 21.8l-2.5 1.2H70.2c-82.5 0-85.7 0-87.6-1zm-52.1-71.6a18 18 0 0 1-10-7.7 17 17 0 0 1-.7-15c2.3-5 5.8-7.9 11.4-9.3 9-2.3 18.3 4 19.8 13.4a16.4 16.4 0 0 1-15.2 19c-2.1.1-4.1 0-5.3-.4zm52.1-5.9c-1.3-.6-3-1.7-3.7-2.5-4.7-5-4.2-13.7 1-18 3.7-3.1-1.8-3 91.5-2.8l84.9.1 2 1a12 12 0 0 1 6.7 11c0 3-.2 3.9-1.4 6-.8 1.4-2.1 3-3 3.8-3.7 2.7 1.8 2.6-90.6 2.6h-85l-2.4-1.2z" transform="translate(81.7 82.6)"/></svg>;
|
|
||||||
var search_icon = <svg viewBox="0 0 655.278 655.024"><g transform="translate(-24.497 -195.01)"><path d="m649.96 847.92c-2.9592-1.3629-27.183-24.243-63.36-59.846-32.213-31.702-70.814-69.663-85.78-84.357l-27.21-26.717-4.7897 3.5287c-66.337 48.872-145.32 66.878-224.31 51.138-72.966-14.539-136.58-58.184-178.47-122.44-15.945-24.462-30.723-61.471-36.413-91.191-8.9404-46.696-6.2422-90.39 8.3388-135.04 13.39-41.003 34.756-75.42 66.479-107.09 74.506-74.377 183.71-99.89 284.22-66.397 62.352 20.777 117.67 65.579 150.79 122.12 38.716 66.101 46.59 147.55 21.43 221.66-9.9038 29.171-29.788 63.725-49.916 86.743l-7.0583 8.0717 3.0992 2.919c1.7046 1.6054 40.675 39.928 86.602 85.161 89.007 87.664 87.558 86.034 85.619 96.293-1.2888 6.8209-5.2313 12.041-11.321 14.989-6.7901 3.287-11.55 3.4093-17.952 0.46117zm-316.64-154.63c32.373-5.0481 61.075-15.115 86.553-30.358 47.942-28.683 83.505-72.09 100.89-123.14 35.043-102.91-6.4362-214.07-100.89-270.37-52.514-31.302-117.76-40.564-178.06-25.277-81.183 20.579-145.19 82.918-166.86 162.52-5.5757 20.478-7.445 35.423-7.445 59.52s1.8693 39.042 7.445 59.52c21.409 78.63 85.366 141.52 164.81 162.05 29.22 7.5511 66.493 9.756 93.564 5.5347z" stroke-width="1.28"/></g></svg>;
|
|
||||||
var clear_icon = <svg viewBox="0 0 478.94 479.03"><path d="M217.488 478.45c-30.264-3.146-55.348-10.265-82.714-23.477C62.54 420.1 14.214 353.763 1.824 272.463c-2.412-15.82-2.434-50.027-.043-66.058 16.004-107.32 97.008-188.28 204.71-204.6 14.33-2.172 49.054-2.447 63-.498C323.95 8.915 371.3 32.2 409.03 69.927c37.697 37.698 61.125 85.349 68.605 139.54 1.943 14.08 1.68 48.804-.478 63-6.616 43.533-24.01 83.859-50.468 117-37.556 47.046-92.812 78.608-153.26 87.54-12.553 1.855-44.144 2.671-55.936 1.445zm42.144-32.045c15.649-1.602 29.895-4.63 44.856-9.531 78.146-25.604 133.49-94.718 141.94-177.26 6.245-60.993-16.1-123.3-59.94-167.14-55.797-55.797-139.4-75.365-213.52-49.98-77.69 26.609-131.51 94.14-140.42 176.19-4.761 43.843 6.392 91.899 30.274 130.44 41.468 66.926 119.01 105.26 196.82 97.29zm-138.69-80.346c-4.096-1.784-8.225-6.874-9.022-11.123-1.676-8.935-3.495-6.761 52.877-63.221l52.17-52.25-52.17-52.25c-56.544-56.632-54.56-54.249-52.834-63.451.924-4.923 6.905-10.904 11.828-11.828 9.201-1.726 6.819-3.71 63.451 52.834l52.25 52.169 52.25-52.169c56.632-56.544 54.25-54.56 63.451-52.834 4.923.923 10.904 6.905 11.828 11.828 1.726 9.201 3.71 6.818-52.834 63.451l-52.169 52.25 52.17 52.25c56.543 56.632 54.56 54.249 52.833 63.451-.923 4.923-6.905 10.904-11.828 11.828-9.201 1.726-6.818 3.71-63.455-52.838l-52.255-52.173-51.745 51.696c-28.496 28.469-53.01 52.166-54.56 52.742-3.766 1.4-8.515 1.26-12.234-.36z"/></svg>;
|
|
||||||
|
|
||||||
function getSessionsStyleOption(type) {
|
|
||||||
return (type || "recent") + "-sessions-style";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSessionsStyle(type) {
|
|
||||||
var v = handler.get_local_option(getSessionsStyleOption(type));
|
|
||||||
if (!v) v = type == "ab" ? "list" : "tile";
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
var searchPatterns = {};
|
|
||||||
|
|
||||||
class SearchBar: Reactor.Component {
|
|
||||||
this var type = "";
|
|
||||||
|
|
||||||
function this(params) {
|
|
||||||
this.type = (params || {}).type || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
var value = searchPatterns[this.type] || "";
|
|
||||||
var me = this;
|
|
||||||
self.timer(1ms, function() { me.search_id.value = value; });
|
|
||||||
return <div .search-id>
|
|
||||||
<span .search-icon>{search_icon}</span>
|
|
||||||
<input|text @{this.search_id} novalue={translate("Search ID")} />
|
|
||||||
{value && <span .clear-input>{clear_icon}</span>}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(span.clear-input) {
|
|
||||||
this.onChange('');
|
|
||||||
}
|
|
||||||
|
|
||||||
event change $(input) (_, el) {
|
|
||||||
this.onChange(el.value.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
function onChange(v) {
|
|
||||||
searchPatterns[this.type] = v;
|
|
||||||
app.multipleSessions.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SessionStyle: Reactor.Component {
|
|
||||||
this var type = "";
|
|
||||||
|
|
||||||
function this(params) {
|
|
||||||
this.type = (params || {}).type || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
var sessionsStyle = getSessionsStyle(this.type);
|
|
||||||
return <div .sessions-tab style="margin-left: 0.5em;">
|
|
||||||
<span class={sessionsStyle == "tile" ? "active" : "inactive"}>{svg_tile}</span>
|
|
||||||
<span class={sessionsStyle != "tile" ? "active" : "inactive"}>{svg_list}</span>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(span.inactive) {
|
|
||||||
var option = getSessionsStyleOption(this.type);
|
|
||||||
var sessionsStyle = getSessionsStyle(this.type);
|
|
||||||
handler.set_option(option, sessionsStyle == "tile" ? "list" : "tile");
|
|
||||||
app.multipleSessions.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SessionList: Reactor.Component {
|
|
||||||
this var sessions = [];
|
|
||||||
this var type = "";
|
|
||||||
this var style;
|
|
||||||
|
|
||||||
function this(params) {
|
|
||||||
this.sessions = params.sessions;
|
|
||||||
this.type = params.type || "";
|
|
||||||
this.style = getSessionsStyle(this.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSessions() {
|
|
||||||
var p = searchPatterns[this.type];
|
|
||||||
if (!p) return this.sessions;
|
|
||||||
var tmp = [];
|
|
||||||
this.sessions.map(function(s) {
|
|
||||||
var name = s[4] || s.alias || s[0] || s.id || "";
|
|
||||||
if (name.indexOf(p) >= 0) tmp.push(s);
|
|
||||||
});
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
var sessions = this.getSessions();
|
|
||||||
if (sessions.length == 0) {
|
|
||||||
return <div style="margin: *"><div style="margin: *; font-size: 1.6em;">{translate("Empty")}</div></div>;
|
|
||||||
}
|
|
||||||
var me = this;
|
|
||||||
sessions = sessions.map(function(x) { return me.getSession(x); });
|
|
||||||
return <div .recent-sessions-content key={sessions.length}>
|
|
||||||
<popup>
|
|
||||||
<menu.context #remote-context>
|
|
||||||
<li #connect>{translate('Connect')}</li>
|
|
||||||
<li #transfer>{translate('Transfer File')}</li>
|
|
||||||
<li #tunnel>{translate('TCP Tunneling')}</li>
|
|
||||||
<li #rdp>RDP<EditRdpPort /></li>
|
|
||||||
<div .separator />
|
|
||||||
<li #rename>{translate('Rename')}</li>
|
|
||||||
{this.type != "fav" && <li #remove>{translate('Remove')}</li>}
|
|
||||||
{is_win && <li #shortcut>{translate('Create Desktop Shortcut')}</li>}
|
|
||||||
<li #forget-password>{translate('Unremember Password')}</li>
|
|
||||||
{(!this.type || this.type == "fav") && <li #add-fav>{translate('Add to Favorites')}</li>}
|
|
||||||
{(!this.type || this.type == "fav") && <li #remove-fav>{translate('Remove from Favorites')}</li>}
|
|
||||||
</menu>
|
|
||||||
</popup>
|
|
||||||
{sessions}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSession(s) {
|
|
||||||
var id = s[0] || s.id || "";
|
|
||||||
var username = s[1] || s.username || "";
|
|
||||||
var hostname = s[2] || s.hostname || "";
|
|
||||||
var platform = s[3] || s.platform || "";
|
|
||||||
var alias = s[4] || s.alias || "";
|
|
||||||
if (this.style == "list") {
|
|
||||||
return <div .remote-session-link .remote-session-list id={id} platform={platform} title={alias ? "ID: " + id : ""}>
|
|
||||||
<div .platform style={"background:"+string2RGB(id+platform, 0.5)}>
|
|
||||||
{platform && platformSvg(platform, "white")}
|
|
||||||
</div>
|
|
||||||
<div .name>
|
|
||||||
<div>
|
|
||||||
<div #alias .ellipsis>{alias ? alias : formatId(id)}</div>
|
|
||||||
<div .username .ellipsis>{username}@{hostname}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{svg_menu}
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
return <div .remote-session-link .remote-session id={id} platform={platform} title={alias ? "ID: " + id : ""} style={"background:"+string2RGB(id+platform, 0.5)}>
|
|
||||||
<div .platform>
|
|
||||||
{platform && platformSvg(platform, "white")}
|
|
||||||
<div .username .ellipsis>{username}@{hostname}</div>
|
|
||||||
</div>
|
|
||||||
<div .text>
|
|
||||||
<div #alias .ellipsis>{alias ? alias : formatId(id)}</div>
|
|
||||||
{svg_menu}
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event dblclick $(div.remote-session-link) (evt, me) {
|
|
||||||
createNewConnect(me.id, "connect");
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#menu) (_, me) {
|
|
||||||
var id = me.parent.parent.id;
|
|
||||||
var platform = me.parent.parent.attributes["platform"];
|
|
||||||
this.$(#rdp).style.set{
|
|
||||||
display: (platform == "Windows" && is_win) ? "block" : "none",
|
|
||||||
};
|
|
||||||
this.$(#forget-password).style.set{
|
|
||||||
display: handler.peer_has_password(id) ? "block" : "none",
|
|
||||||
};
|
|
||||||
if (!this.type || this.type == "fav") {
|
|
||||||
var in_fav = handler.get_fav().indexOf(id) >= 0;
|
|
||||||
this.$(#add-fav).style.set{
|
|
||||||
display: in_fav ? "none" : "block",
|
|
||||||
};
|
|
||||||
this.$(#remove-fav).style.set{
|
|
||||||
display: in_fav ? "block" : "none",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// https://sciter.com/forums/topic/replacecustomize-context-menu/
|
|
||||||
var menu = this.$(menu#remote-context);
|
|
||||||
menu.attributes["remote-id"] = id;
|
|
||||||
me.popup(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(menu#remote-context li) (evt, me) {
|
|
||||||
var action = me.id;
|
|
||||||
var id = me.parent.attributes["remote-id"];
|
|
||||||
if (action == "connect") {
|
|
||||||
createNewConnect(id, "connect");
|
|
||||||
} else if (action == "transfer") {
|
|
||||||
createNewConnect(id, "file-transfer");
|
|
||||||
} else if (action == "remove") {
|
|
||||||
if (!this.type) {
|
|
||||||
handler.remove_peer(id);
|
|
||||||
app.update();
|
|
||||||
}
|
|
||||||
} else if (action == "forget-password") {
|
|
||||||
handler.forget_password(id);
|
|
||||||
} else if (action == "shortcut") {
|
|
||||||
handler.create_shortcut(id);
|
|
||||||
} else if (action == "rdp") {
|
|
||||||
createNewConnect(id, "rdp");
|
|
||||||
} else if (action == "add-fav") {
|
|
||||||
var favs = handler.get_fav();
|
|
||||||
if (favs.indexOf(id) < 0) {
|
|
||||||
favs = [id].concat(favs);
|
|
||||||
handler.store_fav(favs);
|
|
||||||
}
|
|
||||||
app.multipleSessions.update();
|
|
||||||
app.update();
|
|
||||||
} else if (action == "remove-fav") {
|
|
||||||
var favs = handler.get_fav();
|
|
||||||
var i = favs.indexOf(id);
|
|
||||||
favs.splice(i, 1);
|
|
||||||
handler.store_fav(favs);
|
|
||||||
app.multipleSessions.update();
|
|
||||||
} else if (action == "tunnel") {
|
|
||||||
createNewConnect(id, "port-forward");
|
|
||||||
} else if (action == "rename") {
|
|
||||||
var old_name = handler.get_peer_option(id, "alias");
|
|
||||||
msgbox("custom-rename", "Rename", "<div .form> \
|
|
||||||
<div><input name='name' .outline-focus style='width: *; height: 23px', value='" + old_name + "' /></div> \
|
|
||||||
</div> \
|
|
||||||
", function(res=null) {
|
|
||||||
if (!res) return;
|
|
||||||
var name = (res.name || "").trim();
|
|
||||||
if (name != old_name) {
|
|
||||||
handler.set_peer_option(id, "alias", name);
|
|
||||||
}
|
|
||||||
app.update();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSessionsType() {
|
|
||||||
return handler.get_local_option("show-sessions-type");
|
|
||||||
}
|
|
||||||
|
|
||||||
class Favorites: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
var sessions = handler.get_fav().map(function(f) {
|
|
||||||
return handler.get_peer(f);
|
|
||||||
});
|
|
||||||
return <SessionList sessions={sessions} type="fav" />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MultipleSessions: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
var type = getSessionsType();
|
|
||||||
return <div style="size: *">
|
|
||||||
<div .sessions-bar>
|
|
||||||
<div style="width:*" .sessions-tab #sessions-type>
|
|
||||||
<span class={!type ? 'active' : 'inactive'}>{translate('Recent Sessions')}</span>
|
|
||||||
<span #fav class={type == "fav" ? 'active' : 'inactive'}>{translate('Favorites')}</span>
|
|
||||||
</div>
|
|
||||||
{!this.hidden && <SearchBar type={type} />}
|
|
||||||
{!this.hidden && <SessionStyle type={type} />}
|
|
||||||
</div>
|
|
||||||
{!this.hidden &&
|
|
||||||
((type == "fav" && <Favorites />) ||
|
|
||||||
<SessionList sessions={handler.get_recent_sessions()} />)}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stupidUpdate() {
|
|
||||||
/* hidden is workaround of stupid sciter bug */
|
|
||||||
this.hidden = true;
|
|
||||||
this.update();
|
|
||||||
var me = this;
|
|
||||||
self.timer(60ms, function() {
|
|
||||||
me.hidden = false;
|
|
||||||
me.update();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(div#sessions-type span.inactive) (_, el) {
|
|
||||||
handler.set_option('show-sessions-type', el.id || "");
|
|
||||||
this.stupidUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSize() {
|
|
||||||
var w = this.$(.sessions-bar).box(#width) - 220;
|
|
||||||
this.$(#sessions-type span).style.set{
|
|
||||||
"max-width": (w / 2) + "px",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
view.on("size", function() { if (app && app.multipleSessions) app.multipleSessions.onSize(); });
|
|
||||||
@@ -4,11 +4,11 @@ body {
|
|||||||
|
|
||||||
div.content {
|
div.content {
|
||||||
flow: horizontal;
|
flow: horizontal;
|
||||||
size: *;
|
size: "*";
|
||||||
}
|
}
|
||||||
|
|
||||||
div.left-panel {
|
div.left-panel {
|
||||||
size: *;
|
size: "*";
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
border-spacing: 1em;
|
border-spacing: 1em;
|
||||||
overflow-x: scroll-indicator;
|
overflow-x: scroll-indicator;
|
||||||
@@ -38,7 +38,7 @@ div.chaticon:active {
|
|||||||
div.right-panel {
|
div.right-panel {
|
||||||
background: white;
|
background: white;
|
||||||
border-left: color(border) 1px solid;
|
border-left: color(border) 1px solid;
|
||||||
size: *;
|
size: "*";
|
||||||
}
|
}
|
||||||
|
|
||||||
div.icon-and-id {
|
div.icon-and-id {
|
||||||
@@ -71,7 +71,7 @@ div.permissions > div {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.permissions icon {
|
div.permissions icon {
|
||||||
margin: *;
|
margin: "*";
|
||||||
size: 32px;
|
size: 32px;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
@@ -99,7 +99,7 @@ icon.audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.buttons {
|
div.buttons {
|
||||||
width: *;
|
width: "*";
|
||||||
border-spacing: 0.5em;
|
border-spacing: 0.5em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -120,14 +120,14 @@ button#disconnect:active {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media platform != "OSX" {
|
@media not_osx {
|
||||||
header .window-toolbar {
|
header .window-toolbar {
|
||||||
left: 40px;
|
left: 40px;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media platform == "OSX" {
|
@media is_osx {
|
||||||
header .tabs-wrapper {
|
header .tabs-wrapper {
|
||||||
margin-left: 80px;
|
margin-left: 80px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
@@ -135,13 +135,13 @@ header .tabs-wrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.tabs-wrapper {
|
div.tabs-wrapper {
|
||||||
size: *;
|
size: "*";
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.tabs {
|
div.tabs {
|
||||||
size: *;
|
size: "*";
|
||||||
flow: horizontal;
|
flow: horizontal;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -156,7 +156,7 @@ div.border-bottom {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: *;
|
width: "*";
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: color(border) 1px solid;
|
background: color(border) 1px solid;
|
||||||
}
|
}
|
||||||
@@ -213,7 +213,7 @@ div.tab-arrows {
|
|||||||
|
|
||||||
div.tab-arrows span {
|
div.tab-arrows span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: *;
|
height: "*";
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 6px 2px;
|
padding: 6px 2px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<style>
|
</style><link rel="stylesheet" href="common.css">
|
||||||
@import url(common.css);
|
</style><link rel="stylesheet" href="cm.css">
|
||||||
@import url(cm.css);
|
<script type="module" src="cm.js"></script>
|
||||||
</style>
|
|
||||||
<script type="text/tiscript">
|
|
||||||
include "common.tis";
|
|
||||||
include "cm.tis";
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<header>
|
<header>
|
||||||
<div.window-icon role="window-icon"><icon /></div>
|
<div class="window-icon" role="window-icon"><icon /></div>
|
||||||
<caption role="window-caption" />
|
<caption role="window-caption" />
|
||||||
<div.border-bottom />
|
<div class="border-bottom" />
|
||||||
<div.window-toolbar />
|
<div class="window-toolbar" />
|
||||||
<div.window-buttons />
|
<div class="window-buttons" />
|
||||||
</header>
|
</header>
|
||||||
<body #handler>
|
<body #handler>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
383
src/ui/cm.js
Normal file
383
src/ui/cm.js
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
import { handler,view,is_osx,string2RGB,adjustBorder,svg_chat,translate,ChatBox,getNowStr,setWindowButontsAndIcon,is_linux } from "./common.js";
|
||||||
|
import {$} from "@sciter";
|
||||||
|
// TODO in sciterjs window-frame
|
||||||
|
// view.windowFrame = is_osx ? #extended : #solid;
|
||||||
|
|
||||||
|
var body;
|
||||||
|
var connections = [];
|
||||||
|
var show_chat = false;
|
||||||
|
|
||||||
|
class Body extends Element {
|
||||||
|
cur = 0;
|
||||||
|
|
||||||
|
this() {
|
||||||
|
body = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (connections.length == 0) return <div />;
|
||||||
|
let c = connections[this.cur];
|
||||||
|
this.connection = c;
|
||||||
|
this.cid = c.id;
|
||||||
|
let auth = c.authorized;
|
||||||
|
let callback = (msg)=> {
|
||||||
|
this.sendMsg(msg);
|
||||||
|
};
|
||||||
|
setTimeout(adaptSize, 1);
|
||||||
|
let right_style = show_chat ? "" : "display: none";
|
||||||
|
return (<div class="content">
|
||||||
|
<div class="left-panel">
|
||||||
|
<div class="icon-and-id">
|
||||||
|
<div class="icon" style={"background: " + string2RGB(c.name, 1)}>
|
||||||
|
{c.name[0].toUpperCase()}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="id" style="font-weight: bold; font-size: 1.2em;">{c.name}</div>
|
||||||
|
<div class="id">({c.peer_id})</div>
|
||||||
|
<div style="margin-top: 1.2em">{translate('Connected')} {" "} <span id="time">{getElaspsed(c.time)}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div />
|
||||||
|
{c.is_file_transfer || c.port_forward ? "" : <div>{translate('Permissions')}</div>}
|
||||||
|
{c.is_file_transfer || c.port_forward ? "" : <div class="permissions">
|
||||||
|
<div class={!c.keyboard ? "disabled" : ""} title={translate('Allow using keyboard and mouse')}><icon class="keyboard" /></div>
|
||||||
|
<div class={!c.clipboard ? "disabled" : ""} title={translate('Allow using clipboard')}><icon class="clipboard" /></div>
|
||||||
|
<div class={!c.audio ? "disabled" : ""} title={translate('Allow hearing sound')}><icon class="audio" /></div>
|
||||||
|
</div>}
|
||||||
|
{c.port_forward ? <div>Port Forwarding: {c.port_forward}</div> : ""}
|
||||||
|
<div style="size:*"/>
|
||||||
|
<div class="buttons">
|
||||||
|
{auth ? "" : <button class="button" tabindex="-1" id="accept">{translate('Accept')}</button>}
|
||||||
|
{auth ? "" : <button class="button" tabindex="-1" class="outline" id="dismiss">{translate('Dismiss')}</button>}
|
||||||
|
{auth ? <button class="button" tabindex="-1" id="disconnect">{translate('Disconnect')}</button> : ""}
|
||||||
|
</div>
|
||||||
|
{c.is_file_transfer || c.port_forward ? "" : <div class="chaticon">{svg_chat}</div>}
|
||||||
|
</div>
|
||||||
|
<div class="right-panel" style={right_style}>
|
||||||
|
{c.is_file_transfer || c.port_forward ? "" : <ChatBox msgs={c.msgs} callback={callback} />}
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMsg(text) {
|
||||||
|
if (!text) return;
|
||||||
|
let { cid, connection } = this;
|
||||||
|
checkClickTime(function() {
|
||||||
|
connection.msgs.push({ name: "me", text: text, time: getNowStr()});
|
||||||
|
handler.xcall("send_msg",cid, text);
|
||||||
|
body.componentUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at icon.keyboard"](e) {
|
||||||
|
let { cid, connection } = this;
|
||||||
|
checkClickTime(function() {
|
||||||
|
connection.keyboard = !connection.keyboard;
|
||||||
|
body.componentUpdate();
|
||||||
|
handler.xcall("switch_permission",cid, "keyboard", connection.keyboard);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at icon.clipboard"]() {
|
||||||
|
let { cid, connection } = this;
|
||||||
|
checkClickTime(function() {
|
||||||
|
connection.clipboard = !connection.clipboard;
|
||||||
|
body.componentUpdate();
|
||||||
|
handler.xcall("switch_permission",cid, "clipboard", connection.clipboard);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at icon.audio"]() {
|
||||||
|
let { cid, connection } = this;
|
||||||
|
checkClickTime(function() {
|
||||||
|
connection.audio = !connection.audio;
|
||||||
|
body.componentUpdate();
|
||||||
|
handler.xcall("switch_permission",cid, "audio", connection.audio);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at button#accept"]() {
|
||||||
|
let { cid, connection } = this;
|
||||||
|
checkClickTime(function() {
|
||||||
|
connection.authorized = true;
|
||||||
|
body.componentUpdate();
|
||||||
|
handler.xcall("authorize",cid);
|
||||||
|
setTimeout(()=>view.state = Window.WINDOW_MINIMIZED,30);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at button#dismiss"]() {
|
||||||
|
let cid = this.cid;
|
||||||
|
checkClickTime(function() {
|
||||||
|
handler.close(cid); // TEST
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at button#disconnect"]() {
|
||||||
|
let cid = this.cid;
|
||||||
|
checkClickTime(function() {
|
||||||
|
handler.close(cid); // TEST
|
||||||
|
});
|
||||||
|
}
|
||||||
|
["on click at div.chaticon"]() {
|
||||||
|
checkClickTime(function() {
|
||||||
|
show_chat = !show_chat;
|
||||||
|
adaptSize();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$("body").content(<Body />);
|
||||||
|
|
||||||
|
var header;
|
||||||
|
|
||||||
|
class Header extends Element {
|
||||||
|
this() {
|
||||||
|
header = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let me = this;
|
||||||
|
let conn = connections[body.cur];
|
||||||
|
if (conn && conn.unreaded > 0) {
|
||||||
|
let el = this.select("#unreaded" + conn.id); // TODO select
|
||||||
|
if (el) el.style.setProperty("display","inline-block");
|
||||||
|
setTimeout(function() {
|
||||||
|
conn.unreaded = 0;
|
||||||
|
let el = this.select("#unreaded" + conn.id); // TODO
|
||||||
|
if (el) el.style.setProperty("display","none");
|
||||||
|
},300);
|
||||||
|
}
|
||||||
|
let tabs = connections.map((c, i)=> this.renderTab(c, i));
|
||||||
|
return (<div class="tabs-wrapper"><div class="tabs">
|
||||||
|
{tabs}
|
||||||
|
</div>
|
||||||
|
<div class="tab-arrows">
|
||||||
|
<span class="left-arrow"><</span>
|
||||||
|
<span class="right-arrow">></span>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTab(c, i) {
|
||||||
|
let cur = body.cur;
|
||||||
|
return (<div class={i == cur ? "active-tab tab" : "tab"}>
|
||||||
|
{c.name}
|
||||||
|
{c.unreaded > 0 ? <span class="unreaded" id={"unreaded" + c.id}>{c.unreaded}</span> : ""}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
update_cur(idx) {
|
||||||
|
checkClickTime(function(){
|
||||||
|
body.cur = idx;
|
||||||
|
update();
|
||||||
|
setTimeout(adjustHeader,1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at div.tab"] (_, me) {
|
||||||
|
let idx = me.index;
|
||||||
|
if (idx == body.cur) return;
|
||||||
|
this.update_cur(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at span.left-arrow"]() {
|
||||||
|
let cur = body.cur;
|
||||||
|
if (cur == 0) return;
|
||||||
|
this.update_cur(cur - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at span.right-arrow"]() {
|
||||||
|
let cur = body.cur;
|
||||||
|
if (cur == connections.length - 1) return;
|
||||||
|
this.update_cur(cur + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_osx) {
|
||||||
|
$("header").content(<Header />);
|
||||||
|
$("header").attributes["role"] = "window-caption"; // TODO
|
||||||
|
} else {
|
||||||
|
$("div.window-toolbar").content(<Header />);
|
||||||
|
setWindowButontsAndIcon(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkClickTime(callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
function adaptSize() {
|
||||||
|
$("div.right-panel").style.setProperty("display",show_chat ? "block" : "none");
|
||||||
|
let el = $("div.chaticon");
|
||||||
|
if (el) el.classList.toggle("active", show_chat);
|
||||||
|
let [x, y, w, h] = view.state.box("rectw", "border", "screen");
|
||||||
|
if (show_chat && w < 600) {
|
||||||
|
view.move(x - (600 - w), y, 600, h);
|
||||||
|
} else if (!show_chat && w > 450) {
|
||||||
|
view.move(x + (w - 300), y, 300, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
header.componentUpdate();
|
||||||
|
body.componentUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function bring_to_top(idx=-1) {
|
||||||
|
if (view.state == Window.WINDOW_HIDDEN || view.state == Window.WINDOW_MINIMIZED) {
|
||||||
|
if (is_linux) {
|
||||||
|
view.focus = $("body");
|
||||||
|
} else {
|
||||||
|
view.state = Window.WINDOW_SHOWN;
|
||||||
|
}
|
||||||
|
if (idx >= 0) body.cur = idx;
|
||||||
|
} else {
|
||||||
|
view.isTopmost = true; // TEST
|
||||||
|
view.isTopmost = false; // TEST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.addConnection = function(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio) {
|
||||||
|
let conn;
|
||||||
|
connections.map(function(c) {
|
||||||
|
if (c.id == id) conn = c;
|
||||||
|
});
|
||||||
|
if (conn) {
|
||||||
|
conn.authorized = authorized;
|
||||||
|
update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!name) name = "NA";
|
||||||
|
connections.push({
|
||||||
|
id: id, is_file_transfer: is_file_transfer, peer_id: peer_id,
|
||||||
|
port_forward: port_forward,
|
||||||
|
name: name, authorized: authorized, time: new Date(),
|
||||||
|
keyboard: keyboard, clipboard: clipboard, msgs: [], unreaded: 0,
|
||||||
|
audio: audio,
|
||||||
|
});
|
||||||
|
body.cur = connections.length - 1;
|
||||||
|
bring_to_top();
|
||||||
|
update();
|
||||||
|
setTimeout(adjustHeader,1);
|
||||||
|
if (authorized) {
|
||||||
|
setTimeout(()=>view.state = Window.WINDOW_MINIMIZED,3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.removeConnection = function(id) {
|
||||||
|
let i = -1;
|
||||||
|
connections.map(function(c, idx) {
|
||||||
|
if (c.id == id) i = idx;
|
||||||
|
});
|
||||||
|
connections.splice(i, 1);
|
||||||
|
if (connections.length == 0) {
|
||||||
|
handler.xcall("exit");
|
||||||
|
} else {
|
||||||
|
if (body.cur >= i && body.cur > 0) body.cur -= 1;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.newMessage = function(id, text) {
|
||||||
|
let idx = -1;
|
||||||
|
connections.map(function(c, i) {
|
||||||
|
if (c.id == id) idx = i;
|
||||||
|
});
|
||||||
|
let conn = connections[idx];
|
||||||
|
if (!conn) return;
|
||||||
|
conn.msgs.push({name: conn.name, text: text, time: getNowStr()});
|
||||||
|
bring_to_top(idx);
|
||||||
|
if (idx == body.cur) show_chat = true;
|
||||||
|
conn.unreaded += 1;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.awake = function() {
|
||||||
|
view.state = Window.WINDOW_SHOWN;
|
||||||
|
view.focus = $("body");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST
|
||||||
|
// view << event statechange {
|
||||||
|
// adjustBorder();
|
||||||
|
// }
|
||||||
|
view.on("statechange",()=>{
|
||||||
|
adjustBorder();
|
||||||
|
})
|
||||||
|
|
||||||
|
document.on("ready",()=>{
|
||||||
|
adjustBorder();
|
||||||
|
let [sw, sh] = view.screenBox("workarea", "dimension");
|
||||||
|
let w = 300;
|
||||||
|
let h = 400;
|
||||||
|
view.move(sw - w, 0, w, h);
|
||||||
|
})
|
||||||
|
|
||||||
|
document.on("unloadequest",(evt)=>{
|
||||||
|
view.state = Window.WINDOW_HIDDEN;
|
||||||
|
console.log("cm unloadequest")
|
||||||
|
evt.preventDefault(); // prevent unloading TEST
|
||||||
|
})
|
||||||
|
|
||||||
|
function getElaspsed(time) {
|
||||||
|
// let now = new Date();
|
||||||
|
// let seconds = Date.diff(time, now, #seconds);
|
||||||
|
// let hours = seconds / 3600;
|
||||||
|
// let days = hours / 24;
|
||||||
|
// hours = hours % 24;
|
||||||
|
// let minutes = seconds % 3600 / 60;
|
||||||
|
// seconds = seconds % 60;
|
||||||
|
// let out = String.printf("%02d:%02d:%02d", hours, minutes, seconds);
|
||||||
|
// if (days > 0) {
|
||||||
|
// out = String.printf("%d day%s %s", days, days > 1 ? "s" : "", out);
|
||||||
|
// }
|
||||||
|
let out = "TIME TODO" + new Date(); // TODO
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateTime
|
||||||
|
setInterval(function() {
|
||||||
|
let el = $("#time");
|
||||||
|
if (el) {
|
||||||
|
let c = connections[body.cur];
|
||||||
|
if (c) {
|
||||||
|
el.text = getElaspsed(c.time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},1000);
|
||||||
|
|
||||||
|
|
||||||
|
function adjustHeader() {
|
||||||
|
let hw = $("header").state.box("width");
|
||||||
|
let tabswrapper = $("div.tabs-wrapper");
|
||||||
|
let tabs = $("div.tabs");
|
||||||
|
let arrows = $("div.tab-arrows");
|
||||||
|
if (!arrows) return;
|
||||||
|
let n = connections.length;
|
||||||
|
let wtab = 80;
|
||||||
|
let max = hw - 98;
|
||||||
|
let need_width = n * wtab + 2; // include border of active tab
|
||||||
|
if (need_width < max) {
|
||||||
|
arrows.style.setProperty("display","none");
|
||||||
|
tabs.style.setProperty("width",need_width);
|
||||||
|
tabs.style.setProperty("margin-left",0);
|
||||||
|
tabswrapper.style.setProperty("width",need_width);
|
||||||
|
} else {
|
||||||
|
let margin = (body.cur + 1) * wtab - max + 30;
|
||||||
|
if (margin < 0) margin = 0;
|
||||||
|
arrows.style.setProperty("display","block");
|
||||||
|
tabs.style.setProperty("width",(max - 20 + margin) + 'px');
|
||||||
|
tabs.style.setProperty("margin-left",-margin + 'px');
|
||||||
|
tabswrapper.style.setProperty("width",(max + 10) + 'px');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.onsizechange = ()=>{
|
||||||
|
console.log("cm onsizechange");
|
||||||
|
adjustHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
// handler.addConnection(0, false, 0, "", "test1", true, false, false, false);
|
||||||
|
// handler.addConnection(1, false, 0, "", "test2--------", true, false, false, false);
|
||||||
|
// handler.addConnection(2, false, 0, "", "test3", true, false, false, false);
|
||||||
|
// handler.newMessage(0, 'h');
|
||||||
@@ -91,13 +91,13 @@ impl ConnectionManager {
|
|||||||
clipboard,
|
clipboard,
|
||||||
audio
|
audio
|
||||||
),
|
),
|
||||||
);
|
); // TODO
|
||||||
self.write().unwrap().senders.insert(id, tx);
|
self.write().unwrap().senders.insert(id, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_connection(&self, id: i32) {
|
fn remove_connection(&self, id: i32) {
|
||||||
self.write().unwrap().senders.remove(&id);
|
self.write().unwrap().senders.remove(&id);
|
||||||
self.call("removeConnection", &make_args!(id));
|
self.call("removeConnection", &make_args!(id)); // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_data(
|
async fn handle_data(
|
||||||
|
|||||||
401
src/ui/cm.tis
401
src/ui/cm.tis
@@ -1,401 +0,0 @@
|
|||||||
view.windowFrame = is_osx ? #extended : #solid;
|
|
||||||
|
|
||||||
var body;
|
|
||||||
var connections = [];
|
|
||||||
var show_chat = false;
|
|
||||||
|
|
||||||
class Body: Reactor.Component
|
|
||||||
{
|
|
||||||
this var cur = 0;
|
|
||||||
|
|
||||||
function this() {
|
|
||||||
body = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
if (connections.length == 0) return <div />;
|
|
||||||
var c = connections[this.cur];
|
|
||||||
this.connection = c;
|
|
||||||
this.cid = c.id;
|
|
||||||
var auth = c.authorized;
|
|
||||||
var me = this;
|
|
||||||
var callback = function(msg) {
|
|
||||||
me.sendMsg(msg);
|
|
||||||
};
|
|
||||||
self.timer(1ms, adaptSize);
|
|
||||||
var right_style = show_chat ? "" : "display: none";
|
|
||||||
return <div .content>
|
|
||||||
<div .left-panel>
|
|
||||||
<div .icon-and-id>
|
|
||||||
<div .icon style={"background: " + string2RGB(c.name, 1)}>
|
|
||||||
{c.name[0].toUpperCase()}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div .id style="font-weight: bold; font-size: 1.2em;">{c.name}</div>
|
|
||||||
<div .id>({c.peer_id})</div>
|
|
||||||
<div style="margin-top: 1.2em">{translate('Connected')} {" "} <span #time>{getElaspsed(c.time)}</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div />
|
|
||||||
{c.is_file_transfer || c.port_forward ? "" : <div>{translate('Permissions')}</div>}
|
|
||||||
{c.is_file_transfer || c.port_forward ? "" : <div .permissions>
|
|
||||||
<div class={!c.keyboard ? "disabled" : ""} title={translate('Allow using keyboard and mouse')}><icon .keyboard /></div>
|
|
||||||
<div class={!c.clipboard ? "disabled" : ""} title={translate('Allow using clipboard')}><icon .clipboard /></div>
|
|
||||||
<div class={!c.audio ? "disabled" : ""} title={translate('Allow hearing sound')}><icon .audio /></div>
|
|
||||||
</div>}
|
|
||||||
{c.port_forward ? <div>Port Forwarding: {c.port_forward}</div> : ""}
|
|
||||||
<div style="size:*"/>
|
|
||||||
<div .buttons>
|
|
||||||
{auth ? "" : <button .button tabindex="-1" #accept>{translate('Accept')}</button>}
|
|
||||||
{auth ? "" : <button .button tabindex="-1" .outline #dismiss>{translate('Dismiss')}</button>}
|
|
||||||
{auth ? <button .button tabindex="-1" #disconnect>{translate('Disconnect')}</button> : ""}
|
|
||||||
</div>
|
|
||||||
{c.is_file_transfer || c.port_forward ? "" : <div .chaticon>{svg_chat}</div>}
|
|
||||||
</div>
|
|
||||||
<div .right-panel style={right_style}>
|
|
||||||
{c.is_file_transfer || c.port_forward ? "" : <ChatBox msgs={c.msgs} callback={callback} />}
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMsg(text) {
|
|
||||||
if (!text) return;
|
|
||||||
var { cid, connection } = this;
|
|
||||||
checkClickTime(function() {
|
|
||||||
connection.msgs.push({ name: "me", text: text, time: getNowStr()});
|
|
||||||
handler.send_msg(cid, text);
|
|
||||||
body.update();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(icon.keyboard) (e) {
|
|
||||||
var { cid, connection } = this;
|
|
||||||
checkClickTime(function() {
|
|
||||||
connection.keyboard = !connection.keyboard;
|
|
||||||
body.update();
|
|
||||||
handler.switch_permission(cid, "keyboard", connection.keyboard);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(icon.clipboard) {
|
|
||||||
var { cid, connection } = this;
|
|
||||||
checkClickTime(function() {
|
|
||||||
connection.clipboard = !connection.clipboard;
|
|
||||||
body.update();
|
|
||||||
handler.switch_permission(cid, "clipboard", connection.clipboard);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(icon.audio) {
|
|
||||||
var { cid, connection } = this;
|
|
||||||
checkClickTime(function() {
|
|
||||||
connection.audio = !connection.audio;
|
|
||||||
body.update();
|
|
||||||
handler.switch_permission(cid, "audio", connection.audio);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(button#accept) {
|
|
||||||
var { cid, connection } = this;
|
|
||||||
checkClickTime(function() {
|
|
||||||
connection.authorized = true;
|
|
||||||
body.update();
|
|
||||||
handler.authorize(cid);
|
|
||||||
self.timer(30ms, function() {
|
|
||||||
view.windowState = View.WINDOW_MINIMIZED;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(button#dismiss) {
|
|
||||||
var cid = this.cid;
|
|
||||||
checkClickTime(function() {
|
|
||||||
handler.close(cid);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(button#disconnect) {
|
|
||||||
var cid = this.cid;
|
|
||||||
checkClickTime(function() {
|
|
||||||
handler.close(cid);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(body).content(<Body />);
|
|
||||||
|
|
||||||
var header;
|
|
||||||
|
|
||||||
class Header: Reactor.Component
|
|
||||||
{
|
|
||||||
function this() {
|
|
||||||
header = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
var me = this;
|
|
||||||
var conn = connections[body.cur];
|
|
||||||
if (conn && conn.unreaded > 0) {;
|
|
||||||
var el = me.select("#unreaded" + conn.id);
|
|
||||||
if (el) el.style.set {
|
|
||||||
display: "inline-block",
|
|
||||||
};
|
|
||||||
self.timer(300ms, function() {
|
|
||||||
conn.unreaded = 0;
|
|
||||||
var el = me.select("#unreaded" + conn.id);
|
|
||||||
if (el) el.style.set {
|
|
||||||
display: "none",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var tabs = connections.map(function(c, i) { return me.renderTab(c, i) });
|
|
||||||
return <div .tabs-wrapper><div .tabs>
|
|
||||||
{tabs}
|
|
||||||
</div>
|
|
||||||
<div .tab-arrows>
|
|
||||||
<span .left-arrow><</span>
|
|
||||||
<span .right-arrow>></span>
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTab(c, i) {
|
|
||||||
var cur = body.cur;
|
|
||||||
return <div class={i == cur ? "active-tab tab" : "tab"}>
|
|
||||||
{c.name}
|
|
||||||
{c.unreaded > 0 ? <span .unreaded id={"unreaded" + c.id}>{c.unreaded}</span> : ""}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_cur(idx) {
|
|
||||||
checkClickTime(function() {
|
|
||||||
body.cur = idx;
|
|
||||||
update();
|
|
||||||
self.timer(1ms, adjustHeader);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(div.tab) (_, me) {
|
|
||||||
var idx = me.index;
|
|
||||||
if (idx == body.cur) return;
|
|
||||||
this.update_cur(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(span.left-arrow) {
|
|
||||||
var cur = body.cur;
|
|
||||||
if (cur == 0) return;
|
|
||||||
this.update_cur(cur - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(span.right-arrow) {
|
|
||||||
var cur = body.cur;
|
|
||||||
if (cur == connections.length - 1) return;
|
|
||||||
this.update_cur(cur + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_osx) {
|
|
||||||
$(header).content(<Header />);
|
|
||||||
$(header).attributes["role"] = "window-caption";
|
|
||||||
} else {
|
|
||||||
$(div.window-toolbar).content(<Header />);
|
|
||||||
setWindowButontsAndIcon(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(div.chaticon) {
|
|
||||||
checkClickTime(function() {
|
|
||||||
show_chat = !show_chat;
|
|
||||||
adaptSize();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkClickTime(callback) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
function adaptSize() {
|
|
||||||
$(div.right-panel).style.set {
|
|
||||||
display: show_chat ? "block" : "none",
|
|
||||||
};
|
|
||||||
var el = $(div.chaticon);
|
|
||||||
if (el) el.attributes.toggleClass("active", show_chat);
|
|
||||||
var (x, y, w, h) = view.box(#rectw, #border, #screen);
|
|
||||||
if (show_chat && w < 600) {
|
|
||||||
view.move(x - (600 - w), y, 600, h);
|
|
||||||
} else if (!show_chat && w > 450) {
|
|
||||||
view.move(x + (w - 300), y, 300, h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
header.update();
|
|
||||||
body.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function bring_to_top(idx=-1) {
|
|
||||||
if (view.windowState == View.WINDOW_HIDDEN || view.windowState == View.WINDOW_MINIMIZED) {
|
|
||||||
if (is_linux) {
|
|
||||||
view.focus = self;
|
|
||||||
} else {
|
|
||||||
view.windowState = View.WINDOW_SHOWN;
|
|
||||||
}
|
|
||||||
if (idx >= 0) body.cur = idx;
|
|
||||||
} else {
|
|
||||||
view.windowTopmost = true;
|
|
||||||
view.windowTopmost = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.addConnection = function(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio) {
|
|
||||||
var conn;
|
|
||||||
connections.map(function(c) {
|
|
||||||
if (c.id == id) conn = c;
|
|
||||||
});
|
|
||||||
if (conn) {
|
|
||||||
conn.authorized = authorized;
|
|
||||||
update();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!name) name = "NA";
|
|
||||||
connections.push({
|
|
||||||
id: id, is_file_transfer: is_file_transfer, peer_id: peer_id,
|
|
||||||
port_forward: port_forward,
|
|
||||||
name: name, authorized: authorized, time: new Date(),
|
|
||||||
keyboard: keyboard, clipboard: clipboard, msgs: [], unreaded: 0,
|
|
||||||
audio: audio,
|
|
||||||
});
|
|
||||||
body.cur = connections.length - 1;
|
|
||||||
bring_to_top();
|
|
||||||
update();
|
|
||||||
self.timer(1ms, adjustHeader);
|
|
||||||
if (authorized) {
|
|
||||||
self.timer(3s, function() {
|
|
||||||
view.windowState = View.WINDOW_MINIMIZED;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.removeConnection = function(id) {
|
|
||||||
var i = -1;
|
|
||||||
connections.map(function(c, idx) {
|
|
||||||
if (c.id == id) i = idx;
|
|
||||||
});
|
|
||||||
connections.splice(i, 1);
|
|
||||||
if (connections.length == 0) {
|
|
||||||
handler.exit();
|
|
||||||
} else {
|
|
||||||
if (body.cur >= i && body.cur > 0) body.cur -= 1;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.newMessage = function(id, text) {
|
|
||||||
var idx = -1;
|
|
||||||
connections.map(function(c, i) {
|
|
||||||
if (c.id == id) idx = i;
|
|
||||||
});
|
|
||||||
var conn = connections[idx];
|
|
||||||
if (!conn) return;
|
|
||||||
conn.msgs.push({name: conn.name, text: text, time: getNowStr()});
|
|
||||||
bring_to_top(idx);
|
|
||||||
if (idx == body.cur) show_chat = true;
|
|
||||||
conn.unreaded += 1;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.awake = function() {
|
|
||||||
view.windowState = View.WINDOW_SHOWN;
|
|
||||||
view.focus = self;
|
|
||||||
}
|
|
||||||
|
|
||||||
view << event statechange {
|
|
||||||
adjustBorder();
|
|
||||||
}
|
|
||||||
|
|
||||||
function self.ready() {
|
|
||||||
adjustBorder();
|
|
||||||
var (sw, sh) = view.screenBox(#workarea, #dimension);
|
|
||||||
var w = 300;
|
|
||||||
var h = 400;
|
|
||||||
view.move(sw - w, 0, w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElaspsed(time) {
|
|
||||||
var now = new Date();
|
|
||||||
var seconds = Date.diff(time, now, #seconds);
|
|
||||||
var hours = seconds / 3600;
|
|
||||||
var days = hours / 24;
|
|
||||||
hours = hours % 24;
|
|
||||||
var minutes = seconds % 3600 / 60;
|
|
||||||
seconds = seconds % 60;
|
|
||||||
var out = String.printf("%02d:%02d:%02d", hours, minutes, seconds);
|
|
||||||
if (days > 0) {
|
|
||||||
out = String.printf("%d day%s %s", days, days > 1 ? "s" : "", out);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTime() {
|
|
||||||
self.timer(1s, function() {
|
|
||||||
var el = $(#time);
|
|
||||||
if (el) {
|
|
||||||
var c = connections[body.cur];
|
|
||||||
if (c) {
|
|
||||||
el.text = getElaspsed(c.time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateTime();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTime();
|
|
||||||
|
|
||||||
function self.closing() {
|
|
||||||
view.windowState = View.WINDOW_HIDDEN;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function adjustHeader() {
|
|
||||||
var hw = $(header).box(#width);
|
|
||||||
var tabswrapper = $(div.tabs-wrapper);
|
|
||||||
var tabs = $(div.tabs);
|
|
||||||
var arrows = $(div.tab-arrows);
|
|
||||||
if (!arrows) return;
|
|
||||||
var n = connections.length;
|
|
||||||
var wtab = 80;
|
|
||||||
var max = hw - 98;
|
|
||||||
var need_width = n * wtab + 2; // include border of active tab
|
|
||||||
if (need_width < max) {
|
|
||||||
arrows.style.set {
|
|
||||||
display: "none",
|
|
||||||
};
|
|
||||||
tabs.style.set {
|
|
||||||
width: need_width,
|
|
||||||
margin-left: 0,
|
|
||||||
};
|
|
||||||
tabswrapper.style.set {
|
|
||||||
width: need_width,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
var margin = (body.cur + 1) * wtab - max + 30;
|
|
||||||
if (margin < 0) margin = 0;
|
|
||||||
arrows.style.set {
|
|
||||||
display: "block",
|
|
||||||
};
|
|
||||||
tabs.style.set {
|
|
||||||
width: (max - 20 + margin) + 'px',
|
|
||||||
margin-left: -margin + 'px'
|
|
||||||
};
|
|
||||||
tabswrapper.style.set {
|
|
||||||
width: (max + 10) + 'px',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
view.on("size", adjustHeader);
|
|
||||||
|
|
||||||
// handler.addConnection(0, false, 0, "", "test1", true, false, false, false);
|
|
||||||
// handler.addConnection(1, false, 0, "", "test2--------", true, false, false, false);
|
|
||||||
// handler.addConnection(2, false, 0, "", "test3", true, false, false, false);
|
|
||||||
// handler.newMessage(0, 'h');
|
|
||||||
379
src/ui/common.js
Normal file
379
src/ui/common.js
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
|
||||||
|
export const view = Window.this;
|
||||||
|
export const handler = document.$("#handler") || view;
|
||||||
|
|
||||||
|
try { view.windowIcon = document.url(handler.xcall("get_icon")); } catch (e) { }
|
||||||
|
|
||||||
|
export const OS = view.mediaVar("platform");
|
||||||
|
export const is_osx = OS == "OSX";
|
||||||
|
export const is_win = OS == "Windows";
|
||||||
|
export const is_linux = OS == "Linux";
|
||||||
|
|
||||||
|
view.mediaVar("is_osx", is_osx);
|
||||||
|
view.mediaVar("not_osx", !is_osx);
|
||||||
|
handler.is_port_forward = false;
|
||||||
|
handler.is_file_transfer = false;
|
||||||
|
export var is_xfce = false;
|
||||||
|
try { is_xfce = handler.xcall("is_xfce"); } catch (e) { }
|
||||||
|
|
||||||
|
|
||||||
|
export function translate(name) {
|
||||||
|
try {
|
||||||
|
return handler.xcall("t", name);
|
||||||
|
} catch (_) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hashCode(str) {
|
||||||
|
let hash = 160 << 16 + 114 << 8 + 91;
|
||||||
|
for (let i = 0; i < str.length; i += 1) {
|
||||||
|
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
return hash % 16777216;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function intToRGB(i, a = 1) {
|
||||||
|
return `rgba(${((i >> 16) & 0xFF)}, ${((i >> 8) & 0x7F)},${(i & 0xFF)},${a})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function string2RGB(s, a = 1) {
|
||||||
|
return intToRGB(hashCode(s), a);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTime() {
|
||||||
|
return new Date().valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function platformSvg(platform, color) {
|
||||||
|
platform = (platform || "").toLowerCase();
|
||||||
|
if (platform == "linux") {
|
||||||
|
return (<svg viewBox="0 0 256 256">
|
||||||
|
<g transform="translate(0 256) scale(.1 -.1)" fill={color}>
|
||||||
|
<path d="m1215 2537c-140-37-242-135-286-278-23-75-23-131 1-383l18-200-54-60c-203-224-383-615-384-831v-51l-66-43c-113-75-194-199-194-300 0-110 99-234 244-305 103-50 185-69 296-69 100 0 156 14 211 54 26 18 35 19 78 10 86-18 233-24 335-12 85 10 222 38 269 56 9 4 19-7 29-35 20-50 52-64 136-57 98 8 180 52 282 156 124 125 180 244 180 380 0 80-28 142-79 179l-36 26 4 119c5 175-22 292-105 460-74 149-142 246-286 409-43 49-78 92-78 97 0 4-7 52-15 107-8 54-19 140-24 189-13 121-41 192-103 260-95 104-248 154-373 122zm172-112c62-19 134-80 163-140 15-31 28-92 41-193 27-214 38-276 57-304 9-14 59-74 111-134 92-106 191-246 236-334 69-137 115-339 101-451l-7-55-71 10c-100 13-234-5-265-36-54-55-85-207-82-412l1-141-51-17c-104-34-245-51-380-45-69 3-142 10-162 16-32 10-37 17-53 68-23 72-87 201-136 273-80 117-158 188-237 215-37 13-37 13-34 61 13 211 182 555 373 759 57 62 58 63 58 121 0 33-9 149-19 259-21 224-18 266 26 347 67 122 193 174 330 133zm687-1720c32-9 71-25 87-36 60-42 59-151-4-274-59-119-221-250-317-257-34-3-35-2-48 47-18 65-20 329-3 413 16 83 29 110 55 115 51 10 177 6 230-8zm-1418-80c79-46 187-195 247-340 41-99 43-121 12-141-39-25-148-30-238-10-142 32-264 112-307 202-20 41-21 50-10 87 24 83 102 166 192 207 54 25 53 25 104-5z" />
|
||||||
|
<path d="m1395 1945c-92-16-220-52-256-70-28-15-29-18-29-89 0-247 165-397 345-312 60 28 77 46 106 111 54 123 0 378-80 374-9 0-47-7-86-14zm74-156c15-69 14-112-5-159s-55-70-111-70c-48 0-78 20-102 68-15 29-41 131-41 159 0 9 230 63 242 57 3-2 11-27 17-55z" />
|
||||||
|
</g>
|
||||||
|
</svg>);
|
||||||
|
}
|
||||||
|
if (platform == "mac os") {
|
||||||
|
return (<svg viewBox="0 0 384 512">
|
||||||
|
<path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z" fill={color} />
|
||||||
|
</svg>);
|
||||||
|
}
|
||||||
|
return (<svg viewBox="0 0 448 512">
|
||||||
|
<path d="M0 93.7l183.6-25.3v177.4H0V93.7zm0 324.6l183.6 25.3V268.4H0v149.9zm203.8 28L448 480V268.4H203.8v177.9zm0-380.6v180.1H448V32L203.8 65.7z" fill={color} />
|
||||||
|
</svg>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function centerize(w, h) {
|
||||||
|
let [sx, sy, sw, sh] = view.screenBox("workarea", "rectw");
|
||||||
|
if (w > sw) w = sw;
|
||||||
|
if (h > sh) h = sh;
|
||||||
|
const x = (sx + sw - w) / 2;
|
||||||
|
const y = (sy + sh - h) / 2;
|
||||||
|
view.move(x, y, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO CSS
|
||||||
|
export function setWindowButontsAndIcon(only_min = false) {
|
||||||
|
if (only_min) {
|
||||||
|
document.$("div.window-buttons").content(
|
||||||
|
<div>
|
||||||
|
<button class="window minimize" id="button-window" tabindex="-1" role="window-minimize" ><div /></button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
document.$("div.window-buttons").content(<div>
|
||||||
|
<button class="window minimize" tabindex="-1" role="window-minimize" ><div /></button>
|
||||||
|
<button class="window maximize" tabindex="-1" role="window-maximize" ><div /></button>
|
||||||
|
<button class="window close" tabindex="-1" role="window-close" ><div /></button>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
document.$("div.window-icon>icon").style.setProperty(
|
||||||
|
"background-image", `url(${handler.xcall("get_icon")})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function adjustBorder() {
|
||||||
|
if (is_osx) {
|
||||||
|
let headerStyle = document.$("header").style;
|
||||||
|
if (view.state == Window.WINDOW_FULL_SCREEN) {
|
||||||
|
headerStyle.setProperty("display", "none",);
|
||||||
|
} else {
|
||||||
|
headerStyle.setProperty("display", "block",);
|
||||||
|
headerStyle.setProperty("padding", "0",);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (view.state == Window.WINDOW_MAXIMIZED) {
|
||||||
|
document.style.setProperty("border", "window-frame-width solid transparent");
|
||||||
|
} else if (view.state == Window.WINDOW_FULL_SCREEN) {
|
||||||
|
document.style.setProperty("border", "none");
|
||||||
|
} else {
|
||||||
|
document.style.setProperty("border", "black solid 1px");
|
||||||
|
}
|
||||||
|
let el = document.$("button#maximize");
|
||||||
|
if (el) el.classList.toggle("restore", view.state == Window.WINDOW_MAXIMIZED);
|
||||||
|
el = document.$("span#fullscreen");
|
||||||
|
if (el) el.classList.toggle("active", view.state == Window.WINDOW_FULL_SCREEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const svg_checkmark = (<svg viewBox="0 0 492 492"><path d="M484 105l-16-17a27 27 0 00-38 0L204 315 62 173c-5-5-12-7-19-7s-14 2-19 7L8 189a27 27 0 000 38l160 160v1l16 16c5 5 12 8 19 8 8 0 14-3 20-8l16-16v-1l245-244a27 27 0 000-38z" /></svg>);
|
||||||
|
export const svg_edit = (<svg id="edit" viewBox="0 0 384 384">
|
||||||
|
<path d="M0 304v80h80l236-236-80-80zM378 56L328 6c-8-8-22-8-30 0l-39 39 80 80 39-39c8-8 8-22 0-30z" />
|
||||||
|
</svg>);
|
||||||
|
export const svg_eye = (<svg viewBox="0 0 469.33 469.33">
|
||||||
|
<path d="m234.67 170.67c-35.307 0-64 28.693-64 64s28.693 64 64 64 64-28.693 64-64-28.694-64-64-64z" />
|
||||||
|
<path d="m234.67 74.667c-106.67 0-197.76 66.346-234.67 160 36.907 93.653 128 160 234.67 160 106.77 0 197.76-66.347 234.67-160-36.907-93.654-127.89-160-234.67-160zm0 266.67c-58.88 0-106.67-47.787-106.67-106.67s47.787-106.67 106.67-106.67 106.67 47.787 106.67 106.67-47.787 106.67-106.67 106.67z" />
|
||||||
|
</svg>);
|
||||||
|
export const svg_send = (<svg viewBox="0 0 448 448">
|
||||||
|
<polygon points="0.213 32 0 181.33 320 224 0 266.67 0.213 416 448 224" />
|
||||||
|
</svg>);
|
||||||
|
export const svg_chat = (<svg viewBox="0 0 511.07 511.07">
|
||||||
|
<path d="m74.39 480.54h-36.213l25.607-25.607c13.807-13.807 22.429-31.765 24.747-51.246-36.029-23.644-62.375-54.751-76.478-90.425-14.093-35.647-15.864-74.888-5.121-113.48 12.89-46.309 43.123-88.518 85.128-118.85 45.646-32.963 102.47-50.387 164.33-50.387 77.927 0 143.61 22.389 189.95 64.745 41.744 38.159 64.734 89.63 64.734 144.93 0 26.868-5.471 53.011-16.26 77.703-11.165 25.551-27.514 48.302-48.593 67.619-46.399 42.523-112.04 65-189.83 65-28.877 0-59.01-3.855-85.913-10.929-25.465 26.123-59.972 40.929-96.086 40.929zm182-420c-124.04 0-200.15 73.973-220.56 147.28-19.284 69.28 9.143 134.74 76.043 175.12l7.475 4.511-0.23 8.727c-0.456 17.274-4.574 33.912-11.945 48.952 17.949-6.073 34.236-17.083 46.99-32.151l6.342-7.493 9.405 2.813c26.393 7.894 57.104 12.241 86.477 12.241 154.37 0 224.68-93.473 224.68-180.32 0-46.776-19.524-90.384-54.976-122.79-40.713-37.216-99.397-56.888-169.71-56.888z" />
|
||||||
|
</svg>);
|
||||||
|
|
||||||
|
export function scrollToBottom(el) {
|
||||||
|
// TEST .box()
|
||||||
|
let y = el.state.box("height", "content") - el.state.box("height", "client");
|
||||||
|
el.scrollTo(0, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNowStr() {
|
||||||
|
let now = new Date();
|
||||||
|
return String.printf("%02d:%02d:%02d", now.hour, now.minute, now.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************** start of chatbox ****************************************/
|
||||||
|
export class ChatBox extends Element {
|
||||||
|
msgs = [];
|
||||||
|
callback;
|
||||||
|
|
||||||
|
this(props) {
|
||||||
|
if (props) {
|
||||||
|
this.msgs = props.msgs || [];
|
||||||
|
this.callback = props.callback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMsg(msg) {
|
||||||
|
let cls = msg.name == "me" ? "right-side msg" : "left-side msg";
|
||||||
|
return (
|
||||||
|
<div class={cls}>
|
||||||
|
{msg.name == "me" ?
|
||||||
|
<div class="time">{msg.time + " "} me</div> :
|
||||||
|
<div class="name">{msg.name} {" " + msg.time}</div>
|
||||||
|
}
|
||||||
|
<div class="text">{msg.text}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let msgs = this.msgs.map((msg) => this.renderMsg(msg));
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollToBottom(this.msgs);
|
||||||
|
}, 1);
|
||||||
|
// TODO @{this.msgs} in TIS:<htmlarea spellcheck="false" readonly .msgs @{this.msgs} >
|
||||||
|
return (<div class="msgbox">
|
||||||
|
<htmlarea spellcheck="false" readonly class="msg" >
|
||||||
|
{msgs}
|
||||||
|
</htmlarea>
|
||||||
|
<div class="send">
|
||||||
|
<input type="text" class="outline-focus" />
|
||||||
|
<span>{svg_send}</span>
|
||||||
|
</div>
|
||||||
|
</div >);
|
||||||
|
}
|
||||||
|
|
||||||
|
send() {
|
||||||
|
let el = this.$("input");
|
||||||
|
let value = (el.value || "").trim();
|
||||||
|
el.value = "";
|
||||||
|
if (!value) return;
|
||||||
|
if (this.callback) this.callback(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on keydown at input"](evt) {
|
||||||
|
// TODO is shortcutKey useless?
|
||||||
|
if (!evt.shortcutKey) {
|
||||||
|
// TODO TEST Windows/Mac
|
||||||
|
if (evt.code == "KeyRETURN") {
|
||||||
|
this.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at div.send span"](evt) {
|
||||||
|
this.send();
|
||||||
|
view.focus = this.$("input");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/******************** end of chatbox ****************************************/
|
||||||
|
|
||||||
|
/******************** start of msgbox ****************************************/
|
||||||
|
var remember_password = false;
|
||||||
|
var msgbox_params;
|
||||||
|
function getMsgboxParams() {
|
||||||
|
return msgbox_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tmp workaround https://sciter.com/forums/topic/menu-not-be-hidden-when-open-dialog-on-linux/
|
||||||
|
export function msgbox(type, title, text, callback = null, height = 180, width = 500, retry = 0, contentStyle = "") {
|
||||||
|
if (is_linux) { // fix menu not hidden issue
|
||||||
|
setTimeout(() => msgbox_(type, title, text, callback, height, width, retry, contentStyle), 1);
|
||||||
|
} else {
|
||||||
|
msgbox_(type, title, text, callback, height, width, retry, contentStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function msgbox_(type, title, text, callback, height, width, retry, contentStyle) {
|
||||||
|
let has_msgbox = msgbox_params != null;
|
||||||
|
if (!has_msgbox && !type) return;
|
||||||
|
let remember = false;
|
||||||
|
try {
|
||||||
|
remember = handler.xcall("get_remember");
|
||||||
|
} catch (e) { }
|
||||||
|
msgbox_params = {
|
||||||
|
remember: remember, type: type, text: text, title: title,
|
||||||
|
getParams: getMsgboxParams,
|
||||||
|
callback: callback, translate: translate,
|
||||||
|
retry: retry, contentStyle: contentStyle,
|
||||||
|
};
|
||||||
|
if (has_msgbox) return;
|
||||||
|
let dialog = {
|
||||||
|
client: true,
|
||||||
|
parameters: msgbox_params,
|
||||||
|
width: width + (is_xfce ? 50 : 0),
|
||||||
|
height: height + (is_xfce ? 50 : 0),
|
||||||
|
};
|
||||||
|
let html = handler.xcall("get_msgbox");
|
||||||
|
if (html) dialog.html = html;
|
||||||
|
else dialog.url = document.url("msgbox.html");
|
||||||
|
let res = view.modal(dialog);
|
||||||
|
msgbox_params = null;
|
||||||
|
console.log(`msgbox return, type: ${type}, res: ${res}`);
|
||||||
|
if (type.indexOf("custom") >= 0) {
|
||||||
|
//
|
||||||
|
} else if (!res) {
|
||||||
|
if (!handler.is_port_forward) view.close();
|
||||||
|
} else if (res == "!alive") {
|
||||||
|
// do nothing
|
||||||
|
} else if (res.type == "input-password") {
|
||||||
|
if (!handler.is_port_forward) msgbox("connecting", "Connecting...", "Logging in...");
|
||||||
|
handler.login(res.password, res.remember);
|
||||||
|
if (!is_port_forward) msgbox("connecting", "Connecting...", "Logging in...");
|
||||||
|
} else if (res.reconnect) {
|
||||||
|
if (!handler.is_port_forward) connecting();
|
||||||
|
handler.reconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function connecting() {
|
||||||
|
handler.msgbox("connecting", "Connecting...", "Connection in progress. Please wait.");
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.msgbox = function (type, title, text, retry = 0) {
|
||||||
|
setTimeout(() => msgbox(type, title, text, null, 180, 500, retry), 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
var reconnectTimeout = 1;
|
||||||
|
handler.msgbox_retry = function (type, title, text, hasRetry) {
|
||||||
|
handler.msgbox(type, title, text, hasRetry ? reconnectTimeout : 0);
|
||||||
|
if (hasRetry) {
|
||||||
|
reconnectTimeout *= 2;
|
||||||
|
} else {
|
||||||
|
reconnectTimeout = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/******************** end of msgbox ****************************************/
|
||||||
|
// TODO Progress
|
||||||
|
// function Progress()
|
||||||
|
// {
|
||||||
|
// var _val;
|
||||||
|
// var pos = -0.25;
|
||||||
|
|
||||||
|
// function step() {
|
||||||
|
// if( _val !== undefined ) { this.refresh(); return false; }
|
||||||
|
// pos += 0.02;
|
||||||
|
// if( pos > 1.25)
|
||||||
|
// pos = -0.25;
|
||||||
|
// this.refresh();
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function paintNoValue(gfx)
|
||||||
|
// {
|
||||||
|
// var (w,h) = this.box(#dimension,#inner);
|
||||||
|
// var x = pos * w;
|
||||||
|
// w = w * 0.25;
|
||||||
|
// gfx.fillColor( this.style#color )
|
||||||
|
// .pushLayer(#inner-box)
|
||||||
|
// .rectangle(x,0,w,h)
|
||||||
|
// .popLayer();
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// this[#value] = property(v) {
|
||||||
|
// get return _val;
|
||||||
|
// set {
|
||||||
|
// _val = undefined;
|
||||||
|
// pos = -0.25;
|
||||||
|
// this.paintContent = paintNoValue;
|
||||||
|
// this.animate(step);
|
||||||
|
// this.refresh();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// this.value = "";
|
||||||
|
// }
|
||||||
|
|
||||||
|
const svg_eye_cross = (<svg viewBox="0 -21 511.96 511">
|
||||||
|
<path d="m506.68 261.88c7.043-16.984 7.043-36.461 0-53.461-41.621-100.4-140.03-165.27-250.71-165.27-46.484 0-90.797 11.453-129.64 32.191l-68.605-68.609c-8.3438-8.3398-21.824-8.3398-30.168 0-8.3398 8.3398-8.3398 21.824 0 30.164l271.49 271.49 86.484 86.488 68.676 68.672c4.1797 4.1797 9.6406 6.2695 15.102 6.2695 5.4609 0 10.922-2.0898 15.082-6.25 8.3438-8.3398 8.3438-21.824 0-30.164l-62.145-62.145c36.633-27.883 66.094-65.109 84.438-109.38zm-293.91-100.1c12.648-7.5742 27.391-11.969 43.199-11.969 47.062 0 85.332 38.273 85.332 85.336 0 15.805-4.3945 30.547-11.969 43.199z" />
|
||||||
|
<path d="m255.97 320.48c-47.062 0-85.336-38.273-85.336-85.332 0-3.0938 0.59766-6.0195 0.91797-9.0039l-106.15-106.16c-25.344 24.707-46.059 54.465-60.117 88.43-7.043 16.98-7.043 36.457 0 53.461 41.598 100.39 140.01 165.27 250.69 165.27 34.496 0 67.797-6.3164 98.559-18.027l-89.559-89.559c-2.9844 0.32031-5.9062 0.91797-9 0.91797z" />
|
||||||
|
</svg>);
|
||||||
|
|
||||||
|
export class PasswordComponent extends Element {
|
||||||
|
visible = false;
|
||||||
|
value = '';
|
||||||
|
name = 'password';
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
if (props && props.value) {
|
||||||
|
this.value = props.value;
|
||||||
|
}
|
||||||
|
if (props && props.name) {
|
||||||
|
this.name = props.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="password">
|
||||||
|
<input class="outline-focus" name={this.name} value={this.value} type={this.visible ? "text" : "password"} />
|
||||||
|
{this.visible ? svg_eye_cross : svg_eye}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at svg"](svg) {
|
||||||
|
let el = this.$("input");
|
||||||
|
let value = el.value;
|
||||||
|
// TODO selectionStart/selectionEnd run ok,but always return 0
|
||||||
|
let start = el.xcall("selectionStart") || 0;
|
||||||
|
let end = el.xcall("selectionEnd");
|
||||||
|
this.componentUpdate({ visible: !this.visible });
|
||||||
|
setTimeout(() => {
|
||||||
|
let el = this.$("input");
|
||||||
|
view.focus = el;
|
||||||
|
el.value = value;
|
||||||
|
el.xcall("setSelection", start, end);
|
||||||
|
}, 30)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isReasonableSize(r) {
|
||||||
|
let x = r[0];
|
||||||
|
let y = r[1];
|
||||||
|
return !(x < -3200 || x > 3200 || y < -3200 || y > 3200);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,378 +0,0 @@
|
|||||||
include "sciter:reactor.tis";
|
|
||||||
|
|
||||||
var handler = $(#handler) || view;
|
|
||||||
try { view.windowIcon = self.url(handler.get_icon()); } catch(e) {}
|
|
||||||
var OS = view.mediaVar("platform");
|
|
||||||
var is_osx = OS == "OSX";
|
|
||||||
var is_win = OS == "Windows";
|
|
||||||
var is_linux = OS == "Linux";
|
|
||||||
var is_file_transfer;
|
|
||||||
var is_xfce = false;
|
|
||||||
try { is_xfce = handler.is_xfce(); } catch(e) {}
|
|
||||||
|
|
||||||
|
|
||||||
function translate(name) {
|
|
||||||
try {
|
|
||||||
return handler.t(name);
|
|
||||||
} catch(_) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hashCode(str) {
|
|
||||||
var hash = 160 << 16 + 114 << 8 + 91;
|
|
||||||
for (var i = 0; i < str.length; i += 1) {
|
|
||||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
||||||
}
|
|
||||||
return hash % 16777216;
|
|
||||||
}
|
|
||||||
|
|
||||||
function intToRGB(i, a = 1) {
|
|
||||||
return 'rgba(' + ((i >> 16) & 0xFF) + ', ' + ((i >> 8) & 0x7F)
|
|
||||||
+ ',' + (i & 0xFF) + ',' + a + ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
function string2RGB(s, a = 1) {
|
|
||||||
return intToRGB(hashCode(s), a);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTime() {
|
|
||||||
var now = new Date();
|
|
||||||
return now.valueOf();
|
|
||||||
}
|
|
||||||
|
|
||||||
function platformSvg(platform, color) {
|
|
||||||
platform = (platform || "").toLowerCase();
|
|
||||||
if (platform == "linux") {
|
|
||||||
return <svg viewBox="0 0 256 256">
|
|
||||||
<g transform="translate(0 256) scale(.1 -.1)" fill={color}>
|
|
||||||
<path d="m1215 2537c-140-37-242-135-286-278-23-75-23-131 1-383l18-200-54-60c-203-224-383-615-384-831v-51l-66-43c-113-75-194-199-194-300 0-110 99-234 244-305 103-50 185-69 296-69 100 0 156 14 211 54 26 18 35 19 78 10 86-18 233-24 335-12 85 10 222 38 269 56 9 4 19-7 29-35 20-50 52-64 136-57 98 8 180 52 282 156 124 125 180 244 180 380 0 80-28 142-79 179l-36 26 4 119c5 175-22 292-105 460-74 149-142 246-286 409-43 49-78 92-78 97 0 4-7 52-15 107-8 54-19 140-24 189-13 121-41 192-103 260-95 104-248 154-373 122zm172-112c62-19 134-80 163-140 15-31 28-92 41-193 27-214 38-276 57-304 9-14 59-74 111-134 92-106 191-246 236-334 69-137 115-339 101-451l-7-55-71 10c-100 13-234-5-265-36-54-55-85-207-82-412l1-141-51-17c-104-34-245-51-380-45-69 3-142 10-162 16-32 10-37 17-53 68-23 72-87 201-136 273-80 117-158 188-237 215-37 13-37 13-34 61 13 211 182 555 373 759 57 62 58 63 58 121 0 33-9 149-19 259-21 224-18 266 26 347 67 122 193 174 330 133zm687-1720c32-9 71-25 87-36 60-42 59-151-4-274-59-119-221-250-317-257-34-3-35-2-48 47-18 65-20 329-3 413 16 83 29 110 55 115 51 10 177 6 230-8zm-1418-80c79-46 187-195 247-340 41-99 43-121 12-141-39-25-148-30-238-10-142 32-264 112-307 202-20 41-21 50-10 87 24 83 102 166 192 207 54 25 53 25 104-5z"/>
|
|
||||||
<path d="m1395 1945c-92-16-220-52-256-70-28-15-29-18-29-89 0-247 165-397 345-312 60 28 77 46 106 111 54 123 0 378-80 374-9 0-47-7-86-14zm74-156c15-69 14-112-5-159s-55-70-111-70c-48 0-78 20-102 68-15 29-41 131-41 159 0 9 230 63 242 57 3-2 11-27 17-55z"/>
|
|
||||||
</g>
|
|
||||||
</svg>;
|
|
||||||
}
|
|
||||||
if (platform == "mac os") {
|
|
||||||
return <svg viewBox="0 0 384 512">
|
|
||||||
<path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z" fill={color}/>
|
|
||||||
</svg>;
|
|
||||||
}
|
|
||||||
return <svg viewBox="0 0 448 512">
|
|
||||||
<path d="M0 93.7l183.6-25.3v177.4H0V93.7zm0 324.6l183.6 25.3V268.4H0v149.9zm203.8 28L448 480V268.4H203.8v177.9zm0-380.6v180.1H448V32L203.8 65.7z" fill={color}/>
|
|
||||||
</svg>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function centerize(w, h) {
|
|
||||||
var (sx, sy, sw, sh) = view.screenBox(#workarea, #rectw);
|
|
||||||
if (w > sw) w = sw;
|
|
||||||
if (h > sh) h = sh;
|
|
||||||
var x = (sx + sw - w) / 2;
|
|
||||||
var y = (sy + sh - h) / 2;
|
|
||||||
view.move(x, y, w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWindowButontsAndIcon(only_min=false) {
|
|
||||||
if (only_min) {
|
|
||||||
$(div.window-buttons).content(<div>
|
|
||||||
<button.window tabindex="-1" role="window-minimize" #minimize><div /></button>
|
|
||||||
</div>);
|
|
||||||
} else {
|
|
||||||
$(div.window-buttons).content(<div>
|
|
||||||
<button.window tabindex="-1" role="window-minimize" #minimize><div /></button>
|
|
||||||
<button.window tabindex="-1" role="window-maximize" #maximize><div /></button>
|
|
||||||
<button.window tabindex="-1" role="window-close" #close><div /></button>
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
$(div.window-icon>icon).style.set {
|
|
||||||
"background-image": "url('" + handler.get_icon() + "')",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function adjustBorder() {
|
|
||||||
if (is_osx) {
|
|
||||||
if (view.windowState == View.WINDOW_FULL_SCREEN) {
|
|
||||||
$(header).style.set {
|
|
||||||
display: "none",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
$(header).style.set {
|
|
||||||
display: "block",
|
|
||||||
padding: "0",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (view.windowState == view.WINDOW_MAXIMIZED) {
|
|
||||||
self.style.set {
|
|
||||||
border: "window-frame-width solid transparent",
|
|
||||||
};
|
|
||||||
} else if (view.windowState == view.WINDOW_FULL_SCREEN) {
|
|
||||||
self.style.set {
|
|
||||||
border: "none",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
self.style.set {
|
|
||||||
border: "black solid 1px",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var el = $(button#maximize);
|
|
||||||
if (el) el.attributes.toggleClass("restore", view.windowState == View.WINDOW_MAXIMIZED);
|
|
||||||
el = $(span#fullscreen);
|
|
||||||
if (el) el.attributes.toggleClass("active", view.windowState == View.WINDOW_FULL_SCREEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
var svg_checkmark = <svg viewBox="0 0 492 492"><path d="M484 105l-16-17a27 27 0 00-38 0L204 315 62 173c-5-5-12-7-19-7s-14 2-19 7L8 189a27 27 0 000 38l160 160v1l16 16c5 5 12 8 19 8 8 0 14-3 20-8l16-16v-1l245-244a27 27 0 000-38z"/></svg>;
|
|
||||||
var svg_edit = <svg #edit viewBox="0 0 384 384">
|
|
||||||
<path d="M0 304v80h80l236-236-80-80zM378 56L328 6c-8-8-22-8-30 0l-39 39 80 80 39-39c8-8 8-22 0-30z"/>
|
|
||||||
</svg>;
|
|
||||||
var svg_eye = <svg viewBox="0 0 469.33 469.33">
|
|
||||||
<path d="m234.67 170.67c-35.307 0-64 28.693-64 64s28.693 64 64 64 64-28.693 64-64-28.694-64-64-64z"/>
|
|
||||||
<path d="m234.67 74.667c-106.67 0-197.76 66.346-234.67 160 36.907 93.653 128 160 234.67 160 106.77 0 197.76-66.347 234.67-160-36.907-93.654-127.89-160-234.67-160zm0 266.67c-58.88 0-106.67-47.787-106.67-106.67s47.787-106.67 106.67-106.67 106.67 47.787 106.67 106.67-47.787 106.67-106.67 106.67z"/>
|
|
||||||
</svg>;
|
|
||||||
var svg_send = <svg viewBox="0 0 448 448">
|
|
||||||
<polygon points="0.213 32 0 181.33 320 224 0 266.67 0.213 416 448 224"/>
|
|
||||||
</svg>;
|
|
||||||
var svg_chat = <svg viewBox="0 0 511.07 511.07">
|
|
||||||
<path d="m74.39 480.54h-36.213l25.607-25.607c13.807-13.807 22.429-31.765 24.747-51.246-36.029-23.644-62.375-54.751-76.478-90.425-14.093-35.647-15.864-74.888-5.121-113.48 12.89-46.309 43.123-88.518 85.128-118.85 45.646-32.963 102.47-50.387 164.33-50.387 77.927 0 143.61 22.389 189.95 64.745 41.744 38.159 64.734 89.63 64.734 144.93 0 26.868-5.471 53.011-16.26 77.703-11.165 25.551-27.514 48.302-48.593 67.619-46.399 42.523-112.04 65-189.83 65-28.877 0-59.01-3.855-85.913-10.929-25.465 26.123-59.972 40.929-96.086 40.929zm182-420c-124.04 0-200.15 73.973-220.56 147.28-19.284 69.28 9.143 134.74 76.043 175.12l7.475 4.511-0.23 8.727c-0.456 17.274-4.574 33.912-11.945 48.952 17.949-6.073 34.236-17.083 46.99-32.151l6.342-7.493 9.405 2.813c26.393 7.894 57.104 12.241 86.477 12.241 154.37 0 224.68-93.473 224.68-180.32 0-46.776-19.524-90.384-54.976-122.79-40.713-37.216-99.397-56.888-169.71-56.888z"/>
|
|
||||||
</svg>;
|
|
||||||
|
|
||||||
function scrollToBottom(el) {
|
|
||||||
var y = el.box(#height, #content) - el.box(#height, #client);
|
|
||||||
el.scrollTo(0, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNowStr() {
|
|
||||||
var now = new Date();
|
|
||||||
return String.printf("%02d:%02d:%02d", now.hour, now.minute, now.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************** start of chatbox ****************************************/
|
|
||||||
class ChatBox: Reactor.Component {
|
|
||||||
this var msgs = [];
|
|
||||||
this var callback;
|
|
||||||
|
|
||||||
function this(params) {
|
|
||||||
if (params) {
|
|
||||||
this.msgs = params.msgs || [];
|
|
||||||
this.callback = params.callback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderMsg(msg) {
|
|
||||||
var cls = msg.name == "me" ? "right-side msg" : "left-side msg";
|
|
||||||
return <div class={cls}>
|
|
||||||
{msg.name == "me" ?
|
|
||||||
<div .name>{msg.time + " "} me</div> :
|
|
||||||
<div .name>{msg.name} {" " + msg.time}</div>
|
|
||||||
}
|
|
||||||
<div .text>{msg.text}</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
var me = this;
|
|
||||||
var msgs = this.msgs.map(function(msg) { return me.renderMsg(msg); });
|
|
||||||
self.timer(1ms, function() {
|
|
||||||
scrollToBottom(me.msgs);
|
|
||||||
});
|
|
||||||
return <div .msgbox>
|
|
||||||
<htmlarea spellcheck="false" readonly .msgs @{this.msgs} >
|
|
||||||
{msgs}
|
|
||||||
</htmlarea>
|
|
||||||
<div .send>
|
|
||||||
<input|text .outline-focus />
|
|
||||||
<span>{svg_send}</span>
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function send() {
|
|
||||||
var el = this.$(input);
|
|
||||||
var value = (el.value || "").trim();
|
|
||||||
el.value = "";
|
|
||||||
if (!value) return;
|
|
||||||
if (this.callback) this.callback(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
event keydown $(input) (evt) {
|
|
||||||
if (!evt.shortcutKey) {
|
|
||||||
if (evt.keyCode == Event.VK_ENTER ||
|
|
||||||
(view.mediaVar("platform") == "OSX" && evt.keyCode == 0x4C)) {
|
|
||||||
this.send();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(div.send span) {
|
|
||||||
this.send();
|
|
||||||
view.focus = $(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/******************** end of chatbox ****************************************/
|
|
||||||
|
|
||||||
/******************** start of msgbox ****************************************/
|
|
||||||
var remember_password = false;
|
|
||||||
var msgbox_params;
|
|
||||||
function getMsgboxParams() {
|
|
||||||
return msgbox_params;
|
|
||||||
}
|
|
||||||
|
|
||||||
// tmp workaround https://sciter.com/forums/topic/menu-not-be-hidden-when-open-dialog-on-linux/
|
|
||||||
function msgbox(type, title, text, callback=null, height=180, width=500, retry=0, contentStyle="") {
|
|
||||||
if (is_linux) { // fix menu not hidden issue
|
|
||||||
self.timer(1ms,
|
|
||||||
function() {
|
|
||||||
msgbox_(type, title, text, callback, height, width, retry, contentStyle);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
msgbox_(type, title, text, callback, height, width, retry, contentStyle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function msgbox_(type, title, text, callback, height, width, retry, contentStyle) {
|
|
||||||
var has_msgbox = msgbox_params != null;
|
|
||||||
if (!has_msgbox && !type) return;
|
|
||||||
var remember = false;
|
|
||||||
try {
|
|
||||||
remember = handler.get_remember();
|
|
||||||
} catch(e) {}
|
|
||||||
msgbox_params = {
|
|
||||||
remember: remember, type: type, text: text, title: title,
|
|
||||||
getParams: getMsgboxParams,
|
|
||||||
callback: callback, translate: translate,
|
|
||||||
retry: retry, contentStyle: contentStyle,
|
|
||||||
};
|
|
||||||
if (has_msgbox) return;
|
|
||||||
var dialog = {
|
|
||||||
client: true,
|
|
||||||
parameters: msgbox_params,
|
|
||||||
width: width + (is_xfce ? 50 : 0),
|
|
||||||
height: height + (is_xfce ? 50 : 0),
|
|
||||||
};
|
|
||||||
var html = handler.get_msgbox();
|
|
||||||
if (html) dialog.html = html;
|
|
||||||
else dialog.url = self.url("msgbox.html");
|
|
||||||
var res = view.dialog(dialog);
|
|
||||||
msgbox_params = null;
|
|
||||||
stdout.printf("msgbox return, type: %s, res: %s\n", type, res);
|
|
||||||
if (type.indexOf("custom") >= 0) {
|
|
||||||
//
|
|
||||||
} else if (!res) {
|
|
||||||
if (!is_port_forward) view.close();
|
|
||||||
} else if (res == "!alive") {
|
|
||||||
// do nothing
|
|
||||||
} else if (res.type == "input-password") {
|
|
||||||
handler.login(res.password, res.remember);
|
|
||||||
if (!is_port_forward) msgbox("connecting", "Connecting...", "Logging in...");
|
|
||||||
} else if (res.reconnect) {
|
|
||||||
if (!is_port_forward) connecting();
|
|
||||||
handler.reconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function connecting() {
|
|
||||||
handler.msgbox("connecting", "Connecting...", "Connection in progress. Please wait.");
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.msgbox = function(type, title, text, retry=0) {
|
|
||||||
self.timer(30ms, function() { msgbox(type, title, text, null, 180, 500, retry); });
|
|
||||||
}
|
|
||||||
|
|
||||||
var reconnectTimeout = 1;
|
|
||||||
handler.msgbox_retry = function(type, title, text, hasRetry) {
|
|
||||||
handler.msgbox(type, title, text, hasRetry ? reconnectTimeout : 0);
|
|
||||||
if (hasRetry) {
|
|
||||||
reconnectTimeout *= 2;
|
|
||||||
} else {
|
|
||||||
reconnectTimeout = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/******************** end of msgbox ****************************************/
|
|
||||||
|
|
||||||
function Progress()
|
|
||||||
{
|
|
||||||
var _val;
|
|
||||||
var pos = -0.25;
|
|
||||||
|
|
||||||
function step() {
|
|
||||||
if( _val !== undefined ) { this.refresh(); return false; }
|
|
||||||
pos += 0.02;
|
|
||||||
if( pos > 1.25)
|
|
||||||
pos = -0.25;
|
|
||||||
this.refresh();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function paintNoValue(gfx)
|
|
||||||
{
|
|
||||||
var (w,h) = this.box(#dimension,#inner);
|
|
||||||
var x = pos * w;
|
|
||||||
w = w * 0.25;
|
|
||||||
gfx.fillColor( this.style#color )
|
|
||||||
.pushLayer(#inner-box)
|
|
||||||
.rectangle(x,0,w,h)
|
|
||||||
.popLayer();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this[#value] = property(v) {
|
|
||||||
get return _val;
|
|
||||||
set {
|
|
||||||
_val = undefined;
|
|
||||||
pos = -0.25;
|
|
||||||
this.paintContent = paintNoValue;
|
|
||||||
this.animate(step);
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
var svg_eye_cross = <svg viewBox="0 -21 511.96 511">
|
|
||||||
<path d="m506.68 261.88c7.043-16.984 7.043-36.461 0-53.461-41.621-100.4-140.03-165.27-250.71-165.27-46.484 0-90.797 11.453-129.64 32.191l-68.605-68.609c-8.3438-8.3398-21.824-8.3398-30.168 0-8.3398 8.3398-8.3398 21.824 0 30.164l271.49 271.49 86.484 86.488 68.676 68.672c4.1797 4.1797 9.6406 6.2695 15.102 6.2695 5.4609 0 10.922-2.0898 15.082-6.25 8.3438-8.3398 8.3438-21.824 0-30.164l-62.145-62.145c36.633-27.883 66.094-65.109 84.438-109.38zm-293.91-100.1c12.648-7.5742 27.391-11.969 43.199-11.969 47.062 0 85.332 38.273 85.332 85.336 0 15.805-4.3945 30.547-11.969 43.199z"/>
|
|
||||||
<path d="m255.97 320.48c-47.062 0-85.336-38.273-85.336-85.332 0-3.0938 0.59766-6.0195 0.91797-9.0039l-106.15-106.16c-25.344 24.707-46.059 54.465-60.117 88.43-7.043 16.98-7.043 36.457 0 53.461 41.598 100.39 140.01 165.27 250.69 165.27 34.496 0 67.797-6.3164 98.559-18.027l-89.559-89.559c-2.9844 0.32031-5.9062 0.91797-9 0.91797z"/>
|
|
||||||
</svg>;
|
|
||||||
|
|
||||||
class PasswordComponent: Reactor.Component {
|
|
||||||
this var visible = false;
|
|
||||||
this var value = '';
|
|
||||||
this var name = 'password';
|
|
||||||
|
|
||||||
function this(params) {
|
|
||||||
if (params && params.value) {
|
|
||||||
this.value = params.value;
|
|
||||||
}
|
|
||||||
if (params && params.name) {
|
|
||||||
this.name = params.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
return <div .password>
|
|
||||||
<input name={this.name} value={this.value} type={this.visible ? "text" : "password"} .outline-focus />
|
|
||||||
{this.visible ? svg_eye_cross : svg_eye}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(svg) {
|
|
||||||
var el = this.$(input);
|
|
||||||
var value = el.value;
|
|
||||||
var start = el.xcall(#selectionStart) || 0;
|
|
||||||
var end = el.xcall(#selectionEnd);
|
|
||||||
this.update({ visible: !this.visible });
|
|
||||||
var me = this;
|
|
||||||
self.timer(30ms, function() {
|
|
||||||
var el = me.$(input);
|
|
||||||
view.focus = el;
|
|
||||||
el.value = value;
|
|
||||||
el.xcall(#setSelection, start, end);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isReasonableSize(r) {
|
|
||||||
var x = r[0];
|
|
||||||
var y = r[1];
|
|
||||||
return !(x < -3200 || x > 3200 || y < -3200 || y > 3200);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,30 +1,34 @@
|
|||||||
|
import { handler,svg_send,translate,msgbox } from "./common.js";
|
||||||
|
import {$} from "@sciter";
|
||||||
|
|
||||||
var remote_home_dir;
|
var remote_home_dir;
|
||||||
|
|
||||||
var svg_add_folder = <svg viewBox="0 0 443.29 443.29">
|
const svg_add_folder = (<svg viewBox="0 0 443.29 443.29">
|
||||||
<path d="m277.06 332.47h27.706v-55.412h55.412v-27.706h-55.412v-55.412h-27.706v55.412h-55.412v27.706h55.412z"/>
|
<path d="m277.06 332.47h27.706v-55.412h55.412v-27.706h-55.412v-55.412h-27.706v55.412h-55.412v27.706h55.412z"/>
|
||||||
<path d="m415.59 83.118h-202.06l-51.353-51.353c-2.597-2.597-6.115-4.058-9.794-4.058h-124.68c-15.274-1e-3 -27.706 12.431-27.706 27.705v332.47c0 15.273 12.432 27.706 27.706 27.706h387.88c15.273 0 27.706-12.432 27.706-27.706v-277.06c0-15.274-12.432-27.706-27.706-27.706zm0 304.76h-387.88v-332.47h118.94l51.354 51.353c2.597 2.597 6.115 4.058 9.794 4.058h207.79z"/>
|
<path d="m415.59 83.118h-202.06l-51.353-51.353c-2.597-2.597-6.115-4.058-9.794-4.058h-124.68c-15.274-1e-3 -27.706 12.431-27.706 27.705v332.47c0 15.273 12.432 27.706 27.706 27.706h387.88c15.273 0 27.706-12.432 27.706-27.706v-277.06c0-15.274-12.432-27.706-27.706-27.706zm0 304.76h-387.88v-332.47h118.94l51.354 51.353c2.597 2.597 6.115 4.058 9.794 4.058h207.79z"/>
|
||||||
</svg>;
|
</svg>);
|
||||||
var svg_trash = <svg viewBox="0 0 473.41 473.41">
|
const svg_trash = (<svg viewBox="0 0 473.41 473.41">
|
||||||
<path d="m443.82 88.765h-88.765v-73.971c0-8.177-6.617-14.794-14.794-14.794h-207.12c-8.177 0-14.794 6.617-14.794 14.794v73.971h-88.764v29.588h14.39l57.116 342.69c1.185 7.137 7.354 12.367 14.592 12.367h241.64c7.238 0 13.407-5.23 14.592-12.367l57.116-342.69h14.794c-1e-3 0-1e-3 -29.588-1e-3 -29.588zm-295.88-59.177h177.53v59.176h-177.53zm196.85 414.24h-216.58l-54.241-325.47h325.06z"/>
|
<path d="m443.82 88.765h-88.765v-73.971c0-8.177-6.617-14.794-14.794-14.794h-207.12c-8.177 0-14.794 6.617-14.794 14.794v73.971h-88.764v29.588h14.39l57.116 342.69c1.185 7.137 7.354 12.367 14.592 12.367h241.64c7.238 0 13.407-5.23 14.592-12.367l57.116-342.69h14.794c-1e-3 0-1e-3 -29.588-1e-3 -29.588zm-295.88-59.177h177.53v59.176h-177.53zm196.85 414.24h-216.58l-54.241-325.47h325.06z"/>
|
||||||
<path transform="matrix(.064 -.998 .998 .064 -.546 19.418)" d="m-360.4 301.29h207.54v29.592h-207.54z"/>
|
<path transform="matrix(.064 -.998 .998 .064 -.546 19.418)" d="m-360.4 301.29h207.54v29.592h-207.54z"/>
|
||||||
<path transform="matrix(.998 -.064 .064 .998 -.628 .399)" d="m141.64 202.35h29.592v207.54h-29.592z"/>
|
<path transform="matrix(.998 -.064 .064 .998 -.628 .399)" d="m141.64 202.35h29.592v207.54h-29.592z"/>
|
||||||
</svg>;
|
</svg>);
|
||||||
var svg_arrow = <svg viewBox="0 0 482.24 482.24">
|
export const svg_arrow = (<svg viewBox="0 0 482.24 482.24">
|
||||||
<path d="m206.81 447.79-206.81-206.67 206.74-206.67 24.353 24.284-165.17 165.17h416.31v34.445h-416.31l165.24 165.24z"/>
|
<path d="m206.81 447.79-206.81-206.67 206.74-206.67 24.353 24.284-165.17 165.17h416.31v34.445h-416.31l165.24 165.24z"/>
|
||||||
</svg>;
|
</svg>);
|
||||||
var svg_home = <svg viewBox="0 0 476.91 476.91">
|
const svg_home = (<svg viewBox="0 0 476.91 476.91">
|
||||||
<path d="m461.78 209.41-212.21-204.89c-6.182-6.026-16.042-6.026-22.224 0l-212.2 204.88c-3.124 3.015-4.888 7.17-4.888 11.512 0 8.837 7.164 16 16 16h28.2v224c0 8.837 7.163 16 16 16h112c8.837 0 16-7.163 16-16v-128h80v128c0 8.837 7.163 16 16 16h112c8.837 0 16-7.163 16-16v-224h28.2c4.338 0 8.489-1.761 11.504-4.88 6.141-6.354 5.969-16.483-0.384-22.624zm-39.32 11.504c-8.837 0-16 7.163-16 16v224h-112v-128c0-8.837-7.163-16-16-16h-80c-8.837 0-16 7.163-16 16v128h-112v-224c0-8.837-7.163-16-16-16h-28.2l212.2-204.88 212.28 204.88h-28.28z"/>
|
<path d="m461.78 209.41-212.21-204.89c-6.182-6.026-16.042-6.026-22.224 0l-212.2 204.88c-3.124 3.015-4.888 7.17-4.888 11.512 0 8.837 7.164 16 16 16h28.2v224c0 8.837 7.163 16 16 16h112c8.837 0 16-7.163 16-16v-128h80v128c0 8.837 7.163 16 16 16h112c8.837 0 16-7.163 16-16v-224h28.2c4.338 0 8.489-1.761 11.504-4.88 6.141-6.354 5.969-16.483-0.384-22.624zm-39.32 11.504c-8.837 0-16 7.163-16 16v224h-112v-128c0-8.837-7.163-16-16-16h-80c-8.837 0-16 7.163-16 16v128h-112v-224c0-8.837-7.163-16-16-16h-28.2l212.2-204.88 212.28 204.88h-28.28z"/>
|
||||||
</svg>;
|
</svg>);
|
||||||
var svg_refresh = <svg viewBox="0 0 551.13 551.13">
|
const svg_refresh = (<svg viewBox="0 0 551.13 551.13">
|
||||||
<path d="m482.24 310.01c0 113.97-92.707 206.67-206.67 206.67s-206.67-92.708-206.67-206.67c0-102.21 74.639-187.09 172.23-203.56v65.78l86.114-86.114-86.114-86.115v71.641c-116.65 16.802-206.67 117.14-206.67 238.37 0 132.96 108.16 241.12 241.12 241.12s241.12-108.16 241.12-241.12z"/>
|
<path d="m482.24 310.01c0 113.97-92.707 206.67-206.67 206.67s-206.67-92.708-206.67-206.67c0-102.21 74.639-187.09 172.23-203.56v65.78l86.114-86.114-86.114-86.115v71.641c-116.65 16.802-206.67 117.14-206.67 238.37 0 132.96 108.16 241.12 241.12 241.12s241.12-108.16 241.12-241.12z"/>
|
||||||
</svg>;
|
</svg>);
|
||||||
var svg_cancel = <svg .cancel viewBox="0 0 612 612"><polygon points="612 36.004 576.52 0.603 306 270.61 35.478 0.603 0 36.004 270.52 306.01 0 576 35.478 611.4 306 341.41 576.52 611.4 612 576 341.46 306.01"/></svg>;
|
export const svg_cancel = (<svg class="cancel" viewBox="0 0 612 612"><polygon points="612 36.004 576.52 0.603 306 270.61 35.478 0.603 0 36.004 270.52 306.01 0 576 35.478 611.4 306 341.41 576.52 611.4 612 576 341.46 306.01"/></svg>);
|
||||||
var svg_computer = <svg .computer viewBox="0 0 480 480">
|
const svg_computer = (<svg class="computer" viewBox="0 0 480 480">
|
||||||
<g>
|
<g>
|
||||||
<path fill="#2C8CFF" d="m276 395v11.148c0 2.327-1.978 4.15-4.299 3.985-21.145-1.506-42.392-1.509-63.401-0.011-2.322 0.166-4.3-1.657-4.3-3.985v-11.137c0-2.209 1.791-4 4-4h64c2.209 0 4 1.791 4 4zm204-340v288c0 17.65-14.35 32-32 32h-416c-17.65 0-32-14.35-32-32v-288c0-17.65 14.35-32 32-32h416c17.65 0 32 14.35 32 32zm-125.62 386.36c-70.231-21.843-158.71-21.784-228.76 0-4.22 1.31-6.57 5.8-5.26 10.02 1.278 4.085 5.639 6.591 10.02 5.26 66.093-20.58 151.37-21.125 219.24 0 4.22 1.31 8.71-1.04 10.02-5.26s-1.04-8.71-5.26-10.02z"/>
|
<path fill="#2C8CFF" d="m276 395v11.148c0 2.327-1.978 4.15-4.299 3.985-21.145-1.506-42.392-1.509-63.401-0.011-2.322 0.166-4.3-1.657-4.3-3.985v-11.137c0-2.209 1.791-4 4-4h64c2.209 0 4 1.791 4 4zm204-340v288c0 17.65-14.35 32-32 32h-416c-17.65 0-32-14.35-32-32v-288c0-17.65 14.35-32 32-32h416c17.65 0 32 14.35 32 32zm-125.62 386.36c-70.231-21.843-158.71-21.784-228.76 0-4.22 1.31-6.57 5.8-5.26 10.02 1.278 4.085 5.639 6.591 10.02 5.26 66.093-20.58 151.37-21.125 219.24 0 4.22 1.31 8.71-1.04 10.02-5.26s-1.04-8.71-5.26-10.02z"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>;
|
</svg>);
|
||||||
|
|
||||||
|
// TODO
|
||||||
function getSize(type, size) {
|
function getSize(type, size) {
|
||||||
if (!size) {
|
if (!size) {
|
||||||
if (type <= 3) return "";
|
if (type <= 3) return "";
|
||||||
@@ -47,15 +51,15 @@ function getSize(type, size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getParentPath(is_remote, path) {
|
function getParentPath(is_remote, path) {
|
||||||
var sep = handler.get_path_sep(is_remote);
|
let sep = handler.xcall("get_path_sep",is_remote);
|
||||||
var res = path.lastIndexOf(sep);
|
let res = path.lastIndexOf(sep);
|
||||||
if (res <= 0) return "/";
|
if (res <= 0) return "/";
|
||||||
return path.substr(0, res);
|
return path.substr(0, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileName(is_remote, path) {
|
function getFileName(is_remote, path) {
|
||||||
var sep = handler.get_path_sep(is_remote);
|
let sep = handler.xcall("get_path_sep",is_remote);
|
||||||
var res = path.lastIndexOf(sep);
|
let res = path.lastIndexOf(sep);
|
||||||
return path.substr(res + 1);
|
return path.substr(res + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,76 +67,75 @@ function getExt(name) {
|
|||||||
if (name.indexOf(".") == 0) {
|
if (name.indexOf(".") == 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
var i = name.lastIndexOf(".");
|
let i = name.lastIndexOf(".");
|
||||||
if (i > 0) return name.substr(i + 1);
|
if (i > 0) return name.substr(i + 1);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
var jobIdCounter = 1;
|
var jobIdCounter = 1;
|
||||||
|
|
||||||
class JobTable: Reactor.Component {
|
class JobTable extends Element {
|
||||||
this var jobs = [];
|
jobs = [];
|
||||||
this var job_map = {};
|
job_map = {};
|
||||||
|
|
||||||
function render() {
|
render() {
|
||||||
var me = this;
|
let rows = this.jobs.map((job, i)=>this.renderRow(job, i));
|
||||||
var rows = this.jobs.map(function(job, i) { return me.renderRow(job, i); });
|
return (<section><table class="has_current job-table">
|
||||||
return <section><table .has_current .job-table>
|
|
||||||
<tbody key={rows.length}>
|
<tbody key={rows.length}>
|
||||||
{rows}
|
{rows}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table></section>;
|
</table></section>);
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(svg.cancel) (_, me) {
|
["on click at svg.cancel"](_, me) {
|
||||||
var job = this.jobs[me.parent.parent.index];
|
let job = this.jobs[me.parentElement.parentElement.index];
|
||||||
var id = job.id;
|
let id = job.id;
|
||||||
handler.cancel_job(id);
|
handler.xcall("cancel_job",id);
|
||||||
delete this.job_map[id];
|
delete this.job_map[id];
|
||||||
var i = -1;
|
let i = -1;
|
||||||
this.jobs.map(function(job, idx) {
|
this.jobs.map(function(job, idx) {
|
||||||
if (job.id == id) i = idx;
|
if (job.id == id) i = idx;
|
||||||
});
|
});
|
||||||
this.jobs.splice(i, 1);
|
this.jobs.splice(i, 1);
|
||||||
this.update();
|
this.componentUpdate();
|
||||||
var is_remote = job.is_remote;
|
let is_remote = job.is_remote;
|
||||||
if (job.type != "del-dir") is_remote = !is_remote;
|
if (job.type != "del-dir") is_remote = !is_remote;
|
||||||
refreshDir(is_remote);
|
refreshDir(is_remote);
|
||||||
}
|
}
|
||||||
|
|
||||||
function send(path, is_remote) {
|
send(path, is_remote) {
|
||||||
var to;
|
let to;
|
||||||
var show_hidden;
|
let show_hidden;
|
||||||
if (is_remote) {
|
if (is_remote) {
|
||||||
to = file_transfer.local_folder_view.fd.path;
|
to = file_transfer.local_folder_view.fd.path; // NULL
|
||||||
show_hidden = file_transfer.remote_folder_view.show_hidden;
|
show_hidden = file_transfer.remote_folder_view.show_hidden;
|
||||||
} else {
|
} else {
|
||||||
to = file_transfer.remote_folder_view.fd.path;
|
to = file_transfer.remote_folder_view.fd.path;
|
||||||
show_hidden = file_transfer.local_folder_view.show_hidden;
|
show_hidden = file_transfer.local_folder_view.show_hidden;
|
||||||
}
|
}
|
||||||
if (!to) return;
|
if (!to) return;
|
||||||
to += handler.get_path_sep(!is_remote) + getFileName(is_remote, path);
|
to += handler.xcall("get_path_sep",!is_remote) + getFileName(is_remote, path);
|
||||||
var id = jobIdCounter;
|
let id = jobIdCounter;
|
||||||
jobIdCounter += 1;
|
jobIdCounter += 1;
|
||||||
this.jobs.push({ type: "transfer",
|
this.jobs.push({ type: "transfer",
|
||||||
id: id, path: path, to: to,
|
id: id, path: path, to: to,
|
||||||
include_hidden: show_hidden,
|
include_hidden: show_hidden,
|
||||||
is_remote: is_remote });
|
is_remote: is_remote });
|
||||||
this.job_map[id] = this.jobs[this.jobs.length - 1];
|
this.job_map[id] = this.jobs[this.jobs.length - 1];
|
||||||
handler.send_files(id, path, to, show_hidden, is_remote);
|
handler.xcall("send_files",id, path, to, show_hidden, is_remote);
|
||||||
this.update();
|
this.componentUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
function addDelDir(path, is_remote) {
|
addDelDir(path, is_remote) {
|
||||||
var id = jobIdCounter;
|
let id = jobIdCounter;
|
||||||
jobIdCounter += 1;
|
jobIdCounter += 1;
|
||||||
this.jobs.push({ type: "del-dir", id: id, path: path, is_remote: is_remote });
|
this.jobs.push({ type: "del-dir", id: id, path: path, is_remote: is_remote });
|
||||||
this.job_map[id] = this.jobs[this.jobs.length - 1];
|
this.job_map[id] = this.jobs[this.jobs.length - 1];
|
||||||
handler.remove_dir_all(id, path, is_remote);
|
handler.xcall("remove_dir_all",id, path, is_remote);
|
||||||
this.update();
|
this.componentUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSvg(job) {
|
getSvg(job) {
|
||||||
if (job.type == "transfer") {
|
if (job.type == "transfer") {
|
||||||
return svg_send;
|
return svg_send;
|
||||||
} else if (job.type == "del-dir") {
|
} else if (job.type == "del-dir") {
|
||||||
@@ -140,19 +143,19 @@ class JobTable: Reactor.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatus(job) {
|
getStatus(job) {
|
||||||
if (!job.entries) return translate("Waiting");
|
if (!job.entries) return translate("Waiting");
|
||||||
var i = job.file_num + 1;
|
let i = job.file_num + 1;
|
||||||
var n = job.num_entries || job.entries.length;
|
let n = job.num_entries || job.entries.length;
|
||||||
if (i > n) i = n;
|
if (i > n) i = n;
|
||||||
var res = i + ' / ' + n + " " + translate("files");
|
let res = i + ' / ' + n + " " + translate("files");
|
||||||
if (job.total_size > 0) {
|
if (job.total_size > 0) {
|
||||||
var s = getSize(0, job.finished_size);
|
let s = getSize(0, job.finished_size);
|
||||||
if (s) s += " / ";
|
if (s) s += " / ";
|
||||||
res += ", " + s + getSize(0, job.total_size);
|
res += ", " + s + getSize(0, job.total_size);
|
||||||
}
|
}
|
||||||
// below has problem if some file skipped
|
// below has problem if some file skipped
|
||||||
var percent = job.total_size == 0 ? 100 : (100. * job.finished_size / job.total_size).toInteger(); // (100. * i / (n || 1)).toInteger();
|
let percent = job.total_size == 0 ? 100 : (100. * job.finished_size / job.total_size).toInteger(); // (100. * i / (n || 1)).toInteger();
|
||||||
if (job.finished) percent = '100';
|
if (job.finished) percent = '100';
|
||||||
if (percent) res += ", " + percent + "%";
|
if (percent) res += ", " + percent + "%";
|
||||||
if (job.finished) res = translate("Finished") + " " + res;
|
if (job.finished) res = translate("Finished") + " " + res;
|
||||||
@@ -160,17 +163,18 @@ class JobTable: Reactor.Component {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateJob(job) {
|
updateJob(job) {
|
||||||
var el = this.select("div[id=s" + job.id + "]");
|
let el = this.$("div#s" + job.id); // TODO TEST
|
||||||
|
console.log("updateJob el",el);
|
||||||
if (el) el.text = this.getStatus(job);
|
if (el) el.text = this.getStatus(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateJobStatus(id, file_num = -1, err = null, speed = null, finished_size = 0) {
|
updateJobStatus(id, file_num = -1, err = null, speed = null, finished_size = 0) {
|
||||||
var job = this.job_map[id];
|
let job = this.job_map[id];
|
||||||
if (!job) return;
|
if (!job) return;
|
||||||
if (file_num < job.file_num) return;
|
if (file_num < job.file_num) return;
|
||||||
job.file_num = file_num;
|
job.file_num = file_num;
|
||||||
var n = job.num_entries || job.entries.length;
|
let n = job.num_entries || job.entries.length;
|
||||||
job.finished = job.file_num >= n - 1 || err == "cancel";
|
job.finished = job.file_num >= n - 1 || err == "cancel";
|
||||||
job.finished_size = finished_size;
|
job.finished_size = finished_size;
|
||||||
job.speed = speed || 0;
|
job.speed = speed || 0;
|
||||||
@@ -178,151 +182,158 @@ class JobTable: Reactor.Component {
|
|||||||
if (job.type == "del-dir") {
|
if (job.type == "del-dir") {
|
||||||
if (job.finished) {
|
if (job.finished) {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
handler.remove_dir(job.id, job.path, job.is_remote);
|
handler.xcall("remove_dir",job.id, job.path, job.is_remote);
|
||||||
refreshDir(job.is_remote);
|
refreshDir(job.is_remote);
|
||||||
}
|
}
|
||||||
} else if (!job.no_confirm) {
|
} else if (!job.no_confirm) {
|
||||||
handler.confirm_delete_files(id, job.file_num + 1);
|
handler.xcall("confirm_delete_files",id, job.file_num + 1);
|
||||||
}
|
}
|
||||||
} else if (job.finished || file_num == -1) {
|
} else if (job.finished || file_num == -1) {
|
||||||
refreshDir(!job.is_remote);
|
refreshDir(!job.is_remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderRow(job, i) {
|
renderRow(job, i) {
|
||||||
var svg = this.getSvg(job);
|
svg = this.getSvg(job);
|
||||||
return <tr class={job.is_remote ? "is_remote" : ""}><td>
|
return (<tr class={job.is_remote ? "is_remote" : ""}><td>
|
||||||
{svg}
|
{svg}
|
||||||
<div .text>
|
<div class="text">
|
||||||
<div .path>{job.path}</div>
|
<div class="path">{job.path}</div>
|
||||||
<div id={"s" + job.id}>{this.getStatus(job)}</div>
|
<div id={"s" + job.id}>{this.getStatus(job)}</div>
|
||||||
</div>
|
</div>
|
||||||
{svg_cancel}
|
{svg_cancel}
|
||||||
</td></tr>;
|
</td></tr>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FolderView : Reactor.Component {
|
class FolderView extends Element {
|
||||||
this var fd = {};
|
fd = {};
|
||||||
this var history = [];
|
history = [];
|
||||||
this var show_hidden = false;
|
show_hidden = false;
|
||||||
|
select_dir;
|
||||||
|
|
||||||
function sep() {
|
sep() {
|
||||||
return handler.get_path_sep(this.is_remote);
|
return handler.xcall("get_path_sep",this.is_remote);
|
||||||
}
|
}
|
||||||
|
|
||||||
function this(params) {
|
this(params) {
|
||||||
this.is_remote = params.is_remote;
|
this.is_remote = params.is_remote;
|
||||||
if (this.is_remote) {
|
if (this.is_remote) {
|
||||||
this.show_hidden = !!handler.get_option("remote_show_hidden");
|
this.show_hidden = !!handler.xcall("get_option","remote_show_hidden");
|
||||||
} else {
|
} else {
|
||||||
this.show_hidden = !!handler.get_option("local_show_hidden");
|
this.show_hidden = !!handler.xcall("get_option","local_show_hidden");
|
||||||
}
|
}
|
||||||
if (!this.is_remote) {
|
if (!this.is_remote) {
|
||||||
var dir = handler.get_option("local_dir");
|
let dir = handler.xcall("get_option","local_dir");
|
||||||
if (dir) {
|
if (dir) {
|
||||||
this.fd = handler.read_dir(dir, this.show_hidden);
|
this.fd = handler.xcall("read_dir",dir, this.show_hidden);
|
||||||
if (this.fd) return;
|
if (this.fd) return;
|
||||||
}
|
}
|
||||||
this.fd = handler.read_dir(handler.get_home_dir(), this.show_hidden);
|
this.fd = handler.xcall("read_dir",handler.xcall("get_home_dir"), this.show_hidden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort predicate
|
// sort predicate
|
||||||
function foldersFirst(a, b) {
|
foldersFirst(a, b) {
|
||||||
if (a.type <= 3 && b.type > 3) return -1;
|
if (a.type <= 3 && b.type > 3) return -1;
|
||||||
if (a.type > 3 && b.type <= 3) return +1;
|
if (a.type > 3 && b.type <= 3) return +1;
|
||||||
if (a.name == b.name) return 0;
|
if (a.name == b.name) return 0;
|
||||||
return a.name.toLowerCase().lexicalCompare(b.name.toLowerCase());
|
return a.name.toLowerCase().lexicalCompare(b.name.toLowerCase()); // TODO lexicalCompare
|
||||||
}
|
}
|
||||||
|
|
||||||
function render()
|
render()
|
||||||
{
|
{
|
||||||
return <section>
|
return (<section>
|
||||||
{this.renderTitle()}
|
{this.renderTitle()}
|
||||||
{this.renderNavBar()}
|
{this.renderNavBar()}
|
||||||
{this.renderOpBar()}
|
{this.renderOpBar()}
|
||||||
{this.renderTable()}
|
{this.renderTable()}
|
||||||
</section>;
|
</section>);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTitle() {
|
renderTitle() {
|
||||||
return <div .title>
|
return (<div class="title">
|
||||||
{svg_computer}
|
{svg_computer}
|
||||||
<div .platform>{platformSvg(handler.get_platform(this.is_remote), "white")}</div>
|
<div class="platform">{platformSvg(handler.xcall("get_platform",this.is_remote), "white")}</div>
|
||||||
<div><span>{translate(this.is_remote ? "Remote Computer" : "Local Computer")}</span></div>
|
<div><span>{translate(this.is_remote ? "Remote Computer" : "Local Computer")}</span></div>
|
||||||
</div>
|
</div>)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNavBar() {
|
renderNavBar() {
|
||||||
return <div .toolbar .navbar>
|
return <div class="toolbar navbar">
|
||||||
<div .home .button>{svg_home}</div>
|
<div class="home button">{svg_home}</div>
|
||||||
<div .goback .button>{svg_arrow}</div>
|
<div class="goback button">{svg_arrow}</div>
|
||||||
<div .goup .button>{svg_arrow}</div>
|
<div class="goup button">{svg_arrow}</div>
|
||||||
{this.renderSelect()}
|
{this.renderSelect()}
|
||||||
<div .refresh .button>{svg_refresh}</div>
|
<div class="refresh button">{svg_refresh}</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSelect() {
|
// TODO
|
||||||
return <select editable .select-dir @{this.select_dir}>
|
componentDidMount(){
|
||||||
|
this.select_dir = this.$("select.select-dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSelect() {
|
||||||
|
return (<select editable class="select-dir">
|
||||||
<option>/</option>
|
<option>/</option>
|
||||||
</select>;
|
</select>);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOpBar() {
|
renderOpBar() {
|
||||||
if (this.is_remote) {
|
if (this.is_remote) {
|
||||||
return <div .toolbar .remote>
|
return (<div class="toolbar remote">
|
||||||
<div .send .button>{svg_send}<span>{translate('Receive')}</span></div>
|
<div class="send button">{svg_send}<span>{translate('Receive')}</span></div>
|
||||||
<div .spacer></div>
|
<div class="spacer"></div>
|
||||||
<div .add-folder .button>{svg_add_folder}</div>
|
<div class="add-folder button">{svg_add_folder}</div>
|
||||||
<div .trash .button>{svg_trash}</div>
|
<div class="trash button">{svg_trash}</div>
|
||||||
</div>;
|
</div>);
|
||||||
}
|
}
|
||||||
return <div .toolbar>
|
return (<div class="toolbar">
|
||||||
<div .add-folder .button>{svg_add_folder}</div>
|
<div class="add-folder button">{svg_add_folder}</div>
|
||||||
<div .trash .button>{svg_trash}</div>
|
<div class="trash button">{svg_trash}</div>
|
||||||
<div .spacer></div>
|
<div class="spacer"></div>
|
||||||
<div .send .button><span>{translate('Send')}</span>{svg_send}</div>
|
<div class="send button"><span>{translate('Send')}</span>{svg_send}</div>
|
||||||
</div>;
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_updated() {
|
get_updated() {
|
||||||
this.table.sortRows(false);
|
this.table.sortRows(false); // TODO sortRows
|
||||||
if (this.fd && this.fd.path) this.select_dir.value = this.fd.path;
|
if (this.fd && this.fd.path) this.select_dir.value = this.fd.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTable() {
|
renderTable() {
|
||||||
var fd = this.fd;
|
let fd = this.fd;
|
||||||
var entries = fd.entries || [];
|
let entries = fd.entries || [];
|
||||||
var table = this.table;
|
let table = this.table;
|
||||||
if (!table || !table.sortBy) {
|
if (!table || !table.sortBy) {
|
||||||
entries.sort(this.foldersFirst);
|
entries.sort(this.foldersFirst); // TODO sort function
|
||||||
}
|
}
|
||||||
var me = this;
|
let path = fd.path;
|
||||||
var path = fd.path;
|
|
||||||
if (path != "/" && path) {
|
if (path != "/" && path) {
|
||||||
entries = [{ name: "..", type: 1 }].concat(entries);
|
entries = [{ name: "..", type: 1 }].concat(entries);
|
||||||
}
|
}
|
||||||
var rows = entries.map(function(e) { return me.renderRow(e); });
|
let rows = entries.map(e=>this.renderRow(e));
|
||||||
var id = (this.is_remote ? "remote" : "local") + "-folder-view";
|
let id = (this.is_remote ? "remote" : "local") + "-folder-view";
|
||||||
return <table @{this.table} .folder-view .has_current id={id}>
|
//@{} return (<table @{this.table} .folder-view .has_current id={id}>
|
||||||
|
|
||||||
|
return (<table class="folder-view has_current" id={id}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th></th><th .sortable>{translate('Name')}</th><th .sortable>{translate('Modified')}</th><th .sortable>{translate('Size')}</th></tr>
|
<tr><th></th><th class="sortable">{translate('Name')}</th><th class="sortable">{translate('Modified')}</th><th class="sortable">{translate('Size')}</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows}
|
||||||
</tbody>
|
</tbody>
|
||||||
<popup>
|
<popup>
|
||||||
<menu.context id={id}>
|
<menu class="context" id={id}>
|
||||||
<li #switch-hidden class={this.show_hidden ? "selected" : ""}><span>{svg_checkmark}</span>{translate('Show Hidden Files')}</li>
|
<li id="switch-hidden" class={this.show_hidden ? "selected" : ""}><span>{svg_checkmark}</span>{translate('Show Hidden Files')}</li>
|
||||||
</menu>
|
</menu>
|
||||||
</popup>
|
</popup>
|
||||||
</table>;
|
</table>);
|
||||||
}
|
}
|
||||||
|
|
||||||
function joinPath(name) {
|
joinPath(name) {
|
||||||
var path = this.fd.path;
|
let path = this.fd.path;
|
||||||
if (path == "/") {
|
if (path == "/") {
|
||||||
if (this.sep() == "/") return this.sep() + name;
|
if (this.sep() == "/") return this.sep() + name;
|
||||||
else return name;
|
else return name;
|
||||||
@@ -330,91 +341,89 @@ class FolderView : Reactor.Component {
|
|||||||
return path + (path[path.length - 1] == this.sep() ? "" : this.sep()) + name;
|
return path + (path[path.length - 1] == this.sep() ? "" : this.sep()) + name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function attached() {
|
attached() {
|
||||||
var me = this;
|
this.table.onRowDoubleClick = (row)=>{
|
||||||
this.table.onRowDoubleClick = function (row) {
|
let type = row[0].attributes["type"];
|
||||||
var type = row[0].attributes["type"];
|
|
||||||
if (type > 3) return;
|
if (type > 3) return;
|
||||||
var name = row[1].text;
|
let name = row[1].text;
|
||||||
var path = name == ".." ? getParentPath(me.is_remote, me.fd.path) : me.joinPath(name);
|
let path = name == ".." ? getParentPath(this.is_remote, this.fd.path) : this.joinPath(name);
|
||||||
me.goto(path, true);
|
this.goto(path, true);
|
||||||
}
|
}
|
||||||
this.get_updated();
|
this.get_updated();
|
||||||
}
|
}
|
||||||
|
|
||||||
function goto(path, push) {
|
goto(path, push) {
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
if (this.sep() == "\\" && path.length == 2) { // windows drive
|
if (this.sep() == "\\" && path.length == 2) { // windows drive
|
||||||
path += "\\";
|
path += "\\";
|
||||||
}
|
}
|
||||||
if (push) this.pushHistory();
|
if (push) this.pushHistory();
|
||||||
if (this.is_remote) {
|
if (this.is_remote) {
|
||||||
handler.read_remote_dir(path, this.show_hidden);
|
handler.xcall("read_remote_dir",path, this.show_hidden);
|
||||||
} else {
|
} else {
|
||||||
var fd = handler.read_dir(path, this.show_hidden);
|
var fd = handler.xcall("read_dir",path, this.show_hidden);
|
||||||
this.refresh({ fd: fd });
|
this.refresh({ fd: fd });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function refresh(data) {
|
refresh(data) {
|
||||||
if (!data.fd || !data.fd.path) return;
|
if (!data.fd || !data.fd.path) return;
|
||||||
if (this.is_remote && !remote_home_dir) {
|
if (this.is_remote && !remote_home_dir) {
|
||||||
remote_home_dir = data.fd.path;
|
remote_home_dir = data.fd.path;
|
||||||
}
|
}
|
||||||
this.update(data);
|
this.componentUpdate(data);
|
||||||
var me = this;
|
setTimeout(()=>this.get_updated(),1);
|
||||||
self.timer(1ms, function() { me.get_updated(); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderRow(entry) {
|
renderRow(entry) {
|
||||||
var path;
|
let path;
|
||||||
if (this.is_remote) {
|
if (this.is_remote) {
|
||||||
path = handler.get_icon_path(entry.type, getExt(entry.name));
|
path = handler.xcall("get_icon_path",entry.type, getExt(entry.name));
|
||||||
} else {
|
} else {
|
||||||
path = this.joinPath(entry.name);
|
path = this.joinPath(entry.name);
|
||||||
}
|
}
|
||||||
var tm = entry.time ? new Date(entry.time.toFloat() * 1000.).toLocaleString() : 0;
|
let tm = entry.time ? new Date(entry.time.toFloat() * 1000.).toLocaleString() : 0; // TODO toFloat()
|
||||||
return <tr role="option">
|
return (<tr role="option">
|
||||||
<td type={entry.type} filename={path}></td>
|
<td type={entry.type} filename={path}></td>
|
||||||
<td>{entry.name}</td>
|
<td>{entry.name}</td>
|
||||||
<td value={entry.time || 0}>{tm || ""}</td>
|
<td value={entry.time || 0}>{tm || ""}</td>
|
||||||
<td value={entry.size || 0}>{getSize(entry.type, entry.size)}</td>
|
<td value={entry.size || 0}>{getSize(entry.type, entry.size)}</td>
|
||||||
</tr>;
|
</tr>);
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(#switch-hidden) {
|
["on click at #switch-hidden"]() {
|
||||||
this.show_hidden = !this.show_hidden;
|
this.show_hidden = !this.show_hidden;
|
||||||
this.refreshDir();
|
this.refreshDir();
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(.goup) () {
|
["on click at .goup"]() {
|
||||||
var path = this.fd.path;
|
let path = this.fd.path;
|
||||||
if (!path || path == "/") return;
|
if (!path || path == "/") return;
|
||||||
path = getParentPath(this.is_remote, path);
|
path = getParentPath(this.is_remote, path);
|
||||||
this.goto(path, true);
|
this.goto(path, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(.goback) () {
|
["on click at .goback"] () {
|
||||||
var path = this.history.pop();
|
let path = this.history.pop();
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
this.goto(path, false);
|
this.goto(path, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(.trash) () {
|
["on click at .trash"]() {
|
||||||
var rows = this.getCurrentRows();
|
let rows = this.getCurrentRows();
|
||||||
if (!rows || rows.length == 0) return;
|
if (!rows || rows.length == 0) return;
|
||||||
|
|
||||||
var delete_dirs = new Array();
|
let delete_dirs = new Array();
|
||||||
|
|
||||||
for (var i = 0; i < rows.length; ++i) {
|
for (let i = 0; i < rows.length; ++i) {
|
||||||
var row = rows[i];
|
let row = rows[i];
|
||||||
|
|
||||||
var path = row[0];
|
let path = row[0];
|
||||||
var type = row[1];
|
let type = row[1];
|
||||||
|
|
||||||
var new_history = [];
|
let new_history = [];
|
||||||
for (var j = 0; j < this.history.length; ++j) {
|
for (let j = 0; j < this.history.length; ++j) {
|
||||||
var h = this.history[j];
|
let h = this.history[j];
|
||||||
if ((h + this.sep()).indexOf(path + this.sep()) == -1) new_history.push(h);
|
if ((h + this.sep()).indexOf(path + this.sep()) == -1) new_history.push(h);
|
||||||
}
|
}
|
||||||
this.history = new_history;
|
this.history = new_history;
|
||||||
@@ -424,97 +433,96 @@ class FolderView : Reactor.Component {
|
|||||||
confirmDelete(path, this.is_remote);
|
confirmDelete(path, this.is_remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (var i = 0; i < delete_dirs.length; ++i) {
|
for (let i = 0; i < delete_dirs.length; ++i) {
|
||||||
file_transfer.job_table.addDelDir(delete_dirs[i], this.is_remote);
|
file_transfer.job_table.addDelDir(delete_dirs[i], this.is_remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(.add-folder) () {
|
["on click at .add-folder"]() {
|
||||||
var me = this;
|
let me = this;
|
||||||
msgbox("custom", translate("Create Folder"), "<div .form> \
|
msgbox("custom", translate("Create Folder"), "<div .form> \
|
||||||
<div>" + translate("Please enter the folder name") + ":</div> \
|
<div>" + translate("Please enter the folder name") + ":</div> \
|
||||||
<div><input|text(name) .outline-focus /></div> \
|
<div><input|text(name) .outline-focus /></div> \
|
||||||
</div>", function(res=null) {
|
</div>", function(res=null) {
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
if (!res.name) return;
|
if (!res.name) return;
|
||||||
var name = res.name.trim();
|
let name = res.name.trim();
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
if (name.indexOf(me.sep()) >= 0) {
|
if (name.indexOf(me.sep()) >= 0) {
|
||||||
handler.msgbox("custom-error", "Create Folder", "Invalid folder name");
|
handler.msgbox("custom-error", "Create Folder", "Invalid folder name");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var path = me.joinPath(name);
|
let path = me.joinPath(name);
|
||||||
handler.create_dir(jobIdCounter, path, me.is_remote);
|
handler.xcall("create_dir",jobIdCounter, path, me.is_remote);
|
||||||
create_dir_jobs[jobIdCounter] = { is_remote: me.is_remote, path: path };
|
create_dir_jobs[jobIdCounter] = { is_remote: me.is_remote, path: path };
|
||||||
jobIdCounter += 1;
|
jobIdCounter += 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshDir() {
|
refreshDir() {
|
||||||
this.goto(this.fd.path, false);
|
this.goto(this.fd.path, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(.refresh) () {
|
["on click at .refresh"]() {
|
||||||
this.refreshDir();
|
this.refreshDir();
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(.home) () {
|
["on click at .home"]() {
|
||||||
var path = this.is_remote ? remote_home_dir : handler.get_home_dir();
|
let path = this.is_remote ? remote_home_dir : handler.xcall("get_home_dir");
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
if (path == this.fd.path) return;
|
if (path == this.fd.path) return;
|
||||||
this.goto(path, true);
|
this.goto(path, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentRow() {
|
getCurrentRow() {
|
||||||
var row = this.table.getCurrentRow();
|
let row = this.table.getCurrentRow(); // TEST getCurrentRow
|
||||||
if (!row) return;
|
if (!row) return;
|
||||||
var name = row[1].text;
|
let name = row[1].text;
|
||||||
if (!name || name == "..") return;
|
if (!name || name == "..") return;
|
||||||
var type = row[0].attributes["type"];
|
let type = row[0].attributes["type"];
|
||||||
return [this.joinPath(name), type];
|
return [this.joinPath(name), type];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentRows() {
|
getCurrentRows() {
|
||||||
var rows = this.table.getCurrentRows();
|
let rows = this.table.getCurrentRows();
|
||||||
if (!rows || rows.length== 0) return;
|
if (!rows || rows.length== 0) return;
|
||||||
|
|
||||||
var records = new Array();
|
let records = new Array();
|
||||||
|
|
||||||
for (var i = 0; i < rows.length; ++i) {
|
for (let i = 0; i < rows.length; ++i) {
|
||||||
var name = rows[i][1].text;
|
let name = rows[i][1].text;
|
||||||
if (!name || name == "..") continue;
|
if (!name || name == "..") continue;
|
||||||
|
|
||||||
var type = rows[i][0].attributes["type"];
|
let type = rows[i][0].attributes["type"];
|
||||||
records.push([this.joinPath(name), type]);
|
records.push([this.joinPath(name), type]);
|
||||||
}
|
}
|
||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(.send) () {
|
["on click at .send"]() {
|
||||||
var rows = this.getCurrentRows();
|
let rows = this.getCurrentRows();
|
||||||
if (!rows || rows.length == 0) return;
|
if (!rows || rows.length == 0) return;
|
||||||
for (var i = 0; i < rows.length; ++i) {
|
for (let i = 0; i < rows.length; ++i) {
|
||||||
file_transfer.job_table.send(rows[i][0], this.is_remote);
|
file_transfer.job_table.send(rows[i][0], this.is_remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event change $(.select-dir) (_, el) {
|
["on change at .select-dir"](_, el) {
|
||||||
var x = getTime() - last_key_time;
|
var x = getTime() - last_key_time; // TODO getTime
|
||||||
if (x < 1000) return;
|
if (x < 1000) return;
|
||||||
if (this.fd.path != el.value) {
|
if (this.fd.path != el.value) {
|
||||||
this.goto(el.value, true);
|
this.goto(el.value, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event keydown $(.select-dir) (evt, me) {
|
["on keydown at .select-dir"](evt, me) {
|
||||||
if (evt.keyCode == Event.VK_ENTER ||
|
if (evt.code == "KeyRETURN") { // TODO TEST mac
|
||||||
(view.mediaVar("platform") == "OSX" && evt.keyCode == 0x4C)) {
|
|
||||||
this.goto(me.value, true);
|
this.goto(me.value, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushHistory() {
|
pushHistory() {
|
||||||
var path = this.fd.path;
|
let path = this.fd.path;
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
if (path != this.history[this.history.length - 1]) this.history.push(path);
|
if (path != this.history[this.history.length - 1]) this.history.push(path);
|
||||||
}
|
}
|
||||||
@@ -522,32 +530,37 @@ class FolderView : Reactor.Component {
|
|||||||
|
|
||||||
var file_transfer;
|
var file_transfer;
|
||||||
|
|
||||||
class FileTransfer: Reactor.Component {
|
class FileTransfer extends Element {
|
||||||
function this() {
|
this() {
|
||||||
file_transfer = this;
|
file_transfer = this;
|
||||||
}
|
}
|
||||||
|
// TODO @{}
|
||||||
|
// <FolderView is_remote={false} @{this.local_folder_view} />
|
||||||
|
// <FolderView is_remote={true} @{this.remote_folder_view}/>
|
||||||
|
// <JobTable @{this.job_table} />
|
||||||
|
|
||||||
function render() {
|
render() {
|
||||||
return <div #file-transfer>
|
return (<div id="file-transfer">
|
||||||
<FolderView is_remote={false} @{this.local_folder_view} />
|
<FolderView is_remote={false} />
|
||||||
<FolderView is_remote={true} @{this.remote_folder_view}/>
|
<FolderView is_remote={true} />
|
||||||
<JobTable @{this.job_table} />
|
<JobTable />
|
||||||
</div>;
|
</div>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeFileTransfer()
|
export function initializeFileTransfer()
|
||||||
{
|
{
|
||||||
$(#file-transfer-wrapper).content(<FileTransfer />);
|
$("#file-transfer-wrapper").content(<FileTransfer />);
|
||||||
$(#video-wrapper).style.set { visibility: "hidden", position: "absolute" };
|
$("#video-wrapper").style.setProperty("visibility","hidden");
|
||||||
$(#file-transfer-wrapper).style.set { display: "block" };
|
$("#video-wrapper").style.setProperty("position","absolute");
|
||||||
|
$("#file-transfer-wrapper").style.setProperty("display","block");
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.updateFolderFiles = function(fd) {
|
handler.updateFolderFiles = function(fd) {
|
||||||
fd.entries = fd.entries || [];
|
fd.entries = fd.entries || [];
|
||||||
if (fd.id > 0) {
|
if (fd.id > 0) {
|
||||||
var jt = file_transfer.job_table;
|
let jt = file_transfer.job_table;
|
||||||
var job = jt.job_map[fd.id];
|
let job = jt.job_map[fd.id];
|
||||||
if (job) {
|
if (job) {
|
||||||
job.file_num = -1;
|
job.file_num = -1;
|
||||||
job.total_size = fd.total_size;
|
job.total_size = fd.total_size;
|
||||||
@@ -565,7 +578,7 @@ handler.jobProgress = function(id, file_num, speed, finished_size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handler.jobDone = function(id, file_num = -1) {
|
handler.jobDone = function(id, file_num = -1) {
|
||||||
var job = deleting_single_file_jobs[id] || create_dir_jobs[id];
|
let job = deleting_single_file_jobs[id] || create_dir_jobs[id];
|
||||||
if (job) {
|
if (job) {
|
||||||
refreshDir(job.is_remote);
|
refreshDir(job.is_remote);
|
||||||
return;
|
return;
|
||||||
@@ -604,7 +617,7 @@ function confirmDelete(path, is_remote) {
|
|||||||
<div.ellipsis style=\"font-weight: bold;\">" + path + "</div> \
|
<div.ellipsis style=\"font-weight: bold;\">" + path + "</div> \
|
||||||
</div>", function(res=null) {
|
</div>", function(res=null) {
|
||||||
if (res) {
|
if (res) {
|
||||||
handler.remove_file(jobIdCounter, path, 0, is_remote);
|
handler.xcall("remove_file",jobIdCounter, path, 0, is_remote);
|
||||||
deleting_single_file_jobs[jobIdCounter] = { is_remote: is_remote, path: path };
|
deleting_single_file_jobs[jobIdCounter] = { is_remote: is_remote, path: path };
|
||||||
jobIdCounter += 1;
|
jobIdCounter += 1;
|
||||||
}
|
}
|
||||||
@@ -618,7 +631,7 @@ handler.confirmDeleteFiles = function(id, i, name) {
|
|||||||
var n = job.num_entries;
|
var n = job.num_entries;
|
||||||
if (i >= n) return;
|
if (i >= n) return;
|
||||||
var file_path = job.path;
|
var file_path = job.path;
|
||||||
if (name) file_path += handler.get_path_sep(job.is_remote) + name;
|
if (name) file_path += handler.xcall("get_path_sep",job.is_remote) + name;
|
||||||
msgbox("custom-skip", "Confirm Delete", "<div .form> \
|
msgbox("custom-skip", "Confirm Delete", "<div .form> \
|
||||||
<div>" + translate('Deleting') + " #" + (i + 1) + " / " + n + " " + translate('files') + ".</div> \
|
<div>" + translate('Deleting') + " #" + (i + 1) + " / " + n + " " + translate('files') + ".</div> \
|
||||||
<div>" + translate('Are you sure you want to delete this file?') + "</div> \
|
<div>" + translate('Are you sure you want to delete this file?') + "</div> \
|
||||||
@@ -633,18 +646,18 @@ handler.confirmDeleteFiles = function(id, i, name) {
|
|||||||
} else {
|
} else {
|
||||||
job.no_confirm = res.remember;
|
job.no_confirm = res.remember;
|
||||||
if (job.no_confirm) handler.set_no_confirm(id);
|
if (job.no_confirm) handler.set_no_confirm(id);
|
||||||
handler.remove_file(id, file_path, i, job.is_remote);
|
handler.xcall("remove_file",id, file_path, i, job.is_remote);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function save_file_transfer_close_state() {
|
export function save_file_transfer_close_state() {
|
||||||
var local_dir = file_transfer.local_folder_view.fd.path || "";
|
var local_dir = file_transfer.local_folder_view.fd.path || "";
|
||||||
var local_show_hidden = file_transfer.local_folder_view.show_hidden ? "Y" : "";
|
var local_show_hidden = file_transfer.local_folder_view.show_hidden ? "Y" : "";
|
||||||
var remote_dir = file_transfer.remote_folder_view.fd.path || "";
|
var remote_dir = file_transfer.remote_folder_view.fd.path || "";
|
||||||
var remote_show_hidden = file_transfer.remote_folder_view.show_hidden ? "Y" : "";
|
var remote_show_hidden = file_transfer.remote_folder_view.show_hidden ? "Y" : "";
|
||||||
handler.save_close_state("local_dir", local_dir);
|
handler.xcall("save_close_state","local_dir", local_dir);
|
||||||
handler.save_close_state("local_show_hidden", local_show_hidden);
|
handler.xcall("save_close_state","local_show_hidden", local_show_hidden);
|
||||||
handler.save_close_state("remote_dir", remote_dir);
|
handler.xcall("save_close_state","remote_dir", remote_dir);
|
||||||
handler.save_close_state("remote_show_hidden", remote_show_hidden);
|
handler.xcall("save_close_state","remote_show_hidden", remote_show_hidden);
|
||||||
}
|
}
|
||||||
411
src/ui/header.js
Normal file
411
src/ui/header.js
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
import { handler,view,setWindowButontsAndIcon,translate,msgbox,adjustBorder,is_osx,is_xfce,svg_chat,svg_checkmark, is_linux } from "./common.js";
|
||||||
|
import {$,$$} from "@sciter";
|
||||||
|
import { adaptDisplay, audio_enabled, clipboard_enabled, keyboard_enabled } from "./remote.js";
|
||||||
|
var pi = handler.xcall("get_default_pi"); // peer information
|
||||||
|
|
||||||
|
var chat_msgs = [];
|
||||||
|
|
||||||
|
const svg_fullscreen = (<svg viewBox="0 0 357 357">
|
||||||
|
<path d="M51,229.5H0V357h127.5v-51H51V229.5z M0,127.5h51V51h76.5V0H0V127.5z M306,306h-76.5v51H357V229.5h-51V306z M229.5,0v51 H306v76.5h51V0H229.5z"/>
|
||||||
|
</svg>);
|
||||||
|
const svg_action = (<svg viewBox="-91 0 512 512"><path d="M315 211H191L298 22a15 15 0 00-13-22H105c-6 0-12 4-14 10L1 281a15 15 0 0014 20h127L61 491a15 15 0 0025 16l240-271a15 15 0 00-11-25z"/></svg>);
|
||||||
|
const svg_display = (<svg viewBox="0 0 640 512">
|
||||||
|
<path d="M592 0H48A48 48 0 0 0 0 48v320a48 48 0 0 0 48 48h240v32H112a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16H352v-32h240a48 48 0 0 0 48-48V48a48 48 0 0 0-48-48zm-16 352H64V64h512z"/>
|
||||||
|
</svg>);
|
||||||
|
const svg_secure = (<svg viewBox="0 0 347.97 347.97">
|
||||||
|
<path fill="#3F7D46" d="m317.31 54.367c-59.376 0-104.86-16.964-143.33-54.367-38.461 37.403-83.947 54.367-143.32 54.367 0 97.405-20.155 236.94 143.32 293.6 163.48-56.666 143.33-196.2 143.33-293.6zm-155.2 171.41-47.749-47.756 21.379-21.378 26.37 26.376 50.121-50.122 21.378 21.378-71.499 71.502z"/>
|
||||||
|
</svg>);
|
||||||
|
const svg_insecure = (<svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="none" stroke="red" stroke-width="14.827"/><g fill="red"><path d="M238.802 115.023l-111.573 114.68-8.6-8.367L230.2 106.656z"/><path d="M125.559 108.093l114.68 111.572-8.368 8.601-114.68-111.572z"/></g></svg>);
|
||||||
|
const svg_insecure_relay = (<svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="none" stroke="red" stroke-width="14.827"/><g fill="red"><path d="M231.442 247.498l-7.754-10.205c-17.268 12.441-38.391 17.705-59.478 14.822-21.087-2.883-39.613-13.569-52.166-30.088-25.916-34.101-17.997-82.738 17.65-108.42 32.871-23.685 78.02-19.704 105.172 7.802l-32.052 7.987 3.082 12.369 48.722-12.142-11.712-46.998-12.822 3.196 4.496 18.039c-31.933-24.008-78.103-25.342-112.642-.458-31.361 22.596-44.3 60.436-35.754 94.723 2.77 11.115 7.801 21.862 15.192 31.588 30.19 39.727 88.538 47.705 130.066 17.785z"/></g></svg>);
|
||||||
|
const svg_secure_relay = (<svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="#3f7d46" stroke="#3f7d46" stroke-width="14.827"/><g fill="red"><path d="M231.442 247.498l-7.754-10.205c-17.268 12.441-38.391 17.705-59.478 14.822-21.087-2.883-39.613-13.569-52.166-30.088-25.916-34.101-17.997-82.738 17.65-108.42 32.871-23.685 78.02-19.704 105.172 7.802l-32.052 7.987 3.082 12.369 48.722-12.142-11.712-46.998-12.822 3.196 4.496 18.039c-31.933-24.008-78.103-25.342-112.642-.458-31.361 22.596-44.3 60.436-35.754 94.723 2.77 11.115 7.801 21.862 15.192 31.588 30.19 39.727 88.538 47.705 130.066 17.785z" fill="#fff"/></g></svg>);
|
||||||
|
|
||||||
|
var cur_window_state = view.state;
|
||||||
|
|
||||||
|
|
||||||
|
if (is_linux) {
|
||||||
|
// check_state_change;
|
||||||
|
setInterval(() => {
|
||||||
|
if (view.state != cur_window_state) {
|
||||||
|
stateChanged();
|
||||||
|
}
|
||||||
|
}, 30);
|
||||||
|
} else {
|
||||||
|
view.on("statechange",()=>{
|
||||||
|
stateChanged();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_id() {
|
||||||
|
return handler.xcall("get_option","alias") || handler.xcall("get_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
function stateChanged() {
|
||||||
|
console.log('state changed from ' + cur_window_state + ' -> ' + view.state);
|
||||||
|
cur_window_state = view.state;
|
||||||
|
adjustBorder();
|
||||||
|
adaptDisplay();
|
||||||
|
if (cur_window_state != Window.WINDOW_MINIMIZED) {
|
||||||
|
view.focus = handler; // to make focus away from restore/maximize button, so that enter key work
|
||||||
|
}
|
||||||
|
let fs = view.state == Window.WINDOW_FULL_SCREEN;
|
||||||
|
let el = $("#fullscreen");
|
||||||
|
if (el) el.classList.toggle("active", fs);
|
||||||
|
el = $("#maximize");
|
||||||
|
if (el) {
|
||||||
|
el.state.disabled = fs; // TODO TEST
|
||||||
|
}
|
||||||
|
if (fs) {
|
||||||
|
$("header").style.setProperty("display","none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export var header;
|
||||||
|
var old_window_state = Window.WINDOW_SHOWN;
|
||||||
|
var input_blocked;
|
||||||
|
|
||||||
|
class Header extends Element {
|
||||||
|
this() {
|
||||||
|
header = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let icon_conn;
|
||||||
|
let title_conn;
|
||||||
|
if (this.secure_connection && this.direct_connection) {
|
||||||
|
icon_conn = svg_secure;
|
||||||
|
title_conn = translate("Direct and encrypted connection");
|
||||||
|
} else if (this.secure_connection && !this.direct_connection) {
|
||||||
|
icon_conn = svg_secure_relay;
|
||||||
|
title_conn = translate("Relayed and encrypted connection");
|
||||||
|
} else if (!this.secure_connection && this.direct_connection) {
|
||||||
|
icon_conn = svg_insecure;
|
||||||
|
title_conn = translate("Direct and unencrypted connection");
|
||||||
|
} else {
|
||||||
|
icon_conn = svg_insecure_relay;
|
||||||
|
title_conn = translate("Relayed and unencrypted connection");
|
||||||
|
}
|
||||||
|
let title = get_id();
|
||||||
|
if (pi.hostname) title += "(" + pi.username + "@" + pi.hostname + ")";
|
||||||
|
if ((pi.displays || []).length == 0) {
|
||||||
|
return (<div class="ellipsis" style="size:*;text-align:center;margin:*;">{title}</div>);
|
||||||
|
}
|
||||||
|
let screens = pi.displays.map(function(d, i) {
|
||||||
|
return <div id="screen" class={pi.current_display == i ? "current" : ""}>
|
||||||
|
{i+1}
|
||||||
|
</div>;
|
||||||
|
});
|
||||||
|
updateWindowToolbarPosition();
|
||||||
|
let style = "flow:horizontal;";
|
||||||
|
if (is_osx) style += "margin:*";
|
||||||
|
setTimeout(toggleMenuState,1);
|
||||||
|
|
||||||
|
return (<div style={style}>
|
||||||
|
{is_osx || is_xfce ? "" : <span id="fullscreen">{svg_fullscreen}</span>}
|
||||||
|
<div id="screens">
|
||||||
|
<span id="secure" title={title_conn}>{icon_conn}</span>
|
||||||
|
<div class="remote-id">{get_id()}</div>
|
||||||
|
<div style="flow:horizontal;border-spacing: 0.5em;">{screens}</div>
|
||||||
|
{this.renderGlobalScreens()}
|
||||||
|
</div>
|
||||||
|
<span id="chat">{svg_chat}</span>
|
||||||
|
<span id="action">{svg_action}</span>
|
||||||
|
<span id="display">{svg_display}</span>
|
||||||
|
{this.renderDisplayPop()}
|
||||||
|
{this.renderActionPop()}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDisplayPop() {
|
||||||
|
return (<popup>
|
||||||
|
<menu class="context" id="display-options">
|
||||||
|
<li id="adjust-window" style="display:none">{translate('Adjust Window')}</li>
|
||||||
|
<div id="adjust-window" class="separator" style="display:none"/>
|
||||||
|
<li id="original" type="view-style"><span>{svg_checkmark}</span>{translate('Original')}</li>
|
||||||
|
<li id="shrink" type="view-style"><span>{svg_checkmark}</span>{translate('Shrink')}</li>
|
||||||
|
<li id="stretch" type="view-style"><span>{svg_checkmark}</span>{translate('Stretch')}</li>
|
||||||
|
<div class="separator" />
|
||||||
|
<li id="best" type="image-quality"><span>{svg_checkmark}</span>{translate('Good image quality')}</li>
|
||||||
|
<li id="balanced" type="image-quality"><span>{svg_checkmark}</span>{translate('Balanced')}</li>
|
||||||
|
<li id="low" type="image-quality"><span>{svg_checkmark}</span>{translate('Optimize reaction time')}</li>
|
||||||
|
<li id="custom" type="image-quality"><span>{svg_checkmark}</span>{translate('Custom')}</li>
|
||||||
|
<div class="separator" />
|
||||||
|
<li id="show-remote-cursor" class="toggle-option"><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>
|
||||||
|
{audio_enabled ? <li id="disable-audio" class="toggle-option"><span>{svg_checkmark}</span>{translate('Mute')}</li> : ""}
|
||||||
|
{keyboard_enabled && clipboard_enabled ? <li id="disable-clipboard" class="toggle-option"><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""}
|
||||||
|
{keyboard_enabled ? <li id="lock-after-session-end" class="toggle-option"><span>{svg_checkmark}</span>{translate('Lock after session end')}</li> : ""}
|
||||||
|
{false && pi.platform == "Windows" ? <li id="privacy-mode" class="toggle-option"><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
|
||||||
|
</menu>
|
||||||
|
</popup>);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderActionPop() {
|
||||||
|
return (<popup>
|
||||||
|
<menu class="context" id="action-options">
|
||||||
|
<li id="transfer-file">{translate('Transfer File')}</li>
|
||||||
|
<li id="tunnel">{translate('TCP Tunneling')}</li>
|
||||||
|
<div class="separator" />
|
||||||
|
{keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ? <li id="ctrl-alt-del">{translate('Insert')} Ctrl + Alt + Del</li> : ""}
|
||||||
|
<div class="separator" />
|
||||||
|
{keyboard_enabled ? <li id="lock-screen">{translate('Insert Lock')}</li> : ""}
|
||||||
|
{false && pi.platform == "Windows" ? <li id="block-input">Block user input </li> : ""}
|
||||||
|
{handler.xcall("support_refresh") ? <li id="refresh">{translate('Refresh')}</li> : ""}
|
||||||
|
</menu>
|
||||||
|
</popup>);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderGlobalScreens() {
|
||||||
|
if (pi.displays.length < 3) return "";
|
||||||
|
let x0 = 9999999;
|
||||||
|
let y0 = 9999999;
|
||||||
|
let x = -9999999;
|
||||||
|
let y = -9999999;
|
||||||
|
pi.displays.map(function(d, i) {
|
||||||
|
if (d.x < x0) x0 = d.x;
|
||||||
|
if (d.y < y0) y0 = d.y;
|
||||||
|
let dx = d.x + d.width;
|
||||||
|
if (dx > x) x = dx;
|
||||||
|
let dy = d.y + d.height;
|
||||||
|
if (dy > y) y = dy;
|
||||||
|
});
|
||||||
|
let w = x - x0;
|
||||||
|
let h = y - y0;
|
||||||
|
let scale = 16. / h;
|
||||||
|
let screens = pi.displays.map(function(d, i) {
|
||||||
|
let min_wh = d.width > d.height ? d.height : d.width;
|
||||||
|
let fs = min_wh * 0.9 * scale;
|
||||||
|
let style = "width:" + (d.width * scale) + "px;" +
|
||||||
|
"height:" + (d.height * scale) + "px;" +
|
||||||
|
"left:" + ((d.x - x0) * scale) + "px;" +
|
||||||
|
"top:" + ((d.y - y0) * scale) + "px;" +
|
||||||
|
"font-size:" + fs + "px;";
|
||||||
|
if (is_osx) {
|
||||||
|
style += "line-height:" + fs + "px;";
|
||||||
|
}
|
||||||
|
return <div style={style} class={pi.current_display == i ? "current" : ""}>{i+1}</div>;
|
||||||
|
});
|
||||||
|
|
||||||
|
let style = "width:" + (w * scale) + "px; height:" + (h * scale) + "px;";
|
||||||
|
return <div id="global-screens" style={style}>
|
||||||
|
{screens}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #fullscreen"](_, el) {
|
||||||
|
if (view.state == Window.WINDOW_FULL_SCREEN) {
|
||||||
|
if (old_window_state == Window.WINDOW_MAXIMIZED) {
|
||||||
|
view.state = Window.WINDOW_SHOWN;
|
||||||
|
}
|
||||||
|
view.state = old_window_state;
|
||||||
|
} else {
|
||||||
|
old_window_state = view.state;
|
||||||
|
if (view.state == Window.WINDOW_MAXIMIZED) {
|
||||||
|
view.state = Window.WINDOW_SHOWN;
|
||||||
|
}
|
||||||
|
view.state = Window.WINDOW_FULL_SCREEN;
|
||||||
|
if (is_linux) { setTimeout(()=>view.state = Window.WINDOW_FULL_SCREEN,150); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #chat"]() {
|
||||||
|
startChat();
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #action"](_, me) {
|
||||||
|
let menu = $("menu#action-options");
|
||||||
|
me.popup(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #display"](_, me) {
|
||||||
|
let menu = $("menu#display-options");
|
||||||
|
me.popup(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #screen"](_, me) {
|
||||||
|
if (pi.current_display == me.index) return;
|
||||||
|
handler.xcall("switch_display",me.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #transfer-file"]() {
|
||||||
|
handler.xcall("transfer_file");
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #tunnel"] () {
|
||||||
|
handler.xcall("tunnel");
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #ctrl-alt-del"]() {
|
||||||
|
handler.xcall("ctrl_alt_del");
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #lock-screen"]() {
|
||||||
|
handler.xcall("lock_screen");
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #refresh"] () {
|
||||||
|
handler.xcall("refresh_video");
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #block-input"] (_,me) {
|
||||||
|
if (!input_blocked) {
|
||||||
|
handler.xcall("toggle_option","block-input");
|
||||||
|
input_blocked = true;
|
||||||
|
me.text = "Unblock user input"; // TEST
|
||||||
|
} else {
|
||||||
|
handler.xcall("toggle_option","unblock-input");
|
||||||
|
input_blocked = false;
|
||||||
|
me.text = "Block user input";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at menu#display-options>li"] (_, me) {
|
||||||
|
if (me.id == "custom") {
|
||||||
|
handle_custom_image_quality();
|
||||||
|
} else if (me.attributes.hasClass("toggle-option")) {
|
||||||
|
handler.toggle_option(me.id);
|
||||||
|
toggleMenuState();
|
||||||
|
} else if (!me.attributes.hasClass("selected")) {
|
||||||
|
let type = me.attributes["type"];
|
||||||
|
if (type == "image-quality") {
|
||||||
|
handler.xcall("save_image_quality",me.id);
|
||||||
|
} else if (type == "view-style") {
|
||||||
|
handler.xcall("save_view_style",me.id);
|
||||||
|
adaptDisplay();
|
||||||
|
}
|
||||||
|
toggleMenuState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_custom_image_quality() {
|
||||||
|
let tmp = handler.xcall("get_custom_image_quality");
|
||||||
|
let bitrate0 = tmp[0] || 50;
|
||||||
|
let quantizer0 = tmp.length > 1 ? tmp[1] : 100;
|
||||||
|
msgbox("custom", "Custom Image Quality", "<div .form> \
|
||||||
|
<div><input type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate0 + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% bitrate</div> \
|
||||||
|
<div><input type=\"hslider\" style=\"width: 50%\" name=\"quantizer\" max=\"100\" min=\"0\" value=\"" + quantizer0 + "\"/ buddy=\"quantizer-buddy\"><b #quantizer-buddy>x</b>% quantizer</div> \
|
||||||
|
</div>", function(res=null) {
|
||||||
|
if (!res) return;
|
||||||
|
if (!res.bitrate) return;
|
||||||
|
handler.xcall("save_custom_image_quality",res.bitrate, res.quantizer);
|
||||||
|
toggleMenuState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMenuState() {
|
||||||
|
let values = [];
|
||||||
|
let q = handler.xcall("get_image_quality");
|
||||||
|
if (!q) q = "balanced";
|
||||||
|
values.push(q);
|
||||||
|
let s = handler.xcall("get_view_style");
|
||||||
|
if (!s) s = "original";
|
||||||
|
values.push(s);
|
||||||
|
for (let el of $$("menu#display-options>li")) {
|
||||||
|
el.classList.toggle("selected", values.indexOf(el.id) >= 0);
|
||||||
|
}
|
||||||
|
for (let id of ["show-remote-cursor", "disable-audio", "disable-clipboard", "lock-after-session-end", "privacy-mode"]) {
|
||||||
|
let el = $('#' + id); // TEST
|
||||||
|
if (el) {
|
||||||
|
el.classList.toggle("selected", handler.xcall("get_toggle_option",id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_osx) {
|
||||||
|
$("header").content(<Header />);
|
||||||
|
$("header").attributes["role"] = "window-caption"; // TODO
|
||||||
|
} else {
|
||||||
|
if (handler.is_file_transfer || handler.is_port_forward) {
|
||||||
|
$("caption").content(<Header />);
|
||||||
|
} else {
|
||||||
|
$("div.window-toolbar").content(<Header />);
|
||||||
|
}
|
||||||
|
setWindowButontsAndIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(handler.is_file_transfer || handler.is_port_forward)) {
|
||||||
|
$("header").style.setProperty("height","32px");
|
||||||
|
if (!is_osx) {
|
||||||
|
$("div.window-icon").style.setProperty("size","32px");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.updatePi = function(v) {
|
||||||
|
pi = v;
|
||||||
|
header.componentUpdate();
|
||||||
|
if (handler.is_port_forward) {
|
||||||
|
view.state = Window.WINDOW_MINIMIZED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.switchDisplay = function(i) {
|
||||||
|
pi.current_display = i;
|
||||||
|
header.componentUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWindowToolbarPosition() {
|
||||||
|
if (is_osx) return;
|
||||||
|
setTimeout(function() {
|
||||||
|
let el = $("div.window-toolbar");
|
||||||
|
let w1 = el.state.box("width", "border"); // TEST
|
||||||
|
let w2 = $("header").state.box("width", "border");
|
||||||
|
let x = (w2 - w1) / 2;
|
||||||
|
el.style.setProperty("left",x + "px");
|
||||||
|
el.style.setProperty("display","block")
|
||||||
|
},1);
|
||||||
|
}
|
||||||
|
|
||||||
|
view.onsizechange = function() {
|
||||||
|
// ensure size is done, so add timer
|
||||||
|
setTimeout(function() {
|
||||||
|
updateWindowToolbarPosition();
|
||||||
|
adaptDisplay();
|
||||||
|
},1);
|
||||||
|
};
|
||||||
|
|
||||||
|
handler.newMessage = function(text) {
|
||||||
|
chat_msgs.push({text: text, name: pi.username || "", time: getNowStr()});
|
||||||
|
startChat();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMsg(text) {
|
||||||
|
chat_msgs.push({text: text, name: "me", time: getNowStr()});
|
||||||
|
handler.xcall("send_chat",text);
|
||||||
|
if (chatbox) chatbox.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
var chatbox;
|
||||||
|
function startChat() {
|
||||||
|
if (chatbox) {
|
||||||
|
chatbox.state = Window.WINDOW_SHOWN; // TODO TEST el.state
|
||||||
|
chatbox.refresh(); // TODO el.refresh
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let icon = handler.xcall("get_icon");
|
||||||
|
let [sx, sy, sw, sh] = view.screenBox("workarea", "rectw"); // TEST
|
||||||
|
let w = 300;
|
||||||
|
let h = 400;
|
||||||
|
let x = (sx + sw - w) / 2;
|
||||||
|
let y = sy + 80;
|
||||||
|
let params = {
|
||||||
|
type: Window.FRAME_WINDOW,
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
client: true,
|
||||||
|
parameters: { msgs: chat_msgs, callback: sendMsg, icon: icon },
|
||||||
|
caption: get_id(),
|
||||||
|
};
|
||||||
|
let html = handler.xcall("get_chatbox");
|
||||||
|
if (html) params.html = html;
|
||||||
|
else params.url = document.url("chatbox.html");
|
||||||
|
chatbox = view.window(params); // TEST
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.setConnectionType = function(secured, direct) {
|
||||||
|
// TEST
|
||||||
|
header.componentUpdate({
|
||||||
|
secure_connection: secured,
|
||||||
|
direct_connection: direct,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,413 +0,0 @@
|
|||||||
var pi = handler.get_default_pi(); // peer information
|
|
||||||
var chat_msgs = [];
|
|
||||||
|
|
||||||
var svg_fullscreen = <svg viewBox="0 0 357 357">
|
|
||||||
<path d="M51,229.5H0V357h127.5v-51H51V229.5z M0,127.5h51V51h76.5V0H0V127.5z M306,306h-76.5v51H357V229.5h-51V306z M229.5,0v51 H306v76.5h51V0H229.5z"/>
|
|
||||||
</svg>;
|
|
||||||
var svg_action = <svg viewBox="-91 0 512 512"><path d="M315 211H191L298 22a15 15 0 00-13-22H105c-6 0-12 4-14 10L1 281a15 15 0 0014 20h127L61 491a15 15 0 0025 16l240-271a15 15 0 00-11-25z"/></svg>;
|
|
||||||
var svg_display = <svg viewBox="0 0 640 512">
|
|
||||||
<path d="M592 0H48A48 48 0 0 0 0 48v320a48 48 0 0 0 48 48h240v32H112a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16H352v-32h240a48 48 0 0 0 48-48V48a48 48 0 0 0-48-48zm-16 352H64V64h512z"/>
|
|
||||||
</svg>;
|
|
||||||
var svg_secure = <svg viewBox="0 0 347.97 347.97">
|
|
||||||
<path fill="#3F7D46" d="m317.31 54.367c-59.376 0-104.86-16.964-143.33-54.367-38.461 37.403-83.947 54.367-143.32 54.367 0 97.405-20.155 236.94 143.32 293.6 163.48-56.666 143.33-196.2 143.33-293.6zm-155.2 171.41-47.749-47.756 21.379-21.378 26.37 26.376 50.121-50.122 21.378 21.378-71.499 71.502z"/>
|
|
||||||
</svg>;
|
|
||||||
var svg_insecure = <svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="none" stroke="red" stroke-width="14.827"/><g fill="red"><path d="M238.802 115.023l-111.573 114.68-8.6-8.367L230.2 106.656z"/><path d="M125.559 108.093l114.68 111.572-8.368 8.601-114.68-111.572z"/></g></svg>;
|
|
||||||
var svg_insecure_relay = <svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="none" stroke="red" stroke-width="14.827"/><g fill="red"><path d="M231.442 247.498l-7.754-10.205c-17.268 12.441-38.391 17.705-59.478 14.822-21.087-2.883-39.613-13.569-52.166-30.088-25.916-34.101-17.997-82.738 17.65-108.42 32.871-23.685 78.02-19.704 105.172 7.802l-32.052 7.987 3.082 12.369 48.722-12.142-11.712-46.998-12.822 3.196 4.496 18.039c-31.933-24.008-78.103-25.342-112.642-.458-31.361 22.596-44.3 60.436-35.754 94.723 2.77 11.115 7.801 21.862 15.192 31.588 30.19 39.727 88.538 47.705 130.066 17.785z"/></g></svg>;
|
|
||||||
var svg_secure_relay = <svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="#3f7d46" stroke="#3f7d46" stroke-width="14.827"/><g fill="red"><path d="M231.442 247.498l-7.754-10.205c-17.268 12.441-38.391 17.705-59.478 14.822-21.087-2.883-39.613-13.569-52.166-30.088-25.916-34.101-17.997-82.738 17.65-108.42 32.871-23.685 78.02-19.704 105.172 7.802l-32.052 7.987 3.082 12.369 48.722-12.142-11.712-46.998-12.822 3.196 4.496 18.039c-31.933-24.008-78.103-25.342-112.642-.458-31.361 22.596-44.3 60.436-35.754 94.723 2.77 11.115 7.801 21.862 15.192 31.588 30.19 39.727 88.538 47.705 130.066 17.785z" fill="#fff"/></g></svg>;
|
|
||||||
|
|
||||||
var cur_window_state = view.windowState;
|
|
||||||
function check_state_change() {
|
|
||||||
if (view.windowState != cur_window_state) {
|
|
||||||
stateChanged();
|
|
||||||
}
|
|
||||||
self.timer(30ms, check_state_change);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_linux) {
|
|
||||||
check_state_change();
|
|
||||||
} else {
|
|
||||||
view << event statechange {
|
|
||||||
stateChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_id() {
|
|
||||||
return handler.get_option('alias') || handler.get_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
function stateChanged() {
|
|
||||||
stdout.println('state changed from ' + cur_window_state + ' -> ' + view.windowState);
|
|
||||||
cur_window_state = view.windowState;
|
|
||||||
adjustBorder();
|
|
||||||
adaptDisplay();
|
|
||||||
if (cur_window_state != View.WINDOW_MINIMIZED) {
|
|
||||||
view.focus = handler; // to make focus away from restore/maximize button, so that enter key work
|
|
||||||
}
|
|
||||||
var fs = view.windowState == View.WINDOW_FULL_SCREEN;
|
|
||||||
var el = $(#fullscreen);
|
|
||||||
if (el) el.attributes.toggleClass("active", fs);
|
|
||||||
el = $(#maximize);
|
|
||||||
if (el) {
|
|
||||||
el.state.disabled = fs;
|
|
||||||
}
|
|
||||||
if (fs) {
|
|
||||||
$(header).style.set {
|
|
||||||
display: "none",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var header;
|
|
||||||
var old_window_state = View.WINDOW_SHOWN;
|
|
||||||
var input_blocked;
|
|
||||||
|
|
||||||
class Header: Reactor.Component {
|
|
||||||
function this() {
|
|
||||||
header = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
var icon_conn;
|
|
||||||
var title_conn;
|
|
||||||
if (this.secure_connection && this.direct_connection) {
|
|
||||||
icon_conn = svg_secure;
|
|
||||||
title_conn = translate("Direct and encrypted connection");
|
|
||||||
} else if (this.secure_connection && !this.direct_connection) {
|
|
||||||
icon_conn = svg_secure_relay;
|
|
||||||
title_conn = translate("Relayed and encrypted connection");
|
|
||||||
} else if (!this.secure_connection && this.direct_connection) {
|
|
||||||
icon_conn = svg_insecure;
|
|
||||||
title_conn = translate("Direct and unencrypted connection");
|
|
||||||
} else {
|
|
||||||
icon_conn = svg_insecure_relay;
|
|
||||||
title_conn = translate("Relayed and unencrypted connection");
|
|
||||||
}
|
|
||||||
var title = get_id();
|
|
||||||
if (pi.hostname) title += "(" + pi.username + "@" + pi.hostname + ")";
|
|
||||||
if ((pi.displays || []).length == 0) {
|
|
||||||
return <div .ellipsis style="size:*;text-align:center;margin:*;">{title}</div>;
|
|
||||||
}
|
|
||||||
var screens = pi.displays.map(function(d, i) {
|
|
||||||
return <div #screen class={pi.current_display == i ? "current" : ""}>
|
|
||||||
{i+1}
|
|
||||||
</div>;
|
|
||||||
});
|
|
||||||
updateWindowToolbarPosition();
|
|
||||||
var style = "flow:horizontal;";
|
|
||||||
if (is_osx) style += "margin:*";
|
|
||||||
self.timer(1ms, toggleMenuState);
|
|
||||||
return <div style={style}>
|
|
||||||
{is_osx || is_xfce ? "" : <span #fullscreen>{svg_fullscreen}</span>}
|
|
||||||
<div #screens>
|
|
||||||
<span #secure title={title_conn}>{icon_conn}</span>
|
|
||||||
<div .remote-id>{get_id()}</div>
|
|
||||||
<div style="flow:horizontal;border-spacing: 0.5em;">{screens}</div>
|
|
||||||
{this.renderGlobalScreens()}
|
|
||||||
</div>
|
|
||||||
<span #chat>{svg_chat}</span>
|
|
||||||
<span #action>{svg_action}</span>
|
|
||||||
<span #display>{svg_display}</span>
|
|
||||||
{this.renderDisplayPop()}
|
|
||||||
{this.renderActionPop()}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDisplayPop() {
|
|
||||||
return <popup>
|
|
||||||
<menu.context #display-options>
|
|
||||||
<li #adjust-window style="display:none">{translate('Adjust Window')}</li>
|
|
||||||
<div #adjust-window .separator style="display:none"/>
|
|
||||||
<li #original type="view-style"><span>{svg_checkmark}</span>{translate('Original')}</li>
|
|
||||||
<li #shrink type="view-style"><span>{svg_checkmark}</span>{translate('Shrink')}</li>
|
|
||||||
<li #stretch type="view-style"><span>{svg_checkmark}</span>{translate('Stretch')}</li>
|
|
||||||
<div .separator />
|
|
||||||
<li #best type="image-quality"><span>{svg_checkmark}</span>{translate('Good image quality')}</li>
|
|
||||||
<li #balanced type="image-quality"><span>{svg_checkmark}</span>{translate('Balanced')}</li>
|
|
||||||
<li #low type="image-quality"><span>{svg_checkmark}</span>{translate('Optimize reaction time')}</li>
|
|
||||||
<li #custom type="image-quality"><span>{svg_checkmark}</span>{translate('Custom')}</li>
|
|
||||||
<div .separator />
|
|
||||||
<li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>
|
|
||||||
{audio_enabled ? <li #disable-audio .toggle-option><span>{svg_checkmark}</span>{translate('Mute')}</li> : ""}
|
|
||||||
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""}
|
|
||||||
{keyboard_enabled ? <li #lock-after-session-end .toggle-option><span>{svg_checkmark}</span>{translate('Lock after session end')}</li> : ""}
|
|
||||||
{false && pi.platform == "Windows" ? <li #privacy-mode .toggle-option><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
|
|
||||||
</menu>
|
|
||||||
</popup>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderActionPop() {
|
|
||||||
return <popup>
|
|
||||||
<menu.context #action-options>
|
|
||||||
<li #transfer-file>{translate('Transfer File')}</li>
|
|
||||||
<li #tunnel>{translate('TCP Tunneling')}</li>
|
|
||||||
<div .separator />
|
|
||||||
{keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ? <li #ctrl-alt-del>{translate('Insert')} Ctrl + Alt + Del</li> : ""}
|
|
||||||
<div .separator />
|
|
||||||
{keyboard_enabled ? <li #lock-screen>{translate('Insert Lock')}</li> : ""}
|
|
||||||
{false && pi.platform == "Windows" ? <li #block-input>Block user input </li> : ""}
|
|
||||||
{handler.support_refresh() ? <li #refresh>{translate('Refresh')}</li> : ""}
|
|
||||||
</menu>
|
|
||||||
</popup>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderGlobalScreens() {
|
|
||||||
if (pi.displays.length < 3) return "";
|
|
||||||
var x0 = 9999999;
|
|
||||||
var y0 = 9999999;
|
|
||||||
var x = -9999999;
|
|
||||||
var y = -9999999;
|
|
||||||
pi.displays.map(function(d, i) {
|
|
||||||
if (d.x < x0) x0 = d.x;
|
|
||||||
if (d.y < y0) y0 = d.y;
|
|
||||||
var dx = d.x + d.width;
|
|
||||||
if (dx > x) x = dx;
|
|
||||||
var dy = d.y + d.height;
|
|
||||||
if (dy > y) y = dy;
|
|
||||||
});
|
|
||||||
var w = x - x0;
|
|
||||||
var h = y - y0;
|
|
||||||
var scale = 16. / h;
|
|
||||||
var screens = pi.displays.map(function(d, i) {
|
|
||||||
var min_wh = d.width > d.height ? d.height : d.width;
|
|
||||||
var fs = min_wh * 0.9 * scale;
|
|
||||||
var style = "width:" + (d.width * scale) + "px;" +
|
|
||||||
"height:" + (d.height * scale) + "px;" +
|
|
||||||
"left:" + ((d.x - x0) * scale) + "px;" +
|
|
||||||
"top:" + ((d.y - y0) * scale) + "px;" +
|
|
||||||
"font-size:" + fs + "px;";
|
|
||||||
if (is_osx) {
|
|
||||||
style += "line-height:" + fs + "px;";
|
|
||||||
}
|
|
||||||
return <div style={style} class={pi.current_display == i ? "current" : ""}>{i+1}</div>;
|
|
||||||
});
|
|
||||||
|
|
||||||
var style = "width:" + (w * scale) + "px; height:" + (h * scale) + "px;";
|
|
||||||
return <div #global-screens style={style}>
|
|
||||||
{screens}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#fullscreen) (_, el) {
|
|
||||||
if (view.windowState == View.WINDOW_FULL_SCREEN) {
|
|
||||||
if (old_window_state == View.WINDOW_MAXIMIZED) {
|
|
||||||
view.windowState = View.WINDOW_SHOWN;
|
|
||||||
}
|
|
||||||
view.windowState = old_window_state;
|
|
||||||
} else {
|
|
||||||
old_window_state = view.windowState;
|
|
||||||
if (view.windowState == View.WINDOW_MAXIMIZED) {
|
|
||||||
view.windowState = View.WINDOW_SHOWN;
|
|
||||||
}
|
|
||||||
view.windowState = View.WINDOW_FULL_SCREEN;
|
|
||||||
if (is_linux) { self.timer(150ms, function() { view.windowState = View.WINDOW_FULL_SCREEN; }); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#chat) {
|
|
||||||
startChat();
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#action) (_, me) {
|
|
||||||
var menu = $(menu#action-options);
|
|
||||||
me.popup(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#display) (_, me) {
|
|
||||||
var menu = $(menu#display-options);
|
|
||||||
me.popup(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#screen) (_, me) {
|
|
||||||
if (pi.current_display == me.index) return;
|
|
||||||
handler.switch_display(me.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#transfer-file) {
|
|
||||||
handler.transfer_file();
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#tunnel) {
|
|
||||||
handler.tunnel();
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#ctrl-alt-del) {
|
|
||||||
handler.ctrl_alt_del();
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#lock-screen) {
|
|
||||||
handler.lock_screen();
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#refresh) {
|
|
||||||
handler.refresh_video();
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#block-input) {
|
|
||||||
if (!input_blocked) {
|
|
||||||
handler.toggle_option("block-input");
|
|
||||||
input_blocked = true;
|
|
||||||
$(#block-input).text = "Unblock user input";
|
|
||||||
} else {
|
|
||||||
handler.toggle_option("unblock-input");
|
|
||||||
input_blocked = false;
|
|
||||||
$(#block-input).text = "Block user input";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(menu#display-options>li) (_, me) {
|
|
||||||
if (me.id == "custom") {
|
|
||||||
handle_custom_image_quality();
|
|
||||||
} else if (me.attributes.hasClass("toggle-option")) {
|
|
||||||
handler.toggle_option(me.id);
|
|
||||||
toggleMenuState();
|
|
||||||
} else if (!me.attributes.hasClass("selected")) {
|
|
||||||
var type = me.attributes["type"];
|
|
||||||
if (type == "image-quality") {
|
|
||||||
handler.save_image_quality(me.id);
|
|
||||||
} else if (type == "view-style") {
|
|
||||||
handler.save_view_style(me.id);
|
|
||||||
adaptDisplay();
|
|
||||||
}
|
|
||||||
toggleMenuState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handle_custom_image_quality() {
|
|
||||||
var tmp = handler.get_custom_image_quality();
|
|
||||||
var bitrate0 = tmp[0] || 50;
|
|
||||||
var quantizer0 = tmp.length > 1 ? tmp[1] : 100;
|
|
||||||
msgbox("custom", "Custom Image Quality", "<div .form> \
|
|
||||||
<div><input type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate0 + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% bitrate</div> \
|
|
||||||
<div><input type=\"hslider\" style=\"width: 50%\" name=\"quantizer\" max=\"100\" min=\"0\" value=\"" + quantizer0 + "\"/ buddy=\"quantizer-buddy\"><b #quantizer-buddy>x</b>% quantizer</div> \
|
|
||||||
</div>", function(res=null) {
|
|
||||||
if (!res) return;
|
|
||||||
if (!res.bitrate) return;
|
|
||||||
handler.save_custom_image_quality(res.bitrate, res.quantizer);
|
|
||||||
toggleMenuState();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleMenuState() {
|
|
||||||
var values = [];
|
|
||||||
var q = handler.get_image_quality();
|
|
||||||
if (!q) q = "balanced";
|
|
||||||
values.push(q);
|
|
||||||
var s = handler.get_view_style();
|
|
||||||
if (!s) s = "original";
|
|
||||||
values.push(s);
|
|
||||||
for (var el in $$(menu#display-options>li)) {
|
|
||||||
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
|
|
||||||
}
|
|
||||||
for (var id in ["show-remote-cursor", "disable-audio", "disable-clipboard", "lock-after-session-end", "privacy-mode"]) {
|
|
||||||
var el = self.select('#' + id);
|
|
||||||
if (el) {
|
|
||||||
el.attributes.toggleClass("selected", handler.get_toggle_option(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_osx) {
|
|
||||||
$(header).content(<Header />);
|
|
||||||
$(header).attributes["role"] = "window-caption";
|
|
||||||
} else {
|
|
||||||
if (is_file_transfer || is_port_forward) {
|
|
||||||
$(caption).content(<Header />);
|
|
||||||
} else {
|
|
||||||
$(div.window-toolbar).content(<Header />);
|
|
||||||
}
|
|
||||||
setWindowButontsAndIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(is_file_transfer || is_port_forward)) {
|
|
||||||
$(header).style.set {
|
|
||||||
height: "32px",
|
|
||||||
};
|
|
||||||
if (!is_osx) {
|
|
||||||
$(div.window-icon).style.set {
|
|
||||||
size: "32px",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.updatePi = function(v) {
|
|
||||||
pi = v;
|
|
||||||
header.update();
|
|
||||||
if (is_port_forward) {
|
|
||||||
view.windowState = View.WINDOW_MINIMIZED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.switchDisplay = function(i) {
|
|
||||||
pi.current_display = i;
|
|
||||||
header.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateWindowToolbarPosition() {
|
|
||||||
if (is_osx) return;
|
|
||||||
self.timer(1ms, function() {
|
|
||||||
var el = $(div.window-toolbar);
|
|
||||||
var w1 = el.box(#width, #border);
|
|
||||||
var w2 = $(header).box(#width, #border);
|
|
||||||
var x = (w2 - w1) / 2;
|
|
||||||
el.style.set {
|
|
||||||
left: x + "px",
|
|
||||||
display: "block",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
view.on("size", function() {
|
|
||||||
// ensure size is done, so add timer
|
|
||||||
self.timer(1ms, function() {
|
|
||||||
updateWindowToolbarPosition();
|
|
||||||
adaptDisplay();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
handler.newMessage = function(text) {
|
|
||||||
chat_msgs.push({text: text, name: pi.username || "", time: getNowStr()});
|
|
||||||
startChat();
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMsg(text) {
|
|
||||||
chat_msgs.push({text: text, name: "me", time: getNowStr()});
|
|
||||||
handler.send_chat(text);
|
|
||||||
if (chatbox) chatbox.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
var chatbox;
|
|
||||||
function startChat() {
|
|
||||||
if (chatbox) {
|
|
||||||
chatbox.windowState = View.WINDOW_SHOWN;
|
|
||||||
chatbox.refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var icon = handler.get_icon();
|
|
||||||
var (sx, sy, sw, sh) = view.screenBox(#workarea, #rectw);
|
|
||||||
var w = 300;
|
|
||||||
var h = 400;
|
|
||||||
var x = (sx + sw - w) / 2;
|
|
||||||
var y = sy + 80;
|
|
||||||
var params = {
|
|
||||||
type: View.FRAME_WINDOW,
|
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
client: true,
|
|
||||||
parameters: { msgs: chat_msgs, callback: sendMsg, icon: icon },
|
|
||||||
caption: get_id(),
|
|
||||||
};
|
|
||||||
var html = handler.get_chatbox();
|
|
||||||
if (html) params.html = html;
|
|
||||||
else params.url = self.url("chatbox.html");
|
|
||||||
chatbox = view.window(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.setConnectionType = function(secured, direct) {
|
|
||||||
header.update({
|
|
||||||
secure_connection: secured,
|
|
||||||
direct_connection: direct,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<style>
|
<link rel="stylesheet" href="common.css">
|
||||||
@import url(common.css);
|
<link rel="stylesheet" href="index.css">
|
||||||
@import url(index.css);
|
<script type="module" src="index.js"></script>
|
||||||
</style>
|
<!-- <script type="text/tiscript">
|
||||||
<script type="text/tiscript">
|
|
||||||
include "common.tis";
|
include "common.tis";
|
||||||
include "ab.tis";
|
include "ab.tis";
|
||||||
include "index.tis";
|
include "index.tis";
|
||||||
</script>
|
</script> -->
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
730
src/ui/index.js
Normal file
730
src/ui/index.js
Normal file
@@ -0,0 +1,730 @@
|
|||||||
|
import { is_osx,view,OS,handler,translate,msgbox,is_win,svg_checkmark,svg_edit,isReasonableSize,centerize,svg_eye, PasswordComponent } from "./common";
|
||||||
|
import { SearchBar,SessionStyle,SessionList, MultipleSessions } from "./ab.js";
|
||||||
|
import {$} from "@sciter"; //TEST $$ import
|
||||||
|
|
||||||
|
if (is_osx) view.blurBehind = "light";
|
||||||
|
console.log("current platform:", OS);
|
||||||
|
console.log("wayland",handler.xcall("is_login_wayland"));
|
||||||
|
// html min-width, min-height not working on mac, below works for all
|
||||||
|
view.minSize = [500, 300]; // TODO not work on ubuntu
|
||||||
|
|
||||||
|
export var app; // 注意判空
|
||||||
|
var tmp = handler.xcall("get_connect_status");
|
||||||
|
var connect_status = tmp[0];
|
||||||
|
var service_stopped = false;
|
||||||
|
var software_update_url = "";
|
||||||
|
var key_confirmed = tmp[1];
|
||||||
|
var system_error = "";
|
||||||
|
|
||||||
|
export const svg_menu = <svg id="menu" viewBox="0 0 512 512">
|
||||||
|
<circle cx="256" cy="256" r="64"/>
|
||||||
|
<circle cx="256" cy="448" r="64"/>
|
||||||
|
<circle cx="256" cy="64" r="64"/>
|
||||||
|
</svg>;
|
||||||
|
|
||||||
|
var my_id = "";
|
||||||
|
function get_id() {
|
||||||
|
my_id = handler.xcall("get_id");
|
||||||
|
return my_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectStatus extends Element {
|
||||||
|
render() {
|
||||||
|
return(<div class="connect-status">
|
||||||
|
<span class={"connect-status-icon connect-status" + (service_stopped ? 0 : connect_status)} />
|
||||||
|
{this.getConnectStatusStr()}
|
||||||
|
{service_stopped ? <span class="link" id="start-service">{translate('Start Service')}</span> : ""}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
getConnectStatusStr() {
|
||||||
|
if (service_stopped) {
|
||||||
|
return translate("Service is not running");
|
||||||
|
} else if (connect_status == -1) {
|
||||||
|
return translate('not_ready_status');
|
||||||
|
} else if (connect_status == 0) {
|
||||||
|
return translate('connecting_status');
|
||||||
|
}
|
||||||
|
return translate("Ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #start-service"]() {
|
||||||
|
handler.xcall("set_option","stop-service", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createNewConnect(id, type) {
|
||||||
|
id = id.replace(/\s/g, "");
|
||||||
|
app.remote_id.value = formatId(id);
|
||||||
|
if (!id) return;
|
||||||
|
if (id == my_id) {
|
||||||
|
msgbox("custom-error", "Error", "You cannot connect to your own computer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handler.xcall("set_remote_id",id);
|
||||||
|
handler.xcall("new_remote",id, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
var direct_server;
|
||||||
|
class DirectServer extends Element {
|
||||||
|
this() {
|
||||||
|
direct_server = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var text = translate("Enable Direct IP Access");
|
||||||
|
var cls = handler.xcall("get_option", "direct-server") == "Y" ? "selected" : "line-through";
|
||||||
|
return <li class={cls}><span>{svg_checkmark}</span>{text}</li>;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick() {
|
||||||
|
handler.xcall("set_option", "direct-server", handler.xcall("get_option", "direct-server") == "Y" ? "" : "Y");
|
||||||
|
this.componentUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var myIdMenu;
|
||||||
|
var audioInputMenu;
|
||||||
|
class AudioInputs extends Element {
|
||||||
|
this() {
|
||||||
|
audioInputMenu = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// TODO this.show
|
||||||
|
if (!this.show) return <li />;
|
||||||
|
let inputs = handler.xcall("get_sound_inputs");
|
||||||
|
if (is_win) inputs = ["System Sound"].concat(inputs);
|
||||||
|
if (!inputs.length) return <div/>;
|
||||||
|
inputs = ["Mute"].concat(inputs);
|
||||||
|
setTimeout(()=>this.toggleMenuState(),1);
|
||||||
|
return (<li>{translate('Audio Input')}
|
||||||
|
<menu id="audio-input" key={inputs.length}>
|
||||||
|
{inputs.map((name)=><li id={name}><span>{svg_checkmark}</span>{translate(name)}</li>)}
|
||||||
|
</menu>
|
||||||
|
</li>);
|
||||||
|
}
|
||||||
|
|
||||||
|
get_default() {
|
||||||
|
if (is_win) return "System Sound";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
get_value() {
|
||||||
|
return handler.xcall("get_option","audio-input") || this.get_default();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMenuState() {
|
||||||
|
let v = this.get_value();
|
||||||
|
for (let el of this.$$("menu#audio-input>li")) {
|
||||||
|
let selected = el.id == v;
|
||||||
|
el.classList.toggle("selected", selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at menu#audio-input>li"](_, me) {
|
||||||
|
let v = me.id;
|
||||||
|
if (v == this.get_value()) return;
|
||||||
|
if (v == this.get_default()) v = "";
|
||||||
|
handler.xcall("set_option","audio-input", v);
|
||||||
|
this.toggleMenuState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyIdMenu extends Element {
|
||||||
|
this() {
|
||||||
|
myIdMenu = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (<div id="myid">
|
||||||
|
{this.renderPop()}
|
||||||
|
ID{svg_menu}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPop() {
|
||||||
|
return (<popup>
|
||||||
|
<menu class="context" id="config-options">
|
||||||
|
<li id="enable-keyboard"><span>{svg_checkmark}</span>{translate('Enable Keyboard/Mouse')}</li>
|
||||||
|
<li id="enable-clipboard"><span>{svg_checkmark}</span>{translate('Enable Clipboard')}</li>
|
||||||
|
<li id="enable-file-transfer"><span>{svg_checkmark}</span>{translate('Enable File Transfer')}</li>
|
||||||
|
<li id="enable-tunnel"><span>{svg_checkmark}</span>{translate('Enable TCP Tunneling')}</li>
|
||||||
|
<AudioInputs />
|
||||||
|
<div class="separator" />
|
||||||
|
<li id="whitelist" title={translate('whitelist_tip')}>{translate('IP Whitelisting')}</li>
|
||||||
|
<li id="custom-server">{translate('ID/Relay Server')}</li>
|
||||||
|
<li id="socks5-server">{translate('Socks5 Proxy')}</li>
|
||||||
|
<div class="separator" />
|
||||||
|
<li id="stop-service" class={service_stopped ? "line-through" : "selected"}><span>{svg_checkmark}</span>{translate("Enable Service")}</li>
|
||||||
|
<DirectServer />
|
||||||
|
<div class="separator" />
|
||||||
|
<li id="about">{translate('About')} {" "} {handler.xcall("get_app_name")}</li>
|
||||||
|
</menu>
|
||||||
|
</popup>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
["on click at svg#menu"](_, me) {
|
||||||
|
|
||||||
|
audioInputMenu.componentUpdate({ show: true });
|
||||||
|
this.toggleMenuState();
|
||||||
|
let menu = this.$("menu#config-options");
|
||||||
|
me.popup(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMenuState() {
|
||||||
|
for (let el of this.$$("menu#config-options>li")) {
|
||||||
|
if (el.id && el.id.indexOf("enable-") == 0) {
|
||||||
|
let enabled = handler.xcall("get_option",el.id) != "N";
|
||||||
|
console.log(el.id,enabled)
|
||||||
|
el.classList.toggle("selected", enabled);
|
||||||
|
el.classList.toggle("line-through", !enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at menu#config-options>li"] (_, me) {
|
||||||
|
if (me.id && me.id.indexOf("enable-") == 0) {
|
||||||
|
handler.xcall("set_option",me.id, handler.xcall("get_option",me.id) == "N" ? "" : "N");
|
||||||
|
}
|
||||||
|
if (me.id == "whitelist") {
|
||||||
|
let old_value = handler.xcall("get_option","whitelist").split(",").join("\n");
|
||||||
|
msgbox("custom-whitelist", translate("IP Whitelisting"), "<div class='form'> \
|
||||||
|
<div>" + translate("whitelist_sep") + "</div> \
|
||||||
|
<textarea spellcheck=\"false\" name=\"text\" novalue=\"0.0.0.0\" style=\"overflow: scroll-indicator; width:*; height: 160px; font-size: 1.2em; padding: 0.5em;\">" + old_value + "</textarea>\
|
||||||
|
</div> \
|
||||||
|
",
|
||||||
|
function(res=null) {
|
||||||
|
if (!res) return;
|
||||||
|
let value = (res.text || "").trim();
|
||||||
|
if (value) {
|
||||||
|
let values = value.split(/[\s,;\n]+/g);
|
||||||
|
for (let ip in values) {
|
||||||
|
if (!ip.match(/^\d+\.\d+\.\d+\.\d+$/)) {
|
||||||
|
return translate("Invalid IP") + ": " + ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = values.join("\n");
|
||||||
|
}
|
||||||
|
if (value == old_value) return;
|
||||||
|
console.log("whitelist updated");
|
||||||
|
handler.xcall("set_option","whitelist", value.replace("\n", ","));
|
||||||
|
}, 300);
|
||||||
|
} else if (me.id == "custom-server") {
|
||||||
|
let configOptions = handler.xcall("get_options");
|
||||||
|
let old_relay = configOptions["relay-server"] || "";
|
||||||
|
let old_id = configOptions["custom-rendezvous-server"] || "";
|
||||||
|
msgbox("custom-server", "ID/Relay Server", "<div class='form'> \
|
||||||
|
<div><span style='width: 100px; display:inline-block'>" + translate("ID Server") + ": </span><input .outline-focus style='width: 250px' name='id' value='" + old_id + "' /></div> \
|
||||||
|
<div><span style='width: 100px; display:inline-block'>" + translate("Relay Server") + ": </span><input style='width: 250px' name='relay' value='" + old_relay + "' /></div> \
|
||||||
|
</div> \
|
||||||
|
",
|
||||||
|
function(res=null) {
|
||||||
|
if (!res) return;
|
||||||
|
let id = (res.id || "").trim();
|
||||||
|
let relay = (res.relay || "").trim();
|
||||||
|
if (id == old_id && relay == old_relay) return;
|
||||||
|
if (id) {
|
||||||
|
let err = handler.xcall("test_if_valid_server",id);
|
||||||
|
if (err) return translate("ID Server") + ": " + err;
|
||||||
|
}
|
||||||
|
if (relay) {
|
||||||
|
let err = handler.xcall("test_if_valid_server",relay);
|
||||||
|
if (err) return translate("Relay Server") + ": " + err;
|
||||||
|
}
|
||||||
|
configOptions["custom-rendezvous-server"] = id;
|
||||||
|
configOptions["relay-server"] = relay;
|
||||||
|
handler.xcall("set_options",configOptions);
|
||||||
|
}, 240);
|
||||||
|
} else if (me.id == "socks5-server") {
|
||||||
|
var socks5 = handler.xcall("get_socks") || {};
|
||||||
|
var old_proxy = socks5[0] || "";
|
||||||
|
var old_username = socks5[1] || "";
|
||||||
|
var old_password = socks5[2] || "";
|
||||||
|
msgbox("custom-server", "Socks5 Proxy", <div class="form set-password">
|
||||||
|
<div><span>{translate("Hostname")}</span><input class="outline-focus" style='width: *' name='proxy' value={old_proxy} /></div>
|
||||||
|
<div><span>{translate("Username")}</span><input style='width: *' name='username' value={old_username} /></div>
|
||||||
|
<div><span>{translate("Password")}</span><PasswordComponent value={old_password} /></div>
|
||||||
|
</div>
|
||||||
|
, function(res=null) {
|
||||||
|
if (!res) return;
|
||||||
|
var proxy = (res.proxy || "").trim();
|
||||||
|
var username = (res.username || "").trim();
|
||||||
|
var password = (res.password || "").trim();
|
||||||
|
if (proxy == old_proxy && username == old_username && password == old_password) return;
|
||||||
|
if (proxy) {
|
||||||
|
var err = handler.xcall("test_if_valid_server", proxy);
|
||||||
|
if (err) return translate("Server") + ": " + err;
|
||||||
|
}
|
||||||
|
handler.xcall("set_socks", proxy, username, password);
|
||||||
|
}, 240);
|
||||||
|
} else if (me.id == "stop-service") {
|
||||||
|
handler.xcall("set_option","stop-service", service_stopped ? "" : "Y");
|
||||||
|
} else if (me.id == "about") {
|
||||||
|
let name = handler.xcall("get_app_name");
|
||||||
|
msgbox("custom-nocancel-nook-hasclose", "About " + name, "<div style='line-height: 2em'> \
|
||||||
|
<div>Version: " + handler.xcall("get_version") + " \
|
||||||
|
<div class='link custom-event' url='http://rustdesk.com/privacy'>Privacy Statement</div> \
|
||||||
|
<div class='link custom-event' url='http://rustdesk.com'>Website</div> \
|
||||||
|
<div style='background: #2c8cff; color: white; padding: 1em; margin-top: 1em;'>Copyright © 2020 CarrieZ Studio \
|
||||||
|
<br /> Author: Carrie \
|
||||||
|
<p style='font-weight: bold'>Made with heart in this chaotic world!</p>\
|
||||||
|
</div>\
|
||||||
|
</div>",
|
||||||
|
function(el) {
|
||||||
|
if (el && el.attributes) {
|
||||||
|
handler.xcall("open_url",el.attributes['url']);
|
||||||
|
};
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class App extends Element{
|
||||||
|
remote_id;
|
||||||
|
recent_sessions;
|
||||||
|
connect_status;
|
||||||
|
this() {
|
||||||
|
app = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
this.remote_id = this.$("#ID");
|
||||||
|
this.multipleSessions = this.$("#multipleSessions");
|
||||||
|
this.connect_status = this.$("#ConnectStatus");
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let is_can_screen_recording = handler.xcall("is_can_screen_recording",false);
|
||||||
|
return(<div class="app">
|
||||||
|
<popup>
|
||||||
|
<menu class="context" id="edit-password-context">
|
||||||
|
<li id="refresh-password">{translate('Refresh random password')}</li>
|
||||||
|
<li id="set-password">{translate('Set your own password')}</li>
|
||||||
|
</menu>
|
||||||
|
</popup>
|
||||||
|
<div class="left-pane">
|
||||||
|
<div>
|
||||||
|
<div class="title">{translate('Your Desktop')}</div>
|
||||||
|
<div class="lighter-text">{translate('desk_tip')}</div>
|
||||||
|
<div class="your-desktop">
|
||||||
|
<MyIdMenu />
|
||||||
|
{key_confirmed ? <input type="text" readonly value={formatId(get_id())}/> : translate("Generating ...")}
|
||||||
|
</div>
|
||||||
|
<div class="your-desktop">
|
||||||
|
<div>{translate('Password')}</div>
|
||||||
|
<Password />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{handler.xcall("is_installed") ? "": <InstallMe />}
|
||||||
|
{handler.xcall("is_installed") && software_update_url ? <UpdateMe /> : ""}
|
||||||
|
{handler.xcall("is_installed") && !software_update_url && handler.xcall("is_installed_lower_version") ? <UpgradeMe /> : ""}
|
||||||
|
{is_can_screen_recording ? "": <CanScreenRecording />}
|
||||||
|
{is_can_screen_recording && !handler.xcall("is_process_trusted",false) ? <TrustMe /> : ""}
|
||||||
|
{system_error ? <SystemError /> : ""}
|
||||||
|
{!system_error && handler.xcall("is_login_wayland") && !handler.xcall("current_is_wayland") ? <FixWayland /> : ""}
|
||||||
|
{!system_error && handler.xcall("current_is_wayland") ? <ModifyDefaultLogin /> : ""}
|
||||||
|
</div>
|
||||||
|
<div class="right-pane">
|
||||||
|
<div class="right-content">
|
||||||
|
<div class="card-connect">
|
||||||
|
<div class="title">{translate('Control Remote Desktop')}</div>
|
||||||
|
<ID id="ID" />
|
||||||
|
<div class="right-buttons">
|
||||||
|
<button class="button outline" id="file-transfer">{translate('Transfer File')}</button>
|
||||||
|
<button class="button" id="connect">{translate('Connect')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MultipleSessions id="multipleSessions" />
|
||||||
|
</div>
|
||||||
|
<ConnectStatus id="ConnectStatus" />
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at button#connect"](){
|
||||||
|
this.newRemote("connect");
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at button#file-transfer"]() {
|
||||||
|
this.newRemote("file-transfer");
|
||||||
|
}
|
||||||
|
|
||||||
|
["on keydown"](evt) {
|
||||||
|
if (!evt.shortcutKey) {
|
||||||
|
// TODO TEST Windows/Mac
|
||||||
|
if (evt.code == "KeyRETURN") {
|
||||||
|
var el = $("button#connect");
|
||||||
|
view.focus = el;
|
||||||
|
el.click();
|
||||||
|
// simulate button click effect, windows does not have this issue
|
||||||
|
el.classList.toggle("active", true);
|
||||||
|
el.timer(300, ()=> el.classList.toggle("active", false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newRemote(type) {
|
||||||
|
createNewConnect(this.remote_id.value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InstallMe extends Element {
|
||||||
|
render() {
|
||||||
|
return (<div class="install-me">
|
||||||
|
<span />
|
||||||
|
<div>{translate('install_tip')}</div>
|
||||||
|
<div style="text-align: center; margin-top: 1em;"><button id="install-me" class="button">{translate('Install')}</button></div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #install-me"]() {
|
||||||
|
handler.xcall("goto_install");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const http = function() {
|
||||||
|
function makeRequest(httpverb) {
|
||||||
|
return function( params ) {
|
||||||
|
params.type = httpverb;
|
||||||
|
// TODO request
|
||||||
|
view.request(params);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function download(from, to, ...args) {
|
||||||
|
// TODO #get
|
||||||
|
let rqp = { type:"get", url: from, toFile: to };
|
||||||
|
let fn = 0;
|
||||||
|
let on = 0;
|
||||||
|
// TODO p in / p of?
|
||||||
|
for( let p in args )
|
||||||
|
if( p instanceof Function )
|
||||||
|
{
|
||||||
|
switch(++fn) {
|
||||||
|
case 1: rqp.success = p; break;
|
||||||
|
case 2: rqp.error = p; break;
|
||||||
|
case 3: rqp.progress = p; break;
|
||||||
|
}
|
||||||
|
} else if( p instanceof Object )
|
||||||
|
{
|
||||||
|
switch(++on) {
|
||||||
|
case 1: rqp.params = p; break;
|
||||||
|
case 2: rqp.headers = p; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO request
|
||||||
|
view.request(rqp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
get: makeRequest("get"),
|
||||||
|
post: makeRequest("post"),
|
||||||
|
put: makeRequest("put"),
|
||||||
|
del: makeRequest("delete"),
|
||||||
|
download: download
|
||||||
|
};
|
||||||
|
|
||||||
|
}();
|
||||||
|
|
||||||
|
class UpgradeMe extends Element {
|
||||||
|
render() {
|
||||||
|
let update_or_download = is_osx ? "download" : "update";
|
||||||
|
return (<div class="install-me">
|
||||||
|
<div>{translate('Status')}</div>
|
||||||
|
<div>{translate('Your installation is lower version.')}</div>
|
||||||
|
<div id="install-me" class="link" style="padding-top: 1em">{translate('Click to upgrade')}</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #install-me"]() {
|
||||||
|
handler.xcall("update_me");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateMe extends Element {
|
||||||
|
render() {
|
||||||
|
let update_or_download = "download"; // !is_win ? "download" : "update";
|
||||||
|
return (<div class="install-me">
|
||||||
|
<div>{translate('Status')}</div>
|
||||||
|
<div>There is a newer version of {handler.xcall("get_app_name")} ({handler.xcall("get_new_version")}) available.</div>
|
||||||
|
<div id="install-me" class="link" style="padding-top: 1em">Click to {update_or_download}</div>
|
||||||
|
<div id="download-percent" style="display:hidden; padding-top: 1em;" />
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #install-me"]() {
|
||||||
|
handler.xcall("open_url","https://rustdesk.com");
|
||||||
|
return;
|
||||||
|
if (!is_win) {
|
||||||
|
handler.xcall("open_url","https://rustdesk.com");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let url = software_update_url + '.' + handler.xcall("get_software_ext");
|
||||||
|
let path = handler.xcall("get_software_store_path");
|
||||||
|
let onsuccess = function(md5) {
|
||||||
|
this.$("#download-percent").content(translate("Installing ..."));
|
||||||
|
handler.xcall("update_me",path);
|
||||||
|
};
|
||||||
|
let onerror = function(err) {
|
||||||
|
msgbox("custom-error", "Download Error", "Failed to download");
|
||||||
|
};
|
||||||
|
let onprogress = function(loaded, total) {
|
||||||
|
if (!total) total = 5 * 1024 * 1024;
|
||||||
|
let el = this.$("#download-percent");
|
||||||
|
el.style.setProperty("display","block");
|
||||||
|
el.content("Downloading %" + (loaded * 100 / total));
|
||||||
|
};
|
||||||
|
console.log("Downloading " + url + " to " + path);
|
||||||
|
http.download(
|
||||||
|
url,
|
||||||
|
document.url(path),
|
||||||
|
onsuccess, onerror, onprogress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SystemError extends Element {
|
||||||
|
render() {
|
||||||
|
return (<div class="install-me">
|
||||||
|
<div>{system_error}</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TrustMe extends Element {
|
||||||
|
render() {
|
||||||
|
return (<div class="trust-me">
|
||||||
|
<div>{translate('Configuration Permissions')}</div>
|
||||||
|
<div>{translate('config_acc')}</div>
|
||||||
|
<div id="trust-me" class="link">{translate('Configure')}</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #trust-me"] () {
|
||||||
|
handler.xcall("is_process_trusted",true);
|
||||||
|
watch_trust();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CanScreenRecording extends Element {
|
||||||
|
render() {
|
||||||
|
return (<div class="trust-me">
|
||||||
|
<div>{translate('Configuration Permissions')}</div>
|
||||||
|
<div>{translate('config_screen')}</div>
|
||||||
|
<div id="screen-recording" class="link">{translate('Configure')}</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #screen-recording"]() {
|
||||||
|
handler.xcall("is_can_screen_recording",true);
|
||||||
|
watch_trust();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FixWayland extends Element {
|
||||||
|
render() {
|
||||||
|
return (<div class="trust-me">
|
||||||
|
<div>{translate('Warning')}</div>
|
||||||
|
<div>{translate('Login screen using Wayland is not supported')}</div>
|
||||||
|
<div id="fix-wayland" class="link">{translate('Fix it')}</div>
|
||||||
|
<div style="text-align: center">({translate('Reboot required')})</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #fix-wayland"] () {
|
||||||
|
handler.xcall("fix_login_wayland");
|
||||||
|
app.componentUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModifyDefaultLogin extends Element {
|
||||||
|
render() {
|
||||||
|
return (<div class="trust-me">
|
||||||
|
<div>{translate('Warning')}</div>
|
||||||
|
<div>{translate('Current Wayland display server is not supported')}</div>
|
||||||
|
<div id="modify-default-login" class="link">{translate('Fix it')}</div>
|
||||||
|
<div style="text-align: center">({translate('Reboot required')})</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #modify-default-login"]() {
|
||||||
|
let r = handler.xcall("modify_default_login");
|
||||||
|
if (r) {
|
||||||
|
msgbox("custom-error", "Error", r);
|
||||||
|
}
|
||||||
|
app.componentUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function watch_trust() {
|
||||||
|
// not use TrustMe::update, because it is buggy
|
||||||
|
let trusted = handler.xcall("is_process_trusted",false);
|
||||||
|
let el = $("div.trust-me");
|
||||||
|
if (el) {
|
||||||
|
el.style.setProperty("display", trusted ? "none" : "block");
|
||||||
|
}
|
||||||
|
// if (trusted) return;
|
||||||
|
// TODO dont have exit?
|
||||||
|
setTimeout(() => {
|
||||||
|
watch_trust()
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PasswordEyeArea extends Element {
|
||||||
|
render() {
|
||||||
|
return (<div class="eye-area" style="width: *">
|
||||||
|
<input type="text" readonly value="******" />
|
||||||
|
{svg_eye}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on mouseenter"]() {
|
||||||
|
this.leaved = false;
|
||||||
|
setTimeout(()=> {
|
||||||
|
if (this.leaved) return;
|
||||||
|
this.$("input").value = handler.xcall("get_password");
|
||||||
|
},300);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on mouseleave"]() {
|
||||||
|
this.leaved = true;
|
||||||
|
this.$("input").value = "******";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Password extends Element {
|
||||||
|
render() {
|
||||||
|
return (<div class="password" style="flow:horizontal">
|
||||||
|
<PasswordEyeArea />
|
||||||
|
{svg_edit}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at svg#edit"](_,me) {
|
||||||
|
let menu = $("menu#edit-password-context");
|
||||||
|
me.popup(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at li#refresh-password"] () {
|
||||||
|
handler.xcall("update_password");
|
||||||
|
this.componentUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at li#set-password"] () {
|
||||||
|
// option .form .set-password ...
|
||||||
|
msgbox("custom-password", translate("Set Password"), "<div .form .set-password> \
|
||||||
|
<div><span>" + translate('Password') + ":</span><input|password(password) .outline-focus /></div> \
|
||||||
|
<div><span>" + translate('Confirmation') + ":</span><input|password(confirmation) /></div> \
|
||||||
|
</div> \
|
||||||
|
",
|
||||||
|
function(res=null) {
|
||||||
|
if (!res) return;
|
||||||
|
let p0 = (res.password || "").trim();
|
||||||
|
let p1 = (res.confirmation || "").trim();
|
||||||
|
if (p0.length < 6) {
|
||||||
|
return translate("Too short, at least 6 characters.");
|
||||||
|
}
|
||||||
|
if (p0 != p1) {
|
||||||
|
return translate("The confirmation is not identical.");
|
||||||
|
}
|
||||||
|
handler.xcall("update_password",p0);
|
||||||
|
this.componentUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ID extends Element {
|
||||||
|
render() {
|
||||||
|
return <input type="text" id="remote_id" class="outline-focus" novalue={translate("Enter Remote ID")} maxlength="15" value={formatId(handler.xcall("get_remote_id"))} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST
|
||||||
|
// https://github.com/c-smile/sciter-sdk/blob/master/doc/content/sciter/Event.htm
|
||||||
|
["on change"]() {
|
||||||
|
let fid = formatId(this.value);
|
||||||
|
let d = this.value.length - (this.old_value || "").length;
|
||||||
|
this.old_value = this.value;
|
||||||
|
let start = this.xcall("selectionStart") || 0;
|
||||||
|
let end = this.xcall("selectionEnd");
|
||||||
|
if (fid == this.value || d <= 0 || start != end) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// fix Caret position
|
||||||
|
this.value = fid;
|
||||||
|
let text_after_caret = this.old_value.substr(start);
|
||||||
|
let n = fid.length - formatId(text_after_caret).length;
|
||||||
|
this.xcall("setSelection", n, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var reg = /^\d+$/;
|
||||||
|
export function formatId(id) {
|
||||||
|
id = id.replace(/\s/g, "");
|
||||||
|
if (reg.test(id) && id.length > 3) {
|
||||||
|
let n = id.length;
|
||||||
|
let a = n % 3 || 3;
|
||||||
|
let new_id = id.substr(0, a);
|
||||||
|
for (let i = a; i < n; i += 3) {
|
||||||
|
new_id += " " + id.substr(i, 3);
|
||||||
|
}
|
||||||
|
return new_id;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.content(<App />);
|
||||||
|
|
||||||
|
document.on("ready",()=>{
|
||||||
|
let r = handler.xcall("get_size");
|
||||||
|
if (isReasonableSize(r) && r[2] > 0) {
|
||||||
|
view.move(r[0], r[1], r[2], r[3]);
|
||||||
|
} else {
|
||||||
|
centerize(800, 600);
|
||||||
|
}
|
||||||
|
if (!handler.xcall("get_remote_id")) {
|
||||||
|
view.focus = $("#remote_id"); // TEST
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
document.on("unloadequest",(evt)=>{
|
||||||
|
// evt.preventDefault() // can prevent window close
|
||||||
|
let [x, y, w, h] = view.box("rectw", "border", "desktop");
|
||||||
|
handler.xcall("save_size",x, y, w, h);
|
||||||
|
})
|
||||||
|
|
||||||
|
// check connect status
|
||||||
|
setInterval(() => {
|
||||||
|
let tmp = !!handler.xcall("get_option","stop-service");
|
||||||
|
if (tmp != service_stopped) {
|
||||||
|
service_stopped = tmp;
|
||||||
|
app.connect_status.componentUpdate();
|
||||||
|
myIdMenu.componentUpdate();
|
||||||
|
}
|
||||||
|
tmp = handler.xcall("get_connect_status");
|
||||||
|
if (tmp[0] != connect_status) {
|
||||||
|
connect_status = tmp[0];
|
||||||
|
app.connect_status.componentUpdate();
|
||||||
|
}
|
||||||
|
if (tmp[1] != key_confirmed) {
|
||||||
|
key_confirmed = tmp[1];
|
||||||
|
app.componentUpdate();
|
||||||
|
}
|
||||||
|
if (tmp[2] && tmp[2] != my_id) {
|
||||||
|
console.log("id updated");
|
||||||
|
app.componentUpdate();
|
||||||
|
}
|
||||||
|
tmp = handler.xcall("get_error");
|
||||||
|
if (system_error != tmp) {
|
||||||
|
system_error = tmp;
|
||||||
|
app.componentUpdate();
|
||||||
|
}
|
||||||
|
tmp = handler.xcall("get_software_update_url");
|
||||||
|
if (tmp != software_update_url) {
|
||||||
|
software_update_url = tmp;
|
||||||
|
app.componentUpdate();
|
||||||
|
}
|
||||||
|
if (handler.xcall("recent_sessions_updated")) {
|
||||||
|
console.log("recent sessions updated");
|
||||||
|
app.componentUpdate();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
720
src/ui/index.tis
720
src/ui/index.tis
@@ -1,720 +0,0 @@
|
|||||||
if (is_osx) view.windowBlurbehind = #light;
|
|
||||||
stdout.println("current platform:", OS);
|
|
||||||
|
|
||||||
// html min-width, min-height not working on mac, below works for all
|
|
||||||
view.windowMinSize = (500, 300);
|
|
||||||
|
|
||||||
var app;
|
|
||||||
var tmp = handler.get_connect_status();
|
|
||||||
var connect_status = tmp[0];
|
|
||||||
var service_stopped = false;
|
|
||||||
var software_update_url = "";
|
|
||||||
var key_confirmed = tmp[1];
|
|
||||||
var system_error = "";
|
|
||||||
|
|
||||||
var svg_menu = <svg #menu viewBox="0 0 512 512">
|
|
||||||
<circle cx="256" cy="256" r="64"/>
|
|
||||||
<circle cx="256" cy="448" r="64"/>
|
|
||||||
<circle cx="256" cy="64" r="64"/>
|
|
||||||
</svg>;
|
|
||||||
|
|
||||||
var my_id = "";
|
|
||||||
function get_id() {
|
|
||||||
my_id = handler.get_id();
|
|
||||||
return my_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConnectStatus: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
return
|
|
||||||
<div .connect-status>
|
|
||||||
<span class={"connect-status-icon connect-status" + (service_stopped ? 0 : connect_status)} />
|
|
||||||
{this.getConnectStatusStr()}
|
|
||||||
{service_stopped ? <span .link #start-service>{translate('Start Service')}</span> : ""}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConnectStatusStr() {
|
|
||||||
if (service_stopped) {
|
|
||||||
return translate("Service is not running");
|
|
||||||
} else if (connect_status == -1) {
|
|
||||||
return translate('not_ready_status');
|
|
||||||
} else if (connect_status == 0) {
|
|
||||||
return translate('connecting_status');
|
|
||||||
}
|
|
||||||
return translate("Ready");
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#start-service) () {
|
|
||||||
handler.set_option("stop-service", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNewConnect(id, type) {
|
|
||||||
id = id.replace(/\s/g, "");
|
|
||||||
app.remote_id.value = formatId(id);
|
|
||||||
if (!id) return;
|
|
||||||
if (id == my_id) {
|
|
||||||
msgbox("custom-error", "Error", "You cannot connect to your own computer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handler.set_remote_id(id);
|
|
||||||
handler.new_remote(id, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
var direct_server;
|
|
||||||
class DirectServer: Reactor.Component {
|
|
||||||
function this() {
|
|
||||||
direct_server = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
var text = translate("Enable Direct IP Access");
|
|
||||||
var cls = handler.get_option("direct-server") == "Y" ? "selected" : "line-through";
|
|
||||||
return <li class={cls}><span>{svg_checkmark}</span>{text}</li>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClick() {
|
|
||||||
handler.set_option("direct-server", handler.get_option("direct-server") == "Y" ? "" : "Y");
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var myIdMenu;
|
|
||||||
var audioInputMenu;
|
|
||||||
class AudioInputs: Reactor.Component {
|
|
||||||
function this() {
|
|
||||||
audioInputMenu = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
if (!this.show) return <li />;
|
|
||||||
var inputs = handler.get_sound_inputs();
|
|
||||||
if (is_win) inputs = ["System Sound"].concat(inputs);
|
|
||||||
if (!inputs.length) return <li style="display:hidden" />;
|
|
||||||
inputs = ["Mute"].concat(inputs);
|
|
||||||
var me = this;
|
|
||||||
self.timer(1ms, function() { me.toggleMenuState() });
|
|
||||||
return <li>{translate('Audio Input')}
|
|
||||||
<menu #audio-input key={inputs.length}>
|
|
||||||
{inputs.map(function(name) {
|
|
||||||
return <li id={name}><span>{svg_checkmark}</span>{translate(name)}</li>;
|
|
||||||
})}
|
|
||||||
</menu>
|
|
||||||
</li>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_default() {
|
|
||||||
if (is_win) return "System Sound";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_value() {
|
|
||||||
return handler.get_option("audio-input") || this.get_default();
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleMenuState() {
|
|
||||||
var v = this.get_value();
|
|
||||||
for (var el in $$(menu#audio-input>li)) {
|
|
||||||
var selected = el.id == v;
|
|
||||||
el.attributes.toggleClass("selected", selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(menu#audio-input>li) (_, me) {
|
|
||||||
var v = me.id;
|
|
||||||
if (v == this.get_value()) return;
|
|
||||||
if (v == this.get_default()) v = "";
|
|
||||||
handler.set_option("audio-input", v);
|
|
||||||
this.toggleMenuState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyIdMenu: Reactor.Component {
|
|
||||||
function this() {
|
|
||||||
myIdMenu = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
return <div #myid>
|
|
||||||
{this.renderPop()}
|
|
||||||
ID{svg_menu}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPop() {
|
|
||||||
return <popup>
|
|
||||||
<menu.context #config-options>
|
|
||||||
<li #enable-keyboard><span>{svg_checkmark}</span>{translate('Enable Keyboard/Mouse')}</li>
|
|
||||||
<li #enable-clipboard><span>{svg_checkmark}</span>{translate('Enable Clipboard')}</li>
|
|
||||||
<li #enable-file-transfer><span>{svg_checkmark}</span>{translate('Enable File Transfer')}</li>
|
|
||||||
<li #enable-tunnel><span>{svg_checkmark}</span>{translate('Enable TCP Tunneling')}</li>
|
|
||||||
<AudioInputs />
|
|
||||||
<div .separator />
|
|
||||||
<li #whitelist title={translate('whitelist_tip')}>{translate('IP Whitelisting')}</li>
|
|
||||||
<li #custom-server>{translate('ID/Relay Server')}</li>
|
|
||||||
<li #socks5-server>{translate('Socks5 Proxy')}</li>
|
|
||||||
<div .separator />
|
|
||||||
<li #stop-service class={service_stopped ? "line-through" : "selected"}><span>{svg_checkmark}</span>{translate("Enable Service")}</li>
|
|
||||||
<DirectServer />
|
|
||||||
<div .separator />
|
|
||||||
<li #about>{translate('About')} {" "} {handler.get_app_name()}</li>
|
|
||||||
</menu>
|
|
||||||
</popup>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(svg#menu) (_, me) {
|
|
||||||
audioInputMenu.update({ show: true });
|
|
||||||
this.toggleMenuState();
|
|
||||||
if (direct_server) direct_server.update();
|
|
||||||
var menu = $(menu#config-options);
|
|
||||||
me.popup(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleMenuState() {
|
|
||||||
for (var el in $$(menu#config-options>li)) {
|
|
||||||
if (el.id && el.id.indexOf("enable-") == 0) {
|
|
||||||
var enabled = handler.get_option(el.id) != "N";
|
|
||||||
el.attributes.toggleClass("selected", enabled);
|
|
||||||
el.attributes.toggleClass("line-through", !enabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(menu#config-options>li) (_, me) {
|
|
||||||
if (me.id && me.id.indexOf("enable-") == 0) {
|
|
||||||
handler.set_option(me.id, handler.get_option(me.id) == "N" ? "" : "N");
|
|
||||||
}
|
|
||||||
if (me.id == "whitelist") {
|
|
||||||
var old_value = handler.get_option("whitelist").split(",").join("\n");
|
|
||||||
msgbox("custom-whitelist", translate("IP Whitelisting"), "<div .form> \
|
|
||||||
<div>" + translate("whitelist_sep") + "</div> \
|
|
||||||
<textarea spellcheck=\"false\" name=\"text\" novalue=\"0.0.0.0\" style=\"overflow: scroll-indicator; width:*; height: 160px; font-size: 1.2em; padding: 0.5em;\">" + old_value + "</textarea>\
|
|
||||||
</div> \
|
|
||||||
", function(res=null) {
|
|
||||||
if (!res) return;
|
|
||||||
var value = (res.text || "").trim();
|
|
||||||
if (value) {
|
|
||||||
var values = value.split(/[\s,;\n]+/g);
|
|
||||||
for (var ip in values) {
|
|
||||||
if (!ip.match(/^\d+\.\d+\.\d+\.\d+$/)) {
|
|
||||||
return translate("Invalid IP") + ": " + ip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value = values.join("\n");
|
|
||||||
}
|
|
||||||
if (value == old_value) return;
|
|
||||||
stdout.println("whitelist updated");
|
|
||||||
handler.set_option("whitelist", value.replace("\n", ","));
|
|
||||||
}, 300);
|
|
||||||
} else if (me.id == "custom-server") {
|
|
||||||
var configOptions = handler.get_options();
|
|
||||||
var old_relay = configOptions["relay-server"] || "";
|
|
||||||
var old_id = configOptions["custom-rendezvous-server"] || "";
|
|
||||||
msgbox("custom-server", "ID/Relay Server", "<div .form> \
|
|
||||||
<div><span style='width: 100px; display:inline-block'>" + translate("ID Server") + ": </span><input .outline-focus style='width: 250px' name='id' value='" + old_id + "' /></div> \
|
|
||||||
<div><span style='width: 100px; display:inline-block'>" + translate("Relay Server") + ": </span><input style='width: 250px' name='relay' value='" + old_relay + "' /></div> \
|
|
||||||
</div> \
|
|
||||||
", function(res=null) {
|
|
||||||
if (!res) return;
|
|
||||||
var id = (res.id || "").trim();
|
|
||||||
var relay = (res.relay || "").trim();
|
|
||||||
if (id == old_id && relay == old_relay) return;
|
|
||||||
if (id) {
|
|
||||||
var err = handler.test_if_valid_server(id);
|
|
||||||
if (err) return translate("ID Server") + ": " + err;
|
|
||||||
}
|
|
||||||
if (relay) {
|
|
||||||
var err = handler.test_if_valid_server(relay);
|
|
||||||
if (err) return translate("Relay Server") + ": " + err;
|
|
||||||
}
|
|
||||||
configOptions["custom-rendezvous-server"] = id;
|
|
||||||
configOptions["relay-server"] = relay;
|
|
||||||
handler.set_options(configOptions);
|
|
||||||
}, 240);
|
|
||||||
} else if (me.id == "socks5-server") {
|
|
||||||
var socks5 = handler.get_socks() || {};
|
|
||||||
var old_proxy = socks5[0] || "";
|
|
||||||
var old_username = socks5[1] || "";
|
|
||||||
var old_password = socks5[2] || "";
|
|
||||||
msgbox("custom-server", "Socks5 Proxy", <div .form .set-password>
|
|
||||||
<div><span>{translate("Hostname")}</span><input .outline-focus style='width: *' name='proxy' value={old_proxy} /></div>
|
|
||||||
<div><span>{translate("Username")}</span><input style='width: *' name='username' value={old_username} /></div>
|
|
||||||
<div><span>{translate("Password")}</span><PasswordComponent value={old_password} /></div>
|
|
||||||
</div>
|
|
||||||
, function(res=null) {
|
|
||||||
if (!res) return;
|
|
||||||
var proxy = (res.proxy || "").trim();
|
|
||||||
var username = (res.username || "").trim();
|
|
||||||
var password = (res.password || "").trim();
|
|
||||||
if (proxy == old_proxy && username == old_username && password == old_password) return;
|
|
||||||
if (proxy) {
|
|
||||||
var err = handler.test_if_valid_server(proxy);
|
|
||||||
if (err) return translate("Server") + ": " + err;
|
|
||||||
}
|
|
||||||
handler.set_socks(proxy, username, password);
|
|
||||||
}, 240);
|
|
||||||
} else if (me.id == "stop-service") {
|
|
||||||
handler.set_option("stop-service", service_stopped ? "" : "Y");
|
|
||||||
} else if (me.id == "about") {
|
|
||||||
var name = handler.get_app_name();
|
|
||||||
msgbox("custom-nocancel-nook-hasclose", "About " + name, "<div style='line-height: 2em'> \
|
|
||||||
<div>Version: " + handler.get_version() + " \
|
|
||||||
<div .link .custom-event url='http://rustdesk.com/privacy'>Privacy Statement</div> \
|
|
||||||
<div .link .custom-event url='http://rustdesk.com'>Website</div> \
|
|
||||||
<div style='background: #2c8cff; color: white; padding: 1em; margin-top: 1em;'>Copyright © 2020 CarrieZ Studio \
|
|
||||||
<br /> Author: Carrie \
|
|
||||||
<p style='font-weight: bold'>Made with heart in this chaotic world!</p>\
|
|
||||||
</div>\
|
|
||||||
</div>", function(el) {
|
|
||||||
if (el && el.attributes) {
|
|
||||||
handler.open_url(el.attributes['url']);
|
|
||||||
};
|
|
||||||
}, 400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class App: Reactor.Component
|
|
||||||
{
|
|
||||||
function this() {
|
|
||||||
app = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
var is_can_screen_recording = handler.is_can_screen_recording(false);
|
|
||||||
return
|
|
||||||
<div .app>
|
|
||||||
<popup><menu.context #edit-password-context>
|
|
||||||
<li #refresh-password>{translate('Refresh random password')}</li>
|
|
||||||
<li #set-password>{translate('Set your own password')}</li>
|
|
||||||
</menu></popup>
|
|
||||||
<div .left-pane>
|
|
||||||
<div>
|
|
||||||
<div .title>{translate('Your Desktop')}</div>
|
|
||||||
<div .lighter-text>{translate('desk_tip')}</div>
|
|
||||||
<div .your-desktop>
|
|
||||||
<MyIdMenu />
|
|
||||||
{key_confirmed ? <input type="text" readonly value={formatId(get_id())}/> : translate("Generating ...")}
|
|
||||||
</div>
|
|
||||||
<div .your-desktop>
|
|
||||||
<div>{translate('Password')}</div>
|
|
||||||
<Password />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{handler.is_installed() ? "": <InstallMe />}
|
|
||||||
{handler.is_installed() && software_update_url ? <UpdateMe /> : ""}
|
|
||||||
{handler.is_installed() && !software_update_url && handler.is_installed_lower_version() ? <UpgradeMe /> : ""}
|
|
||||||
{is_can_screen_recording ? "": <CanScreenRecording />}
|
|
||||||
{is_can_screen_recording && !handler.is_process_trusted(false) ? <TrustMe /> : ""}
|
|
||||||
{system_error ? <SystemError /> : ""}
|
|
||||||
{!system_error && handler.is_login_wayland() && !handler.current_is_wayland() ? <FixWayland /> : ""}
|
|
||||||
{!system_error && handler.current_is_wayland() ? <ModifyDefaultLogin /> : ""}
|
|
||||||
</div>
|
|
||||||
<div .right-pane>
|
|
||||||
<div .right-content>
|
|
||||||
<div .card-connect>
|
|
||||||
<div .title>{translate('Control Remote Desktop')}</div>
|
|
||||||
<ID @{this.remote_id} />
|
|
||||||
<div .right-buttons>
|
|
||||||
<button .button .outline #file-transfer>{translate('Transfer File')}</button>
|
|
||||||
<button .button #connect>{translate('Connect')}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<MultipleSessions @{this.multipleSessions} />
|
|
||||||
</div>
|
|
||||||
<ConnectStatus @{this.connect_status} />
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(button#connect) {
|
|
||||||
this.newRemote("connect");
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(button#file-transfer) {
|
|
||||||
this.newRemote("file-transfer");
|
|
||||||
}
|
|
||||||
|
|
||||||
function newRemote(type) {
|
|
||||||
createNewConnect(this.remote_id.value, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class InstallMe: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
return <div .install-me>
|
|
||||||
<span />
|
|
||||||
<div>{translate('install_tip')}</div>
|
|
||||||
<div style="text-align: center; margin-top: 1em;"><button #install-me .button>{translate('Install')}</button></div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#install-me) {
|
|
||||||
handler.goto_install();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const http = function() {
|
|
||||||
|
|
||||||
function makeRequest(httpverb) {
|
|
||||||
return function( params ) {
|
|
||||||
params.type = httpverb;
|
|
||||||
view.request(params);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function download(from, to, args..)
|
|
||||||
{
|
|
||||||
var rqp = { type:#get, url: from, toFile: to };
|
|
||||||
var fn = 0;
|
|
||||||
var on = 0;
|
|
||||||
for( var p in args )
|
|
||||||
if( p instanceof Function )
|
|
||||||
{
|
|
||||||
switch(++fn) {
|
|
||||||
case 1: rqp.success = p; break;
|
|
||||||
case 2: rqp.error = p; break;
|
|
||||||
case 3: rqp.progress = p; break;
|
|
||||||
}
|
|
||||||
} else if( p instanceof Object )
|
|
||||||
{
|
|
||||||
switch(++on) {
|
|
||||||
case 1: rqp.params = p; break;
|
|
||||||
case 2: rqp.headers = p; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
view.request(rqp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
get: makeRequest(#get),
|
|
||||||
post: makeRequest(#post),
|
|
||||||
put: makeRequest(#put),
|
|
||||||
del: makeRequest(#delete),
|
|
||||||
download: download
|
|
||||||
};
|
|
||||||
|
|
||||||
}();
|
|
||||||
|
|
||||||
class UpgradeMe: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
var update_or_download = is_osx ? "download" : "update";
|
|
||||||
return <div .install-me>
|
|
||||||
<div>{translate('Status')}</div>
|
|
||||||
<div>{translate('Your installation is lower version.')}</div>
|
|
||||||
<div #install-me .link style="padding-top: 1em">{translate('Click to upgrade')}</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#install-me) {
|
|
||||||
handler.update_me("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UpdateMe: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
var update_or_download = "download"; // !is_win ? "download" : "update";
|
|
||||||
return <div .install-me>
|
|
||||||
<div>{translate('Status')}</div>
|
|
||||||
<div>There is a newer version of {handler.get_app_name()} ({handler.get_new_version()}) available.</div>
|
|
||||||
<div #install-me .link style="padding-top: 1em">Click to {update_or_download}</div>
|
|
||||||
<div #download-percent style="display:hidden; padding-top: 1em;" />
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#install-me) {
|
|
||||||
handler.open_url("https://rustdesk.com");
|
|
||||||
return;
|
|
||||||
if (!is_win) {
|
|
||||||
handler.open_url("https://rustdesk.com");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var url = software_update_url + '.' + handler.get_software_ext();
|
|
||||||
var path = handler.get_software_store_path();
|
|
||||||
var onsuccess = function(md5) {
|
|
||||||
$(#download-percent).content(translate("Installing ..."));
|
|
||||||
handler.update_me(path);
|
|
||||||
};
|
|
||||||
var onerror = function(err) {
|
|
||||||
msgbox("custom-error", "Download Error", "Failed to download");
|
|
||||||
};
|
|
||||||
var onprogress = function(loaded, total) {
|
|
||||||
if (!total) total = 5 * 1024 * 1024;
|
|
||||||
var el = $(#download-percent);
|
|
||||||
el.style.set{display: "block"};
|
|
||||||
el.content("Downloading %" + (loaded * 100 / total));
|
|
||||||
};
|
|
||||||
stdout.println("Downloading " + url + " to " + path);
|
|
||||||
http.download(
|
|
||||||
url,
|
|
||||||
self.url(path),
|
|
||||||
onsuccess, onerror, onprogress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SystemError: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
return <div .install-me>
|
|
||||||
<div>{system_error}</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TrustMe: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
return <div .trust-me>
|
|
||||||
<div>{translate('Configuration Permissions')}</div>
|
|
||||||
<div>{translate('config_acc')}</div>
|
|
||||||
<div #trust-me .link>{translate('Configure')}</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#trust-me) {
|
|
||||||
handler.is_process_trusted(true);
|
|
||||||
watch_trust();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CanScreenRecording: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
return <div .trust-me>
|
|
||||||
<div>{translate('Configuration Permissions')}</div>
|
|
||||||
<div>{translate('config_screen')}</div>
|
|
||||||
<div #screen-recording .link>{translate('Configure')}</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#screen-recording) {
|
|
||||||
handler.is_can_screen_recording(true);
|
|
||||||
watch_trust();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FixWayland: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
return <div .trust-me>
|
|
||||||
<div>{translate('Warning')}</div>
|
|
||||||
<div>{translate('Login screen using Wayland is not supported')}</div>
|
|
||||||
<div #fix-wayland .link>{translate('Fix it')}</div>
|
|
||||||
<div style="text-align: center">({translate('Reboot required')})</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#fix-wayland) {
|
|
||||||
handler.fix_login_wayland();
|
|
||||||
app.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ModifyDefaultLogin: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
return <div .trust-me>
|
|
||||||
<div>{translate('Warning')}</div>
|
|
||||||
<div>{translate('Current Wayland display server is not supported')}</div>
|
|
||||||
<div #modify-default-login .link>{translate('Fix it')}</div>
|
|
||||||
<div style="text-align: center">({translate('Reboot required')})</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#modify-default-login) {
|
|
||||||
if (var r = handler.modify_default_login()) {
|
|
||||||
msgbox("custom-error", "Error", r);
|
|
||||||
}
|
|
||||||
app.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function watch_trust() {
|
|
||||||
// not use TrustMe::update, because it is buggy
|
|
||||||
var trusted = handler.is_process_trusted(false);
|
|
||||||
var el = $(div.trust-me);
|
|
||||||
if (el) {
|
|
||||||
el.style.set {
|
|
||||||
display: trusted ? "none" : "block",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// if (trusted) return;
|
|
||||||
self.timer(1s, watch_trust);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PasswordEyeArea : Reactor.Component {
|
|
||||||
render() {
|
|
||||||
return
|
|
||||||
<div .eye-area style="width: *">
|
|
||||||
<input|text @{this.input} readonly value="******" />
|
|
||||||
{svg_eye}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event mouseenter {
|
|
||||||
var me = this;
|
|
||||||
me.leaved = false;
|
|
||||||
me.timer(300ms, function() {
|
|
||||||
if (me.leaved) return;
|
|
||||||
me.input.value = handler.get_password();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
event mouseleave {
|
|
||||||
this.leaved = true;
|
|
||||||
this.input.value = "******";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Password: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
return <div .password style="flow:horizontal">
|
|
||||||
<PasswordEyeArea />
|
|
||||||
{svg_edit}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(svg#edit) (_, me) {
|
|
||||||
var menu = $(menu#edit-password-context);
|
|
||||||
me.popup(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(li#refresh-password) {
|
|
||||||
handler.update_password("");
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(li#set-password) {
|
|
||||||
var me = this;
|
|
||||||
msgbox("custom-password", translate("Set Password"), "<div .form .set-password> \
|
|
||||||
<div><span>" + translate('Password') + ":</span><input|password(password) .outline-focus /></div> \
|
|
||||||
<div><span>" + translate('Confirmation') + ":</span><input|password(confirmation) /></div> \
|
|
||||||
</div> \
|
|
||||||
", function(res=null) {
|
|
||||||
if (!res) return;
|
|
||||||
var p0 = (res.password || "").trim();
|
|
||||||
var p1 = (res.confirmation || "").trim();
|
|
||||||
if (p0.length < 6) {
|
|
||||||
return translate("Too short, at least 6 characters.");
|
|
||||||
}
|
|
||||||
if (p0 != p1) {
|
|
||||||
return translate("The confirmation is not identical.");
|
|
||||||
}
|
|
||||||
handler.update_password(p0);
|
|
||||||
me.update();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ID: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
return <input type="text" #remote_id .outline-focus novalue={translate("Enter Remote ID")} maxlength="15"
|
|
||||||
value={formatId(handler.get_remote_id())} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/c-smile/sciter-sdk/blob/master/doc/content/sciter/Event.htm
|
|
||||||
event change {
|
|
||||||
var fid = formatId(this.value);
|
|
||||||
var d = this.value.length - (this.old_value || "").length;
|
|
||||||
this.old_value = this.value;
|
|
||||||
var start = this.xcall(#selectionStart) || 0;
|
|
||||||
var end = this.xcall(#selectionEnd);
|
|
||||||
if (fid == this.value || d <= 0 || start != end) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// fix Caret position
|
|
||||||
this.value = fid;
|
|
||||||
var text_after_caret = this.old_value.substr(start);
|
|
||||||
var n = fid.length - formatId(text_after_caret).length;
|
|
||||||
this.xcall(#setSelection, n, n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var reg = /^\d+$/;
|
|
||||||
function formatId(id) {
|
|
||||||
id = id.replace(/\s/g, "");
|
|
||||||
if (reg.test(id) && id.length > 3) {
|
|
||||||
var n = id.length;
|
|
||||||
var a = n % 3 || 3;
|
|
||||||
var new_id = id.substr(0, a);
|
|
||||||
for (var i = a; i < n; i += 3) {
|
|
||||||
new_id += " " + id.substr(i, 3);
|
|
||||||
}
|
|
||||||
return new_id;
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
event keydown (evt) {
|
|
||||||
if (!evt.shortcutKey) {
|
|
||||||
if (evt.keyCode == Event.VK_ENTER ||
|
|
||||||
(is_osx && evt.keyCode == 0x4C) ||
|
|
||||||
(is_linux && evt.keyCode == 65421)) {
|
|
||||||
var el = $(button#connect);
|
|
||||||
view.focus = el;
|
|
||||||
el.sendEvent("click");
|
|
||||||
// simulate button click effect, windows does not have this issue
|
|
||||||
el.attributes.toggleClass("active", true);
|
|
||||||
self.timer(0.3s, function() {
|
|
||||||
el.attributes.toggleClass("active", false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(body).content(<App />);
|
|
||||||
|
|
||||||
function self.closing() {
|
|
||||||
// return false; // can prevent window close
|
|
||||||
var (x, y, w, h) = view.box(#rectw, #border, #screen);
|
|
||||||
handler.save_size(x, y, w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
function self.ready() {
|
|
||||||
var r = handler.get_size();
|
|
||||||
if (isReasonableSize(r) && r[2] > 0) {
|
|
||||||
view.move(r[0], r[1], r[2], r[3]);
|
|
||||||
} else {
|
|
||||||
centerize(800, 600);
|
|
||||||
}
|
|
||||||
if (!handler.get_remote_id()) {
|
|
||||||
view.focus = $(#remote_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkConnectStatus() {
|
|
||||||
self.timer(1s, function() {
|
|
||||||
var tmp = !!handler.get_option("stop-service");
|
|
||||||
if (tmp != service_stopped) {
|
|
||||||
service_stopped = tmp;
|
|
||||||
app.connect_status.update();
|
|
||||||
myIdMenu.update();
|
|
||||||
}
|
|
||||||
tmp = handler.get_connect_status();
|
|
||||||
if (tmp[0] != connect_status) {
|
|
||||||
connect_status = tmp[0];
|
|
||||||
app.connect_status.update();
|
|
||||||
}
|
|
||||||
if (tmp[1] != key_confirmed) {
|
|
||||||
key_confirmed = tmp[1];
|
|
||||||
app.update();
|
|
||||||
}
|
|
||||||
if (tmp[2] && tmp[2] != my_id) {
|
|
||||||
stdout.println("id updated");
|
|
||||||
app.update();
|
|
||||||
}
|
|
||||||
tmp = handler.get_error();
|
|
||||||
if (system_error != tmp) {
|
|
||||||
system_error = tmp;
|
|
||||||
app.update();
|
|
||||||
}
|
|
||||||
tmp = handler.get_software_update_url();
|
|
||||||
if (tmp != software_update_url) {
|
|
||||||
software_update_url = tmp;
|
|
||||||
app.update();
|
|
||||||
}
|
|
||||||
if (handler.recent_sessions_updated()) {
|
|
||||||
stdout.println("recent sessions updated");
|
|
||||||
app.update();
|
|
||||||
}
|
|
||||||
checkConnectStatus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
checkConnectStatus();
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<html window-frame="extended">
|
<html window-frame="extended">
|
||||||
<head>
|
<head>
|
||||||
|
<link rel="stylesheet" href="common.css">
|
||||||
<style>
|
<style>
|
||||||
@import url(common.css);
|
|
||||||
html {
|
html {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
}
|
}
|
||||||
caption {
|
caption {
|
||||||
@ELLIPSIS;
|
@ELLIPSIS;
|
||||||
size: *;
|
size: "*";
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: white;
|
color: white;
|
||||||
padding-top: 0.33em;
|
padding-top: 0.33em;
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
}
|
}
|
||||||
div.set-password div.password {
|
div.set-password div.password {
|
||||||
width: *;
|
width: "*";
|
||||||
}
|
}
|
||||||
div.set-password input {
|
div.set-password input {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
@@ -60,10 +60,11 @@
|
|||||||
@ELLIPSIS;
|
@ELLIPSIS;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="text/tiscript">
|
<script type="module" src="msgbox.js"></script>
|
||||||
|
<!-- <script type="text/tiscript">
|
||||||
include "common.tis";
|
include "common.tis";
|
||||||
include "msgbox.tis";
|
include "msgbox.tis";
|
||||||
</script>
|
</script> -->
|
||||||
</head>
|
</head>
|
||||||
<body></body>
|
<body></body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { PasswordComponent } from "./common";
|
||||||
|
import {$,$$} from "@sciter"; //TEST $$ import
|
||||||
|
const view = Window.this;
|
||||||
var type, title, text, getParams, remember, retry, callback, contentStyle;
|
var type, title, text, getParams, remember, retry, callback, contentStyle;
|
||||||
var my_translate;
|
var my_translate; // TEST
|
||||||
|
|
||||||
function updateParams(params) {
|
function updateParams(params) {
|
||||||
type = params.type;
|
type = params.type;
|
||||||
title = params.title;
|
title = params.title;
|
||||||
@@ -11,18 +13,17 @@ function updateParams(params) {
|
|||||||
my_translate = params.translate;
|
my_translate = params.translate;
|
||||||
retry = params.retry;
|
retry = params.retry;
|
||||||
contentStyle = params.contentStyle;
|
contentStyle = params.contentStyle;
|
||||||
|
|
||||||
try { text = translate_text(text); } catch (e) {}
|
try { text = translate_text(text); } catch (e) {}
|
||||||
if (retry > 0) {
|
if (retry > 0) {
|
||||||
self.timer(retry * 1000, function() {
|
setTimeout(()=>view.close({ reconnect: true }),retry * 1000);// TEST
|
||||||
view.close({ reconnect: true });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function translate_text(text) {
|
function translate_text(text) {
|
||||||
if (text.indexOf('Failed') == 0 && text.indexOf(': ') > 0) {
|
if (text.indexOf('Failed') == 0 && text.indexOf(': ') > 0) {
|
||||||
var fds = text.split(': ');
|
let fds = text.split(': ');
|
||||||
for (var i = 0; i < fds.length; ++i) {
|
for (let i = 0; i < fds.length; ++i) {
|
||||||
fds[i] = my_translate(fds[i]);
|
fds[i] = my_translate(fds[i]);
|
||||||
}
|
}
|
||||||
text = fds.join(': ');
|
text = fds.join(': ');
|
||||||
@@ -30,17 +31,17 @@ function translate_text(text) {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
var params = view.parameters;
|
var params = view.parameters; // TEST
|
||||||
updateParams(params);
|
updateParams(params);
|
||||||
|
|
||||||
var body;
|
var body;
|
||||||
|
|
||||||
class Body: Reactor.Component {
|
class Body extends Element {
|
||||||
function this() {
|
this() {
|
||||||
body = this;
|
body = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIcon(color) {
|
getIcon(color) {
|
||||||
if (type == "input-password") {
|
if (type == "input-password") {
|
||||||
return <svg viewBox="0 0 505 505"><circle cx="252.5" cy="252.5" r="252.5" fill={color}/><path d="M271.9 246.1c29.2 17.5 67.6 13.6 92.7-11.5 29.7-29.7 29.7-77.8 0-107.4s-77.8-29.7-107.4 0c-25.1 25.1-29 63.5-11.5 92.7L118.1 347.4l26.2 26.2 26.4 26.4 10.6-10.6-10.1-10.1 9.7-9.7 10.1 10.1 10.6-10.6-10.1-10 9.7-9.7 10.1 10.1 10.6-10.6-26.4-26.3 76.4-76.5z" fill="#fff"/><circle cx="337.4" cy="154.4" r="17.7" fill={color}/></svg>;
|
return <svg viewBox="0 0 505 505"><circle cx="252.5" cy="252.5" r="252.5" fill={color}/><path d="M271.9 246.1c29.2 17.5 67.6 13.6 92.7-11.5 29.7-29.7 29.7-77.8 0-107.4s-77.8-29.7-107.4 0c-25.1 25.1-29 63.5-11.5 92.7L118.1 347.4l26.2 26.2 26.4 26.4 10.6-10.6-10.1-10.1 9.7-9.7 10.1 10.1 10.6-10.6-10.1-10 9.7-9.7 10.1 10.1 10.6-10.6-26.4-26.3 76.4-76.5z" fill="#fff"/><circle cx="337.4" cy="154.4" r="17.7" fill={color}/></svg>;
|
||||||
}
|
}
|
||||||
@@ -56,23 +57,24 @@ class Body: Reactor.Component {
|
|||||||
return <span />;
|
return <span />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInputPasswordContent() {
|
getInputPasswordContent() {
|
||||||
var ts = remember ? { checked: true } : {};
|
var ts = remember ? { checked: true } : {};
|
||||||
return <div .form>
|
// <div><button|checkbox(remember) {ts}>{my_translate('Remember password')}</button></div>
|
||||||
|
return <div class="form">
|
||||||
<div>{my_translate('Please enter your password')}</div>
|
<div>{my_translate('Please enter your password')}</div>
|
||||||
<PasswordComponent />
|
<PasswordComponent />
|
||||||
<div><button|checkbox(remember) {ts}>{my_translate('Remember password')}</button></div>
|
<div><button type="checkbox">{my_translate('Remember password')}</button></div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContent() {
|
getContent() {
|
||||||
if (type == "input-password") {
|
if (type == "input-password") {
|
||||||
return this.getInputPasswordContent();
|
return this.getInputPasswordContent();
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getColor() {
|
getColor() {
|
||||||
if (type == "input-password") {
|
if (type == "input-password") {
|
||||||
return "#AD448E";
|
return "#AD448E";
|
||||||
}
|
}
|
||||||
@@ -85,26 +87,20 @@ class Body: Reactor.Component {
|
|||||||
return "#2C8CFF";
|
return "#2C8CFF";
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasSkip() {
|
hasSkip() {
|
||||||
return type.indexOf("skip") >= 0;
|
return type.indexOf("skip") >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
render() {
|
||||||
var color = this.getColor();
|
let color = this.getColor();
|
||||||
var icon = this.getIcon(color);
|
let icon = this.getIcon(color);
|
||||||
var content = this.getContent();
|
let content = this.getContent();
|
||||||
var hasCancel = type.indexOf("error") < 0 && type != "success" && type.indexOf("nocancel") < 0;
|
let hasCancel = type.indexOf("error") < 0 && type != "success" && type.indexOf("nocancel") < 0;
|
||||||
var hasOk = type != "connecting" && type.indexOf("nook") < 0;
|
let hasOk = type != "connecting" && type.indexOf("nook") < 0;
|
||||||
var hasClose = type.indexOf("hasclose") >= 0;
|
let hasClose = type.indexOf("hasclose") >= 0;
|
||||||
var show_progress = type == "connecting";
|
let show_progress = type == "connecting";
|
||||||
self.style.set { border: color + " solid 1px" };
|
document.body.style.setProperty("border",(color + " solid 1px"));
|
||||||
var me = this;
|
setTimeout(()=>this.$("#content").content(my_translate(content)),1);
|
||||||
self.timer(1ms, function() {
|
|
||||||
if (typeof content == "string")
|
|
||||||
me.$(#content).html = my_translate(content);
|
|
||||||
else
|
|
||||||
me.$(#content).content(content);
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
<div style="size: *">
|
<div style="size: *">
|
||||||
<header style={"height: 2em; background: " + color}>
|
<header style={"height: 2em; background: " + color}>
|
||||||
@@ -113,72 +109,110 @@ class Body: Reactor.Component {
|
|||||||
<div style="padding: 1em 2em; size: *;">
|
<div style="padding: 1em 2em; size: *;">
|
||||||
<div style="height: *; flow: horizontal">
|
<div style="height: *; flow: horizontal">
|
||||||
{icon && <div style="height: *; margin: * 0; padding-right: 2em;">{icon}</div>}
|
{icon && <div style="height: *; margin: * 0; padding-right: 2em;">{icon}</div>}
|
||||||
<div style={contentStyle || "size: *; margin: * 0;"} #content />
|
<div id="content" style={contentStyle || "size: *; margin: * 0;"} />
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align: right;">
|
<div style="text-align: right;">
|
||||||
<span style="display:inline-block; max-width: 260px; font-size:12px;" #error />
|
<span id="error" style="display:inline-block; max-width: 260px; font-size:12px;" />
|
||||||
<progress #progress style={"color:" + color + "; display: " + (show_progress ? "inline-block" : "none")} />
|
<progress id="progress" style={"color:" + color + "; display: " + (show_progress ? "inline-block" : "none")} />
|
||||||
{hasCancel || hasRetry ? <button .button #cancel .outline>{my_translate(hasRetry ? "OK" : "Cancel")}</button> : ""}
|
{hasCancel || retry ? <button id="cancel" class="button outline">{my_translate(retry ? "OK" : "Cancel")}</button> : ""}
|
||||||
{this.hasSkip() ? <button .button #skip .outline>{my_translate('Skip')}</button> : ""}
|
{this.hasSkip() ? <button id="skip" class="button outline">{my_translate('Skip')}</button> : ""}
|
||||||
{hasOk || hasRetry ? <button .button #submit>{my_translate(hasRetry ? "Retry" : "OK")}</button> : ""}
|
{hasOk || retry ? <button id="submit" class="button" >{my_translate(retry ? "Retry" : "OK")}</button> : ""}
|
||||||
{hasClose ? <button .button #cancel .outline>{my_translate('Close')}</button> : ""}
|
{hasClose ? <button id="cancel" class="button outline">{my_translate('Close')}</button> : ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(.custom-event) (_, me) {
|
// TEST
|
||||||
|
["on click at .custom-event"](_, me) {
|
||||||
if (callback) callback(me);
|
if (callback) callback(me);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$(body).content(<Body />);
|
["on click at button#cancel"]() {
|
||||||
|
view.close();
|
||||||
|
if (callback) callback(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at button#skip"]() {
|
||||||
|
let values = getValues();
|
||||||
|
values.skip = true;
|
||||||
|
view.close(values);
|
||||||
|
if (callback) callback(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at button#submit"](){
|
||||||
|
if (type == "error") {
|
||||||
|
if (retry) {
|
||||||
|
view.close({ reconnect: true });
|
||||||
|
} else {
|
||||||
|
view.close();
|
||||||
|
if (callback) callback(null);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type == "re-input-password") {
|
||||||
|
type = "input-password";
|
||||||
|
body.componentUpdate();
|
||||||
|
set_outline_focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var values = getValues();
|
||||||
|
if (callback) {
|
||||||
|
var err = callback(values, show_progress);
|
||||||
|
if (err && !err.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
show_progress(false, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.close(values);
|
||||||
|
}
|
||||||
|
["on keydown"] (evt) { // TEST
|
||||||
|
if (!evt.shortcutKey) {
|
||||||
|
// TODO TEST Windows/Mac
|
||||||
|
if (evt.code == "KeyRETURN") {
|
||||||
|
submit();
|
||||||
|
}
|
||||||
|
if (evt.code == "KeyESCAPE") {
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function show_progress(show=1, err="") {
|
function show_progress(show=1, err="") {
|
||||||
if (show == -1) {
|
if (show == -1) {
|
||||||
view.close()
|
view.close()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$(#progress).style.set {
|
$("#progress").style.setProperty("display",show ? "inline-block" : "none");
|
||||||
display: show ? "inline-block" : "none"
|
$("#error").text = err;
|
||||||
};
|
|
||||||
$(#error).text = err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
if ($(button#submit)) {
|
if ($("button#submit")) {
|
||||||
$(button#submit).sendEvent("click");
|
$("button#submit").click(); // TEST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
if ($(button#cancel)) {
|
if ($("button#cancel")) {
|
||||||
$(button#cancel).sendEvent("click");
|
$("button#cancel").click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(button#cancel) {
|
|
||||||
view.close();
|
|
||||||
if (callback) callback(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(button#skip) {
|
|
||||||
var values = getValues();
|
|
||||||
values.skip = true;
|
|
||||||
view.close(values);
|
|
||||||
if (callback) callback(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getValues() {
|
function getValues() {
|
||||||
var values = { type: type };
|
let values = { type: type };
|
||||||
for (var el in $$(.form input)) {
|
for (let el of $$(".form input")) {
|
||||||
values[el.attributes["name"]] = el.value;
|
values[el.getAttribute("name")] = el.value;
|
||||||
}
|
}
|
||||||
for (var el in $$(.form textarea)) {
|
for (let el of $$(".form textarea")) {
|
||||||
values[el.attributes["name"]] = el.value;
|
values[el.getAttribute("name")] = el.value;
|
||||||
}
|
}
|
||||||
for (var el in $$(.form button)) {
|
for (let el of $$(".form button")) {
|
||||||
values[el.attributes["name"]] = el.value;
|
values[el.getAttribute("name")] = el.value;
|
||||||
}
|
}
|
||||||
if (type == "input-password") {
|
if (type == "input-password") {
|
||||||
values.password = (values.password || "").trim();
|
values.password = (values.password || "").trim();
|
||||||
@@ -189,76 +223,31 @@ function getValues() {
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(button#submit) {
|
|
||||||
if (type == "error") {
|
|
||||||
if (hasRetry) {
|
|
||||||
view.close({ reconnect: true });
|
|
||||||
} else {
|
|
||||||
view.close();
|
|
||||||
if (callback) callback(null);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type == "re-input-password") {
|
|
||||||
type = "input-password";
|
|
||||||
body.update();
|
|
||||||
set_outline_focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var values = getValues();
|
|
||||||
if (callback) {
|
|
||||||
var err = callback(values, show_progress);
|
|
||||||
if (err && !err.trim()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (err) {
|
|
||||||
show_progress(false, err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
view.close(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
event keydown (evt) {
|
|
||||||
if (!evt.shortcutKey) {
|
|
||||||
if (evt.keyCode == Event.VK_ENTER ||
|
|
||||||
(is_osx && evt.keyCode == 0x4C) ||
|
|
||||||
(is_linux && evt.keyCode == 65421)) {
|
|
||||||
submit();
|
|
||||||
}
|
|
||||||
if (evt.keyCode == Event.VK_ESCAPE) {
|
|
||||||
cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_outline_focus() {
|
function set_outline_focus() {
|
||||||
self.timer(30ms, function() {
|
setTimeout(function() {
|
||||||
var el = $(.outline-focus);
|
let el = $(".outline-focus");
|
||||||
if (el) view.focus = el;
|
if (el) view.focus = el;
|
||||||
else {
|
else {
|
||||||
el = $(#submit);
|
el = $("#submit");
|
||||||
if (el) view.focus = el;
|
if (el) view.focus = el;
|
||||||
}
|
}
|
||||||
});
|
},30);
|
||||||
}
|
}
|
||||||
|
|
||||||
set_outline_focus();
|
set_outline_focus();
|
||||||
|
|
||||||
function checkParams() {
|
// checkParams
|
||||||
self.timer(30ms, function() {
|
setInterval(function() {
|
||||||
var tmp = getParams();
|
let tmp = getParams();
|
||||||
if (!tmp || !tmp.type) {
|
if (!tmp || !tmp.type) {
|
||||||
view.close("!alive");
|
view.close("!alive");
|
||||||
return;
|
return;
|
||||||
} else if (tmp != params) {
|
} else if (tmp != params) {
|
||||||
params = tmp;
|
params = tmp;
|
||||||
updateParams(params);
|
updateParams(params);
|
||||||
body.update();
|
body.componentUpdate();
|
||||||
set_outline_focus();
|
set_outline_focus();
|
||||||
}
|
}
|
||||||
checkParams();
|
},30);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
checkParams();
|
document.body.content(<Body />);
|
||||||
81
src/ui/port_forward.js
Normal file
81
src/ui/port_forward.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { translate, handler } from "./common.js";
|
||||||
|
import { svg_arrow, svg_cancel } from "./file_transfer.js";
|
||||||
|
|
||||||
|
class PortForward extends Element {
|
||||||
|
render() {
|
||||||
|
let args = handler.xcall("get_args");
|
||||||
|
let is_rdp = handler.xcall("is_rdp");
|
||||||
|
if (is_rdp) {
|
||||||
|
this.pfs = [["", "", "RDP"]];
|
||||||
|
args = ["rdp"];
|
||||||
|
} else if (args.length) {
|
||||||
|
this.pfs = [args];
|
||||||
|
} else {
|
||||||
|
this.pfs = handler.xcall("get_port_forwards");
|
||||||
|
}
|
||||||
|
let pfs = this.pfs.map(function(pf, i) {
|
||||||
|
return (<tr key={i} class="value">
|
||||||
|
<td>{is_rdp ? <button class="button" id="new-rdp">New RDP</button> : pf[0]}</td>
|
||||||
|
<td class="right-arrow" style="text-align: center; padding-left: 0">{args.length ? svg_arrow : ""}</td>
|
||||||
|
<td>{pf[1] || "localhost"}</td>
|
||||||
|
<td>{pf[2]}</td>
|
||||||
|
{args.length ? "" : <td class="remove">{svg_cancel}</td>}
|
||||||
|
</tr>);
|
||||||
|
});
|
||||||
|
return <div id="file-transfer"><section>
|
||||||
|
{pfs.length ? <div style="background: green; color: white; text-align: center; padding: 0.5em;">
|
||||||
|
<span style="font-size: 1.2em">{translate('Listening ...')}</span><br/>
|
||||||
|
<span style="font-size: 0.8em; color: #ddd">{translate('not_close_tcp_tip')}</span>
|
||||||
|
</div> : ""}
|
||||||
|
<table id="port-forward">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{translate('Local Port')}</th>
|
||||||
|
<th style="width: 1em" />
|
||||||
|
<th>{translate('Remote Host')}</th>
|
||||||
|
<th>{translate('Remote Port')}</th>
|
||||||
|
{args.length ? "" : <th style="width: 6em">{translate('Action')}</th>}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody key={pfs.length}>
|
||||||
|
{args.length ? "" :
|
||||||
|
<tr>
|
||||||
|
<td><input type="number" id="port" /></td>
|
||||||
|
<td class="right-arrow" style="text-align: center">{svg_arrow}</td>
|
||||||
|
<td><input type="text" id="remote-host" novalue="localhost" /></td>
|
||||||
|
<td><input type="number" id="remote-port" /></td>
|
||||||
|
<td style="margin:0;"><button class="button" id="add">{translate('Add')}</button></td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
{pfs}
|
||||||
|
</tbody>
|
||||||
|
</table></section></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #add"] () {
|
||||||
|
let port = ($("#port").value || "").toInteger() || 0; // TODO toInteger
|
||||||
|
let remote_host = $("#remote-host").value || "";
|
||||||
|
let remote_port = ($("#remote-port").value || "").toInteger() || 0; // TODO toInteger
|
||||||
|
if (port <= 0 || remote_port <= 0) return;
|
||||||
|
handler.xcall("add_port_forward",port, remote_host, remote_port);
|
||||||
|
this.componentUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at #new-rdp"] () {
|
||||||
|
handler.xcall("new_rdp");
|
||||||
|
}
|
||||||
|
|
||||||
|
["on click at .remove svg"](_, me) {
|
||||||
|
let pf = this.pfs[me.parentElement.parentElement.index - 1];
|
||||||
|
handler.xcall("remove_port_forward",pf[0]);
|
||||||
|
this.componentUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initializePortForward()
|
||||||
|
{
|
||||||
|
document.$("#file-transfer-wrapper").content(<PortForward />);
|
||||||
|
document.$("#video-wrapper").style.setProperty("visibility","hidden");
|
||||||
|
document.$("#video-wrapper").style.setProperty("position","absolute")
|
||||||
|
document.$("#file-transfer-wrapper").style.setProperty("display","block");
|
||||||
|
}
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
class PortForward: Reactor.Component {
|
|
||||||
function render() {
|
|
||||||
var args = handler.get_args();
|
|
||||||
var is_rdp = handler.is_rdp();
|
|
||||||
if (is_rdp) {
|
|
||||||
this.pfs = [["", "", "RDP"]];
|
|
||||||
args = ["rdp"];
|
|
||||||
} else if (args.length) {
|
|
||||||
this.pfs = [args];
|
|
||||||
} else {
|
|
||||||
this.pfs = handler.get_port_forwards();
|
|
||||||
}
|
|
||||||
var pfs = this.pfs.map(function(pf, i) {
|
|
||||||
return <tr key={i} .value>
|
|
||||||
<td>{is_rdp ? <button .button #new-rdp>New RDP</button> : pf[0]}</td>
|
|
||||||
<td .right-arrow style="text-align: center; padding-left: 0">{args.length ? svg_arrow : ""}</td>
|
|
||||||
<td>{pf[1] || "localhost"}</td>
|
|
||||||
<td>{pf[2]}</td>
|
|
||||||
{args.length ? "" : <td .remove>{svg_cancel}</td>}
|
|
||||||
</tr>;
|
|
||||||
});
|
|
||||||
return <div #file-transfer><section>
|
|
||||||
{pfs.length ? <div style="background: green; color: white; text-align: center; padding: 0.5em;">
|
|
||||||
<span style="font-size: 1.2em">{translate('Listening ...')}</span><br/>
|
|
||||||
<span style="font-size: 0.8em; color: #ddd">{translate('not_close_tcp_tip')}</span>
|
|
||||||
</div> : ""}
|
|
||||||
<table #port-forward>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{translate('Local Port')}</th>
|
|
||||||
<th style="width: 1em" />
|
|
||||||
<th>{translate('Remote Host')}</th>
|
|
||||||
<th>{translate('Remote Port')}</th>
|
|
||||||
{args.length ? "" : <th style="width: 6em">{translate('Action')}</th>}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody key={pfs.length}>
|
|
||||||
{args.length ? "" :
|
|
||||||
<tr>
|
|
||||||
<td><input|number #port /></td>
|
|
||||||
<td .right-arrow style="text-align: center">{svg_arrow}</td>
|
|
||||||
<td><input|text #remote-host novalue="localhost" /></td>
|
|
||||||
<td><input|number #remote-port /></td>
|
|
||||||
<td style="margin:0;"><button .button #add>{translate('Add')}</button></td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
{pfs}
|
|
||||||
</tbody>
|
|
||||||
</table></section></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#add) () {
|
|
||||||
var port = ($(#port).value || "").toInteger() || 0;
|
|
||||||
var remote_host = $(#remote-host).value || "";
|
|
||||||
var remote_port = ($(#remote-port).value || "").toInteger() || 0;
|
|
||||||
if (port <= 0 || remote_port <= 0) return;
|
|
||||||
handler.add_port_forward(port, remote_host, remote_port);
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#new-rdp) {
|
|
||||||
handler.new_rdp();
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(.remove svg) (_, me) {
|
|
||||||
var pf = this.pfs[me.parent.parent.index - 1];
|
|
||||||
handler.remove_port_forward(pf[0]);
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializePortForward()
|
|
||||||
{
|
|
||||||
$(#file-transfer-wrapper).content(<PortForward />);
|
|
||||||
$(#video-wrapper).style.set { visibility: "hidden", position: "absolute" };
|
|
||||||
$(#file-transfer-wrapper).style.set { display: "block" };
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,24 @@
|
|||||||
<html window-resizable window-frame="extended">
|
<html window-resizable window-frame="extended">
|
||||||
<head>
|
<head>
|
||||||
<style>
|
<link rel="stylesheet" href="common.css">
|
||||||
|
<link rel="stylesheet" href="remote.css">
|
||||||
|
<link rel="stylesheet" href="file_transfer.css">
|
||||||
|
<link rel="stylesheet" href="header.css">
|
||||||
|
<!-- <style>
|
||||||
@import url(common.css);
|
@import url(common.css);
|
||||||
@import url(remote.css);
|
@import url(remote.css);
|
||||||
@import url(file_transfer.css);
|
@import url(file_transfer.css);
|
||||||
@import url(header.css);
|
@import url(header.css);
|
||||||
</style>
|
</style> -->
|
||||||
<script type="text/tiscript">
|
<script type="module" src="remote.js"></script>
|
||||||
|
<!-- <script type="text/tiscript">
|
||||||
include "common.tis";
|
include "common.tis";
|
||||||
include "remote.tis";
|
include "remote.tis";
|
||||||
include "file_transfer.tis";
|
include "file_transfer.tis";
|
||||||
include "port_forward.tis";
|
include "port_forward.tis";
|
||||||
include "grid.tis";
|
include "grid.tis";
|
||||||
include "header.tis";
|
include "header.tis";
|
||||||
</script>
|
</script> -->
|
||||||
</head>
|
</head>
|
||||||
<header>
|
<header>
|
||||||
<div.window-icon role="window-icon"><icon /></div>
|
<div.window-icon role="window-icon"><icon /></div>
|
||||||
|
|||||||
470
src/ui/remote.js
Normal file
470
src/ui/remote.js
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
import { $ } from "@sciter";
|
||||||
|
import { handler,view,isReasonableSize,msgbox,is_osx, is_linux, centerize, connecting } from "./common.js";
|
||||||
|
import { initializeFileTransfer, save_file_transfer_close_state } from "./file_transfer.js";
|
||||||
|
import { initializePortForward } from "./port_forward.js";
|
||||||
|
import { header } from "./header.js";
|
||||||
|
|
||||||
|
const body = document.body;
|
||||||
|
var cursor_img = $("img#cursor");
|
||||||
|
var last_key_time = 0;
|
||||||
|
var display_width = 0;
|
||||||
|
var display_height = 0;
|
||||||
|
var display_origin_x = 0;
|
||||||
|
var display_origin_y = 0;
|
||||||
|
var display_scale = 1;
|
||||||
|
export var keyboard_enabled = true; // server side
|
||||||
|
export var clipboard_enabled = true; // server side
|
||||||
|
export var audio_enabled = true; // server side
|
||||||
|
|
||||||
|
handler.is_port_forward = handler.xcall("is_port_forward");
|
||||||
|
handler.is_file_transfer = handler.xcall("is_file_transfer");
|
||||||
|
|
||||||
|
handler.setDisplay = function(x, y, w, h) {
|
||||||
|
display_width = w;
|
||||||
|
display_height = h;
|
||||||
|
display_origin_x = x;
|
||||||
|
display_origin_y = y;
|
||||||
|
adaptDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function adaptDisplay() {
|
||||||
|
let w = display_width;
|
||||||
|
let h = display_height;
|
||||||
|
if (!w || !h) return;
|
||||||
|
let style = handler.xcall("get_view_style");
|
||||||
|
display_scale = 1.;
|
||||||
|
let [sx, sy, sw, sh] = view.screenBox(view.state == Window.WINDOW_FULL_SCREEN ? "frame" : "workarea", "rectw");
|
||||||
|
if (sw >= w && sh > h) {
|
||||||
|
let hh = $("header").state.box("height", "border");
|
||||||
|
let el = $("div#adjust-window");
|
||||||
|
if (sh > h + hh && el) {
|
||||||
|
el.style.setProperty("display","block");
|
||||||
|
el = $("li#adjust-window");
|
||||||
|
el.style.setProperty("display","block");
|
||||||
|
el.on("click",function() {
|
||||||
|
view.state = Window.WINDOW_SHOWN;
|
||||||
|
let [x, y] = body.state.box("position", "border", "screen"); // TEST
|
||||||
|
// extra for border
|
||||||
|
let extra = 2;
|
||||||
|
view.move(x, y, w + extra, h + hh + extra);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (style != "original") {
|
||||||
|
let bw = body.state.box("width", "border");
|
||||||
|
let bh = body.state.box("height", "border");
|
||||||
|
if (view.state == Window.WINDOW_FULL_SCREEN) {
|
||||||
|
bw = sw;
|
||||||
|
bh = sh;
|
||||||
|
}
|
||||||
|
if (bw > 0 && bh > 0) {
|
||||||
|
// TEST del toFloat()
|
||||||
|
let scale_x = bw / w;
|
||||||
|
let scale_y = bh / h;
|
||||||
|
let scale = scale_x < scale_y ? scale_x : scale_y;
|
||||||
|
if ((scale > 1 && style == "stretch") ||
|
||||||
|
(scale < 1 && style == "shrink")) {
|
||||||
|
display_scale = scale;
|
||||||
|
w = w * scale;
|
||||||
|
h = h * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handler.style.setProperty("width",w + "px");
|
||||||
|
handler.style.setProperty("height",h + "px")
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://sciter.com/event-handling/
|
||||||
|
// https://sciter.com/docs/content/sciter/Event.htm
|
||||||
|
|
||||||
|
var entered = false;
|
||||||
|
|
||||||
|
// TODO !
|
||||||
|
// var keymap = {};
|
||||||
|
// for (var (k, v) in Event) {
|
||||||
|
// k = k + ""
|
||||||
|
// if (k[0] == "V" && k[1] == "K") {
|
||||||
|
// keymap[v] = k;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// VK_ENTER = VK_RETURN
|
||||||
|
// somehow, handler.onKey and view.onKey not working
|
||||||
|
// function self.onKey(evt) {
|
||||||
|
// last_key_time = getTime();
|
||||||
|
// if (is_file_transfer || is_port_forward) return false;
|
||||||
|
// if (!entered) return false;
|
||||||
|
// if (!keyboard_enabled) return false;
|
||||||
|
// switch (evt.type) {
|
||||||
|
// case Event.KEY_DOWN:
|
||||||
|
// handler.key_down_or_up(1, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey,
|
||||||
|
// evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
|
||||||
|
// if (is_osx && evt.commandKey) {
|
||||||
|
// handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey,
|
||||||
|
// evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// case Event.KEY_UP:
|
||||||
|
// handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey,
|
||||||
|
// evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
|
||||||
|
// break;
|
||||||
|
// case Event.KEY_CHAR:
|
||||||
|
// // the keypress event is fired when the element receives character value. Event.keyCode is a UNICODE code point of the character
|
||||||
|
// handler.key_down_or_up(2, "", evt.keyCode, evt.altKey,
|
||||||
|
// evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
var wait_window_toolbar = false;
|
||||||
|
var last_mouse_mask;
|
||||||
|
var acc_wheel_delta_x = 0;
|
||||||
|
var acc_wheel_delta_y = 0;
|
||||||
|
var last_wheel_time = 0;
|
||||||
|
var inertia_velocity_x = 0;
|
||||||
|
var inertia_velocity_y = 0;
|
||||||
|
var acc_wheel_delta_x0 = 0;
|
||||||
|
var acc_wheel_delta_y0 = 0;
|
||||||
|
var total_wheel_time = 0;
|
||||||
|
var wheeling = false;
|
||||||
|
var dragging = false;
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/5833399/calculating-scroll-inertia-momentum
|
||||||
|
function resetWheel() {
|
||||||
|
acc_wheel_delta_x = 0;
|
||||||
|
acc_wheel_delta_y = 0;
|
||||||
|
last_wheel_time = 0;
|
||||||
|
inertia_velocity_x = 0;
|
||||||
|
inertia_velocity_y = 0;
|
||||||
|
acc_wheel_delta_x0 = 0;
|
||||||
|
acc_wheel_delta_y0 = 0;
|
||||||
|
total_wheel_time = 0;
|
||||||
|
wheeling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var INERTIA_ACCELERATION = 30;
|
||||||
|
|
||||||
|
// not good, precision not enough to simulate accelation effect,
|
||||||
|
// seems have to use pixel based rather line based delta
|
||||||
|
function accWheel(v, is_x) {
|
||||||
|
if (wheeling) return;
|
||||||
|
var abs_v = Math.abs(v);
|
||||||
|
var max_t = abs_v / INERTIA_ACCELERATION;
|
||||||
|
for (var t = 0.1; t < max_t; t += 0.1) {
|
||||||
|
var d = Math.round((abs_v - t * INERTIA_ACCELERATION / 2) * t).toInteger();
|
||||||
|
if (d >= 1) {
|
||||||
|
abs_v -= t * INERTIA_ACCELERATION;
|
||||||
|
if (v < 0) {
|
||||||
|
d = -d;
|
||||||
|
v = -abs_v;
|
||||||
|
} else {
|
||||||
|
v = abs_v;
|
||||||
|
}
|
||||||
|
handler.xcall("send_mouse",3, is_x ? d : 0, !is_x ? d : 0, false, false, false, false);
|
||||||
|
accWheel(v, is_x);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // TODO
|
||||||
|
// handler.onMouse(evt)
|
||||||
|
// {
|
||||||
|
// if (is_file_transfer || is_port_forward) return false;
|
||||||
|
// if (view.windowState == View.WINDOW_FULL_SCREEN && !dragging) {
|
||||||
|
// var dy = evt.y - scroll_body.scroll(#top);
|
||||||
|
// if (dy <= 1) {
|
||||||
|
// if (!wait_window_toolbar) {
|
||||||
|
// wait_window_toolbar = true;
|
||||||
|
// self.timer(300ms, function() {
|
||||||
|
// if (!wait_window_toolbar) return;
|
||||||
|
// var extra = 0;
|
||||||
|
// // workaround for stupid Sciter, without this, click
|
||||||
|
// // event not triggered on top part of buttons on toolbar
|
||||||
|
// if (is_osx) extra = 10;
|
||||||
|
// if (view.windowState == View.WINDOW_FULL_SCREEN) {
|
||||||
|
// $(header).style.set {
|
||||||
|
// display: "block",
|
||||||
|
// padding: (2 * workarea_offset + extra) + "px 0 0 0",
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// wait_window_toolbar = false;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// wait_window_toolbar = false;
|
||||||
|
// var h = $(header).style;
|
||||||
|
// if (dy > 20 && h#display != "none") {
|
||||||
|
// h.set {
|
||||||
|
// display: "none",
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if (!got_mouse_control) {
|
||||||
|
// if (Math.abs(evt.x - cur_local_x) > 12 || Math.abs(evt.y - cur_local_y) > 12) {
|
||||||
|
// got_mouse_control = true;
|
||||||
|
// } else {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// var mask = 0;
|
||||||
|
// var wheel_delta_x;
|
||||||
|
// var wheel_delta_y;
|
||||||
|
// switch(evt.type) {
|
||||||
|
// case Event.MOUSE_DOWN:
|
||||||
|
// mask = 1;
|
||||||
|
// dragging = true;
|
||||||
|
// break;
|
||||||
|
// case Event.MOUSE_UP:
|
||||||
|
// mask = 2;
|
||||||
|
// dragging = false;
|
||||||
|
// break;
|
||||||
|
// case Event.MOUSE_MOVE:
|
||||||
|
// if (cursor_img.style#display != "none" && keyboard_enabled) {
|
||||||
|
// cursor_img.style#display = "none";
|
||||||
|
// handler.style#cursor = '';
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// case Event.MOUSE_WHEEL:
|
||||||
|
// // mouseWheelDistance = 8 * [currentUserDefs floatForKey:@"com.apple.scrollwheel.scaling"];
|
||||||
|
// mask = 3;
|
||||||
|
// {
|
||||||
|
// var (dx, dy) = evt.wheelDeltas;
|
||||||
|
// if (dx > 0) dx = 1;
|
||||||
|
// else if (dx < 0) dx = -1;
|
||||||
|
// if (dy > 0) dy = 1;
|
||||||
|
// else if (dy < 0) dy = -1;
|
||||||
|
// if (Math.abs(dx) > Math.abs(dy)) {
|
||||||
|
// dy = 0;
|
||||||
|
// } else {
|
||||||
|
// dx = 0;
|
||||||
|
// }
|
||||||
|
// acc_wheel_delta_x += dx;
|
||||||
|
// acc_wheel_delta_y += dy;
|
||||||
|
// wheel_delta_x = acc_wheel_delta_x.toInteger();
|
||||||
|
// wheel_delta_y = acc_wheel_delta_y.toInteger();
|
||||||
|
// acc_wheel_delta_x -= wheel_delta_x;
|
||||||
|
// acc_wheel_delta_y -= wheel_delta_y;
|
||||||
|
// var now = getTime();
|
||||||
|
// var dt = last_wheel_time > 0 ? (now - last_wheel_time) / 1000 : 0;
|
||||||
|
// if (dt > 0) {
|
||||||
|
// var vx = dx / dt;
|
||||||
|
// var vy = dy / dt;
|
||||||
|
// if (vx != 0 || vy != 0) {
|
||||||
|
// inertia_velocity_x = vx;
|
||||||
|
// inertia_velocity_y = vy;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// acc_wheel_delta_x0 += dx;
|
||||||
|
// acc_wheel_delta_y0 += dy;
|
||||||
|
// total_wheel_time += dt;
|
||||||
|
// if (dx == 0 && dy == 0) {
|
||||||
|
// wheeling = false;
|
||||||
|
// if (dt < 0.1 && total_wheel_time > 0) {
|
||||||
|
// var v2 = (acc_wheel_delta_y0 / total_wheel_time) * inertia_velocity_y;
|
||||||
|
// if (v2 > 0) {
|
||||||
|
// v2 = Math.sqrt(v2);
|
||||||
|
// inertia_velocity_y = inertia_velocity_y < 0 ? -v2 : v2;
|
||||||
|
// accWheel(inertia_velocity_y, false);
|
||||||
|
// }
|
||||||
|
// v2 = (acc_wheel_delta_x0 / total_wheel_time) * inertia_velocity_x;
|
||||||
|
// if (v2 > 0) {
|
||||||
|
// v2 = Math.sqrt(v2);
|
||||||
|
// inertia_velocity_x = inertia_velocity_x < 0 ? -v2 : v2;
|
||||||
|
// accWheel(inertia_velocity_x, true);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// resetWheel();
|
||||||
|
// } else {
|
||||||
|
// wheeling = true;
|
||||||
|
// }
|
||||||
|
// last_wheel_time = now;
|
||||||
|
// if (wheel_delta_x == 0 && wheel_delta_y == 0) return keyboard_enabled;
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// case Event.MOUSE_DCLICK: // seq: down, up, dclick, up
|
||||||
|
// mask = 1;
|
||||||
|
// break;
|
||||||
|
// case Event.MOUSE_ENTER:
|
||||||
|
// entered = true;
|
||||||
|
// console.log("enter");
|
||||||
|
// return keyboard_enabled;
|
||||||
|
// case Event.MOUSE_LEAVE:
|
||||||
|
// entered = false;
|
||||||
|
// console.log("leave");
|
||||||
|
// return keyboard_enabled;
|
||||||
|
// default:
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// var x = evt.x;
|
||||||
|
// var y = evt.y;
|
||||||
|
// if (mask != 0) {
|
||||||
|
// // to gain control of the mouse, user must move mouse
|
||||||
|
// if (cur_x != x || cur_y != y) {
|
||||||
|
// return keyboard_enabled;
|
||||||
|
// }
|
||||||
|
// // save bandwidth
|
||||||
|
// x = 0;
|
||||||
|
// y = 0;
|
||||||
|
// } else {
|
||||||
|
// cur_local_x = cur_x = x;
|
||||||
|
// cur_local_y = cur_y = y;
|
||||||
|
// }
|
||||||
|
// if (mask != 3) {
|
||||||
|
// resetWheel();
|
||||||
|
// }
|
||||||
|
// if (!keyboard_enabled) return false;
|
||||||
|
// x = (x / display_scale).toInteger();
|
||||||
|
// y = (y / display_scale).toInteger();
|
||||||
|
// // insert down between two up, osx has this behavior for triple click
|
||||||
|
// if (last_mouse_mask == 2 && mask == 2) {
|
||||||
|
// handler.send_mouse((evt.buttons << 3) | 1, x + display_origin_x, y + display_origin_y, evt.altKey,
|
||||||
|
// evt.ctrlKey, evt.shiftKey, evt.commandKey);
|
||||||
|
// }
|
||||||
|
// last_mouse_mask = mask;
|
||||||
|
// // to-do: altKey, ctrlKey etc
|
||||||
|
// handler.send_mouse((evt.buttons << 3) | mask,
|
||||||
|
// mask == 3 ? wheel_delta_x : x + display_origin_x,
|
||||||
|
// mask == 3 ? wheel_delta_y : y + display_origin_y,
|
||||||
|
// evt.altKey,
|
||||||
|
// evt.ctrlKey, evt.shiftKey, evt.commandKey);
|
||||||
|
// return true;
|
||||||
|
// };
|
||||||
|
|
||||||
|
var cur_hotx = 0;
|
||||||
|
var cur_hoty = 0;
|
||||||
|
var cur_img = null;
|
||||||
|
var cur_x = 0;
|
||||||
|
var cur_y = 0;
|
||||||
|
var cur_local_x = 0;
|
||||||
|
var cur_local_y = 0;
|
||||||
|
var cursors = {};
|
||||||
|
var image_binded;
|
||||||
|
|
||||||
|
handler.setCursorData = function(id, hotx, hoty, width, height, colors) {
|
||||||
|
cur_hotx = hotx;
|
||||||
|
cur_hoty = hoty;
|
||||||
|
cursor_img.style.setProperty("width",width + "px");
|
||||||
|
cursor_img.style.setProperty("height",height + "px");
|
||||||
|
|
||||||
|
let img = Image.fromBytes(colors);
|
||||||
|
if (img) {
|
||||||
|
image_binded = true;
|
||||||
|
cursors[id] = [img, hotx, hoty, width, height];
|
||||||
|
this.bindImage("in-memory:cursor", img);
|
||||||
|
if (cursor_img.style.getProperty("display") == 'none') { // TEST getProperty
|
||||||
|
setTimeout(function() { handler.style.cursor(cur_img, cur_hotx, cur_hoty) },1); // TODO cursor
|
||||||
|
}
|
||||||
|
cur_img = img;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.setCursorId = function(id) {
|
||||||
|
let img = cursors[id];
|
||||||
|
if (img) {
|
||||||
|
image_binded = true;
|
||||||
|
cur_hotx = img[1];
|
||||||
|
cur_hoty = img[2];
|
||||||
|
cursor_img.style.setProperty("width",img[3] + "px");
|
||||||
|
cursor_img.style.setProperty("height",img[4] + "px");
|
||||||
|
img = img[0];
|
||||||
|
this.bindImage("in-memory:cursor", img);
|
||||||
|
if (cursor_img.style.getProperty("display") == 'none') {
|
||||||
|
setTimeout(function() { handler.style.cursor(cur_img, cur_hotx, cur_hoty) },1);
|
||||||
|
}
|
||||||
|
cur_img = img;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var got_mouse_control = true;
|
||||||
|
handler.setCursorPosition = function(x, y) {
|
||||||
|
if (!image_binded) return;
|
||||||
|
got_mouse_control = false;
|
||||||
|
cur_x = x - display_origin_x;
|
||||||
|
cur_y = y - display_origin_y;
|
||||||
|
var x = cur_x - cur_hotx;
|
||||||
|
var y = cur_y - cur_hoty;
|
||||||
|
x *= display_scale;
|
||||||
|
y *= display_scale;
|
||||||
|
cursor_img.style.setProperty("width",x + "px");
|
||||||
|
cursor_img.style.setProperty("width",y + "px");
|
||||||
|
if (cursor_img.style.getProperty("display") == 'none') {
|
||||||
|
handler.style.setProperty("cursor",'none');
|
||||||
|
cursor_img.style.setProperty("display","block");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.on("ready",()=> {
|
||||||
|
let w = 960;
|
||||||
|
let h = 640;
|
||||||
|
if (handler.is_file_transfer || handler.is_port_forward) {
|
||||||
|
let r = handler.xcall("get_size");
|
||||||
|
if (isReasonableSize(r) && r[2] > 0) {
|
||||||
|
view.move(r[0], r[1], r[2], r[3]);
|
||||||
|
} else {
|
||||||
|
centerize(w, h);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
centerize(w, h);
|
||||||
|
}
|
||||||
|
if (!handler.is_port_forward) connecting();
|
||||||
|
if (handler.is_file_transfer) initializeFileTransfer();
|
||||||
|
if (handler.is_port_forward) initializePortForward();
|
||||||
|
})
|
||||||
|
|
||||||
|
var workarea_offset = 0;
|
||||||
|
var size_adapted;
|
||||||
|
handler.adaptSize = function() {
|
||||||
|
if (size_adapted) return;
|
||||||
|
size_adapted = true;
|
||||||
|
let [sx, sy, sw, sh] = view.screenBox("workarea", "rectw");
|
||||||
|
let [fx, fy, fw, fh] = view.screenBox("frame", "rectw");
|
||||||
|
if (is_osx) workarea_offset = sy;
|
||||||
|
let r = handler.xcall("get_size");
|
||||||
|
if (isReasonableSize(r) && r[2] > 0) {
|
||||||
|
if (r[2] >= fw && r[3] >= fh && !is_linux) {
|
||||||
|
view.state = Window.WINDOW_FULL_SCREEN;
|
||||||
|
console.log("Initialize to full screen");
|
||||||
|
} else if (r[2] >= sw && r[3] >= sh) {
|
||||||
|
view.state = Window.WINDOW_MAXIMIZED;
|
||||||
|
console.log("Initialize to full screen");
|
||||||
|
} else {
|
||||||
|
view.move(r[0], r[1], r[2], r[3]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let w = handler.state.box("width", "border")
|
||||||
|
let h = handler.state.box("height", "border")
|
||||||
|
if (w >= sw || h >= sh) {
|
||||||
|
view.state = Window.WINDOW_MAXIMIZED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// extra for border
|
||||||
|
let extra = 2;
|
||||||
|
centerize(w + extra, handler.state.box("height", "border") + h + extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.on("unloadequest",()=> {
|
||||||
|
let [x, y, w, h] = body.state.box("rectw", "border", "screen");
|
||||||
|
console.log("remote.js unloadequest",x, y, w, h)
|
||||||
|
if (handler.is_file_transfer) save_file_transfer_close_state();
|
||||||
|
if (handler.is_file_transfer || handler.is_port_forward || size_adapted) handler.xcall("save_size",x, y, w, h);
|
||||||
|
})
|
||||||
|
|
||||||
|
handler.setPermission = function(name, enabled) {
|
||||||
|
if (name == "keyboard") keyboard_enabled = enabled;
|
||||||
|
if (name == "audio") audio_enabled = enabled;
|
||||||
|
if (name == "clipboard") clipboard_enabled = enabled;
|
||||||
|
header.componentUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
handler.closeSuccess = function() {
|
||||||
|
// handler.msgbox("success", "Successful", "Ready to go.");
|
||||||
|
console.log("remote.js handler.closeSuccess");
|
||||||
|
handler.msgbox("", "", "");
|
||||||
|
}
|
||||||
@@ -75,7 +75,7 @@ impl Deref for Handler {
|
|||||||
|
|
||||||
impl sciter::EventHandler for Handler {
|
impl sciter::EventHandler for Handler {
|
||||||
fn get_subscription(&mut self) -> Option<EVENT_GROUPS> {
|
fn get_subscription(&mut self) -> Option<EVENT_GROUPS> {
|
||||||
Some(EVENT_GROUPS::HANDLE_BEHAVIOR_EVENT)
|
Some(EVENT_GROUPS::HANDLE_METHOD_CALL | EVENT_GROUPS::HANDLE_SCRIPTING_METHOD_CALL | EVENT_GROUPS::HANDLE_BEHAVIOR_EVENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attached(&mut self, root: HELEMENT) {
|
fn attached(&mut self, root: HELEMENT) {
|
||||||
|
|||||||
@@ -1,464 +0,0 @@
|
|||||||
var cursor_img = $(img#cursor);
|
|
||||||
var last_key_time = 0;
|
|
||||||
is_file_transfer = handler.is_file_transfer();
|
|
||||||
var is_port_forward = handler.is_port_forward();
|
|
||||||
var display_width = 0;
|
|
||||||
var display_height = 0;
|
|
||||||
var display_origin_x = 0;
|
|
||||||
var display_origin_y = 0;
|
|
||||||
var display_scale = 1;
|
|
||||||
var keyboard_enabled = true; // server side
|
|
||||||
var clipboard_enabled = true; // server side
|
|
||||||
var audio_enabled = true; // server side
|
|
||||||
var scroll_body = $(body);
|
|
||||||
|
|
||||||
handler.setDisplay = function(x, y, w, h) {
|
|
||||||
display_width = w;
|
|
||||||
display_height = h;
|
|
||||||
display_origin_x = x;
|
|
||||||
display_origin_y = y;
|
|
||||||
adaptDisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
function adaptDisplay() {
|
|
||||||
var w = display_width;
|
|
||||||
var h = display_height;
|
|
||||||
if (!w || !h) return;
|
|
||||||
var style = handler.get_view_style();
|
|
||||||
display_scale = 1.;
|
|
||||||
var (sx, sy, sw, sh) = view.screenBox(view.windowState == View.WINDOW_FULL_SCREEN ? #frame : #workarea, #rectw);
|
|
||||||
if (sw >= w && sh > h) {
|
|
||||||
var hh = $(header).box(#height, #border);
|
|
||||||
var el = $(div#adjust-window);
|
|
||||||
if (sh > h + hh && el) {
|
|
||||||
el.style.set{ display: "block" };
|
|
||||||
el = $(li#adjust-window);
|
|
||||||
el.style.set{ display: "block" };
|
|
||||||
el.onClick = function() {
|
|
||||||
view.windowState == View.WINDOW_SHOWN;
|
|
||||||
var (x, y) = view.box(#position, #border, #screen);
|
|
||||||
// extra for border
|
|
||||||
var extra = 2;
|
|
||||||
view.move(x, y, w + extra, h + hh + extra);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (style != "original") {
|
|
||||||
var bw = $(body).box(#width, #border);
|
|
||||||
var bh = $(body).box(#height, #border);
|
|
||||||
if (view.windowState == View.WINDOW_FULL_SCREEN) {
|
|
||||||
bw = sw;
|
|
||||||
bh = sh;
|
|
||||||
}
|
|
||||||
if (bw > 0 && bh > 0) {
|
|
||||||
var scale_x = bw.toFloat() / w;
|
|
||||||
var scale_y = bh.toFloat() / h;
|
|
||||||
var scale = scale_x < scale_y ? scale_x : scale_y;
|
|
||||||
if ((scale > 1 && style == "stretch") ||
|
|
||||||
(scale < 1 && style == "shrink")) {
|
|
||||||
display_scale = scale;
|
|
||||||
w = w * scale;
|
|
||||||
h = h * scale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handler.style.set {
|
|
||||||
width: w + "px",
|
|
||||||
height: h + "px",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://sciter.com/event-handling/
|
|
||||||
// https://sciter.com/docs/content/sciter/Event.htm
|
|
||||||
|
|
||||||
var entered = false;
|
|
||||||
|
|
||||||
var keymap = {};
|
|
||||||
for (var (k, v) in Event) {
|
|
||||||
k = k + ""
|
|
||||||
if (k[0] == "V" && k[1] == "K") {
|
|
||||||
keymap[v] = k;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VK_ENTER = VK_RETURN
|
|
||||||
// somehow, handler.onKey and view.onKey not working
|
|
||||||
function self.onKey(evt) {
|
|
||||||
last_key_time = getTime();
|
|
||||||
if (is_file_transfer || is_port_forward) return false;
|
|
||||||
if (!entered) return false;
|
|
||||||
if (!keyboard_enabled) return false;
|
|
||||||
switch (evt.type) {
|
|
||||||
case Event.KEY_DOWN:
|
|
||||||
handler.key_down_or_up(1, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey,
|
|
||||||
evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
|
|
||||||
if (is_osx && evt.commandKey) {
|
|
||||||
handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey,
|
|
||||||
evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Event.KEY_UP:
|
|
||||||
handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey,
|
|
||||||
evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
|
|
||||||
break;
|
|
||||||
case Event.KEY_CHAR:
|
|
||||||
// the keypress event is fired when the element receives character value. Event.keyCode is a UNICODE code point of the character
|
|
||||||
handler.key_down_or_up(2, "", evt.keyCode, evt.altKey,
|
|
||||||
evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var wait_window_toolbar = false;
|
|
||||||
var last_mouse_mask;
|
|
||||||
var acc_wheel_delta_x = 0;
|
|
||||||
var acc_wheel_delta_y = 0;
|
|
||||||
var last_wheel_time = 0;
|
|
||||||
var inertia_velocity_x = 0;
|
|
||||||
var inertia_velocity_y = 0;
|
|
||||||
var acc_wheel_delta_x0 = 0;
|
|
||||||
var acc_wheel_delta_y0 = 0;
|
|
||||||
var total_wheel_time = 0;
|
|
||||||
var wheeling = false;
|
|
||||||
var dragging = false;
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/5833399/calculating-scroll-inertia-momentum
|
|
||||||
function resetWheel() {
|
|
||||||
acc_wheel_delta_x = 0;
|
|
||||||
acc_wheel_delta_y = 0;
|
|
||||||
last_wheel_time = 0;
|
|
||||||
inertia_velocity_x = 0;
|
|
||||||
inertia_velocity_y = 0;
|
|
||||||
acc_wheel_delta_x0 = 0;
|
|
||||||
acc_wheel_delta_y0 = 0;
|
|
||||||
total_wheel_time = 0;
|
|
||||||
wheeling = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var INERTIA_ACCELERATION = 30;
|
|
||||||
|
|
||||||
// not good, precision not enough to simulate accelation effect,
|
|
||||||
// seems have to use pixel based rather line based delta
|
|
||||||
function accWheel(v, is_x) {
|
|
||||||
if (wheeling) return;
|
|
||||||
var abs_v = Math.abs(v);
|
|
||||||
var max_t = abs_v / INERTIA_ACCELERATION;
|
|
||||||
for (var t = 0.1; t < max_t; t += 0.1) {
|
|
||||||
var d = Math.round((abs_v - t * INERTIA_ACCELERATION / 2) * t).toInteger();
|
|
||||||
if (d >= 1) {
|
|
||||||
abs_v -= t * INERTIA_ACCELERATION;
|
|
||||||
if (v < 0) {
|
|
||||||
d = -d;
|
|
||||||
v = -abs_v;
|
|
||||||
} else {
|
|
||||||
v = abs_v;
|
|
||||||
}
|
|
||||||
handler.send_mouse(3, is_x ? d : 0, !is_x ? d : 0, false, false, false, false);
|
|
||||||
accWheel(v, is_x);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handler.onMouse(evt)
|
|
||||||
{
|
|
||||||
if (is_file_transfer || is_port_forward) return false;
|
|
||||||
if (view.windowState == View.WINDOW_FULL_SCREEN && !dragging) {
|
|
||||||
var dy = evt.y - scroll_body.scroll(#top);
|
|
||||||
if (dy <= 1) {
|
|
||||||
if (!wait_window_toolbar) {
|
|
||||||
wait_window_toolbar = true;
|
|
||||||
self.timer(300ms, function() {
|
|
||||||
if (!wait_window_toolbar) return;
|
|
||||||
var extra = 0;
|
|
||||||
// workaround for stupid Sciter, without this, click
|
|
||||||
// event not triggered on top part of buttons on toolbar
|
|
||||||
if (is_osx) extra = 10;
|
|
||||||
if (view.windowState == View.WINDOW_FULL_SCREEN) {
|
|
||||||
$(header).style.set {
|
|
||||||
display: "block",
|
|
||||||
padding: (2 * workarea_offset + extra) + "px 0 0 0",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
wait_window_toolbar = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wait_window_toolbar = false;
|
|
||||||
var h = $(header).style;
|
|
||||||
if (dy > 20 && h#display != "none") {
|
|
||||||
h.set {
|
|
||||||
display: "none",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!got_mouse_control) {
|
|
||||||
if (Math.abs(evt.x - cur_local_x) > 12 || Math.abs(evt.y - cur_local_y) > 12) {
|
|
||||||
got_mouse_control = true;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var mask = 0;
|
|
||||||
var wheel_delta_x;
|
|
||||||
var wheel_delta_y;
|
|
||||||
switch(evt.type) {
|
|
||||||
case Event.MOUSE_DOWN:
|
|
||||||
mask = 1;
|
|
||||||
dragging = true;
|
|
||||||
break;
|
|
||||||
case Event.MOUSE_UP:
|
|
||||||
mask = 2;
|
|
||||||
dragging = false;
|
|
||||||
break;
|
|
||||||
case Event.MOUSE_MOVE:
|
|
||||||
if (cursor_img.style#display != "none" && keyboard_enabled) {
|
|
||||||
cursor_img.style#display = "none";
|
|
||||||
handler.style#cursor = '';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Event.MOUSE_WHEEL:
|
|
||||||
// mouseWheelDistance = 8 * [currentUserDefs floatForKey:@"com.apple.scrollwheel.scaling"];
|
|
||||||
mask = 3;
|
|
||||||
{
|
|
||||||
var (dx, dy) = evt.wheelDeltas;
|
|
||||||
if (dx > 0) dx = 1;
|
|
||||||
else if (dx < 0) dx = -1;
|
|
||||||
if (dy > 0) dy = 1;
|
|
||||||
else if (dy < 0) dy = -1;
|
|
||||||
if (Math.abs(dx) > Math.abs(dy)) {
|
|
||||||
dy = 0;
|
|
||||||
} else {
|
|
||||||
dx = 0;
|
|
||||||
}
|
|
||||||
acc_wheel_delta_x += dx;
|
|
||||||
acc_wheel_delta_y += dy;
|
|
||||||
wheel_delta_x = acc_wheel_delta_x.toInteger();
|
|
||||||
wheel_delta_y = acc_wheel_delta_y.toInteger();
|
|
||||||
acc_wheel_delta_x -= wheel_delta_x;
|
|
||||||
acc_wheel_delta_y -= wheel_delta_y;
|
|
||||||
var now = getTime();
|
|
||||||
var dt = last_wheel_time > 0 ? (now - last_wheel_time) / 1000 : 0;
|
|
||||||
if (dt > 0) {
|
|
||||||
var vx = dx / dt;
|
|
||||||
var vy = dy / dt;
|
|
||||||
if (vx != 0 || vy != 0) {
|
|
||||||
inertia_velocity_x = vx;
|
|
||||||
inertia_velocity_y = vy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
acc_wheel_delta_x0 += dx;
|
|
||||||
acc_wheel_delta_y0 += dy;
|
|
||||||
total_wheel_time += dt;
|
|
||||||
if (dx == 0 && dy == 0) {
|
|
||||||
wheeling = false;
|
|
||||||
if (dt < 0.1 && total_wheel_time > 0) {
|
|
||||||
var v2 = (acc_wheel_delta_y0 / total_wheel_time) * inertia_velocity_y;
|
|
||||||
if (v2 > 0) {
|
|
||||||
v2 = Math.sqrt(v2);
|
|
||||||
inertia_velocity_y = inertia_velocity_y < 0 ? -v2 : v2;
|
|
||||||
accWheel(inertia_velocity_y, false);
|
|
||||||
}
|
|
||||||
v2 = (acc_wheel_delta_x0 / total_wheel_time) * inertia_velocity_x;
|
|
||||||
if (v2 > 0) {
|
|
||||||
v2 = Math.sqrt(v2);
|
|
||||||
inertia_velocity_x = inertia_velocity_x < 0 ? -v2 : v2;
|
|
||||||
accWheel(inertia_velocity_x, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resetWheel();
|
|
||||||
} else {
|
|
||||||
wheeling = true;
|
|
||||||
}
|
|
||||||
last_wheel_time = now;
|
|
||||||
if (wheel_delta_x == 0 && wheel_delta_y == 0) return keyboard_enabled;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Event.MOUSE_DCLICK: // seq: down, up, dclick, up
|
|
||||||
mask = 1;
|
|
||||||
break;
|
|
||||||
case Event.MOUSE_ENTER:
|
|
||||||
entered = true;
|
|
||||||
stdout.println("enter");
|
|
||||||
return keyboard_enabled;
|
|
||||||
case Event.MOUSE_LEAVE:
|
|
||||||
entered = false;
|
|
||||||
stdout.println("leave");
|
|
||||||
return keyboard_enabled;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var x = evt.x;
|
|
||||||
var y = evt.y;
|
|
||||||
if (mask != 0) {
|
|
||||||
// to gain control of the mouse, user must move mouse
|
|
||||||
if (cur_x != x || cur_y != y) {
|
|
||||||
return keyboard_enabled;
|
|
||||||
}
|
|
||||||
// save bandwidth
|
|
||||||
x = 0;
|
|
||||||
y = 0;
|
|
||||||
} else {
|
|
||||||
cur_local_x = cur_x = x;
|
|
||||||
cur_local_y = cur_y = y;
|
|
||||||
}
|
|
||||||
if (mask != 3) {
|
|
||||||
resetWheel();
|
|
||||||
}
|
|
||||||
if (!keyboard_enabled) return false;
|
|
||||||
x = (x / display_scale).toInteger();
|
|
||||||
y = (y / display_scale).toInteger();
|
|
||||||
// insert down between two up, osx has this behavior for triple click
|
|
||||||
if (last_mouse_mask == 2 && mask == 2) {
|
|
||||||
handler.send_mouse((evt.buttons << 3) | 1, x + display_origin_x, y + display_origin_y, evt.altKey,
|
|
||||||
evt.ctrlKey, evt.shiftKey, evt.commandKey);
|
|
||||||
}
|
|
||||||
last_mouse_mask = mask;
|
|
||||||
// to-do: altKey, ctrlKey etc
|
|
||||||
handler.send_mouse((evt.buttons << 3) | mask,
|
|
||||||
mask == 3 ? wheel_delta_x : x + display_origin_x,
|
|
||||||
mask == 3 ? wheel_delta_y : y + display_origin_y,
|
|
||||||
evt.altKey,
|
|
||||||
evt.ctrlKey, evt.shiftKey, evt.commandKey);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
var cur_hotx = 0;
|
|
||||||
var cur_hoty = 0;
|
|
||||||
var cur_img = null;
|
|
||||||
var cur_x = 0;
|
|
||||||
var cur_y = 0;
|
|
||||||
var cur_local_x = 0;
|
|
||||||
var cur_local_y = 0;
|
|
||||||
var cursors = {};
|
|
||||||
var image_binded;
|
|
||||||
|
|
||||||
handler.setCursorData = function(id, hotx, hoty, width, height, colors) {
|
|
||||||
cur_hotx = hotx;
|
|
||||||
cur_hoty = hoty;
|
|
||||||
cursor_img.style.set {
|
|
||||||
width: width + "px",
|
|
||||||
height: height + "px",
|
|
||||||
};
|
|
||||||
var img = Image.fromBytes(colors);
|
|
||||||
if (img) {
|
|
||||||
image_binded = true;
|
|
||||||
cursors[id] = [img, hotx, hoty, width, height];
|
|
||||||
this.bindImage("in-memory:cursor", img);
|
|
||||||
if (cursor_img.style#display == 'none') {
|
|
||||||
self.timer(1ms, function() { handler.style.cursor(cur_img, cur_hotx, cur_hoty); });
|
|
||||||
}
|
|
||||||
cur_img = img;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.setCursorId = function(id) {
|
|
||||||
var img = cursors[id];
|
|
||||||
if (img) {
|
|
||||||
image_binded = true;
|
|
||||||
cur_hotx = img[1];
|
|
||||||
cur_hoty = img[2];
|
|
||||||
cursor_img.style.set {
|
|
||||||
width: img[3] + "px",
|
|
||||||
height: img[4] + "px",
|
|
||||||
};
|
|
||||||
img = img[0];
|
|
||||||
this.bindImage("in-memory:cursor", img);
|
|
||||||
if (cursor_img.style#display == 'none') {
|
|
||||||
self.timer(1ms, function() { handler.style.cursor(cur_img, cur_hotx, cur_hoty); });
|
|
||||||
}
|
|
||||||
cur_img = img;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var got_mouse_control = true;
|
|
||||||
handler.setCursorPosition = function(x, y) {
|
|
||||||
if (!image_binded) return;
|
|
||||||
got_mouse_control = false;
|
|
||||||
cur_x = x - display_origin_x;
|
|
||||||
cur_y = y - display_origin_y;
|
|
||||||
var x = cur_x - cur_hotx;
|
|
||||||
var y = cur_y - cur_hoty;
|
|
||||||
x *= display_scale;
|
|
||||||
y *= display_scale;
|
|
||||||
cursor_img.style.set {
|
|
||||||
left: x + "px",
|
|
||||||
top: y + "px",
|
|
||||||
};
|
|
||||||
if (cursor_img.style#display == 'none') {
|
|
||||||
handler.style#cursor = 'none';
|
|
||||||
cursor_img.style#display = "block";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function self.ready() {
|
|
||||||
var w = 960;
|
|
||||||
var h = 640;
|
|
||||||
if (is_file_transfer || is_port_forward) {
|
|
||||||
var r = handler.get_size();
|
|
||||||
if (isReasonableSize(r) && r[2] > 0) {
|
|
||||||
view.move(r[0], r[1], r[2], r[3]);
|
|
||||||
} else {
|
|
||||||
centerize(w, h);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
centerize(w, h);
|
|
||||||
}
|
|
||||||
if (!is_port_forward) connecting();
|
|
||||||
if (is_file_transfer) initializeFileTransfer();
|
|
||||||
if (is_port_forward) initializePortForward();
|
|
||||||
}
|
|
||||||
|
|
||||||
var workarea_offset = 0;
|
|
||||||
var size_adapted;
|
|
||||||
handler.adaptSize = function() {
|
|
||||||
if (size_adapted) return;
|
|
||||||
size_adapted = true;
|
|
||||||
var (sx, sy, sw, sh) = view.screenBox(#workarea, #rectw);
|
|
||||||
var (fx, fy, fw, fh) = view.screenBox(#frame, #rectw);
|
|
||||||
if (is_osx) workarea_offset = sy;
|
|
||||||
var r = handler.get_size();
|
|
||||||
if (isReasonableSize(r) && r[2] > 0) {
|
|
||||||
if (r[2] >= fw && r[3] >= fh && !is_linux) {
|
|
||||||
view.windowState = View.WINDOW_FULL_SCREEN;
|
|
||||||
stdout.println("Initialize to full screen");
|
|
||||||
} else if (r[2] >= sw && r[3] >= sh) {
|
|
||||||
view.windowState = View.WINDOW_MAXIMIZED;
|
|
||||||
stdout.println("Initialize to full screen");
|
|
||||||
} else {
|
|
||||||
view.move(r[0], r[1], r[2], r[3]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var w = handler.box(#width, #border)
|
|
||||||
var h = handler.box(#height, #border)
|
|
||||||
if (w >= sw || h >= sh) {
|
|
||||||
view.windowState = View.WINDOW_MAXIMIZED;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// extra for border
|
|
||||||
var extra = 2;
|
|
||||||
centerize(w + extra, handler.box(#height, #border) + h + extra);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function self.closing() {
|
|
||||||
var (x, y, w, h) = view.box(#rectw, #border, #screen);
|
|
||||||
if (is_file_transfer) save_file_transfer_close_state();
|
|
||||||
if (is_file_transfer || is_port_forward || size_adapted) handler.save_size(x, y, w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.setPermission = function(name, enabled) {
|
|
||||||
if (name == "keyboard") keyboard_enabled = enabled;
|
|
||||||
if (name == "audio") audio_enabled = enabled;
|
|
||||||
if (name == "clipboard") clipboard_enabled = enabled;
|
|
||||||
header.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.closeSuccess = function() {
|
|
||||||
// handler.msgbox("success", "Successful", "Ready to go.");
|
|
||||||
handler.msgbox("", "", "");
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user