Skip to content

Commit

Permalink
feat(legend): add keyboard navigation (opensearch-project#880)
Browse files Browse the repository at this point in the history
  • Loading branch information
rshen91 authored Dec 9, 2020
1 parent 4d929d0 commit b471a94
Show file tree
Hide file tree
Showing 36 changed files with 361 additions and 129 deletions.
218 changes: 138 additions & 80 deletions packages/osd-charts/.playground/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,91 +17,149 @@
* under the License.
*/

/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React from 'react';

import {
AnnotationDomainTypes,
Axis,
BarSeries,
Chart,
LineAnnotation,
LineAnnotationDatum,
ScaleType,
Settings,
} from '../src';
import { Icon } from '../src/components/icons/icon';
import { Position } from '../src/utils/commons';
import { arrayKnobs } from '../stories/utils/knobs';

function generateAnnotationData(values: any[]): LineAnnotationDatum[] {
return values.map((value, index) => ({ dataValue: value, details: `detail-${index}` }));
interface Food {
label: string;
count: number;
actionLabel: string;
}
const data = arrayKnobs('data values', [2.5, 7.2]);
const dataValues = generateAnnotationData(data);

type Foods = Array<Food>;

type FoodArray = Array<string>;

export class Playground extends React.Component {
foods: Foods = [
{ label: 'pie', count: 2, actionLabel: 'tab' },
{ label: 'asparagus', count: 5, actionLabel: 'tab' },
{ label: 'brownies', count: 0, actionLabel: 'enter' },
{ label: 'popsicles', count: 3, actionLabel: 'enter' },
];

foodsAsAnArray: FoodArray = ['tab', 'tab', 'tab', 'enter'];

getFoodsArrayAction = (foodsArray: FoodArray) => {
for (let i = 0; i < foodsArray.length; i++) {
if (foodsArray[i] === 'tab') {
// alert('tab!');
} else if (foodsArray[i] === 'enter') {
// alert('enter!');
}
}
};

getFoodAction = (foodLabel: Food[keyof Food]) => {
// eslint-disable-next-line array-callback-return
return this.foods.map(({ label, count, actionLabel }) => {
if (foodLabel === label && actionLabel === 'tab') {
let c = 0;
while (c < count) {
// alert(`${label} Tab!`);
c++;
}
} else if (foodLabel === label && actionLabel === 'enter') {
let c = 0;
while (c < count) {
// alert(`${label} Enter!`);
c++;
}
}
});
};

makingFood = (ms: number, food: string) => {
return new Promise((resolve) => {
setTimeout(() => {
// console.log(`resolving the promise ${food}`, new Date());
resolve(`done with ${food}`);
}, ms);
// console.log(`starting the promise ${food}`, new Date());
});
};

getNumberOfFood = (food: any) => {
return this.makingFood(1000, food).then(() => this.getFoodAction(food));
};

getNumberOfFoodArray = () => {
return this.makingFood(1000, 'apple').then(() => this.getFoodsArrayAction(this.foodsAsAnArray));
};

getAsyncNumberOfFoodArray = async () => {
// const result = await this.makingFood(2000).then(() => this.getFoodsArrayAction(this.foodsAsAnArray));
// alert(result);
};

// forLoop = async () => {
// alert('start');
// for (let index = 0; index < this.foods.length; index++) {
// const foodLabel = this.foods[index].label;
// const numFood = await this.getFoodNumber(foodLabel);
// alert(numFood);
// }
// const foodsPromiseArray = this.foods.map(async (foodObject) => {
// for (let i = 0; i < foodObject.length; i++) {
// const numFoodAction = foodObject[i].actionLabel;
// if (numFoodAction === 'enter') {
// alert ('Enter!');
// } else if (numFoodAction === 'tab') {
// alert('tab!');
// }
// }
// });
// const numberOfFoods = await Promise.all(foodsPromiseArray);
// alert(numberOfFoods);
// alert('End');
// };

// getFoodArray =
// async() => {
// console.log(await this.makingFood(1000, 'apricot'));
// console.log(await this.makingFood(50, 'apple'));
// const foodTimeArray = [
// { ms: 1000, food: 'a', count: 2 },
// { ms: 50, food: 'b', count: 1 },
// { ms: 500, food: 'c', count: 3 },
// ];

// for (let i = 0; i < foodTimeArray.length; i++) {
// void this.makingFood(foodTimeArray[i].ms, foodTimeArray[i].food);
// }

// const foodMap = foodTimeArray.map(({ ms, food }) => {
// return this.makingFood(ms, food);
// });

// console.log('before the promise');
// for (const i of foodTimeArray) {
// const j = 0;
// while (j < i.count) {
// await this.makingFood(i.ms, i.food);
// j++;
// }
// }

// await Promise.all();
// console.log('after the promise');
// };

render() {
return (
<div className="chart">
<Chart className="story-chart">
<Settings showLegend showLegendExtra />
<LineAnnotation
id="annotation_1"
domainType={AnnotationDomainTypes.XDomain}
dataValues={dataValues}
marker={<Icon type="alert" />}
/>
<LineAnnotation id="1" domainType={AnnotationDomainTypes.YDomain} dataValues={dataValues} />
<Axis id="horizontal" position={Position.Bottom} title="x-domain axis" />
<Axis id="left" title="y-domain axis left" position={Position.Left} />
<Axis id="right" title="y-domain axis right" position={Position.Right} />
<BarSeries
id="bars"
groupId="group1"
xScaleType={ScaleType.Linear}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={[
{ x: 0, y: 0 },
{ x: 1, y: 5 },
{ x: 3, y: 20 },
]}
/>
<BarSeries
id="bars1"
groupId="group2"
xScaleType={ScaleType.Linear}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={[
{ x: 0, y: 100 },
{ x: 1, y: 50 },
{ x: 3, y: 200 },
]}
/>
</Chart>
</div>
);
// console.log(this.makingFood(1000, 'apricot'));
// console.log(this.makingFood(50, 'apple'));
// void this.getFoodArray();

return null;
// <>
// <div className="page" style={{ width: 5000, height: 5000, backgroundColor: 'yellow' }}>
// <div id="root" style={{ backgroundColor: 'blueviolet' }}>
// {/* <div>{this.foods.map(({ label }) => this.getNumberOfFood(label))}</div> */}
// {/* <div>{alert(this.makingFood(50000).then(this.getFoodsArrayAction(this.foodsAsAnArray)))}</div> */}
// {/* <div>{alert(this.getNumberOfFoodArray())}</div> */}
// {/* <div>{alert(this.getAsyncNumberOfFoodArray())}</div> */}
// <div>{this.makingFood(50)}</div>
// </div>
// </div>
// </>
}
}
13 changes: 13 additions & 0 deletions packages/osd-charts/.storybook/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ html {
position: relative;
box-sizing: border-box;
background-color: blanchedalmond;

&.disable-animations {
*,
*::after,
*::before {
transition-delay: 0s !important;
transition-duration: 0s !important;
animation-delay: -0.0001s !important;
animation-duration: 0s !important;
animation-play-state: paused !important;
caret-color: transparent !important;
}
}
}

#story-root + div table {
Expand Down
80 changes: 77 additions & 3 deletions packages/osd-charts/integration/page_objects/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ interface ElementBBox {
height: number;
}

interface KeyboardKey {
key: string;
count: number;
}

type KeyboardKeys = Array<KeyboardKey>;

/**
* Used to get postion from any value of cursor position
*
Expand Down Expand Up @@ -211,6 +218,15 @@ class CommonPage {
return buffer;
}

/**
* Move mouse
* @param mousePosition
* @param selector
*/
async moveMouse(x: number, y: number) {
await page.mouse.move(x, y);
}

/**
* Move mouse relative to element
*
Expand All @@ -220,7 +236,7 @@ class CommonPage {
async moveMouseRelativeToDOMElement(mousePosition: MousePosition, selector: string) {
const element = await this.getBoundingClientRect(selector);
const { x, y } = getCursorPosition(mousePosition, element);
await page.mouse.move(x, y);
await this.moveMouse(x, y);
}

/**
Expand All @@ -245,10 +261,10 @@ class CommonPage {
const element = await this.getBoundingClientRect(selector);
const { x: x0, y: y0 } = getCursorPosition(start, element);
const { x: x1, y: y1 } = getCursorPosition(end, element);
await page.mouse.move(x0, y0);
await this.moveMouse(x0, y0);
await page.mouse.down();
await page.waitFor(DRAG_DETECTION_TIMEOUT);
await page.mouse.move(x1, y1);
await this.moveMouse(x1, y1);
}

/**
Expand All @@ -261,6 +277,30 @@ class CommonPage {
await page.mouse.up();
}

/**
* Press keyboard keys
* @param count
* @param key
*/
// eslint-disable-next-line class-methods-use-this
async pressKey(key: string, count: number) {
if (key === 'tab') {
let i = 0;
while (i < count) {
// eslint-disable-next-line eslint-comments/disable-enable-pair
/* eslint-disable no-await-in-loop */
await page.keyboard.press('Tab');
i++;
}
} else if (key === 'enter') {
let i = 0;
while (i < count) {
await page.keyboard.press('Enter');
i++;
}
}
}

/**
* Drag and drop mouse relative to element
*
Expand Down Expand Up @@ -341,6 +381,34 @@ class CommonPage {
});
}

/**
* Expect a chart given a url from storybook with keyboard events
* @param url
* @param keyboardEvents
* @param options
*/
async expectChartWithKeyboardEventsAtUrlToMatchScreenshot(
url: string,
keyboardEvents: KeyboardKeys,
options?: Omit<ScreenshotElementAtUrlOptions, 'action'>,
) {
const action = async () => {
await this.disableAnimations();
// click to focus within the chart
await this.clickMouseRelativeToDOMElement({ top: 0, left: 0 }, this.chartSelector);
// eslint-disable-next-line no-restricted-syntax
for (const actions of keyboardEvents) {
await this.pressKey(actions.key, actions.count);
}
await this.moveMouseRelativeToDOMElement({ top: 0, left: 0 }, this.chartSelector);
};

await this.expectChartAtUrlToMatchScreenshot(url, {
...options,
action,
});
}

/**
* Expect a chart given a url from storybook with mouse move
*
Expand Down Expand Up @@ -383,6 +451,12 @@ class CommonPage {
});
}

async disableAnimations() {
await page.evaluate(() => {
document.querySelector('#story-root')!.classList.add('disable-animations');
});
}

/**
* Wait for an element to be on the DOM
*
Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit b471a94

Please sign in to comment.