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';
|
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.
|
/// A simple function for formatting strings in a more expressive manner.
|
||||||
/// While JavaScript *does* have string interpolation, it's not a total replacement
|
/// 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.
|
/// 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;
|
let foundSpecifierEnd = false;
|
||||||
|
|
||||||
// Make sure the specifier is not cut off (the last character of the string)
|
// Make sure the specifier is not cut off (the last character of the string)
|
||||||
if (i + 3 >= pat.length) {
|
if (i + 3 > pat.length) {
|
||||||
throw new Error(`Error in format pattern "${pat}": Cutoff/invalid format specifier`);
|
throw new Error(`Error in format pattern "${pat}": Cutoff/invalid format specifier`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try and find the specifier end ('}').
|
// Try and find the specifier end ('}').
|
||||||
|
|
@ -40,13 +44,14 @@ export function Format(pattern: string, ...args: Array<StringLike>) {
|
||||||
|
|
||||||
case '{':
|
case '{':
|
||||||
throw new Error(`Error in format pattern "${pat}": Cannot start a format specifier in an existing replacement`);
|
throw new Error(`Error in format pattern "${pat}": Cannot start a format specifier in an existing replacement`);
|
||||||
break;
|
|
||||||
|
|
||||||
case ' ':
|
case ' ':
|
||||||
throw new Error(`Error in format pattern "${pat}": Whitespace inside format specifier`);
|
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:
|
default:
|
||||||
|
if (isalpha(pat.charCodeAt(j))) throw new Error(`Error in format pattern "${pat}": Malformed format specifier`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import * as bootstrap from 'bootstrap';
|
||||||
import MuteState from './protocol/MuteState.js';
|
import MuteState from './protocol/MuteState.js';
|
||||||
import { Unsubscribe } from 'nanoevents';
|
import { Unsubscribe } from 'nanoevents';
|
||||||
import { I18nStringKey, TheI18n } from './i18n.js';
|
import { I18nStringKey, TheI18n } from './i18n.js';
|
||||||
|
import { Format } from './format.js';
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
const w = window as any;
|
const w = window as any;
|
||||||
|
|
@ -318,46 +319,39 @@ async function openVM(vm: VM): Promise<void> {
|
||||||
location.hash = vm.id;
|
location.hash = vm.id;
|
||||||
// Create the client
|
// Create the client
|
||||||
VM = new CollabVMClient(vm.url);
|
VM = new CollabVMClient(vm.url);
|
||||||
|
|
||||||
// Register event listeners
|
// Register event listeners
|
||||||
|
|
||||||
// An array of nanoevent unsubscribe callbacks. These are called when the VM is closed to cleanup nanoevent state.
|
VM!.on('chat', (username, message) => chatMessage(username, message));
|
||||||
let unsubscribeCallbacks: Unsubscribe[] = [];
|
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)));
|
VM!.on('renamestatus', (status) => {
|
||||||
unsubscribeCallbacks.push(VM!.on('adduser', (user) => addUser(user)));
|
// TODO: i18n these
|
||||||
unsubscribeCallbacks.push(VM!.on('remuser', (user) => remUser(user)));
|
switch (status) {
|
||||||
unsubscribeCallbacks.push(VM!.on('rename', (oldname, newname, selfrename) => userRenamed(oldname, newname, selfrename)));
|
case 'taken':
|
||||||
unsubscribeCallbacks.push(
|
alert(TheI18n.GetString(I18nStringKey.kError_UsernameTaken));
|
||||||
VM!.on('renamestatus', (status) => {
|
break;
|
||||||
// TODO: i18n these
|
case 'invalid':
|
||||||
switch (status) {
|
alert(TheI18n.GetString(I18nStringKey.kError_UsernameInvalid));
|
||||||
case 'taken':
|
break;
|
||||||
alert(TheI18n.GetString(I18nStringKey.kError_UsernameTaken));
|
case 'blacklisted':
|
||||||
break;
|
alert(TheI18n.GetString(I18nStringKey.kError_UsernameBlacklisted));
|
||||||
case 'invalid':
|
break;
|
||||||
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));
|
|
||||||
|
|
||||||
// Call all the unsubscribe callbacks.
|
VM!.on('turn', (status) => turnUpdate(status));
|
||||||
for (let l of unsubscribeCallbacks) l();
|
VM!.on('vote', (status: VoteStatus) => voteUpdate(status));
|
||||||
unsubscribeCallbacks = [];
|
VM!.on('voteend', () => voteEnd());
|
||||||
closeVM();
|
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
|
// Wait for the client to open
|
||||||
await VM!.WaitForOpen();
|
await VM!.WaitForOpen();
|
||||||
|
|
@ -374,7 +368,7 @@ async function openVM(vm: VM): Promise<void> {
|
||||||
throw new Error('Failed to connect to node');
|
throw new Error('Failed to connect to node');
|
||||||
}
|
}
|
||||||
// Set the title
|
// Set the title
|
||||||
document.title = vm.id + ' - CollabVM';
|
document.title = Format("{0} - {1}", vm.id, TheI18n.GetString(I18nStringKey.kGeneric_CollabVM));
|
||||||
// Append canvas
|
// Append canvas
|
||||||
elements.vmDisplay.appendChild(VM!.canvas);
|
elements.vmDisplay.appendChild(VM!.canvas);
|
||||||
// Switch to the VM view
|
// Switch to the VM view
|
||||||
|
|
@ -389,7 +383,7 @@ function closeVM() {
|
||||||
// Close the VM
|
// Close the VM
|
||||||
VM.close();
|
VM.close();
|
||||||
VM = null;
|
VM = null;
|
||||||
document.title = 'CollabVM';
|
document.title = TheI18n.GetString(I18nStringKey.kGeneric_CollabVM);
|
||||||
turn = -1;
|
turn = -1;
|
||||||
// Remove the canvas
|
// Remove the canvas
|
||||||
elements.vmDisplay.innerHTML = '';
|
elements.vmDisplay.innerHTML = '';
|
||||||
|
|
@ -844,6 +838,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
// Initalize the i18n system
|
// Initalize the i18n system
|
||||||
await TheI18n.Init();
|
await TheI18n.Init();
|
||||||
|
|
||||||
|
document.title = TheI18n.GetString(I18nStringKey.kGeneric_CollabVM);
|
||||||
|
|
||||||
// Load all VMs
|
// Load all VMs
|
||||||
await loadList();
|
await loadList();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ export default class CollabVMClient {
|
||||||
// public events
|
// public events
|
||||||
private publicEmitter: Emitter<CollabVMClientEvents>;
|
private publicEmitter: Emitter<CollabVMClientEvents>;
|
||||||
|
|
||||||
|
private unsubscribeCallbacks: Array<Unsubscribe> = [];
|
||||||
|
|
||||||
constructor(url: string) {
|
constructor(url: string) {
|
||||||
// Save the URL
|
// Save the URL
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
|
@ -434,6 +436,13 @@ export default class CollabVMClient {
|
||||||
// Close the connection
|
// Close the connection
|
||||||
close() {
|
close() {
|
||||||
this.connectedToVM = false;
|
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();
|
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 {
|
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', () => {
|
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');
|
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