Archive commit

This commit is contained in:
Erik McClure
2019-08-28 21:15:02 -07:00
commit 735845746e
1435 changed files with 140724 additions and 0 deletions
@@ -0,0 +1,29 @@
.sr-only(#ariaAnnounce aria-live="assertive")
.character-list([class.character-list-searchable]="searchable" [class.in-game]="inGame")
.dropdown.character-select-search.input-group(
(mousedown)="$event.stopPropagation()" (mouseup)="$event.stopPropagation()"
(click)="$event.stopPropagation()" (keydown)="keydown($event)")
input.form-control(
#searchInput type="search" [placeholder]="placeholder" [(ngModel)]="search" (input)="updatePonies()"
autocomplete="nope")
.input-group-append(*ngIf="tags.length" #tagsDropdown="ag-dropdown" dropdown)
button.btn.btn-secondary.rounded-right(dropdownToggle title="Search by tags")
fa-icon([icon]="hashIcon")
.dropdown-menu.dropdown-menu-right.shadow.tag-list(*dropdownMenu)
button.dropdown-item(*ngFor="let tag of tags" (click)="search = tag; tagsDropdown.close(); updatePonies()")
| {{tag}}
ul.character-select-list(role="listbox" [attr.aria-activedescendant]="'pony-item-' + selectedIndex")
li(
*ngFor="let p of ponies; let i = index"
[id]="'pony-item-' + i"
[class.active]="i === selectedIndex"
[class.selected]="p?.id === selectedPony?.id")
a.d-flex(role="option" (click)="select(p)" (mouseenter)="setPreview(p)" (mouseleave)="unsetPreview(p)")
span.flex-grow-1.character-name {{p.name}}
span.character-desc.text-muted {{p.desc}}
li(*ngIf="canNew")
a.text-center(role="option" (click)="createNew()")
em new pony
@@ -0,0 +1,108 @@
@import '../../../../styles/partials/variables';
.character-list {
width: 300px;
max-width: calc(100vw - 70px);
}
.character-list.in-game {
max-width: calc(100vw - 90px);
}
.character-select-search {
height: 30px;
padding: 0 5px;
display: none;
position: relative;
> .form-control {
background: white;
color: #222;
border-color: #eee !important;
border-radius: 3px !important;
box-shadow: none !important;
// width: 100%;
&:active, &:focus {
border-color: #ddd !important;
}
}
.character-list-searchable > & {
display: flex;
}
}
.hash-button {
position: absolute;
right: 7px;
top: 5px;
font-size: 16px;
padding: 0 10px;
}
.character-select-list {
background: #fff;
padding: 0;
margin: 0;
list-style: none;
font-size: $font-size-base;
text-align: left;
padding-top: 1px;
max-height: 400px;
overflow-x: hidden;
overflow-y: auto;
> li {
&.active {
background: $nav-tabs-link-active-color;
}
&.selected > a {
font-weight: bold;
}
> a {
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: $line-height-base;
color: $dropdown-link-color;
white-space: nowrap;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
&:hover,
&:focus {
text-decoration: none;
color: $dropdown-link-hover-color;
background-color: $dropdown-link-hover-bg;
}
}
}
.character-list-searchable > & {
border-top: solid 1px #eee;
margin-top: 9px;
}
}
.character-desc {
font-size: smaller;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 4px;
margin-left: 10px;
}
.character-name {
white-space: nowrap;
}
.tag-list {
max-height: 400px;
overflow-y: auto;
}
@@ -0,0 +1,172 @@
import { Component, Output, EventEmitter, ViewChild, ElementRef, OnInit, Input, NgZone } from '@angular/core';
import { uniq } from 'lodash';
import { Key } from '../../../client/input/input';
import { PonyObject } from '../../../common/interfaces';
import { clamp, flatten } from '../../../common/utils';
import { isMobile } from '../../../client/data';
import { Model } from '../../services/model';
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];
}
function sortTagToNumber(tag: string) {
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 || '');
}
function comparePonies(a: PonyObject, b: PonyObject) {
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);
}
}
@Component({
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();
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 (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;
}
}
return true;
}
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.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);
}
});
}
}