Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PORT] tgui say v1.1 #2116

Merged
merged 4 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions code/modules/tgui_input/say_modal/modal.dm
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
/datum/tgui_say/New(client/client, id)
src.client = client
window = new(client, id)
winset(client, "tgui_say", "size=1,1;is-visible=0;")
window.subscribe(src, PROC_REF(on_message))
window.is_browser = TRUE

Expand All @@ -62,7 +63,9 @@
*/
/datum/tgui_say/proc/load()
window_open = FALSE
winshow(client, "tgui_say", FALSE)

winset(client, "tgui_say", "pos=848,500;size=231,30;is-visible=0;")

window.send_message("props", list(
lightMode = client.prefs?.read_preference(/datum/preference/toggle/tgui_say_light_mode),
maxLength = max_length,
Expand All @@ -84,9 +87,7 @@
window_open = TRUE
if(payload["channel"] != OOC_CHANNEL && payload["channel"] != LOOC_CHANNEL && (payload["channel"] != ADMIN_CHANNEL) && (payload["channel"] != MENTOR_CHANNEL)) // monke: add LOOC
start_thinking()
if(client.typing_indicators)
log_speech_indicators("[key_name(client)] started typing at [loc_name(client.mob)], indicators enabled.")
else
if(!client.typing_indicators)
log_speech_indicators("[key_name(client)] started typing at [loc_name(client.mob)], indicators DISABLED.")
return TRUE

Expand All @@ -97,9 +98,7 @@
/datum/tgui_say/proc/close()
window_open = FALSE
stop_thinking()
if(client.typing_indicators)
log_speech_indicators("[key_name(client)] stopped typing at [loc_name(client.mob)], indicators enabled.")
else
if(!client.typing_indicators)
log_speech_indicators("[key_name(client)] stopped typing at [loc_name(client.mob)], indicators DISABLED.")

/**
Expand All @@ -117,10 +116,10 @@
close()
return TRUE
if (type == "thinking")
if(payload["mode"] == TRUE)
if(payload["visible"] == TRUE)
start_thinking()
return TRUE
if(payload["mode"] == FALSE)
if(payload["visible"] == FALSE)
stop_thinking()
return TRUE
return FALSE
Expand Down
39 changes: 39 additions & 0 deletions tgui/packages/common/keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* ### Key codes.
* event.keyCode is deprecated, use this reference instead.
*
* Handles modifier keys (Shift, Alt, Control) and arrow keys.
*
* For alphabetical keys, use the actual character (e.g. 'a') instead of the key code.
*
* Something isn't here that you want? Just add it:
* @url https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
* @usage
* ```ts
* import { KEY } from 'tgui/common/keys';
*
* if (event.key === KEY.Enter) {
* // do something
* }
* ```
*/
export enum KEY {
Alt = 'Alt',
Backspace = 'Backspace',
Control = 'Control',
Delete = 'Delete',
Down = 'Down',
End = 'End',
Enter = 'Enter',
Escape = 'Esc',
Home = 'Home',
Insert = 'Insert',
Left = 'Left',
PageDown = 'PageDown',
PageUp = 'PageUp',
Right = 'Right',
Shift = 'Shift',
Space = ' ',
Tab = 'Tab',
Up = 'Up',
}
32 changes: 21 additions & 11 deletions tgui/packages/common/timer.js → tgui/packages/common/timer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@
* called for N milliseconds. If `immediate` is passed, trigger the
* function on the leading edge, instead of the trailing.
*/
export const debounce = (fn, time, immediate = false) => {
let timeout;
return (...args) => {
export const debounce = <F extends (...args: any[]) => any>(
fn: F,
time: number,
immediate = false
): ((...args: Parameters<F>) => void) => {
let timeout: ReturnType<typeof setTimeout> | null;
return (...args: Parameters<F>) => {
const later = () => {
timeout = null;
if (!immediate) {
fn(...args);
}
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
clearTimeout(timeout!);
timeout = setTimeout(later, time);
if (callNow) {
fn(...args);
Expand All @@ -32,18 +36,24 @@ export const debounce = (fn, time, immediate = false) => {
* Returns a function, that, when invoked, will only be triggered at most once
* during a given window of time.
*/
export const throttle = (fn, time) => {
let previouslyRun, queuedToRun;
return function invokeFn(...args) {
export const throttle = <F extends (...args: any[]) => any>(
fn: F,
time: number
): ((...args: Parameters<F>) => void) => {
let previouslyRun: number | null,
queuedToRun: ReturnType<typeof setTimeout> | null;
return function invokeFn(...args: Parameters<F>) {
const now = Date.now();
queuedToRun = clearTimeout(queuedToRun);
if (queuedToRun) {
clearTimeout(queuedToRun);
}
if (!previouslyRun || now - previouslyRun >= time) {
fn.apply(null, args);
previouslyRun = now;
} else {
queuedToRun = setTimeout(
invokeFn.bind(null, ...args),
time - (now - previouslyRun)
() => invokeFn(...args),
time - (now - (previouslyRun ?? 0))
);
}
};
Expand All @@ -54,5 +64,5 @@ export const throttle = (fn, time) => {
*
* @param {number} time
*/
export const sleep = (time) =>
export const sleep = (time: number): Promise<void> =>
new Promise((resolve) => setTimeout(resolve, time));
58 changes: 58 additions & 0 deletions tgui/packages/tgui-say/ChannelIterator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { ChannelIterator } from './ChannelIterator';

describe('ChannelIterator', () => {
let channelIterator: ChannelIterator;

beforeEach(() => {
channelIterator = new ChannelIterator();
});

it('should cycle through channels properly', () => {
expect(channelIterator.current()).toBe('Say');
expect(channelIterator.next()).toBe('Radio');
expect(channelIterator.next()).toBe('Me');
expect(channelIterator.next()).toBe('OOC');
expect(channelIterator.next()).toBe('LOOC');
expect(channelIterator.next()).toBe('Say'); // Admin is blacklisted so it should be skipped
});

it('should set a channel properly', () => {
channelIterator.set('OOC');
expect(channelIterator.current()).toBe('OOC');
});

it('should return true when current channel is "Say"', () => {
channelIterator.set('Say');
expect(channelIterator.isSay()).toBe(true);
});

it('should return false when current channel is not "Say"', () => {
channelIterator.set('Radio');
expect(channelIterator.isSay()).toBe(false);
});

it('should return true when current channel is visible', () => {
channelIterator.set('Say');
expect(channelIterator.isVisible()).toBe(true);
});

it('should return false when current channel is not visible', () => {
channelIterator.set('OOC');
expect(channelIterator.isVisible()).toBe(false);
});

it('should return false when current channel is not visible', () => {
channelIterator.set('LOOC');
expect(channelIterator.isVisible()).toBe(false);
});

it('should not leak a message from a blacklisted channel', () => {
channelIterator.set('Mentor');
expect(channelIterator.next()).toBe('Mentor');
});

it('should not leak a message from a blacklisted channel', () => {
channelIterator.set('Admin');
expect(channelIterator.next()).toBe('Admin');
});
});
65 changes: 65 additions & 0 deletions tgui/packages/tgui-say/ChannelIterator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
export type Channel =
| 'Say'
| 'Radio'
| 'Me'
| 'OOC'
| 'LOOC'
| 'Mentor'
| 'Admin';

/**
* ### ChannelIterator
* Cycles a predefined list of channels,
* skipping over blacklisted ones,
* and providing methods to manage and query the current channel.
*/
export class ChannelIterator {
private index: number = 0;
private readonly channels: Channel[] = [
'Say',
'Radio',
'Me',
'OOC',
'LOOC',
'Mentor',
'Admin',
];
private readonly blacklist: Channel[] = ['Mentor', 'Admin'];
private readonly quiet: Channel[] = ['OOC', 'LOOC', 'Mentor', 'Admin'];

public next(): Channel {
if (this.blacklist.includes(this.channels[this.index])) {
return this.channels[this.index];
}

for (let index = 1; index <= this.channels.length; index++) {
let nextIndex = (this.index + index) % this.channels.length;
if (!this.blacklist.includes(this.channels[nextIndex])) {
this.index = nextIndex;
break;
}
}

return this.channels[this.index];
}

public set(channel: Channel): void {
this.index = this.channels.indexOf(channel) || 0;
}

public current(): Channel {
return this.channels[this.index];
}

public isSay(): boolean {
return this.channels[this.index] === 'Say';
}

public isVisible(): boolean {
return !this.quiet.includes(this.channels[this.index]);
}

public reset(): void {
this.index = 0;
}
}
50 changes: 50 additions & 0 deletions tgui/packages/tgui-say/ChatHistory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { ChatHistory } from './ChatHistory';

describe('ChatHistory', () => {
let chatHistory: ChatHistory;

beforeEach(() => {
chatHistory = new ChatHistory();
});

it('should add a message to the history', () => {
chatHistory.add('Hello');
expect(chatHistory.getOlderMessage()).toEqual('Hello');
});

it('should retrieve older and newer messages', () => {
chatHistory.add('Hello');
chatHistory.add('World');
expect(chatHistory.getOlderMessage()).toEqual('World');
expect(chatHistory.getOlderMessage()).toEqual('Hello');
expect(chatHistory.getNewerMessage()).toEqual('World');
expect(chatHistory.getNewerMessage()).toBeNull();
expect(chatHistory.getOlderMessage()).toEqual('World');
});

it('should limit the history to 5 messages', () => {
for (let i = 1; i <= 6; i++) {
chatHistory.add(`Message ${i}`);
}

expect(chatHistory.getOlderMessage()).toEqual('Message 6');
for (let i = 5; i >= 2; i--) {
expect(chatHistory.getOlderMessage()).toEqual(`Message ${i}`);
}
expect(chatHistory.getOlderMessage()).toBeNull();
});

it('should handle temp message correctly', () => {
chatHistory.saveTemp('Temp message');
expect(chatHistory.getTemp()).toEqual('Temp message');
expect(chatHistory.getTemp()).toBeNull();
});

it('should reset correctly', () => {
chatHistory.add('Hello');
chatHistory.getOlderMessage();
chatHistory.reset();
expect(chatHistory.isAtLatest()).toBe(true);
expect(chatHistory.getOlderMessage()).toEqual('Hello');
});
});
59 changes: 59 additions & 0 deletions tgui/packages/tgui-say/ChatHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* ### ChatHistory
* A class to manage a chat history,
* maintaining a maximum of five messages and supporting navigation,
* temporary message storage, and query operations.
*/
export class ChatHistory {
private messages: string[] = [];
private index: number = -1; // Initialize index at -1
private temp: string | null = null;

public add(message: string): void {
this.messages.unshift(message);
this.index = -1; // Reset index
if (this.messages.length > 5) {
this.messages.pop();
}
}

public getIndex(): number {
return this.index + 1;
}

public getOlderMessage(): string | null {
if (this.messages.length === 0 || this.index >= this.messages.length - 1) {
return null;
}
this.index++;
return this.messages[this.index];
}

public getNewerMessage(): string | null {
if (this.index <= 0) {
this.index = -1;
return null;
}
this.index--;
return this.messages[this.index];
}

public isAtLatest(): boolean {
return this.index === -1;
}

public saveTemp(message: string): void {
this.temp = message;
}

public getTemp(): string | null {
const temp = this.temp;
this.temp = null;
return temp;
}

public reset(): void {
this.index = -1;
this.temp = null;
}
}
Loading
Loading