format fixes and unittest additions, translated document title
We also actually use our format utilities to set the title.
This commit is contained in:
parent
0cef7194ce
commit
999bdd0809
|
|
@ -1,5 +1,9 @@
|
|||
import { StringLike } from './StringLike';
|
||||
|
||||
function isalpha(char: number) {
|
||||
return RegExp(/^\p{L}/, 'u').test(String.fromCharCode(char));
|
||||
}
|
||||
|
||||
/// A simple function for formatting strings in a more expressive manner.
|
||||
/// While JavaScript *does* have string interpolation, it's not a total replacement
|
||||
/// for just formatting strings, and a method like this is better for data independent formatting.
|
||||
|
|
@ -25,8 +29,8 @@ export function Format(pattern: string, ...args: Array<StringLike>) {
|
|||
let foundSpecifierEnd = false;
|
||||
|
||||
// Make sure the specifier is not cut off (the last character of the string)
|
||||
if (i + 3 >= pat.length) {
|
||||
throw new Error(`Error in format pattern "${pat}": Cutoff/invalid format specifier`);
|
||||
if (i + 3 > pat.length) {
|
||||
throw new Error(`Error in format pattern "${pat}": Cutoff/invalid format specifier`);
|
||||
}
|
||||
|
||||
// Try and find the specifier end ('}').
|
||||
|
|
@ -40,13 +44,14 @@ export function Format(pattern: string, ...args: Array<StringLike>) {
|
|||
|
||||
case '{':
|
||||
throw new Error(`Error in format pattern "${pat}": Cannot start a format specifier in an existing replacement`);
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
throw new Error(`Error in format pattern "${pat}": Whitespace inside format specifier`);
|
||||
break;
|
||||
|
||||
case '-':
|
||||
throw new Error(`Error in format pattern "${pat}": Malformed format specifier`);
|
||||
|
||||
default:
|
||||
if (isalpha(pat.charCodeAt(j))) throw new Error(`Error in format pattern "${pat}": Malformed format specifier`);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import * as bootstrap from 'bootstrap';
|
|||
import MuteState from './protocol/MuteState.js';
|
||||
import { Unsubscribe } from 'nanoevents';
|
||||
import { I18nStringKey, TheI18n } from './i18n.js';
|
||||
import { Format } from './format.js';
|
||||
|
||||
// Elements
|
||||
const w = window as any;
|
||||
|
|
@ -318,46 +319,39 @@ async function openVM(vm: VM): Promise<void> {
|
|||
location.hash = vm.id;
|
||||
// Create the client
|
||||
VM = new CollabVMClient(vm.url);
|
||||
|
||||
// Register event listeners
|
||||
|
||||
// An array of nanoevent unsubscribe callbacks. These are called when the VM is closed to cleanup nanoevent state.
|
||||
let unsubscribeCallbacks: Unsubscribe[] = [];
|
||||
VM!.on('chat', (username, message) => chatMessage(username, message));
|
||||
VM!.on('adduser', (user) => addUser(user));
|
||||
VM!.on('remuser', (user) => remUser(user));
|
||||
VM!.on('rename', (oldname, newname, selfrename) => userRenamed(oldname, newname, selfrename));
|
||||
|
||||
unsubscribeCallbacks.push(VM!.on('chat', (username, message) => chatMessage(username, message)));
|
||||
unsubscribeCallbacks.push(VM!.on('adduser', (user) => addUser(user)));
|
||||
unsubscribeCallbacks.push(VM!.on('remuser', (user) => remUser(user)));
|
||||
unsubscribeCallbacks.push(VM!.on('rename', (oldname, newname, selfrename) => userRenamed(oldname, newname, selfrename)));
|
||||
unsubscribeCallbacks.push(
|
||||
VM!.on('renamestatus', (status) => {
|
||||
// TODO: i18n these
|
||||
switch (status) {
|
||||
case 'taken':
|
||||
alert(TheI18n.GetString(I18nStringKey.kError_UsernameTaken));
|
||||
break;
|
||||
case 'invalid':
|
||||
alert(TheI18n.GetString(I18nStringKey.kError_UsernameInvalid));
|
||||
break;
|
||||
case 'blacklisted':
|
||||
alert(TheI18n.GetString(I18nStringKey.kError_UsernameBlacklisted));
|
||||
break;
|
||||
}
|
||||
})
|
||||
);
|
||||
unsubscribeCallbacks.push(VM!.on('turn', (status) => turnUpdate(status)));
|
||||
unsubscribeCallbacks.push(VM!.on('vote', (status: VoteStatus) => voteUpdate(status)));
|
||||
unsubscribeCallbacks.push(VM!.on('voteend', () => voteEnd()));
|
||||
unsubscribeCallbacks.push(VM!.on('votecd', (voteCooldown) => window.alert(TheI18n.GetString(I18nStringKey.kVM_VoteCooldownTimer, voteCooldown))));
|
||||
unsubscribeCallbacks.push(VM!.on('login', (rank: Rank, perms: Permissions) => onLogin(rank, perms)));
|
||||
unsubscribeCallbacks.push(
|
||||
VM!.on('close', () => {
|
||||
if (!expectedClose) alert(TheI18n.GetString(I18nStringKey.kError_UnexpectedDisconnection));
|
||||
VM!.on('renamestatus', (status) => {
|
||||
// TODO: i18n these
|
||||
switch (status) {
|
||||
case 'taken':
|
||||
alert(TheI18n.GetString(I18nStringKey.kError_UsernameTaken));
|
||||
break;
|
||||
case 'invalid':
|
||||
alert(TheI18n.GetString(I18nStringKey.kError_UsernameInvalid));
|
||||
break;
|
||||
case 'blacklisted':
|
||||
alert(TheI18n.GetString(I18nStringKey.kError_UsernameBlacklisted));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Call all the unsubscribe callbacks.
|
||||
for (let l of unsubscribeCallbacks) l();
|
||||
unsubscribeCallbacks = [];
|
||||
closeVM();
|
||||
})
|
||||
);
|
||||
VM!.on('turn', (status) => turnUpdate(status));
|
||||
VM!.on('vote', (status: VoteStatus) => voteUpdate(status));
|
||||
VM!.on('voteend', () => voteEnd());
|
||||
VM!.on('votecd', (voteCooldown) => window.alert(TheI18n.GetString(I18nStringKey.kVM_VoteCooldownTimer, voteCooldown)));
|
||||
VM!.on('login', (rank: Rank, perms: Permissions) => onLogin(rank, perms));
|
||||
|
||||
VM!.on('close', () => {
|
||||
if (!expectedClose) alert(TheI18n.GetString(I18nStringKey.kError_UnexpectedDisconnection));
|
||||
closeVM();
|
||||
});
|
||||
|
||||
// Wait for the client to open
|
||||
await VM!.WaitForOpen();
|
||||
|
|
@ -374,7 +368,7 @@ async function openVM(vm: VM): Promise<void> {
|
|||
throw new Error('Failed to connect to node');
|
||||
}
|
||||
// Set the title
|
||||
document.title = vm.id + ' - CollabVM';
|
||||
document.title = Format("{0} - {1}", vm.id, TheI18n.GetString(I18nStringKey.kGeneric_CollabVM));
|
||||
// Append canvas
|
||||
elements.vmDisplay.appendChild(VM!.canvas);
|
||||
// Switch to the VM view
|
||||
|
|
@ -389,7 +383,7 @@ function closeVM() {
|
|||
// Close the VM
|
||||
VM.close();
|
||||
VM = null;
|
||||
document.title = 'CollabVM';
|
||||
document.title = TheI18n.GetString(I18nStringKey.kGeneric_CollabVM);
|
||||
turn = -1;
|
||||
// Remove the canvas
|
||||
elements.vmDisplay.innerHTML = '';
|
||||
|
|
@ -844,6 +838,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
// Initalize the i18n system
|
||||
await TheI18n.Init();
|
||||
|
||||
document.title = TheI18n.GetString(I18nStringKey.kGeneric_CollabVM);
|
||||
|
||||
// Load all VMs
|
||||
await loadList();
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ export default class CollabVMClient {
|
|||
// public events
|
||||
private publicEmitter: Emitter<CollabVMClientEvents>;
|
||||
|
||||
private unsubscribeCallbacks: Array<Unsubscribe> = [];
|
||||
|
||||
constructor(url: string) {
|
||||
// Save the URL
|
||||
this.url = url;
|
||||
|
|
@ -434,6 +436,13 @@ export default class CollabVMClient {
|
|||
// Close the connection
|
||||
close() {
|
||||
this.connectedToVM = false;
|
||||
|
||||
// call all unsubscribe callbacks explicitly
|
||||
for(let cb of this.unsubscribeCallbacks) {
|
||||
cb();
|
||||
}
|
||||
this.unsubscribeCallbacks = [];
|
||||
|
||||
if (this.socket.readyState === WebSocket.OPEN) this.socket.close();
|
||||
}
|
||||
|
||||
|
|
@ -588,6 +597,8 @@ export default class CollabVMClient {
|
|||
}
|
||||
|
||||
on<E extends keyof CollabVMClientEvents>(event: E, callback: CollabVMClientEvents[E]): Unsubscribe {
|
||||
return this.publicEmitter.on(event, callback);
|
||||
let unsub = this.publicEmitter.on(event, callback);
|
||||
this.unsubscribeCallbacks.push(unsub);
|
||||
return unsub;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ test('a cut off format specifier throws', () => {
|
|||
});
|
||||
|
||||
test('a malformed format specifier throws', () => {
|
||||
expect(() => Format('a{-0}', 1)).toThrow('Malformed format specifier');
|
||||
expect(() => Format('a{0-}', 1)).toThrow('Malformed format specifier');
|
||||
expect(() => Format('a{0ab}', 1)).toThrow('Malformed format specifier');
|
||||
expect(() => Format('a{ab0ab}', 1)).toThrow('Malformed format specifier');
|
||||
|
||||
// Whitespace is not permitted inside a format specifier
|
||||
expect(() => Format('a{0 }', 1)).toThrow('Whitespace inside format specifier');
|
||||
expect(() => Format('a{ 0}', 1)).toThrow('Whitespace inside format specifier');
|
||||
expect(() => Format('a{ 0 }', 1)).toThrow('Whitespace inside format specifier');
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user