2020-08-15 15:12:41 +00:00
|
|
|
import { doesExist, InvalidArgumentError } from '@apextoaster/js-utils';
|
|
|
|
import { randomItem } from './utils';
|
|
|
|
|
2020-08-12 00:14:42 +00:00
|
|
|
/**
|
|
|
|
* A reference to another label.
|
|
|
|
*/
|
|
|
|
export interface LabelRef {
|
|
|
|
name: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A set of labels to add and/or remove.
|
|
|
|
*/
|
2020-08-15 14:52:08 +00:00
|
|
|
export interface ChangeSet {
|
2020-08-12 00:14:42 +00:00
|
|
|
adds: Array<LabelRef>;
|
|
|
|
removes: Array<LabelRef>;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Common fields for all labels.
|
|
|
|
*/
|
|
|
|
export interface BaseLabel {
|
|
|
|
/**
|
|
|
|
* Label name.
|
|
|
|
*/
|
|
|
|
name: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Display color.
|
|
|
|
*/
|
|
|
|
color?: string;
|
|
|
|
desc?: string;
|
|
|
|
priority: number;
|
|
|
|
requires: Array<unknown>;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Individual labels: the equivalent of a checkbox.
|
|
|
|
*/
|
2020-08-15 14:52:08 +00:00
|
|
|
export interface FlagLabel extends BaseLabel, ChangeSet {
|
2020-08-12 00:14:42 +00:00
|
|
|
/* empty */
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The transition between two state values.
|
|
|
|
*/
|
2020-08-15 14:52:08 +00:00
|
|
|
export interface StateChange extends ChangeSet {
|
2020-08-12 00:14:42 +00:00
|
|
|
/**
|
|
|
|
* Required labels for this state change to occur.
|
|
|
|
*/
|
|
|
|
matches: Array<LabelRef>;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* One of many values for a particular state.
|
|
|
|
*/
|
|
|
|
export interface StateValue extends BaseLabel {
|
|
|
|
/**
|
|
|
|
* State changes that could occur to this value.
|
|
|
|
*/
|
|
|
|
becomes: Array<StateChange>;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Grouped labels: the equivalent of a radio group.
|
|
|
|
*/
|
2020-08-15 14:52:08 +00:00
|
|
|
export interface StateLabel extends BaseLabel, ChangeSet {
|
2020-08-12 00:14:42 +00:00
|
|
|
/**
|
|
|
|
* Values for this state.
|
|
|
|
*/
|
|
|
|
values: Array<StateValue>;
|
|
|
|
}
|
2020-08-13 00:24:05 +00:00
|
|
|
|
2020-08-13 00:33:53 +00:00
|
|
|
/**
|
|
|
|
* Calculate the set of unique names for a list of flags and a list of states, with all state values
|
|
|
|
* qualified and included.
|
|
|
|
*/
|
2020-08-13 00:24:05 +00:00
|
|
|
export function getLabelNames(flags: Array<FlagLabel>, states: Array<StateLabel>): Set<string> {
|
|
|
|
const labels = [];
|
|
|
|
|
|
|
|
for (const flag of flags) {
|
|
|
|
labels.push(flag.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const state of states) {
|
|
|
|
for (const value of state.values) {
|
2020-08-13 23:53:29 +00:00
|
|
|
labels.push(valueName(state, value));
|
2020-08-13 00:24:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Set(labels);
|
|
|
|
}
|
2020-08-13 23:44:13 +00:00
|
|
|
|
|
|
|
export function splitName(name: string): Array<string> {
|
|
|
|
return name.split('/');
|
|
|
|
}
|
|
|
|
|
|
|
|
export function valueName(state: StateLabel, value: StateValue): string {
|
|
|
|
return `${state.name}/${value.name}`;
|
|
|
|
}
|
2020-08-15 14:31:28 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Sort labels by their priority field, highest first.
|
|
|
|
*
|
|
|
|
* TODO: add some sort options: high-first or low-first, case-sensitivity
|
|
|
|
*/
|
|
|
|
export function prioritizeLabels<TLabel extends BaseLabel>(labels: Array<TLabel>): Array<TLabel> {
|
|
|
|
return labels.sort((a, b) => {
|
|
|
|
if (a.priority === b.priority) {
|
|
|
|
const aName = a.name.toLocaleLowerCase();
|
|
|
|
const bName = b.name.toLocaleLowerCase();
|
|
|
|
return aName.localeCompare(bName);
|
|
|
|
} else {
|
|
|
|
// B first for high-to-low
|
|
|
|
return b.priority - a.priority;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2020-08-15 15:12:41 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Pick a label color, preferring the label data if set, falling back to a randomly selected color.
|
|
|
|
*
|
|
|
|
* TODO: this is a terrible overload
|
|
|
|
*/
|
|
|
|
export function colorizeLabel(flag: FlagLabel, colors: Array<string>): string;
|
|
|
|
export function colorizeLabel(state: StateLabel, value: StateValue, colors: Array<string>): string;
|
|
|
|
export function colorizeLabel(label: BaseLabel, colorsOrValue: Array<string> | StateValue, maybeColors?: Array<string>): string {
|
|
|
|
if (Array.isArray(colorsOrValue)) {
|
|
|
|
const { color } = label;
|
|
|
|
if (doesExist(color)) {
|
|
|
|
return color;
|
|
|
|
} else {
|
|
|
|
return randomItem(colorsOrValue);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!Array.isArray(maybeColors)) {
|
|
|
|
throw new InvalidArgumentError();
|
|
|
|
}
|
|
|
|
|
|
|
|
const { color = colorsOrValue.color } = label;
|
|
|
|
if (doesExist(color)) {
|
|
|
|
return color;
|
|
|
|
} else {
|
|
|
|
return randomItem(maybeColors);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|