Finishing the notification system and some fixups for the native client
parent
324869e3fd
commit
b3743c554a
|
@ -23,4 +23,7 @@ setTimeout(() => appLoader.execute(), 0);
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
||||||
//window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function () {};
|
if(__build.target === "client") {
|
||||||
|
/* do this so we don't get a react dev tools warning within the client */
|
||||||
|
(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function () {};
|
||||||
|
}
|
|
@ -8,6 +8,7 @@
|
||||||
border-width: 0 .2em .2em 0;
|
border-width: 0 .2em .2em 0;
|
||||||
padding: .21em;
|
padding: .21em;
|
||||||
height: .5em;
|
height: .5em;
|
||||||
|
width: .5em;
|
||||||
|
|
||||||
&.right {
|
&.right {
|
||||||
transform: rotate(-45deg);
|
transform: rotate(-45deg);
|
||||||
|
|
|
@ -1828,6 +1828,7 @@
|
||||||
<!-- <div class="entry" container="general-updates">{{tr "Updates" /}}</div> -->
|
<!-- <div class="entry" container="general-updates">{{tr "Updates" /}}</div> -->
|
||||||
<div class="entry" container="general-chat">{{tr "Chat" /}}</div>
|
<div class="entry" container="general-chat">{{tr "Chat" /}}</div>
|
||||||
<div class="entry" container="general-keymap">{{tr "Keymap" /}}</div>
|
<div class="entry" container="general-keymap">{{tr "Keymap" /}}</div>
|
||||||
|
<div class="entry" container="general-notifications">{{tr "Notifications" /}}</div>
|
||||||
|
|
||||||
<div class="entry group">{{tr "Audio" /}}</div>
|
<div class="entry group">{{tr "Audio" /}}</div>
|
||||||
<div class="entry" container="audio-microphone">{{tr "Microphone" /}}</div>
|
<div class="entry" container="audio-microphone">{{tr "Microphone" /}}</div>
|
||||||
|
@ -1889,7 +1890,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="container general-updates">{{tr "GU" /}}</div>
|
<div class="container general-updates">{{tr "GU" /}}</div>
|
||||||
<div class="container general-keymap"></div>
|
<div class="container general-keymap"></div>
|
||||||
<div class="container general-chat">
|
<div class="container general-chat"></div>
|
||||||
|
<div class="container general-notifications">
|
||||||
<label>
|
<label>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<input type="checkbox" class="option-fixed-timestamps">
|
<input type="checkbox" class="option-fixed-timestamps">
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 400 400">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #b3b3b3;
|
||||||
|
fill-rule: evenodd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path id="icon" class="cls-1" d="M200.012,0c110.457,0,200,89.545,200,200s-89.54,200-200,200S0.015,310.456.015,200,89.557,0,200.012,0Zm0.01,38.636A160.629,160.629,0,1,1,39.395,199.268,160.632,160.632,0,0,1,200.022,38.639ZM159.407,153.478s42.867-5.468,19.8,68.014c-24.208,77.113-66.566,140.45,63.467,81.447,0,0-64.556,21.6-42.619-37.407,12.79-34.406,28.292-81.421,27.092-93.4C225.387,154.546,207.6,141.025,159.407,153.478Zm65.564-79a24.685,24.685,0,1,1-24.687,24.684A24.685,24.685,0,0,1,224.971,74.479Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 760 B |
|
@ -1,11 +1,11 @@
|
||||||
//Used by CertAccept popup
|
|
||||||
|
|
||||||
/* setup jsrenderer */
|
/* setup jsrenderer */
|
||||||
(window as any).$ = require("jquery");
|
import "jsrender";
|
||||||
(window as any).jQuery = $;
|
if(__build.target === "web") {
|
||||||
|
(window as any).$ = require("jquery");
|
||||||
|
(window as any).jQuery = $;
|
||||||
|
|
||||||
import * as jsrenderInit from "jsrender";
|
require("jsrender")($);
|
||||||
const jsrender = jsrenderInit($);
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||||
|
|
|
@ -538,7 +538,21 @@ export class Settings extends StaticSettings {
|
||||||
|
|
||||||
static readonly FN_EVENTS_NOTIFICATION_ENABLED: (event: string) => SettingsKey<boolean> = event => {
|
static readonly FN_EVENTS_NOTIFICATION_ENABLED: (event: string) => SettingsKey<boolean> = event => {
|
||||||
return {
|
return {
|
||||||
key: "notification_" + event + "_enabled",
|
key: "event_notification_" + event + "_enabled",
|
||||||
|
valueType: "boolean"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static readonly FN_EVENTS_LOG_ENABLED: (event: string) => SettingsKey<boolean> = event => {
|
||||||
|
return {
|
||||||
|
key: "event_log_" + event + "_enabled",
|
||||||
|
valueType: "boolean"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static readonly FN_EVENTS_FOCUS_ENABLED: (event: string) => SettingsKey<boolean> = event => {
|
||||||
|
return {
|
||||||
|
key: "event_focus_" + event + "_enabled",
|
||||||
valueType: "boolean"
|
valueType: "boolean"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,13 @@ const focusDefaultStatus = {};
|
||||||
focusDefaultStatus[EventType.CLIENT_POKE_RECEIVED] = true;
|
focusDefaultStatus[EventType.CLIENT_POKE_RECEIVED] = true;
|
||||||
|
|
||||||
export function requestWindowFocus() {
|
export function requestWindowFocus() {
|
||||||
window.focus();
|
if(__build.target === "web") {
|
||||||
|
window.focus();
|
||||||
|
} else {
|
||||||
|
/* TODO: Abstract that! */
|
||||||
|
const { remote } = __non_webpack_require__("electron");
|
||||||
|
remote.getCurrentWindow().show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFocusRequestEnabled(type: EventType) {
|
export function isFocusRequestEnabled(type: EventType) {
|
||||||
|
|
|
@ -427,12 +427,20 @@ export class Conversation extends AbstractChat<ConversationUIEvents> {
|
||||||
this.pendingHistoryQueries.push(() => {
|
this.pendingHistoryQueries.push(() => {
|
||||||
this.historyQueryResponse = [];
|
this.historyQueryResponse = [];
|
||||||
|
|
||||||
return this.handle.connection.serverConnection.send_command("conversationhistory", {
|
const requestObject = {
|
||||||
cid: this.conversationId,
|
cid: this.conversationId
|
||||||
timestamp_begin: criteria.begin,
|
} as any;
|
||||||
message_count: criteria.limit,
|
|
||||||
timestamp_end: criteria.end
|
if(typeof criteria.begin === "number")
|
||||||
}, { flagset: [ "merge" ], process_result: false }).then(() => {
|
requestObject.timestamp_begin = criteria.begin;
|
||||||
|
|
||||||
|
if(typeof criteria.end === "number")
|
||||||
|
requestObject.timestamp_end = criteria.end;
|
||||||
|
|
||||||
|
if(typeof criteria.limit === "number")
|
||||||
|
requestObject.message_count = criteria.limit;
|
||||||
|
|
||||||
|
return this.handle.connection.serverConnection.send_command("conversationhistory", requestObject, { flagset: [ "merge" ], process_result: false }).then(() => {
|
||||||
resolve({ status: "success", events: this.historyQueryResponse.map(e => {
|
resolve({ status: "success", events: this.historyQueryResponse.map(e => {
|
||||||
return {
|
return {
|
||||||
type: "message",
|
type: "message",
|
||||||
|
@ -778,6 +786,8 @@ export class ConversationManager extends AbstractChatManager<ConversationUIEvent
|
||||||
|
|
||||||
queryUnreadFlags() {
|
queryUnreadFlags() {
|
||||||
/* FIXME: Test for old TeaSpeak servers which don't support this */
|
/* FIXME: Test for old TeaSpeak servers which don't support this */
|
||||||
|
return;
|
||||||
|
|
||||||
const commandData = this.connection.channelTree.channels.map(e => { return { cid: e.channelId, cpw: e.cached_password() }});
|
const commandData = this.connection.channelTree.channels.map(e => { return { cid: e.channelId, cpw: e.cached_password() }});
|
||||||
this.connection.serverConnection.send_command("conversationfetch", commandData).catch(error => {
|
this.connection.serverConnection.send_command("conversationfetch", commandData).catch(error => {
|
||||||
log.warn(LogCategory.CHAT, tr("Failed to query conversation indexes: %o"), error);
|
log.warn(LogCategory.CHAT, tr("Failed to query conversation indexes: %o"), error);
|
||||||
|
|
|
@ -2,7 +2,8 @@ import * as React from "react";
|
||||||
const cssStyle = require("./Switch.scss");
|
const cssStyle = require("./Switch.scss");
|
||||||
|
|
||||||
export interface SwitchProperties {
|
export interface SwitchProperties {
|
||||||
initialState: boolean;
|
value?: boolean;
|
||||||
|
initialState?: boolean;
|
||||||
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
||||||
|
@ -36,10 +37,14 @@ export class Switch extends React.Component<SwitchProperties, SwitchState> {
|
||||||
return (
|
return (
|
||||||
<label ref={this.ref} className={cssStyle.container + " " + (this.props.className || "") + " " + (disabled ? cssStyle.disabled : "")} onBlur={this.props.onBlur}>
|
<label ref={this.ref} className={cssStyle.container + " " + (this.props.className || "") + " " + (disabled ? cssStyle.disabled : "")} onBlur={this.props.onBlur}>
|
||||||
<div className={cssStyle.switch}>
|
<div className={cssStyle.switch}>
|
||||||
<input type="checkbox" onChange={e => {
|
<input
|
||||||
this.setState({ checked: e.currentTarget.checked });
|
type="checkbox"
|
||||||
this.props.onChange && this.props.onChange(e.currentTarget.checked);
|
onChange={e => {
|
||||||
}} disabled={disabled} checked={this.state.checked} />
|
this.setState({ checked: e.currentTarget.checked });
|
||||||
|
this.props.onChange && this.props.onChange(e.currentTarget.checked);
|
||||||
|
}} disabled={disabled}
|
||||||
|
checked={typeof this.props.value === "boolean" ? this.props.value : this.state.checked}
|
||||||
|
/>
|
||||||
<span className={cssStyle.slider}>
|
<span className={cssStyle.slider}>
|
||||||
<span className={cssStyle.dot} />
|
<span className={cssStyle.dot} />
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import {ReactElement} from "react";
|
import {ReactElement} from "react";
|
||||||
|
import {guid} from "tc-shared/crypto/uid";
|
||||||
|
|
||||||
const cssStyle = require("./Tooltip.scss");
|
const cssStyle = require("./Tooltip.scss");
|
||||||
|
|
||||||
interface GlobalTooltipState {
|
interface GlobalTooltipState {
|
||||||
pageX: number;
|
pageX: number;
|
||||||
pageY: number;
|
pageY: number;
|
||||||
|
tooltipId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GlobalTooltip extends React.Component<{}, GlobalTooltipState> {
|
class GlobalTooltip extends React.Component<{}, GlobalTooltipState> {
|
||||||
|
@ -18,7 +20,8 @@ class GlobalTooltip extends React.Component<{}, GlobalTooltipState> {
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
pageX: 0,
|
pageX: 0,
|
||||||
pageY: 0
|
pageY: 0,
|
||||||
|
tooltipId: "unset"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +50,7 @@ class GlobalTooltip extends React.Component<{}, GlobalTooltipState> {
|
||||||
if(!this.currentTooltip_ || this.isUnmount) {
|
if(!this.currentTooltip_ || this.isUnmount) {
|
||||||
return (
|
return (
|
||||||
<div className={cssStyle.container} style={{ top: this.state.pageY, left: this.state.pageX }}>
|
<div className={cssStyle.container} style={{ top: this.state.pageY, left: this.state.pageX }}>
|
||||||
{this.currentTooltip_?.props.tooltip()}
|
<React.Fragment key={this.state.tooltipId}>{this.currentTooltip_?.props.tooltip()}</React.Fragment>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -73,6 +76,7 @@ export interface TooltipProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tooltip extends React.Component<TooltipProperties, TooltipState> {
|
export class Tooltip extends React.Component<TooltipProperties, TooltipState> {
|
||||||
|
readonly tooltipId = guid();
|
||||||
private refContainer = React.createRef<HTMLSpanElement>();
|
private refContainer = React.createRef<HTMLSpanElement>();
|
||||||
private currentContainer: HTMLElement;
|
private currentContainer: HTMLElement;
|
||||||
|
|
||||||
|
@ -105,6 +109,7 @@ export class Tooltip extends React.Component<TooltipProperties, TooltipState> {
|
||||||
globalTooltipRef.current?.setState({
|
globalTooltipRef.current?.setState({
|
||||||
pageY: this.state.pageY,
|
pageY: this.state.pageY,
|
||||||
pageX: this.state.pageX,
|
pageX: this.state.pageX,
|
||||||
|
tooltipId: this.tooltipId
|
||||||
});
|
});
|
||||||
} else if(prevState.forceShow || prevState.hovered) {
|
} else if(prevState.forceShow || prevState.hovered) {
|
||||||
globalTooltipRef.current?.unmountTooltip(this);
|
globalTooltipRef.current?.unmountTooltip(this);
|
||||||
|
|
|
@ -18,5 +18,8 @@ export = () => config_base.config("client").then(config => {
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
config.externals.push({ "jquery": "window.$" });
|
||||||
|
config.externals.push({ "jsrender": "window.$" });
|
||||||
|
|
||||||
return Promise.resolve(config);
|
return Promise.resolve(config);
|
||||||
});
|
});
|
Loading…
Reference in New Issue