mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-25 22:25:53 +02:00
Revert codestyle changes (#40)
* Revert "7f7efbb94bab8574e42d942ad82d414e007b2970" Code style changes should probably be part of a PR
This commit is contained in:
@@ -8,77 +8,77 @@ import { ACTIONS_LIMIT } from '../../../common/constants';
|
||||
import { last } from '../../../common/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'action-bar',
|
||||
templateUrl: 'action-bar.pug',
|
||||
styleUrls: ['action-bar.scss'],
|
||||
selector: 'action-bar',
|
||||
templateUrl: 'action-bar.pug',
|
||||
styleUrls: ['action-bar.scss'],
|
||||
})
|
||||
export class ActionBar {
|
||||
@ViewChild('scroller', { static: true }) scroller!: ElementRef;
|
||||
@Input() blurred = false;
|
||||
activeAction: ButtonAction | undefined = undefined;
|
||||
shortcuts = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '='];
|
||||
private _editable = false;
|
||||
constructor(private game: PonyTownGame, private settings: SettingsService) {
|
||||
}
|
||||
@Input() get editable() {
|
||||
return this._editable;
|
||||
}
|
||||
set editable(value) {
|
||||
if (this._editable !== value) {
|
||||
this._editable = value;
|
||||
this.updateFreeSlots();
|
||||
@ViewChild('scroller', { static: true }) scroller!: ElementRef;
|
||||
@Input() blurred = false;
|
||||
activeAction: ButtonAction | undefined = undefined;
|
||||
shortcuts = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '='];
|
||||
private _editable = false;
|
||||
constructor(private game: PonyTownGame, private settings: SettingsService) {
|
||||
}
|
||||
@Input() get editable() {
|
||||
return this._editable;
|
||||
}
|
||||
set editable(value) {
|
||||
if (this._editable !== value) {
|
||||
this._editable = value;
|
||||
this.updateFreeSlots();
|
||||
|
||||
if (!value) {
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
get actions() {
|
||||
return this.game.actions;
|
||||
}
|
||||
get mobile() {
|
||||
return isMobile;
|
||||
}
|
||||
get hasScroller() {
|
||||
return this.editable && isMobile;
|
||||
}
|
||||
get blurCount() {
|
||||
const boxWidth = isMobile ? 50 : 40;
|
||||
const width = 450 + this.scroller.nativeElement.scrollLeft;
|
||||
return Math.floor(width / boxWidth);
|
||||
}
|
||||
use(action: ButtonAction | undefined) {
|
||||
useAction(this.game, action);
|
||||
}
|
||||
drag(index: number) {
|
||||
this.actions[index].action = undefined;
|
||||
this.updateFreeSlots();
|
||||
}
|
||||
drop(action: ButtonAction | undefined, index: number) {
|
||||
this.actions[index].action = action;
|
||||
this.updateFreeSlots();
|
||||
}
|
||||
save() {
|
||||
const settings = { ...this.settings.account, actions: serializeActions(this.actions) };
|
||||
this.settings.saveAccountSettings(settings);
|
||||
}
|
||||
scroll(e: MouseWheelEvent) {
|
||||
if (e.deltaY) {
|
||||
const delta = e.deltaY > 0 ? 1 : -1;
|
||||
this.scroller.nativeElement.scrollLeft += delta * 20;
|
||||
}
|
||||
}
|
||||
private updateFreeSlots() {
|
||||
const actions = this.actions;
|
||||
if (!value) {
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
get actions() {
|
||||
return this.game.actions;
|
||||
}
|
||||
get mobile() {
|
||||
return isMobile;
|
||||
}
|
||||
get hasScroller() {
|
||||
return this.editable && isMobile;
|
||||
}
|
||||
get blurCount() {
|
||||
const boxWidth = isMobile ? 50 : 40;
|
||||
const width = 450 + this.scroller.nativeElement.scrollLeft;
|
||||
return Math.floor(width / boxWidth);
|
||||
}
|
||||
use(action: ButtonAction | undefined) {
|
||||
useAction(this.game, action);
|
||||
}
|
||||
drag(index: number) {
|
||||
this.actions[index].action = undefined;
|
||||
this.updateFreeSlots();
|
||||
}
|
||||
drop(action: ButtonAction | undefined, index: number) {
|
||||
this.actions[index].action = action;
|
||||
this.updateFreeSlots();
|
||||
}
|
||||
save() {
|
||||
const settings = { ...this.settings.account, actions: serializeActions(this.actions) };
|
||||
this.settings.saveAccountSettings(settings);
|
||||
}
|
||||
scroll(e: MouseWheelEvent) {
|
||||
if (e.deltaY) {
|
||||
const delta = e.deltaY > 0 ? 1 : -1;
|
||||
this.scroller.nativeElement.scrollLeft += delta * 20;
|
||||
}
|
||||
}
|
||||
private updateFreeSlots() {
|
||||
const actions = this.actions;
|
||||
|
||||
if (this.editable) {
|
||||
while (actions.length < 5 || (last(actions)!.action !== undefined && actions.length < ACTIONS_LIMIT)) {
|
||||
actions.push({ action: undefined });
|
||||
}
|
||||
} else {
|
||||
while (actions.length > 0 && last(actions)!.action === undefined) {
|
||||
actions.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.editable) {
|
||||
while (actions.length < 5 || (last(actions)!.action !== undefined && actions.length < ACTIONS_LIMIT)) {
|
||||
actions.push({ action: undefined });
|
||||
}
|
||||
} else {
|
||||
while (actions.length > 0 && last(actions)!.action === undefined) {
|
||||
actions.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,42 +5,42 @@ import { drawAction } from '../../../client/buttonActions';
|
||||
import { removeItem } from '../../../common/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'action-button',
|
||||
templateUrl: 'action-button.pug',
|
||||
styleUrls: ['action-button.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: {
|
||||
'[class.empty]': '!editable && !action',
|
||||
},
|
||||
selector: 'action-button',
|
||||
templateUrl: 'action-button.pug',
|
||||
styleUrls: ['action-button.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: {
|
||||
'[class.empty]': '!editable && !action',
|
||||
},
|
||||
})
|
||||
export class ActionButton {
|
||||
@Input() action?: ButtonAction;
|
||||
@Input() editable = false;
|
||||
@Input() active = false;
|
||||
@Input() shadow = true;
|
||||
@Input() shortcut = '';
|
||||
@Output() use = new EventEmitter<ButtonAction>();
|
||||
@ViewChild('canvas', { static: true }) canvas!: ElementRef;
|
||||
dirty = true;
|
||||
private state: any = {};
|
||||
constructor(private game: PonyTownGame) {
|
||||
}
|
||||
ngOnInit() {
|
||||
actionButtons.push(this);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
removeItem(actionButtons, this);
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.dirty = true;
|
||||
}
|
||||
click() {
|
||||
if (this.action) {
|
||||
this.use.emit(this.action);
|
||||
}
|
||||
}
|
||||
draw() {
|
||||
drawAction(this.canvas.nativeElement, this.action, this.state, this.game);
|
||||
this.dirty = false;
|
||||
}
|
||||
@Input() action?: ButtonAction;
|
||||
@Input() editable = false;
|
||||
@Input() active = false;
|
||||
@Input() shadow = true;
|
||||
@Input() shortcut = '';
|
||||
@Output() use = new EventEmitter<ButtonAction>();
|
||||
@ViewChild('canvas', { static: true }) canvas!: ElementRef;
|
||||
dirty = true;
|
||||
private state: any = {};
|
||||
constructor(private game: PonyTownGame) {
|
||||
}
|
||||
ngOnInit() {
|
||||
actionButtons.push(this);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
removeItem(actionButtons, this);
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.dirty = true;
|
||||
}
|
||||
click() {
|
||||
if (this.action) {
|
||||
this.use.emit(this.action);
|
||||
}
|
||||
}
|
||||
draw() {
|
||||
drawAction(this.canvas.nativeElement, this.action, this.state, this.game);
|
||||
this.dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Component, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import {
|
||||
createButtionActionActions, expressionButtonAction, createButtonCommandActions,
|
||||
createDefaultButtonActions, actionExpressionDefaultPalette, entityButtonAction
|
||||
createButtionActionActions, expressionButtonAction, createButtonCommandActions,
|
||||
createDefaultButtonActions, actionExpressionDefaultPalette, entityButtonAction
|
||||
} from '../../../client/buttonActions';
|
||||
import * as sprites from '../../../generated/sprites';
|
||||
import {
|
||||
Eye, Muzzle, ColorExtraSet, ColorExtra, Iris, ExpressionExtra, PonyEye, ButtonAction, Action,
|
||||
ButtonActionSlot, EntityButtonAction
|
||||
Eye, Muzzle, ColorExtraSet, ColorExtra, Iris, ExpressionExtra, PonyEye, ButtonAction, Action,
|
||||
ButtonActionSlot, EntityButtonAction
|
||||
} from '../../../common/interfaces';
|
||||
import { createExpression } from '../../../client/clientUtils';
|
||||
import { ACTION_EXPRESSION_BG, ACTION_EXPRESSION_EYE_COLOR, fillToOutline } from '../../../common/colors';
|
||||
@@ -18,137 +18,137 @@ import { PonyTownGame } from '../../../client/game';
|
||||
import { getEntityNames } from '../../services/model';
|
||||
|
||||
function eyeSprite(e: PonyEye | undefined) {
|
||||
return createEyeSprite(e, 0, sprites.defaultPalette);
|
||||
return createEyeSprite(e, 0, sprites.defaultPalette);
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'actions-modal',
|
||||
templateUrl: 'actions-modal.pug',
|
||||
styleUrls: ['actions-modal.scss'],
|
||||
selector: 'actions-modal',
|
||||
templateUrl: 'actions-modal.pug',
|
||||
styleUrls: ['actions-modal.scss'],
|
||||
})
|
||||
export class ActionsModal implements OnInit, OnDestroy {
|
||||
readonly lockIcon = faLock;
|
||||
readonly actionsIcon = faApple;
|
||||
readonly expressionsIcon = faLaughBeam;
|
||||
readonly chatIcon = faComment;
|
||||
readonly optionsIcon = faCog;
|
||||
readonly devIcon = faCogs;
|
||||
readonly dev = BETA;
|
||||
@Output() close = new EventEmitter();
|
||||
actions = createButtionActionActions();
|
||||
commands = createButtonCommandActions();
|
||||
emoteAction = expressionButtonAction(createExpression(Eye.Neutral, Eye.Neutral, Muzzle.Smile));
|
||||
entityAction = entityButtonAction('apple');
|
||||
entityActions: EntityButtonAction[] = [];
|
||||
entityName = 'apple';
|
||||
lockEyes = true;
|
||||
lockIrises = true;
|
||||
eyesLeft: ColorExtraSet = sprites.eyeLeft.map(e => e && e[0]).map(eyeSprite);
|
||||
eyesRight: ColorExtraSet = sprites.eyeRight.map(e => e && e[0]).map(eyeSprite);
|
||||
irisesLeft: ColorExtraSet = times(Iris.COUNT, i => createEyeSprite(sprites.eyeLeft[1]![0]!, i, sprites.defaultPalette));
|
||||
irisesRight: ColorExtraSet = times(Iris.COUNT, i => createEyeSprite(sprites.eyeRight[1]![0]!, i, sprites.defaultPalette));
|
||||
muzzles: ColorExtraSet = sprites.noses
|
||||
.map(n => n[0][0])
|
||||
.map(({ color, colors, mouth }) => ({
|
||||
color, colors, extra: mouth, palettes: [actionExpressionDefaultPalette.colors]
|
||||
} as ColorExtra));
|
||||
noseFills = [ACTION_EXPRESSION_BG];
|
||||
noseOutlines = [fillToOutline(ACTION_EXPRESSION_BG)];
|
||||
coatFill = ACTION_EXPRESSION_BG;
|
||||
eyeColor = ACTION_EXPRESSION_EYE_COLOR;
|
||||
muzzle: Muzzle = 0;
|
||||
eyeLeft: Eye = 1;
|
||||
eyeRight: Eye = 1;
|
||||
irisLeft: Iris = 0;
|
||||
irisRight: Iris = 0;
|
||||
tabIndex = 0;
|
||||
blush = false;
|
||||
sleeping = false;
|
||||
tears = false;
|
||||
crying = false;
|
||||
hearts = false;
|
||||
activeTab = 'right-eye';
|
||||
private interval: any = 0;
|
||||
private subscription?: Subscription;
|
||||
private actionsToUndo: ButtonActionSlot[][] = [];
|
||||
constructor(private game: PonyTownGame) {
|
||||
this.updateEmoteAction();
|
||||
}
|
||||
ngOnInit() {
|
||||
document.body.classList.add('actions-modal-opened');
|
||||
this.game.editingActions = true;
|
||||
this.interval = setInterval(() => this.game.send(server => server.action(Action.KeepAlive)), 10000);
|
||||
this.subscription = this.game.onLeft.subscribe(() => this.ok());
|
||||
readonly lockIcon = faLock;
|
||||
readonly actionsIcon = faApple;
|
||||
readonly expressionsIcon = faLaughBeam;
|
||||
readonly chatIcon = faComment;
|
||||
readonly optionsIcon = faCog;
|
||||
readonly devIcon = faCogs;
|
||||
readonly dev = BETA;
|
||||
@Output() close = new EventEmitter();
|
||||
actions = createButtionActionActions();
|
||||
commands = createButtonCommandActions();
|
||||
emoteAction = expressionButtonAction(createExpression(Eye.Neutral, Eye.Neutral, Muzzle.Smile));
|
||||
entityAction = entityButtonAction('apple');
|
||||
entityActions: EntityButtonAction[] = [];
|
||||
entityName = 'apple';
|
||||
lockEyes = true;
|
||||
lockIrises = true;
|
||||
eyesLeft: ColorExtraSet = sprites.eyeLeft.map(e => e && e[0]).map(eyeSprite);
|
||||
eyesRight: ColorExtraSet = sprites.eyeRight.map(e => e && e[0]).map(eyeSprite);
|
||||
irisesLeft: ColorExtraSet = times(Iris.COUNT, i => createEyeSprite(sprites.eyeLeft[1]![0]!, i, sprites.defaultPalette));
|
||||
irisesRight: ColorExtraSet = times(Iris.COUNT, i => createEyeSprite(sprites.eyeRight[1]![0]!, i, sprites.defaultPalette));
|
||||
muzzles: ColorExtraSet = sprites.noses
|
||||
.map(n => n[0][0])
|
||||
.map(({ color, colors, mouth }) => ({
|
||||
color, colors, extra: mouth, palettes: [actionExpressionDefaultPalette.colors]
|
||||
} as ColorExtra));
|
||||
noseFills = [ACTION_EXPRESSION_BG];
|
||||
noseOutlines = [fillToOutline(ACTION_EXPRESSION_BG)];
|
||||
coatFill = ACTION_EXPRESSION_BG;
|
||||
eyeColor = ACTION_EXPRESSION_EYE_COLOR;
|
||||
muzzle: Muzzle = 0;
|
||||
eyeLeft: Eye = 1;
|
||||
eyeRight: Eye = 1;
|
||||
irisLeft: Iris = 0;
|
||||
irisRight: Iris = 0;
|
||||
tabIndex = 0;
|
||||
blush = false;
|
||||
sleeping = false;
|
||||
tears = false;
|
||||
crying = false;
|
||||
hearts = false;
|
||||
activeTab = 'right-eye';
|
||||
private interval: any = 0;
|
||||
private subscription?: Subscription;
|
||||
private actionsToUndo: ButtonActionSlot[][] = [];
|
||||
constructor(private game: PonyTownGame) {
|
||||
this.updateEmoteAction();
|
||||
}
|
||||
ngOnInit() {
|
||||
document.body.classList.add('actions-modal-opened');
|
||||
this.game.editingActions = true;
|
||||
this.interval = setInterval(() => this.game.send(server => server.action(Action.KeepAlive)), 10000);
|
||||
this.subscription = this.game.onLeft.subscribe(() => this.ok());
|
||||
|
||||
if (BETA) {
|
||||
this.entityActions = getEntityNames().map(name => entityButtonAction(name));
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
document.body.classList.remove('actions-modal-opened');
|
||||
this.game.editingActions = false;
|
||||
clearInterval(this.interval);
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
ok() {
|
||||
this.close.emit();
|
||||
}
|
||||
changed(locked: boolean) {
|
||||
if (locked) {
|
||||
this.eyeLeft = this.eyeRight;
|
||||
}
|
||||
if (BETA) {
|
||||
this.entityActions = getEntityNames().map(name => entityButtonAction(name));
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
document.body.classList.remove('actions-modal-opened');
|
||||
this.game.editingActions = false;
|
||||
clearInterval(this.interval);
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
ok() {
|
||||
this.close.emit();
|
||||
}
|
||||
changed(locked: boolean) {
|
||||
if (locked) {
|
||||
this.eyeLeft = this.eyeRight;
|
||||
}
|
||||
|
||||
if (this.lockIrises) {
|
||||
this.irisLeft = this.irisRight;
|
||||
}
|
||||
if (this.lockIrises) {
|
||||
this.irisLeft = this.irisRight;
|
||||
}
|
||||
|
||||
this.updateEmoteAction();
|
||||
}
|
||||
drop(action: ButtonAction) {
|
||||
if (action.type === 'expression' && action.expression) {
|
||||
const e = action.expression;
|
||||
this.lockEyes = e.right === e.left;
|
||||
this.lockIrises = e.rightIris === e.leftIris;
|
||||
this.eyeRight = e.right;
|
||||
this.eyeLeft = e.left;
|
||||
this.muzzle = e.muzzle;
|
||||
this.irisRight = e.rightIris;
|
||||
this.irisLeft = e.leftIris;
|
||||
this.blush = hasFlag(e.extra, ExpressionExtra.Blush);
|
||||
this.sleeping = hasFlag(e.extra, ExpressionExtra.Zzz);
|
||||
this.tears = hasFlag(e.extra, ExpressionExtra.Tears);
|
||||
this.crying = hasFlag(e.extra, ExpressionExtra.Cry);
|
||||
this.hearts = hasFlag(e.extra, ExpressionExtra.Hearts);
|
||||
this.changed(this.lockEyes);
|
||||
}
|
||||
}
|
||||
updateEmoteAction() {
|
||||
const extra =
|
||||
(this.blush ? ExpressionExtra.Blush : 0) |
|
||||
(this.sleeping ? ExpressionExtra.Zzz : 0) |
|
||||
(this.tears ? ExpressionExtra.Tears : 0) |
|
||||
(this.crying ? ExpressionExtra.Cry : 0) |
|
||||
(this.hearts ? ExpressionExtra.Hearts : 0);
|
||||
this.updateEmoteAction();
|
||||
}
|
||||
drop(action: ButtonAction) {
|
||||
if (action.type === 'expression' && action.expression) {
|
||||
const e = action.expression;
|
||||
this.lockEyes = e.right === e.left;
|
||||
this.lockIrises = e.rightIris === e.leftIris;
|
||||
this.eyeRight = e.right;
|
||||
this.eyeLeft = e.left;
|
||||
this.muzzle = e.muzzle;
|
||||
this.irisRight = e.rightIris;
|
||||
this.irisLeft = e.leftIris;
|
||||
this.blush = hasFlag(e.extra, ExpressionExtra.Blush);
|
||||
this.sleeping = hasFlag(e.extra, ExpressionExtra.Zzz);
|
||||
this.tears = hasFlag(e.extra, ExpressionExtra.Tears);
|
||||
this.crying = hasFlag(e.extra, ExpressionExtra.Cry);
|
||||
this.hearts = hasFlag(e.extra, ExpressionExtra.Hearts);
|
||||
this.changed(this.lockEyes);
|
||||
}
|
||||
}
|
||||
updateEmoteAction() {
|
||||
const extra =
|
||||
(this.blush ? ExpressionExtra.Blush : 0) |
|
||||
(this.sleeping ? ExpressionExtra.Zzz : 0) |
|
||||
(this.tears ? ExpressionExtra.Tears : 0) |
|
||||
(this.crying ? ExpressionExtra.Cry : 0) |
|
||||
(this.hearts ? ExpressionExtra.Hearts : 0);
|
||||
|
||||
const expression = createExpression(this.eyeRight, this.eyeLeft, this.muzzle, this.irisRight, this.irisLeft, extra);
|
||||
this.emoteAction = expressionButtonAction(expression);
|
||||
}
|
||||
resetToDefault() {
|
||||
this.actionsToUndo.push(this.game.actions);
|
||||
this.game.actions = [...createDefaultButtonActions(), { action: undefined }];
|
||||
}
|
||||
clearActionBar() {
|
||||
this.actionsToUndo.push(this.game.actions);
|
||||
this.game.actions = this.game.actions.map(() => ({ action: undefined }));
|
||||
}
|
||||
undo() {
|
||||
if (this.actionsToUndo.length) {
|
||||
this.game.actions = this.actionsToUndo.pop()!;
|
||||
}
|
||||
}
|
||||
updateEntity() {
|
||||
if (BETA) {
|
||||
this.entityAction = entityButtonAction(this.entityName);
|
||||
}
|
||||
}
|
||||
const expression = createExpression(this.eyeRight, this.eyeLeft, this.muzzle, this.irisRight, this.irisLeft, extra);
|
||||
this.emoteAction = expressionButtonAction(expression);
|
||||
}
|
||||
resetToDefault() {
|
||||
this.actionsToUndo.push(this.game.actions);
|
||||
this.game.actions = [...createDefaultButtonActions(), { action: undefined }];
|
||||
}
|
||||
clearActionBar() {
|
||||
this.actionsToUndo.push(this.game.actions);
|
||||
this.game.actions = this.game.actions.map(() => ({ action: undefined }));
|
||||
}
|
||||
undo() {
|
||||
if (this.actionsToUndo.length) {
|
||||
this.game.actions = this.actionsToUndo.pop()!;
|
||||
}
|
||||
}
|
||||
updateEntity() {
|
||||
if (BETA) {
|
||||
this.entityAction = entityButtonAction(this.entityName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,44 +2,44 @@ import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from
|
||||
import { parseColor, colorToCSS } from '../../../common/color';
|
||||
|
||||
@Component({
|
||||
selector: 'bitmap-box',
|
||||
templateUrl: 'bitmap-box.pug',
|
||||
styleUrls: ['bitmap-box.scss'],
|
||||
selector: 'bitmap-box',
|
||||
templateUrl: 'bitmap-box.pug',
|
||||
styleUrls: ['bitmap-box.scss'],
|
||||
})
|
||||
export class BitmapBox implements OnChanges {
|
||||
@Input() width = 5;
|
||||
@Input() height = 5;
|
||||
@Input() bitmap?: string[];
|
||||
@Input() tool?: string;
|
||||
@Input() color = 'red';
|
||||
@Output() colorChange = new EventEmitter<string>();
|
||||
rows?: number[][];
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.width || changes.height) {
|
||||
this.rows = [];
|
||||
@Input() width = 5;
|
||||
@Input() height = 5;
|
||||
@Input() bitmap?: string[];
|
||||
@Input() tool?: string;
|
||||
@Input() color = 'red';
|
||||
@Output() colorChange = new EventEmitter<string>();
|
||||
rows?: number[][];
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.width || changes.height) {
|
||||
this.rows = [];
|
||||
|
||||
for (let y = 0; y < this.height; y++) {
|
||||
this.rows[y] = [];
|
||||
for (let y = 0; y < this.height; y++) {
|
||||
this.rows[y] = [];
|
||||
|
||||
for (let x = 0; x < this.width; x++) {
|
||||
this.rows[y][x] = x + this.width * y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
draw(index: number) {
|
||||
if (this.bitmap) {
|
||||
if (this.tool === 'eraser') {
|
||||
this.bitmap[index] = '';
|
||||
} else if (this.tool === 'brush') {
|
||||
this.bitmap[index] = parseColor(this.bitmap[index]) === parseColor(this.color) ? '' : this.color;
|
||||
} else if (this.tool === 'eyedropper') {
|
||||
this.color = this.bitmap[index];
|
||||
this.colorChange.emit(this.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
colorAt(index: number) {
|
||||
return this.bitmap && this.bitmap[index] ? colorToCSS(parseColor(this.bitmap[index])) : '';
|
||||
}
|
||||
for (let x = 0; x < this.width; x++) {
|
||||
this.rows[y][x] = x + this.width * y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
draw(index: number) {
|
||||
if (this.bitmap) {
|
||||
if (this.tool === 'eraser') {
|
||||
this.bitmap[index] = '';
|
||||
} else if (this.tool === 'brush') {
|
||||
this.bitmap[index] = parseColor(this.bitmap[index]) === parseColor(this.color) ? '' : this.color;
|
||||
} else if (this.tool === 'eyedropper') {
|
||||
this.color = this.bitmap[index];
|
||||
this.colorChange.emit(this.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
colorAt(index: number) {
|
||||
return this.bitmap && this.bitmap[index] ? colorToCSS(parseColor(this.bitmap[index])) : '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,26 +5,26 @@ import { CM_SIZE } from '../../../common/constants';
|
||||
import { faTrash, faEraser, faPaintBrush, faEyeDropper } from '../../../client/icons';
|
||||
|
||||
export interface ButtMarkEditorState {
|
||||
brushType: string;
|
||||
brush: string;
|
||||
brushType: string;
|
||||
brush: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'butt-mark-editor',
|
||||
templateUrl: 'butt-mark-editor.pug',
|
||||
selector: 'butt-mark-editor',
|
||||
templateUrl: 'butt-mark-editor.pug',
|
||||
})
|
||||
export class ButtMarkEditor {
|
||||
readonly trashIcon = faTrash;
|
||||
readonly eraserIcon = faEraser;
|
||||
readonly eyeDropperIcon = faEyeDropper;
|
||||
readonly paintBrushIcon = faPaintBrush;
|
||||
readonly cmSize = CM_SIZE;
|
||||
@Input() info!: PonyInfo;
|
||||
@Input() state = {
|
||||
brushType: 'brush',
|
||||
brush: 'orange',
|
||||
};
|
||||
clearCM() {
|
||||
fill(this.info.cm!, '');
|
||||
}
|
||||
readonly trashIcon = faTrash;
|
||||
readonly eraserIcon = faEraser;
|
||||
readonly eyeDropperIcon = faEyeDropper;
|
||||
readonly paintBrushIcon = faPaintBrush;
|
||||
readonly cmSize = CM_SIZE;
|
||||
@Input() info!: PonyInfo;
|
||||
@Input() state = {
|
||||
brushType: 'brush',
|
||||
brush: 'orange',
|
||||
};
|
||||
clearCM() {
|
||||
fill(this.info.cm!, '');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,164 +9,164 @@ import { LATEST_CHARACTER_LIMIT } from '../../../common/constants';
|
||||
import { faHashtag } from '../../../client/icons';
|
||||
|
||||
function getSortTag(pony: PonyObject) {
|
||||
const match = pony.desc && /(?:^| )@(top|end|\d+)(?:$| )/.exec(pony.desc);
|
||||
return match && match[1];
|
||||
const match = pony.desc && /(?:^| )@(top|end|\d+)(?:$| )/.exec(pony.desc);
|
||||
return match && match[1];
|
||||
}
|
||||
|
||||
function sortTagToNumber(tag: string) {
|
||||
if (tag === 'top') {
|
||||
return -1;
|
||||
} else if (tag === 'end') {
|
||||
return 999999999;
|
||||
} else {
|
||||
return +tag;
|
||||
}
|
||||
if (tag === 'top') {
|
||||
return -1;
|
||||
} else if (tag === 'end') {
|
||||
return 999999999;
|
||||
} else {
|
||||
return +tag;
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackComparePonies(a: PonyObject, b: PonyObject) {
|
||||
return a.name.localeCompare(b.name) || (a.desc || '').localeCompare(b.desc || '');
|
||||
return a.name.localeCompare(b.name) || (a.desc || '').localeCompare(b.desc || '');
|
||||
}
|
||||
|
||||
function comparePonies(a: PonyObject, b: PonyObject) {
|
||||
const aTag = getSortTag(a);
|
||||
const bTag = getSortTag(b);
|
||||
const aTag = getSortTag(a);
|
||||
const bTag = getSortTag(b);
|
||||
|
||||
if (aTag && bTag) {
|
||||
return (sortTagToNumber(aTag) - sortTagToNumber(bTag)) || fallbackComparePonies(a, b);
|
||||
} else if (aTag) {
|
||||
return aTag === 'end' ? 1 : -1;
|
||||
} else if (bTag) {
|
||||
return bTag === 'end' ? -1 : 1;
|
||||
} else {
|
||||
return fallbackComparePonies(a, b);
|
||||
}
|
||||
if (aTag && bTag) {
|
||||
return (sortTagToNumber(aTag) - sortTagToNumber(bTag)) || fallbackComparePonies(a, b);
|
||||
} else if (aTag) {
|
||||
return aTag === 'end' ? 1 : -1;
|
||||
} else if (bTag) {
|
||||
return bTag === 'end' ? -1 : 1;
|
||||
} else {
|
||||
return fallbackComparePonies(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'character-list',
|
||||
templateUrl: 'character-list.pug',
|
||||
styleUrls: ['character-list.scss'],
|
||||
selector: 'character-list',
|
||||
templateUrl: 'character-list.pug',
|
||||
styleUrls: ['character-list.scss'],
|
||||
})
|
||||
export class CharacterList implements OnInit {
|
||||
readonly hashIcon = faHashtag;
|
||||
@Input() inGame = false;
|
||||
@Input() canNew = false;
|
||||
@Output() close = new EventEmitter<void>();
|
||||
@Output() newCharacter = new EventEmitter<void>();
|
||||
@Output() selectCharacter = new EventEmitter<PonyObject>();
|
||||
@Output() previewCharacter = new EventEmitter<PonyObject | undefined>();
|
||||
@ViewChild('ariaAnnounce', { static: true }) ariaAnnounce!: ElementRef;
|
||||
@ViewChild('searchInput', { static: true }) searchInput!: ElementRef;
|
||||
search?: string;
|
||||
selectedIndex = -1;
|
||||
ponies: PonyObject[] = [];
|
||||
tags: string[] = [];
|
||||
private previewPony: PonyObject | undefined = undefined;
|
||||
constructor(private model: Model, private zone: NgZone) {
|
||||
}
|
||||
get selectedPony() {
|
||||
return this.model.pony;
|
||||
}
|
||||
get searchable() {
|
||||
return this.model.ponies.length > LATEST_CHARACTER_LIMIT;
|
||||
}
|
||||
get placeholder() {
|
||||
return `search (${this.model.ponies.length} / ${this.model.characterLimit} ponies)`;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.updatePonies();
|
||||
readonly hashIcon = faHashtag;
|
||||
@Input() inGame = false;
|
||||
@Input() canNew = false;
|
||||
@Output() close = new EventEmitter<void>();
|
||||
@Output() newCharacter = new EventEmitter<void>();
|
||||
@Output() selectCharacter = new EventEmitter<PonyObject>();
|
||||
@Output() previewCharacter = new EventEmitter<PonyObject | undefined>();
|
||||
@ViewChild('ariaAnnounce', { static: true }) ariaAnnounce!: ElementRef;
|
||||
@ViewChild('searchInput', { static: true }) searchInput!: ElementRef;
|
||||
search?: string;
|
||||
selectedIndex = -1;
|
||||
ponies: PonyObject[] = [];
|
||||
tags: string[] = [];
|
||||
private previewPony: PonyObject | undefined = undefined;
|
||||
constructor(private model: Model, private zone: NgZone) {
|
||||
}
|
||||
get selectedPony() {
|
||||
return this.model.pony;
|
||||
}
|
||||
get searchable() {
|
||||
return this.model.ponies.length > LATEST_CHARACTER_LIMIT;
|
||||
}
|
||||
get placeholder() {
|
||||
return `search (${this.model.ponies.length} / ${this.model.characterLimit} ponies)`;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.updatePonies();
|
||||
|
||||
this.tags = uniq(flatten(this.ponies.map(p => (p.desc || '').split(/ /g).map(x => x.trim())))
|
||||
.filter(x => /^#/.test(x)))
|
||||
.sort();
|
||||
this.tags = uniq(flatten(this.ponies.map(p => (p.desc || '').split(/ /g).map(x => x.trim())))
|
||||
.filter(x => /^#/.test(x)))
|
||||
.sort();
|
||||
|
||||
if (!isMobile) {
|
||||
setTimeout(() => this.searchInput.nativeElement.focus());
|
||||
}
|
||||
}
|
||||
keydown(e: KeyboardEvent) {
|
||||
if (e.keyCode === Key.ESCAPE) {
|
||||
if (this.search) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.search = '';
|
||||
this.updatePonies();
|
||||
} else {
|
||||
this.closed();
|
||||
}
|
||||
} else if (e.keyCode === Key.ENTER) {
|
||||
const pony = this.ponies[this.selectedIndex];
|
||||
if (!isMobile) {
|
||||
setTimeout(() => this.searchInput.nativeElement.focus());
|
||||
}
|
||||
}
|
||||
keydown(e: KeyboardEvent) {
|
||||
if (e.keyCode === Key.ESCAPE) {
|
||||
if (this.search) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.search = '';
|
||||
this.updatePonies();
|
||||
} else {
|
||||
this.closed();
|
||||
}
|
||||
} else if (e.keyCode === Key.ENTER) {
|
||||
const pony = this.ponies[this.selectedIndex];
|
||||
|
||||
if (pony) {
|
||||
this.select(pony);
|
||||
} else {
|
||||
this.closed();
|
||||
}
|
||||
} else if (e.keyCode === Key.UP) {
|
||||
this.setSelectedIndex(this.selectedIndex <= 0 ? (this.ponies.length - 1) : (this.selectedIndex - 1));
|
||||
} else if (e.keyCode === Key.DOWN) {
|
||||
this.setSelectedIndex(this.selectedIndex === (this.ponies.length - 1) ? 0 : (this.selectedIndex + 1));
|
||||
}
|
||||
}
|
||||
setPreview(pony: PonyObject) {
|
||||
this.previewPony = pony;
|
||||
this.previewCharacter.emit(this.model.parsePonyObject(pony));
|
||||
}
|
||||
unsetPreview(pony: PonyObject) {
|
||||
if (this.previewPony && pony && this.previewPony.id === pony.id) {
|
||||
this.previewPony = undefined;
|
||||
this.previewCharacter.emit(undefined);
|
||||
}
|
||||
}
|
||||
updatePonies() {
|
||||
this.zone.run(() => {
|
||||
const query = this.search && this.search.toLowerCase().trim();
|
||||
if (pony) {
|
||||
this.select(pony);
|
||||
} else {
|
||||
this.closed();
|
||||
}
|
||||
} else if (e.keyCode === Key.UP) {
|
||||
this.setSelectedIndex(this.selectedIndex <= 0 ? (this.ponies.length - 1) : (this.selectedIndex - 1));
|
||||
} else if (e.keyCode === Key.DOWN) {
|
||||
this.setSelectedIndex(this.selectedIndex === (this.ponies.length - 1) ? 0 : (this.selectedIndex + 1));
|
||||
}
|
||||
}
|
||||
setPreview(pony: PonyObject) {
|
||||
this.previewPony = pony;
|
||||
this.previewCharacter.emit(this.model.parsePonyObject(pony));
|
||||
}
|
||||
unsetPreview(pony: PonyObject) {
|
||||
if (this.previewPony && pony && this.previewPony.id === pony.id) {
|
||||
this.previewPony = undefined;
|
||||
this.previewCharacter.emit(undefined);
|
||||
}
|
||||
}
|
||||
updatePonies() {
|
||||
this.zone.run(() => {
|
||||
const query = this.search && this.search.toLowerCase().trim();
|
||||
|
||||
function matchesWords(text: string, words: string[]) {
|
||||
for (const word of words) {
|
||||
if (text.indexOf(word) === -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function matchesWords(text: string, words: string[]) {
|
||||
for (const word of words) {
|
||||
if (text.indexOf(word) === -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (query) {
|
||||
const words = query.split(/ /g).map(x => x.trim());
|
||||
if (query) {
|
||||
const words = query.split(/ /g).map(x => x.trim());
|
||||
|
||||
this.ponies = this.model.ponies.filter(pony => {
|
||||
const text = `${pony.name} ${pony.desc || ''}`.toLowerCase();
|
||||
return matchesWords(text, words);
|
||||
}).sort(comparePonies);
|
||||
} else {
|
||||
this.ponies = this.model.ponies.slice().sort(comparePonies);
|
||||
}
|
||||
this.ponies = this.model.ponies.filter(pony => {
|
||||
const text = `${pony.name} ${pony.desc || ''}`.toLowerCase();
|
||||
return matchesWords(text, words);
|
||||
}).sort(comparePonies);
|
||||
} else {
|
||||
this.ponies = this.model.ponies.slice().sort(comparePonies);
|
||||
}
|
||||
|
||||
this.setSelectedIndex(this.selectedIndex);
|
||||
this.previewCharacter.emit(undefined);
|
||||
});
|
||||
}
|
||||
select(pony: PonyObject) {
|
||||
this.selectCharacter.emit(pony);
|
||||
}
|
||||
createNew() {
|
||||
this.newCharacter.emit();
|
||||
}
|
||||
private closed() {
|
||||
this.zone.run(() => this.close.emit());
|
||||
}
|
||||
private setSelectedIndex(index: number) {
|
||||
this.zone.run(() => {
|
||||
this.selectedIndex = clamp(index, -1, this.ponies.length - 1);
|
||||
const pony = this.ponies[index];
|
||||
this.ariaAnnounce.nativeElement.textContent = pony ? pony.name : '';
|
||||
this.setSelectedIndex(this.selectedIndex);
|
||||
this.previewCharacter.emit(undefined);
|
||||
});
|
||||
}
|
||||
select(pony: PonyObject) {
|
||||
this.selectCharacter.emit(pony);
|
||||
}
|
||||
createNew() {
|
||||
this.newCharacter.emit();
|
||||
}
|
||||
private closed() {
|
||||
this.zone.run(() => this.close.emit());
|
||||
}
|
||||
private setSelectedIndex(index: number) {
|
||||
this.zone.run(() => {
|
||||
this.selectedIndex = clamp(index, -1, this.ponies.length - 1);
|
||||
const pony = this.ponies[index];
|
||||
this.ariaAnnounce.nativeElement.textContent = pony ? pony.name : '';
|
||||
|
||||
if (pony) {
|
||||
this.setPreview(pony);
|
||||
} else if (this.previewPony) {
|
||||
this.unsetPreview(this.previewPony);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (pony) {
|
||||
this.setPreview(pony);
|
||||
} else if (this.previewPony) {
|
||||
this.unsetPreview(this.previewPony);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {
|
||||
Component, Input, ElementRef, AfterViewInit, OnDestroy, NgZone, ViewChild, OnChanges, HostListener
|
||||
Component, Input, ElementRef, AfterViewInit, OnDestroy, NgZone, ViewChild, OnChanges, HostListener
|
||||
} from '@angular/core';
|
||||
import { PonyInfo, PonyState } from '../../../common/interfaces';
|
||||
import { toPalette } from '../../../common/ponyInfo';
|
||||
import { GRASS_COLOR, TRANSPARENT } from '../../../common/colors';
|
||||
import {
|
||||
createCanvas, disableImageSmoothing, getPixelRatio, resizeCanvas, resizeCanvasWithRatio
|
||||
createCanvas, disableImageSmoothing, getPixelRatio, resizeCanvas, resizeCanvasWithRatio
|
||||
} from '../../../client/canvasUtils';
|
||||
import { BLINK_FRAMES } from '../../../client/ponyUtils';
|
||||
import { defaultPonyState, defaultDrawPonyOptions } from '../../../client/ponyHelpers';
|
||||
@@ -21,172 +21,172 @@ const DEFAULT_STATE = defaultPonyState();
|
||||
const DEFAULT_OPTIONS = defaultDrawPonyOptions();
|
||||
|
||||
@Component({
|
||||
selector: 'character-preview',
|
||||
template: '<canvas class="rounded" #canvas></canvas>',
|
||||
styles: [`:host { display: block; } canvas { width: 100%; height: 100%; }`],
|
||||
selector: 'character-preview',
|
||||
template: '<canvas class="rounded" #canvas></canvas>',
|
||||
styles: [`:host { display: block; } canvas { width: 100%; height: 100%; }`],
|
||||
})
|
||||
export class CharacterPreview implements OnDestroy, OnChanges, AfterViewInit {
|
||||
@Input() scale = 3;
|
||||
@Input() name?: string;
|
||||
@Input() tag?: string;
|
||||
@Input() pony?: PonyInfo;
|
||||
@Input() state?: PonyState = defaultPonyState();
|
||||
@Input() noBackground = false;
|
||||
@Input() noOutline = false;
|
||||
@Input() noShadow = false;
|
||||
@Input() extra = false;
|
||||
@Input() passive = false;
|
||||
@Input() blinks = true;
|
||||
@ViewChild('canvas', { static: true }) canvas!: ElementRef;
|
||||
private batch?: ContextSpriteBatch;
|
||||
private nameBatch?: ContextSpriteBatch;
|
||||
private frame = 0;
|
||||
private lastFrame = 0;
|
||||
private initialized = false;
|
||||
private nextBlink = performance.now() + 2000;
|
||||
private blinkFrame = -1;
|
||||
constructor(private zone: NgZone) {
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
return loadAndInitSpriteSheets()
|
||||
.then(() => this.initialized = true)
|
||||
.then(() => this.ngOnChanges());
|
||||
}
|
||||
ngOnDestroy() {
|
||||
cancelAnimationFrame(this.frame);
|
||||
}
|
||||
ngOnChanges() {
|
||||
if (!this.frame) {
|
||||
this.zone.runOutsideAngular(() => this.frame = requestAnimationFrame(this.onFrame));
|
||||
}
|
||||
}
|
||||
@HostListener('window:resize')
|
||||
redraw() {
|
||||
this.tryDraw();
|
||||
}
|
||||
blink() {
|
||||
this.nextBlink = performance.now();
|
||||
}
|
||||
private onFrame = () => {
|
||||
if (this.passive && this.initialized) {
|
||||
this.frame = 0;
|
||||
this.tryDraw();
|
||||
return;
|
||||
}
|
||||
@Input() scale = 3;
|
||||
@Input() name?: string;
|
||||
@Input() tag?: string;
|
||||
@Input() pony?: PonyInfo;
|
||||
@Input() state?: PonyState = defaultPonyState();
|
||||
@Input() noBackground = false;
|
||||
@Input() noOutline = false;
|
||||
@Input() noShadow = false;
|
||||
@Input() extra = false;
|
||||
@Input() passive = false;
|
||||
@Input() blinks = true;
|
||||
@ViewChild('canvas', { static: true }) canvas!: ElementRef;
|
||||
private batch?: ContextSpriteBatch;
|
||||
private nameBatch?: ContextSpriteBatch;
|
||||
private frame = 0;
|
||||
private lastFrame = 0;
|
||||
private initialized = false;
|
||||
private nextBlink = performance.now() + 2000;
|
||||
private blinkFrame = -1;
|
||||
constructor(private zone: NgZone) {
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
return loadAndInitSpriteSheets()
|
||||
.then(() => this.initialized = true)
|
||||
.then(() => this.ngOnChanges());
|
||||
}
|
||||
ngOnDestroy() {
|
||||
cancelAnimationFrame(this.frame);
|
||||
}
|
||||
ngOnChanges() {
|
||||
if (!this.frame) {
|
||||
this.zone.runOutsideAngular(() => this.frame = requestAnimationFrame(this.onFrame));
|
||||
}
|
||||
}
|
||||
@HostListener('window:resize')
|
||||
redraw() {
|
||||
this.tryDraw();
|
||||
}
|
||||
blink() {
|
||||
this.nextBlink = performance.now();
|
||||
}
|
||||
private onFrame = () => {
|
||||
if (this.passive && this.initialized) {
|
||||
this.frame = 0;
|
||||
this.tryDraw();
|
||||
return;
|
||||
}
|
||||
|
||||
this.frame = requestAnimationFrame(this.onFrame);
|
||||
this.frame = requestAnimationFrame(this.onFrame);
|
||||
|
||||
const now = performance.now();
|
||||
const now = performance.now();
|
||||
|
||||
if ((now - this.lastFrame) > (1000 / 24)) {
|
||||
if (this.blinks) {
|
||||
if (this.blinkFrame === -1) {
|
||||
if (this.nextBlink < now) {
|
||||
this.blinkFrame = 0;
|
||||
}
|
||||
} else {
|
||||
this.blinkFrame++;
|
||||
if ((now - this.lastFrame) > (1000 / 24)) {
|
||||
if (this.blinks) {
|
||||
if (this.blinkFrame === -1) {
|
||||
if (this.nextBlink < now) {
|
||||
this.blinkFrame = 0;
|
||||
}
|
||||
} else {
|
||||
this.blinkFrame++;
|
||||
|
||||
if (this.blinkFrame >= BLINK_FRAMES.length) {
|
||||
this.nextBlink = now + Math.random() * 2000 + 3000;
|
||||
this.blinkFrame = -1;
|
||||
}
|
||||
}
|
||||
if (this.blinkFrame >= BLINK_FRAMES.length) {
|
||||
this.nextBlink = now + Math.random() * 2000 + 3000;
|
||||
this.blinkFrame = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state) {
|
||||
this.state.blinkFrame = this.blinkFrame === -1 ? 1 : BLINK_FRAMES[this.blinkFrame];
|
||||
}
|
||||
}
|
||||
if (this.state) {
|
||||
this.state.blinkFrame = this.blinkFrame === -1 ? 1 : BLINK_FRAMES[this.blinkFrame];
|
||||
}
|
||||
}
|
||||
|
||||
this.lastFrame = now;
|
||||
this.tryDraw();
|
||||
}
|
||||
}
|
||||
private tryDraw() {
|
||||
try {
|
||||
this.draw();
|
||||
} catch { }
|
||||
}
|
||||
private draw() {
|
||||
if (!this.initialized)
|
||||
return;
|
||||
this.lastFrame = now;
|
||||
this.tryDraw();
|
||||
}
|
||||
}
|
||||
private tryDraw() {
|
||||
try {
|
||||
this.draw();
|
||||
} catch { }
|
||||
}
|
||||
private draw() {
|
||||
if (!this.initialized)
|
||||
return;
|
||||
|
||||
const canvas = this.canvas.nativeElement as HTMLCanvasElement;
|
||||
const canvas = this.canvas.nativeElement as HTMLCanvasElement;
|
||||
|
||||
const { width, height } = canvas.getBoundingClientRect();
|
||||
resizeCanvasWithRatio(canvas, width, height, false);
|
||||
const { width, height } = canvas.getBoundingClientRect();
|
||||
resizeCanvasWithRatio(canvas, width, height, false);
|
||||
|
||||
const scale = this.scale * getPixelRatio();
|
||||
const bufferWidth = Math.round(canvas.width / scale);
|
||||
const bufferHeight = Math.round(canvas.height / scale);
|
||||
const scale = this.scale * getPixelRatio();
|
||||
const bufferWidth = Math.round(canvas.width / scale);
|
||||
const bufferHeight = Math.round(canvas.height / scale);
|
||||
|
||||
if (!bufferWidth || !bufferHeight)
|
||||
return;
|
||||
if (!bufferWidth || !bufferHeight)
|
||||
return;
|
||||
|
||||
this.batch = this.batch || new ContextSpriteBatch(createCanvas(bufferWidth, bufferHeight));
|
||||
resizeCanvas(this.batch.canvas, bufferWidth, bufferHeight);
|
||||
this.batch = this.batch || new ContextSpriteBatch(createCanvas(bufferWidth, bufferHeight));
|
||||
resizeCanvas(this.batch.canvas, bufferWidth, bufferHeight);
|
||||
|
||||
const x = Math.round(bufferWidth / 2);
|
||||
const y = Math.round(bufferHeight / 2 + 28);
|
||||
const x = Math.round(bufferWidth / 2);
|
||||
const y = Math.round(bufferHeight / 2 + 28);
|
||||
|
||||
if (this.pony) {
|
||||
this.batch.start(paletteSpriteSheet, this.noBackground ? TRANSPARENT : GRASS_COLOR);
|
||||
if (this.pony) {
|
||||
this.batch.start(paletteSpriteSheet, this.noBackground ? TRANSPARENT : GRASS_COLOR);
|
||||
|
||||
try {
|
||||
const options = { ...DEFAULT_OPTIONS, shadow: !this.noShadow, extra: !!this.extra };
|
||||
drawPony(this.batch, toPalette(this.pony), this.state || DEFAULT_STATE, x, y, options);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
try {
|
||||
const options = { ...DEFAULT_OPTIONS, shadow: !this.noShadow, extra: !!this.extra };
|
||||
drawPony(this.batch, toPalette(this.pony), this.state || DEFAULT_STATE, x, y, options);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
this.batch.end();
|
||||
}
|
||||
this.batch.end();
|
||||
}
|
||||
|
||||
const viewContext = canvas.getContext('2d');
|
||||
const viewContext = canvas.getContext('2d');
|
||||
|
||||
if (!viewContext)
|
||||
return;
|
||||
if (!viewContext)
|
||||
return;
|
||||
|
||||
disableImageSmoothing(viewContext);
|
||||
disableImageSmoothing(viewContext);
|
||||
|
||||
if (this.noBackground) {
|
||||
viewContext.clearRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
if (this.noBackground) {
|
||||
viewContext.clearRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
|
||||
viewContext.save();
|
||||
viewContext.scale(scale, scale);
|
||||
viewContext.save();
|
||||
viewContext.scale(scale, scale);
|
||||
|
||||
// draw outline
|
||||
if (this.pony && this.noShadow && this.noBackground && !this.noOutline) {
|
||||
for (let x = -1; x <= 1; x++) {
|
||||
for (let y = -1; y <= 1; y++) {
|
||||
viewContext.drawImage(this.batch.canvas, x, y);
|
||||
}
|
||||
}
|
||||
// draw outline
|
||||
if (this.pony && this.noShadow && this.noBackground && !this.noOutline) {
|
||||
for (let x = -1; x <= 1; x++) {
|
||||
for (let y = -1; y <= 1; y++) {
|
||||
viewContext.drawImage(this.batch.canvas, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
viewContext.globalCompositeOperation = 'source-in';
|
||||
viewContext.fillStyle = colorToCSS(GRASS_COLOR);
|
||||
viewContext.fillRect(0, 0, viewContext.canvas.width, viewContext.canvas.height);
|
||||
viewContext.globalCompositeOperation = 'source-over';
|
||||
}
|
||||
viewContext.globalCompositeOperation = 'source-in';
|
||||
viewContext.fillStyle = colorToCSS(GRASS_COLOR);
|
||||
viewContext.fillRect(0, 0, viewContext.canvas.width, viewContext.canvas.height);
|
||||
viewContext.globalCompositeOperation = 'source-over';
|
||||
}
|
||||
|
||||
viewContext.drawImage(this.batch.canvas, 0, 0);
|
||||
viewContext.restore();
|
||||
viewContext.drawImage(this.batch.canvas, 0, 0);
|
||||
viewContext.restore();
|
||||
|
||||
// draw name plate
|
||||
if (!this.noShadow && this.name) {
|
||||
const name = replaceEmojis(this.name);
|
||||
const scale = 2 * getPixelRatio();
|
||||
const nameBufferWidth = Math.round(canvas.width / scale);
|
||||
this.nameBatch = this.nameBatch || new ContextSpriteBatch(createCanvas(nameBufferWidth, 25));
|
||||
resizeCanvas(this.nameBatch.canvas, nameBufferWidth, 25);
|
||||
this.nameBatch.start(paletteSpriteSheet, TRANSPARENT);
|
||||
drawNamePlate(this.nameBatch, name, nameBufferWidth / 2, 11, DrawNameFlags.None, commonPalettes, this.tag);
|
||||
this.nameBatch.end();
|
||||
viewContext.save();
|
||||
viewContext.scale(scale, scale);
|
||||
viewContext.drawImage(this.nameBatch.canvas, 0, 10);
|
||||
viewContext.restore();
|
||||
}
|
||||
}
|
||||
// draw name plate
|
||||
if (!this.noShadow && this.name) {
|
||||
const name = replaceEmojis(this.name);
|
||||
const scale = 2 * getPixelRatio();
|
||||
const nameBufferWidth = Math.round(canvas.width / scale);
|
||||
this.nameBatch = this.nameBatch || new ContextSpriteBatch(createCanvas(nameBufferWidth, 25));
|
||||
resizeCanvas(this.nameBatch.canvas, nameBufferWidth, 25);
|
||||
this.nameBatch.start(paletteSpriteSheet, TRANSPARENT);
|
||||
drawNamePlate(this.nameBatch, name, nameBufferWidth / 2, 11, DrawNameFlags.None, commonPalettes, this.tag);
|
||||
this.nameBatch.end();
|
||||
viewContext.save();
|
||||
viewContext.scale(scale, scale);
|
||||
viewContext.drawImage(this.nameBatch.canvas, 0, 10);
|
||||
viewContext.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,117 +12,117 @@ import { delay } from '../../../common/utils';
|
||||
import { isMobile } from '../../../client/data';
|
||||
|
||||
@Component({
|
||||
selector: 'character-select',
|
||||
templateUrl: 'character-select.pug',
|
||||
styleUrls: ['character-select.scss'],
|
||||
selector: 'character-select',
|
||||
templateUrl: 'character-select.pug',
|
||||
styleUrls: ['character-select.scss'],
|
||||
})
|
||||
export class CharacterSelect {
|
||||
readonly maxNameLength = PLAYER_NAME_MAX_LENGTH;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly deleteIcon = faTrash;
|
||||
readonly removeIcon = faTimes;
|
||||
readonly confirmIcon = faCheck;
|
||||
@Input() newButton = false;
|
||||
@Input() editButton = false;
|
||||
@Input() removeButton = false;
|
||||
@Input() error?: string;
|
||||
@Output() errorChange = new EventEmitter<string | undefined>();
|
||||
@Output() change = new EventEmitter<PonyObject>();
|
||||
@Output() preview = new EventEmitter<PonyObject | undefined>();
|
||||
@ViewChild('nameInput', { static: true }) nameInput!: ElementRef;
|
||||
@ViewChild('ariaAnnounce', { static: true }) ariaAnnounce!: ElementRef;
|
||||
@ViewChild('dropdown', { static: true }) dropdown!: Dropdown;
|
||||
removing = false;
|
||||
private locked = false; // TEMP: move to model
|
||||
constructor(
|
||||
private element: ElementRef,
|
||||
private router: Router,
|
||||
private model: Model,
|
||||
private gameService: GameService,
|
||||
) {
|
||||
}
|
||||
get joining() {
|
||||
return this.gameService.joining;
|
||||
}
|
||||
get pony() {
|
||||
return this.model.pony;
|
||||
}
|
||||
get canNew() {
|
||||
return !this.joining && this.model.account && this.model.account.characterCount < this.model.characterLimit;
|
||||
}
|
||||
get canEdit() {
|
||||
return !this.joining;
|
||||
}
|
||||
get canRemove() {
|
||||
return !this.joining && !this.locked && !this.model.pending && !!this.pony
|
||||
&& !!this.pony.id && this.error !== VERSION_ERROR;
|
||||
}
|
||||
get hasPonies() {
|
||||
return !!this.model.ponies.length;
|
||||
}
|
||||
select(pony: PonyObject) {
|
||||
if (pony) {
|
||||
this.removing = false;
|
||||
this.model.selectPony(pony);
|
||||
this.change.emit(pony);
|
||||
this.preview.emit(undefined);
|
||||
}
|
||||
readonly maxNameLength = PLAYER_NAME_MAX_LENGTH;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly deleteIcon = faTrash;
|
||||
readonly removeIcon = faTimes;
|
||||
readonly confirmIcon = faCheck;
|
||||
@Input() newButton = false;
|
||||
@Input() editButton = false;
|
||||
@Input() removeButton = false;
|
||||
@Input() error?: string;
|
||||
@Output() errorChange = new EventEmitter<string | undefined>();
|
||||
@Output() change = new EventEmitter<PonyObject>();
|
||||
@Output() preview = new EventEmitter<PonyObject | undefined>();
|
||||
@ViewChild('nameInput', { static: true }) nameInput!: ElementRef;
|
||||
@ViewChild('ariaAnnounce', { static: true }) ariaAnnounce!: ElementRef;
|
||||
@ViewChild('dropdown', { static: true }) dropdown!: Dropdown;
|
||||
removing = false;
|
||||
private locked = false; // TEMP: move to model
|
||||
constructor(
|
||||
private element: ElementRef,
|
||||
private router: Router,
|
||||
private model: Model,
|
||||
private gameService: GameService,
|
||||
) {
|
||||
}
|
||||
get joining() {
|
||||
return this.gameService.joining;
|
||||
}
|
||||
get pony() {
|
||||
return this.model.pony;
|
||||
}
|
||||
get canNew() {
|
||||
return !this.joining && this.model.account && this.model.account.characterCount < this.model.characterLimit;
|
||||
}
|
||||
get canEdit() {
|
||||
return !this.joining;
|
||||
}
|
||||
get canRemove() {
|
||||
return !this.joining && !this.locked && !this.model.pending && !!this.pony
|
||||
&& !!this.pony.id && this.error !== VERSION_ERROR;
|
||||
}
|
||||
get hasPonies() {
|
||||
return !!this.model.ponies.length;
|
||||
}
|
||||
select(pony: PonyObject) {
|
||||
if (pony) {
|
||||
this.removing = false;
|
||||
this.model.selectPony(pony);
|
||||
this.change.emit(pony);
|
||||
this.preview.emit(undefined);
|
||||
}
|
||||
|
||||
this.dropdown.close();
|
||||
this.focusName();
|
||||
}
|
||||
createNew() {
|
||||
if (this.canNew) {
|
||||
this.removing = false;
|
||||
this.model.selectPony(createDefaultPonyObject());
|
||||
this.change.emit(this.pony);
|
||||
this.router.navigate(['/character']);
|
||||
this.focusName();
|
||||
}
|
||||
}
|
||||
edit() {
|
||||
if (this.canEdit) {
|
||||
this.removing = false;
|
||||
this.router.navigate(['/character']);
|
||||
}
|
||||
}
|
||||
remove() {
|
||||
if (this.canRemove) {
|
||||
this.removing = true;
|
||||
focusElementAfterTimeout(this.element.nativeElement, '.cancel-remove-button');
|
||||
}
|
||||
}
|
||||
cancelRemove() {
|
||||
this.removing = false;
|
||||
focusElementAfterTimeout(this.element.nativeElement, '.remove-button');
|
||||
}
|
||||
confirmRemove() {
|
||||
if (this.canRemove) {
|
||||
this.setError(undefined);
|
||||
this.removing = false;
|
||||
this.locked = true;
|
||||
this.dropdown.close();
|
||||
this.focusName();
|
||||
}
|
||||
createNew() {
|
||||
if (this.canNew) {
|
||||
this.removing = false;
|
||||
this.model.selectPony(createDefaultPonyObject());
|
||||
this.change.emit(this.pony);
|
||||
this.router.navigate(['/character']);
|
||||
this.focusName();
|
||||
}
|
||||
}
|
||||
edit() {
|
||||
if (this.canEdit) {
|
||||
this.removing = false;
|
||||
this.router.navigate(['/character']);
|
||||
}
|
||||
}
|
||||
remove() {
|
||||
if (this.canRemove) {
|
||||
this.removing = true;
|
||||
focusElementAfterTimeout(this.element.nativeElement, '.cancel-remove-button');
|
||||
}
|
||||
}
|
||||
cancelRemove() {
|
||||
this.removing = false;
|
||||
focusElementAfterTimeout(this.element.nativeElement, '.remove-button');
|
||||
}
|
||||
confirmRemove() {
|
||||
if (this.canRemove) {
|
||||
this.setError(undefined);
|
||||
this.removing = false;
|
||||
this.locked = true;
|
||||
|
||||
this.model.removePony(this.pony)
|
||||
.then(() => this.change.emit(this.pony))
|
||||
.catch((e: Error) => this.setError(e.message))
|
||||
.then(() => this.ariaAnnounce.nativeElement.textContent = 'Character removed')
|
||||
.then(() => delay(2000))
|
||||
.then(() => this.locked = false)
|
||||
.then(() => this.focusName());
|
||||
}
|
||||
}
|
||||
onToggle(show: boolean) {
|
||||
if (!show) {
|
||||
this.preview.emit(undefined);
|
||||
}
|
||||
}
|
||||
private focusName() {
|
||||
if (!isMobile) {
|
||||
this.nameInput.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
private setError(error: string | undefined) {
|
||||
this.error = error;
|
||||
this.errorChange.emit(error);
|
||||
}
|
||||
this.model.removePony(this.pony)
|
||||
.then(() => this.change.emit(this.pony))
|
||||
.catch((e: Error) => this.setError(e.message))
|
||||
.then(() => this.ariaAnnounce.nativeElement.textContent = 'Character removed')
|
||||
.then(() => delay(2000))
|
||||
.then(() => this.locked = false)
|
||||
.then(() => this.focusName());
|
||||
}
|
||||
}
|
||||
onToggle(show: boolean) {
|
||||
if (!show) {
|
||||
this.preview.emit(undefined);
|
||||
}
|
||||
}
|
||||
private focusName() {
|
||||
if (!isMobile) {
|
||||
this.nameInput.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
private setError(error: string | undefined) {
|
||||
this.error = error;
|
||||
this.errorChange.emit(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ const chatTypeNames: string[] = [];
|
||||
const chatTypeClasses: string[] = [];
|
||||
|
||||
function setupChatType(type: ChatType, name: string) {
|
||||
chatTypeNames[type] = name;
|
||||
chatTypeClasses[type] = `chat-${name.replace(/ /, '-')}`;
|
||||
chatTypeNames[type] = name;
|
||||
chatTypeClasses[type] = `chat-${name.replace(/ /, '-')}`;
|
||||
}
|
||||
|
||||
setupChatType(ChatType.Say, 'say');
|
||||
@@ -33,356 +33,356 @@ setupChatType(ChatType.Think, 'think');
|
||||
setupChatType(ChatType.PartyThink, 'party think');
|
||||
|
||||
function isActionCommand(message: string) {
|
||||
return /^\/(yawn|sneeze|achoo|laugh|lol|haha|хаха|jaja)/i.test(message);
|
||||
return /^\/(yawn|sneeze|achoo|laugh|lol|haha|хаха|jaja)/i.test(message);
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'chat-box',
|
||||
templateUrl: 'chat-box.pug',
|
||||
styleUrls: ['chat-box.scss'],
|
||||
selector: 'chat-box',
|
||||
templateUrl: 'chat-box.pug',
|
||||
styleUrls: ['chat-box.scss'],
|
||||
})
|
||||
export class ChatBox implements AfterViewInit, OnDestroy {
|
||||
readonly maxSayLength = SAY_MAX_LENGTH;
|
||||
readonly commentIcon = faComment;
|
||||
readonly sendIcon = faAngleDoubleRight;
|
||||
@ViewChild('inputElement', { static: true }) inputElement!: ElementRef;
|
||||
@ViewChild('typeBox', { static: true }) typeBox!: ElementRef;
|
||||
@ViewChild('typePrefix', { static: true }) typePrefix!: ElementRef;
|
||||
@ViewChild('typeName', { static: true }) typeName!: ElementRef;
|
||||
@ViewChild('chatBox', { static: true }) chatBox!: ElementRef;
|
||||
@ViewChild('chatBoxInput', { static: true }) chatBoxInput!: ElementRef;
|
||||
isOpen = false;
|
||||
message: string | undefined = '';
|
||||
chatType = ChatType.Say;
|
||||
private pasted = false;
|
||||
private lastMessages: string[] = [];
|
||||
private state: AutocompleteState = {};
|
||||
private subscriptions: Subscription[];
|
||||
private _disabled = false;
|
||||
constructor(private game: PonyTownGame, zone: NgZone) {
|
||||
this.subscriptions = [
|
||||
this.game.onChat.subscribe(() => zone.run(() => this.chat(undefined))),
|
||||
this.game.onToggleChat.subscribe(() => zone.run(() => this.toggle())),
|
||||
this.game.onCommand.subscribe(() => zone.run(() => this.command())),
|
||||
this.game.onLeft.subscribe(() => {
|
||||
this.chatType = ChatType.Say;
|
||||
this.close();
|
||||
}),
|
||||
];
|
||||
readonly maxSayLength = SAY_MAX_LENGTH;
|
||||
readonly commentIcon = faComment;
|
||||
readonly sendIcon = faAngleDoubleRight;
|
||||
@ViewChild('inputElement', { static: true }) inputElement!: ElementRef;
|
||||
@ViewChild('typeBox', { static: true }) typeBox!: ElementRef;
|
||||
@ViewChild('typePrefix', { static: true }) typePrefix!: ElementRef;
|
||||
@ViewChild('typeName', { static: true }) typeName!: ElementRef;
|
||||
@ViewChild('chatBox', { static: true }) chatBox!: ElementRef;
|
||||
@ViewChild('chatBoxInput', { static: true }) chatBoxInput!: ElementRef;
|
||||
isOpen = false;
|
||||
message: string | undefined = '';
|
||||
chatType = ChatType.Say;
|
||||
private pasted = false;
|
||||
private lastMessages: string[] = [];
|
||||
private state: AutocompleteState = {};
|
||||
private subscriptions: Subscription[];
|
||||
private _disabled = false;
|
||||
constructor(private game: PonyTownGame, zone: NgZone) {
|
||||
this.subscriptions = [
|
||||
this.game.onChat.subscribe(() => zone.run(() => this.chat(undefined))),
|
||||
this.game.onToggleChat.subscribe(() => zone.run(() => this.toggle())),
|
||||
this.game.onCommand.subscribe(() => zone.run(() => this.command())),
|
||||
this.game.onLeft.subscribe(() => {
|
||||
this.chatType = ChatType.Say;
|
||||
this.close();
|
||||
}),
|
||||
];
|
||||
|
||||
this.game.onCancel = () => this.isOpen ? (zone.run(() => this.close()), true) : false;
|
||||
}
|
||||
@Input() get disabled() {
|
||||
return this._disabled;
|
||||
}
|
||||
set disabled(value) {
|
||||
this._disabled = value;
|
||||
this.game.onCancel = () => this.isOpen ? (zone.run(() => this.close()), true) : false;
|
||||
}
|
||||
@Input() get disabled() {
|
||||
return this._disabled;
|
||||
}
|
||||
set disabled(value) {
|
||||
this._disabled = value;
|
||||
|
||||
if (value) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
get input() {
|
||||
return this.inputElement.nativeElement as HTMLInputElement;
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
this.chatBox.nativeElement.hidden = true;
|
||||
this.input.addEventListener('paste', () => this.pasted = true);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(s => s.unsubscribe());
|
||||
}
|
||||
send(_event: Event | undefined) {
|
||||
let chatType = this.chatType;
|
||||
let message = replaceEmojis(cleanMessage(this.message || '')).substr(0, SAY_MAX_LENGTH);
|
||||
const handled = handleActionCommand(message, this.game);
|
||||
const spam = this.pasted && chatType !== ChatType.Party && isSpamMessage(message, this.lastMessages);
|
||||
const empty = !this.game.player || !message;
|
||||
const ignoreAction = isActionCommand(message) && this.game.player && hasHeadAnimation(this.game.player);
|
||||
const whisperTo = this.game.whisperTo;
|
||||
let entityId = whisperTo && whisperTo.id || 0;
|
||||
if (value) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
get input() {
|
||||
return this.inputElement.nativeElement as HTMLInputElement;
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
this.chatBox.nativeElement.hidden = true;
|
||||
this.input.addEventListener('paste', () => this.pasted = true);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(s => s.unsubscribe());
|
||||
}
|
||||
send(_event: Event | undefined) {
|
||||
let chatType = this.chatType;
|
||||
let message = replaceEmojis(cleanMessage(this.message || '')).substr(0, SAY_MAX_LENGTH);
|
||||
const handled = handleActionCommand(message, this.game);
|
||||
const spam = this.pasted && chatType !== ChatType.Party && isSpamMessage(message, this.lastMessages);
|
||||
const empty = !this.game.player || !message;
|
||||
const ignoreAction = isActionCommand(message) && this.game.player && hasHeadAnimation(this.game.player);
|
||||
const whisperTo = this.game.whisperTo;
|
||||
let entityId = whisperTo && whisperTo.id || 0;
|
||||
|
||||
if (/^\/(w|whisper) .+$/i.test(message)) {
|
||||
chatType = ChatType.Whisper;
|
||||
message = message.substr(/^\/w /i.test(message) ? 3 : 9);
|
||||
if (/^\/(w|whisper) .+$/i.test(message)) {
|
||||
chatType = ChatType.Whisper;
|
||||
message = message.substr(/^\/w /i.test(message) ? 3 : 9);
|
||||
|
||||
let offset = 0;
|
||||
let entity: Entity | FakeEntity | undefined = undefined;
|
||||
let offset = 0;
|
||||
let entity: Entity | FakeEntity | undefined = undefined;
|
||||
|
||||
do {
|
||||
offset = message.indexOf(' ', offset);
|
||||
do {
|
||||
offset = message.indexOf(' ', offset);
|
||||
|
||||
if (offset === -1)
|
||||
break;
|
||||
if (offset === -1)
|
||||
break;
|
||||
|
||||
const name = message.substr(0, offset);
|
||||
entity = findBestEntityByName(this.game, name);
|
||||
offset++;
|
||||
} while (!entity);
|
||||
const name = message.substr(0, offset);
|
||||
entity = findBestEntityByName(this.game, name);
|
||||
offset++;
|
||||
} while (!entity);
|
||||
|
||||
if (entity) {
|
||||
message = message.substr(offset);
|
||||
entityId = entity.id;
|
||||
} else {
|
||||
entityId = 0;
|
||||
}
|
||||
}
|
||||
if (entity) {
|
||||
message = message.substr(offset);
|
||||
entityId = entity.id;
|
||||
} else {
|
||||
entityId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (handled || spam || empty || ignoreAction || this.say(message, chatType, entityId)) {
|
||||
if (message) {
|
||||
this.lastMessages.push(message);
|
||||
if (handled || spam || empty || ignoreAction || this.say(message, chatType, entityId)) {
|
||||
if (message) {
|
||||
this.lastMessages.push(message);
|
||||
|
||||
while (this.lastMessages.length > 5) {
|
||||
this.lastMessages.shift();
|
||||
}
|
||||
}
|
||||
while (this.lastMessages.length > 5) {
|
||||
this.lastMessages.shift();
|
||||
}
|
||||
}
|
||||
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
keydown(e: KeyboardEvent) {
|
||||
if (e.keyCode !== Key.TAB && e.keyCode !== Key.SHIFT) {
|
||||
this.state.lastEmoji = undefined;
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
keydown(e: KeyboardEvent) {
|
||||
if (e.keyCode !== Key.TAB && e.keyCode !== Key.SHIFT) {
|
||||
this.state.lastEmoji = undefined;
|
||||
}
|
||||
|
||||
if (e.keyCode === Key.TAB) {
|
||||
if (this.message) {
|
||||
if (/^\/(w|whisper) .+$/i.test(this.message)) {
|
||||
const space = this.message.indexOf(' ');
|
||||
const names = findMatchingEntityNames(this.game, this.message.substr(space + 1));
|
||||
if (e.keyCode === Key.TAB) {
|
||||
if (this.message) {
|
||||
if (/^\/(w|whisper) .+$/i.test(this.message)) {
|
||||
const space = this.message.indexOf(' ');
|
||||
const names = findMatchingEntityNames(this.game, this.message.substr(space + 1));
|
||||
|
||||
if (names.length === 1) {
|
||||
this.message = `${this.message.substring(0, space)} ${names[0]}`;
|
||||
}
|
||||
} else {
|
||||
this.message = autocompleteMesssage(this.message, e.shiftKey, this.state);
|
||||
}
|
||||
}
|
||||
if (names.length === 1) {
|
||||
this.message = `${this.message.substring(0, space)} ${names[0]}`;
|
||||
}
|
||||
} else {
|
||||
this.message = autocompleteMesssage(this.message, e.shiftKey, this.state);
|
||||
}
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === Key.ENTER && this.isOpen) {
|
||||
this.send(e);
|
||||
} else if (e.keyCode === Key.ESCAPE) {
|
||||
this.close();
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === Key.SPACE) {
|
||||
if (!this.message)
|
||||
return;
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === Key.ENTER && this.isOpen) {
|
||||
this.send(e);
|
||||
} else if (e.keyCode === Key.ESCAPE) {
|
||||
this.close();
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === Key.SPACE) {
|
||||
if (!this.message)
|
||||
return;
|
||||
|
||||
const isParty = /^\/(p|party)$/i.test(this.message);
|
||||
const isSay = /^\/(s|say)$/i.test(this.message);
|
||||
const isSup = /^\/(ss)$/i.test(this.message);
|
||||
const isSup1 = /^\/(s1)$/i.test(this.message);
|
||||
const isSup2 = /^\/(s2)$/i.test(this.message);
|
||||
const isSup3 = /^\/(s3)$/i.test(this.message);
|
||||
const isParty = /^\/(p|party)$/i.test(this.message);
|
||||
const isSay = /^\/(s|say)$/i.test(this.message);
|
||||
const isSup = /^\/(ss)$/i.test(this.message);
|
||||
const isSup1 = /^\/(s1)$/i.test(this.message);
|
||||
const isSup2 = /^\/(s2)$/i.test(this.message);
|
||||
const isSup3 = /^\/(s3)$/i.test(this.message);
|
||||
|
||||
const supporter = this.game.model.supporter;
|
||||
const isSayOrInvalid = isSay
|
||||
|| (isParty && !isInParty(this.game))
|
||||
|| (isSup && supporter === 0)
|
||||
|| (isSup1 && supporter < 1)
|
||||
|| (isSup2 && supporter < 2)
|
||||
|| (isSup3 && supporter < 3);
|
||||
const supporter = this.game.model.supporter;
|
||||
const isSayOrInvalid = isSay
|
||||
|| (isParty && !isInParty(this.game))
|
||||
|| (isSup && supporter === 0)
|
||||
|| (isSup1 && supporter < 1)
|
||||
|| (isSup2 && supporter < 2)
|
||||
|| (isSup3 && supporter < 3);
|
||||
|
||||
if (isSayOrInvalid) {
|
||||
this.changeChatType(e, ChatType.Say);
|
||||
} else if (isParty) {
|
||||
this.changeChatType(e, ChatType.Party);
|
||||
} else if (isSup) {
|
||||
this.changeChatType(e, ChatType.Supporter);
|
||||
} else if (isSup1) {
|
||||
this.changeChatType(e, ChatType.Supporter1);
|
||||
} else if (isSup2) {
|
||||
this.changeChatType(e, ChatType.Supporter2);
|
||||
} else if (isSup3) {
|
||||
this.changeChatType(e, ChatType.Supporter3);
|
||||
} else if (/^\/(t|think)$/i.test(this.message)) {
|
||||
if (isPartyChat(this.chatType)) {
|
||||
this.changeChatType(e, ChatType.PartyThink);
|
||||
} else {
|
||||
this.changeChatType(e, ChatType.Think);
|
||||
}
|
||||
} else if (/^\/(r|reply)$/i.test(this.message)) {
|
||||
const lastWhisperFrom = this.game.lastWhisperFrom;
|
||||
const entity = lastWhisperFrom && findEntityOrMockByAnyMeans(this.game, lastWhisperFrom.entityId);
|
||||
if (isSayOrInvalid) {
|
||||
this.changeChatType(e, ChatType.Say);
|
||||
} else if (isParty) {
|
||||
this.changeChatType(e, ChatType.Party);
|
||||
} else if (isSup) {
|
||||
this.changeChatType(e, ChatType.Supporter);
|
||||
} else if (isSup1) {
|
||||
this.changeChatType(e, ChatType.Supporter1);
|
||||
} else if (isSup2) {
|
||||
this.changeChatType(e, ChatType.Supporter2);
|
||||
} else if (isSup3) {
|
||||
this.changeChatType(e, ChatType.Supporter3);
|
||||
} else if (/^\/(t|think)$/i.test(this.message)) {
|
||||
if (isPartyChat(this.chatType)) {
|
||||
this.changeChatType(e, ChatType.PartyThink);
|
||||
} else {
|
||||
this.changeChatType(e, ChatType.Think);
|
||||
}
|
||||
} else if (/^\/(r|reply)$/i.test(this.message)) {
|
||||
const lastWhisperFrom = this.game.lastWhisperFrom;
|
||||
const entity = lastWhisperFrom && findEntityOrMockByAnyMeans(this.game, lastWhisperFrom.entityId);
|
||||
|
||||
if (entity) {
|
||||
this.game.whisperTo = entity;
|
||||
this.changeChatType(e, ChatType.Whisper);
|
||||
} else {
|
||||
this.changeChatType(e, ChatType.Say);
|
||||
}
|
||||
} else if (/^\/(w|whisper) .+$/i.test(this.message) && !e.shiftKey) {
|
||||
const name = this.message.substr(/^\/w /i.test(this.message) ? 3 : 9);
|
||||
const entity = findBestEntityByName(this.game, name);
|
||||
if (entity) {
|
||||
this.game.whisperTo = entity;
|
||||
this.changeChatType(e, ChatType.Whisper);
|
||||
} else {
|
||||
this.changeChatType(e, ChatType.Say);
|
||||
}
|
||||
} else if (/^\/(w|whisper) .+$/i.test(this.message) && !e.shiftKey) {
|
||||
const name = this.message.substr(/^\/w /i.test(this.message) ? 3 : 9);
|
||||
const entity = findBestEntityByName(this.game, name);
|
||||
|
||||
if (entity) {
|
||||
this.game.whisperTo = entity;
|
||||
this.changeChatType(e, ChatType.Whisper);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private say(message: string, chatType: ChatType, entityId: number): boolean {
|
||||
this.game.lastChatMessageType = chatType;
|
||||
return !!this.game.send(server => server.say(entityId, message, chatType));
|
||||
}
|
||||
private changeChatType(e: KeyboardEvent, chatType: ChatType) {
|
||||
this.chatType = chatType;
|
||||
this.message = '';
|
||||
this.updateChatType();
|
||||
e.preventDefault();
|
||||
}
|
||||
private chat(event: Event | undefined) {
|
||||
if (this.isOpen) {
|
||||
this.send(event);
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
private command() {
|
||||
if (!this.isOpen) {
|
||||
this.chat(undefined);
|
||||
this.message = '/';
|
||||
this.input.selectionStart = this.input.selectionEnd = 10000;
|
||||
}
|
||||
}
|
||||
private open() {
|
||||
if (!this.isOpen) {
|
||||
this.isOpen = true;
|
||||
this.chatBox.nativeElement.hidden = false;
|
||||
}
|
||||
if (entity) {
|
||||
this.game.whisperTo = entity;
|
||||
this.changeChatType(e, ChatType.Whisper);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private say(message: string, chatType: ChatType, entityId: number): boolean {
|
||||
this.game.lastChatMessageType = chatType;
|
||||
return !!this.game.send(server => server.say(entityId, message, chatType));
|
||||
}
|
||||
private changeChatType(e: KeyboardEvent, chatType: ChatType) {
|
||||
this.chatType = chatType;
|
||||
this.message = '';
|
||||
this.updateChatType();
|
||||
e.preventDefault();
|
||||
}
|
||||
private chat(event: Event | undefined) {
|
||||
if (this.isOpen) {
|
||||
this.send(event);
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
private command() {
|
||||
if (!this.isOpen) {
|
||||
this.chat(undefined);
|
||||
this.message = '/';
|
||||
this.input.selectionStart = this.input.selectionEnd = 10000;
|
||||
}
|
||||
}
|
||||
private open() {
|
||||
if (!this.isOpen) {
|
||||
this.isOpen = true;
|
||||
this.chatBox.nativeElement.hidden = false;
|
||||
}
|
||||
|
||||
this.chatType = isValidChatType(this.chatType, this.game) ? this.chatType : ChatType.Say;
|
||||
this.updateChatType();
|
||||
this.input.focus();
|
||||
}
|
||||
private close() {
|
||||
if (this.isOpen) {
|
||||
this.input.blur();
|
||||
this.isOpen = false;
|
||||
this.chatBox.nativeElement.hidden = true;
|
||||
this.message = '';
|
||||
this.pasted = false;
|
||||
}
|
||||
}
|
||||
toggle() {
|
||||
if (this.isOpen) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
toggleChatType() {
|
||||
const chatTypes = getChatTypes(this.game);
|
||||
this.chatType = chatTypes[(chatTypes.indexOf(this.chatType) + 1) % chatTypes.length];
|
||||
this.updateChatType();
|
||||
this.input.focus();
|
||||
}
|
||||
setChatType(type: 'say' | 'party' | 'whisper') {
|
||||
if (type === 'say') {
|
||||
this.chatType = ChatType.Say;
|
||||
this.open();
|
||||
} else if (type === 'party' && isInParty(this.game)) {
|
||||
this.chatType = ChatType.Party;
|
||||
this.open();
|
||||
} else if (type === 'whisper') {
|
||||
this.chatType = ChatType.Whisper;
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
private currentTypeClass = '';
|
||||
private currentTypePrefix = '';
|
||||
private currentTypeName = '';
|
||||
private updateChatType() {
|
||||
let typeName: string;
|
||||
let typePrefix: string;
|
||||
let changed = false;
|
||||
this.chatType = isValidChatType(this.chatType, this.game) ? this.chatType : ChatType.Say;
|
||||
this.updateChatType();
|
||||
this.input.focus();
|
||||
}
|
||||
private close() {
|
||||
if (this.isOpen) {
|
||||
this.input.blur();
|
||||
this.isOpen = false;
|
||||
this.chatBox.nativeElement.hidden = true;
|
||||
this.message = '';
|
||||
this.pasted = false;
|
||||
}
|
||||
}
|
||||
toggle() {
|
||||
if (this.isOpen) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
toggleChatType() {
|
||||
const chatTypes = getChatTypes(this.game);
|
||||
this.chatType = chatTypes[(chatTypes.indexOf(this.chatType) + 1) % chatTypes.length];
|
||||
this.updateChatType();
|
||||
this.input.focus();
|
||||
}
|
||||
setChatType(type: 'say' | 'party' | 'whisper') {
|
||||
if (type === 'say') {
|
||||
this.chatType = ChatType.Say;
|
||||
this.open();
|
||||
} else if (type === 'party' && isInParty(this.game)) {
|
||||
this.chatType = ChatType.Party;
|
||||
this.open();
|
||||
} else if (type === 'whisper') {
|
||||
this.chatType = ChatType.Whisper;
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
private currentTypeClass = '';
|
||||
private currentTypePrefix = '';
|
||||
private currentTypeName = '';
|
||||
private updateChatType() {
|
||||
let typeName: string;
|
||||
let typePrefix: string;
|
||||
let changed = false;
|
||||
|
||||
const typeClass = chatTypeClass(this.chatType, this.game.model.supporter);
|
||||
const typeClass = chatTypeClass(this.chatType, this.game.model.supporter);
|
||||
|
||||
if (this.currentTypeClass !== typeClass) {
|
||||
this.currentTypeClass = typeClass;
|
||||
(this.chatBoxInput.nativeElement as HTMLElement).className = typeClass;
|
||||
}
|
||||
if (this.currentTypeClass !== typeClass) {
|
||||
this.currentTypeClass = typeClass;
|
||||
(this.chatBoxInput.nativeElement as HTMLElement).className = typeClass;
|
||||
}
|
||||
|
||||
if (this.chatType === ChatType.Whisper) {
|
||||
typePrefix = 'To ';
|
||||
typeName = this.game.whisperTo && this.game.whisperTo.name || 'unknown';
|
||||
} else {
|
||||
typePrefix = '';
|
||||
typeName = chatTypeNames[this.chatType];
|
||||
}
|
||||
if (this.chatType === ChatType.Whisper) {
|
||||
typePrefix = 'To ';
|
||||
typeName = this.game.whisperTo && this.game.whisperTo.name || 'unknown';
|
||||
} else {
|
||||
typePrefix = '';
|
||||
typeName = chatTypeNames[this.chatType];
|
||||
}
|
||||
|
||||
if (this.currentTypePrefix !== typePrefix) {
|
||||
changed = true;
|
||||
this.currentTypePrefix = typePrefix;
|
||||
(this.typePrefix.nativeElement as HTMLElement).textContent = typePrefix;
|
||||
}
|
||||
if (this.currentTypePrefix !== typePrefix) {
|
||||
changed = true;
|
||||
this.currentTypePrefix = typePrefix;
|
||||
(this.typePrefix.nativeElement as HTMLElement).textContent = typePrefix;
|
||||
}
|
||||
|
||||
if (this.currentTypeName !== typeName) {
|
||||
changed = true;
|
||||
this.currentTypeName = typeName;
|
||||
replaceNodes(this.typeName.nativeElement, typeName);
|
||||
}
|
||||
if (this.currentTypeName !== typeName) {
|
||||
changed = true;
|
||||
this.currentTypeName = typeName;
|
||||
replaceNodes(this.typeName.nativeElement, typeName);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
const { width } = (this.typeBox.nativeElement as HTMLElement).getBoundingClientRect();
|
||||
const padding = 35 + 13 + Math.ceil(width);
|
||||
(this.inputElement.nativeElement as HTMLElement).style.paddingLeft = `${padding}px`;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
const { width } = (this.typeBox.nativeElement as HTMLElement).getBoundingClientRect();
|
||||
const padding = 35 + 13 + Math.ceil(width);
|
||||
(this.inputElement.nativeElement as HTMLElement).style.paddingLeft = `${padding}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function chatTypeClass(chatType: ChatType, supporter: number) {
|
||||
if (chatType === ChatType.Supporter) {
|
||||
switch (supporter) {
|
||||
case 1: return 'chat-sup chat-sup1';
|
||||
case 2: return 'chat-sup chat-sup2';
|
||||
case 3: return 'chat-sup chat-sup3';
|
||||
}
|
||||
}
|
||||
if (chatType === ChatType.Supporter) {
|
||||
switch (supporter) {
|
||||
case 1: return 'chat-sup chat-sup1';
|
||||
case 2: return 'chat-sup chat-sup2';
|
||||
case 3: return 'chat-sup chat-sup3';
|
||||
}
|
||||
}
|
||||
|
||||
return chatTypeClasses[chatType];
|
||||
return chatTypeClasses[chatType];
|
||||
}
|
||||
|
||||
function isValidChatType(type: ChatType, game: PonyTownGame) {
|
||||
const supporter = game.model.supporter;
|
||||
const supporter = game.model.supporter;
|
||||
|
||||
switch (type) {
|
||||
case ChatType.Say:
|
||||
case ChatType.Think:
|
||||
case ChatType.Whisper:
|
||||
return true;
|
||||
case ChatType.Party:
|
||||
case ChatType.PartyThink:
|
||||
return isInParty(game);
|
||||
case ChatType.Supporter:
|
||||
return supporter > 0;
|
||||
case ChatType.Supporter1:
|
||||
return supporter >= 1;
|
||||
case ChatType.Supporter2:
|
||||
return supporter >= 2;
|
||||
case ChatType.Supporter3:
|
||||
return supporter >= 3;
|
||||
case ChatType.Dismiss:
|
||||
return false;
|
||||
default:
|
||||
return invalidEnumReturn(type, false);
|
||||
}
|
||||
switch (type) {
|
||||
case ChatType.Say:
|
||||
case ChatType.Think:
|
||||
case ChatType.Whisper:
|
||||
return true;
|
||||
case ChatType.Party:
|
||||
case ChatType.PartyThink:
|
||||
return isInParty(game);
|
||||
case ChatType.Supporter:
|
||||
return supporter > 0;
|
||||
case ChatType.Supporter1:
|
||||
return supporter >= 1;
|
||||
case ChatType.Supporter2:
|
||||
return supporter >= 2;
|
||||
case ChatType.Supporter3:
|
||||
return supporter >= 3;
|
||||
case ChatType.Dismiss:
|
||||
return false;
|
||||
default:
|
||||
return invalidEnumReturn(type, false);
|
||||
}
|
||||
}
|
||||
|
||||
function getChatTypes(game: PonyTownGame) {
|
||||
const chatTypes = [ChatType.Say];
|
||||
const supporter = game.model.supporter;
|
||||
const chatTypes = [ChatType.Say];
|
||||
const supporter = game.model.supporter;
|
||||
|
||||
if (isInParty(game)) {
|
||||
chatTypes.push(ChatType.Party);
|
||||
}
|
||||
if (isInParty(game)) {
|
||||
chatTypes.push(ChatType.Party);
|
||||
}
|
||||
|
||||
if (supporter) {
|
||||
chatTypes.push(ChatType.Supporter);
|
||||
}
|
||||
if (supporter) {
|
||||
chatTypes.push(ChatType.Supporter);
|
||||
}
|
||||
|
||||
return chatTypes;
|
||||
return chatTypes;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,21 +2,21 @@ import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from
|
||||
import { faCheck } from '../../../client/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'check-box',
|
||||
templateUrl: 'check-box.pug',
|
||||
styleUrls: ['check-box.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'check-box',
|
||||
templateUrl: 'check-box.pug',
|
||||
styleUrls: ['check-box.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckBox {
|
||||
@Input() icon = faCheck;
|
||||
@Input() label?: string;
|
||||
@Input() disabled = false;
|
||||
@Input() checked = false;
|
||||
@Output() checkedChange = new EventEmitter<boolean>();
|
||||
toggle() {
|
||||
if (!this.disabled) {
|
||||
this.checked = !this.checked;
|
||||
this.checkedChange.emit(this.checked);
|
||||
}
|
||||
}
|
||||
@Input() icon = faCheck;
|
||||
@Input() label?: string;
|
||||
@Input() disabled = false;
|
||||
@Input() checked = false;
|
||||
@Output() checkedChange = new EventEmitter<boolean>();
|
||||
toggle() {
|
||||
if (!this.disabled) {
|
||||
this.checked = !this.checked;
|
||||
this.checkedChange.emit(this.checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,119 +7,119 @@ import { faChevronDown } from '../../../client/icons';
|
||||
const SIZE = 175;
|
||||
|
||||
@Component({
|
||||
selector: 'color-picker',
|
||||
templateUrl: 'color-picker.pug',
|
||||
styleUrls: ['color-picker.scss'],
|
||||
selector: 'color-picker',
|
||||
templateUrl: 'color-picker.pug',
|
||||
styleUrls: ['color-picker.scss'],
|
||||
})
|
||||
export class ColorPicker {
|
||||
readonly chevronIcon = faChevronDown;
|
||||
@Input() isOpen = false;
|
||||
@Input() isDisabled = false;
|
||||
@Input() disabledColor = '';
|
||||
@Input() color = '';
|
||||
@Input() indicatorColor = '';
|
||||
@Input() label?: string = undefined;
|
||||
@Input() labelledBy?: string = undefined;
|
||||
@Output() colorChange = new EventEmitter<string>();
|
||||
s = 0;
|
||||
v = 0;
|
||||
h = 0;
|
||||
private lastColor = '';
|
||||
private closeHandler = () => this.close();
|
||||
get inputColor() {
|
||||
return this.isDisabled && this.disabledColor ? this.disabledColor : this.color;
|
||||
}
|
||||
set inputColor(value) {
|
||||
if (!this.isDisabled) {
|
||||
this.color = value;
|
||||
}
|
||||
}
|
||||
get bg() {
|
||||
return colorToCSS(parseColorFast(this.inputColor));
|
||||
}
|
||||
get svLeft() {
|
||||
this.updateHsv();
|
||||
return this.s * 100;
|
||||
}
|
||||
get svTop() {
|
||||
this.updateHsv();
|
||||
return (1 - this.v) * 100;
|
||||
}
|
||||
get hueTop() {
|
||||
this.updateHsv();
|
||||
return this.h * 100 / 360;
|
||||
}
|
||||
get hue() {
|
||||
this.updateHsv();
|
||||
return colorToCSS(colorFromHSVA(this.h, 1, 1, 1));
|
||||
}
|
||||
focus(e: Event) {
|
||||
this.isOpen = true;
|
||||
(e.target as HTMLInputElement).select();
|
||||
}
|
||||
dragSV({ event, x, y }: AgDragEvent) {
|
||||
event.preventDefault();
|
||||
readonly chevronIcon = faChevronDown;
|
||||
@Input() isOpen = false;
|
||||
@Input() isDisabled = false;
|
||||
@Input() disabledColor = '';
|
||||
@Input() color = '';
|
||||
@Input() indicatorColor = '';
|
||||
@Input() label?: string = undefined;
|
||||
@Input() labelledBy?: string = undefined;
|
||||
@Output() colorChange = new EventEmitter<string>();
|
||||
s = 0;
|
||||
v = 0;
|
||||
h = 0;
|
||||
private lastColor = '';
|
||||
private closeHandler = () => this.close();
|
||||
get inputColor() {
|
||||
return this.isDisabled && this.disabledColor ? this.disabledColor : this.color;
|
||||
}
|
||||
set inputColor(value) {
|
||||
if (!this.isDisabled) {
|
||||
this.color = value;
|
||||
}
|
||||
}
|
||||
get bg() {
|
||||
return colorToCSS(parseColorFast(this.inputColor));
|
||||
}
|
||||
get svLeft() {
|
||||
this.updateHsv();
|
||||
return this.s * 100;
|
||||
}
|
||||
get svTop() {
|
||||
this.updateHsv();
|
||||
return (1 - this.v) * 100;
|
||||
}
|
||||
get hueTop() {
|
||||
this.updateHsv();
|
||||
return this.h * 100 / 360;
|
||||
}
|
||||
get hue() {
|
||||
this.updateHsv();
|
||||
return colorToCSS(colorFromHSVA(this.h, 1, 1, 1));
|
||||
}
|
||||
focus(e: Event) {
|
||||
this.isOpen = true;
|
||||
(e.target as HTMLInputElement).select();
|
||||
}
|
||||
dragSV({ event, x, y }: AgDragEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
this.updateHsv();
|
||||
this.s = clamp(x / SIZE, 0, 1);
|
||||
this.v = 1 - clamp(y / SIZE, 0, 1);
|
||||
this.updateColor();
|
||||
}
|
||||
dragHue({ event, y }: AgDragEvent) {
|
||||
event.preventDefault();
|
||||
this.updateHsv();
|
||||
this.s = clamp(x / SIZE, 0, 1);
|
||||
this.v = 1 - clamp(y / SIZE, 0, 1);
|
||||
this.updateColor();
|
||||
}
|
||||
dragHue({ event, y }: AgDragEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
this.updateHsv();
|
||||
this.h = clamp(360 * y / SIZE, 0, 360);
|
||||
this.updateColor();
|
||||
}
|
||||
updateHsv() {
|
||||
if (this.lastColor !== this.color) {
|
||||
const { h, s, v } = colorToHSVA(parseColorFast(this.color), this.h);
|
||||
this.h = h;
|
||||
this.s = s;
|
||||
this.v = v;
|
||||
this.lastColor = this.color;
|
||||
}
|
||||
}
|
||||
updateColor() {
|
||||
const color = colorToHexRGB(colorFromHSVA(this.h, this.s, this.v, 1));
|
||||
const changed = this.color !== color;
|
||||
this.lastColor = this.color = color;
|
||||
this.updateHsv();
|
||||
this.h = clamp(360 * y / SIZE, 0, 360);
|
||||
this.updateColor();
|
||||
}
|
||||
updateHsv() {
|
||||
if (this.lastColor !== this.color) {
|
||||
const { h, s, v } = colorToHSVA(parseColorFast(this.color), this.h);
|
||||
this.h = h;
|
||||
this.s = s;
|
||||
this.v = v;
|
||||
this.lastColor = this.color;
|
||||
}
|
||||
}
|
||||
updateColor() {
|
||||
const color = colorToHexRGB(colorFromHSVA(this.h, this.s, this.v, 1));
|
||||
const changed = this.color !== color;
|
||||
this.lastColor = this.color = color;
|
||||
|
||||
if (changed) {
|
||||
this.colorChange.emit(color);
|
||||
}
|
||||
}
|
||||
inputChanged(value: string) {
|
||||
this.color = value;
|
||||
this.colorChange.emit(this.color);
|
||||
}
|
||||
stopEvent(e: Event) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
open() {
|
||||
if (!this.isOpen) {
|
||||
this.isOpen = true;
|
||||
if (changed) {
|
||||
this.colorChange.emit(color);
|
||||
}
|
||||
}
|
||||
inputChanged(value: string) {
|
||||
this.color = value;
|
||||
this.colorChange.emit(this.color);
|
||||
}
|
||||
stopEvent(e: Event) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
open() {
|
||||
if (!this.isOpen) {
|
||||
this.isOpen = true;
|
||||
|
||||
setTimeout(() => {
|
||||
document.addEventListener('mousedown', this.closeHandler);
|
||||
document.addEventListener('touchstart', this.closeHandler);
|
||||
});
|
||||
}
|
||||
}
|
||||
close() {
|
||||
this.isOpen = false;
|
||||
document.removeEventListener('mousedown', this.closeHandler);
|
||||
document.removeEventListener('touchstart', this.closeHandler);
|
||||
}
|
||||
toggleOpen() {
|
||||
if (!this.isDisabled) {
|
||||
if (this.isOpen) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
document.addEventListener('mousedown', this.closeHandler);
|
||||
document.addEventListener('touchstart', this.closeHandler);
|
||||
});
|
||||
}
|
||||
}
|
||||
close() {
|
||||
this.isOpen = false;
|
||||
document.removeEventListener('mousedown', this.closeHandler);
|
||||
document.removeEventListener('touchstart', this.closeHandler);
|
||||
}
|
||||
toggleOpen() {
|
||||
if (!this.isDisabled) {
|
||||
if (this.isOpen) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@ import { Component, ChangeDetectionStrategy, Output, Input, EventEmitter } from
|
||||
import { uniqueId } from 'lodash';
|
||||
|
||||
@Component({
|
||||
selector: 'custom-checkbox',
|
||||
templateUrl: 'custom-checkbox.pug',
|
||||
styleUrls: ['custom-checkbox.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'custom-checkbox',
|
||||
templateUrl: 'custom-checkbox.pug',
|
||||
styleUrls: ['custom-checkbox.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomCheckbox {
|
||||
@Input() disabled = false;
|
||||
@Input() help = '';
|
||||
@Input() checked = false;
|
||||
@Output() checkedChange = new EventEmitter<boolean>();
|
||||
helpId = uniqueId('custom-checkbox-help-');
|
||||
@Input() disabled = false;
|
||||
@Input() help = '';
|
||||
@Input() checked = false;
|
||||
@Output() checkedChange = new EventEmitter<boolean>();
|
||||
helpId = uniqueId('custom-checkbox-help-');
|
||||
}
|
||||
|
||||
@@ -4,52 +4,52 @@ import { MONTH_NAMES_EN } from '../../../common/constants';
|
||||
import { getLocale } from '../../../client/clientUtils';
|
||||
|
||||
@Component({
|
||||
selector: 'date-picker',
|
||||
templateUrl: 'date-picker.pug',
|
||||
selector: 'date-picker',
|
||||
templateUrl: 'date-picker.pug',
|
||||
})
|
||||
export class DatePicker {
|
||||
readonly days = times(31, i => i + 1);
|
||||
readonly years: number[] = [];
|
||||
readonly months = getMonthNames();
|
||||
day = 0;
|
||||
month = 0;
|
||||
year = 0;
|
||||
@Output() dateChange = new EventEmitter<string | undefined>();
|
||||
constructor() {
|
||||
const minYear = 1914;
|
||||
const maxYear = (new Date()).getFullYear() - 6;
|
||||
readonly days = times(31, i => i + 1);
|
||||
readonly years: number[] = [];
|
||||
readonly months = getMonthNames();
|
||||
day = 0;
|
||||
month = 0;
|
||||
year = 0;
|
||||
@Output() dateChange = new EventEmitter<string | undefined>();
|
||||
constructor() {
|
||||
const minYear = 1914;
|
||||
const maxYear = (new Date()).getFullYear() - 6;
|
||||
|
||||
for (let year = maxYear; year >= minYear; year--) {
|
||||
this.years.push(year);
|
||||
}
|
||||
}
|
||||
@Input() get date() {
|
||||
const date = createValidBirthDate(this.day, this.month, this.year);
|
||||
return date && formatISODate(date);
|
||||
}
|
||||
set date(value) {
|
||||
if (value) {
|
||||
const { day, month, year } = parseISODate(value);
|
||||
this.day = day;
|
||||
this.month = month;
|
||||
this.year = year;
|
||||
}
|
||||
}
|
||||
change() {
|
||||
this.dateChange.emit(this.date);
|
||||
}
|
||||
for (let year = maxYear; year >= minYear; year--) {
|
||||
this.years.push(year);
|
||||
}
|
||||
}
|
||||
@Input() get date() {
|
||||
const date = createValidBirthDate(this.day, this.month, this.year);
|
||||
return date && formatISODate(date);
|
||||
}
|
||||
set date(value) {
|
||||
if (value) {
|
||||
const { day, month, year } = parseISODate(value);
|
||||
this.day = day;
|
||||
this.month = month;
|
||||
this.year = year;
|
||||
}
|
||||
}
|
||||
change() {
|
||||
this.dateChange.emit(this.date);
|
||||
}
|
||||
}
|
||||
|
||||
function getMonthNames() {
|
||||
try {
|
||||
const format = new Intl.DateTimeFormat(getLocale(), { month: 'long' });
|
||||
try {
|
||||
const format = new Intl.DateTimeFormat(getLocale(), { month: 'long' });
|
||||
|
||||
return times(12, i => {
|
||||
const date = new Date(523456789);
|
||||
date.setMonth(i);
|
||||
return format.format(date);
|
||||
});
|
||||
} catch {
|
||||
return MONTH_NAMES_EN;
|
||||
}
|
||||
return times(12, i => {
|
||||
const date = new Date(523456789);
|
||||
date.setMonth(i);
|
||||
return format.format(date);
|
||||
});
|
||||
} catch {
|
||||
return MONTH_NAMES_EN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Directive, AfterViewInit, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[agAutoFocus]'
|
||||
selector: '[agAutoFocus]'
|
||||
})
|
||||
export class AgAutoFocus implements AfterViewInit {
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => this.element.nativeElement.focus(), 100);
|
||||
}
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => this.element.nativeElement.focus(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,138 +3,138 @@ import { noop } from 'lodash';
|
||||
import { getButton, getX, getY, AnyEvent } from '../../../common/utils';
|
||||
|
||||
export interface AgDragEvent {
|
||||
event: AnyEvent;
|
||||
type: 'start' | 'drag' | 'end';
|
||||
x: number;
|
||||
y: number;
|
||||
dx: number;
|
||||
dy: number;
|
||||
event: AnyEvent;
|
||||
type: 'start' | 'drag' | 'end';
|
||||
x: number;
|
||||
y: number;
|
||||
dx: number;
|
||||
dy: number;
|
||||
}
|
||||
|
||||
export interface AgDragOptions {
|
||||
relative?: 'self' | 'parent';
|
||||
prevent?: boolean;
|
||||
relative?: 'self' | 'parent';
|
||||
prevent?: boolean;
|
||||
}
|
||||
|
||||
export function handleDrag(element: HTMLElement, emit: (event: AgDragEvent) => void, options: AgDragOptions = {}) {
|
||||
// typeof PointerEvent !== 'undefined'
|
||||
const eventSets = window.navigator.pointerEnabled ? [
|
||||
{ down: 'pointerdown', move: 'pointermove', up: 'pointerup' }, // , up2: 'pointercancel' },
|
||||
] : [
|
||||
{ down: 'mousedown', move: 'mousemove', up: 'mouseup' },
|
||||
{ down: 'touchstart', move: 'touchmove', up: 'touchend', up2: 'touchcancel' },
|
||||
];
|
||||
const emptyRect = { left: 0, top: 0 };
|
||||
let rect = emptyRect;
|
||||
let scrollLeft = 0;
|
||||
let scrollTop = 0;
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let button = 0;
|
||||
let dragging = false;
|
||||
let lastEvent: any;
|
||||
// typeof PointerEvent !== 'undefined'
|
||||
const eventSets = window.navigator.pointerEnabled ? [
|
||||
{ down: 'pointerdown', move: 'pointermove', up: 'pointerup' }, // , up2: 'pointercancel' },
|
||||
] : [
|
||||
{ down: 'mousedown', move: 'mousemove', up: 'mouseup' },
|
||||
{ down: 'touchstart', move: 'touchmove', up: 'touchend', up2: 'touchcancel' },
|
||||
];
|
||||
const emptyRect = { left: 0, top: 0 };
|
||||
let rect = emptyRect;
|
||||
let scrollLeft = 0;
|
||||
let scrollTop = 0;
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let button = 0;
|
||||
let dragging = false;
|
||||
let lastEvent: any;
|
||||
|
||||
function setupScrollAndRect() {
|
||||
// TODO: fix issue with scroll
|
||||
switch (options.relative) {
|
||||
case 'self':
|
||||
rect = element.getBoundingClientRect();
|
||||
scrollLeft = -(window.scrollX || window.pageXOffset || 0);
|
||||
scrollTop = -(window.scrollY || window.pageYOffset || 0);
|
||||
break;
|
||||
case 'parent':
|
||||
rect = element.parentElement!.getBoundingClientRect();
|
||||
scrollLeft = element.parentElement!.scrollLeft;
|
||||
scrollTop = element.parentElement!.scrollTop;
|
||||
break;
|
||||
default:
|
||||
rect = emptyRect;
|
||||
scrollLeft = 0;
|
||||
scrollTop = 0;
|
||||
}
|
||||
}
|
||||
function setupScrollAndRect() {
|
||||
// TODO: fix issue with scroll
|
||||
switch (options.relative) {
|
||||
case 'self':
|
||||
rect = element.getBoundingClientRect();
|
||||
scrollLeft = -(window.scrollX || window.pageXOffset || 0);
|
||||
scrollTop = -(window.scrollY || window.pageYOffset || 0);
|
||||
break;
|
||||
case 'parent':
|
||||
rect = element.parentElement!.getBoundingClientRect();
|
||||
scrollLeft = element.parentElement!.scrollLeft;
|
||||
scrollTop = element.parentElement!.scrollTop;
|
||||
break;
|
||||
default:
|
||||
rect = emptyRect;
|
||||
scrollLeft = 0;
|
||||
scrollTop = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function send(event: AnyEvent, type: 'start' | 'drag' | 'end') {
|
||||
const x = getX(event);
|
||||
const y = getY(event);
|
||||
function send(event: AnyEvent, type: 'start' | 'drag' | 'end') {
|
||||
const x = getX(event);
|
||||
const y = getY(event);
|
||||
|
||||
emit({
|
||||
event,
|
||||
type,
|
||||
x: x - rect.left + scrollLeft,
|
||||
y: y - rect.top + scrollTop,
|
||||
dx: x - startX,
|
||||
dy: y - startY,
|
||||
});
|
||||
}
|
||||
emit({
|
||||
event,
|
||||
type,
|
||||
x: x - rect.left + scrollLeft,
|
||||
y: y - rect.top + scrollTop,
|
||||
dx: x - startX,
|
||||
dy: y - startY,
|
||||
});
|
||||
}
|
||||
|
||||
const handlers = eventSets.map(events => {
|
||||
function move(e: any) {
|
||||
lastEvent = e;
|
||||
e.preventDefault();
|
||||
send(e, 'drag');
|
||||
}
|
||||
const handlers = eventSets.map(events => {
|
||||
function move(e: any) {
|
||||
lastEvent = e;
|
||||
e.preventDefault();
|
||||
send(e, 'drag');
|
||||
}
|
||||
|
||||
function up(e: any) {
|
||||
if (getButton(e) === button) {
|
||||
// touchend event does not have x, y coordinates, use last touchmove event instead
|
||||
if (e.type !== 'touchend' && e.type !== 'touchcancel') {
|
||||
lastEvent = e;
|
||||
}
|
||||
end();
|
||||
}
|
||||
}
|
||||
function up(e: any) {
|
||||
if (getButton(e) === button) {
|
||||
// touchend event does not have x, y coordinates, use last touchmove event instead
|
||||
if (e.type !== 'touchend' && e.type !== 'touchcancel') {
|
||||
lastEvent = e;
|
||||
}
|
||||
end();
|
||||
}
|
||||
}
|
||||
|
||||
function end() {
|
||||
send(lastEvent, 'end');
|
||||
window.removeEventListener(events.move, move);
|
||||
window.removeEventListener(events.up, up);
|
||||
events.up2 && window.removeEventListener(events.up2, up);
|
||||
window.removeEventListener('blur', end);
|
||||
dragging = false;
|
||||
}
|
||||
function end() {
|
||||
send(lastEvent, 'end');
|
||||
window.removeEventListener(events.move, move);
|
||||
window.removeEventListener(events.up, up);
|
||||
events.up2 && window.removeEventListener(events.up2, up);
|
||||
window.removeEventListener('blur', end);
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
function handler(e: any) {
|
||||
if (!dragging) {
|
||||
setupScrollAndRect();
|
||||
dragging = true;
|
||||
button = getButton(e);
|
||||
startX = getX(e);
|
||||
startY = getY(e);
|
||||
send(e, 'start');
|
||||
lastEvent = e;
|
||||
function handler(e: any) {
|
||||
if (!dragging) {
|
||||
setupScrollAndRect();
|
||||
dragging = true;
|
||||
button = getButton(e);
|
||||
startX = getX(e);
|
||||
startY = getY(e);
|
||||
send(e, 'start');
|
||||
lastEvent = e;
|
||||
|
||||
window.addEventListener(events.move, move);
|
||||
window.addEventListener(events.up, up);
|
||||
events.up2 && window.addEventListener(events.up2, up);
|
||||
window.addEventListener('blur', end);
|
||||
e.stopPropagation();
|
||||
window.addEventListener(events.move, move);
|
||||
window.addEventListener(events.up, up);
|
||||
events.up2 && window.addEventListener(events.up2, up);
|
||||
window.addEventListener('blur', end);
|
||||
e.stopPropagation();
|
||||
|
||||
if (options.prevent) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.prevent) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
element.addEventListener(events.down, handler);
|
||||
return () => element.removeEventListener(events.down, handler);
|
||||
});
|
||||
element.addEventListener(events.down, handler);
|
||||
return () => element.removeEventListener(events.down, handler);
|
||||
});
|
||||
|
||||
return () => handlers.forEach(f => f());
|
||||
return () => handlers.forEach(f => f());
|
||||
}
|
||||
|
||||
@Directive({ selector: '[agDrag]' })
|
||||
export class AgDrag implements OnInit, OnDestroy {
|
||||
@Input('agDragRelative') relative: 'self' | 'parent' | undefined = undefined;
|
||||
@Input('agDragPrevent') prevent = false;
|
||||
@Output('agDrag') drag = new EventEmitter<AgDragEvent>();
|
||||
private unsubscribe = noop;
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.unsubscribe = handleDrag(this.element.nativeElement, e => this.drag.emit(e), this);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.unsubscribe();
|
||||
}
|
||||
@Input('agDragRelative') relative: 'self' | 'parent' | undefined = undefined;
|
||||
@Input('agDragPrevent') prevent = false;
|
||||
@Output('agDrag') drag = new EventEmitter<AgDragEvent>();
|
||||
private unsubscribe = noop;
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.unsubscribe = handleDrag(this.element.nativeElement, e => this.drag.emit(e), this);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { Directive, OnInit, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: 'a[href]'
|
||||
selector: 'a[href]'
|
||||
})
|
||||
export class Anchor implements OnInit {
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
ngOnInit() {
|
||||
const a = this.element.nativeElement as HTMLAnchorElement;
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
ngOnInit() {
|
||||
const a = this.element.nativeElement as HTMLAnchorElement;
|
||||
|
||||
if (/^(https?|mailto):/.test(a.href) && !a.target) {
|
||||
a.setAttribute('target', '_blank');
|
||||
a.setAttribute('rel', 'noopener noreferrer');
|
||||
}
|
||||
}
|
||||
if (/^(https?|mailto):/.test(a.href) && !a.target) {
|
||||
a.setAttribute('target', '_blank');
|
||||
a.setAttribute('rel', 'noopener noreferrer');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,29 +2,29 @@ import { Directive, Input, Optional } from '@angular/core';
|
||||
import { NgModel } from '@angular/forms';
|
||||
|
||||
@Directive({
|
||||
selector: '[btnHighlight]',
|
||||
host: {
|
||||
'[class.btn-default]': '!on',
|
||||
'[class.btn-primary]': 'on',
|
||||
},
|
||||
selector: '[btnHighlight]',
|
||||
host: {
|
||||
'[class.btn-default]': '!on',
|
||||
'[class.btn-primary]': 'on',
|
||||
},
|
||||
})
|
||||
export class BtnHighlight {
|
||||
@Input() btnHighlight?: boolean = undefined;
|
||||
constructor(@Optional() private model?: NgModel) {
|
||||
}
|
||||
get on() {
|
||||
const value = this.btnHighlight;
|
||||
return (value === true || value === false || !this.model) ? value : !!this.model.value;
|
||||
}
|
||||
@Input() btnHighlight?: boolean = undefined;
|
||||
constructor(@Optional() private model?: NgModel) {
|
||||
}
|
||||
get on() {
|
||||
const value = this.btnHighlight;
|
||||
return (value === true || value === false || !this.model) ? value : !!this.model.value;
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[btnHighlightDanger]',
|
||||
host: {
|
||||
'[class.btn-default]': '!btnHighlightDanger',
|
||||
'[class.btn-danger]': 'btnHighlightDanger',
|
||||
},
|
||||
selector: '[btnHighlightDanger]',
|
||||
host: {
|
||||
'[class.btn-default]': '!btnHighlightDanger',
|
||||
'[class.btn-danger]': 'btnHighlightDanger',
|
||||
},
|
||||
})
|
||||
export class BtnHighlightDanger {
|
||||
@Input() btnHighlightDanger = false;
|
||||
@Input() btnHighlightDanger = false;
|
||||
}
|
||||
|
||||
@@ -6,202 +6,202 @@ import { rect } from '../../../common/rect';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DraggableService {
|
||||
root?: ElementRef;
|
||||
draggedItem?: any;
|
||||
activeDropZone?: DraggableDrop<any>;
|
||||
dropZones: DraggableDrop<any>[] = [];
|
||||
get rootElement(): HTMLElement {
|
||||
return this.root ? this.root.nativeElement : document.body;
|
||||
}
|
||||
setActiveDropZone(dropZone: DraggableDrop<any> | undefined) {
|
||||
if (this.activeDropZone !== dropZone) {
|
||||
if (this.activeDropZone) {
|
||||
this.activeDropZone.setActive(false);
|
||||
}
|
||||
root?: ElementRef;
|
||||
draggedItem?: any;
|
||||
activeDropZone?: DraggableDrop<any>;
|
||||
dropZones: DraggableDrop<any>[] = [];
|
||||
get rootElement(): HTMLElement {
|
||||
return this.root ? this.root.nativeElement : document.body;
|
||||
}
|
||||
setActiveDropZone(dropZone: DraggableDrop<any> | undefined) {
|
||||
if (this.activeDropZone !== dropZone) {
|
||||
if (this.activeDropZone) {
|
||||
this.activeDropZone.setActive(false);
|
||||
}
|
||||
|
||||
this.activeDropZone = dropZone;
|
||||
this.activeDropZone = dropZone;
|
||||
|
||||
if (this.activeDropZone) {
|
||||
this.activeDropZone.setActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
startMove(element: HTMLElement, item: any) {
|
||||
this.setActiveDropZone(undefined);
|
||||
this.rootElement.appendChild(element);
|
||||
this.draggedItem = item;
|
||||
this.initRects();
|
||||
}
|
||||
endMove() {
|
||||
if (this.activeDropZone) {
|
||||
this.activeDropZone.drop.emit(this.draggedItem);
|
||||
this.setActiveDropZone(undefined);
|
||||
}
|
||||
if (this.activeDropZone) {
|
||||
this.activeDropZone.setActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
startMove(element: HTMLElement, item: any) {
|
||||
this.setActiveDropZone(undefined);
|
||||
this.rootElement.appendChild(element);
|
||||
this.draggedItem = item;
|
||||
this.initRects();
|
||||
}
|
||||
endMove() {
|
||||
if (this.activeDropZone) {
|
||||
this.activeDropZone.drop.emit(this.draggedItem);
|
||||
this.setActiveDropZone(undefined);
|
||||
}
|
||||
|
||||
this.draggedItem = undefined;
|
||||
}
|
||||
addDropZone(dropZone: DraggableDrop<any>) {
|
||||
this.dropZones.push(dropZone);
|
||||
this.draggedItem = undefined;
|
||||
}
|
||||
addDropZone(dropZone: DraggableDrop<any>) {
|
||||
this.dropZones.push(dropZone);
|
||||
|
||||
if (this.draggedItem) {
|
||||
this.initRects();
|
||||
}
|
||||
}
|
||||
removeDropZone(dropZone: DraggableDrop<any>) {
|
||||
removeItem(this.dropZones, dropZone);
|
||||
if (this.draggedItem) {
|
||||
this.initRects();
|
||||
}
|
||||
}
|
||||
removeDropZone(dropZone: DraggableDrop<any>) {
|
||||
removeItem(this.dropZones, dropZone);
|
||||
|
||||
if (this.draggedItem) {
|
||||
this.initRects();
|
||||
}
|
||||
if (this.draggedItem) {
|
||||
this.initRects();
|
||||
}
|
||||
|
||||
if (this.activeDropZone === dropZone) {
|
||||
this.setActiveDropZone(undefined);
|
||||
}
|
||||
}
|
||||
updateHover(x: number, y: number) {
|
||||
if (this.draggedItem) {
|
||||
for (const zone of this.dropZones) {
|
||||
if (pointInRect(x, y, zone.rect)) {
|
||||
this.setActiveDropZone(zone);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.activeDropZone === dropZone) {
|
||||
this.setActiveDropZone(undefined);
|
||||
}
|
||||
}
|
||||
updateHover(x: number, y: number) {
|
||||
if (this.draggedItem) {
|
||||
for (const zone of this.dropZones) {
|
||||
if (pointInRect(x, y, zone.rect)) {
|
||||
this.setActiveDropZone(zone);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.setActiveDropZone(undefined);
|
||||
}
|
||||
}
|
||||
private initRects() {
|
||||
this.dropZones.forEach(i => i.initRect());
|
||||
}
|
||||
this.setActiveDropZone(undefined);
|
||||
}
|
||||
}
|
||||
private initRects() {
|
||||
this.dropZones.forEach(i => i.initRect());
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'draggable-outlet',
|
||||
template: `<div></div>`,
|
||||
styles: [`:host { position: fixed; top: 0; left: 0; z-index: 10000; }`],
|
||||
selector: 'draggable-outlet',
|
||||
template: `<div></div>`,
|
||||
styles: [`:host { position: fixed; top: 0; left: 0; z-index: 10000; }`],
|
||||
})
|
||||
export class DraggableOutlet {
|
||||
constructor(element: ElementRef, service: DraggableService) {
|
||||
service.root = element;
|
||||
}
|
||||
constructor(element: ElementRef, service: DraggableService) {
|
||||
service.root = element;
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({ selector: '[draggableDrop]' })
|
||||
export class DraggableDrop<T> implements OnInit, OnDestroy {
|
||||
@Input('draggablePad') pad = 0;
|
||||
@Output('draggableDrop') drop = new EventEmitter<T>();
|
||||
rect = rect(0, 0, 0, 0);
|
||||
constructor(private element: ElementRef, private service: DraggableService) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.service.addDropZone(this);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.service.removeDropZone(this);
|
||||
}
|
||||
setActive(active: boolean) {
|
||||
const element = this.element.nativeElement as HTMLElement;
|
||||
@Input('draggablePad') pad = 0;
|
||||
@Output('draggableDrop') drop = new EventEmitter<T>();
|
||||
rect = rect(0, 0, 0, 0);
|
||||
constructor(private element: ElementRef, private service: DraggableService) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.service.addDropZone(this);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.service.removeDropZone(this);
|
||||
}
|
||||
setActive(active: boolean) {
|
||||
const element = this.element.nativeElement as HTMLElement;
|
||||
|
||||
if (active) {
|
||||
element.classList.add('draggable-hover');
|
||||
} else {
|
||||
element.classList.remove('draggable-hover');
|
||||
}
|
||||
}
|
||||
initRect() {
|
||||
const element = this.element.nativeElement as HTMLElement;
|
||||
const clientBounds = element.getBoundingClientRect();
|
||||
this.rect.x = clientBounds.left - this.pad;
|
||||
this.rect.y = clientBounds.top - this.pad;
|
||||
this.rect.w = clientBounds.width + 2 * this.pad;
|
||||
this.rect.h = clientBounds.height + 2 * this.pad;
|
||||
}
|
||||
if (active) {
|
||||
element.classList.add('draggable-hover');
|
||||
} else {
|
||||
element.classList.remove('draggable-hover');
|
||||
}
|
||||
}
|
||||
initRect() {
|
||||
const element = this.element.nativeElement as HTMLElement;
|
||||
const clientBounds = element.getBoundingClientRect();
|
||||
this.rect.x = clientBounds.left - this.pad;
|
||||
this.rect.y = clientBounds.top - this.pad;
|
||||
this.rect.w = clientBounds.width + 2 * this.pad;
|
||||
this.rect.h = clientBounds.height + 2 * this.pad;
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[draggableItem]',
|
||||
host: {
|
||||
'[style.touch-action]': `touchAction`,
|
||||
}
|
||||
selector: '[draggableItem]',
|
||||
host: {
|
||||
'[style.touch-action]': `touchAction`,
|
||||
}
|
||||
})
|
||||
export class DraggableItem<T> implements OnInit, OnDestroy {
|
||||
@Input('draggableItem') item: T | undefined;
|
||||
@Output('draggableDrag') dragStarted = new EventEmitter<void>();
|
||||
private startX = 0;
|
||||
private startY = 0;
|
||||
private draggable?: HTMLElement;
|
||||
private width = 0;
|
||||
private height = 0;
|
||||
private unsubscribeDrag = noop;
|
||||
private _disabled = false;
|
||||
constructor(private element: ElementRef, private service: DraggableService) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.setupDragEvents();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.unsubscribeDrag();
|
||||
}
|
||||
get touchAction() {
|
||||
return this.disabled ? 'inherit' : 'none';
|
||||
}
|
||||
@Input('draggableDisabled') get disabled() {
|
||||
return this._disabled;
|
||||
}
|
||||
set disabled(value) {
|
||||
if (this._disabled !== value) {
|
||||
this._disabled = value;
|
||||
this.setupDragEvents();
|
||||
}
|
||||
}
|
||||
private setupDragEvents() {
|
||||
this.unsubscribeDrag();
|
||||
this.unsubscribeDrag = noop;
|
||||
@Input('draggableItem') item: T | undefined;
|
||||
@Output('draggableDrag') dragStarted = new EventEmitter<void>();
|
||||
private startX = 0;
|
||||
private startY = 0;
|
||||
private draggable?: HTMLElement;
|
||||
private width = 0;
|
||||
private height = 0;
|
||||
private unsubscribeDrag = noop;
|
||||
private _disabled = false;
|
||||
constructor(private element: ElementRef, private service: DraggableService) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.setupDragEvents();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.unsubscribeDrag();
|
||||
}
|
||||
get touchAction() {
|
||||
return this.disabled ? 'inherit' : 'none';
|
||||
}
|
||||
@Input('draggableDisabled') get disabled() {
|
||||
return this._disabled;
|
||||
}
|
||||
set disabled(value) {
|
||||
if (this._disabled !== value) {
|
||||
this._disabled = value;
|
||||
this.setupDragEvents();
|
||||
}
|
||||
}
|
||||
private setupDragEvents() {
|
||||
this.unsubscribeDrag();
|
||||
this.unsubscribeDrag = noop;
|
||||
|
||||
if (!this.disabled) {
|
||||
this.unsubscribeDrag = handleDrag(this.element.nativeElement, e => this.drag(e), { prevent: true });
|
||||
}
|
||||
}
|
||||
drag(e: AgDragEvent) {
|
||||
if (this.item && !this.disabled && !this.draggable && (Math.abs(e.dx) > 5 || Math.abs(e.dy) > 5)) {
|
||||
const element = this.element.nativeElement as HTMLElement;
|
||||
const rect = element.getBoundingClientRect();
|
||||
this.startX = rect.left;
|
||||
this.startY = rect.top;
|
||||
this.draggable = element.cloneNode(true) as HTMLElement;
|
||||
this.draggable.style.position = 'absolute';
|
||||
this.draggable.style.width = `${rect.width}px`;
|
||||
this.draggable.style.height = `${rect.height}px`;
|
||||
this.draggable.style.margin = '0';
|
||||
this.draggable.classList.add('draggable-dragging');
|
||||
this.width = rect.width;
|
||||
this.height = rect.height;
|
||||
if (!this.disabled) {
|
||||
this.unsubscribeDrag = handleDrag(this.element.nativeElement, e => this.drag(e), { prevent: true });
|
||||
}
|
||||
}
|
||||
drag(e: AgDragEvent) {
|
||||
if (this.item && !this.disabled && !this.draggable && (Math.abs(e.dx) > 5 || Math.abs(e.dy) > 5)) {
|
||||
const element = this.element.nativeElement as HTMLElement;
|
||||
const rect = element.getBoundingClientRect();
|
||||
this.startX = rect.left;
|
||||
this.startY = rect.top;
|
||||
this.draggable = element.cloneNode(true) as HTMLElement;
|
||||
this.draggable.style.position = 'absolute';
|
||||
this.draggable.style.width = `${rect.width}px`;
|
||||
this.draggable.style.height = `${rect.height}px`;
|
||||
this.draggable.style.margin = '0';
|
||||
this.draggable.classList.add('draggable-dragging');
|
||||
this.width = rect.width;
|
||||
this.height = rect.height;
|
||||
|
||||
const src = element.querySelectorAll('canvas') as NodeListOf<HTMLCanvasElement>;
|
||||
const dst = this.draggable.querySelectorAll('canvas') as NodeListOf<HTMLCanvasElement>;
|
||||
const src = element.querySelectorAll('canvas') as NodeListOf<HTMLCanvasElement>;
|
||||
const dst = this.draggable.querySelectorAll('canvas') as NodeListOf<HTMLCanvasElement>;
|
||||
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
const context = dst.item(i).getContext('2d');
|
||||
context && context.drawImage(src.item(i), 0, 0);
|
||||
}
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
const context = dst.item(i).getContext('2d');
|
||||
context && context.drawImage(src.item(i), 0, 0);
|
||||
}
|
||||
|
||||
this.service.startMove(this.draggable, this.item!);
|
||||
this.dragStarted.emit();
|
||||
}
|
||||
this.service.startMove(this.draggable, this.item!);
|
||||
this.dragStarted.emit();
|
||||
}
|
||||
|
||||
if (this.draggable) {
|
||||
if (e.type === 'end') {
|
||||
this.draggable!.parentNode!.removeChild(this.draggable!);
|
||||
this.draggable = undefined;
|
||||
this.service.endMove();
|
||||
} else {
|
||||
const x = clamp(this.startX + e.dx, 0, window.innerWidth - this.width);
|
||||
const y = clamp(this.startY + e.dy, 0, window.innerHeight - this.height);
|
||||
setTransform(this.draggable, `translate3d(${x}px, ${y}px, 0px)`);
|
||||
this.service.updateHover(e.x, e.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.draggable) {
|
||||
if (e.type === 'end') {
|
||||
this.draggable!.parentNode!.removeChild(this.draggable!);
|
||||
this.draggable = undefined;
|
||||
this.service.endMove();
|
||||
} else {
|
||||
const x = clamp(this.startX + e.dx, 0, window.innerWidth - this.width);
|
||||
const y = clamp(this.startY + e.dy, 0, window.innerHeight - this.height);
|
||||
setTransform(this.draggable, `translate3d(${x}px, ${y}px, 0px)`);
|
||||
this.service.updateHover(e.x, e.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const draggableComponents = [DraggableOutlet, DraggableItem, DraggableDrop];
|
||||
|
||||
@@ -1,227 +1,227 @@
|
||||
import {
|
||||
Directive, HostListener, Input, Output, EventEmitter, TemplateRef, ViewContainerRef, ContentChild,
|
||||
Renderer2, ElementRef, EmbeddedViewRef, Component, Injectable
|
||||
Directive, HostListener, Input, Output, EventEmitter, TemplateRef, ViewContainerRef, ContentChild,
|
||||
Renderer2, ElementRef, EmbeddedViewRef, Component, Injectable
|
||||
} from '@angular/core';
|
||||
import { uniqueId } from 'lodash';
|
||||
import { focusFirstElement } from '../../../client/htmlUtils';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DropdownOutletService {
|
||||
viewContainer?: ViewContainerRef;
|
||||
rootElement?: HTMLElement;
|
||||
viewContainer?: ViewContainerRef;
|
||||
rootElement?: HTMLElement;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dropdown-outlet',
|
||||
template: `<ng-template></ng-template>`,
|
||||
selector: 'dropdown-outlet',
|
||||
template: `<ng-template></ng-template>`,
|
||||
})
|
||||
export class DropdownOutlet {
|
||||
constructor(service: DropdownOutletService, viewContainer: ViewContainerRef, element: ElementRef) {
|
||||
service.viewContainer = viewContainer;
|
||||
service.rootElement = element.nativeElement.parentElement;
|
||||
}
|
||||
constructor(service: DropdownOutletService, viewContainer: ViewContainerRef, element: ElementRef) {
|
||||
service.viewContainer = viewContainer;
|
||||
service.rootElement = element.nativeElement.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[dropdownMenu]',
|
||||
selector: '[dropdownMenu]',
|
||||
})
|
||||
export class DropdownMenu {
|
||||
ref?: EmbeddedViewRef<any>;
|
||||
id = uniqueId('dropdown-menu-');
|
||||
private onClose?: () => void;
|
||||
constructor(
|
||||
private templateRef: TemplateRef<any>,
|
||||
private viewContainer: ViewContainerRef,
|
||||
private renderer: Renderer2,
|
||||
private service: DropdownOutletService,
|
||||
) {
|
||||
}
|
||||
private get root(): HTMLElement {
|
||||
return this.ref && this.ref.rootNodes[0];
|
||||
}
|
||||
open(useOutlet: boolean, rootElement: HTMLElement) {
|
||||
if (!this.ref) {
|
||||
if (useOutlet) {
|
||||
this.ref = this.service.viewContainer!.createEmbeddedView(this.templateRef);
|
||||
} else {
|
||||
this.ref = this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
}
|
||||
ref?: EmbeddedViewRef<any>;
|
||||
id = uniqueId('dropdown-menu-');
|
||||
private onClose?: () => void;
|
||||
constructor(
|
||||
private templateRef: TemplateRef<any>,
|
||||
private viewContainer: ViewContainerRef,
|
||||
private renderer: Renderer2,
|
||||
private service: DropdownOutletService,
|
||||
) {
|
||||
}
|
||||
private get root(): HTMLElement {
|
||||
return this.ref && this.ref.rootNodes[0];
|
||||
}
|
||||
open(useOutlet: boolean, rootElement: HTMLElement) {
|
||||
if (!this.ref) {
|
||||
if (useOutlet) {
|
||||
this.ref = this.service.viewContainer!.createEmbeddedView(this.templateRef);
|
||||
} else {
|
||||
this.ref = this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
}
|
||||
|
||||
const { renderer, root } = this;
|
||||
const { renderer, root } = this;
|
||||
|
||||
renderer.addClass(root, 'show');
|
||||
renderer.setAttribute(root, 'id', this.id);
|
||||
renderer.addClass(root, 'show');
|
||||
renderer.setAttribute(root, 'id', this.id);
|
||||
|
||||
if (useOutlet) {
|
||||
const positionMenu = () => {
|
||||
const rect = rootElement.getBoundingClientRect();
|
||||
const menuRect = root.getBoundingClientRect();
|
||||
let transform: string;
|
||||
if (useOutlet) {
|
||||
const positionMenu = () => {
|
||||
const rect = rootElement.getBoundingClientRect();
|
||||
const menuRect = root.getBoundingClientRect();
|
||||
let transform: string;
|
||||
|
||||
if ((rect.bottom + menuRect.height) > window.innerHeight) {
|
||||
transform = `translate3d(${Math.round(rect.left)}px, ${Math.round(rect.top - menuRect.height)}px, 0)`;
|
||||
renderer.addClass(root, 'dropdown-menu-up');
|
||||
} else {
|
||||
transform = `translate3d(${Math.round(rect.left)}px, ${Math.round(rect.bottom)}px, 0)`;
|
||||
renderer.removeClass(root, 'dropdown-menu-up');
|
||||
}
|
||||
if ((rect.bottom + menuRect.height) > window.innerHeight) {
|
||||
transform = `translate3d(${Math.round(rect.left)}px, ${Math.round(rect.top - menuRect.height)}px, 0)`;
|
||||
renderer.addClass(root, 'dropdown-menu-up');
|
||||
} else {
|
||||
transform = `translate3d(${Math.round(rect.left)}px, ${Math.round(rect.bottom)}px, 0)`;
|
||||
renderer.removeClass(root, 'dropdown-menu-up');
|
||||
}
|
||||
|
||||
renderer.setStyle(root, 'transform', transform);
|
||||
};
|
||||
renderer.setStyle(root, 'transform', transform);
|
||||
};
|
||||
|
||||
renderer.addClass(root, 'dropdown-in-outlet');
|
||||
positionMenu();
|
||||
renderer.addClass(root, 'dropdown-in-outlet');
|
||||
positionMenu();
|
||||
|
||||
const closeDropdown = () => {
|
||||
this.close();
|
||||
};
|
||||
const closeDropdown = () => {
|
||||
this.close();
|
||||
};
|
||||
|
||||
document.addEventListener('scroll', closeDropdown, true);
|
||||
window.addEventListener('resize', closeDropdown, true);
|
||||
document.addEventListener('scroll', closeDropdown, true);
|
||||
window.addEventListener('resize', closeDropdown, true);
|
||||
|
||||
this.onClose = () => {
|
||||
document.removeEventListener('scroll', closeDropdown, true);
|
||||
window.removeEventListener('resize', closeDropdown, true);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
close() {
|
||||
if (this.ref) {
|
||||
this.ref.destroy();
|
||||
this.ref = undefined;
|
||||
}
|
||||
this.onClose = () => {
|
||||
document.removeEventListener('scroll', closeDropdown, true);
|
||||
window.removeEventListener('resize', closeDropdown, true);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
close() {
|
||||
if (this.ref) {
|
||||
this.ref.destroy();
|
||||
this.ref = undefined;
|
||||
}
|
||||
|
||||
if (this.onClose) {
|
||||
this.onClose();
|
||||
this.onClose = undefined;
|
||||
}
|
||||
}
|
||||
checkTarget(e: Event) {
|
||||
return this.root && this.root.contains(e.target as any);
|
||||
}
|
||||
focusFirstElement() {
|
||||
if (this.root) {
|
||||
focusFirstElement(this.root);
|
||||
}
|
||||
}
|
||||
if (this.onClose) {
|
||||
this.onClose();
|
||||
this.onClose = undefined;
|
||||
}
|
||||
}
|
||||
checkTarget(e: Event) {
|
||||
return this.root && this.root.contains(e.target as any);
|
||||
}
|
||||
focusFirstElement() {
|
||||
if (this.root) {
|
||||
focusFirstElement(this.root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[dropdown]',
|
||||
exportAs: 'ag-dropdown',
|
||||
host: {
|
||||
'[class.show]': 'isOpen',
|
||||
},
|
||||
selector: '[dropdown]',
|
||||
exportAs: 'ag-dropdown',
|
||||
host: {
|
||||
'[class.show]': 'isOpen',
|
||||
},
|
||||
})
|
||||
export class Dropdown {
|
||||
dropdownToggle?: DropdownToggle;
|
||||
@ContentChild(DropdownMenu, { static: false }) menu!: DropdownMenu;
|
||||
@Input() autoClose: boolean | 'outsideClick' = true;
|
||||
@Input() preventAutoCloseOnOutlet = false;
|
||||
@Input() hookToCanvas = false;
|
||||
@Input() focusOnOpen = true;
|
||||
@Input() focusOnClose = true;
|
||||
@Input() useOutlet = false;
|
||||
@Input() isOpen = false;
|
||||
@Output() isOpenChange = new EventEmitter<boolean>();
|
||||
get menuId() {
|
||||
return this.isOpen ? this.menu.id : '';
|
||||
}
|
||||
constructor(private element: ElementRef, private service: DropdownOutletService) {
|
||||
}
|
||||
open() {
|
||||
if (!this.isOpen) {
|
||||
this.isOpen = true;
|
||||
this.isOpenChange.emit(true);
|
||||
this.menu.open(this.useOutlet, this.element.nativeElement);
|
||||
dropdownToggle?: DropdownToggle;
|
||||
@ContentChild(DropdownMenu, { static: false }) menu!: DropdownMenu;
|
||||
@Input() autoClose: boolean | 'outsideClick' = true;
|
||||
@Input() preventAutoCloseOnOutlet = false;
|
||||
@Input() hookToCanvas = false;
|
||||
@Input() focusOnOpen = true;
|
||||
@Input() focusOnClose = true;
|
||||
@Input() useOutlet = false;
|
||||
@Input() isOpen = false;
|
||||
@Output() isOpenChange = new EventEmitter<boolean>();
|
||||
get menuId() {
|
||||
return this.isOpen ? this.menu.id : '';
|
||||
}
|
||||
constructor(private element: ElementRef, private service: DropdownOutletService) {
|
||||
}
|
||||
open() {
|
||||
if (!this.isOpen) {
|
||||
this.isOpen = true;
|
||||
this.isOpenChange.emit(true);
|
||||
this.menu.open(this.useOutlet, this.element.nativeElement);
|
||||
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', this.closeHandler);
|
||||
document.addEventListener('keydown', this.closeHandler);
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', this.closeHandler);
|
||||
document.addEventListener('keydown', this.closeHandler);
|
||||
|
||||
if (this.focusOnOpen) {
|
||||
this.menu.focusFirstElement();
|
||||
}
|
||||
if (this.focusOnOpen) {
|
||||
this.menu.focusFirstElement();
|
||||
}
|
||||
|
||||
if (this.hookToCanvas) {
|
||||
const canvas = document.getElementById('canvas');
|
||||
if (this.hookToCanvas) {
|
||||
const canvas = document.getElementById('canvas');
|
||||
|
||||
if (canvas) {
|
||||
canvas.addEventListener('touchstart', this.canvasCloseHandler);
|
||||
canvas.addEventListener('mousedown', this.canvasCloseHandler);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
close() {
|
||||
if (this.isOpen) {
|
||||
this.isOpen = false;
|
||||
this.isOpenChange.emit(false);
|
||||
this.menu.close();
|
||||
if (canvas) {
|
||||
canvas.addEventListener('touchstart', this.canvasCloseHandler);
|
||||
canvas.addEventListener('mousedown', this.canvasCloseHandler);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
close() {
|
||||
if (this.isOpen) {
|
||||
this.isOpen = false;
|
||||
this.isOpenChange.emit(false);
|
||||
this.menu.close();
|
||||
|
||||
if (this.focusOnClose && this.dropdownToggle) {
|
||||
this.dropdownToggle.focus();
|
||||
}
|
||||
if (this.focusOnClose && this.dropdownToggle) {
|
||||
this.dropdownToggle.focus();
|
||||
}
|
||||
|
||||
document.removeEventListener('click', this.closeHandler);
|
||||
document.removeEventListener('keydown', this.closeHandler);
|
||||
document.removeEventListener('click', this.closeHandler);
|
||||
document.removeEventListener('keydown', this.closeHandler);
|
||||
|
||||
if (this.hookToCanvas) {
|
||||
const canvas = document.getElementById('canvas');
|
||||
if (this.hookToCanvas) {
|
||||
const canvas = document.getElementById('canvas');
|
||||
|
||||
if (canvas) {
|
||||
canvas.removeEventListener('touchstart', this.canvasCloseHandler);
|
||||
canvas.removeEventListener('mousedown', this.canvasCloseHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
toggle() {
|
||||
if (this.isOpen) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
private closeHandler: any = (e: KeyboardEvent) => {
|
||||
if (
|
||||
!e.keyCode
|
||||
&& (this.autoClose || (this.dropdownToggle && this.dropdownToggle.checkTarget(e)))
|
||||
&& !(this.preventAutoCloseOnOutlet && this.service.rootElement && this.service.rootElement.contains(e.target as any))
|
||||
&& !(this.autoClose === 'outsideClick' && this.menu.checkTarget(e))
|
||||
) {
|
||||
this.close();
|
||||
} else if (this.autoClose && e.keyCode === 27) { // esc
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
private canvasCloseHandler: any = () => this.close();
|
||||
if (canvas) {
|
||||
canvas.removeEventListener('touchstart', this.canvasCloseHandler);
|
||||
canvas.removeEventListener('mousedown', this.canvasCloseHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
toggle() {
|
||||
if (this.isOpen) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
private closeHandler: any = (e: KeyboardEvent) => {
|
||||
if (
|
||||
!e.keyCode
|
||||
&& (this.autoClose || (this.dropdownToggle && this.dropdownToggle.checkTarget(e)))
|
||||
&& !(this.preventAutoCloseOnOutlet && this.service.rootElement && this.service.rootElement.contains(e.target as any))
|
||||
&& !(this.autoClose === 'outsideClick' && this.menu.checkTarget(e))
|
||||
) {
|
||||
this.close();
|
||||
} else if (this.autoClose && e.keyCode === 27) { // esc
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
private canvasCloseHandler: any = () => this.close();
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[dropdownToggle]',
|
||||
host: {
|
||||
'aria-haspopup': 'true',
|
||||
'[attr.aria-expanded]': 'dropdown.isOpen',
|
||||
'[attr.aria-controls]': 'dropdown.isOpen ? dropdown.menuId : undefined',
|
||||
},
|
||||
selector: '[dropdownToggle]',
|
||||
host: {
|
||||
'aria-haspopup': 'true',
|
||||
'[attr.aria-expanded]': 'dropdown.isOpen',
|
||||
'[attr.aria-controls]': 'dropdown.isOpen ? dropdown.menuId : undefined',
|
||||
},
|
||||
})
|
||||
export class DropdownToggle {
|
||||
constructor(private element: ElementRef, public dropdown: Dropdown) {
|
||||
dropdown.dropdownToggle = this;
|
||||
}
|
||||
@HostListener('click')
|
||||
click() {
|
||||
this.dropdown.toggle();
|
||||
}
|
||||
checkTarget(e: Event) {
|
||||
return this.element.nativeElement.contains(e.target);
|
||||
}
|
||||
focus() {
|
||||
this.element.nativeElement.focus();
|
||||
}
|
||||
constructor(private element: ElementRef, public dropdown: Dropdown) {
|
||||
dropdown.dropdownToggle = this;
|
||||
}
|
||||
@HostListener('click')
|
||||
click() {
|
||||
this.dropdown.toggle();
|
||||
}
|
||||
checkTarget(e: Event) {
|
||||
return this.element.nativeElement.contains(e.target);
|
||||
}
|
||||
focus() {
|
||||
this.element.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export const dropdownDirectives = [Dropdown, DropdownToggle, DropdownMenu, DropdownOutlet];
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { Directive, ElementRef, Input, HostListener, HostBinding, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[fixToTop]',
|
||||
selector: '[fixToTop]',
|
||||
})
|
||||
export class FixToTop {
|
||||
@Input() fixToTopOffset = 0;
|
||||
@Output() fixToTop = new EventEmitter<boolean>();
|
||||
@HostBinding('class.fixed-to-top') fixed = false;
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
@HostListener('window:scroll')
|
||||
scroll() {
|
||||
const element = this.element.nativeElement as HTMLElement;
|
||||
const { top } = element.getBoundingClientRect();
|
||||
@Input() fixToTopOffset = 0;
|
||||
@Output() fixToTop = new EventEmitter<boolean>();
|
||||
@HostBinding('class.fixed-to-top') fixed = false;
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
@HostListener('window:scroll')
|
||||
scroll() {
|
||||
const element = this.element.nativeElement as HTMLElement;
|
||||
const { top } = element.getBoundingClientRect();
|
||||
|
||||
if (this.fixed !== top < this.fixToTopOffset) {
|
||||
this.fixed = top < this.fixToTopOffset;
|
||||
this.fixToTop.emit(this.fixed);
|
||||
}
|
||||
}
|
||||
if (this.fixed !== top < this.fixToTopOffset) {
|
||||
this.fixed = top < this.fixToTopOffset;
|
||||
this.fixToTop.emit(this.fixed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Directive, AfterViewInit, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[focusTitle]',
|
||||
host: {
|
||||
'tabindex': '-1',
|
||||
},
|
||||
selector: '[focusTitle]',
|
||||
host: {
|
||||
'tabindex': '-1',
|
||||
},
|
||||
})
|
||||
export class FocusTitle implements AfterViewInit {
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => this.element.nativeElement.focus());
|
||||
}
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => this.element.nativeElement.focus());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,55 +3,55 @@ import { isParentOf, focusFirstElement, findFocusableElements } from '../../../c
|
||||
import { isMobile } from '../../../client/data';
|
||||
|
||||
@Directive({
|
||||
selector: '[focusTrap]',
|
||||
selector: '[focusTrap]',
|
||||
})
|
||||
export class FocusTrap implements OnInit, OnDestroy {
|
||||
private on = true;
|
||||
private lastActiveElement?: HTMLElement;
|
||||
@Input() set focusTrap(value: boolean) {
|
||||
if (this.on !== value) {
|
||||
this.on = value;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.update();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.focusTrap = false;
|
||||
}
|
||||
private update() {
|
||||
if (!isMobile) {
|
||||
if (this.on) {
|
||||
this.lastActiveElement = document.activeElement as HTMLElement;
|
||||
document.addEventListener('focusin', this.focus);
|
||||
private on = true;
|
||||
private lastActiveElement?: HTMLElement;
|
||||
@Input() set focusTrap(value: boolean) {
|
||||
if (this.on !== value) {
|
||||
this.on = value;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.update();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.focusTrap = false;
|
||||
}
|
||||
private update() {
|
||||
if (!isMobile) {
|
||||
if (this.on) {
|
||||
this.lastActiveElement = document.activeElement as HTMLElement;
|
||||
document.addEventListener('focusin', this.focus);
|
||||
|
||||
if (!isParentOf(this.element.nativeElement, this.lastActiveElement)) {
|
||||
setTimeout(() => this.lastActiveElement = focusFirstElement(this.element.nativeElement));
|
||||
}
|
||||
} else {
|
||||
this.lastActiveElement = undefined;
|
||||
document.removeEventListener('focusin', this.focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
private focus = (e: Event) => {
|
||||
if (isParentOf(this.element.nativeElement, e.target as any)) {
|
||||
this.lastActiveElement = e.target as any;
|
||||
} else {
|
||||
const focusable = findFocusableElements(this.element.nativeElement);
|
||||
if (!isParentOf(this.element.nativeElement, this.lastActiveElement)) {
|
||||
setTimeout(() => this.lastActiveElement = focusFirstElement(this.element.nativeElement));
|
||||
}
|
||||
} else {
|
||||
this.lastActiveElement = undefined;
|
||||
document.removeEventListener('focusin', this.focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
private focus = (e: Event) => {
|
||||
if (isParentOf(this.element.nativeElement, e.target as any)) {
|
||||
this.lastActiveElement = e.target as any;
|
||||
} else {
|
||||
const focusable = findFocusableElements(this.element.nativeElement);
|
||||
|
||||
if (focusable.length) {
|
||||
if (this.lastActiveElement === focusable[0]) {
|
||||
this.lastActiveElement = focusable[focusable.length - 1];
|
||||
} else {
|
||||
this.lastActiveElement = focusable[0];
|
||||
}
|
||||
if (focusable.length) {
|
||||
if (this.lastActiveElement === focusable[0]) {
|
||||
this.lastActiveElement = focusable[focusable.length - 1];
|
||||
} else {
|
||||
this.lastActiveElement = focusable[0];
|
||||
}
|
||||
|
||||
this.lastActiveElement.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.lastActiveElement.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,57 +4,57 @@ import { hasFeatureFlag, featureFlagsChanged } from '../../../client/clientUtils
|
||||
import { Model } from '../../services/model';
|
||||
|
||||
@Directive({
|
||||
selector: '[hasFeature]',
|
||||
selector: '[hasFeature]',
|
||||
})
|
||||
export class HasFeature implements AfterViewInit, OnDestroy {
|
||||
private subscriptions: Subscription[] = [];
|
||||
private showing = false;
|
||||
private _flag: string | undefined = undefined;
|
||||
private _orMod = false;
|
||||
private _alsoIf = true;
|
||||
private ref?: EmbeddedViewRef<any>;
|
||||
constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef, private model: Model) {
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
this.subscriptions.push(featureFlagsChanged.subscribe(() => this.update()));
|
||||
this.subscriptions.push(this.model.accountChanged.subscribe(() => this.update()));
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(s => s.unsubscribe());
|
||||
}
|
||||
@Input()
|
||||
set hasFeature(value: string | undefined) {
|
||||
if (this._flag !== value) {
|
||||
this._flag = value;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
@Input()
|
||||
set hasFeatureOrMod(value: boolean) {
|
||||
if (this._orMod !== value) {
|
||||
this._orMod = value;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
@Input()
|
||||
set hasFeatureAlso(value: boolean) {
|
||||
if (this._alsoIf !== value) {
|
||||
this._alsoIf = value;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
private update() {
|
||||
const show = this._alsoIf && (hasFeatureFlag(this._flag as any) || (this._orMod && this.model.isMod));
|
||||
private subscriptions: Subscription[] = [];
|
||||
private showing = false;
|
||||
private _flag: string | undefined = undefined;
|
||||
private _orMod = false;
|
||||
private _alsoIf = true;
|
||||
private ref?: EmbeddedViewRef<any>;
|
||||
constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef, private model: Model) {
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
this.subscriptions.push(featureFlagsChanged.subscribe(() => this.update()));
|
||||
this.subscriptions.push(this.model.accountChanged.subscribe(() => this.update()));
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(s => s.unsubscribe());
|
||||
}
|
||||
@Input()
|
||||
set hasFeature(value: string | undefined) {
|
||||
if (this._flag !== value) {
|
||||
this._flag = value;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
@Input()
|
||||
set hasFeatureOrMod(value: boolean) {
|
||||
if (this._orMod !== value) {
|
||||
this._orMod = value;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
@Input()
|
||||
set hasFeatureAlso(value: boolean) {
|
||||
if (this._alsoIf !== value) {
|
||||
this._alsoIf = value;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
private update() {
|
||||
const show = this._alsoIf && (hasFeatureFlag(this._flag as any) || (this._orMod && this.model.isMod));
|
||||
|
||||
if (this.showing !== show) {
|
||||
this.showing = show;
|
||||
if (this.showing !== show) {
|
||||
this.showing = show;
|
||||
|
||||
if (show) {
|
||||
this.ref = this.ref || this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
} else {
|
||||
this.viewContainer.clear();
|
||||
this.ref = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (show) {
|
||||
this.ref = this.ref || this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
} else {
|
||||
this.viewContainer.clear();
|
||||
this.ref = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,19 @@ import { uniqueId } from 'lodash';
|
||||
import { findParentElement } from '../../../client/htmlUtils';
|
||||
|
||||
@Directive({
|
||||
selector: '[labelledBy]',
|
||||
selector: '[labelledBy]',
|
||||
})
|
||||
export class LabelledBy implements OnInit {
|
||||
@Input('labelledBy') selector!: string;
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
ngOnInit() {
|
||||
const element = this.element.nativeElement as HTMLElement;
|
||||
const target = findParentElement(element, this.selector);
|
||||
const id = element.id = element.id || uniqueId('labelled-by-');
|
||||
@Input('labelledBy') selector!: string;
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
ngOnInit() {
|
||||
const element = this.element.nativeElement as HTMLElement;
|
||||
const target = findParentElement(element, this.selector);
|
||||
const id = element.id = element.id || uniqueId('labelled-by-');
|
||||
|
||||
if (target) {
|
||||
target.setAttribute('aria-labelledby', id);
|
||||
}
|
||||
}
|
||||
if (target) {
|
||||
target.setAttribute('aria-labelledby', id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ import { Directive, HostBinding } from '@angular/core';
|
||||
import { RouterLinkActive } from '@angular/router';
|
||||
|
||||
@Directive({
|
||||
selector: '[linkCurrent]',
|
||||
selector: '[linkCurrent]',
|
||||
})
|
||||
export class LinkCurrent {
|
||||
constructor(private routerLinkActive: RouterLinkActive) {
|
||||
}
|
||||
@HostBinding('attr.aria-current')
|
||||
get current() {
|
||||
return this.routerLinkActive.isActive ? 'true' : undefined;
|
||||
}
|
||||
constructor(private routerLinkActive: RouterLinkActive) {
|
||||
}
|
||||
@HostBinding('attr.aria-current')
|
||||
get current() {
|
||||
return this.routerLinkActive.isActive ? 'true' : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ import { Directive, Input, HostBinding } from '@angular/core';
|
||||
import { getUrl } from '../../../client/rev';
|
||||
|
||||
@Directive({
|
||||
selector: '[revSrc]',
|
||||
selector: '[revSrc]',
|
||||
})
|
||||
export class RevSrc {
|
||||
@HostBinding() get src() {
|
||||
return this.revSrc && getUrl(this.revSrc);
|
||||
}
|
||||
@Input() revSrc?: string;
|
||||
@HostBinding() get src() {
|
||||
return this.revSrc && getUrl(this.revSrc);
|
||||
}
|
||||
@Input() revSrc?: string;
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@ import { Tabset } from '../tabset/tabset';
|
||||
import { StorageService } from '../../services/storageService';
|
||||
|
||||
@Directive({
|
||||
selector: '[saveActiveTab]',
|
||||
selector: '[saveActiveTab]',
|
||||
})
|
||||
export class SaveActiveTab implements OnInit, OnDestroy {
|
||||
@Input('saveActiveTab') key!: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(@Host() private tabset: Tabset, private storage: StorageService) {
|
||||
}
|
||||
ngOnInit() {
|
||||
// this.tabset.activeIndex = parseInt(this.storage.getItem(this.key) || '0', 10);
|
||||
this.tabset.select(parseInt(this.storage.getItem(this.key) || '0', 10));
|
||||
this.subscription = this.tabset.activeIndexChange.subscribe((i: number) => {
|
||||
this.storage.setItem(this.key, i.toString());
|
||||
});
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
@Input('saveActiveTab') key!: string;
|
||||
private subscription?: Subscription;
|
||||
constructor(@Host() private tabset: Tabset, private storage: StorageService) {
|
||||
}
|
||||
ngOnInit() {
|
||||
// this.tabset.activeIndex = parseInt(this.storage.getItem(this.key) || '0', 10);
|
||||
this.tabset.select(parseInt(this.storage.getItem(this.key) || '0', 10));
|
||||
this.subscription = this.tabset.activeIndexChange.subscribe((i: number) => {
|
||||
this.storage.setItem(this.key, i.toString());
|
||||
});
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,76 +5,76 @@ import { font } from '../../../client/fonts';
|
||||
import { getCharacterSprite } from '../../../graphics/spriteFont';
|
||||
|
||||
@Component({
|
||||
selector: 'emote-box',
|
||||
template: '<img #image class="emote-box pixelart" />',
|
||||
styles: ['.emote-box { pointer-events: none; }'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'emote-box',
|
||||
template: '<img #image class="emote-box pixelart" />',
|
||||
styles: ['.emote-box { pointer-events: none; }'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class EmoteBox implements AfterViewInit {
|
||||
@ViewChild('image', { static: true }) image!: ElementRef;
|
||||
private emoteValue = '';
|
||||
private scaleValue = 2;
|
||||
private initialized = false;
|
||||
constructor(private zone: NgZone) {
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
loadAndInitSpriteSheets()
|
||||
.then(() => {
|
||||
this.initialized = true;
|
||||
this.zone.runOutsideAngular(() => this.redraw());
|
||||
});
|
||||
}
|
||||
get emote() {
|
||||
return this.emoteValue;
|
||||
}
|
||||
@Input()
|
||||
set emote(value: string) {
|
||||
if (this.emoteValue !== value) {
|
||||
this.emoteValue = value;
|
||||
this.zone.runOutsideAngular(() => this.redraw());
|
||||
}
|
||||
}
|
||||
get scale() {
|
||||
return this.scaleValue;
|
||||
}
|
||||
@Input()
|
||||
set scale(value: number) {
|
||||
if (this.scaleValue !== value) {
|
||||
this.scaleValue = value;
|
||||
this.zone.runOutsideAngular(() => this.redraw());
|
||||
}
|
||||
}
|
||||
redraw() {
|
||||
if (this.initialized) {
|
||||
const emote = findEmoji(this.emote);
|
||||
const sprite = font && emote && getCharacterSprite(emote.symbol, font);
|
||||
const image = this.image.nativeElement as HTMLImageElement;
|
||||
@ViewChild('image', { static: true }) image!: ElementRef;
|
||||
private emoteValue = '';
|
||||
private scaleValue = 2;
|
||||
private initialized = false;
|
||||
constructor(private zone: NgZone) {
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
loadAndInitSpriteSheets()
|
||||
.then(() => {
|
||||
this.initialized = true;
|
||||
this.zone.runOutsideAngular(() => this.redraw());
|
||||
});
|
||||
}
|
||||
get emote() {
|
||||
return this.emoteValue;
|
||||
}
|
||||
@Input()
|
||||
set emote(value: string) {
|
||||
if (this.emoteValue !== value) {
|
||||
this.emoteValue = value;
|
||||
this.zone.runOutsideAngular(() => this.redraw());
|
||||
}
|
||||
}
|
||||
get scale() {
|
||||
return this.scaleValue;
|
||||
}
|
||||
@Input()
|
||||
set scale(value: number) {
|
||||
if (this.scaleValue !== value) {
|
||||
this.scaleValue = value;
|
||||
this.zone.runOutsideAngular(() => this.redraw());
|
||||
}
|
||||
}
|
||||
redraw() {
|
||||
if (this.initialized) {
|
||||
const emote = findEmoji(this.emote);
|
||||
const sprite = font && emote && getCharacterSprite(emote.symbol, font);
|
||||
const image = this.image.nativeElement as HTMLImageElement;
|
||||
|
||||
if (sprite) {
|
||||
const width = sprite.w + sprite.ox;
|
||||
const height = 10; // sprite.h + sprite.oy;
|
||||
if (sprite) {
|
||||
const width = sprite.w + sprite.ox;
|
||||
const height = 10; // sprite.h + sprite.oy;
|
||||
|
||||
image.style.width = `${width * this.scale}px`;
|
||||
image.style.height = `${height * this.scale}px`;
|
||||
image.style.marginTop = `${-this.scale}px`;
|
||||
image.style.display = 'inline-block';
|
||||
image.style.visibility = 'hidden';
|
||||
image.style.width = `${width * this.scale}px`;
|
||||
image.style.height = `${height * this.scale}px`;
|
||||
image.style.marginTop = `${-this.scale}px`;
|
||||
image.style.display = 'inline-block';
|
||||
image.style.visibility = 'hidden';
|
||||
|
||||
if (emote) {
|
||||
image.setAttribute('aria-label', emote.names[0]);
|
||||
}
|
||||
if (emote) {
|
||||
image.setAttribute('aria-label', emote.names[0]);
|
||||
}
|
||||
|
||||
getEmojiImageAsync(sprite, src => {
|
||||
image.src = src;
|
||||
image.alt = emote ? emote.symbol : '';
|
||||
image.style.visibility = 'visible';
|
||||
});
|
||||
} else {
|
||||
image.style.width = `0px`;
|
||||
image.style.height = `0px`;
|
||||
image.src = '';
|
||||
image.alt = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
getEmojiImageAsync(sprite, src => {
|
||||
image.src = src;
|
||||
image.alt = emote ? emote.symbol : '';
|
||||
image.style.visibility = 'visible';
|
||||
});
|
||||
} else {
|
||||
image.style.width = `0px`;
|
||||
image.style.height = `0px`;
|
||||
image.src = '';
|
||||
image.alt = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,46 +2,46 @@ import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { faLock } from '../../../client/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'fill-outline',
|
||||
templateUrl: 'fill-outline.pug',
|
||||
styleUrls: ['fill-outline.scss'],
|
||||
selector: 'fill-outline',
|
||||
templateUrl: 'fill-outline.pug',
|
||||
styleUrls: ['fill-outline.scss'],
|
||||
})
|
||||
export class FillOutline {
|
||||
readonly lockIcon = faLock;
|
||||
@Input() label = 'Color';
|
||||
@Input() indicatorColor = '';
|
||||
@Input() base?: string;
|
||||
@Input() fill?: string;
|
||||
@Output() fillChange = new EventEmitter<string>();
|
||||
@Input() outline?: string;
|
||||
@Output() outlineChange = new EventEmitter<string>();
|
||||
@Input() locked?: boolean;
|
||||
@Output() lockedChange = new EventEmitter<boolean>();
|
||||
@Input() nonLockable = false;
|
||||
@Input() outlineLocked = false;
|
||||
@Output() outlineLockedChange = new EventEmitter<boolean>();
|
||||
@Input() outlineHidden = false;
|
||||
@Output() change = new EventEmitter<void>();
|
||||
get hasLock() {
|
||||
return this.locked !== undefined;
|
||||
}
|
||||
onChange() {
|
||||
this.change.emit();
|
||||
}
|
||||
onFillChange(value: string) {
|
||||
this.fillChange.emit(value);
|
||||
this.onChange();
|
||||
}
|
||||
onOutlineChange(value: string) {
|
||||
this.outlineChange.emit(value);
|
||||
this.onChange();
|
||||
}
|
||||
onLockedChange(value: boolean) {
|
||||
this.lockedChange.emit(value);
|
||||
this.onChange();
|
||||
}
|
||||
onOutlineLockedChange(value: boolean) {
|
||||
this.outlineLockedChange.emit(value);
|
||||
this.onChange();
|
||||
}
|
||||
readonly lockIcon = faLock;
|
||||
@Input() label = 'Color';
|
||||
@Input() indicatorColor = '';
|
||||
@Input() base?: string;
|
||||
@Input() fill?: string;
|
||||
@Output() fillChange = new EventEmitter<string>();
|
||||
@Input() outline?: string;
|
||||
@Output() outlineChange = new EventEmitter<string>();
|
||||
@Input() locked?: boolean;
|
||||
@Output() lockedChange = new EventEmitter<boolean>();
|
||||
@Input() nonLockable = false;
|
||||
@Input() outlineLocked = false;
|
||||
@Output() outlineLockedChange = new EventEmitter<boolean>();
|
||||
@Input() outlineHidden = false;
|
||||
@Output() change = new EventEmitter<void>();
|
||||
get hasLock() {
|
||||
return this.locked !== undefined;
|
||||
}
|
||||
onChange() {
|
||||
this.change.emit();
|
||||
}
|
||||
onFillChange(value: string) {
|
||||
this.fillChange.emit(value);
|
||||
this.onChange();
|
||||
}
|
||||
onOutlineChange(value: string) {
|
||||
this.outlineChange.emit(value);
|
||||
this.onChange();
|
||||
}
|
||||
onLockedChange(value: boolean) {
|
||||
this.lockedChange.emit(value);
|
||||
this.onChange();
|
||||
}
|
||||
onOutlineLockedChange(value: boolean) {
|
||||
this.outlineLockedChange.emit(value);
|
||||
this.onChange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,55 +7,55 @@ import { removeItem } from '../../../common/utils';
|
||||
import { SettingsService } from '../../services/settingsService';
|
||||
|
||||
@Component({
|
||||
selector: 'friends-box',
|
||||
templateUrl: 'friends-box.pug',
|
||||
styleUrls: ['friends-box.scss'],
|
||||
selector: 'friends-box',
|
||||
templateUrl: 'friends-box.pug',
|
||||
styleUrls: ['friends-box.scss'],
|
||||
})
|
||||
export class FriendsBox {
|
||||
readonly friendsIcon = faUserFriends;
|
||||
readonly cogIcon = faCog;
|
||||
readonly addToPartyIcon = faUserPlus;
|
||||
readonly userOptionsIcon = faUserCog;
|
||||
readonly statusIcon = faCircle;
|
||||
@Output() sendMessage = new EventEmitter<Friend>();
|
||||
removing?: Friend;
|
||||
constructor(private settings: SettingsService, private model: Model, private game: PonyTownGame) {
|
||||
}
|
||||
get friends() {
|
||||
return this.model.friends;
|
||||
}
|
||||
get hidden() {
|
||||
return !!this.settings.account.hidden;
|
||||
}
|
||||
toggleHidden() {
|
||||
this.settings.account.hidden = !this.settings.account.hidden;
|
||||
this.settings.saveAccountSettings(this.settings.account);
|
||||
}
|
||||
toggle() {
|
||||
this.removing = undefined;
|
||||
}
|
||||
sendMessageTo(friend: Friend) {
|
||||
this.sendMessage.emit(friend);
|
||||
}
|
||||
inviteToParty(friend: Friend) {
|
||||
this.game.send(server => server.playerAction(friend.entityId, PlayerAction.InviteToParty, undefined));
|
||||
}
|
||||
remove(friend: Friend) {
|
||||
this.removing = friend;
|
||||
}
|
||||
cancelRemove() {
|
||||
this.removing = undefined;
|
||||
}
|
||||
confirmRemove() {
|
||||
if (this.removing && this.model.friends) {
|
||||
const { accountId } = this.removing;
|
||||
this.game.send(server => server.actionParam(Action.RemoveFriend, accountId));
|
||||
removeItem(this.model.friends, this.removing);
|
||||
this.removing = undefined;
|
||||
}
|
||||
}
|
||||
setStatus(status: string) {
|
||||
this.settings.account.hidden = status === 'invisible';
|
||||
this.settings.saveAccountSettings(this.settings.account);
|
||||
}
|
||||
readonly friendsIcon = faUserFriends;
|
||||
readonly cogIcon = faCog;
|
||||
readonly addToPartyIcon = faUserPlus;
|
||||
readonly userOptionsIcon = faUserCog;
|
||||
readonly statusIcon = faCircle;
|
||||
@Output() sendMessage = new EventEmitter<Friend>();
|
||||
removing?: Friend;
|
||||
constructor(private settings: SettingsService, private model: Model, private game: PonyTownGame) {
|
||||
}
|
||||
get friends() {
|
||||
return this.model.friends;
|
||||
}
|
||||
get hidden() {
|
||||
return !!this.settings.account.hidden;
|
||||
}
|
||||
toggleHidden() {
|
||||
this.settings.account.hidden = !this.settings.account.hidden;
|
||||
this.settings.saveAccountSettings(this.settings.account);
|
||||
}
|
||||
toggle() {
|
||||
this.removing = undefined;
|
||||
}
|
||||
sendMessageTo(friend: Friend) {
|
||||
this.sendMessage.emit(friend);
|
||||
}
|
||||
inviteToParty(friend: Friend) {
|
||||
this.game.send(server => server.playerAction(friend.entityId, PlayerAction.InviteToParty, undefined));
|
||||
}
|
||||
remove(friend: Friend) {
|
||||
this.removing = friend;
|
||||
}
|
||||
cancelRemove() {
|
||||
this.removing = undefined;
|
||||
}
|
||||
confirmRemove() {
|
||||
if (this.removing && this.model.friends) {
|
||||
const { accountId } = this.removing;
|
||||
this.game.send(server => server.actionParam(Action.RemoveFriend, accountId));
|
||||
removeItem(this.model.friends, this.removing);
|
||||
this.removing = undefined;
|
||||
}
|
||||
}
|
||||
setStatus(status: string) {
|
||||
this.settings.account.hidden = status === 'invisible';
|
||||
this.settings.saveAccountSettings(this.settings.account);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,24 @@ import { InstallService } from '../../services/installService';
|
||||
import { isMobile } from '../../../client/data';
|
||||
|
||||
@Component({
|
||||
selector: 'install-button',
|
||||
templateUrl: 'install-button.pug',
|
||||
styleUrls: ['install-button.scss'],
|
||||
selector: 'install-button',
|
||||
templateUrl: 'install-button.pug',
|
||||
styleUrls: ['install-button.scss'],
|
||||
})
|
||||
export class InstallButton {
|
||||
readonly closeIcon = faTimes;
|
||||
constructor(private installService: InstallService) {
|
||||
}
|
||||
get canInstall() {
|
||||
return this.installService.canInstall;
|
||||
}
|
||||
get isMobile() {
|
||||
return isMobile;
|
||||
}
|
||||
install() {
|
||||
this.installService.install();
|
||||
}
|
||||
dismiss() {
|
||||
this.installService.dismiss();
|
||||
}
|
||||
readonly closeIcon = faTimes;
|
||||
constructor(private installService: InstallService) {
|
||||
}
|
||||
get canInstall() {
|
||||
return this.installService.canInstall;
|
||||
}
|
||||
get isMobile() {
|
||||
return isMobile;
|
||||
}
|
||||
install() {
|
||||
this.installService.install();
|
||||
}
|
||||
dismiss() {
|
||||
this.installService.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,26 +7,26 @@ import { removeItem } from '../../../common/utils';
|
||||
import { Model } from '../../services/model';
|
||||
|
||||
@Component({
|
||||
selector: 'invites-modal',
|
||||
templateUrl: 'invites-modal.pug',
|
||||
selector: 'invites-modal',
|
||||
templateUrl: 'invites-modal.pug',
|
||||
})
|
||||
export class InvitesModal implements OnInit {
|
||||
@Output() close = new EventEmitter();
|
||||
invites: (SupporterInvite & { pony: PalettePonyInfo; })[] = [];
|
||||
error?: string;
|
||||
constructor(private model: Model, private game: PonyTownGame) {
|
||||
}
|
||||
get inviteLimit() {
|
||||
return this.model.supporterInviteLimit;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.game.send(server => server.getInvites())!
|
||||
.then(invites => invites.map(i => ({ ...i, pony: toPalette(decompressPonyString(i.info)) })))
|
||||
.then(invites => this.invites = invites);
|
||||
}
|
||||
remove(invite: SupporterInvite) {
|
||||
this.error = undefined;
|
||||
this.game.send(server => server.actionParam(Action.CancelSupporterInvite, invite.id));
|
||||
removeItem(this.invites, invite);
|
||||
}
|
||||
@Output() close = new EventEmitter();
|
||||
invites: (SupporterInvite & { pony: PalettePonyInfo; })[] = [];
|
||||
error?: string;
|
||||
constructor(private model: Model, private game: PonyTownGame) {
|
||||
}
|
||||
get inviteLimit() {
|
||||
return this.model.supporterInviteLimit;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.game.send(server => server.getInvites())!
|
||||
.then(invites => invites.map(i => ({ ...i, pony: toPalette(decompressPonyString(i.info)) })))
|
||||
.then(invites => this.invites = invites);
|
||||
}
|
||||
remove(invite: SupporterInvite) {
|
||||
this.error = undefined;
|
||||
this.game.send(server => server.actionParam(Action.CancelSupporterInvite, invite.id));
|
||||
removeItem(this.invites, invite);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'kbd-key',
|
||||
templateUrl: 'kbd-key.pug',
|
||||
selector: 'kbd-key',
|
||||
templateUrl: 'kbd-key.pug',
|
||||
})
|
||||
export class KbdKey {
|
||||
@Input() title?: string;
|
||||
@Input() title?: string;
|
||||
}
|
||||
|
||||
@@ -9,53 +9,53 @@ import { SettingsService } from '../../services/settingsService';
|
||||
import { REQUEST_DATE_OF_BIRTH } from '../../../common/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'menu-bar',
|
||||
templateUrl: 'menu-bar.pug',
|
||||
styleUrls: ['menu-bar.scss'],
|
||||
selector: 'menu-bar',
|
||||
templateUrl: 'menu-bar.pug',
|
||||
styleUrls: ['menu-bar.scss'],
|
||||
})
|
||||
export class MenuBar {
|
||||
readonly signUpProviders = signUpProviders;
|
||||
readonly signInProviders = signInProviders;
|
||||
readonly starIcon = faStar;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly userIcon = faUser;
|
||||
readonly alertIcon = faExclamationCircle;
|
||||
readonly cogIcon = faCog;
|
||||
readonly statusIcon = faCircle;
|
||||
@Input() logo = false;
|
||||
@Input() loading = false;
|
||||
@Input() loadingError = false;
|
||||
@Input() account?: AccountData;
|
||||
@Output() signOut = new EventEmitter();
|
||||
@Output() signIn = new EventEmitter<OAuthProvider>();
|
||||
constructor(private model: Model, private settings: SettingsService) {
|
||||
}
|
||||
get hasSupporterIcon() {
|
||||
return isSupporterOrPastSupporter(this.account);
|
||||
}
|
||||
get supporterTitle() {
|
||||
return supporterTitle(this.account);
|
||||
}
|
||||
get supporterClass() {
|
||||
return supporterClass(this.account);
|
||||
}
|
||||
get showAccountAlert() {
|
||||
return this.model.missingBirthdate && REQUEST_DATE_OF_BIRTH;
|
||||
}
|
||||
get hidden() {
|
||||
return !!this.settings.account.hidden;
|
||||
}
|
||||
icon(id: string) {
|
||||
return getProviderIcon(id);
|
||||
}
|
||||
signInTo(provider: OAuthProvider) {
|
||||
this.signIn.emit(provider);
|
||||
}
|
||||
@HostListener('window:resize')
|
||||
resize() {
|
||||
}
|
||||
setStatus(status: string) {
|
||||
this.settings.account.hidden = status === 'invisible';
|
||||
this.settings.saveAccountSettings(this.settings.account);
|
||||
}
|
||||
readonly signUpProviders = signUpProviders;
|
||||
readonly signInProviders = signInProviders;
|
||||
readonly starIcon = faStar;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly userIcon = faUser;
|
||||
readonly alertIcon = faExclamationCircle;
|
||||
readonly cogIcon = faCog;
|
||||
readonly statusIcon = faCircle;
|
||||
@Input() logo = false;
|
||||
@Input() loading = false;
|
||||
@Input() loadingError = false;
|
||||
@Input() account?: AccountData;
|
||||
@Output() signOut = new EventEmitter();
|
||||
@Output() signIn = new EventEmitter<OAuthProvider>();
|
||||
constructor(private model: Model, private settings: SettingsService) {
|
||||
}
|
||||
get hasSupporterIcon() {
|
||||
return isSupporterOrPastSupporter(this.account);
|
||||
}
|
||||
get supporterTitle() {
|
||||
return supporterTitle(this.account);
|
||||
}
|
||||
get supporterClass() {
|
||||
return supporterClass(this.account);
|
||||
}
|
||||
get showAccountAlert() {
|
||||
return this.model.missingBirthdate && REQUEST_DATE_OF_BIRTH;
|
||||
}
|
||||
get hidden() {
|
||||
return !!this.settings.account.hidden;
|
||||
}
|
||||
icon(id: string) {
|
||||
return getProviderIcon(id);
|
||||
}
|
||||
signInTo(provider: OAuthProvider) {
|
||||
this.signIn.emit(provider);
|
||||
}
|
||||
@HostListener('window:resize')
|
||||
resize() {
|
||||
}
|
||||
setStatus(status: string) {
|
||||
this.settings.account.hidden = status === 'invisible';
|
||||
this.settings.saveAccountSettings(this.settings.account);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import { Component, Input } from '@angular/core';
|
||||
import { emptyIcon } from '../../../client/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'menu-item',
|
||||
templateUrl: 'menu-item.pug',
|
||||
styleUrls: ['menu-item.scss'],
|
||||
selector: 'menu-item',
|
||||
templateUrl: 'menu-item.pug',
|
||||
styleUrls: ['menu-item.scss'],
|
||||
})
|
||||
export class MenuItem {
|
||||
@Input() route: any;
|
||||
@Input() name?: string;
|
||||
@Input() icon = emptyIcon;
|
||||
@Input() route: any;
|
||||
@Input() name?: string;
|
||||
@Input() icon = emptyIcon;
|
||||
}
|
||||
|
||||
@@ -9,89 +9,89 @@ const ageLabels = ['', 'M', 'A', '', '', '[M]', '[A]'];
|
||||
const ageTitles = ['Not set', 'Minor', 'Adult', '', '', 'Minor (locked)', 'Adult (locked)'];
|
||||
|
||||
@Component({
|
||||
selector: 'mod-box',
|
||||
templateUrl: 'mod-box.pug',
|
||||
styleUrls: ['mod-box.scss'],
|
||||
selector: 'mod-box',
|
||||
templateUrl: 'mod-box.pug',
|
||||
styleUrls: ['mod-box.scss'],
|
||||
})
|
||||
export class ModBox implements OnDestroy {
|
||||
readonly flagIcon = faFlag;
|
||||
readonly noteIcon = faStickyNote;
|
||||
readonly muteIcon = faMicrophoneSlash;
|
||||
readonly hideIcon = faEyeSlash;
|
||||
readonly moreIcon = faUserCog;
|
||||
readonly dangerIcon = faExclamationCircle;
|
||||
readonly timeouts = TIMEOUTS;
|
||||
@Input() pony!: Pony;
|
||||
isNoteOpen = false;
|
||||
constructor(private model: Model, private game: PonyTownGame) {
|
||||
}
|
||||
get ageLabel() {
|
||||
return ageLabels[this.modInfo && this.modInfo.age || 0];
|
||||
}
|
||||
get ageTitle() {
|
||||
return ageTitles[this.modInfo && this.modInfo.age || 0];
|
||||
}
|
||||
get modInfo() {
|
||||
return this.pony.modInfo;
|
||||
}
|
||||
get account() {
|
||||
return this.modInfo && this.modInfo.account;
|
||||
}
|
||||
get country() {
|
||||
return this.modInfo && this.modInfo.country;
|
||||
}
|
||||
get mute() {
|
||||
return this.modInfo && this.modInfo.mute;
|
||||
}
|
||||
get muteTooltip() {
|
||||
return this.mute ? (this.mute === 'perma' ? 'Permanently Muted' : `Muted for ${this.mute}`) : 'Mute';
|
||||
}
|
||||
get shadow() {
|
||||
return this.modInfo && this.modInfo.shadow;
|
||||
}
|
||||
get shadowTooltip() {
|
||||
return this.shadow ? (this.shadow === 'perma' ? 'Permanently Shadowed' : `Shadowed for ${this.shadow}`) : 'Shadow';
|
||||
}
|
||||
get counters() {
|
||||
return this.modInfo && this.modInfo.counters;
|
||||
}
|
||||
get hasCounters() {
|
||||
const counters = this.counters;
|
||||
return counters && (counters.spam || counters.swears || counters.timeouts);
|
||||
}
|
||||
get check() {
|
||||
return this.model.modCheck;
|
||||
}
|
||||
get note() {
|
||||
return this.modInfo && this.modInfo.note;
|
||||
}
|
||||
set note(value) {
|
||||
if (this.modInfo) {
|
||||
this.modInfo.note = value;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this.isNoteOpen) {
|
||||
this.blur();
|
||||
}
|
||||
}
|
||||
className(value: string) {
|
||||
return value ? (value === 'perma' ? 'btn-danger' : 'btn-warning') : 'btn-default';
|
||||
}
|
||||
report() {
|
||||
this.modAction(ModAction.Report);
|
||||
}
|
||||
setMute(value: number) {
|
||||
this.modAction(ModAction.Mute, value);
|
||||
}
|
||||
setShadow(value: number) {
|
||||
this.modAction(ModAction.Shadow, value);
|
||||
}
|
||||
blur() {
|
||||
this.game.send(server => server.setNote(this.pony.id, this.modInfo && this.modInfo.note || ''));
|
||||
this.isNoteOpen = false;
|
||||
}
|
||||
modAction(type: ModAction, param = 0) {
|
||||
return this.game.send(server => server.otherAction(this.pony.id, type, param));
|
||||
}
|
||||
readonly flagIcon = faFlag;
|
||||
readonly noteIcon = faStickyNote;
|
||||
readonly muteIcon = faMicrophoneSlash;
|
||||
readonly hideIcon = faEyeSlash;
|
||||
readonly moreIcon = faUserCog;
|
||||
readonly dangerIcon = faExclamationCircle;
|
||||
readonly timeouts = TIMEOUTS;
|
||||
@Input() pony!: Pony;
|
||||
isNoteOpen = false;
|
||||
constructor(private model: Model, private game: PonyTownGame) {
|
||||
}
|
||||
get ageLabel() {
|
||||
return ageLabels[this.modInfo && this.modInfo.age || 0];
|
||||
}
|
||||
get ageTitle() {
|
||||
return ageTitles[this.modInfo && this.modInfo.age || 0];
|
||||
}
|
||||
get modInfo() {
|
||||
return this.pony.modInfo;
|
||||
}
|
||||
get account() {
|
||||
return this.modInfo && this.modInfo.account;
|
||||
}
|
||||
get country() {
|
||||
return this.modInfo && this.modInfo.country;
|
||||
}
|
||||
get mute() {
|
||||
return this.modInfo && this.modInfo.mute;
|
||||
}
|
||||
get muteTooltip() {
|
||||
return this.mute ? (this.mute === 'perma' ? 'Permanently Muted' : `Muted for ${this.mute}`) : 'Mute';
|
||||
}
|
||||
get shadow() {
|
||||
return this.modInfo && this.modInfo.shadow;
|
||||
}
|
||||
get shadowTooltip() {
|
||||
return this.shadow ? (this.shadow === 'perma' ? 'Permanently Shadowed' : `Shadowed for ${this.shadow}`) : 'Shadow';
|
||||
}
|
||||
get counters() {
|
||||
return this.modInfo && this.modInfo.counters;
|
||||
}
|
||||
get hasCounters() {
|
||||
const counters = this.counters;
|
||||
return counters && (counters.spam || counters.swears || counters.timeouts);
|
||||
}
|
||||
get check() {
|
||||
return this.model.modCheck;
|
||||
}
|
||||
get note() {
|
||||
return this.modInfo && this.modInfo.note;
|
||||
}
|
||||
set note(value) {
|
||||
if (this.modInfo) {
|
||||
this.modInfo.note = value;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this.isNoteOpen) {
|
||||
this.blur();
|
||||
}
|
||||
}
|
||||
className(value: string) {
|
||||
return value ? (value === 'perma' ? 'btn-danger' : 'btn-warning') : 'btn-default';
|
||||
}
|
||||
report() {
|
||||
this.modAction(ModAction.Report);
|
||||
}
|
||||
setMute(value: number) {
|
||||
this.modAction(ModAction.Mute, value);
|
||||
}
|
||||
setShadow(value: number) {
|
||||
this.modAction(ModAction.Shadow, value);
|
||||
}
|
||||
blur() {
|
||||
this.game.send(server => server.setNote(this.pony.id, this.modInfo && this.modInfo.note || ''));
|
||||
this.isNoteOpen = false;
|
||||
}
|
||||
modAction(type: ModAction, param = 0) {
|
||||
return this.game.send(server => server.otherAction(this.pony.id, type, param));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,63 +6,63 @@ import { faBan } from '../../../client/icons';
|
||||
import { getPaletteInfo } from '../../../common/pony';
|
||||
|
||||
@Component({
|
||||
selector: 'notification-item',
|
||||
templateUrl: 'notification-item.pug',
|
||||
styleUrls: ['notification-item.scss'],
|
||||
selector: 'notification-item',
|
||||
templateUrl: 'notification-item.pug',
|
||||
styleUrls: ['notification-item.scss'],
|
||||
})
|
||||
export class NotificationItem implements OnDestroy {
|
||||
readonly banIcon = faBan;
|
||||
@Input() notification!: Notification;
|
||||
constructor(private game: PonyTownGame) {
|
||||
}
|
||||
get isOpen() {
|
||||
return this.notification.open;
|
||||
}
|
||||
set isOpen(value: boolean) {
|
||||
if (value) {
|
||||
this.game.notifications.forEach(n => n.open = false);
|
||||
}
|
||||
readonly banIcon = faBan;
|
||||
@Input() notification!: Notification;
|
||||
constructor(private game: PonyTownGame) {
|
||||
}
|
||||
get isOpen() {
|
||||
return this.notification.open;
|
||||
}
|
||||
set isOpen(value: boolean) {
|
||||
if (value) {
|
||||
this.game.notifications.forEach(n => n.open = false);
|
||||
}
|
||||
|
||||
this.notification.open = value;
|
||||
}
|
||||
get okButton() {
|
||||
return hasFlag(this.notification.flags, NotificationFlags.Ok);
|
||||
}
|
||||
get yesButton() {
|
||||
return hasFlag(this.notification.flags, NotificationFlags.Yes);
|
||||
}
|
||||
get acceptButton() {
|
||||
return hasFlag(this.notification.flags, NotificationFlags.Accept);
|
||||
}
|
||||
get noButton() {
|
||||
return hasFlag(this.notification.flags, NotificationFlags.No);
|
||||
}
|
||||
get rejectButton() {
|
||||
return hasFlag(this.notification.flags, NotificationFlags.Reject);
|
||||
}
|
||||
get ignoreButton() {
|
||||
return hasFlag(this.notification.flags, NotificationFlags.Ignore);
|
||||
}
|
||||
get paletteInfo() {
|
||||
return getPaletteInfo(this.notification.pony);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.isOpen = false;
|
||||
}
|
||||
accept() {
|
||||
this.game.send(server => server.acceptNotification(this.notification.id));
|
||||
}
|
||||
reject() {
|
||||
this.game.send(server => server.rejectNotification(this.notification.id));
|
||||
}
|
||||
ignore() {
|
||||
this.reject();
|
||||
this.notification.open = value;
|
||||
}
|
||||
get okButton() {
|
||||
return hasFlag(this.notification.flags, NotificationFlags.Ok);
|
||||
}
|
||||
get yesButton() {
|
||||
return hasFlag(this.notification.flags, NotificationFlags.Yes);
|
||||
}
|
||||
get acceptButton() {
|
||||
return hasFlag(this.notification.flags, NotificationFlags.Accept);
|
||||
}
|
||||
get noButton() {
|
||||
return hasFlag(this.notification.flags, NotificationFlags.No);
|
||||
}
|
||||
get rejectButton() {
|
||||
return hasFlag(this.notification.flags, NotificationFlags.Reject);
|
||||
}
|
||||
get ignoreButton() {
|
||||
return hasFlag(this.notification.flags, NotificationFlags.Ignore);
|
||||
}
|
||||
get paletteInfo() {
|
||||
return getPaletteInfo(this.notification.pony);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.isOpen = false;
|
||||
}
|
||||
accept() {
|
||||
this.game.send(server => server.acceptNotification(this.notification.id));
|
||||
}
|
||||
reject() {
|
||||
this.game.send(server => server.rejectNotification(this.notification.id));
|
||||
}
|
||||
ignore() {
|
||||
this.reject();
|
||||
|
||||
const pony = this.notification.pony;
|
||||
const pony = this.notification.pony;
|
||||
|
||||
if (pony !== this.game.player) {
|
||||
this.game.send(server => server.playerAction(pony.id, PlayerAction.Ignore, undefined));
|
||||
pony.playerState = setFlag(pony.playerState, EntityPlayerState.Ignored, true);
|
||||
}
|
||||
}
|
||||
if (pony !== this.game.player) {
|
||||
this.game.send(server => server.playerAction(pony.id, PlayerAction.Ignore, undefined));
|
||||
pony.playerState = setFlag(pony.playerState, EntityPlayerState.Ignored, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,29 +5,29 @@ import { faEllipsisV } from '../../../client/icons';
|
||||
const LIMIT = 8;
|
||||
|
||||
@Component({
|
||||
selector: 'notification-list',
|
||||
templateUrl: 'notification-list.pug',
|
||||
styleUrls: ['notification-list.scss'],
|
||||
selector: 'notification-list',
|
||||
templateUrl: 'notification-list.pug',
|
||||
styleUrls: ['notification-list.scss'],
|
||||
})
|
||||
export class NotificationList {
|
||||
readonly ellipsisIcon = faEllipsisV;
|
||||
@Input() notifications!: Notification[];
|
||||
@Input() set notificationsLength(value: number) {
|
||||
while (this.start > value) {
|
||||
this.prev();
|
||||
}
|
||||
}
|
||||
start = 0;
|
||||
get limit() {
|
||||
return this.start + LIMIT;
|
||||
}
|
||||
get hasMore() {
|
||||
return this.notifications.length > (this.start + this.limit);
|
||||
}
|
||||
next() {
|
||||
this.start += this.limit;
|
||||
}
|
||||
prev() {
|
||||
this.start -= this.start <= LIMIT ? LIMIT : LIMIT - 1;
|
||||
}
|
||||
readonly ellipsisIcon = faEllipsisV;
|
||||
@Input() notifications!: Notification[];
|
||||
@Input() set notificationsLength(value: number) {
|
||||
while (this.start > value) {
|
||||
this.prev();
|
||||
}
|
||||
}
|
||||
start = 0;
|
||||
get limit() {
|
||||
return this.start + LIMIT;
|
||||
}
|
||||
get hasMore() {
|
||||
return this.notifications.length > (this.start + this.limit);
|
||||
}
|
||||
next() {
|
||||
this.start += this.limit;
|
||||
}
|
||||
prev() {
|
||||
this.start -= this.start <= LIMIT ? LIMIT : LIMIT - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,27 +4,27 @@ import { Model } from '../../services/model';
|
||||
import { hardReload } from '../../../client/clientUtils';
|
||||
|
||||
@Component({
|
||||
selector: 'page-loader',
|
||||
templateUrl: 'page-loader.pug',
|
||||
styleUrls: ['page-loader.scss'],
|
||||
selector: 'page-loader',
|
||||
templateUrl: 'page-loader.pug',
|
||||
styleUrls: ['page-loader.scss'],
|
||||
})
|
||||
export class PageLoader {
|
||||
readonly spinnerIcon = faSpinner;
|
||||
constructor(private model: Model) {
|
||||
}
|
||||
get loading() {
|
||||
return this.model.loading;
|
||||
}
|
||||
get updating() {
|
||||
return this.model.updating;
|
||||
}
|
||||
get updatingTakesLongTime() {
|
||||
return this.model.updatingTakesLongTime;
|
||||
}
|
||||
get loadingError() {
|
||||
return this.model.loadingError;
|
||||
}
|
||||
reload() {
|
||||
hardReload();
|
||||
}
|
||||
readonly spinnerIcon = faSpinner;
|
||||
constructor(private model: Model) {
|
||||
}
|
||||
get loading() {
|
||||
return this.model.loading;
|
||||
}
|
||||
get updating() {
|
||||
return this.model.updating;
|
||||
}
|
||||
get updatingTakesLongTime() {
|
||||
return this.model.updatingTakesLongTime;
|
||||
}
|
||||
get loadingError() {
|
||||
return this.model.loadingError;
|
||||
}
|
||||
reload() {
|
||||
hardReload();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,20 +5,20 @@ import { partyLeaderIcon, offlineIcon } from '../../../client/icons';
|
||||
import { getPaletteInfo } from '../../../common/pony';
|
||||
|
||||
@Component({
|
||||
selector: 'party-box',
|
||||
templateUrl: 'party-box.pug',
|
||||
styleUrls: ['party-box.scss'],
|
||||
selector: 'party-box',
|
||||
templateUrl: 'party-box.pug',
|
||||
styleUrls: ['party-box.scss'],
|
||||
})
|
||||
export class PartyBox {
|
||||
readonly leaderIcon = partyLeaderIcon;
|
||||
readonly offlineIcon = offlineIcon;
|
||||
@Input() member!: PartyMember;
|
||||
constructor(private game: PonyTownGame) {
|
||||
}
|
||||
get paletteInfo() {
|
||||
return this.member.pony && getPaletteInfo(this.member.pony);
|
||||
}
|
||||
click() {
|
||||
this.game.select(this.member.pony);
|
||||
}
|
||||
readonly leaderIcon = partyLeaderIcon;
|
||||
readonly offlineIcon = offlineIcon;
|
||||
@Input() member!: PartyMember;
|
||||
constructor(private game: PonyTownGame) {
|
||||
}
|
||||
get paletteInfo() {
|
||||
return this.member.pony && getPaletteInfo(this.member.pony);
|
||||
}
|
||||
click() {
|
||||
this.game.select(this.member.pony);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,86 +8,86 @@ import { clamp } from '../../../common/utils';
|
||||
import { isPartyLeader } from '../../../client/partyUtils';
|
||||
|
||||
function visibleMembers(members: PartyMember[], max: number, start: number) {
|
||||
return members.length > max ? Math.max(max - (start > 0 ? 2 : 1), 1) : max;
|
||||
return members.length > max ? Math.max(max - (start > 0 ? 2 : 1), 1) : max;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'party-list',
|
||||
templateUrl: 'party-list.pug',
|
||||
styleUrls: ['party-list.scss'],
|
||||
selector: 'party-list',
|
||||
templateUrl: 'party-list.pug',
|
||||
styleUrls: ['party-list.scss'],
|
||||
})
|
||||
export class PartyList implements OnInit, OnDestroy {
|
||||
readonly ellipsisIcon = faEllipsisV;
|
||||
readonly leaderIcon = partyLeaderIcon;
|
||||
readonly cogIcon = faCog;
|
||||
hidden = false;
|
||||
start = 0;
|
||||
maxMembers = PARTY_LIMIT - 1;
|
||||
members: PartyMember[] = [];
|
||||
private subscription?: Subscription;
|
||||
constructor(private game: PonyTownGame) {
|
||||
}
|
||||
get hasParty() {
|
||||
return this.game.party !== undefined;
|
||||
}
|
||||
get isLeader() {
|
||||
return isPartyLeader(this.game);
|
||||
}
|
||||
get hasMore() {
|
||||
return this.members.length > (this.start + this.visible);
|
||||
}
|
||||
get visible() {
|
||||
return visibleMembers(this.members, this.maxMembers, this.start);
|
||||
}
|
||||
get limit() {
|
||||
return this.start + this.visible;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.subscription = this.game.onPartyUpdate.subscribe(() => this.update());
|
||||
this.resized();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
isMe(member: PartyMember) {
|
||||
return this.game.player && this.game.player.id === member.id;
|
||||
}
|
||||
leave() {
|
||||
this.game.send(server => server.leaveParty());
|
||||
}
|
||||
update() {
|
||||
if (this.hasParty) {
|
||||
this.members = this.game.party ? this.game.party.members.filter(m => !m.self) : [];
|
||||
readonly ellipsisIcon = faEllipsisV;
|
||||
readonly leaderIcon = partyLeaderIcon;
|
||||
readonly cogIcon = faCog;
|
||||
hidden = false;
|
||||
start = 0;
|
||||
maxMembers = PARTY_LIMIT - 1;
|
||||
members: PartyMember[] = [];
|
||||
private subscription?: Subscription;
|
||||
constructor(private game: PonyTownGame) {
|
||||
}
|
||||
get hasParty() {
|
||||
return this.game.party !== undefined;
|
||||
}
|
||||
get isLeader() {
|
||||
return isPartyLeader(this.game);
|
||||
}
|
||||
get hasMore() {
|
||||
return this.members.length > (this.start + this.visible);
|
||||
}
|
||||
get visible() {
|
||||
return visibleMembers(this.members, this.maxMembers, this.start);
|
||||
}
|
||||
get limit() {
|
||||
return this.start + this.visible;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.subscription = this.game.onPartyUpdate.subscribe(() => this.update());
|
||||
this.resized();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
isMe(member: PartyMember) {
|
||||
return this.game.player && this.game.player.id === member.id;
|
||||
}
|
||||
leave() {
|
||||
this.game.send(server => server.leaveParty());
|
||||
}
|
||||
update() {
|
||||
if (this.hasParty) {
|
||||
this.members = this.game.party ? this.game.party.members.filter(m => !m.self) : [];
|
||||
|
||||
while (this.start > 0 && this.members.length <= this.start) {
|
||||
this.start = 0;
|
||||
}
|
||||
} else {
|
||||
this.members = [];
|
||||
this.start = 0;
|
||||
}
|
||||
}
|
||||
@HostListener('window:resize')
|
||||
resized() {
|
||||
const padding = 140 + 110;
|
||||
const max = clamp(Math.floor((window.innerHeight - padding) / 43), 0, PARTY_LIMIT - 1);
|
||||
while (this.start > 0 && this.members.length <= this.start) {
|
||||
this.start = 0;
|
||||
}
|
||||
} else {
|
||||
this.members = [];
|
||||
this.start = 0;
|
||||
}
|
||||
}
|
||||
@HostListener('window:resize')
|
||||
resized() {
|
||||
const padding = 140 + 110;
|
||||
const max = clamp(Math.floor((window.innerHeight - padding) / 43), 0, PARTY_LIMIT - 1);
|
||||
|
||||
if (this.maxMembers !== max) {
|
||||
this.start = 0;
|
||||
this.maxMembers = max;
|
||||
}
|
||||
}
|
||||
next() {
|
||||
this.start += this.visible;
|
||||
}
|
||||
prev() {
|
||||
const max = this.members.length - 1;
|
||||
let start = 0;
|
||||
if (this.maxMembers !== max) {
|
||||
this.start = 0;
|
||||
this.maxMembers = max;
|
||||
}
|
||||
}
|
||||
next() {
|
||||
this.start += this.visible;
|
||||
}
|
||||
prev() {
|
||||
const max = this.members.length - 1;
|
||||
let start = 0;
|
||||
|
||||
while (start < max && (start + visibleMembers(this.members, this.maxMembers, start)) !== this.start) {
|
||||
start++;
|
||||
}
|
||||
while (start < max && (start + visibleMembers(this.members, this.maxMembers, start)) !== this.start) {
|
||||
start++;
|
||||
}
|
||||
|
||||
this.start = start;
|
||||
}
|
||||
this.start = start;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'siteName',
|
||||
name: 'siteName',
|
||||
})
|
||||
export class SiteNamePipe implements PipeTransform {
|
||||
transform(value: string | undefined) {
|
||||
const match = String(value || '').match(/(\w+)\.com/);
|
||||
return match && match[1];
|
||||
}
|
||||
transform(value: string | undefined) {
|
||||
const match = String(value || '').match(/(\w+)\.com/);
|
||||
return match && match[1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
|
||||
import { ServerInfo, AccountDataFlags } from '../../../common/interfaces';
|
||||
import { RequestError, delay, includes, hasFlag } from '../../../common/utils';
|
||||
import {
|
||||
WEBGL_CREATION_ERROR, ACCESS_ERROR, ACCOUNT_ERROR, BROWSER_NOT_SUPPORTED_ERROR, NAME_ERROR, VERSION_ERROR,
|
||||
OFFLINE_ERROR, PROTECTION_ERROR, NOT_AUTHENTICATED_ERROR, CHARACTER_LIMIT_ERROR
|
||||
WEBGL_CREATION_ERROR, ACCESS_ERROR, ACCOUNT_ERROR, BROWSER_NOT_SUPPORTED_ERROR, NAME_ERROR, VERSION_ERROR,
|
||||
OFFLINE_ERROR, PROTECTION_ERROR, NOT_AUTHENTICATED_ERROR, CHARACTER_LIMIT_ERROR
|
||||
} from '../../../common/errors';
|
||||
import { version } from '../../../client/data';
|
||||
import { GameService } from '../../services/gameService';
|
||||
@@ -16,168 +16,168 @@ import { ErrorReporter } from '../../services/errorReporter';
|
||||
import { REQUEST_DATE_OF_BIRTH } from '../../../common/constants';
|
||||
|
||||
const ignoredErrors = [
|
||||
WEBGL_CREATION_ERROR,
|
||||
BROWSER_NOT_SUPPORTED_ERROR,
|
||||
NAME_ERROR,
|
||||
OFFLINE_ERROR,
|
||||
VERSION_ERROR,
|
||||
ACCESS_ERROR,
|
||||
PROTECTION_ERROR,
|
||||
NOT_AUTHENTICATED_ERROR,
|
||||
CHARACTER_LIMIT_ERROR,
|
||||
'Saving in progress',
|
||||
WEBGL_CREATION_ERROR,
|
||||
BROWSER_NOT_SUPPORTED_ERROR,
|
||||
NAME_ERROR,
|
||||
OFFLINE_ERROR,
|
||||
VERSION_ERROR,
|
||||
ACCESS_ERROR,
|
||||
PROTECTION_ERROR,
|
||||
NOT_AUTHENTICATED_ERROR,
|
||||
CHARACTER_LIMIT_ERROR,
|
||||
'Saving in progress',
|
||||
];
|
||||
|
||||
@Component({
|
||||
selector: 'play-box',
|
||||
templateUrl: 'play-box.pug',
|
||||
styleUrls: ['play-box.scss'],
|
||||
selector: 'play-box',
|
||||
templateUrl: 'play-box.pug',
|
||||
styleUrls: ['play-box.scss'],
|
||||
})
|
||||
export class PlayBox implements OnInit {
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly warningIcon = faExclamationCircle;
|
||||
readonly infoIcon = faInfoCircle;
|
||||
readonly requestBirthdate = REQUEST_DATE_OF_BIRTH;
|
||||
@Output() errorChange = new EventEmitter<string | undefined>();
|
||||
@Input() label?: string;
|
||||
joining = false;
|
||||
failedToLoadImages = false;
|
||||
birthdate = '';
|
||||
birthdateSet = false;
|
||||
private locked = false;
|
||||
constructor(
|
||||
public gameService: GameService,
|
||||
public model: Model,
|
||||
private storage: StorageService,
|
||||
private errorReporter: ErrorReporter,
|
||||
) {
|
||||
}
|
||||
@Input()
|
||||
get error() {
|
||||
return this.gameService.error;
|
||||
}
|
||||
set error(value: string | undefined) {
|
||||
if (this.gameService) {
|
||||
this.gameService.error = value;
|
||||
this.errorChange.emit(value);
|
||||
}
|
||||
}
|
||||
get server() {
|
||||
return this.gameService.server;
|
||||
}
|
||||
set server(value: ServerInfo | undefined) {
|
||||
this.gameService.server = value;
|
||||
}
|
||||
get servers() {
|
||||
return this.gameService.servers;
|
||||
}
|
||||
get offline(): boolean {
|
||||
return this.gameService.offline;
|
||||
}
|
||||
get updateWarning() {
|
||||
return this.gameService.updateWarning;
|
||||
}
|
||||
get invalidVersion(): boolean {
|
||||
return !!(this.gameService.versionError || this.error === VERSION_ERROR
|
||||
|| (this.gameService.version && this.gameService.version !== version));
|
||||
}
|
||||
get protectionError(): boolean {
|
||||
return this.gameService.protectionError || this.error === PROTECTION_ERROR;
|
||||
}
|
||||
get canPlay(): boolean {
|
||||
return !!this.server && this.gameService.canPlay && !this.locked && !this.invalidVersion && !this.failedToLoadImages;
|
||||
}
|
||||
get isAccessError(): boolean {
|
||||
return this.error === ACCESS_ERROR || this.error === ACCOUNT_ERROR;
|
||||
}
|
||||
get isWebGLError(): boolean {
|
||||
return this.error === WEBGL_CREATION_ERROR;
|
||||
}
|
||||
get isBrowserError(): boolean {
|
||||
return this.error === BROWSER_NOT_SUPPORTED_ERROR;
|
||||
}
|
||||
get isOtherError(): boolean {
|
||||
return !!this.error && !this.invalidVersion && !this.isAccessError && !this.isWebGLError && !this.isBrowserError;
|
||||
}
|
||||
get ponyLimit() {
|
||||
return this.model.characterLimit;
|
||||
}
|
||||
get hasTooManyPonies(): boolean {
|
||||
return this.model.ponies.length > this.ponyLimit;
|
||||
}
|
||||
get isMarkedForMultiples(): boolean {
|
||||
const account = this.model.account;
|
||||
return !!account && hasFlag(account.flags, AccountDataFlags.Duplicates);
|
||||
}
|
||||
get isAndroidBrowser() {
|
||||
return isAndroidBrowser;
|
||||
}
|
||||
get isBrowserOutdated() {
|
||||
return !isAndroidBrowser && isBrowserOutdated && !this.storage.getBoolean('dismiss-outdated-browser');
|
||||
}
|
||||
get leftMessage() {
|
||||
return this.gameService.leftMessage;
|
||||
}
|
||||
get accountAlert() {
|
||||
return this.model.accountAlert;
|
||||
}
|
||||
ngOnInit() {
|
||||
loadAndInitSpriteSheets()
|
||||
.then(loaded => this.failedToLoadImages = !loaded);
|
||||
}
|
||||
play() {
|
||||
if (this.canPlay) {
|
||||
this.joining = true;
|
||||
this.locked = true;
|
||||
this.error = undefined;
|
||||
readonly spinnerIcon = faSpinner;
|
||||
readonly warningIcon = faExclamationCircle;
|
||||
readonly infoIcon = faInfoCircle;
|
||||
readonly requestBirthdate = REQUEST_DATE_OF_BIRTH;
|
||||
@Output() errorChange = new EventEmitter<string | undefined>();
|
||||
@Input() label?: string;
|
||||
joining = false;
|
||||
failedToLoadImages = false;
|
||||
birthdate = '';
|
||||
birthdateSet = false;
|
||||
private locked = false;
|
||||
constructor(
|
||||
public gameService: GameService,
|
||||
public model: Model,
|
||||
private storage: StorageService,
|
||||
private errorReporter: ErrorReporter,
|
||||
) {
|
||||
}
|
||||
@Input()
|
||||
get error() {
|
||||
return this.gameService.error;
|
||||
}
|
||||
set error(value: string | undefined) {
|
||||
if (this.gameService) {
|
||||
this.gameService.error = value;
|
||||
this.errorChange.emit(value);
|
||||
}
|
||||
}
|
||||
get server() {
|
||||
return this.gameService.server;
|
||||
}
|
||||
set server(value: ServerInfo | undefined) {
|
||||
this.gameService.server = value;
|
||||
}
|
||||
get servers() {
|
||||
return this.gameService.servers;
|
||||
}
|
||||
get offline(): boolean {
|
||||
return this.gameService.offline;
|
||||
}
|
||||
get updateWarning() {
|
||||
return this.gameService.updateWarning;
|
||||
}
|
||||
get invalidVersion(): boolean {
|
||||
return !!(this.gameService.versionError || this.error === VERSION_ERROR
|
||||
|| (this.gameService.version && this.gameService.version !== version));
|
||||
}
|
||||
get protectionError(): boolean {
|
||||
return this.gameService.protectionError || this.error === PROTECTION_ERROR;
|
||||
}
|
||||
get canPlay(): boolean {
|
||||
return !!this.server && this.gameService.canPlay && !this.locked && !this.invalidVersion && !this.failedToLoadImages;
|
||||
}
|
||||
get isAccessError(): boolean {
|
||||
return this.error === ACCESS_ERROR || this.error === ACCOUNT_ERROR;
|
||||
}
|
||||
get isWebGLError(): boolean {
|
||||
return this.error === WEBGL_CREATION_ERROR;
|
||||
}
|
||||
get isBrowserError(): boolean {
|
||||
return this.error === BROWSER_NOT_SUPPORTED_ERROR;
|
||||
}
|
||||
get isOtherError(): boolean {
|
||||
return !!this.error && !this.invalidVersion && !this.isAccessError && !this.isWebGLError && !this.isBrowserError;
|
||||
}
|
||||
get ponyLimit() {
|
||||
return this.model.characterLimit;
|
||||
}
|
||||
get hasTooManyPonies(): boolean {
|
||||
return this.model.ponies.length > this.ponyLimit;
|
||||
}
|
||||
get isMarkedForMultiples(): boolean {
|
||||
const account = this.model.account;
|
||||
return !!account && hasFlag(account.flags, AccountDataFlags.Duplicates);
|
||||
}
|
||||
get isAndroidBrowser() {
|
||||
return isAndroidBrowser;
|
||||
}
|
||||
get isBrowserOutdated() {
|
||||
return !isAndroidBrowser && isBrowserOutdated && !this.storage.getBoolean('dismiss-outdated-browser');
|
||||
}
|
||||
get leftMessage() {
|
||||
return this.gameService.leftMessage;
|
||||
}
|
||||
get accountAlert() {
|
||||
return this.model.accountAlert;
|
||||
}
|
||||
ngOnInit() {
|
||||
loadAndInitSpriteSheets()
|
||||
.then(loaded => this.failedToLoadImages = !loaded);
|
||||
}
|
||||
play() {
|
||||
if (this.canPlay) {
|
||||
this.joining = true;
|
||||
this.locked = true;
|
||||
this.error = undefined;
|
||||
|
||||
const delayTime = (!DEVELOPMENT && this.gameService.wasPlaying) ? 1500 : 10;
|
||||
const delayTime = (!DEVELOPMENT && this.gameService.wasPlaying) ? 1500 : 10;
|
||||
|
||||
delay(delayTime) // delay joing if user reloaded the game instead of leaving cleanly
|
||||
.then(() => this.model.savePony(this.model.pony))
|
||||
.then(pony => this.joining ? this.gameService.join(pony.id) : Promise.resolve())
|
||||
.catch((e: RequestError) => {
|
||||
if (!/^Cancelled/.test(e.message)) {
|
||||
this.error = e.message;
|
||||
delay(delayTime) // delay joing if user reloaded the game instead of leaving cleanly
|
||||
.then(() => this.model.savePony(this.model.pony))
|
||||
.then(pony => this.joining ? this.gameService.join(pony.id) : Promise.resolve())
|
||||
.catch((e: RequestError) => {
|
||||
if (!/^Cancelled/.test(e.message)) {
|
||||
this.error = e.message;
|
||||
|
||||
if (!includes(ignoredErrors, e.message) && !/shader/.test(e.message)) {
|
||||
this.errorReporter.reportError(e, { status: e.status, text: e.text });
|
||||
}
|
||||
if (!includes(ignoredErrors, e.message) && !/shader/.test(e.message)) {
|
||||
this.errorReporter.reportError(e, { status: e.status, text: e.text });
|
||||
}
|
||||
|
||||
DEVELOPMENT && console.error(e);
|
||||
}
|
||||
})
|
||||
.finally(() => this.joining = false)
|
||||
.then(() => delay(1500))
|
||||
.finally(() => this.locked = false);
|
||||
}
|
||||
}
|
||||
cancel() {
|
||||
this.gameService.leave('Cancelled joining');
|
||||
}
|
||||
reload() {
|
||||
location.reload(true);
|
||||
}
|
||||
hardReload() {
|
||||
hardReload();
|
||||
}
|
||||
hasFlag(server: ServerInfo) {
|
||||
return server.countryFlags && server.countryFlags.length;
|
||||
}
|
||||
getIcon(server: ServerInfo) {
|
||||
switch (server.flag) {
|
||||
case 'star': return faStar;
|
||||
case 'test': return faWrench;
|
||||
default: return faGlobe;
|
||||
}
|
||||
}
|
||||
dismissOutdatedBrowser() {
|
||||
this.storage.setBoolean('dismiss-outdated-browser', true);
|
||||
}
|
||||
saveBirthdate() {
|
||||
if (this.birthdate) {
|
||||
this.model.updateAccount({ birthdate: this.birthdate });
|
||||
this.birthdateSet = true;
|
||||
}
|
||||
}
|
||||
DEVELOPMENT && console.error(e);
|
||||
}
|
||||
})
|
||||
.finally(() => this.joining = false)
|
||||
.then(() => delay(1500))
|
||||
.finally(() => this.locked = false);
|
||||
}
|
||||
}
|
||||
cancel() {
|
||||
this.gameService.leave('Cancelled joining');
|
||||
}
|
||||
reload() {
|
||||
location.reload(true);
|
||||
}
|
||||
hardReload() {
|
||||
hardReload();
|
||||
}
|
||||
hasFlag(server: ServerInfo) {
|
||||
return server.countryFlags && server.countryFlags.length;
|
||||
}
|
||||
getIcon(server: ServerInfo) {
|
||||
switch (server.flag) {
|
||||
case 'star': return faStar;
|
||||
case 'test': return faWrench;
|
||||
default: return faGlobe;
|
||||
}
|
||||
}
|
||||
dismissOutdatedBrowser() {
|
||||
this.storage.setBoolean('dismiss-outdated-browser', true);
|
||||
}
|
||||
saveBirthdate() {
|
||||
if (this.birthdate) {
|
||||
this.model.updateAccount({ birthdate: this.birthdate });
|
||||
this.birthdateSet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ import { supporterLink } from '../../../client/data';
|
||||
import { GENERAL_RULES } from '../../../common/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'play-notice',
|
||||
templateUrl: 'play-notice.pug',
|
||||
selector: 'play-notice',
|
||||
templateUrl: 'play-notice.pug',
|
||||
})
|
||||
export class PlayNotice {
|
||||
readonly patreonLink = supporterLink;
|
||||
readonly rules = GENERAL_RULES;
|
||||
readonly patreonLink = supporterLink;
|
||||
readonly rules = GENERAL_RULES;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import { getPaletteInfo } from '../../../common/pony';
|
||||
import { Model } from '../../services/model';
|
||||
import { PonyTownGame } from '../../../client/game';
|
||||
import {
|
||||
partyLeaderIcon, faUserPlus, faUserTimes, faCheck, faMicrophoneSlash, faEyeSlash, faStar, faUserMinus,
|
||||
faUserCog, faComment
|
||||
partyLeaderIcon, faUserPlus, faUserTimes, faCheck, faMicrophoneSlash, faEyeSlash, faStar, faUserMinus,
|
||||
faUserCog, faComment
|
||||
} from '../../../client/icons';
|
||||
import { DAY } from '../../../common/constants';
|
||||
import { isPonyInParty, isPartyLeader } from '../../../client/partyUtils';
|
||||
@@ -14,100 +14,100 @@ import { isIgnored, isHidden, isFriend } from '../../../common/entityUtils';
|
||||
import { setFlag } from '../../../common/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'pony-box',
|
||||
templateUrl: 'pony-box.pug',
|
||||
styleUrls: ['pony-box.scss'],
|
||||
selector: 'pony-box',
|
||||
templateUrl: 'pony-box.pug',
|
||||
styleUrls: ['pony-box.scss'],
|
||||
})
|
||||
export class PonyBox {
|
||||
readonly leaderIcon = partyLeaderIcon;
|
||||
readonly inviteIcon = faUserPlus;
|
||||
readonly removeIcon = faUserTimes;
|
||||
readonly cogIcon = faUserCog;
|
||||
readonly checkIcon = faCheck;
|
||||
readonly ignoreIcon = faMicrophoneSlash;
|
||||
readonly hideIcon = faEyeSlash;
|
||||
readonly starIcon = faStar;
|
||||
readonly addFriendIcon = faUserPlus;
|
||||
readonly removeFriendIcon = faUserMinus;
|
||||
readonly messageIcon = faComment;
|
||||
isIgnored = isIgnored;
|
||||
isFriend = isFriend;
|
||||
removingFriend = false;
|
||||
@Input() pony?: Pony;
|
||||
@Output() sendMessage = new EventEmitter<Entity>();
|
||||
constructor(private model: Model, private game: PonyTownGame) {
|
||||
}
|
||||
get ignoredOrHidden() {
|
||||
return this.pony && (isIgnored(this.pony) || isHidden(this.pony));
|
||||
}
|
||||
get isMod() {
|
||||
return this.model.isMod;
|
||||
}
|
||||
get canInviteToParty() {
|
||||
return this.pony && (!this.game.party || (isPartyLeader(this.game) && !isPonyInParty(this.game.party, this.pony, true)));
|
||||
}
|
||||
get canRemoveFromParty() {
|
||||
return this.pony && isPartyLeader(this.game) && isPonyInParty(this.game.party, this.pony, true);
|
||||
}
|
||||
get canPromoteToLeader() {
|
||||
return this.pony && isPartyLeader(this.game) && isPonyInParty(this.game.party, this.pony, false);
|
||||
}
|
||||
get special() {
|
||||
const tag = getTag(this.pony && this.pony.tag);
|
||||
return tag && tag.name;
|
||||
}
|
||||
get specialClass() {
|
||||
const tag = getTag(this.pony && this.pony.tag);
|
||||
return tag && tag.tagClass;
|
||||
}
|
||||
get paletteInfo() {
|
||||
return this.pony && getPaletteInfo(this.pony);
|
||||
}
|
||||
inviteToParty() {
|
||||
this.playerAction(PlayerAction.InviteToParty);
|
||||
}
|
||||
removeFromParty() {
|
||||
this.playerAction(PlayerAction.RemoveFromParty);
|
||||
}
|
||||
promoteToLeader() {
|
||||
this.playerAction(PlayerAction.PromotePartyLeader);
|
||||
}
|
||||
toggleIgnore() {
|
||||
if (this.pony) {
|
||||
const ignored = isIgnored(this.pony);
|
||||
this.playerAction(ignored ? PlayerAction.Unignore : PlayerAction.Ignore);
|
||||
this.pony.playerState = setFlag(this.pony.playerState, EntityPlayerState.Ignored, !ignored);
|
||||
}
|
||||
}
|
||||
hidePlayer(days: number) {
|
||||
this.playerAction(PlayerAction.HidePlayer, days * DAY);
|
||||
}
|
||||
addFriend() {
|
||||
this.playerAction(PlayerAction.AddFriend);
|
||||
}
|
||||
removeFriend() {
|
||||
this.playerAction(PlayerAction.RemoveFriend);
|
||||
}
|
||||
private playerAction(type: PlayerAction, param: any = undefined) {
|
||||
const ponyId = this.pony && this.pony.id;
|
||||
readonly leaderIcon = partyLeaderIcon;
|
||||
readonly inviteIcon = faUserPlus;
|
||||
readonly removeIcon = faUserTimes;
|
||||
readonly cogIcon = faUserCog;
|
||||
readonly checkIcon = faCheck;
|
||||
readonly ignoreIcon = faMicrophoneSlash;
|
||||
readonly hideIcon = faEyeSlash;
|
||||
readonly starIcon = faStar;
|
||||
readonly addFriendIcon = faUserPlus;
|
||||
readonly removeFriendIcon = faUserMinus;
|
||||
readonly messageIcon = faComment;
|
||||
isIgnored = isIgnored;
|
||||
isFriend = isFriend;
|
||||
removingFriend = false;
|
||||
@Input() pony?: Pony;
|
||||
@Output() sendMessage = new EventEmitter<Entity>();
|
||||
constructor(private model: Model, private game: PonyTownGame) {
|
||||
}
|
||||
get ignoredOrHidden() {
|
||||
return this.pony && (isIgnored(this.pony) || isHidden(this.pony));
|
||||
}
|
||||
get isMod() {
|
||||
return this.model.isMod;
|
||||
}
|
||||
get canInviteToParty() {
|
||||
return this.pony && (!this.game.party || (isPartyLeader(this.game) && !isPonyInParty(this.game.party, this.pony, true)));
|
||||
}
|
||||
get canRemoveFromParty() {
|
||||
return this.pony && isPartyLeader(this.game) && isPonyInParty(this.game.party, this.pony, true);
|
||||
}
|
||||
get canPromoteToLeader() {
|
||||
return this.pony && isPartyLeader(this.game) && isPonyInParty(this.game.party, this.pony, false);
|
||||
}
|
||||
get special() {
|
||||
const tag = getTag(this.pony && this.pony.tag);
|
||||
return tag && tag.name;
|
||||
}
|
||||
get specialClass() {
|
||||
const tag = getTag(this.pony && this.pony.tag);
|
||||
return tag && tag.tagClass;
|
||||
}
|
||||
get paletteInfo() {
|
||||
return this.pony && getPaletteInfo(this.pony);
|
||||
}
|
||||
inviteToParty() {
|
||||
this.playerAction(PlayerAction.InviteToParty);
|
||||
}
|
||||
removeFromParty() {
|
||||
this.playerAction(PlayerAction.RemoveFromParty);
|
||||
}
|
||||
promoteToLeader() {
|
||||
this.playerAction(PlayerAction.PromotePartyLeader);
|
||||
}
|
||||
toggleIgnore() {
|
||||
if (this.pony) {
|
||||
const ignored = isIgnored(this.pony);
|
||||
this.playerAction(ignored ? PlayerAction.Unignore : PlayerAction.Ignore);
|
||||
this.pony.playerState = setFlag(this.pony.playerState, EntityPlayerState.Ignored, !ignored);
|
||||
}
|
||||
}
|
||||
hidePlayer(days: number) {
|
||||
this.playerAction(PlayerAction.HidePlayer, days * DAY);
|
||||
}
|
||||
addFriend() {
|
||||
this.playerAction(PlayerAction.AddFriend);
|
||||
}
|
||||
removeFriend() {
|
||||
this.playerAction(PlayerAction.RemoveFriend);
|
||||
}
|
||||
private playerAction(type: PlayerAction, param: any = undefined) {
|
||||
const ponyId = this.pony && this.pony.id;
|
||||
|
||||
if (ponyId) {
|
||||
this.game.send(server => server.playerAction(ponyId, type, param));
|
||||
}
|
||||
}
|
||||
sendMessageTo() {
|
||||
if (this.pony) {
|
||||
this.sendMessage.emit(this.pony);
|
||||
}
|
||||
}
|
||||
// supporter servers
|
||||
get canInviteToSupporterServers() {
|
||||
return false; // DEVELOPMENT; // TODO: check if ignored or hidden
|
||||
}
|
||||
get isInvitedToSupporterServers() {
|
||||
return false;
|
||||
}
|
||||
inviteToSupporterServers() {
|
||||
this.playerAction(PlayerAction.InviteToSupporterServers);
|
||||
}
|
||||
if (ponyId) {
|
||||
this.game.send(server => server.playerAction(ponyId, type, param));
|
||||
}
|
||||
}
|
||||
sendMessageTo() {
|
||||
if (this.pony) {
|
||||
this.sendMessage.emit(this.pony);
|
||||
}
|
||||
}
|
||||
// supporter servers
|
||||
get canInviteToSupporterServers() {
|
||||
return false; // DEVELOPMENT; // TODO: check if ignored or hidden
|
||||
}
|
||||
get isInvitedToSupporterServers() {
|
||||
return false;
|
||||
}
|
||||
inviteToSupporterServers() {
|
||||
this.playerAction(PlayerAction.InviteToSupporterServers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
Component, Input, AfterViewInit, OnChanges, ElementRef, ChangeDetectionStrategy, ViewChild, NgZone
|
||||
Component, Input, AfterViewInit, OnChanges, ElementRef, ChangeDetectionStrategy, ViewChild, NgZone
|
||||
} from '@angular/core';
|
||||
import { PalettePonyInfo } from '../../../common/interfaces';
|
||||
import { ContextSpriteBatch } from '../../../graphics/contextSpriteBatch';
|
||||
@@ -10,15 +10,15 @@ import { drawPony } from '../../../client/ponyDraw';
|
||||
import { paletteSpriteSheet } from '../../../generated/sprites';
|
||||
|
||||
const scales: { [key: string]: number } = {
|
||||
large: 3,
|
||||
medium: 2,
|
||||
small: 1,
|
||||
large: 3,
|
||||
medium: 2,
|
||||
small: 1,
|
||||
};
|
||||
|
||||
const sizes: { [key: string]: number } = {
|
||||
large: 100,
|
||||
medium: 66,
|
||||
small: 33,
|
||||
large: 100,
|
||||
medium: 66,
|
||||
small: 33,
|
||||
};
|
||||
|
||||
const BUFFER_SIZE = 34;
|
||||
@@ -26,61 +26,61 @@ const options = defaultDrawPonyOptions();
|
||||
const state = defaultPonyState();
|
||||
|
||||
@Component({
|
||||
selector: 'portrait-box',
|
||||
templateUrl: 'portrait-box.pug',
|
||||
styleUrls: ['portrait-box.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'portrait-box',
|
||||
templateUrl: 'portrait-box.pug',
|
||||
styleUrls: ['portrait-box.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PortraitBox implements AfterViewInit, OnChanges {
|
||||
@Input() noBorder = false;
|
||||
@Input() flip = false;
|
||||
@Input() size = 'large';
|
||||
@Input() pony: PalettePonyInfo | undefined = undefined;
|
||||
@ViewChild('canvas', { static: true }) canvas!: ElementRef;
|
||||
private frame = 0;
|
||||
private batch?: ContextSpriteBatch;
|
||||
constructor(private zone: NgZone) {
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
loadAndInitSpriteSheets()
|
||||
.then(() => this.redraw());
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.redraw();
|
||||
}
|
||||
private redraw() {
|
||||
this.frame = this.frame || this.zone.runOutsideAngular(() => requestAnimationFrame(() => {
|
||||
this.frame = 0;
|
||||
this.draw();
|
||||
}));
|
||||
}
|
||||
private draw() {
|
||||
const canvas = this.canvas.nativeElement as HTMLCanvasElement;
|
||||
const size = sizes[this.size];
|
||||
resizeCanvasWithRatio(canvas, size, size);
|
||||
@Input() noBorder = false;
|
||||
@Input() flip = false;
|
||||
@Input() size = 'large';
|
||||
@Input() pony: PalettePonyInfo | undefined = undefined;
|
||||
@ViewChild('canvas', { static: true }) canvas!: ElementRef;
|
||||
private frame = 0;
|
||||
private batch?: ContextSpriteBatch;
|
||||
constructor(private zone: NgZone) {
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
loadAndInitSpriteSheets()
|
||||
.then(() => this.redraw());
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.redraw();
|
||||
}
|
||||
private redraw() {
|
||||
this.frame = this.frame || this.zone.runOutsideAngular(() => requestAnimationFrame(() => {
|
||||
this.frame = 0;
|
||||
this.draw();
|
||||
}));
|
||||
}
|
||||
private draw() {
|
||||
const canvas = this.canvas.nativeElement as HTMLCanvasElement;
|
||||
const size = sizes[this.size];
|
||||
resizeCanvasWithRatio(canvas, size, size);
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
if (context) {
|
||||
context.save();
|
||||
context.fillStyle = '#444';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
if (context) {
|
||||
context.save();
|
||||
context.fillStyle = '#444';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (this.pony) {
|
||||
const scale = scales[this.size] * getPixelRatio();
|
||||
this.batch = this.batch || new ContextSpriteBatch(createCanvas(BUFFER_SIZE, BUFFER_SIZE));
|
||||
options.flipped = !this.flip;
|
||||
if (this.pony) {
|
||||
const scale = scales[this.size] * getPixelRatio();
|
||||
this.batch = this.batch || new ContextSpriteBatch(createCanvas(BUFFER_SIZE, BUFFER_SIZE));
|
||||
options.flipped = !this.flip;
|
||||
|
||||
this.batch.start(paletteSpriteSheet, 0);
|
||||
drawPony(this.batch, this.pony, state, 25, 54, options);
|
||||
this.batch.end();
|
||||
this.batch.start(paletteSpriteSheet, 0);
|
||||
drawPony(this.batch, this.pony, state, 25, 54, options);
|
||||
this.batch.end();
|
||||
|
||||
disableImageSmoothing(context);
|
||||
context.scale(this.flip ? scale : -scale, scale);
|
||||
context.drawImage(this.batch.canvas, this.flip ? 0 : -BUFFER_SIZE, 0);
|
||||
}
|
||||
disableImageSmoothing(context);
|
||||
context.scale(this.flip ? scale : -scale, scale);
|
||||
context.drawImage(this.batch.canvas, this.flip ? 0 : -BUFFER_SIZE, 0);
|
||||
}
|
||||
|
||||
context.restore();
|
||||
}
|
||||
}
|
||||
context.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,20 @@ import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { times } from 'lodash';
|
||||
|
||||
@Component({
|
||||
selector: 'scale-picker',
|
||||
templateUrl: 'scale-picker.pug',
|
||||
selector: 'scale-picker',
|
||||
templateUrl: 'scale-picker.pug',
|
||||
})
|
||||
export class ScalePicker {
|
||||
@Input() scale = 1;
|
||||
@Output() scaleChange = new EventEmitter<number>();
|
||||
scales = [1, 2, 3, 4];
|
||||
@Input() set maxScale(value: number) {
|
||||
this.scales = times(value, i => i + 1);
|
||||
}
|
||||
setScale(value: number) {
|
||||
if (value !== this.scale) {
|
||||
this.scale = value;
|
||||
this.scaleChange.emit(value);
|
||||
}
|
||||
}
|
||||
@Input() scale = 1;
|
||||
@Output() scaleChange = new EventEmitter<number>();
|
||||
scales = [1, 2, 3, 4];
|
||||
@Input() set maxScale(value: number) {
|
||||
this.scales = times(value, i => i + 1);
|
||||
}
|
||||
setScale(value: number) {
|
||||
if (value !== this.scale) {
|
||||
this.scale = value;
|
||||
this.scaleChange.emit(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,67 +7,67 @@ const FILLS = ['Orange', 'DodgerBlue', 'LimeGreen', 'Orchid', 'crimson', 'Aquama
|
||||
const OUTLINES = ['Chocolate', 'SteelBlue', 'ForestGreen', 'DarkOrchid', 'darkred', 'DarkTurquoise'];
|
||||
|
||||
@Directive({
|
||||
selector: '[setOutlineHidden]',
|
||||
selector: '[setOutlineHidden]',
|
||||
})
|
||||
export class SetOutlineHidden {
|
||||
@Input() setOutlineHidden = false;
|
||||
@Input() setOutlineHidden = false;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'set-selection',
|
||||
templateUrl: 'set-selection.pug',
|
||||
styleUrls: ['set-selection.scss'],
|
||||
selector: 'set-selection',
|
||||
templateUrl: 'set-selection.pug',
|
||||
styleUrls: ['set-selection.scss'],
|
||||
})
|
||||
export class SetSelection implements OnChanges {
|
||||
readonly exampleFills = FILLS;
|
||||
readonly exampleOutlines = OUTLINES;
|
||||
@Input() label?: string;
|
||||
@Input() base?: string;
|
||||
@Input() set?: SpriteSet<string>;
|
||||
@Input() sets?: ColorExtraSets;
|
||||
@Input() sprites?: ColorExtraSet;
|
||||
@Input() circle?: string;
|
||||
@Input() outlineHidden = false;
|
||||
@Input() nonLockable = false;
|
||||
@Input() compact = false;
|
||||
@Input() onlyPatterns = false;
|
||||
@Input() darken = true;
|
||||
@Output() change = new EventEmitter<void>();
|
||||
constructor(@Optional() private hidden: SetOutlineHidden) {
|
||||
}
|
||||
get isOutlineHidden() {
|
||||
return this.hidden ? this.hidden.setOutlineHidden : this.outlineHidden;
|
||||
}
|
||||
get patternColors() {
|
||||
const set = this.getSet();
|
||||
const pat = this.set && set && set[this.set.pattern || 0];
|
||||
readonly exampleFills = FILLS;
|
||||
readonly exampleOutlines = OUTLINES;
|
||||
@Input() label?: string;
|
||||
@Input() base?: string;
|
||||
@Input() set?: SpriteSet<string>;
|
||||
@Input() sets?: ColorExtraSets;
|
||||
@Input() sprites?: ColorExtraSet;
|
||||
@Input() circle?: string;
|
||||
@Input() outlineHidden = false;
|
||||
@Input() nonLockable = false;
|
||||
@Input() compact = false;
|
||||
@Input() onlyPatterns = false;
|
||||
@Input() darken = true;
|
||||
@Output() change = new EventEmitter<void>();
|
||||
constructor(@Optional() private hidden: SetOutlineHidden) {
|
||||
}
|
||||
get isOutlineHidden() {
|
||||
return this.hidden ? this.hidden.setOutlineHidden : this.outlineHidden;
|
||||
}
|
||||
get patternColors() {
|
||||
const set = this.getSet();
|
||||
const pat = this.set && set && set[this.set.pattern || 0];
|
||||
|
||||
if (pat && !pat.colors) {
|
||||
return 0;
|
||||
} else if (pat) {
|
||||
return getColorCount(pat);
|
||||
} else {
|
||||
return this.nonLockable ? 1 : 0;
|
||||
}
|
||||
}
|
||||
get showColorPatterns(): boolean {
|
||||
const type = this.set && this.set.type || 0;
|
||||
const set = this.sets && this.sets[type];
|
||||
return !!set && set.length > 1;
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.sprites = this.sets ? this.sets.map(s => s ? s[0] : undefined) : undefined;
|
||||
}
|
||||
onChange() {
|
||||
const set = this.getSet();
|
||||
if (pat && !pat.colors) {
|
||||
return 0;
|
||||
} else if (pat) {
|
||||
return getColorCount(pat);
|
||||
} else {
|
||||
return this.nonLockable ? 1 : 0;
|
||||
}
|
||||
}
|
||||
get showColorPatterns(): boolean {
|
||||
const type = this.set && this.set.type || 0;
|
||||
const set = this.sets && this.sets[type];
|
||||
return !!set && set.length > 1;
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.sprites = this.sets ? this.sets.map(s => s ? s[0] : undefined) : undefined;
|
||||
}
|
||||
onChange() {
|
||||
const set = this.getSet();
|
||||
|
||||
if (this.set && set) {
|
||||
this.set.pattern = clamp(this.set.pattern || 0, 0, set.length - 1);
|
||||
}
|
||||
if (this.set && set) {
|
||||
this.set.pattern = clamp(this.set.pattern || 0, 0, set.length - 1);
|
||||
}
|
||||
|
||||
this.change.emit();
|
||||
}
|
||||
private getSet(): ColorExtraSet | undefined {
|
||||
return this.set && this.sets && this.sets[this.set.type || 0];
|
||||
}
|
||||
this.change.emit();
|
||||
}
|
||||
private getSet(): ColorExtraSet | undefined {
|
||||
return this.set && this.sets && this.sets[this.set.type || 0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,124 +8,124 @@ import { Model } from '../../services/model';
|
||||
import { PonyTownGame } from '../../../client/game';
|
||||
import { Dropdown } from '../directives/dropdown';
|
||||
import {
|
||||
emptyIcon, faCog, faSearch, faSignOutAlt, faStepForward, faVolumeOff, faVolumeUp, faVolumeDown, faPlus, faMinus
|
||||
emptyIcon, faCog, faSearch, faSignOutAlt, faStepForward, faVolumeOff, faVolumeUp, faVolumeDown, faPlus, faMinus
|
||||
} from '../../../client/icons';
|
||||
import { SettingsService } from '../../services/settingsService';
|
||||
import { Audio } from '../../services/audio';
|
||||
|
||||
@Component({
|
||||
selector: 'settings-box',
|
||||
templateUrl: 'settings-box.pug',
|
||||
styleUrls: ['settings-box.scss'],
|
||||
selector: 'settings-box',
|
||||
templateUrl: 'settings-box.pug',
|
||||
styleUrls: ['settings-box.scss'],
|
||||
})
|
||||
export class SettingsBox implements OnInit, OnDestroy {
|
||||
readonly cogIcon = faCog;
|
||||
readonly searchIcon = faSearch;
|
||||
readonly signOutIcon = faSignOutAlt;
|
||||
readonly forwardIcon = faStepForward;
|
||||
readonly emptyIcon = emptyIcon;
|
||||
readonly plusIcon = faPlus;
|
||||
readonly minusIcon = faMinus;
|
||||
modalRef?: BsModalRef;
|
||||
time?: string;
|
||||
@ViewChild('dropdown', { static: true }) dropdown!: Dropdown;
|
||||
@ViewChild('actionsModal', { static: true }) actionsModal!: TemplateRef<any>;
|
||||
@ViewChild('settingsModal', { static: true }) settingsModal!: TemplateRef<any>;
|
||||
@ViewChild('invitesModal', { static: true }) invitesModal!: TemplateRef<any>;
|
||||
private subscription?: Subscription;
|
||||
constructor(
|
||||
private model: Model,
|
||||
private modalService: BsModalService,
|
||||
private settingsService: SettingsService,
|
||||
private gameService: GameService,
|
||||
private game: PonyTownGame,
|
||||
private audio: Audio,
|
||||
private zone: NgZone,
|
||||
) {
|
||||
}
|
||||
get scale() {
|
||||
return this.game.scale;
|
||||
}
|
||||
get volume() {
|
||||
return this.game.volume;
|
||||
}
|
||||
set volume(value: number) {
|
||||
this.settingsService.browser.volume = value;
|
||||
this.settingsService.saveBrowserSettings();
|
||||
this.audio.setVolume(value);
|
||||
}
|
||||
get server() {
|
||||
return this.gameService.server && this.gameService.server.name || '';
|
||||
}
|
||||
get settings() {
|
||||
return this.model.account && this.model.account.settings || {};
|
||||
}
|
||||
get track() {
|
||||
return this.game.audio.trackName;
|
||||
}
|
||||
get volumeIcon() {
|
||||
return this.volume === 0 ? faVolumeOff : (this.volume < 50 ? faVolumeDown : faVolumeUp);
|
||||
}
|
||||
get isMod() {
|
||||
return this.model.isMod;
|
||||
}
|
||||
get hasInvites() {
|
||||
return this.isMod; // TEMP
|
||||
}
|
||||
ngOnInit() {
|
||||
this.game.onClock
|
||||
.pipe(
|
||||
distinctUntilChanged(),
|
||||
)
|
||||
.subscribe(text => {
|
||||
if (this.dropdown.isOpen) {
|
||||
this.zone.run(() => this.time = text);
|
||||
} else {
|
||||
this.time = text;
|
||||
}
|
||||
});
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
toggleVolume() {
|
||||
this.volume = this.volume === 0 ? 50 : 0;
|
||||
}
|
||||
volumeStarted() {
|
||||
this.game.audio.forcePlay();
|
||||
}
|
||||
nextTrack() {
|
||||
this.game.audio.playRandomTrack();
|
||||
}
|
||||
leave() {
|
||||
this.gameService.leave('From settings dropdown');
|
||||
this.dropdown.close();
|
||||
}
|
||||
zoomOut() {
|
||||
this.game.zoomOut();
|
||||
}
|
||||
zoomIn() {
|
||||
this.game.zoomIn();
|
||||
}
|
||||
unhideAllHiddenPlayers() {
|
||||
this.game.send(server => server.action(Action.UnhideAllHiddenPlayers));
|
||||
this.dropdown.close();
|
||||
}
|
||||
openModal(template: TemplateRef<any>) {
|
||||
this.modalRef = this.modalService.show(template, { ignoreBackdropClick: true });
|
||||
}
|
||||
openSettings() {
|
||||
this.openModal(this.settingsModal);
|
||||
this.dropdown.close();
|
||||
}
|
||||
openActions() {
|
||||
this.openModal(this.actionsModal);
|
||||
this.dropdown.close();
|
||||
}
|
||||
openInvites() {
|
||||
if (BETA) {
|
||||
this.openModal(this.invitesModal);
|
||||
this.dropdown.close();
|
||||
}
|
||||
}
|
||||
readonly cogIcon = faCog;
|
||||
readonly searchIcon = faSearch;
|
||||
readonly signOutIcon = faSignOutAlt;
|
||||
readonly forwardIcon = faStepForward;
|
||||
readonly emptyIcon = emptyIcon;
|
||||
readonly plusIcon = faPlus;
|
||||
readonly minusIcon = faMinus;
|
||||
modalRef?: BsModalRef;
|
||||
time?: string;
|
||||
@ViewChild('dropdown', { static: true }) dropdown!: Dropdown;
|
||||
@ViewChild('actionsModal', { static: true }) actionsModal!: TemplateRef<any>;
|
||||
@ViewChild('settingsModal', { static: true }) settingsModal!: TemplateRef<any>;
|
||||
@ViewChild('invitesModal', { static: true }) invitesModal!: TemplateRef<any>;
|
||||
private subscription?: Subscription;
|
||||
constructor(
|
||||
private model: Model,
|
||||
private modalService: BsModalService,
|
||||
private settingsService: SettingsService,
|
||||
private gameService: GameService,
|
||||
private game: PonyTownGame,
|
||||
private audio: Audio,
|
||||
private zone: NgZone,
|
||||
) {
|
||||
}
|
||||
get scale() {
|
||||
return this.game.scale;
|
||||
}
|
||||
get volume() {
|
||||
return this.game.volume;
|
||||
}
|
||||
set volume(value: number) {
|
||||
this.settingsService.browser.volume = value;
|
||||
this.settingsService.saveBrowserSettings();
|
||||
this.audio.setVolume(value);
|
||||
}
|
||||
get server() {
|
||||
return this.gameService.server && this.gameService.server.name || '';
|
||||
}
|
||||
get settings() {
|
||||
return this.model.account && this.model.account.settings || {};
|
||||
}
|
||||
get track() {
|
||||
return this.game.audio.trackName;
|
||||
}
|
||||
get volumeIcon() {
|
||||
return this.volume === 0 ? faVolumeOff : (this.volume < 50 ? faVolumeDown : faVolumeUp);
|
||||
}
|
||||
get isMod() {
|
||||
return this.model.isMod;
|
||||
}
|
||||
get hasInvites() {
|
||||
return this.isMod; // TEMP
|
||||
}
|
||||
ngOnInit() {
|
||||
this.game.onClock
|
||||
.pipe(
|
||||
distinctUntilChanged(),
|
||||
)
|
||||
.subscribe(text => {
|
||||
if (this.dropdown.isOpen) {
|
||||
this.zone.run(() => this.time = text);
|
||||
} else {
|
||||
this.time = text;
|
||||
}
|
||||
});
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
toggleVolume() {
|
||||
this.volume = this.volume === 0 ? 50 : 0;
|
||||
}
|
||||
volumeStarted() {
|
||||
this.game.audio.forcePlay();
|
||||
}
|
||||
nextTrack() {
|
||||
this.game.audio.playRandomTrack();
|
||||
}
|
||||
leave() {
|
||||
this.gameService.leave('From settings dropdown');
|
||||
this.dropdown.close();
|
||||
}
|
||||
zoomOut() {
|
||||
this.game.zoomOut();
|
||||
}
|
||||
zoomIn() {
|
||||
this.game.zoomIn();
|
||||
}
|
||||
unhideAllHiddenPlayers() {
|
||||
this.game.send(server => server.action(Action.UnhideAllHiddenPlayers));
|
||||
this.dropdown.close();
|
||||
}
|
||||
openModal(template: TemplateRef<any>) {
|
||||
this.modalRef = this.modalService.show(template, { ignoreBackdropClick: true });
|
||||
}
|
||||
openSettings() {
|
||||
this.openModal(this.settingsModal);
|
||||
this.dropdown.close();
|
||||
}
|
||||
openActions() {
|
||||
this.openModal(this.actionsModal);
|
||||
this.dropdown.close();
|
||||
}
|
||||
openInvites() {
|
||||
if (BETA) {
|
||||
this.openModal(this.invitesModal);
|
||||
this.dropdown.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Subscription } from 'rxjs';
|
||||
import { AccountSettings, BrowserSettings } from '../../../common/interfaces';
|
||||
import { SettingsService } from '../../services/settingsService';
|
||||
import {
|
||||
DEFAULT_CHATLOG_OPACITY, MAX_CHATLOG_RANGE, MIN_CHATLOG_RANGE, isChatlogRangeUnlimited, MAX_FILTER_WORDS_LENGTH
|
||||
DEFAULT_CHATLOG_OPACITY, MAX_CHATLOG_RANGE, MIN_CHATLOG_RANGE, isChatlogRangeUnlimited, MAX_FILTER_WORDS_LENGTH
|
||||
} from '../../../common/constants';
|
||||
import { StorageService } from '../../services/storageService';
|
||||
import { cloneDeep } from '../../../common/utils';
|
||||
@@ -12,111 +12,111 @@ import { updateRangeIndicator } from '../../../client/clientUtils';
|
||||
import { faSlidersH, faCommentSlash, faGamepad, faImage } from '../../../client/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'settings-modal',
|
||||
templateUrl: 'settings-modal.pug',
|
||||
styleUrls: ['settings-modal.scss'],
|
||||
selector: 'settings-modal',
|
||||
templateUrl: 'settings-modal.pug',
|
||||
styleUrls: ['settings-modal.scss'],
|
||||
})
|
||||
export class SettingsModal implements OnInit, OnDestroy {
|
||||
readonly maxChatlogRange = MAX_CHATLOG_RANGE;
|
||||
readonly minChatlogRange = MIN_CHATLOG_RANGE;
|
||||
readonly gameIcon = faSlidersH;
|
||||
readonly chatIcon = faCommentSlash;
|
||||
readonly filtersIcon = faCommentSlash;
|
||||
readonly controlsIcon = faGamepad;
|
||||
readonly graphicsIcon = faImage;
|
||||
@Output() close = new EventEmitter();
|
||||
account: AccountSettings = {};
|
||||
browser: BrowserSettings = {};
|
||||
accountBackup: AccountSettings = {};
|
||||
browserBackup: BrowserSettings = {};
|
||||
private done = false;
|
||||
private subscription?: Subscription;
|
||||
constructor(
|
||||
private settingsService: SettingsService,
|
||||
private storage: StorageService,
|
||||
private game: PonyTownGame,
|
||||
) {
|
||||
}
|
||||
get pane() {
|
||||
return this.storage.getItem('settings-modal-pane') || 'game';
|
||||
}
|
||||
set pane(value: string) {
|
||||
this.storage.setItem('settings-modal-pane', value);
|
||||
}
|
||||
get lockLowGraphicsMode() {
|
||||
return this.game.failedFBO;
|
||||
}
|
||||
get chatlogRangeText() {
|
||||
const range = this.account.chatlogRange;
|
||||
return isChatlogRangeUnlimited(range) ? 'entire screen' : `${range} tiles`;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.accountBackup = cloneDeep(this.settingsService.account);
|
||||
this.browserBackup = cloneDeep(this.settingsService.browser);
|
||||
this.account = this.settingsService.account;
|
||||
this.browser = this.settingsService.browser;
|
||||
this.setupDefaults();
|
||||
this.subscription = this.game.onLeft.subscribe(() => this.cancel());
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.finishChatlogRange();
|
||||
readonly maxChatlogRange = MAX_CHATLOG_RANGE;
|
||||
readonly minChatlogRange = MIN_CHATLOG_RANGE;
|
||||
readonly gameIcon = faSlidersH;
|
||||
readonly chatIcon = faCommentSlash;
|
||||
readonly filtersIcon = faCommentSlash;
|
||||
readonly controlsIcon = faGamepad;
|
||||
readonly graphicsIcon = faImage;
|
||||
@Output() close = new EventEmitter();
|
||||
account: AccountSettings = {};
|
||||
browser: BrowserSettings = {};
|
||||
accountBackup: AccountSettings = {};
|
||||
browserBackup: BrowserSettings = {};
|
||||
private done = false;
|
||||
private subscription?: Subscription;
|
||||
constructor(
|
||||
private settingsService: SettingsService,
|
||||
private storage: StorageService,
|
||||
private game: PonyTownGame,
|
||||
) {
|
||||
}
|
||||
get pane() {
|
||||
return this.storage.getItem('settings-modal-pane') || 'game';
|
||||
}
|
||||
set pane(value: string) {
|
||||
this.storage.setItem('settings-modal-pane', value);
|
||||
}
|
||||
get lockLowGraphicsMode() {
|
||||
return this.game.failedFBO;
|
||||
}
|
||||
get chatlogRangeText() {
|
||||
const range = this.account.chatlogRange;
|
||||
return isChatlogRangeUnlimited(range) ? 'entire screen' : `${range} tiles`;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.accountBackup = cloneDeep(this.settingsService.account);
|
||||
this.browserBackup = cloneDeep(this.settingsService.browser);
|
||||
this.account = this.settingsService.account;
|
||||
this.browser = this.settingsService.browser;
|
||||
this.setupDefaults();
|
||||
this.subscription = this.game.onLeft.subscribe(() => this.cancel());
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.finishChatlogRange();
|
||||
|
||||
if (!this.done) {
|
||||
this.cancel();
|
||||
}
|
||||
if (!this.done) {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
reset() {
|
||||
this.account = this.settingsService.account = {};
|
||||
this.browser = this.settingsService.browser = {};
|
||||
this.setupDefaults();
|
||||
}
|
||||
cancel() {
|
||||
this.done = true;
|
||||
this.settingsService.account = this.accountBackup;
|
||||
this.settingsService.browser = this.browserBackup;
|
||||
this.close.emit();
|
||||
}
|
||||
ok() {
|
||||
if (this.account.filterWords) {
|
||||
let filter = this.account.filterWords;
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
reset() {
|
||||
this.account = this.settingsService.account = {};
|
||||
this.browser = this.settingsService.browser = {};
|
||||
this.setupDefaults();
|
||||
}
|
||||
cancel() {
|
||||
this.done = true;
|
||||
this.settingsService.account = this.accountBackup;
|
||||
this.settingsService.browser = this.browserBackup;
|
||||
this.close.emit();
|
||||
}
|
||||
ok() {
|
||||
if (this.account.filterWords) {
|
||||
let filter = this.account.filterWords;
|
||||
|
||||
while (filter.length > MAX_FILTER_WORDS_LENGTH && /\s/.test(filter)) {
|
||||
filter = filter.trim().replace(/\s+\S+$/, '');
|
||||
}
|
||||
while (filter.length > MAX_FILTER_WORDS_LENGTH && /\s/.test(filter)) {
|
||||
filter = filter.trim().replace(/\s+\S+$/, '');
|
||||
}
|
||||
|
||||
if (filter.length > MAX_FILTER_WORDS_LENGTH) {
|
||||
this.account.filterWords = '';
|
||||
} else {
|
||||
this.account.filterWords = filter;
|
||||
}
|
||||
}
|
||||
if (filter.length > MAX_FILTER_WORDS_LENGTH) {
|
||||
this.account.filterWords = '';
|
||||
} else {
|
||||
this.account.filterWords = filter;
|
||||
}
|
||||
}
|
||||
|
||||
this.done = true;
|
||||
this.settingsService.saveAccountSettings(this.account);
|
||||
this.settingsService.saveBrowserSettings(this.browser);
|
||||
this.close.emit();
|
||||
}
|
||||
updateChatlogRange(range: number | undefined) {
|
||||
document.body.classList.add('translucent-modals');
|
||||
updateRangeIndicator(range, this.game);
|
||||
}
|
||||
finishChatlogRange() {
|
||||
document.body.classList.remove('translucent-modals');
|
||||
updateRangeIndicator(undefined, this.game);
|
||||
}
|
||||
private setupDefaults() {
|
||||
if (this.account.chatlogOpacity === undefined) {
|
||||
this.account.chatlogOpacity = DEFAULT_CHATLOG_OPACITY;
|
||||
}
|
||||
this.done = true;
|
||||
this.settingsService.saveAccountSettings(this.account);
|
||||
this.settingsService.saveBrowserSettings(this.browser);
|
||||
this.close.emit();
|
||||
}
|
||||
updateChatlogRange(range: number | undefined) {
|
||||
document.body.classList.add('translucent-modals');
|
||||
updateRangeIndicator(range, this.game);
|
||||
}
|
||||
finishChatlogRange() {
|
||||
document.body.classList.remove('translucent-modals');
|
||||
updateRangeIndicator(undefined, this.game);
|
||||
}
|
||||
private setupDefaults() {
|
||||
if (this.account.chatlogOpacity === undefined) {
|
||||
this.account.chatlogOpacity = DEFAULT_CHATLOG_OPACITY;
|
||||
}
|
||||
|
||||
if (this.account.chatlogRange === undefined) {
|
||||
this.account.chatlogRange = MAX_CHATLOG_RANGE;
|
||||
}
|
||||
if (this.account.chatlogRange === undefined) {
|
||||
this.account.chatlogRange = MAX_CHATLOG_RANGE;
|
||||
}
|
||||
|
||||
if (this.account.filterWords === undefined) {
|
||||
this.account.filterWords = '';
|
||||
}
|
||||
}
|
||||
if (this.account.filterWords === undefined) {
|
||||
this.account.filterWords = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,87 +75,87 @@ import { SaveActiveTab } from './directives/saveActiveTab';
|
||||
import { SiteNamePipe } from './pipes/siteName';
|
||||
|
||||
const declarations = [
|
||||
ActionBar,
|
||||
ActionButton,
|
||||
ActionsModal,
|
||||
BitmapBox,
|
||||
ButtMarkEditor,
|
||||
MenuBar,
|
||||
MenuItem,
|
||||
CharacterList,
|
||||
CharacterPreview,
|
||||
CharacterSelect,
|
||||
EmoteBox,
|
||||
SliderBar,
|
||||
SpriteBox,
|
||||
SpriteSelection,
|
||||
SupportButton,
|
||||
SupporterPony,
|
||||
SetSelection,
|
||||
SetOutlineHidden,
|
||||
CheckBox,
|
||||
PortraitBox,
|
||||
ScalePicker,
|
||||
SwapBox,
|
||||
ColorPicker,
|
||||
CustomCheckbox,
|
||||
DatePicker,
|
||||
SignInBox,
|
||||
PonyBox,
|
||||
ModBox,
|
||||
PartyBox,
|
||||
PartyList,
|
||||
SiteInfo,
|
||||
SettingsBox,
|
||||
SettingsModal,
|
||||
FillOutline,
|
||||
FriendsBox,
|
||||
InstallButton,
|
||||
InvitesModal,
|
||||
KbdKey,
|
||||
PlayBox,
|
||||
PlayNotice,
|
||||
PageLoader,
|
||||
ChatBox,
|
||||
ChatLog,
|
||||
SiteLinks,
|
||||
NotificationItem,
|
||||
NotificationList,
|
||||
...dropdownDirectives,
|
||||
...tabsetComponents,
|
||||
...draggableComponents,
|
||||
...virtualListDirectives,
|
||||
VirtualList,
|
||||
Anchor,
|
||||
BtnHighlight,
|
||||
BtnHighlightDanger,
|
||||
AgDrag,
|
||||
AgAutoFocus,
|
||||
LinkCurrent,
|
||||
LabelledBy,
|
||||
RevSrc,
|
||||
FixToTop,
|
||||
FocusTitle,
|
||||
FocusTrap,
|
||||
HasFeature,
|
||||
SaveActiveTab,
|
||||
SiteNamePipe,
|
||||
ActionBar,
|
||||
ActionButton,
|
||||
ActionsModal,
|
||||
BitmapBox,
|
||||
ButtMarkEditor,
|
||||
MenuBar,
|
||||
MenuItem,
|
||||
CharacterList,
|
||||
CharacterPreview,
|
||||
CharacterSelect,
|
||||
EmoteBox,
|
||||
SliderBar,
|
||||
SpriteBox,
|
||||
SpriteSelection,
|
||||
SupportButton,
|
||||
SupporterPony,
|
||||
SetSelection,
|
||||
SetOutlineHidden,
|
||||
CheckBox,
|
||||
PortraitBox,
|
||||
ScalePicker,
|
||||
SwapBox,
|
||||
ColorPicker,
|
||||
CustomCheckbox,
|
||||
DatePicker,
|
||||
SignInBox,
|
||||
PonyBox,
|
||||
ModBox,
|
||||
PartyBox,
|
||||
PartyList,
|
||||
SiteInfo,
|
||||
SettingsBox,
|
||||
SettingsModal,
|
||||
FillOutline,
|
||||
FriendsBox,
|
||||
InstallButton,
|
||||
InvitesModal,
|
||||
KbdKey,
|
||||
PlayBox,
|
||||
PlayNotice,
|
||||
PageLoader,
|
||||
ChatBox,
|
||||
ChatLog,
|
||||
SiteLinks,
|
||||
NotificationItem,
|
||||
NotificationList,
|
||||
...dropdownDirectives,
|
||||
...tabsetComponents,
|
||||
...draggableComponents,
|
||||
...virtualListDirectives,
|
||||
VirtualList,
|
||||
Anchor,
|
||||
BtnHighlight,
|
||||
BtnHighlightDanger,
|
||||
AgDrag,
|
||||
AgAutoFocus,
|
||||
LinkCurrent,
|
||||
LabelledBy,
|
||||
RevSrc,
|
||||
FixToTop,
|
||||
FocusTitle,
|
||||
FocusTrap,
|
||||
HasFeature,
|
||||
SaveActiveTab,
|
||||
SiteNamePipe,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
RouterModule,
|
||||
FormsModule,
|
||||
TooltipModule.forRoot(),
|
||||
PopoverModule,
|
||||
ButtonsModule,
|
||||
ModalModule.forRoot(),
|
||||
FontAwesomeModule,
|
||||
// ScrollingModule,
|
||||
],
|
||||
declarations: declarations,
|
||||
exports: declarations,
|
||||
imports: [
|
||||
BrowserModule,
|
||||
RouterModule,
|
||||
FormsModule,
|
||||
TooltipModule.forRoot(),
|
||||
PopoverModule,
|
||||
ButtonsModule,
|
||||
ModalModule.forRoot(),
|
||||
FontAwesomeModule,
|
||||
// ScrollingModule,
|
||||
],
|
||||
declarations: declarations,
|
||||
exports: declarations,
|
||||
})
|
||||
export class SharedModule {
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@ import { emptyIcon, oauthIcons } from '../../../client/icons';
|
||||
import { OAuthProvider } from '../../../common/interfaces';
|
||||
|
||||
export function getProviderIcon(id: string) {
|
||||
return oauthIcons[id] || emptyIcon;
|
||||
return oauthIcons[id] || emptyIcon;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'sign-in-box',
|
||||
templateUrl: 'sign-in-box.pug',
|
||||
styleUrls: ['sign-in-box.scss'],
|
||||
selector: 'sign-in-box',
|
||||
templateUrl: 'sign-in-box.pug',
|
||||
styleUrls: ['sign-in-box.scss'],
|
||||
})
|
||||
export class SignInBox {
|
||||
readonly signUpProviders = signUpProviders;
|
||||
readonly signInProviders = signInProviders;
|
||||
readonly local = local || DEVELOPMENT;
|
||||
@Output() signIn = new EventEmitter<OAuthProvider>();
|
||||
icon(id: string) {
|
||||
return getProviderIcon(id);
|
||||
}
|
||||
signInTo(provider: OAuthProvider) {
|
||||
this.signIn.emit(provider);
|
||||
}
|
||||
readonly signUpProviders = signUpProviders;
|
||||
readonly signInProviders = signInProviders;
|
||||
readonly local = local || DEVELOPMENT;
|
||||
@Output() signIn = new EventEmitter<OAuthProvider>();
|
||||
icon(id: string) {
|
||||
return getProviderIcon(id);
|
||||
}
|
||||
signInTo(provider: OAuthProvider) {
|
||||
this.signIn.emit(provider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@ import { toSocialSiteInfo } from '../../../client/clientUtils';
|
||||
import { getProviderIcon } from '../sign-in-box/sign-in-box';
|
||||
|
||||
@Component({
|
||||
selector: 'site-info',
|
||||
templateUrl: 'site-info.pug',
|
||||
styleUrls: ['site-info.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'site-info',
|
||||
templateUrl: 'site-info.pug',
|
||||
styleUrls: ['site-info.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SiteInfo {
|
||||
info?: SocialSiteInfo;
|
||||
icon: any;
|
||||
@Input() set site(value: SocialSite | undefined) {
|
||||
this.info = value && toSocialSiteInfo(value);
|
||||
this.icon = getProviderIcon(this.info && this.info.icon || '');
|
||||
}
|
||||
info?: SocialSiteInfo;
|
||||
icon: any;
|
||||
@Input() set site(value: SocialSite | undefined) {
|
||||
this.info = value && toSocialSiteInfo(value);
|
||||
this.icon = getProviderIcon(this.info && this.info.icon || '');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'site-links',
|
||||
templateUrl: 'site-links.pug',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'site-links',
|
||||
templateUrl: 'site-links.pug',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SiteLinks {
|
||||
@Input() links: string[] = [];
|
||||
@Input() links: string[] = [];
|
||||
}
|
||||
|
||||
@@ -1,89 +1,89 @@
|
||||
import {
|
||||
Component, Input, Output, EventEmitter, ElementRef, ChangeDetectionStrategy, HostListener, ViewChild
|
||||
Component, Input, Output, EventEmitter, ElementRef, ChangeDetectionStrategy, HostListener, ViewChild
|
||||
} from '@angular/core';
|
||||
import { clamp } from 'lodash';
|
||||
import { AgDragEvent } from '../directives/agDrag';
|
||||
import { Key } from '../../../client/input/input';
|
||||
|
||||
@Component({
|
||||
selector: 'slider-bar',
|
||||
templateUrl: 'slider-bar.pug',
|
||||
styleUrls: ['slider-bar.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: {
|
||||
'role': 'slider',
|
||||
'[tabindex]': 'disabled ? -1 : 0',
|
||||
'[attr.aria-valuemin]': 'min',
|
||||
'[attr.aria-valuemax]': 'max',
|
||||
'[attr.aria-valuenow]': 'value',
|
||||
'[attr.aria-disabled]': 'disabled',
|
||||
},
|
||||
selector: 'slider-bar',
|
||||
templateUrl: 'slider-bar.pug',
|
||||
styleUrls: ['slider-bar.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: {
|
||||
'role': 'slider',
|
||||
'[tabindex]': 'disabled ? -1 : 0',
|
||||
'[attr.aria-valuemin]': 'min',
|
||||
'[attr.aria-valuemax]': 'max',
|
||||
'[attr.aria-valuenow]': 'value',
|
||||
'[attr.aria-disabled]': 'disabled',
|
||||
},
|
||||
})
|
||||
export class SliderBar {
|
||||
@Input() min = 0;
|
||||
@Input() max = 100;
|
||||
@Input() step = 0;
|
||||
@Input() largeStep = 10;
|
||||
@Input() disabled = false;
|
||||
@Input() value = 0;
|
||||
@Output() valueChange = new EventEmitter<number>();
|
||||
@Output() changed = new EventEmitter<number>();
|
||||
@ViewChild('bar', { static: true }) bar!: ElementRef;
|
||||
private currentWidth = 0;
|
||||
get width() {
|
||||
return clamp(((this.value - this.min) / (this.max - this.min)) * 100, 0, 100);
|
||||
}
|
||||
drag({ type, x, event }: AgDragEvent) {
|
||||
if (this.disabled)
|
||||
return;
|
||||
@Input() min = 0;
|
||||
@Input() max = 100;
|
||||
@Input() step = 0;
|
||||
@Input() largeStep = 10;
|
||||
@Input() disabled = false;
|
||||
@Input() value = 0;
|
||||
@Output() valueChange = new EventEmitter<number>();
|
||||
@Output() changed = new EventEmitter<number>();
|
||||
@ViewChild('bar', { static: true }) bar!: ElementRef;
|
||||
private currentWidth = 0;
|
||||
get width() {
|
||||
return clamp(((this.value - this.min) / (this.max - this.min)) * 100, 0, 100);
|
||||
}
|
||||
drag({ type, x, event }: AgDragEvent) {
|
||||
if (this.disabled)
|
||||
return;
|
||||
|
||||
event.preventDefault();
|
||||
event.preventDefault();
|
||||
|
||||
if (type === 'start') {
|
||||
this.currentWidth = this.bar.nativeElement.getBoundingClientRect().width;
|
||||
}
|
||||
if (type === 'start') {
|
||||
this.currentWidth = this.bar.nativeElement.getBoundingClientRect().width;
|
||||
}
|
||||
|
||||
let val = this.min + clamp(x / this.currentWidth, 0, 1) * (this.max - this.min);
|
||||
let val = this.min + clamp(x / this.currentWidth, 0, 1) * (this.max - this.min);
|
||||
|
||||
if (this.step) {
|
||||
val = Math.round(val / this.step) * this.step;
|
||||
}
|
||||
if (this.step) {
|
||||
val = Math.round(val / this.step) * this.step;
|
||||
}
|
||||
|
||||
this.setValue(val, false);
|
||||
this.setValue(val, false);
|
||||
|
||||
if (type === 'end') {
|
||||
this.changed.emit(val);
|
||||
}
|
||||
}
|
||||
@HostListener('keydown', ['$event'])
|
||||
keydown(e: KeyboardEvent) {
|
||||
if (this.disabled)
|
||||
return;
|
||||
if (type === 'end') {
|
||||
this.changed.emit(val);
|
||||
}
|
||||
}
|
||||
@HostListener('keydown', ['$event'])
|
||||
keydown(e: KeyboardEvent) {
|
||||
if (this.disabled)
|
||||
return;
|
||||
|
||||
const step = this.step || 1;
|
||||
const step = this.step || 1;
|
||||
|
||||
if (e.keyCode === Key.LEFT || e.keyCode === Key.DOWN || e.keyCode === Key.PAGE_DOWN) {
|
||||
e.preventDefault();
|
||||
this.setValue(this.value - step * (e.keyCode === Key.PAGE_DOWN ? this.largeStep : 1), true);
|
||||
} else if (e.keyCode === Key.RIGHT || e.keyCode === Key.UP || e.keyCode === Key.PAGE_UP) {
|
||||
e.preventDefault();
|
||||
this.setValue(this.value + step * (e.keyCode === Key.PAGE_UP ? this.largeStep : 1), true);
|
||||
} else if (e.keyCode === Key.HOME) {
|
||||
e.preventDefault();
|
||||
this.setValue(this.min, true);
|
||||
} else if (e.keyCode === Key.END) {
|
||||
e.preventDefault();
|
||||
this.setValue(this.max, true);
|
||||
}
|
||||
}
|
||||
private setValue(value: number, emit: boolean) {
|
||||
if (this.value !== value) {
|
||||
this.value = clamp(value, this.min, this.max);
|
||||
this.valueChange.emit(this.value);
|
||||
if (e.keyCode === Key.LEFT || e.keyCode === Key.DOWN || e.keyCode === Key.PAGE_DOWN) {
|
||||
e.preventDefault();
|
||||
this.setValue(this.value - step * (e.keyCode === Key.PAGE_DOWN ? this.largeStep : 1), true);
|
||||
} else if (e.keyCode === Key.RIGHT || e.keyCode === Key.UP || e.keyCode === Key.PAGE_UP) {
|
||||
e.preventDefault();
|
||||
this.setValue(this.value + step * (e.keyCode === Key.PAGE_UP ? this.largeStep : 1), true);
|
||||
} else if (e.keyCode === Key.HOME) {
|
||||
e.preventDefault();
|
||||
this.setValue(this.min, true);
|
||||
} else if (e.keyCode === Key.END) {
|
||||
e.preventDefault();
|
||||
this.setValue(this.max, true);
|
||||
}
|
||||
}
|
||||
private setValue(value: number, emit: boolean) {
|
||||
if (this.value !== value) {
|
||||
this.value = clamp(value, this.min, this.max);
|
||||
this.valueChange.emit(this.value);
|
||||
|
||||
if (emit) {
|
||||
this.changed.emit(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (emit) {
|
||||
this.changed.emit(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
Component, Input, AfterViewInit, ViewChild, ElementRef, NgZone, DoCheck, IterableDiffers, IterableDiffer, OnChanges
|
||||
Component, Input, AfterViewInit, ViewChild, ElementRef, NgZone, DoCheck, IterableDiffers, IterableDiffer, OnChanges
|
||||
} from '@angular/core';
|
||||
import { Rect, Sprite, ColorExtra, Palette } from '../../../common/interfaces';
|
||||
import { parseColor, colorToCSS } from '../../../common/color';
|
||||
@@ -16,171 +16,171 @@ let redrawFrame = 0;
|
||||
const forRedraw: SpriteBox[] = [];
|
||||
|
||||
function drawAll() {
|
||||
redrawFrame = 0;
|
||||
forRedraw.forEach(box => box.draw());
|
||||
forRedraw.length = 0;
|
||||
redrawFrame = 0;
|
||||
forRedraw.forEach(box => box.draw());
|
||||
forRedraw.length = 0;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'sprite-box',
|
||||
templateUrl: 'sprite-box.pug',
|
||||
styleUrls: ['sprite-box.scss'],
|
||||
selector: 'sprite-box',
|
||||
templateUrl: 'sprite-box.pug',
|
||||
styleUrls: ['sprite-box.scss'],
|
||||
})
|
||||
export class SpriteBox implements AfterViewInit, OnChanges, DoCheck {
|
||||
readonly debug = DEVELOPMENT;
|
||||
readonly noneIcon = faTimes;
|
||||
@Input() size = 52;
|
||||
@Input() scale = 2;
|
||||
@Input() x = 0;
|
||||
@Input() y = 0;
|
||||
@Input() center = true;
|
||||
@Input() index?: number;
|
||||
@Input() sprite?: ColorExtra;
|
||||
@Input() palette?: Palette;
|
||||
@Input() fill?: string[] | string;
|
||||
@Input() outline?: string[] | string;
|
||||
@Input() reverseExtra?: boolean;
|
||||
@Input() timestamp: any;
|
||||
@Input() invisible = false;
|
||||
@Input() darken = true;
|
||||
@ViewChild('canvas', { static: true }) canvas!: ElementRef;
|
||||
private _circle?: string;
|
||||
private fillDiffer: IterableDiffer<string>;
|
||||
private outlineDiffer: IterableDiffer<string>;
|
||||
private batch?: ContextSpriteBatch;
|
||||
constructor(private zone: NgZone, iterableDiffers: IterableDiffers) {
|
||||
this.fillDiffer = iterableDiffers.find([]).create<string>();
|
||||
this.outlineDiffer = iterableDiffers.find([]).create<string>();
|
||||
}
|
||||
@Input() get circle() {
|
||||
return this._circle;
|
||||
}
|
||||
set circle(value) {
|
||||
this._circle = colorToCSS(parseColor(value || ''));
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
loadAndInitSpriteSheets().then(() => this.redraw());
|
||||
}
|
||||
ngDoCheck() {
|
||||
const fillChanges = this.fill && Array.isArray(this.fill) && this.fillDiffer.diff(this.fill);
|
||||
const outlineChanges = this.outline && Array.isArray(this.outline) && this.outlineDiffer.diff(this.outline);
|
||||
readonly debug = DEVELOPMENT;
|
||||
readonly noneIcon = faTimes;
|
||||
@Input() size = 52;
|
||||
@Input() scale = 2;
|
||||
@Input() x = 0;
|
||||
@Input() y = 0;
|
||||
@Input() center = true;
|
||||
@Input() index?: number;
|
||||
@Input() sprite?: ColorExtra;
|
||||
@Input() palette?: Palette;
|
||||
@Input() fill?: string[] | string;
|
||||
@Input() outline?: string[] | string;
|
||||
@Input() reverseExtra?: boolean;
|
||||
@Input() timestamp: any;
|
||||
@Input() invisible = false;
|
||||
@Input() darken = true;
|
||||
@ViewChild('canvas', { static: true }) canvas!: ElementRef;
|
||||
private _circle?: string;
|
||||
private fillDiffer: IterableDiffer<string>;
|
||||
private outlineDiffer: IterableDiffer<string>;
|
||||
private batch?: ContextSpriteBatch;
|
||||
constructor(private zone: NgZone, iterableDiffers: IterableDiffers) {
|
||||
this.fillDiffer = iterableDiffers.find([]).create<string>();
|
||||
this.outlineDiffer = iterableDiffers.find([]).create<string>();
|
||||
}
|
||||
@Input() get circle() {
|
||||
return this._circle;
|
||||
}
|
||||
set circle(value) {
|
||||
this._circle = colorToCSS(parseColor(value || ''));
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
loadAndInitSpriteSheets().then(() => this.redraw());
|
||||
}
|
||||
ngDoCheck() {
|
||||
const fillChanges = this.fill && Array.isArray(this.fill) && this.fillDiffer.diff(this.fill);
|
||||
const outlineChanges = this.outline && Array.isArray(this.outline) && this.outlineDiffer.diff(this.outline);
|
||||
|
||||
if (fillChanges || outlineChanges) {
|
||||
this.redraw();
|
||||
}
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.redraw();
|
||||
}
|
||||
private redraw() {
|
||||
if (!redrawFrame) {
|
||||
this.zone.runOutsideAngular(() => redrawFrame = requestAnimationFrame(drawAll));
|
||||
}
|
||||
if (fillChanges || outlineChanges) {
|
||||
this.redraw();
|
||||
}
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.redraw();
|
||||
}
|
||||
private redraw() {
|
||||
if (!redrawFrame) {
|
||||
this.zone.runOutsideAngular(() => redrawFrame = requestAnimationFrame(drawAll));
|
||||
}
|
||||
|
||||
if (forRedraw.indexOf(this) === -1) {
|
||||
forRedraw.push(this);
|
||||
}
|
||||
}
|
||||
draw() {
|
||||
const size = this.size;
|
||||
const scale = this.scale;
|
||||
const canvas = this.canvas.nativeElement as HTMLCanvasElement;
|
||||
if (forRedraw.indexOf(this) === -1) {
|
||||
forRedraw.push(this);
|
||||
}
|
||||
}
|
||||
draw() {
|
||||
const size = this.size;
|
||||
const scale = this.scale;
|
||||
const canvas = this.canvas.nativeElement as HTMLCanvasElement;
|
||||
|
||||
if (!size || this.invisible)
|
||||
return;
|
||||
if (!size || this.invisible)
|
||||
return;
|
||||
|
||||
if (canvas.width !== size || canvas.height !== size) {
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
}
|
||||
if (canvas.width !== size || canvas.height !== size) {
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
}
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
if (!context)
|
||||
return;
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
context.save();
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
context.save();
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const sprite = this.sprite;
|
||||
const sprite = this.sprite;
|
||||
|
||||
if (sprite) {
|
||||
if (this.circle) {
|
||||
context.fillStyle = this.circle;
|
||||
context.beginPath();
|
||||
context.arc(canvas.width / 2, canvas.height / 2, canvas.width / 3, 0, Math.PI * 2);
|
||||
context.fill();
|
||||
}
|
||||
if (sprite) {
|
||||
if (this.circle) {
|
||||
context.fillStyle = this.circle;
|
||||
context.beginPath();
|
||||
context.arc(canvas.width / 2, canvas.height / 2, canvas.width / 3, 0, Math.PI * 2);
|
||||
context.fill();
|
||||
}
|
||||
|
||||
const bufferSize = size / scale;
|
||||
const batch = this.batch = this.batch || new ContextSpriteBatch(createCanvas(bufferSize, bufferSize));
|
||||
resizeCanvas(batch.canvas, bufferSize, bufferSize);
|
||||
const bufferSize = size / scale;
|
||||
const batch = this.batch = this.batch || new ContextSpriteBatch(createCanvas(bufferSize, bufferSize));
|
||||
resizeCanvas(batch.canvas, bufferSize, bufferSize);
|
||||
|
||||
const fills = Array.isArray(this.fill) ? this.fill : [this.fill];
|
||||
const outlines = Array.isArray(this.outline) ? this.outline : [this.outline];
|
||||
const paletteColors = toColorList(getColorsFromSet({ fills, outlines }, '000000', this.darken));
|
||||
const palette = mockPaletteManager.addArray(paletteColors);
|
||||
const extraPalette = sprite.palettes && mockPaletteManager.addArray(sprite.palettes[0]);
|
||||
const fills = Array.isArray(this.fill) ? this.fill : [this.fill];
|
||||
const outlines = Array.isArray(this.outline) ? this.outline : [this.outline];
|
||||
const paletteColors = toColorList(getColorsFromSet({ fills, outlines }, '000000', this.darken));
|
||||
const palette = mockPaletteManager.addArray(paletteColors);
|
||||
const extraPalette = sprite.palettes && mockPaletteManager.addArray(sprite.palettes[0]);
|
||||
|
||||
let x = this.x;
|
||||
let y = this.y;
|
||||
let x = this.x;
|
||||
let y = this.y;
|
||||
|
||||
if (this.center) {
|
||||
const bounds = rect(0, 0, 0, 0);
|
||||
addRect(bounds, sprite.color);
|
||||
addRect(bounds, sprite.extra);
|
||||
if (this.center) {
|
||||
const bounds = rect(0, 0, 0, 0);
|
||||
addRect(bounds, sprite.color);
|
||||
addRect(bounds, sprite.extra);
|
||||
|
||||
if (sprite.colorMany) {
|
||||
sprite.colorMany.forEach(c => addRect(bounds, c));
|
||||
}
|
||||
if (sprite.colorMany) {
|
||||
sprite.colorMany.forEach(c => addRect(bounds, c));
|
||||
}
|
||||
|
||||
x = Math.round((bufferSize - bounds.w) / 2 - bounds.x);
|
||||
y = Math.round((bufferSize - bounds.h) / 2 - bounds.y);
|
||||
}
|
||||
x = Math.round((bufferSize - bounds.w) / 2 - bounds.x);
|
||||
y = Math.round((bufferSize - bounds.h) / 2 - bounds.y);
|
||||
}
|
||||
|
||||
batch.start(paletteSpriteSheet, 0);
|
||||
batch.start(paletteSpriteSheet, 0);
|
||||
|
||||
if (this.reverseExtra) {
|
||||
batch.drawSprite(sprite.extra, WHITE, extraPalette, x, y);
|
||||
}
|
||||
if (this.reverseExtra) {
|
||||
batch.drawSprite(sprite.extra, WHITE, extraPalette, x, y);
|
||||
}
|
||||
|
||||
if (sprite.colorMany) {
|
||||
for (const color of sprite.colorMany) {
|
||||
batch.drawSprite(color, WHITE, palette, x, y);
|
||||
}
|
||||
} else {
|
||||
batch.drawSprite(sprite.color, WHITE, palette, x, y);
|
||||
}
|
||||
if (sprite.colorMany) {
|
||||
for (const color of sprite.colorMany) {
|
||||
batch.drawSprite(color, WHITE, palette, x, y);
|
||||
}
|
||||
} else {
|
||||
batch.drawSprite(sprite.color, WHITE, palette, x, y);
|
||||
}
|
||||
|
||||
if (!this.reverseExtra) {
|
||||
batch.drawSprite(sprite.extra, WHITE, extraPalette, x, y);
|
||||
}
|
||||
if (!this.reverseExtra) {
|
||||
batch.drawSprite(sprite.extra, WHITE, extraPalette, x, y);
|
||||
}
|
||||
|
||||
batch.end();
|
||||
batch.end();
|
||||
|
||||
disableImageSmoothing(context);
|
||||
context.scale(scale, scale);
|
||||
context.drawImage(batch.canvas, 0, 0);
|
||||
}
|
||||
disableImageSmoothing(context);
|
||||
context.scale(scale, scale);
|
||||
context.drawImage(batch.canvas, 0, 0);
|
||||
}
|
||||
|
||||
context.restore();
|
||||
}
|
||||
context.restore();
|
||||
}
|
||||
}
|
||||
|
||||
function addRect(rect: Rect, sprite: Sprite | undefined) {
|
||||
if (sprite && sprite.w && sprite.h) {
|
||||
if (rect.w === 0 || rect.h === 0) {
|
||||
rect.x = sprite.ox;
|
||||
rect.y = sprite.oy;
|
||||
rect.w = sprite.w;
|
||||
rect.h = sprite.h;
|
||||
} else {
|
||||
const x = Math.min(rect.x, sprite.ox);
|
||||
const y = Math.min(rect.y, sprite.oy);
|
||||
rect.w = Math.max(rect.x + rect.w, sprite.ox + sprite.w) - x;
|
||||
rect.h = Math.max(rect.y + rect.h, sprite.oy + sprite.h) - y;
|
||||
rect.x = x;
|
||||
rect.y = y;
|
||||
}
|
||||
}
|
||||
if (sprite && sprite.w && sprite.h) {
|
||||
if (rect.w === 0 || rect.h === 0) {
|
||||
rect.x = sprite.ox;
|
||||
rect.y = sprite.oy;
|
||||
rect.w = sprite.w;
|
||||
rect.h = sprite.h;
|
||||
} else {
|
||||
const x = Math.min(rect.x, sprite.ox);
|
||||
const y = Math.min(rect.y, sprite.oy);
|
||||
rect.w = Math.max(rect.x + rect.w, sprite.ox + sprite.w) - x;
|
||||
rect.h = Math.max(rect.y + rect.h, sprite.oy + sprite.h) - y;
|
||||
rect.x = x;
|
||||
rect.y = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,91 +7,91 @@ import { focusElementAfterTimeout } from '../../../client/htmlUtils';
|
||||
const MAX = 999999;
|
||||
|
||||
@Component({
|
||||
selector: 'sprite-selection',
|
||||
templateUrl: 'sprite-selection.pug',
|
||||
styleUrls: ['sprite-selection.scss'],
|
||||
host: {
|
||||
'role': 'radiogroup',
|
||||
'tabindex': '0',
|
||||
'(keydown)': 'keydown($event)',
|
||||
'[attr.aria-activedescendant]': 'activeDescendant',
|
||||
},
|
||||
selector: 'sprite-selection',
|
||||
templateUrl: 'sprite-selection.pug',
|
||||
styleUrls: ['sprite-selection.scss'],
|
||||
host: {
|
||||
'role': 'radiogroup',
|
||||
'tabindex': '0',
|
||||
'(keydown)': 'keydown($event)',
|
||||
'[attr.aria-activedescendant]': 'activeDescendant',
|
||||
},
|
||||
})
|
||||
export class SpriteSelection {
|
||||
@Input() selected = 0;
|
||||
@Output() selectedChange = new EventEmitter<number>();
|
||||
@Input() sprites?: ColorExtra[];
|
||||
@Input() fill?: string | string[];
|
||||
@Input() outline?: string | string[];
|
||||
@Input() circle?: string;
|
||||
@Input() reverseExtra = false;
|
||||
@Input() limit = MAX;
|
||||
@Input() skip = 0;
|
||||
@Input() disabled = false;
|
||||
@Input() emptyLabel?: string;
|
||||
@Input() invisible = false;
|
||||
@Input() darken = true;
|
||||
id = uniqueId('sprite-selection-');
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
get hasMore() {
|
||||
return this.sprites && this.sprites.length > this.limit;
|
||||
}
|
||||
get end() {
|
||||
return this.skip + this.limit;
|
||||
}
|
||||
get activeDescendant() {
|
||||
return `${this.id}-${this.selected - this.skip}`;
|
||||
}
|
||||
isSelected(index: number) {
|
||||
return this.selected === (index + this.skip);
|
||||
}
|
||||
select(index: number, focus = false) {
|
||||
if (!this.disabled && this.selected !== index) {
|
||||
this.selected = index;
|
||||
this.selectedChange.emit(index);
|
||||
@Input() selected = 0;
|
||||
@Output() selectedChange = new EventEmitter<number>();
|
||||
@Input() sprites?: ColorExtra[];
|
||||
@Input() fill?: string | string[];
|
||||
@Input() outline?: string | string[];
|
||||
@Input() circle?: string;
|
||||
@Input() reverseExtra = false;
|
||||
@Input() limit = MAX;
|
||||
@Input() skip = 0;
|
||||
@Input() disabled = false;
|
||||
@Input() emptyLabel?: string;
|
||||
@Input() invisible = false;
|
||||
@Input() darken = true;
|
||||
id = uniqueId('sprite-selection-');
|
||||
constructor(private element: ElementRef) {
|
||||
}
|
||||
get hasMore() {
|
||||
return this.sprites && this.sprites.length > this.limit;
|
||||
}
|
||||
get end() {
|
||||
return this.skip + this.limit;
|
||||
}
|
||||
get activeDescendant() {
|
||||
return `${this.id}-${this.selected - this.skip}`;
|
||||
}
|
||||
isSelected(index: number) {
|
||||
return this.selected === (index + this.skip);
|
||||
}
|
||||
select(index: number, focus = false) {
|
||||
if (!this.disabled && this.selected !== index) {
|
||||
this.selected = index;
|
||||
this.selectedChange.emit(index);
|
||||
|
||||
if (this.hasMore && index >= this.end) {
|
||||
this.showMore();
|
||||
}
|
||||
if (this.hasMore && index >= this.end) {
|
||||
this.showMore();
|
||||
}
|
||||
|
||||
if (focus) {
|
||||
focusElementAfterTimeout(this.element.nativeElement, '.active');
|
||||
}
|
||||
}
|
||||
}
|
||||
showMore() {
|
||||
this.limit = MAX;
|
||||
}
|
||||
keydown(e: KeyboardEvent) {
|
||||
const select = this.handleKey(e.keyCode);
|
||||
if (focus) {
|
||||
focusElementAfterTimeout(this.element.nativeElement, '.active');
|
||||
}
|
||||
}
|
||||
}
|
||||
showMore() {
|
||||
this.limit = MAX;
|
||||
}
|
||||
keydown(e: KeyboardEvent) {
|
||||
const select = this.handleKey(e.keyCode);
|
||||
|
||||
if (select !== undefined) {
|
||||
e.preventDefault();
|
||||
this.select(select, true);
|
||||
}
|
||||
}
|
||||
private handleKey(keyCode: number): number | undefined {
|
||||
if (this.sprites) {
|
||||
if (keyCode === Key.RIGHT || keyCode === Key.DOWN) {
|
||||
if (this.selected >= (this.sprites.length - 1)) {
|
||||
return this.skip;
|
||||
} else {
|
||||
return this.selected + 1;
|
||||
}
|
||||
} else if (keyCode === Key.LEFT || keyCode === Key.UP) {
|
||||
if (this.selected <= this.skip) {
|
||||
return this.sprites.length - 1;
|
||||
} else {
|
||||
return this.selected - 1;
|
||||
}
|
||||
} else if (keyCode === Key.HOME) {
|
||||
return this.skip;
|
||||
} else if (keyCode === Key.END) {
|
||||
return this.sprites.length - 1;
|
||||
}
|
||||
}
|
||||
if (select !== undefined) {
|
||||
e.preventDefault();
|
||||
this.select(select, true);
|
||||
}
|
||||
}
|
||||
private handleKey(keyCode: number): number | undefined {
|
||||
if (this.sprites) {
|
||||
if (keyCode === Key.RIGHT || keyCode === Key.DOWN) {
|
||||
if (this.selected >= (this.sprites.length - 1)) {
|
||||
return this.skip;
|
||||
} else {
|
||||
return this.selected + 1;
|
||||
}
|
||||
} else if (keyCode === Key.LEFT || keyCode === Key.UP) {
|
||||
if (this.selected <= this.skip) {
|
||||
return this.sprites.length - 1;
|
||||
} else {
|
||||
return this.selected - 1;
|
||||
}
|
||||
} else if (keyCode === Key.HOME) {
|
||||
return this.skip;
|
||||
} else if (keyCode === Key.END) {
|
||||
return this.sprites.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@ import { Model } from '../../services/model';
|
||||
import { supporterLink } from '../../../client/data';
|
||||
|
||||
@Component({
|
||||
selector: 'support-button',
|
||||
templateUrl: 'support-button.pug',
|
||||
styleUrls: ['support-button.scss'],
|
||||
selector: 'support-button',
|
||||
templateUrl: 'support-button.pug',
|
||||
styleUrls: ['support-button.scss'],
|
||||
})
|
||||
export class SupportButton {
|
||||
readonly patreonLink = supporterLink;
|
||||
constructor(private model: Model) {
|
||||
}
|
||||
get supporter() {
|
||||
return this.model.supporter;
|
||||
}
|
||||
readonly patreonLink = supporterLink;
|
||||
constructor(private model: Model) {
|
||||
}
|
||||
get supporter() {
|
||||
return this.model.supporter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,80 +9,80 @@ import { CharacterPreview } from '../character-preview/character-preview';
|
||||
import { decompressPonyString } from '../../../common/compressPony';
|
||||
|
||||
const BLEP: Expression = {
|
||||
...defaultExpression,
|
||||
muzzle: Muzzle.Blep,
|
||||
...defaultExpression,
|
||||
muzzle: Muzzle.Blep,
|
||||
};
|
||||
|
||||
const EXCITED: Expression = {
|
||||
...defaultExpression,
|
||||
muzzle: Muzzle.SmileOpen,
|
||||
...defaultExpression,
|
||||
muzzle: Muzzle.SmileOpen,
|
||||
};
|
||||
|
||||
const DERP: Expression = {
|
||||
...defaultExpression,
|
||||
muzzle: Muzzle.SmileOpen,
|
||||
leftIris: Iris.Up,
|
||||
...defaultExpression,
|
||||
muzzle: Muzzle.SmileOpen,
|
||||
leftIris: Iris.Up,
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'supporter-pony',
|
||||
templateUrl: 'supporter-pony.pug',
|
||||
selector: 'supporter-pony',
|
||||
templateUrl: 'supporter-pony.pug',
|
||||
})
|
||||
export class SupporterPony implements OnInit, OnDestroy {
|
||||
@ViewChild('characterPreview', { static: true }) characterPreview!: CharacterPreview;
|
||||
@Input() scale = 3;
|
||||
pony = decompressPonyString(SUPPORTER_PONY);
|
||||
state = defaultPonyState();
|
||||
private expression?: Expression;
|
||||
private headAnimation?: HeadAnimation;
|
||||
private headTime = 0;
|
||||
private loop: FrameLoop;
|
||||
constructor(frameService: FrameService) {
|
||||
this.loop = frameService.create(delta => this.tick(delta));
|
||||
}
|
||||
ngOnInit() {
|
||||
this.loop.init();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.loop.destroy();
|
||||
}
|
||||
excite() {
|
||||
this.headTime = 0;
|
||||
this.headAnimation = excite;
|
||||
this.expression = Math.random() < 0.2 ? DERP : EXCITED;
|
||||
}
|
||||
reset() {
|
||||
this.expression = undefined;
|
||||
}
|
||||
private tick(delta: number) {
|
||||
this.headTime += delta;
|
||||
@ViewChild('characterPreview', { static: true }) characterPreview!: CharacterPreview;
|
||||
@Input() scale = 3;
|
||||
pony = decompressPonyString(SUPPORTER_PONY);
|
||||
state = defaultPonyState();
|
||||
private expression?: Expression;
|
||||
private headAnimation?: HeadAnimation;
|
||||
private headTime = 0;
|
||||
private loop: FrameLoop;
|
||||
constructor(frameService: FrameService) {
|
||||
this.loop = frameService.create(delta => this.tick(delta));
|
||||
}
|
||||
ngOnInit() {
|
||||
this.loop.init();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.loop.destroy();
|
||||
}
|
||||
excite() {
|
||||
this.headTime = 0;
|
||||
this.headAnimation = excite;
|
||||
this.expression = Math.random() < 0.2 ? DERP : EXCITED;
|
||||
}
|
||||
reset() {
|
||||
this.expression = undefined;
|
||||
}
|
||||
private tick(delta: number) {
|
||||
this.headTime += delta;
|
||||
|
||||
if (this.headAnimation) {
|
||||
const frame = Math.floor(this.headTime * this.headAnimation.fps);
|
||||
if (this.headAnimation) {
|
||||
const frame = Math.floor(this.headTime * this.headAnimation.fps);
|
||||
|
||||
if (frame >= this.headAnimation.frames.length && !this.headAnimation.loop) {
|
||||
this.headAnimation = undefined;
|
||||
this.state.headAnimation = undefined;
|
||||
this.state.headAnimationFrame = 0;
|
||||
this.characterPreview.blink();
|
||||
} else {
|
||||
this.state.headAnimation = this.headAnimation;
|
||||
this.state.headAnimationFrame = frame % this.headAnimation.frames.length;
|
||||
}
|
||||
} else {
|
||||
this.state.headAnimation = undefined;
|
||||
if (frame >= this.headAnimation.frames.length && !this.headAnimation.loop) {
|
||||
this.headAnimation = undefined;
|
||||
this.state.headAnimation = undefined;
|
||||
this.state.headAnimationFrame = 0;
|
||||
this.characterPreview.blink();
|
||||
} else {
|
||||
this.state.headAnimation = this.headAnimation;
|
||||
this.state.headAnimationFrame = frame % this.headAnimation.frames.length;
|
||||
}
|
||||
} else {
|
||||
this.state.headAnimation = undefined;
|
||||
|
||||
if (this.expression) {
|
||||
if (Math.random() < 0.01) {
|
||||
this.expression = undefined;
|
||||
}
|
||||
} else {
|
||||
if (Math.random() < 0.005) {
|
||||
this.expression = BLEP;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.expression) {
|
||||
if (Math.random() < 0.01) {
|
||||
this.expression = undefined;
|
||||
}
|
||||
} else {
|
||||
if (Math.random() < 0.005) {
|
||||
this.expression = BLEP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.state.expression = this.expression;
|
||||
}
|
||||
this.state.expression = this.expression;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,36 +8,36 @@ import { Model } from '../../services/model';
|
||||
// import { SWAP_TIMEOUT, SECOND } from '../../../common/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'swap-box',
|
||||
templateUrl: 'swap-box.pug',
|
||||
styleUrls: ['swap-box.scss'],
|
||||
selector: 'swap-box',
|
||||
templateUrl: 'swap-box.pug',
|
||||
styleUrls: ['swap-box.scss'],
|
||||
})
|
||||
export class SwapBox {
|
||||
readonly swapIcon = faExchangeAlt;
|
||||
readonly timerIcon = faClock;
|
||||
previewInfo: any;
|
||||
@ViewChild('dropdown', { static: true }) dropdown!: Dropdown;
|
||||
timeout = false;
|
||||
constructor(private game: PonyTownGame, private zone: NgZone, private model: Model) {
|
||||
}
|
||||
toggleSwapDropdown() {
|
||||
this.zone.run(() => setTimeout(() => { }, 10));
|
||||
}
|
||||
swapPony(pony: PonyObject) {
|
||||
this.game.send(server => server.actionParam(Action.SwapCharacter, pony.id));
|
||||
setTimeout(() => {
|
||||
this.dropdown && this.dropdown.close();
|
||||
pony.lastUsed = (new Date()).toISOString();
|
||||
this.model.sortPonies();
|
||||
});
|
||||
readonly swapIcon = faExchangeAlt;
|
||||
readonly timerIcon = faClock;
|
||||
previewInfo: any;
|
||||
@ViewChild('dropdown', { static: true }) dropdown!: Dropdown;
|
||||
timeout = false;
|
||||
constructor(private game: PonyTownGame, private zone: NgZone, private model: Model) {
|
||||
}
|
||||
toggleSwapDropdown() {
|
||||
this.zone.run(() => setTimeout(() => { }, 10));
|
||||
}
|
||||
swapPony(pony: PonyObject) {
|
||||
this.game.send(server => server.actionParam(Action.SwapCharacter, pony.id));
|
||||
setTimeout(() => {
|
||||
this.dropdown && this.dropdown.close();
|
||||
pony.lastUsed = (new Date()).toISOString();
|
||||
this.model.sortPonies();
|
||||
});
|
||||
|
||||
// if (!this.timeout) {
|
||||
// this.timeout = true;
|
||||
// setTimeout(() => this.timeout = false, SWAP_TIMEOUT + SECOND);
|
||||
// }
|
||||
}
|
||||
preview(pony: PonyObject | undefined) {
|
||||
const info = pony && pony.ponyInfo;
|
||||
this.previewInfo = info && toPalette(info, mockPaletteManager);
|
||||
}
|
||||
// if (!this.timeout) {
|
||||
// this.timeout = true;
|
||||
// setTimeout(() => this.timeout = false, SWAP_TIMEOUT + SECOND);
|
||||
// }
|
||||
}
|
||||
preview(pony: PonyObject | undefined) {
|
||||
const info = pony && pony.ponyInfo;
|
||||
this.previewInfo = info && toPalette(info, mockPaletteManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
import {
|
||||
Component, Directive, TemplateRef, Input, ContentChild, ContentChildren, QueryList, Output, EventEmitter
|
||||
Component, Directive, TemplateRef, Input, ContentChild, ContentChildren, QueryList, Output, EventEmitter
|
||||
} from '@angular/core';
|
||||
import { uniqueId } from 'lodash';
|
||||
import { Key } from '../../../client/input/input';
|
||||
|
||||
@Directive({
|
||||
selector: '[tabTitle]'
|
||||
selector: '[tabTitle]'
|
||||
})
|
||||
export class TabTitle {
|
||||
constructor(public templateRef: TemplateRef<any>) {
|
||||
}
|
||||
constructor(public templateRef: TemplateRef<any>) {
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[tabContent]',
|
||||
selector: '[tabContent]',
|
||||
})
|
||||
export class TabContent {
|
||||
constructor(public templateRef: TemplateRef<any>) {
|
||||
}
|
||||
constructor(public templateRef: TemplateRef<any>) {
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: 'tab',
|
||||
selector: 'tab',
|
||||
})
|
||||
export class Tab {
|
||||
@Input() id = uniqueId(`tabset-tab`);
|
||||
@Input() title?: string;
|
||||
@Input() icon?: any;
|
||||
@Input() disabled = false;
|
||||
@ContentChild(TabContent, { static: false }) contentTpl?: TabContent;
|
||||
@ContentChild(TabTitle, { static: false }) titleTpl?: TabTitle;
|
||||
@Input() id = uniqueId(`tabset-tab`);
|
||||
@Input() title?: string;
|
||||
@Input() icon?: any;
|
||||
@Input() disabled = false;
|
||||
@ContentChild(TabContent, { static: false }) contentTpl?: TabContent;
|
||||
@ContentChild(TabTitle, { static: false }) titleTpl?: TabTitle;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'tabset',
|
||||
templateUrl: 'tabset.pug',
|
||||
selector: 'tabset',
|
||||
templateUrl: 'tabset.pug',
|
||||
})
|
||||
export class Tabset {
|
||||
justifyClass?: string;
|
||||
@ContentChildren(Tab) tabs!: QueryList<Tab>;
|
||||
@Input() label = '';
|
||||
@Input() destroyOnHide = true;
|
||||
@Input()
|
||||
set justify(className: 'start' | 'center' | 'end' | 'fill' | 'justified') {
|
||||
if (className === 'fill' || className === 'justified') {
|
||||
this.justifyClass = `nav-${className}`;
|
||||
} else {
|
||||
this.justifyClass = `justify-content-${className}`;
|
||||
}
|
||||
}
|
||||
@Input() orientation: 'horizontal' | 'vertical' = 'horizontal';
|
||||
@Input() type: 'tabs' | 'pills' = 'tabs';
|
||||
@Input() activeIndex = 0;
|
||||
@Output() activeIndexChange = new EventEmitter<number>();
|
||||
constructor() {
|
||||
this.justify = 'start';
|
||||
}
|
||||
get navClass() {
|
||||
return `nav-${this.type}${this.orientation === 'horizontal' ? ` ${this.justifyClass}` : ' flex-column'}`;
|
||||
}
|
||||
select(index: number) {
|
||||
if (this.activeIndex !== index) {
|
||||
this.activeIndex = index;
|
||||
this.activeIndexChange.emit(index);
|
||||
}
|
||||
}
|
||||
keydown(e: KeyboardEvent) {
|
||||
const index = this.handleKey(e.keyCode);
|
||||
justifyClass?: string;
|
||||
@ContentChildren(Tab) tabs!: QueryList<Tab>;
|
||||
@Input() label = '';
|
||||
@Input() destroyOnHide = true;
|
||||
@Input()
|
||||
set justify(className: 'start' | 'center' | 'end' | 'fill' | 'justified') {
|
||||
if (className === 'fill' || className === 'justified') {
|
||||
this.justifyClass = `nav-${className}`;
|
||||
} else {
|
||||
this.justifyClass = `justify-content-${className}`;
|
||||
}
|
||||
}
|
||||
@Input() orientation: 'horizontal' | 'vertical' = 'horizontal';
|
||||
@Input() type: 'tabs' | 'pills' = 'tabs';
|
||||
@Input() activeIndex = 0;
|
||||
@Output() activeIndexChange = new EventEmitter<number>();
|
||||
constructor() {
|
||||
this.justify = 'start';
|
||||
}
|
||||
get navClass() {
|
||||
return `nav-${this.type}${this.orientation === 'horizontal' ? ` ${this.justifyClass}` : ' flex-column'}`;
|
||||
}
|
||||
select(index: number) {
|
||||
if (this.activeIndex !== index) {
|
||||
this.activeIndex = index;
|
||||
this.activeIndexChange.emit(index);
|
||||
}
|
||||
}
|
||||
keydown(e: KeyboardEvent) {
|
||||
const index = this.handleKey(e.keyCode);
|
||||
|
||||
if (index !== undefined) {
|
||||
e.preventDefault();
|
||||
const element = document.getElementById(this.tabs.toArray()[index].id);
|
||||
element && element.focus();
|
||||
this.select(index);
|
||||
}
|
||||
}
|
||||
private handleKey(keyCode: number) {
|
||||
if (keyCode === Key.LEFT) {
|
||||
return this.activeIndex === 0 ? this.tabs.length - 1 : this.activeIndex - 1;
|
||||
} else if (keyCode === Key.RIGHT) {
|
||||
return this.activeIndex === this.tabs.length - 1 ? 0 : this.activeIndex + 1;
|
||||
} else if (keyCode === Key.HOME) {
|
||||
return 0;
|
||||
} else if (keyCode === Key.END) {
|
||||
return this.tabs.length - 1;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
if (index !== undefined) {
|
||||
e.preventDefault();
|
||||
const element = document.getElementById(this.tabs.toArray()[index].id);
|
||||
element && element.focus();
|
||||
this.select(index);
|
||||
}
|
||||
}
|
||||
private handleKey(keyCode: number) {
|
||||
if (keyCode === Key.LEFT) {
|
||||
return this.activeIndex === 0 ? this.tabs.length - 1 : this.activeIndex - 1;
|
||||
} else if (keyCode === Key.RIGHT) {
|
||||
return this.activeIndex === this.tabs.length - 1 ? 0 : this.activeIndex + 1;
|
||||
} else if (keyCode === Key.HOME) {
|
||||
return 0;
|
||||
} else if (keyCode === Key.END) {
|
||||
return this.tabs.length - 1;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const tabsetComponents = [TabContent, TabTitle, Tabset, Tab];
|
||||
|
||||
@@ -1,167 +1,167 @@
|
||||
import {
|
||||
Directive, DoCheck, Input, ViewContainerRef, TemplateRef, IterableDiffers, IterableDiffer,
|
||||
EmbeddedViewRef, Component, ElementRef, ViewChild, NgZone, OnDestroy, ChangeDetectorRef, AfterViewInit
|
||||
Directive, DoCheck, Input, ViewContainerRef, TemplateRef, IterableDiffers, IterableDiffer,
|
||||
EmbeddedViewRef, Component, ElementRef, ViewChild, NgZone, OnDestroy, ChangeDetectorRef, AfterViewInit
|
||||
} from '@angular/core';
|
||||
|
||||
interface Context<T> {
|
||||
$implicit: T;
|
||||
index: number;
|
||||
count: number;
|
||||
_currentIndex: number;
|
||||
$implicit: T;
|
||||
index: number;
|
||||
count: number;
|
||||
_currentIndex: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'virtual-list',
|
||||
template: '<div #padStart></div><ng-content></ng-content><div #padEnd></div>',
|
||||
styleUrls: ['virtual-list.scss'],
|
||||
host: {
|
||||
'tabindex': '0',
|
||||
},
|
||||
selector: 'virtual-list',
|
||||
template: '<div #padStart></div><ng-content></ng-content><div #padEnd></div>',
|
||||
styleUrls: ['virtual-list.scss'],
|
||||
host: {
|
||||
'tabindex': '0',
|
||||
},
|
||||
})
|
||||
export class VirtualList {
|
||||
@Input() itemSize = 50;
|
||||
@ViewChild('padStart', { static: true }) padStart!: ElementRef;
|
||||
@ViewChild('padEnd', { static: true }) padEnd!: ElementRef;
|
||||
constructor(public element: ElementRef) {
|
||||
}
|
||||
@Input() itemSize = 50;
|
||||
@ViewChild('padStart', { static: true }) padStart!: ElementRef;
|
||||
@ViewChild('padEnd', { static: true }) padEnd!: ElementRef;
|
||||
constructor(public element: ElementRef) {
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[virtualFor][virtualForOf]',
|
||||
selector: '[virtualFor][virtualForOf]',
|
||||
})
|
||||
export class VirtualFor<T> implements DoCheck, OnDestroy, AfterViewInit {
|
||||
@Input()
|
||||
set virtualForOf(forOf: T[]) {
|
||||
this.forOf = forOf;
|
||||
this.forOfDirty = true;
|
||||
}
|
||||
private forOf!: T[];
|
||||
private forOfDirty: boolean = true;
|
||||
private differ: IterableDiffer<T> | null = null;
|
||||
private first = 0;
|
||||
private last = 0;
|
||||
constructor(
|
||||
private viewContainer: ViewContainerRef,
|
||||
private template: TemplateRef<Context<T>>,
|
||||
private differs: IterableDiffers,
|
||||
private list: VirtualList,
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
zone: NgZone,
|
||||
) {
|
||||
zone.runOutsideAngular(() => {
|
||||
list.element.nativeElement.addEventListener('scroll', this.detect);
|
||||
window.addEventListener('resize', this.detect);
|
||||
});
|
||||
}
|
||||
private detect = () => this.changeDetector.detectChanges();
|
||||
@Input()
|
||||
set virtualForTemplate(value: TemplateRef<Context<T>>) {
|
||||
if (value) {
|
||||
this.template = value;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.list.element.nativeElement.removeEventListener('scroll', this.detect);
|
||||
window.removeEventListener('resize', this.detect);
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
setTimeout(this.detect, 0);
|
||||
}
|
||||
ngDoCheck(): void {
|
||||
if (this.forOfDirty) {
|
||||
this.forOfDirty = false;
|
||||
const value = this.forOf;
|
||||
@Input()
|
||||
set virtualForOf(forOf: T[]) {
|
||||
this.forOf = forOf;
|
||||
this.forOfDirty = true;
|
||||
}
|
||||
private forOf!: T[];
|
||||
private forOfDirty: boolean = true;
|
||||
private differ: IterableDiffer<T> | null = null;
|
||||
private first = 0;
|
||||
private last = 0;
|
||||
constructor(
|
||||
private viewContainer: ViewContainerRef,
|
||||
private template: TemplateRef<Context<T>>,
|
||||
private differs: IterableDiffers,
|
||||
private list: VirtualList,
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
zone: NgZone,
|
||||
) {
|
||||
zone.runOutsideAngular(() => {
|
||||
list.element.nativeElement.addEventListener('scroll', this.detect);
|
||||
window.addEventListener('resize', this.detect);
|
||||
});
|
||||
}
|
||||
private detect = () => this.changeDetector.detectChanges();
|
||||
@Input()
|
||||
set virtualForTemplate(value: TemplateRef<Context<T>>) {
|
||||
if (value) {
|
||||
this.template = value;
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.list.element.nativeElement.removeEventListener('scroll', this.detect);
|
||||
window.removeEventListener('resize', this.detect);
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
setTimeout(this.detect, 0);
|
||||
}
|
||||
ngDoCheck(): void {
|
||||
if (this.forOfDirty) {
|
||||
this.forOfDirty = false;
|
||||
const value = this.forOf;
|
||||
|
||||
if (!this.differ && value) {
|
||||
try {
|
||||
this.differ = this.differs.find(value).create();
|
||||
} catch {
|
||||
throw new Error(`Cannot find a differ`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.differ && value) {
|
||||
try {
|
||||
this.differ = this.differs.find(value).create();
|
||||
} catch {
|
||||
throw new Error(`Cannot find a differ`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const changes = this.differ && this.differ.diff(this.forOf);
|
||||
const element = this.list.element.nativeElement as HTMLElement;
|
||||
const itemSize = this.list.itemSize;
|
||||
const { height } = element.getBoundingClientRect();
|
||||
const scroll = element.scrollTop;
|
||||
const first = Math.floor(scroll / itemSize);
|
||||
const last = first + Math.ceil(height / itemSize);
|
||||
let scrollChanged = false;
|
||||
const changes = this.differ && this.differ.diff(this.forOf);
|
||||
const element = this.list.element.nativeElement as HTMLElement;
|
||||
const itemSize = this.list.itemSize;
|
||||
const { height } = element.getBoundingClientRect();
|
||||
const scroll = element.scrollTop;
|
||||
const first = Math.floor(scroll / itemSize);
|
||||
const last = first + Math.ceil(height / itemSize);
|
||||
let scrollChanged = false;
|
||||
|
||||
if (this.first !== first || this.last !== last) {
|
||||
this.first = first;
|
||||
this.last = last;
|
||||
scrollChanged = true;
|
||||
}
|
||||
if (this.first !== first || this.last !== last) {
|
||||
this.first = first;
|
||||
this.last = last;
|
||||
scrollChanged = true;
|
||||
}
|
||||
|
||||
if (changes || scrollChanged) {
|
||||
this.applyChanges();
|
||||
}
|
||||
}
|
||||
private applyChanges() {
|
||||
const viewContainer = this.viewContainer;
|
||||
const first = this.first;
|
||||
const last = this.last;
|
||||
const forOf = this.forOf;
|
||||
const actualLast = Math.min(last, forOf.length - 1);
|
||||
if (changes || scrollChanged) {
|
||||
this.applyChanges();
|
||||
}
|
||||
}
|
||||
private applyChanges() {
|
||||
const viewContainer = this.viewContainer;
|
||||
const first = this.first;
|
||||
const last = this.last;
|
||||
const forOf = this.forOf;
|
||||
const actualLast = Math.min(last, forOf.length - 1);
|
||||
|
||||
type Ref = EmbeddedViewRef<Context<T>>;
|
||||
const insertTuples: { item: T; view: Ref; }[] = [];
|
||||
const views: Ref[] = [];
|
||||
type Ref = EmbeddedViewRef<Context<T>>;
|
||||
const insertTuples: { item: T; view: Ref; }[] = [];
|
||||
const views: Ref[] = [];
|
||||
|
||||
for (let i = viewContainer.length - 1; i >= 0; i--) {
|
||||
const ref = viewContainer.get(i) as Ref;
|
||||
for (let i = viewContainer.length - 1; i >= 0; i--) {
|
||||
const ref = viewContainer.get(i) as Ref;
|
||||
|
||||
if (ref.context._currentIndex < first || ref.context._currentIndex > actualLast) {
|
||||
viewContainer.detach(i);
|
||||
views.push(ref);
|
||||
}
|
||||
}
|
||||
if (ref.context._currentIndex < first || ref.context._currentIndex > actualLast) {
|
||||
viewContainer.detach(i);
|
||||
views.push(ref);
|
||||
}
|
||||
}
|
||||
|
||||
for (let index = first, i = 0; index <= actualLast; index++ , i++) {
|
||||
if (viewContainer.length <= i || (viewContainer.get(i) as Ref).context._currentIndex !== index) {
|
||||
let view = views.pop();
|
||||
for (let index = first, i = 0; index <= actualLast; index++ , i++) {
|
||||
if (viewContainer.length <= i || (viewContainer.get(i) as Ref).context._currentIndex !== index) {
|
||||
let view = views.pop();
|
||||
|
||||
if (view) {
|
||||
view.context.$implicit = null!;
|
||||
view.context._currentIndex = index;
|
||||
viewContainer.insert(view, i);
|
||||
} else {
|
||||
const context: Context<T> = { $implicit: null!, index: -1, count: -1, _currentIndex: index };
|
||||
view = viewContainer.createEmbeddedView(this.template, context, i);
|
||||
}
|
||||
if (view) {
|
||||
view.context.$implicit = null!;
|
||||
view.context._currentIndex = index;
|
||||
viewContainer.insert(view, i);
|
||||
} else {
|
||||
const context: Context<T> = { $implicit: null!, index: -1, count: -1, _currentIndex: index };
|
||||
view = viewContainer.createEmbeddedView(this.template, context, i);
|
||||
}
|
||||
|
||||
insertTuples.push({ item: forOf[index], view });
|
||||
}
|
||||
}
|
||||
insertTuples.push({ item: forOf[index], view });
|
||||
}
|
||||
}
|
||||
|
||||
if (DEVELOPMENT && viewContainer.length !== (actualLast - first + 1)) {
|
||||
console.error('virtual-list: Invalid length', viewContainer.length, first, actualLast);
|
||||
}
|
||||
if (DEVELOPMENT && viewContainer.length !== (actualLast - first + 1)) {
|
||||
console.error('virtual-list: Invalid length', viewContainer.length, first, actualLast);
|
||||
}
|
||||
|
||||
for (const view of views) {
|
||||
view.destroy();
|
||||
}
|
||||
for (const view of views) {
|
||||
view.destroy();
|
||||
}
|
||||
|
||||
for (let i = 0; i < insertTuples.length; i++) {
|
||||
insertTuples[i].view.context.$implicit = insertTuples[i].item;
|
||||
}
|
||||
for (let i = 0; i < insertTuples.length; i++) {
|
||||
insertTuples[i].view.context.$implicit = insertTuples[i].item;
|
||||
}
|
||||
|
||||
const count = forOf.length;
|
||||
const count = forOf.length;
|
||||
|
||||
for (let i = 0, ilen = viewContainer.length; i < ilen; i++) {
|
||||
const viewRef = viewContainer.get(i) as Ref;
|
||||
viewRef.context.$implicit = forOf[first + i];
|
||||
viewRef.context.index = first + i;
|
||||
viewRef.context.count = count;
|
||||
}
|
||||
for (let i = 0, ilen = viewContainer.length; i < ilen; i++) {
|
||||
const viewRef = viewContainer.get(i) as Ref;
|
||||
viewRef.context.$implicit = forOf[first + i];
|
||||
viewRef.context.index = first + i;
|
||||
viewRef.context.count = count;
|
||||
}
|
||||
|
||||
const itemSize = this.list.itemSize;
|
||||
this.list.padStart.nativeElement.style.height = `${first * itemSize}px`;
|
||||
this.list.padEnd.nativeElement.style.height = `${(forOf.length - actualLast - 1) * itemSize}px`;
|
||||
}
|
||||
const itemSize = this.list.itemSize;
|
||||
this.list.padStart.nativeElement.style.height = `${first * itemSize}px`;
|
||||
this.list.padEnd.nativeElement.style.height = `${(forOf.length - actualLast - 1) * itemSize}px`;
|
||||
}
|
||||
}
|
||||
|
||||
export const virtualListDirectives = [VirtualFor];
|
||||
|
||||
Reference in New Issue
Block a user