diff --git a/shared/js/connectionlog/History.ts b/shared/js/connectionlog/History.ts index 43138528..7925b75f 100644 --- a/shared/js/connectionlog/History.ts +++ b/shared/js/connectionlog/History.ts @@ -313,6 +313,41 @@ export class ConnectionHistory { }); } + async deleteConnectionAttempts(target: string, targetType: "address" | "server-unique-id") { + if(!this.database) { + return; + } + + const transaction = this.database.transaction(["attempt-history"], "readwrite"); + const store = transaction.objectStore("attempt-history"); + + const cursor = store.index(targetType === "server-unique-id" ? "serverUniqueId" : "targetAddress").openCursor(target); + const promises = []; + while(true) { + const entry = await new Promise((resolve, reject) => { + cursor.onsuccess = () => resolve(cursor.result); + cursor.onerror = () => reject(cursor.error); + }); + + if (!entry) { + break; + } + + promises.push(new Promise(resolve => { + const deleteRequest = entry.delete(); + deleteRequest.onsuccess = resolve; + deleteRequest.onerror = () => { + logWarn(LogCategory.GENERAL, tr("Failed to delete a connection attempt: %o"), deleteRequest.error); + resolve(); + } + })); + + entry.continue(); + } + + await Promise.all(promises); + } + /** * Query the server info of a given server unique id * @param serverUniqueId diff --git a/shared/js/ui/modal/connect/Controller.ts b/shared/js/ui/modal/connect/Controller.ts index a3ceb6c5..4b05c9fe 100644 --- a/shared/js/ui/modal/connect/Controller.ts +++ b/shared/js/ui/modal/connect/Controller.ts @@ -181,6 +181,16 @@ class ConnectController { settings.changeGlobal(Settings.KEY_CONNECT_SHOW_HISTORY, event.enabled); }); + + this.uiEvents.on("action_delete_history", event => { + connectionHistory.deleteConnectionAttempts(event.target, event.targetType).then(() => { + this.history = undefined; + this.sendProperty("history").then(undefined); + }).catch(error => { + logWarn(LogCategory.GENERAL, tr("Failed to delete connection attempts: %o"), error); + }) + }); + this.uiEvents.on("action_manage_profiles", () => { /* TODO: This is more a hack. Proper solution is that the connection profiles fire events if they've been changed... */ const modal = spawnSettingsModal("identity-profiles"); @@ -199,13 +209,16 @@ class ConnectController { this.setSelectedProfile(profile); }); - this.uiEvents.on("action_set_address", event => this.setSelectedAddress(event.address, event.validate)); + this.uiEvents.on("action_set_address", event => this.setSelectedAddress(event.address, event.validate, event.updateUi)); this.uiEvents.on("action_set_nickname", event => { if(this.currentNickname !== event.nickname) { this.currentNickname = event.nickname; - this.sendProperty("nickname").then(undefined); settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, event.nickname); + + if(event.updateUi) { + this.sendProperty("nickname").then(undefined); + } } this.validateStates["nickname"] = event.validate; @@ -219,7 +232,9 @@ class ConnectController { this.currentPassword = event.password; this.currentPasswordHashed = event.hashed; - this.sendProperty("password").then(undefined); + if(event.updateUi) { + this.sendProperty("password").then(undefined); + } this.validateStates["password"] = true; this.updateValidityStates(); @@ -279,12 +294,15 @@ class ConnectController { this.sendProperty("nickname").then(undefined); } - setSelectedAddress(address: string | undefined, validate: boolean) { + setSelectedAddress(address: string | undefined, validate: boolean, updateUi: boolean) { if(this.currentAddress !== address) { this.currentAddress = address; - this.sendProperty("address").then(undefined); settings.changeGlobal(Settings.KEY_CONNECT_ADDRESS, address); this.setSelectedHistoryId(-1); + + if(updateUi) { + this.sendProperty("address").then(undefined); + } } this.validateStates["address"] = validate; @@ -301,7 +319,7 @@ class ConnectController { settings.changeGlobal(Settings.KEY_CONNECT_PROFILE, profile.id); /* Clear out the nickname on profile switch and use the default nickname */ - this.uiEvents.fire("action_set_nickname", { nickname: undefined, validate: true }); + this.uiEvents.fire("action_set_nickname", { nickname: undefined, validate: true, updateUi: true }); this.validateStates["profile"] = true; this.updateValidityStates(); @@ -367,7 +385,7 @@ export function spawnConnectModalNew(options: ConnectModalOptions) { const controller = new ConnectController(); if(typeof options.selectedAddress === "string") { - controller.setSelectedAddress(options.selectedAddress, false); + controller.setSelectedAddress(options.selectedAddress, false, true); } if(typeof options.selectedProfile === "object") { diff --git a/shared/js/ui/modal/connect/Definitions.ts b/shared/js/ui/modal/connect/Definitions.ts index 28580cb6..982e263f 100644 --- a/shared/js/ui/modal/connect/Definitions.ts +++ b/shared/js/ui/modal/connect/Definitions.ts @@ -69,9 +69,9 @@ export interface ConnectUiEvents { target: string, targetType: "address" | "server-unique-id" }, - action_set_nickname: { nickname: string, validate: boolean }, - action_set_address: { address: string, validate: boolean }, - action_set_password: { password: string, hashed: boolean }, + action_set_nickname: { nickname: string, validate: boolean, updateUi: boolean }, + action_set_address: { address: string, validate: boolean, updateUi: boolean }, + action_set_password: { password: string, hashed: boolean, updateUi: boolean }, query_property: { property: keyof ConnectProperties diff --git a/shared/js/ui/modal/connect/Renderer.tsx b/shared/js/ui/modal/connect/Renderer.tsx index fc46569b..0ffa3aab 100644 --- a/shared/js/ui/modal/connect/Renderer.tsx +++ b/shared/js/ui/modal/connect/Renderer.tsx @@ -24,7 +24,7 @@ const ConnectDefaultNewTabContext = React.createContext(false); const cssStyle = require("./Renderer.scss"); -function useProperty(key: T, defaultValue: V) : ConnectProperties[T] | V { +function useProperty(key: T, defaultValue: V) : [ConnectProperties[T] | V, (value: ConnectProperties[T]) => void] { const events = useContext(EventContext); const [ value, setValue ] = useState(() => { events.fire("query_property", { property: key }); @@ -32,7 +32,7 @@ function useProperty(key: T, defaultValue: }); events.reactUse("notify_property", event => event.property === key && setValue(event.value as any)); - return value; + return [value, setValue]; } function usePropertyValid(key: T, defaultValue: PropertyValidState[T]) : PropertyValidState[T] { @@ -48,7 +48,7 @@ function usePropertyValid(key: T, defaultVal const InputServerAddress = () => { const events = useContext(EventContext); - const address = useProperty("address", undefined); + const [address, setAddress] = useProperty("address", undefined); const valid = usePropertyValid("address", true); const newTab = useContext(ConnectDefaultNewTabContext); @@ -64,16 +64,25 @@ const InputServerAddress = () => { invalid={valid ? undefined : Please enter a valid server address} - onInput={value => events.fire("action_set_address", { address: value, validate: false })} - onBlur={() => events.fire("action_set_address", { address: address?.currentAddress, validate: true })} - onEnter={() => events.fire("action_connect", { newTab })} + onInput={value => { + setAddress({ currentAddress: value, defaultAddress: address.defaultAddress }); + events.fire("action_set_address", { address: value, validate: true, updateUi: false }); + }} + onBlur={() => { + events.fire("action_set_address", { address: address?.currentAddress, validate: true, updateUi: true }); + }} + onEnter={() => { + /* Setting the address just to ensure */ + events.fire("action_set_address", { address: address?.currentAddress, validate: true, updateUi: true }); + events.fire("action_connect", { newTab }); + }} /> ) } const InputServerPassword = () => { const events = useContext(EventContext); - const password = useProperty("password", undefined); + const [password, setPassword] = useProperty("password", undefined); return ( { type={"password"} label={Server password} labelType={password?.hashed ? "static" : "floating"} - onInput={value => events.fire("action_set_password", { password: value, hashed: false })} + onInput={value => { + setPassword({ password: value, hashed: false }); + events.fire("action_set_password", { password: value, hashed: false, updateUi: false }); + }} + onBlur={() => { + if(password) { + events.fire("action_set_password", { password: password.password, hashed: password.hashed, updateUi: true }); + } + }} /> ) } const InputNickname = () => { const events = useContext(EventContext); - const nickname = useProperty("nickname", undefined); + const [nickname, setNickname] = useProperty("nickname", undefined); const valid = usePropertyValid("nickname", true); return ( @@ -101,15 +118,18 @@ const InputNickname = () => { label={Nickname} labelType={"static"} invalid={valid ? undefined : Nickname too short or too long} - onInput={value => events.fire("action_set_nickname", { nickname: value, validate: false })} - onBlur={() => events.fire("action_set_nickname", { nickname: nickname?.currentNickname, validate: true })} + onInput={value => { + setNickname({ currentNickname: value, defaultNickname: nickname.defaultNickname }); + events.fire("action_set_nickname", { nickname: value, validate: true, updateUi: false }); + }} + onBlur={() => events.fire("action_set_nickname", { nickname: nickname?.currentNickname, validate: true, updateUi: true })} /> ); } const InputProfile = () => { const events = useContext(EventContext); - const profiles = useProperty("profiles", undefined); + const [profiles] = useProperty("profiles", undefined); const selectedProfile = profiles?.profiles.find(profile => profile.id === profiles?.selected); let invalidMarker; @@ -343,7 +363,7 @@ const HistoryTableEntry = React.memo((props: { entry: ConnectHistoryEntry, selec }); const HistoryTable = () => { - const history = useProperty("history", undefined); + const [history] = useProperty("history", undefined); let body; if(history) {