mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-12-08 20:38:47 +00:00
Disambiguate TypeScript with tsx extension. (#3464)
Using the technique as discussed in #2761.
This commit is contained in:
committed by
Colin Seymour
parent
b66fcb2529
commit
f1be771611
863
samples/TypeScript/triple-slash-reference.tsx
Normal file
863
samples/TypeScript/triple-slash-reference.tsx
Normal file
@@ -0,0 +1,863 @@
|
||||
/// <reference path="../DefinitelyTyped/react/react-global.d.ts" />
|
||||
|
||||
// Fixture taken from https://github.com/RyanCavanaugh/koany/blob/master/koany.tsx
|
||||
|
||||
interface Garden {
|
||||
colors: Gardens.RockColor[];
|
||||
shapes: Gardens.RockShape[];
|
||||
}
|
||||
|
||||
namespace Gardens {
|
||||
export enum RockShape {
|
||||
Empty,
|
||||
Circle,
|
||||
Triangle,
|
||||
Square,
|
||||
Max
|
||||
}
|
||||
export const RockShapes = [RockShape.Circle, RockShape.Triangle, RockShape.Square];
|
||||
export const RockShapesAndEmpty = RockShapes.concat(RockShape.Empty);
|
||||
|
||||
export enum RockColor {
|
||||
Empty,
|
||||
White,
|
||||
Red,
|
||||
Black,
|
||||
Max
|
||||
}
|
||||
export const RockColors = [RockColor.White, RockColor.Red, RockColor.Black];
|
||||
export const RockColorsAndEmpty = RockColors.concat(RockColor.Empty);
|
||||
|
||||
export const Size = 9;
|
||||
|
||||
// 012
|
||||
// 345
|
||||
// 678
|
||||
export const adjacencies = [
|
||||
[1, 3], [0, 4, 2], [1, 5],
|
||||
[0, 4, 6], [3, 1, 7, 5], [2, 4, 8],
|
||||
[3, 7], [6, 4, 8], [7, 5]
|
||||
];
|
||||
}
|
||||
|
||||
module Koan {
|
||||
export enum DescribeContext {
|
||||
// every "white stone" is ...
|
||||
Singular,
|
||||
// all "white stones" are
|
||||
Plural,
|
||||
// every stone in the top row is "white"
|
||||
Adjectival
|
||||
}
|
||||
|
||||
export enum PartType {
|
||||
Selector,
|
||||
Aspect
|
||||
}
|
||||
|
||||
export enum StateTestResult {
|
||||
Fail = 0,
|
||||
WeakPass = 1,
|
||||
Pass = 2
|
||||
}
|
||||
|
||||
/// A general format for producing a Statement
|
||||
export interface StatementTemplate<T> {
|
||||
holes: PartType[];
|
||||
describe(args: T): string;
|
||||
test(g: Garden, args: T): StateTestResult;
|
||||
}
|
||||
|
||||
/// A completed rule that can be used to test a Garden
|
||||
export interface ProducedStatement<T> {
|
||||
test(g: Garden): StateTestResult;
|
||||
description: string;
|
||||
children: T;
|
||||
|
||||
hasPassedAndFailed(): boolean;
|
||||
}
|
||||
|
||||
function rnd(max: number) {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
|
||||
function randomColor(): Gardens.RockColor {
|
||||
return Math.floor(Math.random() * (Gardens.RockColor.Max - 1)) + 1
|
||||
}
|
||||
|
||||
function randomShape(): Gardens.RockShape {
|
||||
return Math.floor(Math.random() * (Gardens.RockShape.Max - 1)) + 1
|
||||
}
|
||||
|
||||
/* New Impl Here */
|
||||
interface SelectorSpec<T> {
|
||||
childTypes?: PartType[];
|
||||
precedence: number;
|
||||
weight: number;
|
||||
test(args: T, g: Garden, index: number): string|number|boolean;
|
||||
describe(args: T, context: DescribeContext): string;
|
||||
isAllValues(values: Array<string>|Array<number>): boolean;
|
||||
}
|
||||
|
||||
interface ProducedSelector {
|
||||
test(g: Garden, index: number): string|number|boolean;
|
||||
getDescription(plural: DescribeContext): string;
|
||||
seenAllValues(): boolean;
|
||||
}
|
||||
|
||||
export function buildSelector<T>(spec: SelectorSpec<T>, args: T): ProducedSelector {
|
||||
let seenResults: { [s: string]: boolean;} = {};
|
||||
return {
|
||||
test: (g: Garden, index: number) => {
|
||||
var result = spec.test(args, g, index);
|
||||
seenResults[result + ''] = true;
|
||||
return result;
|
||||
},
|
||||
getDescription: (context) => {
|
||||
return spec.describe(args, context);
|
||||
},
|
||||
seenAllValues: () => {
|
||||
return spec.isAllValues(Object.keys(seenResults));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export var SelectorTemplates: Array<SelectorSpec<{}>> = [];
|
||||
module LetsMakeSomeSelectors {
|
||||
// Is rock
|
||||
SelectorTemplates.push({
|
||||
test: (args, g, i) => g.colors[i] !== Gardens.RockColor.Empty,
|
||||
describe: (args, context) => {
|
||||
switch(context) {
|
||||
case DescribeContext.Plural:
|
||||
return 'Stones';
|
||||
case DescribeContext.Adjectival:
|
||||
return 'not empty';
|
||||
case DescribeContext.Singular:
|
||||
return 'Stone';
|
||||
}
|
||||
},
|
||||
isAllValues: items => items.length === 2,
|
||||
precedence: 0,
|
||||
weight: 1
|
||||
});
|
||||
|
||||
// Is of a certain color and/or shape
|
||||
Gardens.RockColorsAndEmpty.forEach(color => {
|
||||
let colorName = Gardens.RockColor[color];
|
||||
let colorWeight = color === Gardens.RockColor.Empty ? 1 : 0.33;
|
||||
Gardens.RockShapesAndEmpty.forEach(shape => {
|
||||
let shapeName = Gardens.RockShape[shape];
|
||||
let shapeWeight = shape === Gardens.RockShape.Empty ? 1 : 0.33;
|
||||
SelectorTemplates.push({
|
||||
test: (args, g, i) => {
|
||||
if(color === Gardens.RockColor.Empty) {
|
||||
if (shape === Gardens.RockShape.Empty) {
|
||||
return g.colors[i] === Gardens.RockColor.Empty;
|
||||
} else {
|
||||
return g.shapes[i] === shape;
|
||||
}
|
||||
} else {
|
||||
if (shape === Gardens.RockShape.Empty) {
|
||||
return g.colors[i] === color;
|
||||
} else {
|
||||
return g.shapes[i] === shape && g.colors[i] === color;
|
||||
}
|
||||
}
|
||||
},
|
||||
describe: (args, context) => {
|
||||
if(color === Gardens.RockColor.Empty) {
|
||||
if (shape === Gardens.RockShape.Empty) {
|
||||
switch(context) {
|
||||
case DescribeContext.Plural:
|
||||
return 'Empty Cells';
|
||||
case DescribeContext.Adjectival:
|
||||
return 'Empty';
|
||||
case DescribeContext.Singular:
|
||||
return 'Empty Cell';
|
||||
}
|
||||
} else {
|
||||
switch(context) {
|
||||
case DescribeContext.Plural:
|
||||
return shapeName + 's';
|
||||
case DescribeContext.Adjectival:
|
||||
return 'a ' + shapeName;
|
||||
case DescribeContext.Singular:
|
||||
return shapeName;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (shape === Gardens.RockShape.Empty) {
|
||||
switch(context) {
|
||||
case DescribeContext.Plural:
|
||||
return colorName + ' Stones';
|
||||
case DescribeContext.Adjectival:
|
||||
return colorName;
|
||||
case DescribeContext.Singular:
|
||||
return colorName + ' Stone';
|
||||
}
|
||||
} else {
|
||||
switch(context) {
|
||||
case DescribeContext.Plural:
|
||||
return colorName + ' ' + shapeName + 's';
|
||||
case DescribeContext.Adjectival:
|
||||
return 'a ' + colorName + ' ' + shapeName;
|
||||
case DescribeContext.Singular:
|
||||
return colorName + ' ' + shapeName;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
isAllValues: items => items.length === 2,
|
||||
precedence: 3,
|
||||
weight: (shapeWeight + colorWeight === 2) ? 0.3 : shapeWeight * colorWeight
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// [?] in the [top|middle|bottom] [row|column]
|
||||
[0, 1, 2].forEach(rowCol => {
|
||||
[true, false].forEach(isRow => {
|
||||
var name = (isRow ? ['top', 'middle', 'bottom'] : ['left', 'middle', 'right'])[rowCol] + ' ' + (isRow ? 'row' : 'column');
|
||||
var spec: SelectorSpec<[ProducedSelector]> = {
|
||||
childTypes: [PartType.Selector],
|
||||
test: (args, g, i) => {
|
||||
var c = isRow ? Math.floor(i / 3) : i % 3;
|
||||
if (c === rowCol) {
|
||||
return args[0].test(g, i);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
describe: (args, plural) => args[0].getDescription(plural) + ' in the ' + name,
|
||||
isAllValues: items => items.length === 2,
|
||||
precedence: 4,
|
||||
weight: 1 / 6
|
||||
};
|
||||
SelectorTemplates.push(spec);
|
||||
});
|
||||
});
|
||||
|
||||
// [?] next to a [?]
|
||||
SelectorTemplates.push({
|
||||
childTypes: [PartType.Selector, PartType.Selector],
|
||||
test: (args, g, i) => {
|
||||
if (args[0].test(g, i)) {
|
||||
return Gardens.adjacencies[i].some(x => !!args[1].test(g, x));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
describe: (args, plural) => {
|
||||
return args[0].getDescription(plural) + ' next to a ' + args[1].getDescription(DescribeContext.Singular);
|
||||
},
|
||||
isAllValues: items => items.length === 2,
|
||||
precedence: 4,
|
||||
weight: 1
|
||||
} as SelectorSpec<[ProducedSelector, ProducedSelector]>);
|
||||
}
|
||||
|
||||
export function buildStatement<T>(s: StatementTemplate<T>, args: T): ProducedStatement<T> {
|
||||
let hasPassed = false;
|
||||
let hasFailed = false;
|
||||
|
||||
let result: ProducedStatement<T> = {
|
||||
children: args,
|
||||
description: s.describe(args),
|
||||
test: (g) => {
|
||||
let r = s.test(g, args);
|
||||
if (r === StateTestResult.Pass) {
|
||||
hasPassed = true;
|
||||
} else if(r === StateTestResult.Fail) {
|
||||
hasFailed = true;
|
||||
}
|
||||
return r;
|
||||
},
|
||||
hasPassedAndFailed: () => {
|
||||
return hasPassed && hasFailed && (args as any as ProducedSelector[]).every(c => c.seenAllValues());
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
export let StatementList: StatementTemplate<any>[] = [];
|
||||
module LetsMakeSomeStatements {
|
||||
// Every [?] is a [?]
|
||||
StatementList.push({
|
||||
holes: [PartType.Selector, PartType.Selector],
|
||||
test: (g: Garden, args: [ProducedSelector, ProducedSelector]) => {
|
||||
let didAnyTests = false;
|
||||
for (var i = 0; i < Gardens.Size; i++) {
|
||||
if (args[0].test(g, i)) {
|
||||
if(!args[1].test(g, i)) return StateTestResult.Fail;
|
||||
didAnyTests = true;
|
||||
}
|
||||
}
|
||||
return didAnyTests ? StateTestResult.Pass : StateTestResult.WeakPass;
|
||||
},
|
||||
describe: args => {
|
||||
return 'Every ' + args[0].getDescription(DescribeContext.Singular) + ' is ' + args[1].getDescription(DescribeContext.Adjectival);
|
||||
}
|
||||
});
|
||||
|
||||
// There is exactly 1 [?]
|
||||
StatementList.push({
|
||||
holes: [PartType.Selector],
|
||||
test: (g: Garden, args: [ProducedSelector, ProducedSelector]) => {
|
||||
var count = 0;
|
||||
for (var i = 0; i < Gardens.Size; i++) {
|
||||
if (args[0].test(g, i)) count++;
|
||||
}
|
||||
|
||||
return count === 1 ? StateTestResult.Pass : StateTestResult.Fail;
|
||||
},
|
||||
describe: args => {
|
||||
return 'There is exactly one ' + args[0].description;
|
||||
}
|
||||
});
|
||||
|
||||
// There are more [?] than [?]
|
||||
StatementList.push({
|
||||
holes: [PartType.Selector, PartType.Selector],
|
||||
test: (g: Garden, args: [ProducedSelector, ProducedSelector]) => {
|
||||
var p1c = 0, p2c = 0;
|
||||
for (var i = 0; i < Gardens.Size; i++) {
|
||||
if (args[0].test(g, i)) p1c++;
|
||||
if (args[1].test(g, i)) p2c++;
|
||||
}
|
||||
if(p1c > p2c && p2c > 0) {
|
||||
return StateTestResult.Pass;
|
||||
} else if(p1c > p2c) {
|
||||
return StateTestResult.WeakPass;
|
||||
} else {
|
||||
return StateTestResult.Fail;
|
||||
}
|
||||
},
|
||||
describe: args => {
|
||||
return 'There are more ' + args[0].descriptionPlural + ' than ' + args[1].descriptionPlural;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function randomElementOf<T>(arr: T[]): T {
|
||||
if (arr.length === 0) {
|
||||
return undefined;
|
||||
} else {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
}
|
||||
|
||||
function randomWeightedElementOf<T extends { weight: number }>(arr: T[]): T {
|
||||
var totalWeight = arr.reduce((acc, v) => acc + v.weight, 0);
|
||||
var rnd = Math.random() * totalWeight;
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
rnd -= arr[i].weight;
|
||||
if (rnd <= 0) return arr[i];
|
||||
}
|
||||
// Got destroyed by floating error, just try again
|
||||
return randomWeightedElementOf(arr);
|
||||
}
|
||||
|
||||
export function buildRandomNewSelector(maxPrecedence = 1000000): ProducedSelector {
|
||||
var choices = SelectorTemplates;
|
||||
|
||||
let initial = randomWeightedElementOf(choices.filter(p => p.precedence <= maxPrecedence));
|
||||
// Fill in the holes
|
||||
if (initial.childTypes) {
|
||||
var fills = initial.childTypes.map(h => {
|
||||
if (h === PartType.Selector) {
|
||||
return buildRandomNewSelector(initial.precedence - 1);
|
||||
} else {
|
||||
throw new Error('Only know how to fill Selector holes')
|
||||
}
|
||||
});
|
||||
return buildSelector(initial, fills);
|
||||
} else {
|
||||
return buildSelector(initial, []);
|
||||
}
|
||||
}
|
||||
|
||||
export function makeEmptyGarden(): Garden {
|
||||
var g = {} as Garden;
|
||||
g.colors = [];
|
||||
g.shapes = [];
|
||||
for (var i = 0; i < Gardens.Size; i++) {
|
||||
g.colors.push(Gardens.RockColor.Empty);
|
||||
g.shapes.push(Gardens.RockShape.Empty);
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
export function gardenToString(g: Garden): string {
|
||||
return g.colors.join('') + g.shapes.join('');
|
||||
}
|
||||
|
||||
export function makeRandomGarden(): Garden {
|
||||
var g = makeEmptyGarden();
|
||||
blitRandomGardenPair(g, g);
|
||||
return g;
|
||||
}
|
||||
|
||||
export function cloneGarden(g: Garden): Garden {
|
||||
var result: Garden = {
|
||||
colors: g.colors.slice(0),
|
||||
shapes: g.shapes.slice(0)
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
export function clearGarden(g: Garden) {
|
||||
for (var i = 0; i < Gardens.Size; i++) {
|
||||
g.colors[i] = Gardens.RockColor.Empty;
|
||||
g.shapes[i] = Gardens.RockShape.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
export function blitRandomGardenPair(g1: Garden, g2: Garden): void {
|
||||
let placeCount = 0;
|
||||
for (var i = 0; i < Gardens.Size; i++) {
|
||||
if (rnd(7) === 0) {
|
||||
g1.colors[i] = g2.colors[i] = randomColor();
|
||||
g1.shapes[i] = g2.shapes[i] = randomShape();
|
||||
} else {
|
||||
placeCount++;
|
||||
g1.colors[i] = g2.colors[i] = Gardens.RockColor.Empty;
|
||||
g1.shapes[i] = g2.shapes[i] = Gardens.RockShape.Empty;
|
||||
}
|
||||
}
|
||||
if (placeCount === 0) blitRandomGardenPair(g1, g2);
|
||||
}
|
||||
|
||||
export function blitNumberedGarden(g: Garden, stoneCount: number, n: number): void {
|
||||
clearGarden(g);
|
||||
|
||||
let cellNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8];
|
||||
for (let i = 0; i < stoneCount; i++) {
|
||||
let cellNum = getValue(cellNumbers.length);
|
||||
let cell = cellNumbers.splice(cellNum, 1)[0];
|
||||
g.colors[cell] = getValue(3) + 1;
|
||||
g.shapes[cell] = getValue(3) + 1;
|
||||
}
|
||||
|
||||
function getValue(max: number) {
|
||||
let result = n % max;
|
||||
n = (n - result) / max;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export function mutateGarden(g: Garden): void {
|
||||
while (true) {
|
||||
var op = rnd(5);
|
||||
let x = rnd(Gardens.Size);
|
||||
let y = rnd(Gardens.Size);
|
||||
switch (op) {
|
||||
case 0: // Swap two non-identical cells
|
||||
if (g.colors[x] !== g.colors[y] || g.shapes[x] !== g.shapes[y]) {
|
||||
var tmp: any = g.colors[x];
|
||||
g.colors[x] = g.colors[y];
|
||||
g.colors[y] = tmp;
|
||||
tmp = g.shapes[x];
|
||||
g.shapes[x] = g.shapes[y];
|
||||
g.shapes[y] = tmp;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 1: // Add a stone
|
||||
if (g.colors[x] === Gardens.RockColor.Empty) {
|
||||
g.colors[x] = randomColor();
|
||||
g.shapes[x] = randomShape();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 2: // Remove a stone
|
||||
if (g.colors.filter(x => x !== Gardens.RockColor.Empty).length === 1) continue;
|
||||
|
||||
if (g.colors[x] !== Gardens.RockColor.Empty) {
|
||||
g.colors[x] = Gardens.RockColor.Empty;
|
||||
g.shapes[x] = Gardens.RockShape.Empty;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 3: // Change a color
|
||||
let c = randomColor();
|
||||
if (g.colors[x] !== Gardens.RockColor.Empty && g.colors[x] !== c) {
|
||||
g.colors[x] = c;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 4: // Change a shape
|
||||
let s = randomShape();
|
||||
if (g.shapes[x] !== Gardens.RockShape.Empty && g.shapes[x] !== s) {
|
||||
g.shapes[x] = s;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Indexion {
|
||||
sizes: number[];
|
||||
constructor(...sizes: number[]) {
|
||||
this.sizes = sizes;
|
||||
}
|
||||
|
||||
public getValues(index: number): number[] {
|
||||
let result = new Array<number>(this.sizes.length);
|
||||
this.fillValues(index, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public fillValues(index: number, result: number[]): void {
|
||||
for (var i = 0; i < this.sizes.length; i++) {
|
||||
result[i] = index % this.sizes[i];
|
||||
index -= result[i];
|
||||
index /= this.sizes[i];
|
||||
}
|
||||
}
|
||||
|
||||
public valuesToIndex(values: number[]): number {
|
||||
var result = 0;
|
||||
var factor = 1;
|
||||
for (var i = 0; i < this.sizes.length; i++) {
|
||||
result += values[i] * this.sizes[i] * factor;
|
||||
factor *= this.sizes[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getAdjacentIndices(index: number): number[][] {
|
||||
var baseline = this.getValues(index);
|
||||
var results: number[][] = [];
|
||||
for (var i = 0; i < this.sizes.length; i++) {
|
||||
if(baseline[i] > 0) {
|
||||
baseline[i]--;
|
||||
results.push(baseline.slice());
|
||||
baseline[i]++;
|
||||
}
|
||||
if(baseline[i] < this.sizes[i] - 1) {
|
||||
baseline[i]++;
|
||||
results.push(baseline.slice());
|
||||
baseline[i]--;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public distance(index1: number, index2: number): number {
|
||||
let delta = 0;
|
||||
for (var i = 0; i < this.sizes.length; i++) {
|
||||
var a = index1 % this.sizes[i];
|
||||
var b = index2 % this.sizes[i];
|
||||
delta += Math.abs(b - a);
|
||||
index1 -= a;
|
||||
index2 -= b;
|
||||
index1 /= this.sizes[i];
|
||||
index2 /= this.sizes[i];
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function makeNewExample() {
|
||||
while (true) {
|
||||
var p1 = Koan.buildSelector(Koan.SelectorTemplates[12], []);
|
||||
var p2 = Koan.buildSelector(Koan.SelectorTemplates[14], []);
|
||||
var test = Koan.buildStatement(Koan.StatementList[0], [p1, p2]);
|
||||
|
||||
var examples: Garden[] = [];
|
||||
|
||||
console.log('Attempt to generate examples for "' + test.description + '"');
|
||||
|
||||
var maxGarden = /*(9 * 9) + (9 * 9 * 9 * 8) + */(9 * 9 * 9 * 8 * 9 * 7);
|
||||
let g = Koan.makeEmptyGarden();
|
||||
let passCount = 0, failCount = 0;
|
||||
let resultLookup: boolean[] = [];
|
||||
let lastResult: boolean = undefined;
|
||||
for (var i = 0; i < maxGarden; i++) {
|
||||
Koan.blitNumberedGarden(g, 3, i);
|
||||
let result = test.test(g);
|
||||
if(result === Koan.StateTestResult.Pass) {
|
||||
resultLookup[i] = true;
|
||||
passCount++;
|
||||
|
||||
if (lastResult !== true && examples.length < 10) examples.push(Koan.cloneGarden(g));
|
||||
lastResult = true;
|
||||
} else if (result === Koan.StateTestResult.Fail) {
|
||||
resultLookup[i] = false;
|
||||
failCount++;
|
||||
|
||||
if (lastResult !== false && examples.length < 10) examples.push(Koan.cloneGarden(g));
|
||||
lastResult = false;
|
||||
}
|
||||
|
||||
if (examples.length === 10) break;
|
||||
}
|
||||
|
||||
console.log('Rule passes ' + passCount + ' and fails ' + failCount);
|
||||
|
||||
/*
|
||||
if (!test.hasPassedAndFailed()) {
|
||||
console.log('Rule has unreachable, contradictory, or tautological clauses');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (passCount === 0 || failCount === 0) {
|
||||
console.log('Rule is always true or always false');
|
||||
continue;
|
||||
}
|
||||
*/
|
||||
|
||||
var h = document.createElement('h2');
|
||||
h.innerText = test.description;
|
||||
document.body.appendChild(h);
|
||||
|
||||
return { test: test, examples: examples };
|
||||
}
|
||||
}
|
||||
|
||||
let list: Garden[] = [];
|
||||
let test: Koan.ProducedStatement<any>;
|
||||
window.onload = function() {
|
||||
let rule = makeNewExample();
|
||||
let garden = Koan.makeRandomGarden();
|
||||
list = rule.examples;
|
||||
test = rule.test;
|
||||
|
||||
function renderList() {
|
||||
function makeGarden(g: Garden, i: number) {
|
||||
return <GardenDisplay
|
||||
garden={g}
|
||||
key={i + Koan.gardenToString(g)}
|
||||
test={test}
|
||||
leftButton='✗'
|
||||
rightButton='✎'
|
||||
onLeftButtonClicked={() => {
|
||||
console.log(list.indexOf(g));
|
||||
list.splice(list.indexOf(g), 1);
|
||||
renderList();
|
||||
}}
|
||||
onRightButtonClicked={() => {
|
||||
garden = g;
|
||||
renderEditor();
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
let gardenList = <div>{list.map(makeGarden)}</div>;
|
||||
React.render(gardenList, document.getElementById('results'));
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
function renderEditor() {
|
||||
i++;
|
||||
let editor = <GardenEditor key={i} test={rule.test} garden={garden} onSaveClicked={(garden) => {
|
||||
list.push(garden);
|
||||
renderList();
|
||||
}} />;
|
||||
React.render(editor, document.getElementById('editor'));
|
||||
}
|
||||
|
||||
renderList();
|
||||
renderEditor();
|
||||
}
|
||||
|
||||
function classNames(nameMap: any): string {
|
||||
return Object.keys(nameMap).filter(k => nameMap[k]).join(' ');
|
||||
}
|
||||
|
||||
interface GardenCellProps extends React.Props<{}> {
|
||||
color: Gardens.RockColor;
|
||||
shape: Gardens.RockShape;
|
||||
index: number;
|
||||
|
||||
movable?: boolean;
|
||||
onEdit?(newColor: Gardens.RockColor, newShape: Gardens.RockShape): void;
|
||||
}
|
||||
interface GardenCellState {
|
||||
isDragging?: boolean;
|
||||
}
|
||||
class GardenCell extends React.Component<GardenCellProps, GardenCellState> {
|
||||
state: GardenCellState = {};
|
||||
ignoreNextEdit = false;
|
||||
|
||||
render() {
|
||||
var classes = ['cell', 'index_' + this.props.index];
|
||||
|
||||
if (this.state.isDragging) {
|
||||
// Render as blank
|
||||
} else {
|
||||
classes.push(Gardens.RockColor[this.props.color], Gardens.RockShape[this.props.shape]);
|
||||
}
|
||||
|
||||
if (this.props.movable) classes.push('movable');
|
||||
let events: React.HTMLAttributes = {
|
||||
onDragStart: (e) => {
|
||||
this.ignoreNextEdit = false;
|
||||
e.dataTransfer.dropEffect = 'copyMove';
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('shape', this.props.shape.toString());
|
||||
e.dataTransfer.setData('color', this.props.color.toString());
|
||||
|
||||
let drag = document.getElementById(getGardenName(this.props.color, this.props.shape));
|
||||
let xfer: any = (e.nativeEvent as DragEvent).dataTransfer;
|
||||
xfer.setDragImage(drag, drag.clientWidth * 0.5, drag.clientHeight * 0.5);
|
||||
|
||||
this.setState({ isDragging: true });
|
||||
},
|
||||
onDragEnter: (e) => {
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
e.preventDefault();
|
||||
},
|
||||
onDragOver: (e) => {
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
e.preventDefault();
|
||||
},
|
||||
onDragEnd: (e) => {
|
||||
this.setState({ isDragging: false });
|
||||
if (!this.ignoreNextEdit) {
|
||||
this.props.onEdit && this.props.onEdit(undefined, undefined);
|
||||
}
|
||||
},
|
||||
draggable: true
|
||||
}
|
||||
|
||||
let handleDrop = (event: React.DragEvent) => {
|
||||
if(this.props.onEdit) {
|
||||
if (this.state.isDragging) {
|
||||
// Dragged to self, don't do anything
|
||||
this.ignoreNextEdit = true;
|
||||
} else {
|
||||
let shape: Gardens.RockShape = +event.dataTransfer.getData('shape');
|
||||
let color: Gardens.RockColor = +event.dataTransfer.getData('color');
|
||||
this.props.onEdit(color, shape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <span className={classes.join(' ')} onDrop={handleDrop} {...this.props.movable ? events : {}} />;
|
||||
}
|
||||
}
|
||||
|
||||
interface GardenDisplayProps extends React.Props<GardenDisplay> {
|
||||
garden?: Garden;
|
||||
test?: Koan.ProducedStatement<any>;
|
||||
|
||||
leftButton?: string;
|
||||
rightButton?: string;
|
||||
onLeftButtonClicked?(): void;
|
||||
onRightButtonClicked?(): void;
|
||||
|
||||
editable?: boolean;
|
||||
onChanged?(newGarden: Garden): void;
|
||||
}
|
||||
interface GardenDisplayState {
|
||||
garden?: Garden;
|
||||
}
|
||||
class GardenDisplay extends React.Component<GardenDisplayProps, GardenDisplayState> {
|
||||
state = {
|
||||
garden: Koan.cloneGarden(this.props.garden)
|
||||
};
|
||||
|
||||
leftClicked = () => {
|
||||
this.props.onLeftButtonClicked && this.props.onLeftButtonClicked();
|
||||
};
|
||||
|
||||
rightClicked = () => {
|
||||
this.props.onRightButtonClicked && this.props.onRightButtonClicked();
|
||||
};
|
||||
|
||||
render() {
|
||||
let g = this.state.garden;
|
||||
let pass = (this.props.test && this.props.test.test(this.state.garden));
|
||||
|
||||
let classes = {
|
||||
garden: true,
|
||||
unknown: pass === undefined,
|
||||
pass: pass === Koan.StateTestResult.Pass || pass === Koan.StateTestResult.WeakPass,
|
||||
fail: pass === Koan.StateTestResult.Fail,
|
||||
editable: this.props.editable
|
||||
};
|
||||
|
||||
var children = g.colors.map((_, i) => (
|
||||
<GardenCell
|
||||
key={i}
|
||||
color={g.colors[i]}
|
||||
shape={g.shapes[i]}
|
||||
index={i}
|
||||
movable={this.props.editable}
|
||||
onEdit={(newColor, newShape) => {
|
||||
if(this.props.editable) {
|
||||
let newGarden = Koan.cloneGarden(this.state.garden);
|
||||
newGarden.colors[i] = newColor;
|
||||
newGarden.shapes[i] = newShape;
|
||||
this.setState({ garden: newGarden });
|
||||
this.props.onChanged && this.props.onChanged(newGarden);
|
||||
}
|
||||
}}
|
||||
/>));
|
||||
|
||||
return <div className="gardenDisplay">
|
||||
<div className={classNames(classes)}>{children}</div>
|
||||
<span className="infoRow">
|
||||
{this.props.leftButton && <div className="button left" onClick={this.leftClicked}>{this.props.leftButton}</div>}
|
||||
<div className={"passfail " + (pass ? 'pass' : 'fail')}>{pass ? '✓' : '🚫'}</div>
|
||||
{this.props.rightButton && <div className="button right" onClick={this.rightClicked}>{this.props.rightButton}</div>}
|
||||
</span>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
interface GardenEditorProps extends React.Props<GardenEditor> {
|
||||
onSaveClicked?(garden: Garden): void;
|
||||
test?: Koan.ProducedStatement<any>;
|
||||
garden?: Garden;
|
||||
}
|
||||
interface GardenEditorState {
|
||||
garden?: Garden;
|
||||
pass?: boolean;
|
||||
}
|
||||
class GardenEditor extends React.Component<GardenEditorProps, {}> {
|
||||
state = { garden: this.props.garden };
|
||||
|
||||
save = () => {
|
||||
this.props.onSaveClicked && this.props.onSaveClicked(this.state.garden);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div className="editor">
|
||||
<GardenDisplay garden={this.state.garden} test={this.props.test} editable onChanged={g => this.setState({ garden: g }) } />
|
||||
<StonePalette />
|
||||
<div className="button save" onClick={this.save}>{'💾'}</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
class StonePalette extends React.Component<{}, {}> {
|
||||
render() {
|
||||
let items: JSX.Element[] = [];
|
||||
Gardens.RockColors.forEach(color => {
|
||||
Gardens.RockShapes.forEach(shape => {
|
||||
let name = getGardenName(color, shape);
|
||||
let extraProps = { id: name, key: name };
|
||||
let index = items.length;
|
||||
items.push(<GardenCell
|
||||
color={color}
|
||||
shape={shape}
|
||||
index={index}
|
||||
movable
|
||||
{...extraProps} />)
|
||||
});
|
||||
});
|
||||
return <div className="palette">{items}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function getGardenName(color: Gardens.RockColor, shape: Gardens.RockShape) {
|
||||
return 'draggable.' + Gardens.RockShape[shape] + '.' + Gardens.RockColor[color];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user