Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RTL support #2890

Merged
merged 49 commits into from
Feb 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
b536e14
Add Hebrew to list of languages
Jan 14, 2020
030c6a6
Minor styling adjustments to playground
Jan 14, 2020
143f857
Update Basic Web Chat to function component
Jan 17, 2020
29d2b9f
move prettier-readmes into lint-staged
Jan 17, 2020
9290681
Add getRTLList.js to fetch RTL languages
corinagum Jan 17, 2020
9cacc81
update package.jsono
corinagum Jan 17, 2020
7cecc39
Determine direction based off language if dir='auto'
corinagum Jan 17, 2020
9ac322d
Support manually setting direction in playground
corinagum Jan 17, 2020
51e906a
Add margins to both side of components, ex spinner, for RTL
Jan 22, 2020
87cd6f3
Deprecate spinnerAnimationPaddingRight in favor of spinnerAnimationPa…
Jan 22, 2020
e005ecb
Fix CHANGELOG rebase error
Jan 23, 2020
262c638
Refactor useDirection
Jan 23, 2020
1d40f23
Suggested actions RTL
Jan 23, 2020
7522879
Fix SpinnerAnimation styling for rtl and ltr
Jan 23, 2020
705b2ee
tranform SendIcon and TypingAnimation based on dir
Jan 24, 2020
254ce60
Update LOCALIZATION file
Jan 24, 2020
06d01ef
Update playground App.js with ar-JO
Jan 24, 2020
2c40660
Add ar-JO to localization with rtl support
corinagum Jan 24, 2020
052cfce
Add rtl languages to embed locale
corinagum Jan 24, 2020
beea6c1
Add rtl support for Carousel
corinagum Jan 24, 2020
87ecf72
Add rtl support for bubble nub
Jan 28, 2020
e595834
Add rtl support to Scroll to end button
Jan 28, 2020
1f75936
Fix carousel film strip timestamp in rtl
Jan 29, 2020
6a0eb1f
Add useDirection documentation to HOOKS.md
Jan 29, 2020
7d39c99
md file cleanup
Jan 30, 2020
1e506b3
Refactor fixes
Jan 30, 2020
b68e6b4
RTL support for icons
Jan 30, 2020
088be32
Refactor RTL support in Bubble.js
corinagum Feb 4, 2020
52607f5
Refactor RTL support in carousel
corinagum Feb 4, 2020
d5c3d5c
Refactor RTL for stacked and error
corinagum Feb 4, 2020
144f6e6
Refactor other components for RTL
corinagum Feb 4, 2020
fa07b83
Add unofficial Adaptive Card RTL support
Feb 5, 2020
a85a488
Add RTL to FileContent component
Feb 6, 2020
5780a8d
File cleanup
corinagum Feb 7, 2020
013fc96
Restore change-locale sample & create change-locale-direction
corinagum Feb 10, 2020
2ffba9a
Carousel padding fix & readme cleanup
corinagum Feb 11, 2020
ba4a5b9
Linting fixes
corinagum Feb 11, 2020
13fe63d
Update CHANGELOG & documentation
corinagum Feb 11, 2020
0a949c1
Fix linting errors
corinagum Feb 11, 2020
16cfae2
New RTL tests
corinagum Feb 12, 2020
01bfb41
Add retaken screenshots
Feb 12, 2020
af929b7
Remove echos from tableflip command
Feb 13, 2020
bc859ef
Add more rtl tests
Feb 13, 2020
9413160
Fix #2903
Feb 13, 2020
f058d61
Fix #2902
Feb 13, 2020
410e200
Apply suggestions from code review
corinagum Feb 14, 2020
a4d43fe
Merge branch '1976-RTL' of github.com:corinagum/BotFramework-WebChat …
Feb 14, 2020
98e087f
Comment cleanup
Feb 14, 2020
6933d48
Update packages/component/src/Styles/StyleSet/SpinnerAnimation.js
corinagum Feb 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Added custom hooks - `useTimer` and `useIntervalSince` - to replace the headless `Timer` component, by [@tdurnford](https://github.com/tdurnford), in PR [#2771](https://github.com/microsoft/BotFramework-WebChat/pull/2771)
- Resolves [#2720](https://github.com/Microsoft/BotFramework-WebChat/issues/2720), added customizable activity status using `activityStatusMiddleware` props, by [@compulim](https://github.com/compulim), in PR [#2788](https://github.com/microsoft/BotFramework-WebChat/pull/2788)
- Added default `onError` prop to the `Dictation` component, by [@tonyanziano](https://github.com/tonyanziano), in PR [#2866](https://github.com/microsoft/BotFramework-WebChat/pull/2866)
- Resolves [#1976](https://github.com/microsoft/BotFramework-WebChat/issues/1976). Added RTL support with localization for Hebrew and Arabic, by [@corinagum](https://github.com/corinagum), in PR [#2890](https://github.com/microsoft/BotFramework-WebChat/pull/2890)

### Fixed

Expand Down
4 changes: 3 additions & 1 deletion LOCALIZATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ If you want to help to translate Web Chat to different language, please submit a

| Language code | Translator |
| ------------- | ---------------------------------------------------------- |
| ar-EG | @midineo |
| ar-eg | @midineo |
| ar-jo | muminasaad, Odai Hatem AbuGaith |
| bg-bg | @kalin.krustev |
| cs-cz | @msimecek |
| da-dk | @Simon_lfr, Thomas Skødt Andersen |
Expand All @@ -15,6 +16,7 @@ If you want to help to translate Web Chat to different language, please submit a
| es-es | @SantiEspada, @ckgrafico, @renrous, @axelsrz, @munozemilio |
| fi-fi | @jsur, @sk91swd |
| fr-fr | @meulta, @tao1 |
| he-il | @geea-develop |
| hu-hu | |
| it-it | Maurizio Moriconi, @Andrea-Orimoto, @AntoT84 |
| ja-jp | @bigplants, @corinagum |
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ Here is how how you can add Web Chat control to your website:
body {
height: 100%;
}

body {
margin: 0;
}
Expand Down
2 changes: 1 addition & 1 deletion __tests__/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Automated testing in Web Chat is using multiple open-source technologies.

- Install Docker
- On Windows, set environment variable `COMPOSE_CONVERT_WINDOWS_PATHS=1`
- `npm run start:docker`
- `docker-compose up --build`
- In a separate terminal, run:
- `npm test`

Expand Down
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.
158 changes: 158 additions & 0 deletions __tests__/rtl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { By } from 'selenium-webdriver';

import { imageSnapshotOptions, timeouts } from './constants.json';

import allImagesLoaded from './setup/conditions/allImagesLoaded';
import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown';
import suggestedActionsShown from './setup/conditions/suggestedActionsShown';
import uiConnected from './setup/conditions/uiConnected';

// selenium-webdriver API doc:
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html

jest.setTimeout(timeouts.test);

async function sendMessageAndMatchSnapshot(driver, pageObjects, message) {
await driver.wait(uiConnected(), timeouts.directLine);
await pageObjects.sendMessageViaSendBox(message);

await driver.wait(minNumActivitiesShown(2), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetchImage);

const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
}

describe('rtl UI', () => {
let props;

beforeEach(() => {
props = {
locale: 'ar-EG'
};
});

test('should show "unable to connect" UI in Arabic when credentials are incorrect', async () => {
const { driver } = await setupWebDriver({
props: {
...props
},
createDirectLine: () => {
return window.WebChat.createDirectLine({ token: 'INVALID-TOKEN' });
},
pingBotOnLoad: false,
setup: () =>
new Promise(resolve => {
const scriptElement = document.createElement('script');

scriptElement.onload = resolve;
scriptElement.setAttribute('src', 'https://unpkg.com/[email protected]/client/core.min.js');

document.head.appendChild(scriptElement);
})
});

await driver.wait(async driver => {
return await driver.executeScript(
() => !!~window.WebChatTest.actions.findIndex(({ type }) => type === 'DIRECT_LINE/CONNECT_REJECTED')
);
}, timeouts.directLine);

const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
});

test('unknown command with nubs should display correctly', async () => {
const { driver, pageObjects } = await setupWebDriver({
props: {
...props,
styleOptions: {
bubbleNubOffset: 0,
bubbleNubSize: 10,
bubbleFromUserNubOffset: 0,
bubbleFromUserNubSize: 10
}
}
});

await sendMessageAndMatchSnapshot(driver, pageObjects, 'صباح الخير');
});

test('carousel with avatar initials should display user and bot in reversed positions', async () => {
const { driver, pageObjects } = await setupWebDriver({
props: {
...props,
styleOptions: {
botAvatarInitials: 'WC',
userAvatarInitials: 'WW'
}
}
});

await sendMessageAndMatchSnapshot(driver, pageObjects, 'arabic carousel');
});

test('carousel should scroll to the left instead of right', async () => {
const { driver, pageObjects } = await setupWebDriver({
props: {
...props
}
});

await driver.wait(uiConnected(), timeouts.directLine);
await pageObjects.sendMessageViaSendBox('arabic carousel', { waitForSend: true });

await driver.wait(minNumActivitiesShown(2), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetchImage);

const leftFlipper = await driver.findElement(By.css('button[aria-label="يسار"]'));

await leftFlipper.click();
await leftFlipper.click();
await leftFlipper.click();
await leftFlipper.click();

// Wait for carousel animation to finish
await driver.sleep(timeouts.ui);

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
});

test('with Adaptive Card should be displayed correctly', async () => {
const { driver, pageObjects } = await setupWebDriver({
props: {
...props
}
});

await sendMessageAndMatchSnapshot(driver, pageObjects, 'card arabicgreeting');
});

test('with Audio Card should be displayed correctly', async () => {
const { driver, pageObjects } = await setupWebDriver({
props: {
...props
}
});

await sendMessageAndMatchSnapshot(driver, pageObjects, 'audiocard');
});

test('should show suggested actions with images', async () => {
const { driver, pageObjects } = await setupWebDriver({
props: { ...props }
});

await driver.wait(uiConnected(), timeouts.directLine);
await pageObjects.sendMessageViaSendBox('emptycard', { waitForSend: true });

await driver.wait(suggestedActionsShown(), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetchImage);

const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
});
});
18 changes: 17 additions & 1 deletion docs/HOOKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Following is the list of hooks supported by Web Chat API.
- [`useConnectivityStatus`](#useconnectivitystatus)
- [`useDictateInterims`](#usedictateinterims)
- [`useDictateState`](#usedictatestate)
- [`useDirection`](#useDirection)
- [`useDisabled`](#usedisabled)
- [`useEmitTypingIndicator`](#useemittypingindicator)
- [`useFocusSendBox`](#usefocussendbox)
Expand Down Expand Up @@ -192,6 +193,21 @@ This function will return one of the following dictation states:

To control dictate state, use the [`useStartDictate`](#usestartdictate) and [`useStopDictate`](#usestopdictate) hooks.

## `useDirection`

```js
useDirection(): [string]
```

This function will return one of two language directions:

- `ltr` or otherwise: Web Chat UI will display as left-to-right
- `rtl`: Web Chat UI will display as right-to-left

This value will be automatically configured based on the `locale` of Web Chat.

If you would prefer to set this property manually, change the value `dir` prop passed to Web Chat.

## `useDisabled`

```js
Expand Down Expand Up @@ -248,7 +264,7 @@ useLanguage(): [string]

This function will return the language of the UI. All UI components should honor this value.

To modify this value, change the value in the style options prop passed to Web Chat.
To modify this value, change the value in the `locale` prop passed to Web Chat.

## `useLastTypingAt`

Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
"prettier --write __tests__/**/*.{js,jsx,ts,tsx}",
"prettier --write samples/**/*.{js,html}",
"git add"
],
"*.{md}": [
"prettier --write **/**/*.md --tab-width 3 --single-quote true"
]
},
"scripts": {
Expand All @@ -37,9 +40,7 @@
"posteslint": "npm run prettier-readmes",
"prettier-readmes": "prettier --write **/**/*.md --tab-width 3 --single-quote true",
"start": "concurrently --kill-others --raw \"serve\" \"lerna run --ignore playground --parallel --stream start\"",
"tableflip": "npm run tableflip:start && npx lerna clean --yes --concurrency 8 && npx rimraf node_modules && npm ci && npm run bootstrap -- --concurrency 2 && npm run tableflip:end",
"tableflip:end": "echo ┬──┬ ノ( ゜-゜ノ) Tableflip complete. Now run npm start",
"tableflip:start": "echo (╯ರ ~ ರ)╯︵ ┻━┻ Begin tableflip.",
"tableflip": "npx lerna clean --yes --concurrency 8 && npx rimraf node_modules && npm ci && npm run bootstrap -- --concurrency 8",
"test": "jest --watch",
"test:ci": "npm run test -- --ci --coverage true --no-watch",
"watch": "echo NPM script \"watch\" has been replaced with \"start\"."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import React, { useMemo } from 'react';
import AdaptiveCardRenderer from './AdaptiveCardRenderer';
import useAdaptiveCardsPackage from '../hooks/useAdaptiveCardsPackage';

import { hooks } from 'botframework-webchat-component';

const { useDirection } = hooks;

function stripSubmitAction(card) {
if (!card.actions) {
return card;
Expand All @@ -17,8 +21,27 @@ function stripSubmitAction(card) {
return { ...card, nextActions };
}

function updateRTLInline(element, rtl, adaptiveCardsPackage) {
if (element instanceof adaptiveCardsPackage.Container) {
element.rtl = rtl;
}

// Tree traversal to add rtl boolean to child elements
if (element.getItemAt && element.getItemCount) {
const count = element.getItemCount();

for (let index = 0; index < count; index++) {
const child = element.getItemAt(index);

updateRTLInline(child, rtl, adaptiveCardsPackage);
}
}
}

const AdaptiveCardAttachment = ({ attachment: { content } }) => {
const [{ AdaptiveCard }] = useAdaptiveCardsPackage();
const [adaptiveCardsPackage] = useAdaptiveCardsPackage();
const { AdaptiveCard } = adaptiveCardsPackage;
const [direction] = useDirection();
const { card } = useMemo(() => {
if (content) {
const card = new AdaptiveCard();
Expand All @@ -34,6 +57,9 @@ const AdaptiveCardAttachment = ({ attachment: { content } }) => {
})
);

// Add rtl to Adaptive Card and child elements if Web Chat direction is 'rtl'
updateRTLInline(card, direction === 'rtl', adaptiveCardsPackage);

AdaptiveCard.onParseError = null;

return {
Expand All @@ -43,7 +69,7 @@ const AdaptiveCardAttachment = ({ attachment: { content } }) => {
}

return {};
}, [AdaptiveCard, content]);
}, [AdaptiveCard, adaptiveCardsPackage, content, direction]);

return !!card && <AdaptiveCardRenderer adaptiveCard={card} />;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ export default class AdaptiveCardBuilder {
container: Container;
styleOptions: any;

constructor(adaptiveCards, styleOptions) {
constructor(adaptiveCards, styleOptions, direction = 'ltr') {
this.card = new adaptiveCards.AdaptiveCard();
this.container = new Container();
this.container.rtl = direction === 'rtl';
this.styleOptions = styleOptions;

this.card.addItem(this.container);
Expand Down
7 changes: 4 additions & 3 deletions packages/bundle/src/adaptiveCards/Attachment/CommonCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ import AdaptiveCardBuilder from './AdaptiveCardBuilder';
import AdaptiveCardRenderer from './AdaptiveCardRenderer';
import useAdaptiveCardsPackage from '../hooks/useAdaptiveCardsPackage';

const { useStyleOptions } = hooks;
const { useDirection, useStyleOptions } = hooks;

const CommonCard = ({ attachment: { content } }) => {
const [adaptiveCardsPackage] = useAdaptiveCardsPackage();
const [direction] = useDirection();
const [styleOptions] = useStyleOptions();

const builtCard = useMemo(() => {
if (content) {
const builder = new AdaptiveCardBuilder(adaptiveCardsPackage, styleOptions);
const builder = new AdaptiveCardBuilder(adaptiveCardsPackage, styleOptions, direction);

builder.addCommon(content);

return builder.card;
}
}, [adaptiveCardsPackage, content, styleOptions]);
}, [adaptiveCardsPackage, content, direction, styleOptions]);

return <AdaptiveCardRenderer adaptiveCard={builtCard} tapAction={content && content.tap} />;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import AdaptiveCardBuilder from './AdaptiveCardBuilder';
import AdaptiveCardRenderer from './AdaptiveCardRenderer';
import useAdaptiveCardsPackage from '../hooks/useAdaptiveCardsPackage';

const { useStyleOptions } = hooks;
const { useDirection, useStyleOptions } = hooks;

const HeroCardAttachment = ({ attachment: { content } = {} }) => {
const [adaptiveCardsPackage] = useAdaptiveCardsPackage();
const [styleOptions] = useStyleOptions();
const [direction] = useDirection();
const builtCard = useMemo(() => {
const builder = new AdaptiveCardBuilder(adaptiveCardsPackage, styleOptions);
const builder = new AdaptiveCardBuilder(adaptiveCardsPackage, styleOptions, direction);

if (content) {
(content.images || []).forEach(image => builder.addImage(image.url, null, image.tap));
Expand All @@ -22,7 +23,7 @@ const HeroCardAttachment = ({ attachment: { content } = {} }) => {

return builder.card;
}
}, [adaptiveCardsPackage, content, styleOptions]);
}, [adaptiveCardsPackage, content, direction, styleOptions]);

return <AdaptiveCardRenderer adaptiveCard={builtCard} tapAction={content && content.tap} />;
};
Expand Down
Loading