Skip to content

Commit

Permalink
[PORT] tgui say v1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
Absolucy committed May 31, 2024
1 parent c850cfe commit a0f333c
Show file tree
Hide file tree
Showing 44 changed files with 924 additions and 1,032 deletions.
13 changes: 6 additions & 7 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 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));
47 changes: 47 additions & 0 deletions tgui/packages/tgui-say/ChannelIterator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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('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 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

0 comments on commit a0f333c

Please sign in to comment.