Π·Π΅ΡΠΊΠ°Π»ΠΎ ΠΈΠ· https://github.com/microsoft/BotFramework-Emulator.git
π integrate prettier and eslint (#1240)
This commit is contained in:
Π ΠΎΠ΄ΠΈΡΠ΅Π»Ρ
877843dee4
ΠΠΎΠΌΠΌΠΈΡ
d71c03c25a
4
.babelrc
4
.babelrc
|
@ -10,9 +10,7 @@
|
|||
],
|
||||
"@babel/preset-typescript"
|
||||
],
|
||||
"ignore": [
|
||||
"**/*.d.ts"
|
||||
],
|
||||
"ignore": ["**/*.d.ts"],
|
||||
"sourceMaps": "inline",
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
module.exports = {
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:typescript/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
plugins: ['import', 'notice'],
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
rules: {
|
||||
// eslint rules
|
||||
'no-dupe-class-members': 'off',
|
||||
'no-undef': 'off', // ts compiler catches this
|
||||
'prefer-const': 'error',
|
||||
|
||||
// plugin: import
|
||||
'import/first': 'error',
|
||||
'import/order': ['error', { 'newlines-between': 'always' }],
|
||||
|
||||
// plugin: notice
|
||||
'notice/notice': [
|
||||
'error',
|
||||
{
|
||||
mustMatch: 'Copyright \\(c\\) Microsoft',
|
||||
templateFile: require.resolve('./copyright.js'),
|
||||
messages: {
|
||||
whenFailedToMatch: 'Missing copyright header.',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// plugin: typescript
|
||||
'typescript/explicit-function-return-type': 'off',
|
||||
'typescript/explicit-member-accessibility': 'off',
|
||||
'typescript/indent': 'off',
|
||||
'typescript/no-empty-interface': 'warn',
|
||||
'typescript/no-object-literal-type-assertion': 'off',
|
||||
'typescript/no-parameter-properties': 'off',
|
||||
'typescript/no-use-before-define': [
|
||||
'error',
|
||||
{ functions: false, classes: false },
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.+(js|jsx)'],
|
||||
parser: 'babel-eslint',
|
||||
},
|
||||
{
|
||||
files: ['**/*.+(test|spec).+(js|jsx|ts|tsx)'],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'typescript/class-name-casing': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
module.exports = {
|
||||
extends: ['./.eslintrc.js', 'plugin:react/recommended'],
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'react/no-deprecated': 'warn',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.+(test|spec).+(js|jsx|ts|tsx)'],
|
||||
rules: {
|
||||
'react/display-name': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"parser": "typescript",
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
Π Π°Π·Π½ΠΈΡΠ° ΠΌΠ΅ΠΆΠ΄Ρ ΡΠ°ΠΉΠ»Π°ΠΌΠΈ Π½Π΅ ΠΏΠΎΠΊΠ°Π·Π°Π½Π° ΠΈΠ·-Π·Π° ΡΠ²ΠΎΠ΅Π³ΠΎ Π±ΠΎΠ»ΡΡΠΎΠ³ΠΎ ΡΠ°Π·ΠΌΠ΅ΡΠ°
ΠΠ°Π³ΡΡΠ·ΠΈΡΡ ΡΠ°Π·Π½ΠΈΡΡ
18
package.json
18
package.json
|
@ -2,6 +2,8 @@
|
|||
"scripts": {
|
||||
"bootstrap": "lerna bootstrap --hoist",
|
||||
"build": "npm rebuild node-sass && lerna run build",
|
||||
"lint": "lerna run lint --no-bail",
|
||||
"lint:fix": "lerna run lint:fix --no-bail",
|
||||
"start": "cd packages\\app\\client && npm run start",
|
||||
"test": "jest --no-cache",
|
||||
"test:coveralls": "jest --runInBand --bail --coverage --coverageReporters=text-lcov | coveralls"
|
||||
|
@ -15,7 +17,10 @@
|
|||
"@babel/preset-env": "^7.1.0",
|
||||
"@babel/preset-typescript": "^7.1.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-jest": "23.6.0"
|
||||
"babel-jest": "23.6.0",
|
||||
"husky": "^1.3.1",
|
||||
"lint-staged": "^8.1.0",
|
||||
"prettier": "^1.15.3"
|
||||
},
|
||||
"jest": {
|
||||
"setupTestFrameworkScriptFile": "./testSetup.js",
|
||||
|
@ -35,6 +40,17 @@
|
|||
"node"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx,js,jsx}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"lerna": "3.4.0"
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
**/*.scss.d.ts
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
extends: '../../../.eslintrc.react.js',
|
||||
};
|
Π Π°Π·Π½ΠΈΡΠ° ΠΌΠ΅ΠΆΠ΄Ρ ΡΠ°ΠΉΠ»Π°ΠΌΠΈ Π½Π΅ ΠΏΠΎΠΊΠ°Π·Π°Π½Π° ΠΈΠ·-Π·Π° ΡΠ²ΠΎΠ΅Π³ΠΎ Π±ΠΎΠ»ΡΡΠΎΠ³ΠΎ ΡΠ°Π·ΠΌΠ΅ΡΠ°
ΠΠ°Π³ΡΡΠ·ΠΈΡΡ ΡΠ°Π·Π½ΠΈΡΡ
|
@ -12,7 +12,8 @@
|
|||
"build:shared:dev": "webpack --mode development --progress --colors",
|
||||
"build:app:dev": "webpack --mode development --progress --colors",
|
||||
"build": "run-s lint build:vendors build:shared build:app",
|
||||
"lint": "tslint --project tsconfig.json",
|
||||
"lint": "eslint --color --quiet --ext .js,.jsx,.ts,.tsx ./src",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"start": "run-s build:vendors:dev build:shared:dev webpackdevServer:dev",
|
||||
"webpackdevServer:dev": "webpack-dev-server --mode development --hot --inline --progress --colors --content-base ./public",
|
||||
"test": "jest"
|
||||
|
@ -50,6 +51,7 @@
|
|||
"@types/react": "~16.3.2",
|
||||
"@types/react-dom": "^16.0.4",
|
||||
"@types/request": "^2.47.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "23.6.0",
|
||||
"babel-loader": "^8.0.2",
|
||||
"babel-preset-react-app": "^3.1.1",
|
||||
|
@ -59,6 +61,12 @@
|
|||
"css-loader": "^0.28.11",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-16": "^1.1.1",
|
||||
"eslint": "^5.12.0",
|
||||
"eslint-config-prettier": "^3.5.0",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-notice": "^0.7.7",
|
||||
"eslint-plugin-prettier": "^3.0.1",
|
||||
"eslint-plugin-typescript": "^1.0.0-rc.3",
|
||||
"file-loader": "^1.1.11",
|
||||
"hard-source-webpack-plugin": "^0.12.0",
|
||||
"jest": "^23.0.0",
|
||||
|
@ -75,8 +83,6 @@
|
|||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.21.0",
|
||||
"ts-loader": "^4.4.2",
|
||||
"tslint": "^5.10.0",
|
||||
"tslint-loader": "^3.6.0",
|
||||
"typescript": "3.1.1",
|
||||
"typings-for-css-modules-loader": "^1.7.0",
|
||||
"url-loader": "^1.0.1",
|
||||
|
|
|
@ -1,44 +1,81 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { BotConfigWithPathImpl, CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import {
|
||||
BotConfigWithPathImpl,
|
||||
CommandRegistryImpl,
|
||||
} from '@bfemulator/sdk-shared';
|
||||
import { combineReducers, createStore } from 'redux';
|
||||
|
||||
import * as BotActions from '../data/action/botActions';
|
||||
import { bot } from '../data/reducer/bot';
|
||||
import { resources } from '../data/reducer/resourcesReducer';
|
||||
import { CommandServiceImpl } from '../platform/commands/commandServiceImpl';
|
||||
import { ActiveBotHelper } from '../ui/helpers/activeBotHelper';
|
||||
|
||||
import { registerCommands } from './botCommands';
|
||||
import { combineReducers, createStore } from 'redux';
|
||||
|
||||
const mockBotInfo = {
|
||||
path: 'some/path.bot',
|
||||
displayName: 'MyBot',
|
||||
secret: 'secret'
|
||||
secret: 'secret',
|
||||
};
|
||||
|
||||
const mockBot = BotConfigWithPathImpl.fromJSON({
|
||||
'path': 'some/path',
|
||||
'name': 'AuthBot',
|
||||
'description': '',
|
||||
'padlock': '',
|
||||
'services': [
|
||||
path: 'some/path',
|
||||
name: 'AuthBot',
|
||||
description: '',
|
||||
padlock: '',
|
||||
services: [
|
||||
{
|
||||
'appId': '4f8fde3f-48d3-4d8a-a954-393efe39809e',
|
||||
'id': 'cded37c0-83f2-11e8-ac6d-b7172cd24b28',
|
||||
'type': 'endpoint',
|
||||
'appPassword': 'REDACTED',
|
||||
'endpoint': 'http://localhost:55697/api/messages',
|
||||
'name': 'authsample'
|
||||
}
|
||||
]
|
||||
appId: '4f8fde3f-48d3-4d8a-a954-393efe39809e',
|
||||
id: 'cded37c0-83f2-11e8-ac6d-b7172cd24b28',
|
||||
type: 'endpoint',
|
||||
appPassword: 'REDACTED',
|
||||
endpoint: 'http://localhost:55697/api/messages',
|
||||
name: 'authsample',
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
|
||||
let mockStore = createStore(combineReducers({ bot, resources }), {
|
||||
bot: { botFiles: [mockBotInfo] }
|
||||
const mockStore = createStore(combineReducers({ bot, resources }), {
|
||||
bot: { botFiles: [mockBotInfo] },
|
||||
});
|
||||
|
||||
jest.mock('../data/store', () => ({
|
||||
get store() {
|
||||
return mockStore;
|
||||
}
|
||||
},
|
||||
}));
|
||||
jest.mock('../ui/dialogs/', () => ({}));
|
||||
|
||||
|
@ -51,7 +88,9 @@ describe('The bot commands', () => {
|
|||
|
||||
it('should make the appropriate calls to switch bots', () => {
|
||||
const spy = jest.spyOn(ActiveBotHelper, 'confirmAndSwitchBots');
|
||||
const { handler } = registry.getCommand(SharedConstants.Commands.Bot.Switch);
|
||||
const { handler } = registry.getCommand(
|
||||
SharedConstants.Commands.Bot.Switch
|
||||
);
|
||||
handler({});
|
||||
expect(spy).toHaveBeenCalledWith({});
|
||||
});
|
||||
|
@ -82,11 +121,15 @@ describe('The bot commands', () => {
|
|||
it('should make the appropriate calls to sync the bot list', () => {
|
||||
const dispatchSpy = jest.spyOn(mockStore, 'dispatch');
|
||||
const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall');
|
||||
const { handler } = registry.getCommand(SharedConstants.Commands.Bot.SyncBotList);
|
||||
const { handler } = registry.getCommand(
|
||||
SharedConstants.Commands.Bot.SyncBotList
|
||||
);
|
||||
handler([{}]);
|
||||
|
||||
expect(dispatchSpy).toHaveBeenCalledWith(BotActions.load([{}]));
|
||||
expect(remoteCallSpy).toHaveBeenCalledWith(SharedConstants.Commands.Electron.UpdateFileMenu);
|
||||
expect(remoteCallSpy).toHaveBeenCalledWith(
|
||||
SharedConstants.Commands.Electron.UpdateFileMenu
|
||||
);
|
||||
});
|
||||
|
||||
it('should make the appropriate call when setting the active bot', async () => {
|
||||
|
@ -95,7 +138,9 @@ describe('The bot commands', () => {
|
|||
remoteCallArgs.push(args);
|
||||
return true;
|
||||
};
|
||||
const { handler } = registry.getCommand(SharedConstants.Commands.Bot.SetActive);
|
||||
const { handler } = registry.getCommand(
|
||||
SharedConstants.Commands.Bot.SetActive
|
||||
);
|
||||
await handler(mockBot, mockBotInfo.path);
|
||||
const state: any = mockStore.getState();
|
||||
expect(state.bot.activeBot).toEqual(mockBot);
|
||||
|
@ -104,23 +149,32 @@ describe('The bot commands', () => {
|
|||
});
|
||||
|
||||
it('should dispatch the appropriate actions when updating the list of transcript files on disc', () => {
|
||||
const { handler: transcriptFilesUpdated } = registry.getCommand(SharedConstants.Commands.Bot.TranscriptFilesUpdated);
|
||||
const { handler: transcriptPathUpdated } = registry.getCommand(SharedConstants.Commands.Bot.TranscriptsPathUpdated);
|
||||
const { handler: transcriptFilesUpdated } = registry.getCommand(
|
||||
SharedConstants.Commands.Bot.TranscriptFilesUpdated
|
||||
);
|
||||
const { handler: transcriptPathUpdated } = registry.getCommand(
|
||||
SharedConstants.Commands.Bot.TranscriptsPathUpdated
|
||||
);
|
||||
transcriptFilesUpdated([{ path: 'transcript/path.transcript' }]);
|
||||
transcriptPathUpdated('transcript/');
|
||||
const state: any = mockStore.getState();
|
||||
expect(state.resources.transcripts).toEqual([{ path: 'transcript/path.transcript' }]);
|
||||
expect(state.resources.transcripts).toEqual([
|
||||
{ path: 'transcript/path.transcript' },
|
||||
]);
|
||||
expect(state.resources.transcriptsPath).toBe('transcript/');
|
||||
});
|
||||
|
||||
it('should dispatch the appropriate actions when updating the list of chat files on disc', () => {
|
||||
const { handler: chatFilesUpdated } = registry.getCommand(SharedConstants.Commands.Bot.ChatFilesUpdated);
|
||||
const { handler: chatPathUpdated } = registry.getCommand(SharedConstants.Commands.Bot.ChatsPathUpdated);
|
||||
const { handler: chatFilesUpdated } = registry.getCommand(
|
||||
SharedConstants.Commands.Bot.ChatFilesUpdated
|
||||
);
|
||||
const { handler: chatPathUpdated } = registry.getCommand(
|
||||
SharedConstants.Commands.Bot.ChatsPathUpdated
|
||||
);
|
||||
chatFilesUpdated([{ path: 'chat/path.chat' }]);
|
||||
chatPathUpdated('chat/');
|
||||
const state: any = mockStore.getState();
|
||||
expect(state.resources.chats).toEqual([{ path: 'chat/path.chat' }]);
|
||||
expect(state.resources.chatsPath).toBe('chat/');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -31,21 +31,26 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import {
|
||||
BotInfo,
|
||||
getBotDisplayName,
|
||||
SharedConstants,
|
||||
} from '@bfemulator/app-shared';
|
||||
import { BotConfigWithPath, CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import { ActiveBotHelper } from '../ui/helpers/activeBotHelper';
|
||||
import { pathExistsInRecentBots } from '../data/botHelpers';
|
||||
import { CommandServiceImpl } from '../platform/commands/commandServiceImpl';
|
||||
import { store } from '../data/store';
|
||||
import { IFileService } from 'botframework-config/lib/schema';
|
||||
|
||||
import * as BotActions from '../data/action/botActions';
|
||||
import * as FileActions from '../data/action/fileActions';
|
||||
import { BotInfo, getBotDisplayName, SharedConstants } from '@bfemulator/app-shared';
|
||||
import {
|
||||
chatFilesUpdated,
|
||||
chatsDirectoryUpdated,
|
||||
transcriptDirectoryUpdated,
|
||||
transcriptsUpdated
|
||||
transcriptsUpdated,
|
||||
} from '../data/action/resourcesAction';
|
||||
import { IFileService } from 'botframework-config/lib/schema';
|
||||
import { pathExistsInRecentBots } from '../data/botHelpers';
|
||||
import { store } from '../data/store';
|
||||
import { CommandServiceImpl } from '../platform/commands/commandServiceImpl';
|
||||
import { ActiveBotHelper } from '../ui/helpers/activeBotHelper';
|
||||
|
||||
/** Registers bot commands */
|
||||
export function registerCommands(commandRegistry: CommandRegistryImpl) {
|
||||
|
@ -53,58 +58,89 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Switches the current active bot
|
||||
commandRegistry.registerCommand(Commands.Bot.Switch,
|
||||
(bot: BotConfigWithPath | string) => ActiveBotHelper.confirmAndSwitchBots(bot));
|
||||
commandRegistry.registerCommand(
|
||||
Commands.Bot.Switch,
|
||||
(bot: BotConfigWithPath | string) =>
|
||||
ActiveBotHelper.confirmAndSwitchBots(bot)
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Closes the current active bot
|
||||
commandRegistry.registerCommand(Commands.Bot.Close, () => ActiveBotHelper.confirmAndCloseBot());
|
||||
commandRegistry.registerCommand(Commands.Bot.Close, () =>
|
||||
ActiveBotHelper.confirmAndCloseBot()
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Browse for a .bot file and open it
|
||||
commandRegistry.registerCommand(Commands.Bot.OpenBrowse, () => ActiveBotHelper.confirmAndOpenBotFromFile());
|
||||
commandRegistry.registerCommand(Commands.Bot.OpenBrowse, () =>
|
||||
ActiveBotHelper.confirmAndOpenBotFromFile()
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Loads the bot on the client side using the activeBotHelper
|
||||
commandRegistry.registerCommand(Commands.Bot.Load, (bot: BotConfigWithPath): Promise<any> => {
|
||||
if (!pathExistsInRecentBots(bot.path)) {
|
||||
// create and switch bots
|
||||
return ActiveBotHelper.confirmAndCreateBot(bot, '');
|
||||
commandRegistry.registerCommand(
|
||||
Commands.Bot.Load,
|
||||
(bot: BotConfigWithPath): Promise<any> => {
|
||||
if (!pathExistsInRecentBots(bot.path)) {
|
||||
// create and switch bots
|
||||
return ActiveBotHelper.confirmAndCreateBot(bot, '');
|
||||
}
|
||||
return ActiveBotHelper.confirmAndSwitchBots(bot);
|
||||
}
|
||||
return ActiveBotHelper.confirmAndSwitchBots(bot);
|
||||
});
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Syncs the client side list of bots with bots arg (usually called from server side)
|
||||
commandRegistry.registerCommand(Commands.Bot.SyncBotList, async (bots: BotInfo[]): Promise<void> => {
|
||||
store.dispatch(BotActions.load(bots));
|
||||
await CommandServiceImpl.remoteCall(Commands.Electron.UpdateFileMenu);
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
Commands.Bot.SyncBotList,
|
||||
async (bots: BotInfo[]): Promise<void> => {
|
||||
store.dispatch(BotActions.load(bots));
|
||||
await CommandServiceImpl.remoteCall(Commands.Electron.UpdateFileMenu);
|
||||
}
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sets a bot as active (called from server-side)
|
||||
commandRegistry.registerCommand(Commands.Bot.SetActive, async (bot: BotConfigWithPath, botDirectory: string) => {
|
||||
store.dispatch(BotActions.setActive(bot));
|
||||
store.dispatch(FileActions.setRoot(botDirectory));
|
||||
await Promise.all([
|
||||
CommandServiceImpl.remoteCall(Commands.Electron.UpdateFileMenu),
|
||||
CommandServiceImpl.remoteCall(Commands.Electron.SetTitleBar, getBotDisplayName(bot))
|
||||
]);
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
Commands.Bot.SetActive,
|
||||
async (bot: BotConfigWithPath, botDirectory: string) => {
|
||||
store.dispatch(BotActions.setActive(bot));
|
||||
store.dispatch(FileActions.setRoot(botDirectory));
|
||||
await Promise.all([
|
||||
CommandServiceImpl.remoteCall(Commands.Electron.UpdateFileMenu),
|
||||
CommandServiceImpl.remoteCall(
|
||||
Commands.Electron.SetTitleBar,
|
||||
getBotDisplayName(bot)
|
||||
),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
commandRegistry.registerCommand(Commands.Bot.TranscriptFilesUpdated, (transcripts: IFileService[]) => {
|
||||
store.dispatch(transcriptsUpdated(transcripts));
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
Commands.Bot.TranscriptFilesUpdated,
|
||||
(transcripts: IFileService[]) => {
|
||||
store.dispatch(transcriptsUpdated(transcripts));
|
||||
}
|
||||
);
|
||||
|
||||
commandRegistry.registerCommand(Commands.Bot.ChatFilesUpdated, (chatFiles: IFileService[]) => {
|
||||
store.dispatch(chatFilesUpdated(chatFiles));
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
Commands.Bot.ChatFilesUpdated,
|
||||
(chatFiles: IFileService[]) => {
|
||||
store.dispatch(chatFilesUpdated(chatFiles));
|
||||
}
|
||||
);
|
||||
|
||||
commandRegistry.registerCommand(Commands.Bot.TranscriptsPathUpdated, (path: string) => {
|
||||
store.dispatch(transcriptDirectoryUpdated(path));
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
Commands.Bot.TranscriptsPathUpdated,
|
||||
(path: string) => {
|
||||
store.dispatch(transcriptDirectoryUpdated(path));
|
||||
}
|
||||
);
|
||||
|
||||
commandRegistry.registerCommand(Commands.Bot.ChatsPathUpdated, (path: string) => {
|
||||
store.dispatch(chatsDirectoryUpdated(path));
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
Commands.Bot.ChatsPathUpdated,
|
||||
(path: string) => {
|
||||
store.dispatch(chatsDirectoryUpdated(path));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
|
||||
/** Registers electron commands */
|
||||
export function registerCommands(commandRegistry: CommandRegistryImpl) {
|
||||
|
@ -45,15 +45,20 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
// An update is ready to install
|
||||
commandRegistry.registerCommand(Electron.UpdateAvailable, (...args: any[]) => {
|
||||
// TODO: Show a notification
|
||||
console.log('Update available', ...args);
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
Electron.UpdateAvailable,
|
||||
(...args: any[]) => {
|
||||
// TODO: Show a notification
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Update available', ...args);
|
||||
}
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Application is up to date
|
||||
commandRegistry.registerCommand(Electron.UpdateNotAvailable, () => {
|
||||
// TODO: Show a notification
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Application is up to date');
|
||||
});
|
||||
|
||||
|
|
|
@ -1,25 +1,59 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { newNotification, SharedConstants } from '@bfemulator/app-shared';
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import { combineReducers, createStore } from 'redux';
|
||||
|
||||
import { clientAwareSettingsChanged } from '../data/action/clientAwareSettingsActions';
|
||||
import { beginAdd } from '../data/action/notificationActions';
|
||||
import { bot } from '../data/reducer/bot';
|
||||
import { chat } from '../data/reducer/chat';
|
||||
import { clientAwareSettings } from '../data/reducer/clientAwareSettingsReducer';
|
||||
import { editor } from '../data/reducer/editor';
|
||||
import { combineReducers, createStore } from 'redux';
|
||||
import { RootState, store } from '../data/store';
|
||||
import { CommandServiceImpl } from '../platform/commands/commandServiceImpl';
|
||||
|
||||
import { registerCommands } from './emulatorCommands';
|
||||
|
||||
const mockEndpoint = {
|
||||
endpoint: 'https://localhost:8080/api/messages'
|
||||
endpoint: 'https://localhost:8080/api/messages',
|
||||
};
|
||||
|
||||
let mockStore;
|
||||
jest.mock('../data/store', () => ({
|
||||
get store() {
|
||||
return mockStore;
|
||||
}
|
||||
},
|
||||
}));
|
||||
jest.mock('../ui/dialogs/', () => ({}));
|
||||
|
||||
|
@ -31,17 +65,23 @@ describe('The emulator commands', () => {
|
|||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockStore = createStore(combineReducers({ bot, chat, clientAwareSettings, editor }));
|
||||
mockStore.dispatch(clientAwareSettingsChanged({
|
||||
users: { currentUserId: '1234' },
|
||||
cwd: 'path',
|
||||
locale: 'en-us',
|
||||
serverUrl: 'https://localhost'
|
||||
}));
|
||||
mockStore = createStore(
|
||||
combineReducers({ bot, chat, clientAwareSettings, editor })
|
||||
);
|
||||
mockStore.dispatch(
|
||||
clientAwareSettingsChanged({
|
||||
users: { currentUserId: '1234' },
|
||||
cwd: 'path',
|
||||
locale: 'en-us',
|
||||
serverUrl: 'https://localhost',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('Should open a new emulator tabbed document for an endpoint', () => {
|
||||
const { handler } = registry.getCommand(SharedConstants.Commands.Emulator.NewLiveChat);
|
||||
const { handler } = registry.getCommand(
|
||||
SharedConstants.Commands.Emulator.NewLiveChat
|
||||
);
|
||||
const documentId = handler(mockEndpoint, false);
|
||||
const state: RootState = mockStore.getState();
|
||||
const documentIds = Object.keys(state.chat.chats);
|
||||
|
@ -50,18 +90,28 @@ describe('The emulator commands', () => {
|
|||
});
|
||||
|
||||
it('should set the active tab of an existing chat', () => {
|
||||
const { handler } = registry.getCommand(SharedConstants.Commands.Emulator.NewLiveChat);
|
||||
const { handler } = registry.getCommand(
|
||||
SharedConstants.Commands.Emulator.NewLiveChat
|
||||
);
|
||||
const documentId = handler(mockEndpoint, false);
|
||||
const secondDocumentId = handler({ endpoint: 'https://localhost:8181/api/messages' });
|
||||
const secondDocumentId = handler({
|
||||
endpoint: 'https://localhost:8181/api/messages',
|
||||
});
|
||||
// At this point we should have 2 open documents
|
||||
// with the second on
|
||||
expect(mockStore.getState().editor.editors.primary.activeDocumentId).toBe(secondDocumentId);
|
||||
expect(mockStore.getState().editor.editors.primary.activeDocumentId).toBe(
|
||||
secondDocumentId
|
||||
);
|
||||
handler(mockEndpoint, true); // re-open the original document
|
||||
expect(mockStore.getState().editor.editors.primary.activeDocumentId).toBe(documentId);
|
||||
expect(mockStore.getState().editor.editors.primary.activeDocumentId).toBe(
|
||||
documentId
|
||||
);
|
||||
});
|
||||
|
||||
it('should open a transcript', () => {
|
||||
const { handler } = registry.getCommand(SharedConstants.Commands.Emulator.OpenTranscript);
|
||||
const { handler } = registry.getCommand(
|
||||
SharedConstants.Commands.Emulator.OpenTranscript
|
||||
);
|
||||
const filePath = 'transcript.transcript';
|
||||
handler(filePath, filePath);
|
||||
|
||||
|
@ -71,28 +121,44 @@ describe('The emulator commands', () => {
|
|||
});
|
||||
|
||||
it('Should prompt to open a transcript', async () => {
|
||||
const { handler } = registry.getCommand(SharedConstants.Commands.Emulator.PromptToOpenTranscript);
|
||||
const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall').mockResolvedValue('transcript.transcript');
|
||||
const { handler } = registry.getCommand(
|
||||
SharedConstants.Commands.Emulator.PromptToOpenTranscript
|
||||
);
|
||||
const remoteCallSpy = jest
|
||||
.spyOn(CommandServiceImpl, 'remoteCall')
|
||||
.mockResolvedValue('transcript.transcript');
|
||||
const callSpy = jest.spyOn(CommandServiceImpl, 'call');
|
||||
|
||||
await handler();
|
||||
|
||||
expect(remoteCallSpy).toHaveBeenCalledWith('shell:showExplorer-open-dialog', {
|
||||
'buttonLabel': 'Choose file',
|
||||
'filters': [{ 'extensions': ['transcript'], 'name': 'Transcript Files' }],
|
||||
'properties': ['openFile'],
|
||||
'title': 'Open transcript file'
|
||||
});
|
||||
expect(remoteCallSpy).toHaveBeenCalledWith(
|
||||
'shell:showExplorer-open-dialog',
|
||||
{
|
||||
buttonLabel: 'Choose file',
|
||||
filters: [{ extensions: ['transcript'], name: 'Transcript Files' }],
|
||||
properties: ['openFile'],
|
||||
title: 'Open transcript file',
|
||||
}
|
||||
);
|
||||
|
||||
expect(callSpy).toHaveBeenCalledWith('transcript:open', 'transcript.transcript');
|
||||
expect(callSpy).toHaveBeenCalledWith(
|
||||
'transcript:open',
|
||||
'transcript.transcript'
|
||||
);
|
||||
});
|
||||
|
||||
it('should dispatch a notification when opening a transcript fails', async () => {
|
||||
const { handler } = registry.getCommand(SharedConstants.Commands.Emulator.PromptToOpenTranscript);
|
||||
const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall').mockResolvedValue('transcript.transcript');
|
||||
const callSpy = jest.spyOn(CommandServiceImpl, 'call').mockImplementationOnce(() => {
|
||||
throw new Error('Oh noes!');
|
||||
});
|
||||
const { handler } = registry.getCommand(
|
||||
SharedConstants.Commands.Emulator.PromptToOpenTranscript
|
||||
);
|
||||
const remoteCallSpy = jest
|
||||
.spyOn(CommandServiceImpl, 'remoteCall')
|
||||
.mockResolvedValue('transcript.transcript');
|
||||
const callSpy = jest
|
||||
.spyOn(CommandServiceImpl, 'call')
|
||||
.mockImplementationOnce(() => {
|
||||
throw new Error('Oh noes!');
|
||||
});
|
||||
const dispatchSpy = jest.spyOn(mockStore, 'dispatch');
|
||||
const errMsg = `Error while opening transcript file: Error: Oh noes!`;
|
||||
const notification = newNotification(errMsg);
|
||||
|
@ -101,18 +167,24 @@ describe('The emulator commands', () => {
|
|||
action.payload.notification.id = jasmine.any(String) as any;
|
||||
await handler();
|
||||
expect(remoteCallSpy).toHaveBeenCalled();
|
||||
expect(callSpy).toHaveBeenCalledWith('transcript:open', 'transcript.transcript');
|
||||
expect(callSpy).toHaveBeenCalledWith(
|
||||
'transcript:open',
|
||||
'transcript.transcript'
|
||||
);
|
||||
expect(dispatchSpy).toHaveBeenCalledWith(action);
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should reload a transcript', async () => {
|
||||
const { handler: openTranscriptHandler } =
|
||||
registry.getCommand(SharedConstants.Commands.Emulator.OpenTranscript);
|
||||
const { handler: openTranscriptHandler } = registry.getCommand(
|
||||
SharedConstants.Commands.Emulator.OpenTranscript
|
||||
);
|
||||
await openTranscriptHandler('transcript.transcript');
|
||||
let state = mockStore.getState();
|
||||
expect(state.chat.changeKey).toBe(1);
|
||||
const { handler } = registry.getCommand(SharedConstants.Commands.Emulator.ReloadTranscript);
|
||||
const { handler } = registry.getCommand(
|
||||
SharedConstants.Commands.Emulator.ReloadTranscript
|
||||
);
|
||||
await handler('transcript.transcript');
|
||||
state = mockStore.getState();
|
||||
expect(state.chat.changeKey).toBe(3);
|
||||
|
|
|
@ -32,8 +32,13 @@
|
|||
//
|
||||
|
||||
import { newNotification, SharedConstants } from '@bfemulator/app-shared';
|
||||
import { Activity, CommandRegistryImpl, uniqueId } from '@bfemulator/sdk-shared';
|
||||
import {
|
||||
Activity,
|
||||
CommandRegistryImpl,
|
||||
uniqueId,
|
||||
} from '@bfemulator/sdk-shared';
|
||||
import { IEndpointService } from 'botframework-config/lib/schema';
|
||||
|
||||
import * as Constants from '../constants';
|
||||
import * as ChatActions from '../data/action/chatActions';
|
||||
import * as EditorActions from '../data/action/editorActions';
|
||||
|
@ -48,66 +53,71 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Open a new emulator tabbed document
|
||||
commandRegistry.registerCommand(Emulator.NewLiveChat,
|
||||
commandRegistry.registerCommand(
|
||||
Emulator.NewLiveChat,
|
||||
(endpoint: IEndpointService, focusExistingChat: boolean = false) => {
|
||||
const state = store.getState();
|
||||
let documentId: string;
|
||||
|
||||
if (focusExistingChat && state.chat.chats) {
|
||||
const { chats } = state.chat;
|
||||
documentId = Object.keys(chats)
|
||||
.find((docId) => chats[docId].endpointUrl === endpoint.endpoint);
|
||||
documentId = Object.keys(chats).find(
|
||||
docId => chats[docId].endpointUrl === endpoint.endpoint
|
||||
);
|
||||
}
|
||||
|
||||
if (!documentId) {
|
||||
documentId = uniqueId();
|
||||
const { currentUserId } = state.clientAwareSettings.users;
|
||||
store.dispatch(ChatActions.newDocument(
|
||||
documentId,
|
||||
'livechat',
|
||||
{
|
||||
store.dispatch(
|
||||
ChatActions.newDocument(documentId, 'livechat', {
|
||||
botId: 'bot',
|
||||
endpointId: endpoint.id,
|
||||
endpointUrl: endpoint.endpoint,
|
||||
userId: currentUserId
|
||||
}
|
||||
));
|
||||
userId: currentUserId,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
store.dispatch(EditorActions.open({
|
||||
contentType: Constants.CONTENT_TYPE_LIVE_CHAT,
|
||||
documentId,
|
||||
isGlobal: false
|
||||
}));
|
||||
store.dispatch(
|
||||
EditorActions.open({
|
||||
contentType: Constants.CONTENT_TYPE_LIVE_CHAT,
|
||||
documentId,
|
||||
isGlobal: false,
|
||||
})
|
||||
);
|
||||
return documentId;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Open the transcript file in a tabbed document
|
||||
commandRegistry.registerCommand(Emulator.OpenTranscript,
|
||||
commandRegistry.registerCommand(
|
||||
Emulator.OpenTranscript,
|
||||
(filePath: string, fileName: string, additionalData?: object) => {
|
||||
const tabGroup = getTabGroupForDocument(filePath);
|
||||
const { currentUserId } = store.getState().clientAwareSettings.users;
|
||||
if (!tabGroup) {
|
||||
store.dispatch(ChatActions.newDocument(
|
||||
filePath,
|
||||
'transcript',
|
||||
{
|
||||
store.dispatch(
|
||||
ChatActions.newDocument(filePath, 'transcript', {
|
||||
...additionalData,
|
||||
botId: 'bot',
|
||||
userId: currentUserId
|
||||
}
|
||||
));
|
||||
userId: currentUserId,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
store.dispatch(EditorActions.open({
|
||||
contentType: Constants.CONTENT_TYPE_TRANSCRIPT,
|
||||
documentId: filePath,
|
||||
fileName,
|
||||
filePath,
|
||||
isGlobal: false
|
||||
}));
|
||||
});
|
||||
store.dispatch(
|
||||
EditorActions.open({
|
||||
contentType: Constants.CONTENT_TYPE_TRANSCRIPT,
|
||||
documentId: filePath,
|
||||
fileName,
|
||||
filePath,
|
||||
isGlobal: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Prompt to open a transcript file, then open it
|
||||
|
@ -119,13 +129,16 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) {
|
|||
filters: [
|
||||
{
|
||||
name: 'Transcript Files',
|
||||
extensions: ['transcript']
|
||||
}
|
||||
extensions: ['transcript'],
|
||||
},
|
||||
],
|
||||
};
|
||||
try {
|
||||
const { ShowOpenDialog } = SharedConstants.Commands.Electron;
|
||||
const filename = await CommandServiceImpl.remoteCall(ShowOpenDialog, dialogOptions);
|
||||
const filename = await CommandServiceImpl.remoteCall(
|
||||
ShowOpenDialog,
|
||||
dialogOptions
|
||||
);
|
||||
await CommandServiceImpl.call(Emulator.OpenTranscript, filename);
|
||||
} catch (e) {
|
||||
const errMsg = `Error while opening transcript file: ${e}`;
|
||||
|
@ -136,48 +149,75 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Same as open transcript, except that it closes the transcript first, before reopening it
|
||||
commandRegistry.registerCommand(Emulator.ReloadTranscript,
|
||||
commandRegistry.registerCommand(
|
||||
Emulator.ReloadTranscript,
|
||||
(filePath: string, fileName: string, additionalData?: object) => {
|
||||
const tabGroup = getTabGroupForDocument(filePath);
|
||||
const { currentUserId } = store.getState().clientAwareSettings.users;
|
||||
if (tabGroup) {
|
||||
store.dispatch(EditorActions.close(getTabGroupForDocument(filePath), filePath));
|
||||
store.dispatch(
|
||||
EditorActions.close(getTabGroupForDocument(filePath), filePath)
|
||||
);
|
||||
store.dispatch(ChatActions.closeDocument(filePath));
|
||||
}
|
||||
store.dispatch(ChatActions.newDocument(
|
||||
filePath,
|
||||
'transcript',
|
||||
{
|
||||
store.dispatch(
|
||||
ChatActions.newDocument(filePath, 'transcript', {
|
||||
...additionalData,
|
||||
botId: 'bot',
|
||||
userId: currentUserId
|
||||
}
|
||||
));
|
||||
store.dispatch(EditorActions.open({
|
||||
contentType: Constants.CONTENT_TYPE_TRANSCRIPT,
|
||||
documentId: filePath,
|
||||
filePath,
|
||||
fileName,
|
||||
isGlobal: false
|
||||
}));
|
||||
});
|
||||
userId: currentUserId,
|
||||
})
|
||||
);
|
||||
store.dispatch(
|
||||
EditorActions.open({
|
||||
contentType: Constants.CONTENT_TYPE_TRANSCRIPT,
|
||||
documentId: filePath,
|
||||
filePath,
|
||||
fileName,
|
||||
isGlobal: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Open the chat file in a tabbed document as a transcript
|
||||
commandRegistry.registerCommand(Emulator.OpenChatFile, async (filePath: string, reload?: boolean) => {
|
||||
try {
|
||||
// wait for the main side to use the chatdown library to parse the activities (transcript) out of the .chat file
|
||||
const { activities, fileName }: { activities: Activity[], fileName: string }
|
||||
= await CommandServiceImpl.remoteCall(Emulator.OpenChatFile, filePath);
|
||||
commandRegistry.registerCommand(
|
||||
Emulator.OpenChatFile,
|
||||
async (filePath: string, reload?: boolean) => {
|
||||
try {
|
||||
// wait for the main side to use the chatdown library to parse the activities (transcript) out of the .chat file
|
||||
const {
|
||||
activities,
|
||||
fileName,
|
||||
}: {
|
||||
activities: Activity[];
|
||||
fileName: string;
|
||||
} = await CommandServiceImpl.remoteCall(
|
||||
Emulator.OpenChatFile,
|
||||
filePath
|
||||
);
|
||||
|
||||
// open or reload the transcript
|
||||
if (reload) {
|
||||
await CommandServiceImpl.call(Emulator.ReloadTranscript, filePath, fileName, { activities, inMemory: true });
|
||||
} else {
|
||||
await CommandServiceImpl.call(Emulator.OpenTranscript, filePath, fileName, { activities, inMemory: true });
|
||||
// open or reload the transcript
|
||||
if (reload) {
|
||||
await CommandServiceImpl.call(
|
||||
Emulator.ReloadTranscript,
|
||||
filePath,
|
||||
fileName,
|
||||
{ activities, inMemory: true }
|
||||
);
|
||||
} else {
|
||||
await CommandServiceImpl.call(
|
||||
Emulator.OpenTranscript,
|
||||
filePath,
|
||||
fileName,
|
||||
{ activities, inMemory: true }
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Error while retrieving activities from main side: ${err}`
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(`Error while retrieving activities from main side: ${err}`);
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,24 +31,29 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { store } from '../data/store';
|
||||
import * as FileActions from '../data/action/fileActions';
|
||||
import * as EditorActions from '../data/action/editorActions';
|
||||
import {
|
||||
isChatFile,
|
||||
isTranscriptFile,
|
||||
SharedConstants,
|
||||
} from '@bfemulator/app-shared';
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import { SharedConstants, isChatFile, isTranscriptFile } from '@bfemulator/app-shared';
|
||||
|
||||
import * as EditorActions from '../data/action/editorActions';
|
||||
import * as FileActions from '../data/action/fileActions';
|
||||
import { store } from '../data/store';
|
||||
|
||||
/** Registers file commands */
|
||||
export function registerCommands(commandRegistry: CommandRegistryImpl) {
|
||||
const {File} = SharedConstants.Commands;
|
||||
const { File } = SharedConstants.Commands;
|
||||
// ---------------------------------------------------------------------------
|
||||
// Adds a file to the file store
|
||||
commandRegistry.registerCommand(File.Add, (payload) => {
|
||||
commandRegistry.registerCommand(File.Add, payload => {
|
||||
store.dispatch(FileActions.addFile(payload));
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Removes a file from the file store
|
||||
commandRegistry.registerCommand(File.Remove, (path) => {
|
||||
commandRegistry.registerCommand(File.Remove, path => {
|
||||
store.dispatch(FileActions.removeFile(path));
|
||||
});
|
||||
|
||||
|
|
|
@ -31,9 +31,10 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { store } from '../data/store';
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
|
||||
import { store } from '../data/store';
|
||||
|
||||
/** Registers miscellaneous commands */
|
||||
export function registerCommands(commandRegistry: CommandRegistryImpl) {
|
||||
|
|
|
@ -31,12 +31,13 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { store } from '../data/store';
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { Notification } from '@bfemulator/app-shared';
|
||||
import { getGlobal } from '../utils';
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
|
||||
import * as NotificationActions from '../data/action/notificationActions';
|
||||
import { store } from '../data/store';
|
||||
import { getGlobal } from '../utils';
|
||||
|
||||
/** Registers notification commands */
|
||||
export function registerCommands(commandRegistry: CommandRegistryImpl) {
|
||||
|
@ -44,7 +45,9 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) {
|
|||
// ---------------------------------------------------------------------------
|
||||
// Adds a notification from the main side to the store / notification manager
|
||||
commandRegistry.registerCommand(Commands.Add, () => {
|
||||
const notification: Notification = getGlobal(SharedConstants.NOTIFICATION_FROM_MAIN);
|
||||
const notification: Notification = getGlobal(
|
||||
SharedConstants.NOTIFICATION_FROM_MAIN
|
||||
);
|
||||
store.dispatch(NotificationActions.beginAdd(notification));
|
||||
});
|
||||
|
||||
|
|
|
@ -31,18 +31,19 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
|
||||
import { ExtensionManager } from '../extensions';
|
||||
import * as LogService from '../platform/log/logService';
|
||||
|
||||
import { registerCommands as registerBotCommands } from './botCommands';
|
||||
import { registerCommands as registerElectronCommands } from './electronCommands';
|
||||
import { registerCommands as registerEmulatorCommands } from './emulatorCommands';
|
||||
import { registerCommands as registerFileCommands } from './fileCommands';
|
||||
import { registerCommands as registerMiscCommands } from './miscCommands';
|
||||
import { registerCommands as registerNotificationCommands } from './notificationCommands';
|
||||
import { registerCommands as registerUICommands } from './uiCommands';
|
||||
import { registerCommands as registerSettingsCommand } from './settingsCommands';
|
||||
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import { registerCommands as registerUICommands } from './uiCommands';
|
||||
|
||||
/** Registers all commands */
|
||||
export function registerAllCommands(commandRegistry: CommandRegistryImpl) {
|
||||
|
|
|
@ -1,16 +1,51 @@
|
|||
import { registerCommands } from './settingsCommands';
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { clientAwareSettings } from '../data/reducer/clientAwareSettingsReducer';
|
||||
import { combineReducers, createStore } from 'redux';
|
||||
|
||||
import { clientAwareSettings } from '../data/reducer/clientAwareSettingsReducer';
|
||||
import { store } from '../data/store';
|
||||
import { clientAwareSettingsChanged } from '../data/action/clientAwareSettingsActions';
|
||||
|
||||
let mockStore = createStore(combineReducers({ clientAwareSettings }));
|
||||
import { registerCommands } from './settingsCommands';
|
||||
|
||||
const mockStore = createStore(combineReducers({ clientAwareSettings }));
|
||||
jest.mock('../data/store', () => ({
|
||||
get store() {
|
||||
return mockStore;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
describe('the settings commands', () => {
|
||||
|
@ -21,9 +56,13 @@ describe('the settings commands', () => {
|
|||
});
|
||||
|
||||
it('should dispatch to the store when settings are sent from the main side', () => {
|
||||
const command = registry.getCommand(SharedConstants.Commands.Settings.ReceiveGlobalSettings).handler;
|
||||
const command = registry.getCommand(
|
||||
SharedConstants.Commands.Settings.ReceiveGlobalSettings
|
||||
).handler;
|
||||
const dispatchSpy = jest.spyOn(store, 'dispatch');
|
||||
command({});
|
||||
expect(dispatchSpy).toHaveBeenCalledWith(clientAwareSettingsChanged({} as any));
|
||||
expect(dispatchSpy).toHaveBeenCalledWith(
|
||||
clientAwareSettingsChanged({} as any)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,16 +31,20 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import { ClientAwareSettings, SharedConstants } from '@bfemulator/app-shared';
|
||||
import { store } from '../data/store';
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
|
||||
import { clientAwareSettingsChanged } from '../data/action/clientAwareSettingsActions';
|
||||
import { store } from '../data/store';
|
||||
|
||||
/** Registers settings commands */
|
||||
export function registerCommands(commandRegistry: CommandRegistryImpl) {
|
||||
const { Settings } = SharedConstants.Commands;
|
||||
|
||||
commandRegistry.registerCommand(Settings.ReceiveGlobalSettings, (settings: ClientAwareSettings) => {
|
||||
store.dispatch(clientAwareSettingsChanged(settings));
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
Settings.ReceiveGlobalSettings,
|
||||
(settings: ClientAwareSettings) => {
|
||||
store.dispatch(clientAwareSettingsChanged(settings));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,31 +1,71 @@
|
|||
jest.mock('../ui/dialogs', () => ({
|
||||
AzureLoginPromptDialogContainer: class {
|
||||
},
|
||||
AzureLoginSuccessDialogContainer: class {
|
||||
},
|
||||
BotCreationDialog: class {
|
||||
},
|
||||
DialogService: { showDialog: () => Promise.resolve(true) },
|
||||
SecretPromptDialog: class {
|
||||
}
|
||||
}
|
||||
));
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import { CONTENT_TYPE_APP_SETTINGS, DOCUMENT_ID_APP_SETTINGS } from '../constants';
|
||||
import { AzureAuthAction, AzureAuthWorkflow, invalidateArmToken } from '../data/action/azureAuthActions';
|
||||
|
||||
import {
|
||||
CONTENT_TYPE_APP_SETTINGS,
|
||||
DOCUMENT_ID_APP_SETTINGS,
|
||||
} from '../constants';
|
||||
import {
|
||||
AzureAuthAction,
|
||||
AzureAuthWorkflow,
|
||||
invalidateArmToken,
|
||||
} from '../data/action/azureAuthActions';
|
||||
import { EditorActions, OpenEditorAction } from '../data/action/editorActions';
|
||||
import { NavBarActions, SelectNavBarAction } from '../data/action/navBarActions';
|
||||
import {
|
||||
NavBarActions,
|
||||
SelectNavBarAction,
|
||||
} from '../data/action/navBarActions';
|
||||
import * as editorHelpers from '../data/editorHelpers';
|
||||
import { store } from '../data/store';
|
||||
import {
|
||||
AzureLoginPromptDialogContainer,
|
||||
AzureLoginSuccessDialogContainer,
|
||||
BotCreationDialog,
|
||||
DialogService, OpenBotDialogContainer,
|
||||
SecretPromptDialogContainer
|
||||
DialogService,
|
||||
OpenBotDialogContainer,
|
||||
SecretPromptDialogContainer,
|
||||
} from '../ui/dialogs';
|
||||
|
||||
import { registerCommands } from './uiCommands';
|
||||
jest.mock('../ui/dialogs', () => ({
|
||||
AzureLoginPromptDialogContainer: class {},
|
||||
AzureLoginSuccessDialogContainer: class {},
|
||||
BotCreationDialog: class {},
|
||||
DialogService: { showDialog: () => Promise.resolve(true) },
|
||||
SecretPromptDialog: class {},
|
||||
}));
|
||||
|
||||
const Commands = SharedConstants.Commands.UI;
|
||||
|
||||
|
@ -44,37 +84,45 @@ describe('the uiCommands', () => {
|
|||
|
||||
it('should call DialogService.showDialog when the ShowBotCreationDialog command is dispatched', async () => {
|
||||
const spy = jest.spyOn(DialogService, 'showDialog');
|
||||
const result = await registry.getCommand(Commands.ShowBotCreationDialog).handler();
|
||||
const result = await registry
|
||||
.getCommand(Commands.ShowBotCreationDialog)
|
||||
.handler();
|
||||
expect(spy).toHaveBeenCalledWith(BotCreationDialog);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should call DialogService.showDialog when the ShowSecretPromptDialog command is dispatched', async () => {
|
||||
const spy = jest.spyOn(DialogService, 'showDialog');
|
||||
const result = await registry.getCommand(Commands.ShowSecretPromptDialog).handler();
|
||||
const result = await registry
|
||||
.getCommand(Commands.ShowSecretPromptDialog)
|
||||
.handler();
|
||||
expect(spy).toHaveBeenCalledWith(SecretPromptDialogContainer);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should call DialogService.showDialog when the ShowOpenBotDialog command is dispatched', async () => {
|
||||
const spy = jest.spyOn(DialogService, 'showDialog');
|
||||
const result = await registry.getCommand(Commands.ShowOpenBotDialog).handler();
|
||||
const result = await registry
|
||||
.getCommand(Commands.ShowOpenBotDialog)
|
||||
.handler();
|
||||
expect(spy).toHaveBeenCalledWith(OpenBotDialogContainer);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
describe('should dispatch the appropriate action to the store', () => {
|
||||
it('when the SwitchNavBarTab command is dispatched', () => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let arg: SelectNavBarAction = {} as SelectNavBarAction;
|
||||
store.dispatch = action => (arg as any) = action;
|
||||
store.dispatch = action => ((arg as any) = action);
|
||||
registry.getCommand(Commands.SwitchNavBarTab).handler('Do it Nauuuw!');
|
||||
expect(arg.type).toBe(NavBarActions.select);
|
||||
expect(arg.payload.selection).toBe('Do it Nauuuw!');
|
||||
});
|
||||
|
||||
it('when the ShowAppSettings command is dispatched', () => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let arg: OpenEditorAction = {} as OpenEditorAction;
|
||||
store.dispatch = action => (arg as any) = action;
|
||||
store.dispatch = action => ((arg as any) = action);
|
||||
registry.getCommand(Commands.ShowAppSettings).handler();
|
||||
expect(arg.type).toBe(EditorActions.open);
|
||||
expect(arg.payload.contentType).toBe(CONTENT_TYPE_APP_SETTINGS);
|
||||
|
@ -83,23 +131,29 @@ describe('the uiCommands', () => {
|
|||
});
|
||||
|
||||
it('when the SignInToAzure command is dispatched', async () => {
|
||||
let arg: AzureAuthAction<AzureAuthWorkflow> = {} as AzureAuthAction<AzureAuthWorkflow>;
|
||||
store.dispatch = action => (arg as any) = action;
|
||||
// eslint-disable-next-line prefer-const
|
||||
let arg: AzureAuthAction<AzureAuthWorkflow> = {} as AzureAuthAction<
|
||||
AzureAuthWorkflow
|
||||
>;
|
||||
store.dispatch = action => ((arg as any) = action);
|
||||
registry.getCommand(Commands.SignInToAzure).handler();
|
||||
expect(arg.payload.loginSuccessDialog).toBe(AzureLoginSuccessDialogContainer);
|
||||
expect(arg.payload.loginSuccessDialog).toBe(
|
||||
AzureLoginSuccessDialogContainer
|
||||
);
|
||||
expect(arg.payload.promptDialog).toBe(AzureLoginPromptDialogContainer);
|
||||
});
|
||||
|
||||
it('when the InvalidateArmToken command is dispatched', async () => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let arg: AzureAuthAction<void> = {} as AzureAuthAction<void>;
|
||||
store.dispatch = action => (arg as any) = action;
|
||||
store.dispatch = action => ((arg as any) = action);
|
||||
registry.getCommand(Commands.InvalidateAzureArmToken).handler();
|
||||
expect(arg).toEqual(invalidateArmToken());
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the proper href on the theme tag when the SwitchTheme command is dispatched', () => {
|
||||
let link = document.createElement('link');
|
||||
const link = document.createElement('link');
|
||||
link.id = 'themeVars';
|
||||
document.querySelector('head').appendChild(link);
|
||||
registry.getCommand(Commands.SwitchTheme).handler('light', './light.css');
|
||||
|
|
|
@ -34,11 +34,19 @@
|
|||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { CommandRegistry } from '@bfemulator/sdk-shared';
|
||||
import { ServiceTypes } from 'botframework-config/lib/schema';
|
||||
|
||||
import * as Constants from '../constants';
|
||||
import { azureArmTokenDataChanged, beginAzureAuthWorkflow, invalidateArmToken } from '../data/action/azureAuthActions';
|
||||
import {
|
||||
azureArmTokenDataChanged,
|
||||
beginAzureAuthWorkflow,
|
||||
invalidateArmToken,
|
||||
} from '../data/action/azureAuthActions';
|
||||
import * as EditorActions from '../data/action/editorActions';
|
||||
import * as NavBarActions from '../data/action/navBarActions';
|
||||
import { ProgressIndicatorPayload, updateProgressIndicator } from '../data/action/progressIndicatorActions';
|
||||
import {
|
||||
ProgressIndicatorPayload,
|
||||
updateProgressIndicator,
|
||||
} from '../data/action/progressIndicatorActions';
|
||||
import { switchTheme } from '../data/action/themeActions';
|
||||
import { showWelcomePage } from '../data/editorHelpers';
|
||||
import { AzureAuthState } from '../data/reducer/azureAuthReducer';
|
||||
|
@ -54,7 +62,7 @@ import {
|
|||
ProgressIndicatorContainer,
|
||||
SecretPromptDialogContainer,
|
||||
UpdateAvailableDialogContainer,
|
||||
UpdateUnavailableDialogContainer
|
||||
UpdateUnavailableDialogContainer,
|
||||
} from '../ui/dialogs';
|
||||
|
||||
/** Register UI commands (toggling UI) */
|
||||
|
@ -87,47 +95,72 @@ export function registerCommands(commandRegistry: CommandRegistry) {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Switches navbar tab selection
|
||||
commandRegistry.registerCommand(UI.SwitchNavBarTab, (tabName: string): void => {
|
||||
store.dispatch(NavBarActions.select(tabName));
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
UI.SwitchNavBarTab,
|
||||
(tabName: string): void => {
|
||||
store.dispatch(NavBarActions.select(tabName));
|
||||
}
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Open App Settings
|
||||
commandRegistry.registerCommand(UI.ShowAppSettings, (): void => {
|
||||
const { CONTENT_TYPE_APP_SETTINGS, DOCUMENT_ID_APP_SETTINGS } = Constants;
|
||||
store.dispatch(EditorActions.open({
|
||||
contentType: CONTENT_TYPE_APP_SETTINGS,
|
||||
documentId: DOCUMENT_ID_APP_SETTINGS,
|
||||
isGlobal: true,
|
||||
meta: null
|
||||
}));
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
UI.ShowAppSettings,
|
||||
(): void => {
|
||||
const { CONTENT_TYPE_APP_SETTINGS, DOCUMENT_ID_APP_SETTINGS } = Constants;
|
||||
store.dispatch(
|
||||
EditorActions.open({
|
||||
contentType: CONTENT_TYPE_APP_SETTINGS,
|
||||
documentId: DOCUMENT_ID_APP_SETTINGS,
|
||||
isGlobal: true,
|
||||
meta: null,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Theme switching from main
|
||||
commandRegistry.registerCommand(UI.SwitchTheme, (themeName: string, themeHref: string) => {
|
||||
const linkTags = document.querySelectorAll<HTMLLinkElement>('[data-theme-component="true"]');
|
||||
const themeTag = document.getElementById('themeVars') as HTMLLinkElement;
|
||||
if (themeTag) {
|
||||
themeTag.href = themeHref;
|
||||
commandRegistry.registerCommand(
|
||||
UI.SwitchTheme,
|
||||
(themeName: string, themeHref: string) => {
|
||||
const linkTags = document.querySelectorAll<HTMLLinkElement>(
|
||||
'[data-theme-component="true"]'
|
||||
);
|
||||
const themeTag = document.getElementById('themeVars') as HTMLLinkElement;
|
||||
if (themeTag) {
|
||||
themeTag.href = themeHref;
|
||||
}
|
||||
const themeComponents = Array.prototype.map.call(
|
||||
linkTags,
|
||||
link => link.href
|
||||
); // href is fully qualified
|
||||
store.dispatch(switchTheme(themeName, themeComponents));
|
||||
}
|
||||
const themeComponents = Array.prototype.map.call(linkTags, link => link.href); // href is fully qualified
|
||||
store.dispatch(switchTheme(themeName, themeComponents));
|
||||
});
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Azure sign in
|
||||
commandRegistry.registerCommand(UI.SignInToAzure, (serviceType: ServiceTypes) => {
|
||||
store.dispatch(beginAzureAuthWorkflow(
|
||||
AzureLoginPromptDialogContainer,
|
||||
{ serviceType },
|
||||
AzureLoginSuccessDialogContainer,
|
||||
AzureLoginFailedDialogContainer));
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
UI.SignInToAzure,
|
||||
(serviceType: ServiceTypes) => {
|
||||
store.dispatch(
|
||||
beginAzureAuthWorkflow(
|
||||
AzureLoginPromptDialogContainer,
|
||||
{ serviceType },
|
||||
AzureLoginSuccessDialogContainer,
|
||||
AzureLoginFailedDialogContainer
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
commandRegistry.registerCommand(UI.ArmTokenReceivedOnStartup, (azureAuth: AzureAuthState) => {
|
||||
store.dispatch(azureArmTokenDataChanged(azureAuth.access_token));
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
UI.ArmTokenReceivedOnStartup,
|
||||
(azureAuth: AzureAuthState) => {
|
||||
store.dispatch(azureArmTokenDataChanged(azureAuth.access_token));
|
||||
}
|
||||
);
|
||||
|
||||
commandRegistry.registerCommand(UI.InvalidateAzureArmToken, () => {
|
||||
store.dispatch(invalidateArmToken());
|
||||
|
@ -141,25 +174,44 @@ export function registerCommands(commandRegistry: CommandRegistry) {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shows the progress indicator component
|
||||
commandRegistry.registerCommand(UI.ShowProgressIndicator, async (props?: ProgressIndicatorPayload) => {
|
||||
return await DialogService.showDialog(ProgressIndicatorContainer, props).catch(e => console.error(e));
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
UI.ShowProgressIndicator,
|
||||
async (props?: ProgressIndicatorPayload) => {
|
||||
return await DialogService.showDialog(
|
||||
ProgressIndicatorContainer,
|
||||
props
|
||||
// eslint-disable-next-line no-console
|
||||
).catch(e => console.error(e));
|
||||
}
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Updates the progress of the progress indicator component
|
||||
commandRegistry.registerCommand(UI.UpdateProgressIndicator, (value: ProgressIndicatorPayload) => {
|
||||
store.dispatch(updateProgressIndicator(value));
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
UI.UpdateProgressIndicator,
|
||||
(value: ProgressIndicatorPayload) => {
|
||||
store.dispatch(updateProgressIndicator(value));
|
||||
}
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shows the dialog telling the user that an update is available
|
||||
commandRegistry.registerCommand(UI.ShowUpdateAvailableDialog, async (version: string = '') => {
|
||||
return await DialogService.showDialog(UpdateAvailableDialogContainer, { version }).catch(e => console.error(e));
|
||||
});
|
||||
commandRegistry.registerCommand(
|
||||
UI.ShowUpdateAvailableDialog,
|
||||
async (version: string = '') => {
|
||||
return await DialogService.showDialog(UpdateAvailableDialogContainer, {
|
||||
version,
|
||||
// eslint-disable-next-line no-console
|
||||
}).catch(e => console.error(e));
|
||||
}
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shows the dialog telling the user that an update is unavailable
|
||||
commandRegistry.registerCommand(UI.ShowUpdateUnavailableDialog, async () => {
|
||||
return await DialogService.showDialog(UpdateUnavailableDialogContainer).catch(e => console.error(e));
|
||||
return await DialogService.showDialog(
|
||||
UpdateUnavailableDialogContainer
|
||||
// eslint-disable-next-line no-console
|
||||
).catch(e => console.error(e));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -33,10 +33,14 @@
|
|||
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
|
||||
export const CONTENT_TYPE_APP_SETTINGS = 'application/vnd.microsoft.bfemulator.document.appsettings';
|
||||
export const CONTENT_TYPE_WELCOME_PAGE = 'application/vnd.microsoft.bfemulator.document.welcome';
|
||||
export const CONTENT_TYPE_TRANSCRIPT = 'application/vnd.microsoft.bfemulator.document.transcript';
|
||||
export const CONTENT_TYPE_LIVE_CHAT = SharedConstants.ContentTypes.CONTENT_TYPE_LIVE_CHAT;
|
||||
export const CONTENT_TYPE_APP_SETTINGS =
|
||||
'application/vnd.microsoft.bfemulator.document.appsettings';
|
||||
export const CONTENT_TYPE_WELCOME_PAGE =
|
||||
'application/vnd.microsoft.bfemulator.document.welcome';
|
||||
export const CONTENT_TYPE_TRANSCRIPT =
|
||||
'application/vnd.microsoft.bfemulator.document.transcript';
|
||||
export const CONTENT_TYPE_LIVE_CHAT =
|
||||
SharedConstants.ContentTypes.CONTENT_TYPE_LIVE_CHAT;
|
||||
|
||||
export const NAVBAR_BOT_EXPLORER = 'navbar.botExplorer';
|
||||
export const NAVBAR_SETTINGS = 'navbar.settings';
|
||||
|
@ -46,10 +50,7 @@ export const NAVBAR_RESOURCES = 'navbar:resources';
|
|||
export const EDITOR_KEY_PRIMARY = 'primary';
|
||||
export const EDITOR_KEY_SECONDARY = 'secondary';
|
||||
|
||||
export const EditorKeys = [
|
||||
EDITOR_KEY_PRIMARY,
|
||||
EDITOR_KEY_SECONDARY
|
||||
];
|
||||
export const EditorKeys = [EDITOR_KEY_PRIMARY, EDITOR_KEY_SECONDARY];
|
||||
|
||||
export const DOCUMENT_ID_APP_SETTINGS = 'app:settings';
|
||||
export const DOCUMENT_ID_BOT_SETTINGS = 'bot:settings';
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { Action } from 'redux';
|
||||
import { ComponentClass } from 'react';
|
||||
import { Action } from 'redux';
|
||||
|
||||
export const AZURE_ARM_TOKEN_DATA_CHANGED = 'AZURE_ARM_TOKEN_DATA_CHANGED';
|
||||
export const AZURE_BEGIN_AUTH_WORKFLOW = 'AZURE_BEGIN_AUTH_WORKFLOW';
|
||||
|
@ -61,19 +61,27 @@ export function beginAzureAuthWorkflow(
|
|||
): AzureAuthAction<AzureAuthWorkflow> {
|
||||
return {
|
||||
type: AZURE_BEGIN_AUTH_WORKFLOW,
|
||||
payload: { promptDialog, promptDialogProps, loginSuccessDialog, loginFailedDialog }
|
||||
payload: {
|
||||
promptDialog,
|
||||
promptDialogProps,
|
||||
loginSuccessDialog,
|
||||
loginFailedDialog,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function azureArmTokenDataChanged(armToken: string): AzureAuthAction<ArmTokenData> {
|
||||
export function azureArmTokenDataChanged(
|
||||
armToken: string
|
||||
): AzureAuthAction<ArmTokenData> {
|
||||
return {
|
||||
type: AZURE_ARM_TOKEN_DATA_CHANGED,
|
||||
payload: { access_token: armToken }
|
||||
// eslint-disable-next-line typescript/camelcase
|
||||
payload: { access_token: armToken },
|
||||
};
|
||||
}
|
||||
|
||||
export function invalidateArmToken(): AzureAuthAction<void> {
|
||||
return {
|
||||
type: AZURE_INVALIDATE_ARM_TOKEN
|
||||
type: AZURE_INVALIDATE_ARM_TOKEN,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -39,20 +39,20 @@ export enum BotActions {
|
|||
setActive = 'BOT/SET_ACTIVE',
|
||||
close = 'BOT/CLOSE',
|
||||
browse = 'BOT/BROWSE',
|
||||
hashGenerated = 'BOT/HASH_GENERATED'
|
||||
hashGenerated = 'BOT/HASH_GENERATED',
|
||||
}
|
||||
|
||||
export interface LoadBotAction {
|
||||
type: BotActions.load;
|
||||
payload: {
|
||||
bots: BotInfo[]
|
||||
bots: BotInfo[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface SetActiveBotAction {
|
||||
type: BotActions.setActive;
|
||||
payload: {
|
||||
bot: BotConfigWithPath
|
||||
bot: BotConfigWithPath;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -72,11 +72,11 @@ export interface BotHashAction {
|
|||
}
|
||||
|
||||
export type BotAction =
|
||||
LoadBotAction |
|
||||
SetActiveBotAction |
|
||||
CloseBotAction |
|
||||
BrowseBotAction |
|
||||
BotHashAction;
|
||||
| LoadBotAction
|
||||
| SetActiveBotAction
|
||||
| CloseBotAction
|
||||
| BrowseBotAction
|
||||
| BotHashAction;
|
||||
|
||||
export function load(bots: BotInfo[]): LoadBotAction {
|
||||
// prune bad bots
|
||||
|
@ -85,8 +85,8 @@ export function load(bots: BotInfo[]): LoadBotAction {
|
|||
return {
|
||||
type: BotActions.load,
|
||||
payload: {
|
||||
bots
|
||||
}
|
||||
bots,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -98,28 +98,28 @@ export function setActive(bot: BotConfigWithPath): SetActiveBotAction {
|
|||
return {
|
||||
type: BotActions.setActive,
|
||||
payload: {
|
||||
bot
|
||||
}
|
||||
bot,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function close(): CloseBotAction {
|
||||
return {
|
||||
type: BotActions.close,
|
||||
payload: {}
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function browse(): BrowseBotAction {
|
||||
return {
|
||||
type: BotActions.browse,
|
||||
payload: {}
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function botHashGenerated(hash: string): BotHashAction {
|
||||
return {
|
||||
type: BotActions.hashGenerated,
|
||||
payload: { hash }
|
||||
payload: { hash },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ export enum ChatActions {
|
|||
addTranscript = 'CHAT/TRANSCRIPT/ADD',
|
||||
clearTranscripts = 'CHAT/TRANSCRIPT/CLEAR',
|
||||
removeTranscript = 'CHAT/TRANSCRIPT/REMOVE',
|
||||
updateChat = 'CHAT/DOCUMENT/UPDATE'
|
||||
updateChat = 'CHAT/DOCUMENT/UPDATE',
|
||||
}
|
||||
|
||||
export interface ActiveInspectorChangedPayload {
|
||||
|
@ -56,9 +56,9 @@ export interface ActiveInspectorChangedPayload {
|
|||
export interface NewChatAction {
|
||||
type: ChatActions.newChat;
|
||||
payload: {
|
||||
[propName: string]: any,
|
||||
documentId: string,
|
||||
mode: ChatMode
|
||||
[propName: string]: any;
|
||||
documentId: string;
|
||||
mode: ChatMode;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -85,8 +85,7 @@ export interface SetInspectorObjectsPayload {
|
|||
objs: any;
|
||||
}
|
||||
|
||||
export interface AddTranscriptPayload extends RemoveTranscriptPayload {
|
||||
}
|
||||
export interface AddTranscriptPayload extends RemoveTranscriptPayload {}
|
||||
|
||||
export interface RemoveTranscriptPayload {
|
||||
filename: string;
|
||||
|
@ -103,39 +102,49 @@ export interface ChatAction<T = any> extends Action {
|
|||
|
||||
type ChatMode = 'livechat' | 'transcript';
|
||||
|
||||
export function inspectorChanged(inspectorWebView: HTMLWebViewElement): ChatAction<ActiveInspectorChangedPayload> {
|
||||
export function inspectorChanged(
|
||||
inspectorWebView: HTMLWebViewElement
|
||||
): ChatAction<ActiveInspectorChangedPayload> {
|
||||
return {
|
||||
type: ChatActions.activeInspectorChanged,
|
||||
payload: { inspectorWebView }
|
||||
payload: { inspectorWebView },
|
||||
};
|
||||
}
|
||||
|
||||
export function addTranscript(filename: string): ChatAction<AddTranscriptPayload> {
|
||||
export function addTranscript(
|
||||
filename: string
|
||||
): ChatAction<AddTranscriptPayload> {
|
||||
return {
|
||||
type: ChatActions.addTranscript,
|
||||
payload: {
|
||||
filename
|
||||
}
|
||||
filename,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function clearTranscripts(): ChatAction<{}> {
|
||||
return {
|
||||
type: ChatActions.clearTranscripts,
|
||||
payload: {}
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function removeTranscript(filename: string): ChatAction<RemoveTranscriptPayload> {
|
||||
export function removeTranscript(
|
||||
filename: string
|
||||
): ChatAction<RemoveTranscriptPayload> {
|
||||
return {
|
||||
type: ChatActions.removeTranscript,
|
||||
payload: {
|
||||
filename
|
||||
}
|
||||
filename,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function newDocument(documentId: string, mode: ChatMode, additionalData?: object): NewChatAction {
|
||||
export function newDocument(
|
||||
documentId: string,
|
||||
mode: ChatMode,
|
||||
additionalData?: object
|
||||
): NewChatAction {
|
||||
return {
|
||||
type: ChatActions.newChat,
|
||||
payload: {
|
||||
|
@ -144,62 +153,70 @@ export function newDocument(documentId: string, mode: ChatMode, additionalData?:
|
|||
conversationId: null,
|
||||
directLine: null,
|
||||
log: {
|
||||
entries: []
|
||||
entries: [],
|
||||
},
|
||||
inspectorObjects: [],
|
||||
ui: {
|
||||
horizontalSplitter: [
|
||||
{
|
||||
absolute: null,
|
||||
percentage: 50
|
||||
percentage: 50,
|
||||
},
|
||||
{
|
||||
absolute: null,
|
||||
percentage: 50
|
||||
}
|
||||
percentage: 50,
|
||||
},
|
||||
],
|
||||
verticalSplitter: [
|
||||
{
|
||||
absolute: null,
|
||||
percentage: 50
|
||||
percentage: 50,
|
||||
},
|
||||
{
|
||||
absolute: null,
|
||||
percentage: 50
|
||||
}
|
||||
percentage: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
...additionalData
|
||||
}
|
||||
...additionalData,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function closeDocument(documentId: string): ChatAction<CloseChatPayload> {
|
||||
export function closeDocument(
|
||||
documentId: string
|
||||
): ChatAction<CloseChatPayload> {
|
||||
return {
|
||||
type: ChatActions.closeChat,
|
||||
payload: {
|
||||
documentId,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function newConversation(documentId: string, options: any): ChatAction<NewConversationPayload> {
|
||||
export function newConversation(
|
||||
documentId: string,
|
||||
options: any
|
||||
): ChatAction<NewConversationPayload> {
|
||||
return {
|
||||
type: ChatActions.newConversation,
|
||||
payload: {
|
||||
documentId,
|
||||
options
|
||||
}
|
||||
options,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function appendToLog(documentId: string, entry: LogEntry): ChatAction<AppendLogPayload> {
|
||||
export function appendToLog(
|
||||
documentId: string,
|
||||
entry: LogEntry
|
||||
): ChatAction<AppendLogPayload> {
|
||||
return {
|
||||
type: ChatActions.appendLog,
|
||||
payload: {
|
||||
documentId,
|
||||
entry
|
||||
}
|
||||
entry,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -208,27 +225,33 @@ export function clearLog(documentId: string): ChatAction<ClearLogPayload> {
|
|||
type: ChatActions.clearLog,
|
||||
payload: {
|
||||
documentId,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function setInspectorObjects(documentId: string, objs: any): ChatAction<SetInspectorObjectsPayload> {
|
||||
export function setInspectorObjects(
|
||||
documentId: string,
|
||||
objs: any
|
||||
): ChatAction<SetInspectorObjectsPayload> {
|
||||
objs = Array.isArray(objs) ? objs : [objs];
|
||||
return {
|
||||
type: ChatActions.setInspectorObjects,
|
||||
payload: {
|
||||
documentId,
|
||||
objs
|
||||
}
|
||||
objs,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function updateChat(documentId: string, updatedValues: any): ChatAction<UpdateChatPayload> {
|
||||
export function updateChat(
|
||||
documentId: string,
|
||||
updatedValues: any
|
||||
): ChatAction<UpdateChatPayload> {
|
||||
return {
|
||||
type: ChatActions.updateChat,
|
||||
payload: {
|
||||
documentId,
|
||||
updatedValues
|
||||
}
|
||||
updatedValues,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,37 @@
|
|||
import { Action } from 'redux';
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { ClientAwareSettings } from '@bfemulator/app-shared';
|
||||
import { Action } from 'redux';
|
||||
|
||||
export const CLIENT_AWARE_SETTINGS_CHANGED = 'CLIENT_AWARE_SETTINGS_CHANGED';
|
||||
|
||||
|
@ -10,9 +42,11 @@ export interface ClientAwareSettingsActions extends Action {
|
|||
payload: ClientAwareSettings;
|
||||
}
|
||||
|
||||
export function clientAwareSettingsChanged(settings: ClientAwareSettings): ClientAwareSettingsActions {
|
||||
export function clientAwareSettingsChanged(
|
||||
settings: ClientAwareSettings
|
||||
): ClientAwareSettingsActions {
|
||||
return {
|
||||
type: CLIENT_AWARE_SETTINGS_CHANGED,
|
||||
payload: settings
|
||||
payload: settings,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,17 +31,26 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { IConnectedService, ServiceTypes } from 'botframework-config/lib/schema';
|
||||
import {
|
||||
IConnectedService,
|
||||
ServiceTypes,
|
||||
} from 'botframework-config/lib/schema';
|
||||
import { ComponentClass } from 'react';
|
||||
import { Action } from 'redux';
|
||||
|
||||
import { CONNECTED_SERVICES_PANEL_ID } from './explorerActions';
|
||||
|
||||
export const OPEN_SERVICE_DEEP_LINK = 'OPEN_SERVICE_DEEP_LINK';
|
||||
export const OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE = 'OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE';
|
||||
export const OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU = 'OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU';
|
||||
export const OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU = 'OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU';
|
||||
export const LAUNCH_CONNECTED_SERVICE_EDITOR = 'LAUNCH_CONNECTED_SERVICE_EDITOR';
|
||||
export const LAUNCH_CONNECTED_SERVICE_PICKER = 'LAUNCH_CONNECTED_SERVICE_PICKER';
|
||||
export const OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE =
|
||||
'OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE';
|
||||
export const OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU =
|
||||
'OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU';
|
||||
export const OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU =
|
||||
'OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU';
|
||||
export const LAUNCH_CONNECTED_SERVICE_EDITOR =
|
||||
'LAUNCH_CONNECTED_SERVICE_EDITOR';
|
||||
export const LAUNCH_CONNECTED_SERVICE_PICKER =
|
||||
'LAUNCH_CONNECTED_SERVICE_PICKER';
|
||||
|
||||
export interface ConnectedServiceAction<T> extends Action {
|
||||
payload: T;
|
||||
|
@ -57,18 +66,19 @@ export interface ConnectedServicePayload {
|
|||
|
||||
export function launchConnectedServiceEditor<T>(
|
||||
editorComponent: ComponentClass<T>,
|
||||
connectedService?: IConnectedService): ConnectedServiceAction<ConnectedServicePayload> {
|
||||
connectedService?: IConnectedService
|
||||
): ConnectedServiceAction<ConnectedServicePayload> {
|
||||
return {
|
||||
type: LAUNCH_CONNECTED_SERVICE_EDITOR,
|
||||
payload: { editorComponent, connectedService }
|
||||
payload: { editorComponent, connectedService },
|
||||
};
|
||||
}
|
||||
|
||||
export interface ConnectedServicePickerPayload extends ConnectedServicePayload {
|
||||
azureAuthWorkflowComponents: {
|
||||
promptDialog: ComponentClass<any>,
|
||||
loginSuccessDialog: ComponentClass<any>,
|
||||
loginFailedDialog: ComponentClass<any>
|
||||
promptDialog: ComponentClass<any>;
|
||||
loginSuccessDialog: ComponentClass<any>;
|
||||
loginFailedDialog: ComponentClass<any>;
|
||||
};
|
||||
pickerComponent: ComponentClass<any>;
|
||||
getStartedDialog: ComponentClass<any>;
|
||||
|
@ -76,42 +86,48 @@ export interface ConnectedServicePickerPayload extends ConnectedServicePayload {
|
|||
progressIndicatorComponent?: ComponentClass<any>;
|
||||
}
|
||||
|
||||
export function launchConnectedServicePicker(payload: ConnectedServicePickerPayload)
|
||||
: ConnectedServiceAction<ConnectedServicePickerPayload> {
|
||||
export function launchConnectedServicePicker(
|
||||
payload: ConnectedServicePickerPayload
|
||||
): ConnectedServiceAction<ConnectedServicePickerPayload> {
|
||||
return {
|
||||
type: LAUNCH_CONNECTED_SERVICE_PICKER,
|
||||
payload
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function openServiceDeepLink(connectedService: IConnectedService)
|
||||
: ConnectedServiceAction<ConnectedServicePayload> {
|
||||
export function openServiceDeepLink(
|
||||
connectedService: IConnectedService
|
||||
): ConnectedServiceAction<ConnectedServicePayload> {
|
||||
return {
|
||||
type: OPEN_SERVICE_DEEP_LINK,
|
||||
payload: { connectedService }
|
||||
payload: { connectedService },
|
||||
};
|
||||
}
|
||||
|
||||
export function openContextMenuForConnectedService<T>(
|
||||
editorComponent: ComponentClass<T>,
|
||||
connectedService?: IConnectedService): ConnectedServiceAction<ConnectedServicePayload> {
|
||||
connectedService?: IConnectedService
|
||||
): ConnectedServiceAction<ConnectedServicePayload> {
|
||||
return {
|
||||
type: OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE,
|
||||
payload: { editorComponent, connectedService }
|
||||
payload: { editorComponent, connectedService },
|
||||
};
|
||||
}
|
||||
|
||||
export function openAddServiceContextMenu(payload: ConnectedServicePickerPayload)
|
||||
: ConnectedServiceAction<ConnectedServicePickerPayload> {
|
||||
export function openAddServiceContextMenu(
|
||||
payload: ConnectedServicePickerPayload
|
||||
): ConnectedServiceAction<ConnectedServicePickerPayload> {
|
||||
return {
|
||||
type: OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU,
|
||||
payload
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function openSortContextMenu(): ConnectedServiceAction<ConnectedServicePayload> {
|
||||
export function openSortContextMenu(): ConnectedServiceAction<
|
||||
ConnectedServicePayload
|
||||
> {
|
||||
return {
|
||||
type: OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU,
|
||||
payload: { panelId: CONNECTED_SERVICES_PANEL_ID }
|
||||
payload: { panelId: CONNECTED_SERVICES_PANEL_ID },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -32,13 +32,13 @@
|
|||
//
|
||||
|
||||
export enum DialogActions {
|
||||
setShowing = 'DIALOG/SET_SHOWING'
|
||||
setShowing = 'DIALOG/SET_SHOWING',
|
||||
}
|
||||
|
||||
export interface SetShowingDialogAction {
|
||||
type: DialogActions.setShowing;
|
||||
payload: {
|
||||
showing: boolean
|
||||
showing: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ export function setShowing(showing: boolean = false): SetShowingDialogAction {
|
|||
return {
|
||||
type: DialogActions.setShowing,
|
||||
payload: {
|
||||
showing
|
||||
}
|
||||
showing,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -35,7 +35,8 @@ import { IDispatchService } from 'botframework-config/lib/schema';
|
|||
import { Action } from 'redux';
|
||||
|
||||
export const OPEN_DISPATCH_DEEP_LINK = 'OPEN_DISPATCH_DEEP_LINK';
|
||||
export const OPEN_DISPATCH_EXPLORER_CONTEXT_MENU = 'OPEN_DISPATCH_EXPLORER_CONTEXT_MENU';
|
||||
export const OPEN_DISPATCH_EXPLORER_CONTEXT_MENU =
|
||||
'OPEN_DISPATCH_EXPLORER_CONTEXT_MENU';
|
||||
|
||||
export interface DispatchServiceAction<T> extends Action {
|
||||
payload: T;
|
||||
|
@ -45,17 +46,20 @@ export interface DispatchServicePayload {
|
|||
dispatchService?: IDispatchService;
|
||||
}
|
||||
|
||||
export function openDispatchDeepLink(dispatchService: IDispatchService): DispatchServiceAction<DispatchServicePayload> {
|
||||
export function openDispatchDeepLink(
|
||||
dispatchService: IDispatchService
|
||||
): DispatchServiceAction<DispatchServicePayload> {
|
||||
return {
|
||||
type: OPEN_DISPATCH_DEEP_LINK,
|
||||
payload: { dispatchService }
|
||||
};
|
||||
}
|
||||
|
||||
export function openDispatchExplorerContextMenu(dispatchService: IDispatchService)
|
||||
: DispatchServiceAction<DispatchServicePayload> {
|
||||
return {
|
||||
type: OPEN_DISPATCH_EXPLORER_CONTEXT_MENU,
|
||||
payload: { dispatchService }
|
||||
payload: { dispatchService },
|
||||
};
|
||||
}
|
||||
|
||||
export function openDispatchExplorerContextMenu(
|
||||
dispatchService: IDispatchService
|
||||
): DispatchServiceAction<DispatchServicePayload> {
|
||||
return {
|
||||
type: OPEN_DISPATCH_EXPLORER_CONTEXT_MENU,
|
||||
payload: { dispatchService },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -47,38 +47,38 @@ export enum EditorActions {
|
|||
splitTab = 'EDITOR/SPLIT_TAB',
|
||||
swapTabs = 'EDITOR/SWAP_TABS',
|
||||
toggleDraggingTab = 'EDITOR/TOGGLE_DRAGGING_TAB',
|
||||
updateDocument = 'EDITOR/UPDATE_DOCUMENT'
|
||||
updateDocument = 'EDITOR/UPDATE_DOCUMENT',
|
||||
}
|
||||
|
||||
export interface AppendTabAction {
|
||||
type: EditorActions.appendTab;
|
||||
payload: {
|
||||
srcEditorKey: string,
|
||||
destEditorKey: string,
|
||||
documentId: string
|
||||
srcEditorKey: string;
|
||||
destEditorKey: string;
|
||||
documentId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CloseEditorAction {
|
||||
type: EditorActions.close;
|
||||
payload: {
|
||||
editorKey: string,
|
||||
documentId: string
|
||||
editorKey: string;
|
||||
documentId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CloseAllEditorAction {
|
||||
type: EditorActions.closeAll;
|
||||
payload: {
|
||||
includeGlobal: boolean
|
||||
includeGlobal: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SetDirtyFlagAction {
|
||||
type: EditorActions.setDirtyFlag;
|
||||
payload: {
|
||||
documentId: string
|
||||
dirty: boolean
|
||||
documentId: string;
|
||||
dirty: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -95,41 +95,41 @@ export interface UpdateDocumentAction {
|
|||
export interface SetActiveTabAction {
|
||||
type: EditorActions.setActiveTab;
|
||||
payload: {
|
||||
documentId: string
|
||||
documentId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SetActiveEditorAction {
|
||||
type: EditorActions.setActiveEditor;
|
||||
payload: {
|
||||
editorKey: string
|
||||
editorKey: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SplitTabAction {
|
||||
type: EditorActions.splitTab;
|
||||
payload: {
|
||||
contentType: string,
|
||||
documentId: string,
|
||||
srcEditorKey: string,
|
||||
destEditorKey: string
|
||||
contentType: string;
|
||||
documentId: string;
|
||||
srcEditorKey: string;
|
||||
destEditorKey: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SwapTabsAction {
|
||||
type: EditorActions.swapTabs;
|
||||
payload: {
|
||||
srcEditorKey: string,
|
||||
destEditorKey: string,
|
||||
srcTabId: string,
|
||||
destTabId: string
|
||||
srcEditorKey: string;
|
||||
destEditorKey: string;
|
||||
srcTabId: string;
|
||||
destTabId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ToggleDraggingTabAction {
|
||||
type: EditorActions.toggleDraggingTab;
|
||||
payload: {
|
||||
draggingTab: boolean
|
||||
draggingTab: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -150,62 +150,73 @@ export interface RemoveDocPendingChangeAction {
|
|||
export interface DropTabOnLeftOverlayAction {
|
||||
type: EditorActions.dropTabOnLeftOverlay;
|
||||
payload: {
|
||||
tabId: string
|
||||
tabId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type EditorAction =
|
||||
AppendTabAction |
|
||||
CloseEditorAction |
|
||||
CloseAllEditorAction |
|
||||
SetDirtyFlagAction |
|
||||
OpenEditorAction |
|
||||
UpdateDocumentAction |
|
||||
SetActiveTabAction |
|
||||
SetActiveEditorAction |
|
||||
SplitTabAction |
|
||||
SwapTabsAction |
|
||||
ToggleDraggingTabAction |
|
||||
AddDocPendingChangeAction |
|
||||
RemoveDocPendingChangeAction |
|
||||
DropTabOnLeftOverlayAction;
|
||||
| AppendTabAction
|
||||
| CloseEditorAction
|
||||
| CloseAllEditorAction
|
||||
| SetDirtyFlagAction
|
||||
| OpenEditorAction
|
||||
| UpdateDocumentAction
|
||||
| SetActiveTabAction
|
||||
| SetActiveEditorAction
|
||||
| SplitTabAction
|
||||
| SwapTabsAction
|
||||
| ToggleDraggingTabAction
|
||||
| AddDocPendingChangeAction
|
||||
| RemoveDocPendingChangeAction
|
||||
| DropTabOnLeftOverlayAction;
|
||||
|
||||
export function appendTab(srcEditorKey: string, destEditorKey: string, documentId: string): AppendTabAction {
|
||||
export function appendTab(
|
||||
srcEditorKey: string,
|
||||
destEditorKey: string,
|
||||
documentId: string
|
||||
): AppendTabAction {
|
||||
return {
|
||||
type: EditorActions.appendTab,
|
||||
payload: {
|
||||
srcEditorKey,
|
||||
destEditorKey,
|
||||
documentId
|
||||
}
|
||||
documentId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function addDocPendingChange(documentId: string): AddDocPendingChangeAction {
|
||||
export function addDocPendingChange(
|
||||
documentId: string
|
||||
): AddDocPendingChangeAction {
|
||||
return {
|
||||
type: EditorActions.addDocPendingChange,
|
||||
payload: {
|
||||
documentId
|
||||
}
|
||||
documentId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function removeDocPendingChange(documentId: string): RemoveDocPendingChangeAction {
|
||||
export function removeDocPendingChange(
|
||||
documentId: string
|
||||
): RemoveDocPendingChangeAction {
|
||||
return {
|
||||
type: EditorActions.removeDocPendingChange,
|
||||
payload: {
|
||||
documentId
|
||||
}
|
||||
documentId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function close(editorKey: string, documentId: string): CloseEditorAction {
|
||||
export function close(
|
||||
editorKey: string,
|
||||
documentId: string
|
||||
): CloseEditorAction {
|
||||
return {
|
||||
type: EditorActions.close,
|
||||
payload: {
|
||||
editorKey,
|
||||
documentId
|
||||
}
|
||||
documentId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -213,32 +224,38 @@ export function closeNonGlobalTabs(): CloseAllEditorAction {
|
|||
return {
|
||||
type: EditorActions.closeAll,
|
||||
payload: {
|
||||
includeGlobal: false
|
||||
}
|
||||
includeGlobal: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function setDirtyFlag(documentId: string, dirty: boolean): SetDirtyFlagAction {
|
||||
export function setDirtyFlag(
|
||||
documentId: string,
|
||||
dirty: boolean
|
||||
): SetDirtyFlagAction {
|
||||
return {
|
||||
type: EditorActions.setDirtyFlag,
|
||||
payload: {
|
||||
documentId,
|
||||
dirty
|
||||
}
|
||||
dirty,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function open(document: Document): OpenEditorAction {
|
||||
return {
|
||||
type: EditorActions.open,
|
||||
payload: document
|
||||
payload: document,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateDocument(documentId: string, updatedDocument: Partial<Document>): UpdateDocumentAction {
|
||||
export function updateDocument(
|
||||
documentId: string,
|
||||
updatedDocument: Partial<Document>
|
||||
): UpdateDocumentAction {
|
||||
return {
|
||||
type: EditorActions.updateDocument,
|
||||
payload: { documentId, ...updatedDocument }
|
||||
payload: { documentId, ...updatedDocument },
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -246,8 +263,8 @@ export function setActiveTab(documentId: string): SetActiveTabAction {
|
|||
return {
|
||||
type: EditorActions.setActiveTab,
|
||||
payload: {
|
||||
documentId
|
||||
}
|
||||
documentId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -255,51 +272,63 @@ export function setActiveEditor(editorKey: string): SetActiveEditorAction {
|
|||
return {
|
||||
type: EditorActions.setActiveEditor,
|
||||
payload: {
|
||||
editorKey
|
||||
}
|
||||
editorKey,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function splitTab(contentType: string, documentId: string, srcEditorKey: string, destEditorKey: string)
|
||||
: SplitTabAction {
|
||||
export function splitTab(
|
||||
contentType: string,
|
||||
documentId: string,
|
||||
srcEditorKey: string,
|
||||
destEditorKey: string
|
||||
): SplitTabAction {
|
||||
return {
|
||||
type: EditorActions.splitTab,
|
||||
payload: {
|
||||
contentType,
|
||||
documentId,
|
||||
srcEditorKey,
|
||||
destEditorKey
|
||||
}
|
||||
destEditorKey,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function swapTabs(srcEditorKey: string, destEditorKey: string, srcTabId: string, destTabId: string)
|
||||
: SwapTabsAction {
|
||||
export function swapTabs(
|
||||
srcEditorKey: string,
|
||||
destEditorKey: string,
|
||||
srcTabId: string,
|
||||
destTabId: string
|
||||
): SwapTabsAction {
|
||||
return {
|
||||
type: EditorActions.swapTabs,
|
||||
payload: {
|
||||
srcEditorKey,
|
||||
destEditorKey,
|
||||
srcTabId,
|
||||
destTabId
|
||||
}
|
||||
destTabId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleDraggingTab(draggingTab: boolean): ToggleDraggingTabAction {
|
||||
export function toggleDraggingTab(
|
||||
draggingTab: boolean
|
||||
): ToggleDraggingTabAction {
|
||||
return {
|
||||
type: EditorActions.toggleDraggingTab,
|
||||
payload: {
|
||||
draggingTab
|
||||
}
|
||||
draggingTab,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function dropTabOnLeftOverlay(tabId: string): DropTabOnLeftOverlayAction {
|
||||
export function dropTabOnLeftOverlay(
|
||||
tabId: string
|
||||
): DropTabOnLeftOverlayAction {
|
||||
return {
|
||||
type: EditorActions.dropTabOnLeftOverlay,
|
||||
payload: {
|
||||
tabId
|
||||
}
|
||||
tabId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@
|
|||
import { IEndpointService } from 'botframework-config/lib/schema';
|
||||
import { Action } from 'redux';
|
||||
|
||||
export const OPEN_ENDPOINT_EXPLORER_CONTEXT_MENU = 'OPEN_ENDPOINT_EXPLORER_CONTEXT_MENU';
|
||||
export const OPEN_ENDPOINT_EXPLORER_CONTEXT_MENU =
|
||||
'OPEN_ENDPOINT_EXPLORER_CONTEXT_MENU';
|
||||
|
||||
export interface EndpointServiceAction<T> extends Action {
|
||||
payload: T;
|
||||
|
@ -44,10 +45,11 @@ export interface EndpointServicePayload {
|
|||
endpointService?: IEndpointService;
|
||||
}
|
||||
|
||||
export function openEndpointExplorerContextMenu(endpointService: IEndpointService)
|
||||
: EndpointServiceAction<EndpointServicePayload> {
|
||||
export function openEndpointExplorerContextMenu(
|
||||
endpointService: IEndpointService
|
||||
): EndpointServiceAction<EndpointServicePayload> {
|
||||
return {
|
||||
type: OPEN_ENDPOINT_EXPLORER_CONTEXT_MENU,
|
||||
payload: { endpointService }
|
||||
payload: { endpointService },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -52,27 +52,32 @@ export interface EndpointEditorPayload extends EndpointServicePayload {
|
|||
endpointEditorComponent?: ComponentClass<any>;
|
||||
}
|
||||
|
||||
export function launchEndpointEditor(endpointEditorComponent: ComponentClass<any>,
|
||||
endpointService?: IEndpointService): EndpointServiceAction<EndpointEditorPayload> {
|
||||
export function launchEndpointEditor(
|
||||
endpointEditorComponent: ComponentClass<any>,
|
||||
endpointService?: IEndpointService
|
||||
): EndpointServiceAction<EndpointEditorPayload> {
|
||||
return {
|
||||
type: LAUNCH_ENDPOINT_EDITOR,
|
||||
payload: { endpointEditorComponent, endpointService }
|
||||
payload: { endpointEditorComponent, endpointService },
|
||||
};
|
||||
}
|
||||
|
||||
export function openEndpointInEmulator(endpointService: IEndpointService, focusExistingChatIfAvailable: boolean = false)
|
||||
: EndpointServiceAction<EndpointServicePayload> {
|
||||
export function openEndpointInEmulator(
|
||||
endpointService: IEndpointService,
|
||||
focusExistingChatIfAvailable: boolean = false
|
||||
): EndpointServiceAction<EndpointServicePayload> {
|
||||
return {
|
||||
type: OPEN_ENDPOINT_IN_EMULATOR,
|
||||
payload: { endpointService, focusExistingChatIfAvailable }
|
||||
payload: { endpointService, focusExistingChatIfAvailable },
|
||||
};
|
||||
}
|
||||
|
||||
export function openEndpointExplorerContextMenu(endpointEditorComponent: ComponentClass<any>,
|
||||
endpointService?: IEndpointService)
|
||||
: EndpointServiceAction<EndpointEditorPayload> {
|
||||
export function openEndpointExplorerContextMenu(
|
||||
endpointEditorComponent: ComponentClass<any>,
|
||||
endpointService?: IEndpointService
|
||||
): EndpointServiceAction<EndpointEditorPayload> {
|
||||
return {
|
||||
type: OPEN_ENDPOINT_CONTEXT_MENU,
|
||||
payload: { endpointEditorComponent, endpointService }
|
||||
payload: { endpointEditorComponent, endpointService },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,12 +31,13 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { Action } from 'redux';
|
||||
|
||||
import { SortCriteria } from '../reducer/explorer';
|
||||
export const CONNECTED_SERVICES_PANEL_ID = 'connectedServices';
|
||||
|
||||
export enum ExplorerActions {
|
||||
Show = 'EXPLORER/SHOW',
|
||||
Sort = 'EXPLORER/SORT'
|
||||
Sort = 'EXPLORER/SORT',
|
||||
}
|
||||
|
||||
export interface ExplorerAction<T> extends Action {
|
||||
|
@ -52,13 +53,16 @@ export interface ExplorerPayload {
|
|||
export function showExplorer(show: boolean): ExplorerAction<ExplorerPayload> {
|
||||
return {
|
||||
type: ExplorerActions.Show,
|
||||
payload: { show }
|
||||
payload: { show },
|
||||
};
|
||||
}
|
||||
|
||||
export function sortExplorerContents(panelId: string, sort: SortCriteria): ExplorerAction<ExplorerPayload> {
|
||||
export function sortExplorerContents(
|
||||
panelId: string,
|
||||
sort: SortCriteria
|
||||
): ExplorerAction<ExplorerPayload> {
|
||||
return {
|
||||
type: ExplorerActions.Sort,
|
||||
payload: { sortSelectionByPanelId: { [panelId]: sort } }
|
||||
payload: { sortSelectionByPanelId: { [panelId]: sort } },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -37,33 +37,33 @@ export enum FileActions {
|
|||
setRoot = 'FILE/SET_ROOT',
|
||||
add = 'FILE/ADD',
|
||||
remove = 'FILE/REMOVE',
|
||||
clear = 'FILE/CLEAR'
|
||||
clear = 'FILE/CLEAR',
|
||||
}
|
||||
|
||||
export function addFile(payload: FileInfo) {
|
||||
return {
|
||||
type: FileActions.add,
|
||||
payload
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function clear() {
|
||||
return {
|
||||
type: FileActions.clear,
|
||||
payload: {}
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function removeFile(path: string) {
|
||||
return {
|
||||
type: FileActions.remove,
|
||||
payload: { path }
|
||||
payload: { path },
|
||||
};
|
||||
}
|
||||
|
||||
export function setRoot(path: string) {
|
||||
return {
|
||||
type: FileActions.setRoot,
|
||||
payload: { path }
|
||||
payload: { path },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -32,13 +32,13 @@
|
|||
//
|
||||
|
||||
export enum NavBarActions {
|
||||
select = 'NAVBAR/SELECT'
|
||||
select = 'NAVBAR/SELECT',
|
||||
}
|
||||
|
||||
export interface SelectNavBarAction {
|
||||
type: NavBarActions.select;
|
||||
payload: {
|
||||
selection: string
|
||||
selection: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ export function select(selection: string): SelectNavBarAction {
|
|||
return {
|
||||
type: NavBarActions.select,
|
||||
payload: {
|
||||
selection
|
||||
}
|
||||
selection,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -40,44 +40,44 @@ export enum NotificationActions {
|
|||
finishRemove = 'NOTIFICATION/FINISH_REMOVE',
|
||||
markAllAsRead = 'NOTIFICATION/MARK_ALL_AS_READ',
|
||||
beginClear = 'NOTIFICATION/BEGIN_CLEAR',
|
||||
finishClear = 'NOTIFICATION/FINISH_CLEAR'
|
||||
finishClear = 'NOTIFICATION/FINISH_CLEAR',
|
||||
}
|
||||
|
||||
export type NotificationAction =
|
||||
BeginAddNotificationAction |
|
||||
FinishAddNotificationAction |
|
||||
BeginRemoveNotificationAction |
|
||||
FinishRemoveNotificationAction |
|
||||
MarkAllAsReadNotificationAction |
|
||||
BeginClearNotificationAction |
|
||||
FinishClearNotificationAction;
|
||||
| BeginAddNotificationAction
|
||||
| FinishAddNotificationAction
|
||||
| BeginRemoveNotificationAction
|
||||
| FinishRemoveNotificationAction
|
||||
| MarkAllAsReadNotificationAction
|
||||
| BeginClearNotificationAction
|
||||
| FinishClearNotificationAction;
|
||||
|
||||
export interface BeginAddNotificationAction {
|
||||
type: NotificationActions.beginAdd;
|
||||
payload: {
|
||||
notification: Notification,
|
||||
read: boolean
|
||||
notification: Notification;
|
||||
read: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FinishAddNotificationAction {
|
||||
type: NotificationActions.finishAdd;
|
||||
payload: {
|
||||
notification: Notification
|
||||
notification: Notification;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BeginRemoveNotificationAction {
|
||||
type: NotificationActions.beginRemove;
|
||||
payload: {
|
||||
id: string
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FinishRemoveNotificationAction {
|
||||
type: NotificationActions.finishRemove;
|
||||
payload: {
|
||||
id: string
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -96,22 +96,27 @@ export interface FinishClearNotificationAction {
|
|||
payload: {};
|
||||
}
|
||||
|
||||
export function beginAdd(notification: Notification, read: boolean = false): BeginAddNotificationAction {
|
||||
export function beginAdd(
|
||||
notification: Notification,
|
||||
read: boolean = false
|
||||
): BeginAddNotificationAction {
|
||||
return {
|
||||
type: NotificationActions.beginAdd,
|
||||
payload: {
|
||||
notification,
|
||||
read
|
||||
}
|
||||
read,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function finishAdd(notification: Notification): FinishAddNotificationAction {
|
||||
export function finishAdd(
|
||||
notification: Notification
|
||||
): FinishAddNotificationAction {
|
||||
return {
|
||||
type: NotificationActions.finishAdd,
|
||||
payload: {
|
||||
notification
|
||||
}
|
||||
notification,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -119,8 +124,8 @@ export function beginRemove(id: string): BeginRemoveNotificationAction {
|
|||
return {
|
||||
type: NotificationActions.beginRemove,
|
||||
payload: {
|
||||
id
|
||||
}
|
||||
id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -128,28 +133,28 @@ export function finishRemove(id: string): FinishRemoveNotificationAction {
|
|||
return {
|
||||
type: NotificationActions.finishRemove,
|
||||
payload: {
|
||||
id
|
||||
}
|
||||
id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function markAllAsRead(): MarkAllAsReadNotificationAction {
|
||||
return {
|
||||
type: NotificationActions.markAllAsRead,
|
||||
payload: {}
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function beginClear(): BeginClearNotificationAction {
|
||||
return {
|
||||
type: NotificationActions.beginClear,
|
||||
payload: {}
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function finishClear(): FinishClearNotificationAction {
|
||||
return {
|
||||
type: NotificationActions.finishClear,
|
||||
payload: {}
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
export enum PresentationActions {
|
||||
disable = 'PRESENTATION/DISABLE',
|
||||
enable = 'PRESENTATION/ENABLE'
|
||||
enable = 'PRESENTATION/ENABLE',
|
||||
}
|
||||
|
||||
export interface EnablePresentationAction {
|
||||
|
@ -47,19 +47,19 @@ export interface DisablePresentationAction {
|
|||
}
|
||||
|
||||
export type PresentationAction =
|
||||
EnablePresentationAction |
|
||||
DisablePresentationAction;
|
||||
| EnablePresentationAction
|
||||
| DisablePresentationAction;
|
||||
|
||||
export function enable(): EnablePresentationAction {
|
||||
return {
|
||||
type: PresentationActions.enable,
|
||||
payload: {}
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function disable(): DisablePresentationAction {
|
||||
return {
|
||||
type: PresentationActions.disable,
|
||||
payload: {}
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,3 +1,35 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { Action } from 'redux';
|
||||
|
||||
export const UPDATE_PROGRESS_INDICATOR = 'UPDATE_PROGRESS_INDICATOR';
|
||||
|
@ -12,17 +44,21 @@ export interface ProgressIndicatorPayload {
|
|||
progress: number;
|
||||
}
|
||||
|
||||
export function updateProgressIndicator({ label, progress }: ProgressIndicatorPayload)
|
||||
: ProgressIndicatorAction<ProgressIndicatorPayload> {
|
||||
export function updateProgressIndicator({
|
||||
label,
|
||||
progress,
|
||||
}: ProgressIndicatorPayload): ProgressIndicatorAction<
|
||||
ProgressIndicatorPayload
|
||||
> {
|
||||
return {
|
||||
type: UPDATE_PROGRESS_INDICATOR,
|
||||
payload: { label, progress }
|
||||
payload: { label, progress },
|
||||
};
|
||||
}
|
||||
|
||||
export function cancelCurrentProcess(): ProgressIndicatorAction<void> {
|
||||
return {
|
||||
type: CANCEL_CURRENT_PROCESS,
|
||||
payload: void(0)
|
||||
payload: void 0,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,38 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { IFileService } from 'botframework-config/lib/schema';
|
||||
import { Action } from 'redux';
|
||||
import { ComponentClass } from 'react';
|
||||
import { Action } from 'redux';
|
||||
|
||||
export const TRANSCRIPTS_UPDATED = 'TRANSCRIPTS_UPDATED';
|
||||
export const TRANSCRIPTS_DIRECTORY_UPDATED = 'TRANSCRIPTS_DIRECTORY_UPDATED';
|
||||
|
@ -16,67 +48,87 @@ export interface ResourcesAction<T> extends Action {
|
|||
payload: T;
|
||||
}
|
||||
|
||||
export function transcriptsUpdated(payload: IFileService[]): ResourcesAction<IFileService[]> {
|
||||
export function transcriptsUpdated(
|
||||
payload: IFileService[]
|
||||
): ResourcesAction<IFileService[]> {
|
||||
return {
|
||||
type: TRANSCRIPTS_UPDATED,
|
||||
payload
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function transcriptDirectoryUpdated(payload: string): ResourcesAction<string> {
|
||||
export function transcriptDirectoryUpdated(
|
||||
payload: string
|
||||
): ResourcesAction<string> {
|
||||
return {
|
||||
type: TRANSCRIPTS_DIRECTORY_UPDATED,
|
||||
payload
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function chatsDirectoryUpdated(payload: string): ResourcesAction<string> {
|
||||
export function chatsDirectoryUpdated(
|
||||
payload: string
|
||||
): ResourcesAction<string> {
|
||||
return {
|
||||
type: CHATS_DIRECTORY_UPDATED,
|
||||
payload
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function chatFilesUpdated(payload: IFileService[]): ResourcesAction<IFileService[]> {
|
||||
export function chatFilesUpdated(
|
||||
payload: IFileService[]
|
||||
): ResourcesAction<IFileService[]> {
|
||||
return {
|
||||
type: CHAT_FILES_UPDATED,
|
||||
payload
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function openContextMenuForResource(payload: IFileService): ResourcesAction<IFileService> {
|
||||
export function openContextMenuForResource(
|
||||
payload: IFileService
|
||||
): ResourcesAction<IFileService> {
|
||||
return {
|
||||
type: OPEN_CONTEXT_MENU_FOR_RESOURCE,
|
||||
payload
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function editResource(payload: IFileService): ResourcesAction<IFileService> {
|
||||
export function editResource(
|
||||
payload: IFileService
|
||||
): ResourcesAction<IFileService> {
|
||||
return {
|
||||
type: EDIT_RESOURCE,
|
||||
payload
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function renameResource(payload: IFileService): ResourcesAction<IFileService> {
|
||||
export function renameResource(
|
||||
payload: IFileService
|
||||
): ResourcesAction<IFileService> {
|
||||
return {
|
||||
type: RENAME_RESOURCE,
|
||||
payload
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function openResource(payload: IFileService): ResourcesAction<IFileService> {
|
||||
export function openResource(
|
||||
payload: IFileService
|
||||
): ResourcesAction<IFileService> {
|
||||
return {
|
||||
type: OPEN_RESOURCE,
|
||||
payload
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
declare type ResourceSettingsPayload = { dialog: ComponentClass<any> };
|
||||
declare interface ResourceSettingsPayload {
|
||||
dialog: ComponentClass<any>;
|
||||
}
|
||||
|
||||
export function openResourcesSettings(payload: ResourceSettingsPayload): ResourcesAction<ResourceSettingsPayload> {
|
||||
export function openResourcesSettings(
|
||||
payload: ResourceSettingsPayload
|
||||
): ResourcesAction<ResourceSettingsPayload> {
|
||||
return {
|
||||
type: OPEN_RESOURCE_SETTINGS,
|
||||
payload
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,3 +1,35 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
export const SWITCH_THEME = 'switchTheme';
|
||||
export declare type ThemeType = 'switchTheme';
|
||||
|
||||
|
@ -11,9 +43,12 @@ export interface SwitchThemePayload {
|
|||
themeComponents: string[];
|
||||
}
|
||||
|
||||
export function switchTheme(themeName: string, themeComponents: string[]): ThemeAction<SwitchThemePayload> {
|
||||
export function switchTheme(
|
||||
themeName: string,
|
||||
themeComponents: string[]
|
||||
): ThemeAction<SwitchThemePayload> {
|
||||
return {
|
||||
type: SWITCH_THEME,
|
||||
payload: { themeName, themeComponents }
|
||||
payload: { themeName, themeComponents },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,3 +1,35 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { BotInfo } from '@bfemulator/app-shared';
|
||||
import { Action } from 'redux';
|
||||
|
||||
|
@ -7,9 +39,11 @@ export interface WelcomePageAction<T> extends Action {
|
|||
payload: T;
|
||||
}
|
||||
|
||||
export function openContextMenuForBot(bot: BotInfo): WelcomePageAction<BotInfo> {
|
||||
export function openContextMenuForBot(
|
||||
bot: BotInfo
|
||||
): WelcomePageAction<BotInfo> {
|
||||
return {
|
||||
type: OPEN_CONTEXT_MENU_FOR_BOT,
|
||||
payload: bot
|
||||
payload: bot,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,6 +31,12 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import {
|
||||
getActiveBot,
|
||||
getBotInfoByPath,
|
||||
pathExistsInRecentBots,
|
||||
} from './botHelpers';
|
||||
|
||||
jest.mock('./store', () => ({
|
||||
store: {
|
||||
getState: () => ({
|
||||
|
@ -41,19 +47,15 @@ jest.mock('./store', () => ({
|
|||
padlock: 'padlock1',
|
||||
services: [],
|
||||
},
|
||||
botFiles: [{
|
||||
path: 'path1'
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
botFiles: [
|
||||
{
|
||||
path: 'path1',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
import {
|
||||
getActiveBot,
|
||||
getBotInfoByPath,
|
||||
pathExistsInRecentBots
|
||||
} from './botHelpers';
|
||||
describe('Bot helpers tests', () => {
|
||||
it('should get the active bot', () => {
|
||||
const activeBot = getActiveBot();
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
import { BotInfo } from '@bfemulator/app-shared';
|
||||
import { BotConfigWithPath } from '@bfemulator/sdk-shared';
|
||||
|
||||
import { store } from './store';
|
||||
|
||||
export function getActiveBot(): BotConfigWithPath {
|
||||
|
@ -42,7 +43,9 @@ export function getActiveBot(): BotConfigWithPath {
|
|||
const encoder = new (window as any).TextEncoder();
|
||||
const decoder = new (window as any).TextDecoder();
|
||||
|
||||
export const generateBotHash = async (bot: BotConfigWithPath): Promise<string> => {
|
||||
export const generateBotHash = async (
|
||||
bot: BotConfigWithPath
|
||||
): Promise<string> => {
|
||||
const buffer = encoder.encode(JSON.stringify(bot));
|
||||
const digest = await window.crypto.subtle.digest('SHA-256', buffer);
|
||||
return btoa(encoder.encode(decoder.decode(digest)));
|
||||
|
|
|
@ -35,7 +35,7 @@ import { store } from './store';
|
|||
|
||||
export function documentIdForConversation(conversationId: string): string {
|
||||
const state = store.getState();
|
||||
for (let key in state.chat.chats) {
|
||||
for (const key in state.chat.chats) {
|
||||
if (state.chat.chats[key].conversationId === conversationId) {
|
||||
return state.chat.chats[key].documentId;
|
||||
}
|
||||
|
|
|
@ -39,13 +39,14 @@ class DebugConnection extends EventEmitter {
|
|||
private onopen: any;
|
||||
private onclose: any;
|
||||
|
||||
constructor(connection: any) {
|
||||
public constructor(connection: any) {
|
||||
super();
|
||||
|
||||
this._connection = connection;
|
||||
|
||||
this._connection.onmessage = event => {
|
||||
console.info(`WS.recv: ${ event.data }`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`WS.recv: ${event.data}`);
|
||||
this.emit('message', event);
|
||||
if (this.onmessage) {
|
||||
this.onmessage(event);
|
||||
|
@ -53,6 +54,7 @@ class DebugConnection extends EventEmitter {
|
|||
};
|
||||
|
||||
this._connection.onopen = () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`WS.open`);
|
||||
this.emit('open');
|
||||
if (this.onopen) {
|
||||
|
@ -61,6 +63,7 @@ class DebugConnection extends EventEmitter {
|
|||
};
|
||||
|
||||
this._connection.onclose = () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`WS.close`);
|
||||
this.emit('close');
|
||||
if (this.onclose) {
|
||||
|
@ -69,16 +72,17 @@ class DebugConnection extends EventEmitter {
|
|||
};
|
||||
}
|
||||
|
||||
close() {
|
||||
public close() {
|
||||
this._connection.close();
|
||||
}
|
||||
|
||||
end() {
|
||||
public end() {
|
||||
this._connection.end();
|
||||
}
|
||||
|
||||
send(data: any) {
|
||||
console.info(`WS.send: ${ data }`);
|
||||
public send(data: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`WS.send: ${data}`);
|
||||
this._connection.send(data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,14 +32,17 @@
|
|||
//
|
||||
|
||||
import * as Constants from '../constants';
|
||||
|
||||
import * as EditorActions from './action/editorActions';
|
||||
import { Editor } from './reducer/editor';
|
||||
import { store } from './store';
|
||||
|
||||
export function hasNonGlobalTabs(tabGroups?: { [editorKey: string]: Editor }): number {
|
||||
export function hasNonGlobalTabs(tabGroups?: {
|
||||
[editorKey: string]: Editor;
|
||||
}): number {
|
||||
tabGroups = tabGroups || store.getState().editor.editors;
|
||||
let count = 0;
|
||||
for (let key in tabGroups) {
|
||||
for (const key in tabGroups) {
|
||||
if (tabGroups[key]) {
|
||||
count += Object.keys(tabGroups[key].documents)
|
||||
.map(documentId => tabGroups[key].documents[documentId])
|
||||
|
@ -52,9 +55,12 @@ export function hasNonGlobalTabs(tabGroups?: { [editorKey: string]: Editor }): n
|
|||
/**
|
||||
* Returns name of editor group, or undefined if doc is not open
|
||||
*/
|
||||
export function getTabGroupForDocument(documentId: string, tabGroups?: { [editorKey: string]: Editor }): string {
|
||||
export function getTabGroupForDocument(
|
||||
documentId: string,
|
||||
tabGroups?: { [editorKey: string]: Editor }
|
||||
): string {
|
||||
tabGroups = tabGroups || store.getState().editor.editors;
|
||||
for (let key in tabGroups) {
|
||||
for (const key in tabGroups) {
|
||||
if (tabGroups[key] && tabGroups[key].documents) {
|
||||
if (tabGroups[key].documents[documentId]) {
|
||||
return key;
|
||||
|
@ -66,15 +72,19 @@ export function getTabGroupForDocument(documentId: string, tabGroups?: { [editor
|
|||
|
||||
/** Takes a tab group key and returns the key of the other tab group */
|
||||
export function getOtherTabGroup(tabGroup: string): string {
|
||||
return tabGroup === Constants.EDITOR_KEY_PRIMARY ? Constants.EDITOR_KEY_SECONDARY : Constants.EDITOR_KEY_PRIMARY;
|
||||
return tabGroup === Constants.EDITOR_KEY_PRIMARY
|
||||
? Constants.EDITOR_KEY_SECONDARY
|
||||
: Constants.EDITOR_KEY_PRIMARY;
|
||||
}
|
||||
|
||||
export function showWelcomePage(): void {
|
||||
store.dispatch(EditorActions.open({
|
||||
contentType: Constants.CONTENT_TYPE_WELCOME_PAGE,
|
||||
documentId: Constants.DOCUMENT_ID_WELCOME_PAGE,
|
||||
isGlobal: true
|
||||
}));
|
||||
store.dispatch(
|
||||
EditorActions.open({
|
||||
contentType: Constants.CONTENT_TYPE_WELCOME_PAGE,
|
||||
documentId: Constants.DOCUMENT_ID_WELCOME_PAGE,
|
||||
isGlobal: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function tabGroupHasDocuments(tabGroup: Editor): boolean {
|
||||
|
|
|
@ -30,8 +30,13 @@
|
|||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
/* eslint-disable typescript/camelcase */
|
||||
|
||||
import {
|
||||
azureArmTokenDataChanged,
|
||||
invalidateArmToken,
|
||||
} from '../action/azureAuthActions';
|
||||
|
||||
import { azureArmTokenDataChanged, invalidateArmToken, } from '../action/azureAuthActions';
|
||||
import { azureAuth, AzureAuthState } from './azureAuthReducer';
|
||||
|
||||
describe('Azure auth reducer tests', () => {
|
||||
|
@ -40,7 +45,7 @@ describe('Azure auth reducer tests', () => {
|
|||
beforeEach(() => {
|
||||
startingState = {
|
||||
access_token: null,
|
||||
persistLogin: false
|
||||
persistLogin: false,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
/* eslint-disable typescript/camelcase */
|
||||
|
||||
import {
|
||||
ArmTokenData,
|
||||
|
@ -46,19 +47,20 @@ export interface AzureAuthState {
|
|||
|
||||
const initialState: AzureAuthState = {
|
||||
access_token: null,
|
||||
persistLogin: false
|
||||
persistLogin: false,
|
||||
};
|
||||
|
||||
export function azureAuth(state: AzureAuthState = initialState, action: AzureAuthAction<ArmTokenData | void>)
|
||||
: AzureAuthState {
|
||||
export function azureAuth(
|
||||
state: AzureAuthState = initialState,
|
||||
action: AzureAuthAction<ArmTokenData | void>
|
||||
): AzureAuthState {
|
||||
const { payload = {}, type = '' } = action || {};
|
||||
const { access_token } = (payload || {}) as ArmTokenData;
|
||||
|
||||
switch (type) {
|
||||
|
||||
case AZURE_BEGIN_AUTH_WORKFLOW:
|
||||
case AZURE_INVALIDATE_ARM_TOKEN:
|
||||
return { ...state, access_token: ''};
|
||||
return { ...state, access_token: '' };
|
||||
|
||||
case AZURE_ARM_TOKEN_DATA_CHANGED:
|
||||
return { ...state, access_token };
|
||||
|
|
|
@ -31,16 +31,18 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { bot, BotState } from './bot';
|
||||
import { BotAction, close, load, setActive } from '../action/botActions';
|
||||
import { BotInfo } from '@bfemulator/app-shared';
|
||||
import { BotConfigWithPath } from '@bfemulator/sdk-shared';
|
||||
|
||||
import { BotAction, close, load, setActive } from '../action/botActions';
|
||||
|
||||
import { bot, BotState } from './bot';
|
||||
|
||||
describe('Bot reducer tests', () => {
|
||||
const DEFAULT_STATE: BotState = {
|
||||
activeBot: null,
|
||||
botFiles: [],
|
||||
activeBotDigest: ''
|
||||
activeBotDigest: '',
|
||||
};
|
||||
|
||||
it('should return unaltered state for non-matching action type', () => {
|
||||
|
@ -57,7 +59,7 @@ describe('Bot reducer tests', () => {
|
|||
padlock: null,
|
||||
services: [],
|
||||
path: 'somePath',
|
||||
version: '0.1'
|
||||
version: '0.1',
|
||||
};
|
||||
|
||||
it('should set a bot as active', () => {
|
||||
|
@ -71,23 +73,23 @@ describe('Bot reducer tests', () => {
|
|||
{
|
||||
displayName: 'bot2',
|
||||
path: 'path2',
|
||||
secret: 'test-secret'
|
||||
secret: 'test-secret',
|
||||
},
|
||||
{
|
||||
displayName: 'bot3',
|
||||
path: 'path3',
|
||||
secret: null
|
||||
secret: null,
|
||||
},
|
||||
{
|
||||
displayName: 'bot1',
|
||||
path: 'somePath',
|
||||
secret: null
|
||||
secret: null,
|
||||
},
|
||||
];
|
||||
|
||||
const startingState: BotState = {
|
||||
...DEFAULT_STATE,
|
||||
botFiles: testbots
|
||||
botFiles: testbots,
|
||||
};
|
||||
|
||||
const action = setActive(testbot);
|
||||
|
@ -109,10 +111,10 @@ describe('Bot reducer tests', () => {
|
|||
endpoint: 'someEndpointOverride',
|
||||
appId: 'someAppId',
|
||||
appPassword: 'someAppPw',
|
||||
id: 'someEndpointOverride'
|
||||
}
|
||||
}
|
||||
} as any
|
||||
id: 'someEndpointOverride',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
};
|
||||
|
||||
const action = setActive(testbot);
|
||||
|
@ -129,7 +131,7 @@ describe('Bot reducer tests', () => {
|
|||
expect(endpointOverrides.appPassword).toBe('someAppPw');
|
||||
});
|
||||
|
||||
it('should throw away overrides from the previous bot if they don\'t have the same path', () => {
|
||||
it("should throw away overrides from the previous bot if they don't have the same path", () => {
|
||||
const startingState: BotState = {
|
||||
...DEFAULT_STATE,
|
||||
activeBot: {
|
||||
|
@ -143,10 +145,10 @@ describe('Bot reducer tests', () => {
|
|||
endpoint: 'someEndpointOverride',
|
||||
appId: 'someAppId',
|
||||
appPassword: 'someAppPw',
|
||||
id: 'someEndpointOverride'
|
||||
}
|
||||
}
|
||||
} as any
|
||||
id: 'someEndpointOverride',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
};
|
||||
|
||||
const action = setActive(testbot);
|
||||
|
@ -163,19 +165,19 @@ describe('Bot reducer tests', () => {
|
|||
{
|
||||
displayName: 'bot1',
|
||||
path: 'path1',
|
||||
secret: null
|
||||
secret: null,
|
||||
},
|
||||
{
|
||||
displayName: 'bot2',
|
||||
path: 'path2',
|
||||
secret: 'test-secret'
|
||||
secret: 'test-secret',
|
||||
},
|
||||
{
|
||||
displayName: 'bot3',
|
||||
path: 'path3',
|
||||
secret: null
|
||||
secret: null,
|
||||
},
|
||||
null
|
||||
null,
|
||||
];
|
||||
const action = load(bots);
|
||||
const state = bot(DEFAULT_STATE, action);
|
||||
|
@ -185,18 +187,18 @@ describe('Bot reducer tests', () => {
|
|||
{
|
||||
displayName: 'bot1',
|
||||
path: 'path1',
|
||||
secret: null
|
||||
secret: null,
|
||||
},
|
||||
{
|
||||
displayName: 'bot2',
|
||||
path: 'path2',
|
||||
secret: 'test-secret'
|
||||
secret: 'test-secret',
|
||||
},
|
||||
{
|
||||
displayName: 'bot3',
|
||||
path: 'path3',
|
||||
secret: null
|
||||
}
|
||||
secret: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -207,8 +209,8 @@ describe('Bot reducer tests', () => {
|
|||
name: 'bot',
|
||||
description: 'this is a test bot',
|
||||
padlock: null,
|
||||
services: []
|
||||
} as any
|
||||
services: [],
|
||||
} as any,
|
||||
};
|
||||
const action = close();
|
||||
const endingState = bot(startingState, action);
|
||||
|
|
|
@ -32,7 +32,12 @@
|
|||
//
|
||||
|
||||
import { BotInfo } from '@bfemulator/app-shared';
|
||||
import { applyBotConfigOverrides, BotConfigWithPath, botsAreTheSame } from '@bfemulator/sdk-shared';
|
||||
import {
|
||||
applyBotConfigOverrides,
|
||||
BotConfigWithPath,
|
||||
botsAreTheSame,
|
||||
} from '@bfemulator/sdk-shared';
|
||||
|
||||
import { BotAction, BotActions } from '../action/botActions';
|
||||
|
||||
export interface BotState {
|
||||
|
@ -44,12 +49,11 @@ export interface BotState {
|
|||
const DEFAULT_STATE: BotState = {
|
||||
activeBot: null,
|
||||
activeBotDigest: null,
|
||||
botFiles: []
|
||||
botFiles: [],
|
||||
};
|
||||
|
||||
export function bot(state: BotState = DEFAULT_STATE, action: BotAction) {
|
||||
switch (action.type) {
|
||||
|
||||
case BotActions.load: {
|
||||
state = setBotFilesState(action.payload.bots, state);
|
||||
break;
|
||||
|
@ -57,14 +61,21 @@ export function bot(state: BotState = DEFAULT_STATE, action: BotAction) {
|
|||
|
||||
case BotActions.setActive: {
|
||||
// move active bot up to the top of the recent bots list
|
||||
const mostRecentBot = state.botFiles.find(botArg => botArg && botArg.path === action.payload.bot.path);
|
||||
let recentBots = state.botFiles.filter(botArg => botArg && botArg.path !== action.payload.bot.path);
|
||||
const mostRecentBot = state.botFiles.find(
|
||||
botArg => botArg && botArg.path === action.payload.bot.path
|
||||
);
|
||||
const recentBots = state.botFiles.filter(
|
||||
botArg => botArg && botArg.path !== action.payload.bot.path
|
||||
);
|
||||
if (mostRecentBot) {
|
||||
recentBots.unshift(mostRecentBot);
|
||||
}
|
||||
let newActiveBot = action.payload.bot;
|
||||
if (botsAreTheSame(state.activeBot, newActiveBot)) {
|
||||
newActiveBot = applyBotConfigOverrides(newActiveBot, state.activeBot.overrides);
|
||||
newActiveBot = applyBotConfigOverrides(
|
||||
newActiveBot,
|
||||
state.activeBot.overrides
|
||||
);
|
||||
}
|
||||
state = setBotFilesState(recentBots, state);
|
||||
state = setActiveBot(newActiveBot, state);
|
||||
|
@ -87,16 +98,17 @@ export function bot(state: BotState = DEFAULT_STATE, action: BotAction) {
|
|||
}
|
||||
|
||||
function setActiveBot(botConfig: BotConfigWithPath, state: BotState): BotState {
|
||||
return Object.assign({}, state, {
|
||||
return {
|
||||
...state,
|
||||
get activeBot() {
|
||||
// Clones only - this guarantees only pristine bots will exist in the store
|
||||
return JSON.parse(JSON.stringify(botConfig));
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function setBotFilesState(botFilesState: BotInfo[], state: BotState): BotState {
|
||||
let newState = Object.assign({}, state);
|
||||
const newState = { ...state };
|
||||
|
||||
newState.botFiles = botFilesState;
|
||||
return newState;
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
//
|
||||
|
||||
import LogEntry from '@bfemulator/emulator-core/lib/types/log/entry';
|
||||
|
||||
import {
|
||||
addTranscript,
|
||||
appendToLog,
|
||||
|
@ -43,9 +44,10 @@ import {
|
|||
newDocument,
|
||||
removeTranscript,
|
||||
setInspectorObjects,
|
||||
updateChat
|
||||
updateChat,
|
||||
} from '../action/chatActions';
|
||||
import { closeNonGlobalTabs } from '../action/editorActions';
|
||||
|
||||
import { chat, ChatState } from './chat';
|
||||
|
||||
describe('Chat reducer tests', () => {
|
||||
|
@ -55,9 +57,9 @@ describe('Chat reducer tests', () => {
|
|||
chats: {
|
||||
[testChatId]: {
|
||||
log: {
|
||||
entries: []
|
||||
}
|
||||
}
|
||||
entries: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
transcripts: [],
|
||||
};
|
||||
|
@ -116,13 +118,13 @@ describe('Chat reducer tests', () => {
|
|||
...DEFAULT_STATE,
|
||||
chats: {
|
||||
...DEFAULT_STATE.chats,
|
||||
[testChatId]: {}
|
||||
}
|
||||
[testChatId]: {},
|
||||
},
|
||||
};
|
||||
const endingState = chat(startingState, action);
|
||||
const expectedDoc = {
|
||||
...endingState.chats[testChatId],
|
||||
testing: true
|
||||
testing: true,
|
||||
};
|
||||
expect(endingState.chats[testChatId]).toEqual(expectedDoc);
|
||||
});
|
||||
|
@ -135,10 +137,10 @@ describe('Chat reducer tests', () => {
|
|||
type: 'text',
|
||||
payload: {
|
||||
level: 0,
|
||||
text: 'testing'
|
||||
}
|
||||
}
|
||||
]
|
||||
text: 'testing',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const action = appendToLog(testChatId, logEntry);
|
||||
const startingState = {
|
||||
|
@ -147,10 +149,10 @@ describe('Chat reducer tests', () => {
|
|||
...DEFAULT_STATE.chats,
|
||||
[testChatId]: {
|
||||
log: {
|
||||
entries: []
|
||||
}
|
||||
}
|
||||
}
|
||||
entries: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const endingState = chat(startingState, action);
|
||||
expect(endingState.chats[testChatId].log.entries[0]).toBeTruthy();
|
||||
|
@ -165,10 +167,10 @@ describe('Chat reducer tests', () => {
|
|||
type: 'text',
|
||||
payload: {
|
||||
level: 0,
|
||||
text: 'testing'
|
||||
}
|
||||
}
|
||||
]
|
||||
text: 'testing',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const startingState = {
|
||||
...DEFAULT_STATE,
|
||||
|
@ -176,10 +178,10 @@ describe('Chat reducer tests', () => {
|
|||
...DEFAULT_STATE.chats,
|
||||
[testChatId]: {
|
||||
log: {
|
||||
entries: []
|
||||
}
|
||||
}
|
||||
}
|
||||
entries: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let state = chat(startingState, appendToLog(testChatId, logEntry));
|
||||
|
@ -195,12 +197,16 @@ describe('Chat reducer tests', () => {
|
|||
...DEFAULT_STATE,
|
||||
chats: {
|
||||
...DEFAULT_STATE.chats,
|
||||
[testChatId]: {}
|
||||
}
|
||||
[testChatId]: {},
|
||||
},
|
||||
};
|
||||
const endingState = chat(startingState, action);
|
||||
expect(endingState.chats[testChatId].inspectorObjects.length).toBeGreaterThan(0);
|
||||
expect(endingState.chats[testChatId].inspectorObjects[0]).toEqual({ testing: true });
|
||||
expect(
|
||||
endingState.chats[testChatId].inspectorObjects.length
|
||||
).toBeGreaterThan(0);
|
||||
expect(endingState.chats[testChatId].inspectorObjects[0]).toEqual({
|
||||
testing: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should reset state on a "close all" editor action', () => {
|
||||
|
@ -209,10 +215,10 @@ describe('Chat reducer tests', () => {
|
|||
changeKey: 999,
|
||||
chats: {
|
||||
[tempChat]: {
|
||||
testing: true
|
||||
}
|
||||
testing: true,
|
||||
},
|
||||
},
|
||||
transcripts: ['xs1', 'xs2', 'xs3']
|
||||
transcripts: ['xs1', 'xs2', 'xs3'],
|
||||
};
|
||||
const action = closeNonGlobalTabs();
|
||||
const state = chat(alteredState, action);
|
||||
|
@ -228,11 +234,14 @@ describe('Chat reducer tests', () => {
|
|||
...DEFAULT_STATE.chats,
|
||||
chat1: {
|
||||
id: 'chat',
|
||||
userId: 'userId'
|
||||
}
|
||||
}
|
||||
userId: 'userId',
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = updateChat('chat1', { id: 'updatedChatId', userId: 'updatedUserId' });
|
||||
const action = updateChat('chat1', {
|
||||
id: 'updatedChatId',
|
||||
userId: 'updatedUserId',
|
||||
});
|
||||
const state = chat(startingState, action);
|
||||
expect(state.chats.chat1.id).toBe('updatedChatId');
|
||||
expect(state.chats.chat1.userId).toBe('updatedUserId');
|
||||
|
|
|
@ -47,7 +47,10 @@ const DEFAULT_STATE: ChatState = {
|
|||
transcripts: [],
|
||||
};
|
||||
|
||||
export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | EditorAction): ChatState {
|
||||
export function chat(
|
||||
state: ChatState = DEFAULT_STATE,
|
||||
action: ChatAction | EditorAction
|
||||
): ChatState {
|
||||
switch (action.type) {
|
||||
case ChatActions.addTranscript: {
|
||||
const { payload } = action;
|
||||
|
@ -78,8 +81,8 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit
|
|||
changeKey: state.changeKey + 1,
|
||||
chats: {
|
||||
...state.chats,
|
||||
[payload.documentId]: { ...payload }
|
||||
}
|
||||
[payload.documentId]: { ...payload },
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
@ -89,7 +92,7 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit
|
|||
// can't use the JSON.parse(JSON.stringify())
|
||||
// trick with chats because Subscribers are circular
|
||||
if (payload.documentId in state.chats) {
|
||||
let copy = { ...state };
|
||||
const copy = { ...state };
|
||||
copy.changeKey += 1;
|
||||
delete copy.chats[payload.documentId];
|
||||
state = { ...copy };
|
||||
|
@ -103,16 +106,16 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit
|
|||
if (document) {
|
||||
document = {
|
||||
...document,
|
||||
...payload.options
|
||||
...payload.options,
|
||||
};
|
||||
state = {
|
||||
...state,
|
||||
chats: {
|
||||
...state.chats,
|
||||
[payload.documentId]: {
|
||||
...document
|
||||
}
|
||||
}
|
||||
...document,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
@ -126,20 +129,17 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit
|
|||
...document,
|
||||
log: {
|
||||
...document.log,
|
||||
entries: [
|
||||
...document.log.entries,
|
||||
payload.entry
|
||||
]
|
||||
}
|
||||
entries: [...document.log.entries, payload.entry],
|
||||
},
|
||||
};
|
||||
state = {
|
||||
...state,
|
||||
chats: {
|
||||
...state.chats,
|
||||
[payload.documentId]: {
|
||||
...document
|
||||
}
|
||||
}
|
||||
...document,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
@ -152,17 +152,17 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit
|
|||
document = {
|
||||
...document,
|
||||
log: {
|
||||
entries: []
|
||||
}
|
||||
entries: [],
|
||||
},
|
||||
};
|
||||
state = {
|
||||
...state,
|
||||
chats: {
|
||||
...state.chats,
|
||||
[payload.documentId]: {
|
||||
...document
|
||||
}
|
||||
}
|
||||
...document,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
@ -174,7 +174,7 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit
|
|||
if (document) {
|
||||
document = {
|
||||
...document,
|
||||
inspectorObjects: payload.objs
|
||||
inspectorObjects: payload.objs,
|
||||
};
|
||||
}
|
||||
state = {
|
||||
|
@ -182,9 +182,9 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit
|
|||
chats: {
|
||||
...state.chats,
|
||||
[payload.documentId]: {
|
||||
...document
|
||||
}
|
||||
}
|
||||
...document,
|
||||
},
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
@ -196,16 +196,16 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit
|
|||
if (document) {
|
||||
document = {
|
||||
...document,
|
||||
...updatedValues
|
||||
...updatedValues,
|
||||
};
|
||||
state = {
|
||||
...state,
|
||||
chats: {
|
||||
...state.chats,
|
||||
[payload.documentId]: {
|
||||
...document
|
||||
}
|
||||
}
|
||||
...document,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
@ -223,8 +223,11 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit
|
|||
return state;
|
||||
}
|
||||
|
||||
function setTranscriptsState(transcripts: string[], state: ChatState): ChatState {
|
||||
let newState = Object.assign({}, state);
|
||||
function setTranscriptsState(
|
||||
transcripts: string[],
|
||||
state: ChatState
|
||||
): ChatState {
|
||||
const newState = { ...state };
|
||||
|
||||
newState.transcripts = transcripts;
|
||||
newState.changeKey = state.changeKey + 1;
|
||||
|
|
|
@ -1,8 +1,46 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { ClientAwareSettings } from '@bfemulator/app-shared/built';
|
||||
import { CLIENT_AWARE_SETTINGS_CHANGED, ClientAwareSettingsActions } from '../action/clientAwareSettingsActions';
|
||||
|
||||
export function clientAwareSettings(state: ClientAwareSettings = {} as any, action: ClientAwareSettingsActions)
|
||||
: ClientAwareSettings {
|
||||
import {
|
||||
CLIENT_AWARE_SETTINGS_CHANGED,
|
||||
ClientAwareSettingsActions,
|
||||
} from '../action/clientAwareSettingsActions';
|
||||
|
||||
export function clientAwareSettings(
|
||||
state: ClientAwareSettings = {} as any,
|
||||
action: ClientAwareSettingsActions
|
||||
): ClientAwareSettings {
|
||||
switch (action.type) {
|
||||
case CLIENT_AWARE_SETTINGS_CHANGED:
|
||||
return { ...state, ...action.payload };
|
||||
|
|
|
@ -32,11 +32,12 @@
|
|||
//
|
||||
|
||||
import { DialogAction, setShowing } from '../action/dialogActions';
|
||||
|
||||
import { dialog, DialogState } from './dialog';
|
||||
|
||||
describe('Dialog reducer tests', () => {
|
||||
const DEFAULT_STATE: DialogState = {
|
||||
showing: false
|
||||
showing: false,
|
||||
};
|
||||
|
||||
it('should return unaltered state for non-matching action type', () => {
|
||||
|
|
|
@ -38,10 +38,13 @@ export interface DialogState {
|
|||
}
|
||||
|
||||
const DEFAULT_STATE: DialogState = {
|
||||
showing: false
|
||||
showing: false,
|
||||
};
|
||||
|
||||
export function dialog(state: DialogState = DEFAULT_STATE, action: DialogAction): DialogState {
|
||||
export function dialog(
|
||||
state: DialogState = DEFAULT_STATE,
|
||||
action: DialogAction
|
||||
): DialogState {
|
||||
switch (action.type) {
|
||||
case DialogActions.setShowing: {
|
||||
state = setShowing(action.payload.showing, state);
|
||||
|
@ -55,5 +58,5 @@ export function dialog(state: DialogState = DEFAULT_STATE, action: DialogAction)
|
|||
}
|
||||
|
||||
export function setShowing(showing: boolean, _state: DialogState): DialogState {
|
||||
return { showing: showing };
|
||||
return { showing };
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
//
|
||||
|
||||
import { deepCopySlow } from '@bfemulator/app-shared';
|
||||
|
||||
import * as Constants from '../../constants';
|
||||
import {
|
||||
addDocPendingChange,
|
||||
|
@ -48,8 +49,9 @@ import {
|
|||
splitTab,
|
||||
swapTabs,
|
||||
toggleDraggingTab,
|
||||
updateDocument
|
||||
updateDocument,
|
||||
} from '../action/editorActions';
|
||||
|
||||
import {
|
||||
editor,
|
||||
Editor,
|
||||
|
@ -59,26 +61,25 @@ import {
|
|||
setActiveEditor,
|
||||
setDraggingTab,
|
||||
setEditorState,
|
||||
setNewPrimaryEditor
|
||||
setNewPrimaryEditor,
|
||||
} from './editor';
|
||||
|
||||
let defaultState: EditorState;
|
||||
jest.mock('../../ui/dialogs', () => ({
|
||||
AzureLoginPromptDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
AzureLoginSuccessDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
BotCreationDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
DialogService: { showDialog: () => Promise.resolve(true) },
|
||||
SecretPromptDialog: function mock() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
));
|
||||
AzureLoginPromptDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
AzureLoginSuccessDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
BotCreationDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
DialogService: { showDialog: () => Promise.resolve(true) },
|
||||
SecretPromptDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
}));
|
||||
describe('Editor reducer tests', () => {
|
||||
beforeEach(initializeDefaultState);
|
||||
|
||||
|
@ -98,17 +99,21 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors,
|
||||
[Constants.EDITOR_KEY_PRIMARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
tabOrder: ['doc1', 'doc2']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc1', 'doc2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const srcEditorKey = Constants.EDITOR_KEY_PRIMARY;
|
||||
const destEditorKey = Constants.EDITOR_KEY_PRIMARY;
|
||||
const docIdToAppend = 'doc1';
|
||||
const action = appendTab(srcEditorKey, destEditorKey, docIdToAppend);
|
||||
const endingState = editor(startingState, action);
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder[0]).not.toBe(docIdToAppend);
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder[1]).toBe(docIdToAppend);
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder[0]
|
||||
).not.toBe(docIdToAppend);
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder[1]
|
||||
).toBe(docIdToAppend);
|
||||
|
||||
// assert that draggingTab is toggled off
|
||||
expect(endingState.draggingTab).toBe(false);
|
||||
|
@ -123,21 +128,21 @@ describe('Editor reducer tests', () => {
|
|||
[Constants.EDITOR_KEY_PRIMARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
documents: {
|
||||
'doc1': {},
|
||||
'doc2': {}
|
||||
doc1: {},
|
||||
doc2: {},
|
||||
},
|
||||
tabOrder: ['doc1', 'doc2'],
|
||||
recentTabs: ['doc1', 'doc2']
|
||||
recentTabs: ['doc1', 'doc2'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
documents: {
|
||||
'doc3': {}
|
||||
doc3: {},
|
||||
},
|
||||
tabOrder: ['doc3'],
|
||||
recentTabs: ['doc3']
|
||||
}
|
||||
}
|
||||
recentTabs: ['doc3'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const srcEditorKey = Constants.EDITOR_KEY_PRIMARY;
|
||||
const destEditorKey = Constants.EDITOR_KEY_SECONDARY;
|
||||
|
@ -165,20 +170,20 @@ describe('Editor reducer tests', () => {
|
|||
[Constants.EDITOR_KEY_PRIMARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
documents: {
|
||||
'doc1': {}
|
||||
doc1: {},
|
||||
},
|
||||
tabOrder: ['doc1'],
|
||||
recentTabs: ['doc1']
|
||||
recentTabs: ['doc1'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
documents: {
|
||||
'doc2': {}
|
||||
doc2: {},
|
||||
},
|
||||
tabOrder: ['doc2'],
|
||||
recentTabs: ['doc2']
|
||||
}
|
||||
}
|
||||
recentTabs: ['doc2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const srcEditorKey = Constants.EDITOR_KEY_SECONDARY;
|
||||
const destEditorKey = Constants.EDITOR_KEY_PRIMARY;
|
||||
|
@ -197,28 +202,32 @@ describe('Editor reducer tests', () => {
|
|||
[Constants.EDITOR_KEY_PRIMARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
documents: {
|
||||
'doc1': {}
|
||||
doc1: {},
|
||||
},
|
||||
tabOrder: ['doc1'],
|
||||
recentTabs: ['doc1']
|
||||
recentTabs: ['doc1'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
documents: {
|
||||
'doc2': {}
|
||||
doc2: {},
|
||||
},
|
||||
tabOrder: ['doc2'],
|
||||
recentTabs: ['doc2']
|
||||
}
|
||||
}
|
||||
recentTabs: ['doc2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const srcEditorKey = Constants.EDITOR_KEY_PRIMARY;
|
||||
const destEditorKey = Constants.EDITOR_KEY_SECONDARY;
|
||||
const action = appendTab(srcEditorKey, destEditorKey, 'doc1');
|
||||
const endingState = editor(startingState, action);
|
||||
expect(endingState.activeEditor).toBe(Constants.EDITOR_KEY_PRIMARY);
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder).toEqual(['doc2', 'doc1']);
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].tabOrder).toEqual([]);
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder
|
||||
).toEqual(['doc2', 'doc1']);
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_SECONDARY].tabOrder
|
||||
).toEqual([]);
|
||||
|
||||
// assert that draggingTab is toggled off
|
||||
expect(endingState.draggingTab).toBe(false);
|
||||
|
@ -235,13 +244,13 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc1',
|
||||
documents: {
|
||||
'doc1': {},
|
||||
'doc2': {}
|
||||
doc1: {},
|
||||
doc2: {},
|
||||
},
|
||||
recentTabs: ['doc1', 'doc2'],
|
||||
tabOrder: ['doc1', 'doc2']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc1', 'doc2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const editorKey = Constants.EDITOR_KEY_PRIMARY;
|
||||
const action = close(editorKey, 'doc1');
|
||||
|
@ -263,21 +272,21 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc1',
|
||||
documents: {
|
||||
'doc1': {}
|
||||
doc1: {},
|
||||
},
|
||||
recentTabs: ['doc1'],
|
||||
tabOrder: ['doc1']
|
||||
tabOrder: ['doc1'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
activeDocumentId: 'doc2',
|
||||
documents: {
|
||||
'doc2': {}
|
||||
doc2: {},
|
||||
},
|
||||
recentTabs: ['doc2'],
|
||||
tabOrder: ['doc2']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const editorKey = Constants.EDITOR_KEY_PRIMARY;
|
||||
const action = close(editorKey, 'doc1');
|
||||
|
@ -287,7 +296,11 @@ describe('Editor reducer tests', () => {
|
|||
expect(primaryEditor.recentTabs).toEqual(['doc2']);
|
||||
expect(primaryEditor.tabOrder).toEqual(['doc2']);
|
||||
expect(Object.keys(primaryEditor.documents)).toContain('doc2');
|
||||
expect(Object.keys(endingState.editors[Constants.EDITOR_KEY_SECONDARY].documents)).not.toContain('doc1');
|
||||
expect(
|
||||
Object.keys(
|
||||
endingState.editors[Constants.EDITOR_KEY_SECONDARY].documents
|
||||
)
|
||||
).not.toContain('doc1');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -300,7 +313,7 @@ describe('Editor reducer tests', () => {
|
|||
it('should set the active editor', () => {
|
||||
const startingState: EditorState = {
|
||||
...defaultState,
|
||||
activeEditor: Constants.EDITOR_KEY_SECONDARY
|
||||
activeEditor: Constants.EDITOR_KEY_SECONDARY,
|
||||
};
|
||||
const action = setActiveEditorAction(Constants.EDITOR_KEY_PRIMARY);
|
||||
const endingState = editor(startingState, action);
|
||||
|
@ -317,22 +330,22 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc1',
|
||||
documents: {
|
||||
'doc1': {},
|
||||
'doc2': {}
|
||||
doc1: {},
|
||||
doc2: {},
|
||||
},
|
||||
recentTabs: ['doc1', 'doc2'],
|
||||
tabOrder: ['doc1', 'doc2']
|
||||
tabOrder: ['doc1', 'doc2'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
activeDocumentId: 'doc3',
|
||||
documents: {
|
||||
'doc3': {}
|
||||
doc3: {},
|
||||
},
|
||||
recentTabs: ['doc3'],
|
||||
tabOrder: ['doc3']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc3'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = setActiveTab('doc2');
|
||||
const endingState = editor(startingState, action);
|
||||
|
@ -342,7 +355,7 @@ describe('Editor reducer tests', () => {
|
|||
expect(newActiveEditor.recentTabs).toEqual(['doc2', 'doc1']);
|
||||
});
|
||||
|
||||
it('should set a document\'s dirty flag', () => {
|
||||
it("should set a document's dirty flag", () => {
|
||||
const startingState: EditorState = {
|
||||
...defaultState,
|
||||
editors: {
|
||||
|
@ -351,17 +364,20 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
activeDocumentId: 'doc3',
|
||||
documents: {
|
||||
'doc3': { dirty: false }
|
||||
doc3: { dirty: false },
|
||||
},
|
||||
recentTabs: ['doc3'],
|
||||
tabOrder: ['doc3']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc3'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const dirtyDocId = 'doc3';
|
||||
const action = setDirtyFlag(dirtyDocId, true);
|
||||
const endingState = editor(startingState, action);
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].documents[dirtyDocId].dirty).toBe(true);
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_SECONDARY].documents[dirtyDocId]
|
||||
.dirty
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should close all non-global tabs', () => {
|
||||
|
@ -374,26 +390,26 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc2',
|
||||
documents: {
|
||||
'doc1': { isGlobal: true },
|
||||
'doc2': { isGlobal: false },
|
||||
'doc3': { isGlobal: false }
|
||||
doc1: { isGlobal: true },
|
||||
doc2: { isGlobal: false },
|
||||
doc3: { isGlobal: false },
|
||||
},
|
||||
recentTabs: ['doc2', 'doc1', 'doc3'],
|
||||
tabOrder: ['doc1', 'doc2', 'doc3']
|
||||
tabOrder: ['doc1', 'doc2', 'doc3'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
activeDocumentId: 'doc6',
|
||||
documents: {
|
||||
'doc4': { isGlobal: false },
|
||||
'doc5': { isGlobal: true },
|
||||
'doc6': { isGlobal: true }
|
||||
doc4: { isGlobal: false },
|
||||
doc5: { isGlobal: true },
|
||||
doc6: { isGlobal: true },
|
||||
},
|
||||
recentTabs: ['doc6', 'doc4', 'doc5'],
|
||||
tabOrder: ['doc4', 'doc5', 'doc6']
|
||||
}
|
||||
tabOrder: ['doc4', 'doc5', 'doc6'],
|
||||
},
|
||||
},
|
||||
docsWithPendingChanges: ['doc2', 'doc3']
|
||||
docsWithPendingChanges: ['doc2', 'doc3'],
|
||||
};
|
||||
const action = closeNonGlobalTabs();
|
||||
const endingState = editor(startingState, action);
|
||||
|
@ -421,18 +437,22 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc1',
|
||||
documents: {
|
||||
'doc1': { documentId: 'doc1' }
|
||||
doc1: { documentId: 'doc1' },
|
||||
},
|
||||
recentTabs: ['doc1'],
|
||||
tabOrder: ['doc1']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc1'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const docToUpdateId = 'doc1';
|
||||
const action = updateDocument(docToUpdateId, { isGlobal: true, dirty: true });
|
||||
const action = updateDocument(docToUpdateId, {
|
||||
isGlobal: true,
|
||||
dirty: true,
|
||||
});
|
||||
const endingState = editor(startingState, action);
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].documents[docToUpdateId])
|
||||
.toEqual({ documentId: 'doc1', isGlobal: true, dirty: true });
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_PRIMARY].documents[docToUpdateId]
|
||||
).toEqual({ documentId: 'doc1', isGlobal: true, dirty: true });
|
||||
});
|
||||
|
||||
describe('opening a document', () => {
|
||||
|
@ -446,27 +466,33 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc1',
|
||||
documents: {
|
||||
'doc1': {}
|
||||
doc1: {},
|
||||
},
|
||||
recentTabs: ['doc1'],
|
||||
tabOrder: ['doc1']
|
||||
tabOrder: ['doc1'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
activeDocumentId: 'doc2',
|
||||
documents: {
|
||||
'doc2': {},
|
||||
'doc3': {}
|
||||
doc2: {},
|
||||
doc3: {},
|
||||
},
|
||||
recentTabs: ['doc3', 'doc2'],
|
||||
tabOrder: ['doc2', 'doc3']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc2', 'doc3'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = open({ contentType: Constants.CONTENT_TYPE_APP_SETTINGS, documentId: 'doc2', isGlobal: true });
|
||||
const action = open({
|
||||
contentType: Constants.CONTENT_TYPE_APP_SETTINGS,
|
||||
documentId: 'doc2',
|
||||
isGlobal: true,
|
||||
});
|
||||
const endingState = editor(startingState, action);
|
||||
expect(endingState.activeEditor).toBe(Constants.EDITOR_KEY_SECONDARY);
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].recentTabs).toEqual(['doc2', 'doc3']);
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_SECONDARY].recentTabs
|
||||
).toEqual(['doc2', 'doc3']);
|
||||
});
|
||||
|
||||
it('should focus an already existing document in the same tab group', () => {
|
||||
|
@ -479,15 +505,19 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc1',
|
||||
documents: {
|
||||
'doc1': {},
|
||||
'doc2': {}
|
||||
doc1: {},
|
||||
doc2: {},
|
||||
},
|
||||
recentTabs: ['doc1', 'doc2'],
|
||||
tabOrder: ['doc1', 'doc2']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc1', 'doc2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = open({ contentType: Constants.CONTENT_TYPE_APP_SETTINGS, documentId: 'doc2', isGlobal: true });
|
||||
const action = open({
|
||||
contentType: Constants.CONTENT_TYPE_APP_SETTINGS,
|
||||
documentId: 'doc2',
|
||||
isGlobal: true,
|
||||
});
|
||||
const endingState = editor(startingState, action);
|
||||
const primaryEditor = endingState.editors[Constants.EDITOR_KEY_PRIMARY];
|
||||
expect(primaryEditor.activeDocumentId).toBe('doc2');
|
||||
|
@ -505,15 +535,19 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc1',
|
||||
documents: {
|
||||
'doc1': {},
|
||||
'doc2': {}
|
||||
doc1: {},
|
||||
doc2: {},
|
||||
},
|
||||
recentTabs: ['doc1', 'doc2'],
|
||||
tabOrder: ['doc1', 'doc2']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc1', 'doc2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = open({ contentType: Constants.CONTENT_TYPE_APP_SETTINGS, documentId: 'doc3', isGlobal: true });
|
||||
const action = open({
|
||||
contentType: Constants.CONTENT_TYPE_APP_SETTINGS,
|
||||
documentId: 'doc3',
|
||||
isGlobal: true,
|
||||
});
|
||||
const endingState = editor(startingState, action);
|
||||
const primaryEditor = endingState.editors[Constants.EDITOR_KEY_PRIMARY];
|
||||
expect(primaryEditor.activeDocumentId).toBe('doc3');
|
||||
|
@ -522,7 +556,7 @@ describe('Editor reducer tests', () => {
|
|||
expect(Object.keys(primaryEditor.documents)).toContain('doc3');
|
||||
});
|
||||
|
||||
it('should append new document if current activeDocument isn\'t found', () => {
|
||||
it("should append new document if current activeDocument isn't found", () => {
|
||||
const startingState: EditorState = {
|
||||
...defaultState,
|
||||
activeEditor: Constants.EDITOR_KEY_PRIMARY,
|
||||
|
@ -532,15 +566,19 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc1234',
|
||||
documents: {
|
||||
'doc1': {},
|
||||
'doc2': {}
|
||||
doc1: {},
|
||||
doc2: {},
|
||||
},
|
||||
recentTabs: ['doc1', 'doc2'],
|
||||
tabOrder: ['doc1', 'doc2']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc1', 'doc2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = open({ contentType: Constants.CONTENT_TYPE_APP_SETTINGS, documentId: 'doc3', isGlobal: true });
|
||||
const action = open({
|
||||
contentType: Constants.CONTENT_TYPE_APP_SETTINGS,
|
||||
documentId: 'doc3',
|
||||
isGlobal: true,
|
||||
});
|
||||
const endingState = editor(startingState, action);
|
||||
const primaryEditor = endingState.editors[Constants.EDITOR_KEY_PRIMARY];
|
||||
expect(primaryEditor.activeDocumentId).toBe('doc3');
|
||||
|
@ -561,22 +599,22 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc2',
|
||||
documents: {
|
||||
'doc1': {},
|
||||
'doc2': {}
|
||||
doc1: {},
|
||||
doc2: {},
|
||||
},
|
||||
recentTabs: ['doc1', 'doc2'],
|
||||
tabOrder: ['doc1', 'doc2']
|
||||
tabOrder: ['doc1', 'doc2'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
activeDocumentId: 'doc3',
|
||||
documents: {
|
||||
'doc3': {}
|
||||
doc3: {},
|
||||
},
|
||||
recentTabs: ['doc3'],
|
||||
tabOrder: ['doc3']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc3'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = splitTab(
|
||||
Constants.CONTENT_TYPE_APP_SETTINGS,
|
||||
|
@ -611,14 +649,14 @@ describe('Editor reducer tests', () => {
|
|||
[Constants.EDITOR_KEY_PRIMARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
documents: {
|
||||
'doc1': {},
|
||||
'doc2': {},
|
||||
'doc3': {}
|
||||
doc1: {},
|
||||
doc2: {},
|
||||
doc3: {},
|
||||
},
|
||||
recentTabs: ['doc3', 'doc2', 'doc1'],
|
||||
tabOrder: ['doc1', 'doc2', 'doc3']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc1', 'doc2', 'doc3'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = swapTabs(
|
||||
Constants.EDITOR_KEY_PRIMARY,
|
||||
|
@ -640,23 +678,23 @@ describe('Editor reducer tests', () => {
|
|||
[Constants.EDITOR_KEY_PRIMARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
documents: {
|
||||
'doc1': {},
|
||||
'doc2': {}
|
||||
doc1: {},
|
||||
doc2: {},
|
||||
},
|
||||
recentTabs: ['doc2', 'doc1'],
|
||||
tabOrder: ['doc1', 'doc2']
|
||||
tabOrder: ['doc1', 'doc2'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
activeDocumentId: 'doc3',
|
||||
documents: {
|
||||
'doc3': {},
|
||||
'doc4': {}
|
||||
doc3: {},
|
||||
doc4: {},
|
||||
},
|
||||
recentTabs: ['doc4', 'doc3'],
|
||||
tabOrder: ['doc3', 'doc4']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc3', 'doc4'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = swapTabs(
|
||||
Constants.EDITOR_KEY_SECONDARY,
|
||||
|
@ -666,7 +704,8 @@ describe('Editor reducer tests', () => {
|
|||
);
|
||||
const endingState = editor(startingState, action);
|
||||
const primaryEditor = endingState.editors[Constants.EDITOR_KEY_PRIMARY];
|
||||
const secondaryEditor = endingState.editors[Constants.EDITOR_KEY_SECONDARY];
|
||||
const secondaryEditor =
|
||||
endingState.editors[Constants.EDITOR_KEY_SECONDARY];
|
||||
expect(endingState.activeEditor).toBe(Constants.EDITOR_KEY_SECONDARY);
|
||||
|
||||
expect(Object.keys(primaryEditor.documents)).toContain('doc3');
|
||||
|
@ -688,22 +727,22 @@ describe('Editor reducer tests', () => {
|
|||
[Constants.EDITOR_KEY_PRIMARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
documents: {
|
||||
'doc1': {},
|
||||
'doc2': {}
|
||||
doc1: {},
|
||||
doc2: {},
|
||||
},
|
||||
recentTabs: ['doc2', 'doc1'],
|
||||
tabOrder: ['doc1', 'doc2']
|
||||
tabOrder: ['doc1', 'doc2'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
activeDocumentId: 'doc3',
|
||||
documents: {
|
||||
'doc3': {}
|
||||
doc3: {},
|
||||
},
|
||||
recentTabs: ['doc3'],
|
||||
tabOrder: ['doc3']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc3'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = swapTabs(
|
||||
Constants.EDITOR_KEY_SECONDARY,
|
||||
|
@ -725,21 +764,21 @@ describe('Editor reducer tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc1',
|
||||
documents: {
|
||||
'doc1': {}
|
||||
doc1: {},
|
||||
},
|
||||
recentTabs: ['doc1'],
|
||||
tabOrder: ['doc1']
|
||||
tabOrder: ['doc1'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
activeDocumentId: 'doc2',
|
||||
documents: {
|
||||
'doc2': {}
|
||||
doc2: {},
|
||||
},
|
||||
recentTabs: ['doc2'],
|
||||
tabOrder: ['doc2']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = swapTabs(
|
||||
Constants.EDITOR_KEY_PRIMARY,
|
||||
|
@ -749,7 +788,8 @@ describe('Editor reducer tests', () => {
|
|||
);
|
||||
const endingState = editor(startingState, action);
|
||||
const primaryEditor = endingState.editors[Constants.EDITOR_KEY_PRIMARY];
|
||||
const secondaryEditor = endingState.editors[Constants.EDITOR_KEY_SECONDARY];
|
||||
const secondaryEditor =
|
||||
endingState.editors[Constants.EDITOR_KEY_SECONDARY];
|
||||
expect(endingState.activeEditor).toBe(Constants.EDITOR_KEY_PRIMARY);
|
||||
|
||||
expect(primaryEditor.activeDocumentId).toBe('doc2');
|
||||
|
@ -768,32 +808,34 @@ describe('Editor reducer tests', () => {
|
|||
it('should add a doc to the list', () => {
|
||||
const startingState: EditorState = {
|
||||
...defaultState,
|
||||
docsWithPendingChanges: [
|
||||
'doc1',
|
||||
'doc2',
|
||||
'doc3'
|
||||
]
|
||||
docsWithPendingChanges: ['doc1', 'doc2', 'doc3'],
|
||||
};
|
||||
const action1 = addDocPendingChange('doc4');
|
||||
const endingState1 = editor(startingState, action1);
|
||||
|
||||
expect(endingState1.docsWithPendingChanges).toEqual(['doc1', 'doc2', 'doc3', 'doc4']);
|
||||
expect(endingState1.docsWithPendingChanges).toEqual([
|
||||
'doc1',
|
||||
'doc2',
|
||||
'doc3',
|
||||
'doc4',
|
||||
]);
|
||||
|
||||
// shouldn't allow duplicates
|
||||
const action2 = addDocPendingChange('doc4');
|
||||
const endingState2 = editor(endingState1, action2);
|
||||
|
||||
expect(endingState2.docsWithPendingChanges).toEqual(['doc1', 'doc2', 'doc3', 'doc4']);
|
||||
expect(endingState2.docsWithPendingChanges).toEqual([
|
||||
'doc1',
|
||||
'doc2',
|
||||
'doc3',
|
||||
'doc4',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should remove a doc from the list', () => {
|
||||
const startingState: EditorState = {
|
||||
...defaultState,
|
||||
docsWithPendingChanges: [
|
||||
'doc1',
|
||||
'doc2',
|
||||
'doc3'
|
||||
]
|
||||
docsWithPendingChanges: ['doc1', 'doc2', 'doc3'],
|
||||
};
|
||||
const action = removeDocPendingChange('doc2');
|
||||
const endingState = editor(startingState, action);
|
||||
|
@ -822,34 +864,50 @@ describe('Editor reducer utility function tests', () => {
|
|||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
activeDocumentId: 'doc1',
|
||||
documents: {
|
||||
'doc1': {}
|
||||
doc1: {},
|
||||
},
|
||||
tabOrder: ['doc1'],
|
||||
recentTabs: ['doc1']
|
||||
}
|
||||
}
|
||||
recentTabs: ['doc1'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newPrimaryEditor: Editor = {
|
||||
activeDocumentId: 'doc2',
|
||||
documents: {
|
||||
'doc2': {}
|
||||
doc2: {},
|
||||
},
|
||||
tabOrder: ['doc2'],
|
||||
recentTabs: ['doc2']
|
||||
recentTabs: ['doc2'],
|
||||
};
|
||||
|
||||
const endingState = setNewPrimaryEditor(newPrimaryEditor, startingState);
|
||||
expect(endingState.activeEditor).toBe(Constants.EDITOR_KEY_PRIMARY);
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].activeDocumentId).toEqual('doc2');
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder).toEqual(['doc2']);
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].recentTabs).toEqual(['doc2']);
|
||||
expect(Object.keys(endingState.editors[Constants.EDITOR_KEY_PRIMARY].documents)).toContain('doc2');
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_PRIMARY].activeDocumentId
|
||||
).toEqual('doc2');
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder).toEqual([
|
||||
'doc2',
|
||||
]);
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_PRIMARY].recentTabs
|
||||
).toEqual(['doc2']);
|
||||
expect(
|
||||
Object.keys(endingState.editors[Constants.EDITOR_KEY_PRIMARY].documents)
|
||||
).toContain('doc2');
|
||||
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].activeDocumentId).toBe(null);
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].documents).toEqual({});
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].recentTabs).toEqual([]);
|
||||
expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].tabOrder).toEqual([]);
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_SECONDARY].activeDocumentId
|
||||
).toBe(null);
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_SECONDARY].documents
|
||||
).toEqual({});
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_SECONDARY].recentTabs
|
||||
).toEqual([]);
|
||||
expect(
|
||||
endingState.editors[Constants.EDITOR_KEY_SECONDARY].tabOrder
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('setActiveEditor() functionality', () => {
|
||||
|
@ -863,13 +921,23 @@ describe('Editor reducer utility function tests', () => {
|
|||
activeDocumentId: 'testing',
|
||||
documents: {},
|
||||
tabOrder: ['testing'],
|
||||
recentTabs: ['testing']
|
||||
recentTabs: ['testing'],
|
||||
};
|
||||
const newState = setEditorState(Constants.EDITOR_KEY_PRIMARY, updatedEditor, defaultState);
|
||||
const newState = setEditorState(
|
||||
Constants.EDITOR_KEY_PRIMARY,
|
||||
updatedEditor,
|
||||
defaultState
|
||||
);
|
||||
expect(newState).not.toBe(defaultState);
|
||||
expect(newState.editors[Constants.EDITOR_KEY_PRIMARY].activeDocumentId).toBe('testing');
|
||||
expect(newState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder).toEqual(['testing']);
|
||||
expect(newState.editors[Constants.EDITOR_KEY_PRIMARY].recentTabs).toEqual(['testing']);
|
||||
expect(
|
||||
newState.editors[Constants.EDITOR_KEY_PRIMARY].activeDocumentId
|
||||
).toBe('testing');
|
||||
expect(newState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder).toEqual([
|
||||
'testing',
|
||||
]);
|
||||
expect(newState.editors[Constants.EDITOR_KEY_PRIMARY].recentTabs).toEqual([
|
||||
'testing',
|
||||
]);
|
||||
});
|
||||
|
||||
describe('removeDocumentFromTabGroup() functionality', () => {
|
||||
|
@ -879,19 +947,16 @@ describe('Editor reducer utility function tests', () => {
|
|||
activeDocumentId: docToRemove,
|
||||
documents: {
|
||||
[docToRemove]: {},
|
||||
'doc2': {}
|
||||
doc2: {},
|
||||
},
|
||||
tabOrder: [
|
||||
docToRemove,
|
||||
'doc2'
|
||||
],
|
||||
recentTabs: [
|
||||
docToRemove,
|
||||
'doc2'
|
||||
]
|
||||
tabOrder: [docToRemove, 'doc2'],
|
||||
recentTabs: [docToRemove, 'doc2'],
|
||||
};
|
||||
|
||||
const modifiedEditor = removeDocumentFromTabGroup(tempEditor, docToRemove);
|
||||
const modifiedEditor = removeDocumentFromTabGroup(
|
||||
tempEditor,
|
||||
docToRemove
|
||||
);
|
||||
expect(modifiedEditor).not.toBe(tempEditor);
|
||||
expect(modifiedEditor.activeDocumentId).toBe('doc2');
|
||||
expect(modifiedEditor.recentTabs).not.toContain(docToRemove);
|
||||
|
@ -903,14 +968,10 @@ describe('Editor reducer utility function tests', () => {
|
|||
const tempEditor: Editor = {
|
||||
activeDocumentId: 'doc1',
|
||||
documents: {
|
||||
'doc1': {}
|
||||
doc1: {},
|
||||
},
|
||||
tabOrder: [
|
||||
'doc1'
|
||||
],
|
||||
recentTabs: [
|
||||
'doc1'
|
||||
]
|
||||
tabOrder: ['doc1'],
|
||||
recentTabs: ['doc1'],
|
||||
};
|
||||
|
||||
const modifiedEditor = removeDocumentFromTabGroup(tempEditor, 'doc1');
|
||||
|
@ -933,18 +994,18 @@ describe('Editor reducer utility function tests', () => {
|
|||
activeDocumentId: null,
|
||||
documents: {},
|
||||
recentTabs: [],
|
||||
tabOrder: []
|
||||
tabOrder: [],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
activeDocumentId: 'doc2',
|
||||
documents: {
|
||||
'doc2': {}
|
||||
doc2: {},
|
||||
},
|
||||
recentTabs: ['doc2'],
|
||||
tabOrder: ['doc2']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const endState = fixupTabGroups(startingState);
|
||||
const primaryEditor = endState.editors[Constants.EDITOR_KEY_PRIMARY];
|
||||
|
@ -972,19 +1033,19 @@ describe('Editor reducer utility function tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc1',
|
||||
documents: {
|
||||
'doc1': {}
|
||||
doc1: {},
|
||||
},
|
||||
recentTabs: ['doc1'],
|
||||
tabOrder: ['doc1']
|
||||
tabOrder: ['doc1'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
activeDocumentId: null,
|
||||
documents: {},
|
||||
recentTabs: [],
|
||||
tabOrder: []
|
||||
}
|
||||
}
|
||||
tabOrder: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
const endState = fixupTabGroups(startingState);
|
||||
expect(endState.activeEditor).toBe(Constants.EDITOR_KEY_PRIMARY);
|
||||
|
@ -1000,21 +1061,21 @@ describe('Editor reducer utility function tests', () => {
|
|||
...defaultState.editors[Constants.EDITOR_KEY_PRIMARY],
|
||||
activeDocumentId: 'doc1',
|
||||
documents: {
|
||||
'doc1': {}
|
||||
doc1: {},
|
||||
},
|
||||
recentTabs: ['doc1'],
|
||||
tabOrder: ['doc1']
|
||||
tabOrder: ['doc1'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
...defaultState.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
activeDocumentId: 'doc2',
|
||||
documents: {
|
||||
'doc2': {}
|
||||
doc2: {},
|
||||
},
|
||||
recentTabs: ['doc2'],
|
||||
tabOrder: ['doc2']
|
||||
}
|
||||
}
|
||||
tabOrder: ['doc2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
const endState = fixupTabGroups(startingState);
|
||||
expect(endState).toEqual(startingState);
|
||||
|
@ -1029,15 +1090,15 @@ describe('Editor reducer utility function tests', () => {
|
|||
[Constants.EDITOR_KEY_PRIMARY]: {
|
||||
activeDocumentId: 'doc2',
|
||||
documents: {
|
||||
'doc1': {},
|
||||
'doc2': {},
|
||||
'doc3': {}
|
||||
doc1: {},
|
||||
doc2: {},
|
||||
doc3: {},
|
||||
},
|
||||
recentTabs: ['doc2', 'doc1', 'doc3'],
|
||||
tabOrder: ['doc1', 'doc2', 'doc3']
|
||||
tabOrder: ['doc1', 'doc2', 'doc3'],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {}
|
||||
}
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {},
|
||||
},
|
||||
};
|
||||
|
||||
const tabIdToDrop = 'doc1';
|
||||
|
@ -1074,16 +1135,16 @@ function initializeDefaultState() {
|
|||
activeDocumentId: null,
|
||||
documents: {},
|
||||
tabOrder: [],
|
||||
recentTabs: []
|
||||
recentTabs: [],
|
||||
},
|
||||
[Constants.EDITOR_KEY_SECONDARY]: {
|
||||
activeDocumentId: null,
|
||||
documents: {},
|
||||
tabOrder: [],
|
||||
recentTabs: []
|
||||
}
|
||||
recentTabs: [],
|
||||
},
|
||||
},
|
||||
docsWithPendingChanges: []
|
||||
docsWithPendingChanges: [],
|
||||
};
|
||||
defaultState = deepCopySlow(DEFAULT_STATE);
|
||||
}
|
||||
|
|
|
@ -32,9 +32,10 @@
|
|||
//
|
||||
|
||||
import { deepCopySlow } from '@bfemulator/app-shared';
|
||||
|
||||
import * as Constants from '../../constants';
|
||||
import { EditorAction, EditorActions } from '../action/editorActions';
|
||||
import { BotAction } from '../action/botActions';
|
||||
import { EditorAction, EditorActions } from '../action/editorActions';
|
||||
import { getOtherTabGroup, tabGroupHasDocuments } from '../editorHelpers';
|
||||
|
||||
export interface EditorState {
|
||||
|
@ -72,12 +73,15 @@ const DEFAULT_STATE: EditorState = {
|
|||
draggingTab: false,
|
||||
editors: {
|
||||
[Constants.EDITOR_KEY_PRIMARY]: getNewEditor(),
|
||||
[Constants.EDITOR_KEY_SECONDARY]: getNewEditor()
|
||||
[Constants.EDITOR_KEY_SECONDARY]: getNewEditor(),
|
||||
},
|
||||
docsWithPendingChanges: []
|
||||
docsWithPendingChanges: [],
|
||||
};
|
||||
|
||||
export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction | BotAction): EditorState => {
|
||||
export const editor = (
|
||||
state: EditorState = DEFAULT_STATE,
|
||||
action: EditorAction | BotAction
|
||||
): EditorState => {
|
||||
Object.freeze(state);
|
||||
|
||||
switch (action.type) {
|
||||
|
@ -87,11 +91,14 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
/** if the tab is being appended to the end of its own editor, just re-adjust tab order */
|
||||
if (srcEditorKey === destEditorKey) {
|
||||
let tabOrder = [...state.editors[srcEditorKey].tabOrder];
|
||||
tabOrder = [...tabOrder.filter(docId => docId !== action.payload.documentId), action.payload.documentId];
|
||||
tabOrder = [
|
||||
...tabOrder.filter(docId => docId !== action.payload.documentId),
|
||||
action.payload.documentId,
|
||||
];
|
||||
|
||||
let editorState: Editor = {
|
||||
const editorState: Editor = {
|
||||
...state.editors[srcEditorKey],
|
||||
tabOrder: tabOrder
|
||||
tabOrder,
|
||||
};
|
||||
state = setEditorState(srcEditorKey, editorState, state);
|
||||
state = setDraggingTab(false, state);
|
||||
|
@ -102,28 +109,44 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
* if the tab is being appended to another editor,
|
||||
* we need to modify both editors' docs, recent tabs, and tab order
|
||||
*/
|
||||
const docToAppend = state.editors[srcEditorKey].documents[action.payload.documentId];
|
||||
const docToAppend =
|
||||
state.editors[srcEditorKey].documents[action.payload.documentId];
|
||||
|
||||
// remove any trace of document from source editor
|
||||
const srcEditor = removeDocumentFromTabGroup(state.editors[srcEditorKey], action.payload.documentId);
|
||||
const srcEditor = removeDocumentFromTabGroup(
|
||||
state.editors[srcEditorKey],
|
||||
action.payload.documentId
|
||||
);
|
||||
|
||||
// add the tab to the dest editor
|
||||
const destTabOrder = [...state.editors[destEditorKey].tabOrder, action.payload.documentId];
|
||||
const destRecentTabs = [...state.editors[destEditorKey].recentTabs, action.payload.documentId];
|
||||
const destDocs = Object.assign({}, state.editors[destEditorKey].documents);
|
||||
const destTabOrder = [
|
||||
...state.editors[destEditorKey].tabOrder,
|
||||
action.payload.documentId,
|
||||
];
|
||||
const destRecentTabs = [
|
||||
...state.editors[destEditorKey].recentTabs,
|
||||
action.payload.documentId,
|
||||
];
|
||||
const destDocs = { ...state.editors[destEditorKey].documents };
|
||||
destDocs[action.payload.documentId] = docToAppend;
|
||||
|
||||
const destEditor: Editor = {
|
||||
...state.editors[destEditorKey],
|
||||
documents: destDocs,
|
||||
recentTabs: destRecentTabs,
|
||||
tabOrder: destTabOrder
|
||||
tabOrder: destTabOrder,
|
||||
};
|
||||
|
||||
if (!tabGroupHasDocuments(srcEditor) && srcEditorKey === Constants.EDITOR_KEY_PRIMARY) {
|
||||
if (
|
||||
!tabGroupHasDocuments(srcEditor) &&
|
||||
srcEditorKey === Constants.EDITOR_KEY_PRIMARY
|
||||
) {
|
||||
state = setNewPrimaryEditor(destEditor, state);
|
||||
} else {
|
||||
state = setActiveEditor(!tabGroupHasDocuments(srcEditor) ? destEditorKey : state.activeEditor, state);
|
||||
state = setActiveEditor(
|
||||
!tabGroupHasDocuments(srcEditor) ? destEditorKey : state.activeEditor,
|
||||
state
|
||||
);
|
||||
state = setEditorState(srcEditorKey, srcEditor, state);
|
||||
state = setEditorState(destEditorKey, destEditor, state);
|
||||
}
|
||||
|
@ -138,11 +161,17 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
const { editorKey } = action.payload;
|
||||
|
||||
// remove any trace of document from editor
|
||||
const editor1 = removeDocumentFromTabGroup(state.editors[editorKey], action.payload.documentId);
|
||||
const editor1 = removeDocumentFromTabGroup(
|
||||
state.editors[editorKey],
|
||||
action.payload.documentId
|
||||
);
|
||||
|
||||
// close empty editor if there is another one able to take its place
|
||||
const newPrimaryEditorKey = getOtherTabGroup(editorKey);
|
||||
if (!tabGroupHasDocuments(editor1) && state.editors[newPrimaryEditorKey]) {
|
||||
if (
|
||||
!tabGroupHasDocuments(editor1) &&
|
||||
state.editors[newPrimaryEditorKey]
|
||||
) {
|
||||
// if the editor being closed is the primary editor, have the secondary editor become the primary
|
||||
const tmp: Editor = deepCopySlow(state.editors[newPrimaryEditorKey]);
|
||||
state = setNewPrimaryEditor(tmp, state);
|
||||
|
@ -157,14 +186,14 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
return DEFAULT_STATE;
|
||||
} else {
|
||||
let newState: EditorState = {
|
||||
...state
|
||||
...state,
|
||||
};
|
||||
|
||||
for (let key in state.editors) {
|
||||
for (const key in state.editors) {
|
||||
if (!state.editors.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
let tabGroup = state.editors[key];
|
||||
const tabGroup = state.editors[key];
|
||||
if (tabGroup) {
|
||||
let newTabOrder = [...tabGroup.tabOrder];
|
||||
let newRecentTabs = [...tabGroup.recentTabs];
|
||||
|
@ -175,24 +204,28 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
if (document.isGlobal) {
|
||||
newDocs[documentId] = document;
|
||||
} else {
|
||||
newTabOrder = newTabOrder.filter(documentIdArg => documentIdArg !== documentId);
|
||||
newRecentTabs = newRecentTabs.filter(documentIdArg => documentIdArg !== documentId);
|
||||
newTabOrder = newTabOrder.filter(
|
||||
documentIdArg => documentIdArg !== documentId
|
||||
);
|
||||
newRecentTabs = newRecentTabs.filter(
|
||||
documentIdArg => documentIdArg !== documentId
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let newTabGroup: Editor = {
|
||||
const newTabGroup: Editor = {
|
||||
activeDocumentId: newRecentTabs[0] || null,
|
||||
documents: newDocs,
|
||||
recentTabs: newRecentTabs,
|
||||
tabOrder: newTabOrder
|
||||
tabOrder: newTabOrder,
|
||||
};
|
||||
|
||||
newState = {
|
||||
...newState,
|
||||
editors: {
|
||||
...newState.editors,
|
||||
[key]: newTabGroup
|
||||
}
|
||||
[key]: newTabGroup,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -207,15 +240,18 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
const otherTabGroup = getOtherTabGroup(editorKey);
|
||||
|
||||
// if the document is already in another tab group, focus that one
|
||||
if (tabGroupHasDocuments(state.editors[otherTabGroup])
|
||||
&& state.editors[otherTabGroup].documents[action.payload.documentId]) {
|
||||
const recentTabs = [...state.editors[otherTabGroup].recentTabs]
|
||||
.filter(docId => docId !== action.payload.documentId);
|
||||
if (
|
||||
tabGroupHasDocuments(state.editors[otherTabGroup]) &&
|
||||
state.editors[otherTabGroup].documents[action.payload.documentId]
|
||||
) {
|
||||
const recentTabs = [...state.editors[otherTabGroup].recentTabs].filter(
|
||||
docId => docId !== action.payload.documentId
|
||||
);
|
||||
recentTabs.unshift(action.payload.documentId);
|
||||
const tabGroupState: Editor = {
|
||||
...state.editors[otherTabGroup],
|
||||
activeDocumentId: action.payload.documentId,
|
||||
recentTabs
|
||||
recentTabs,
|
||||
};
|
||||
state = setEditorState(otherTabGroup, tabGroupState, state);
|
||||
state = setActiveEditor(otherTabGroup, state);
|
||||
|
@ -226,19 +262,30 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
if (state.editors[editorKey].documents[action.payload.documentId]) {
|
||||
newTabOrder = [...state.editors[editorKey].tabOrder];
|
||||
} else {
|
||||
const activeDocumentId = state.editors[state.activeEditor].activeDocumentId;
|
||||
const activeIndex = state.editors[editorKey].tabOrder.indexOf(activeDocumentId);
|
||||
const activeDocumentId =
|
||||
state.editors[state.activeEditor].activeDocumentId;
|
||||
const activeIndex = state.editors[editorKey].tabOrder.indexOf(
|
||||
activeDocumentId
|
||||
);
|
||||
if (activeIndex != null && activeIndex !== -1) {
|
||||
state.editors[editorKey].tabOrder.splice(activeIndex + 1, 0, action.payload.documentId);
|
||||
state.editors[editorKey].tabOrder.splice(
|
||||
activeIndex + 1,
|
||||
0,
|
||||
action.payload.documentId
|
||||
);
|
||||
newTabOrder = [...state.editors[editorKey].tabOrder];
|
||||
} else {
|
||||
newTabOrder = [...state.editors[editorKey].tabOrder, action.payload.documentId];
|
||||
newTabOrder = [
|
||||
...state.editors[editorKey].tabOrder,
|
||||
action.payload.documentId,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// move document to top of recent tabs
|
||||
const newRecentTabs = [...state.editors[editorKey].recentTabs]
|
||||
.filter(docId => docId !== action.payload.documentId);
|
||||
const newRecentTabs = [...state.editors[editorKey].recentTabs].filter(
|
||||
docId => docId !== action.payload.documentId
|
||||
);
|
||||
newRecentTabs.unshift(action.payload.documentId);
|
||||
|
||||
// add document to tab group
|
||||
|
@ -256,7 +303,7 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
activeDocumentId: action.payload.documentId,
|
||||
documents: newDocs,
|
||||
recentTabs: newRecentTabs,
|
||||
tabOrder: newTabOrder
|
||||
tabOrder: newTabOrder,
|
||||
};
|
||||
state = setEditorState(editorKey, editorState, state);
|
||||
state = setActiveEditor(editorKey, state);
|
||||
|
@ -291,14 +338,19 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
|
||||
case EditorActions.setActiveTab: {
|
||||
Constants.EditorKeys.forEach(editorKey => {
|
||||
if (state.editors[editorKey] && state.editors[editorKey].documents[action.payload.documentId]) {
|
||||
const recentTabs = state.editors[editorKey].recentTabs.filter(tabId => tabId !== action.payload.documentId);
|
||||
if (
|
||||
state.editors[editorKey] &&
|
||||
state.editors[editorKey].documents[action.payload.documentId]
|
||||
) {
|
||||
const recentTabs = state.editors[editorKey].recentTabs.filter(
|
||||
tabId => tabId !== action.payload.documentId
|
||||
);
|
||||
recentTabs.unshift(action.payload.documentId);
|
||||
|
||||
const editorState = {
|
||||
...state.editors[editorKey],
|
||||
activeDocumentId: action.payload.documentId,
|
||||
recentTabs
|
||||
recentTabs,
|
||||
};
|
||||
state = setEditorState(editorKey, editorState, state);
|
||||
state = setActiveEditor(editorKey, state);
|
||||
|
@ -309,14 +361,17 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
|
||||
case EditorActions.setDirtyFlag: {
|
||||
Constants.EditorKeys.forEach(editorKey => {
|
||||
if (state.editors[editorKey] && state.editors[editorKey].documents[action.payload.documentId]) {
|
||||
if (
|
||||
state.editors[editorKey] &&
|
||||
state.editors[editorKey].documents[action.payload.documentId]
|
||||
) {
|
||||
const newDocs = deepCopySlow(state.editors[editorKey].documents);
|
||||
const docToSet = newDocs[action.payload.documentId];
|
||||
docToSet.dirty = action.payload.dirty;
|
||||
|
||||
const editorState: Editor = {
|
||||
...state.editors[editorKey],
|
||||
documents: newDocs
|
||||
documents: newDocs,
|
||||
};
|
||||
state = setEditorState(editorKey, editorState, state);
|
||||
}
|
||||
|
@ -328,14 +383,19 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
const { srcEditorKey } = action.payload;
|
||||
const { destEditorKey } = action.payload;
|
||||
|
||||
const docToAppend = state.editors[srcEditorKey].documents[action.payload.documentId];
|
||||
const docToAppend =
|
||||
state.editors[srcEditorKey].documents[action.payload.documentId];
|
||||
|
||||
// remove any trace of document from source editor
|
||||
const srcEditor = removeDocumentFromTabGroup(state.editors[srcEditorKey], action.payload.documentId);
|
||||
const srcEditor = removeDocumentFromTabGroup(
|
||||
state.editors[srcEditorKey],
|
||||
action.payload.documentId
|
||||
);
|
||||
|
||||
// add the document to the dest editor
|
||||
const destEditor: Editor = state.editors[destEditorKey] ?
|
||||
deepCopySlow(state.editors[destEditorKey]) : getNewEditor();
|
||||
const destEditor: Editor = state.editors[destEditorKey]
|
||||
? deepCopySlow(state.editors[destEditorKey])
|
||||
: getNewEditor();
|
||||
const destTabOrder = [...destEditor.tabOrder, action.payload.documentId];
|
||||
const destRecentTabs = [...destEditor.recentTabs];
|
||||
destRecentTabs.unshift(action.payload.documentId);
|
||||
|
@ -362,16 +422,20 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
if (srcEditorKey === destEditorKey) {
|
||||
// only change tab order
|
||||
const tabOrder = [...state.editors[srcEditorKey].tabOrder];
|
||||
const srcTabIndex = tabOrder.findIndex(docId => docId === action.payload.srcTabId);
|
||||
const destTabIndex1 = tabOrder.findIndex(docId => docId === action.payload.destTabId);
|
||||
const srcTabIndex = tabOrder.findIndex(
|
||||
docId => docId === action.payload.srcTabId
|
||||
);
|
||||
const destTabIndex1 = tabOrder.findIndex(
|
||||
docId => docId === action.payload.destTabId
|
||||
);
|
||||
|
||||
const destTab = tabOrder[destTabIndex1];
|
||||
tabOrder[destTabIndex1] = tabOrder[srcTabIndex];
|
||||
tabOrder[srcTabIndex] = destTab;
|
||||
|
||||
let editorState = {
|
||||
const editorState = {
|
||||
...state.editors[srcEditorKey],
|
||||
tabOrder
|
||||
tabOrder,
|
||||
};
|
||||
|
||||
state = setEditorState(srcEditorKey, editorState, state);
|
||||
|
@ -379,26 +443,44 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
}
|
||||
|
||||
/** swapping tab into a different tab group */
|
||||
const docToSwap = state.editors[srcEditorKey].documents[action.payload.srcTabId];
|
||||
const docToSwap =
|
||||
state.editors[srcEditorKey].documents[action.payload.srcTabId];
|
||||
|
||||
// remove any trace of document from source editor
|
||||
const srcEditor = removeDocumentFromTabGroup(state.editors[srcEditorKey], action.payload.srcTabId);
|
||||
const srcEditor = removeDocumentFromTabGroup(
|
||||
state.editors[srcEditorKey],
|
||||
action.payload.srcTabId
|
||||
);
|
||||
|
||||
// add the document to the destination tab group
|
||||
const destEditor: Editor = deepCopySlow(state.editors[destEditorKey]);
|
||||
destEditor.documents[action.payload.srcTabId] = docToSwap;
|
||||
const destRecentTabs = [...destEditor.recentTabs, action.payload.srcTabId];
|
||||
const destRecentTabs = [
|
||||
...destEditor.recentTabs,
|
||||
action.payload.srcTabId,
|
||||
];
|
||||
destEditor.recentTabs = destRecentTabs;
|
||||
// insert before the destination tab's position
|
||||
const destTabIndex = destEditor.tabOrder.findIndex(docId => docId === action.payload.destTabId);
|
||||
const destTabOrder = [...destEditor.tabOrder
|
||||
.splice(0, destTabIndex + 1), action.payload.srcTabId, ...destEditor.tabOrder];
|
||||
const destTabIndex = destEditor.tabOrder.findIndex(
|
||||
docId => docId === action.payload.destTabId
|
||||
);
|
||||
const destTabOrder = [
|
||||
...destEditor.tabOrder.splice(0, destTabIndex + 1),
|
||||
action.payload.srcTabId,
|
||||
...destEditor.tabOrder,
|
||||
];
|
||||
destEditor.tabOrder = destTabOrder;
|
||||
|
||||
if (!tabGroupHasDocuments(srcEditor) && srcEditorKey === Constants.EDITOR_KEY_PRIMARY) {
|
||||
if (
|
||||
!tabGroupHasDocuments(srcEditor) &&
|
||||
srcEditorKey === Constants.EDITOR_KEY_PRIMARY
|
||||
) {
|
||||
state = setNewPrimaryEditor(destEditor, state);
|
||||
} else {
|
||||
state = setActiveEditor(!tabGroupHasDocuments(srcEditor) ? destEditorKey : state.activeEditor, state);
|
||||
state = setActiveEditor(
|
||||
!tabGroupHasDocuments(srcEditor) ? destEditorKey : state.activeEditor,
|
||||
state
|
||||
);
|
||||
state = setEditorState(srcEditorKey, srcEditor, state);
|
||||
state = setEditorState(destEditorKey, destEditor, state);
|
||||
}
|
||||
|
@ -411,16 +493,20 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
}
|
||||
|
||||
case EditorActions.addDocPendingChange: {
|
||||
let docsPendingChange = [
|
||||
...state.docsWithPendingChanges.filter(d => d !== action.payload.documentId),
|
||||
action.payload.documentId
|
||||
const docsPendingChange = [
|
||||
...state.docsWithPendingChanges.filter(
|
||||
d => d !== action.payload.documentId
|
||||
),
|
||||
action.payload.documentId,
|
||||
];
|
||||
state = setDocsWithPendingChanges(docsPendingChange, state);
|
||||
break;
|
||||
}
|
||||
|
||||
case EditorActions.removeDocPendingChange: {
|
||||
let docsPendingChange = [...state.docsWithPendingChanges].filter(d => d !== action.payload.documentId);
|
||||
const docsPendingChange = [...state.docsWithPendingChanges].filter(
|
||||
d => d !== action.payload.documentId
|
||||
);
|
||||
state = setDocsWithPendingChanges(docsPendingChange, state);
|
||||
break;
|
||||
}
|
||||
|
@ -438,8 +524,12 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction
|
|||
const primaryDocs = { [tabToIsolate]: docToIsolate };
|
||||
|
||||
// move all documents but the one being dropped to the secondary tab group
|
||||
const secondaryTabOrder = state.editors[primary].tabOrder.filter(tabId => tabId !== tabToIsolate);
|
||||
const secondaryRecentTabs = state.editors[primary].recentTabs.filter(tabId => tabId !== tabToIsolate);
|
||||
const secondaryTabOrder = state.editors[primary].tabOrder.filter(
|
||||
tabId => tabId !== tabToIsolate
|
||||
);
|
||||
const secondaryRecentTabs = state.editors[primary].recentTabs.filter(
|
||||
tabId => tabId !== tabToIsolate
|
||||
);
|
||||
const secondaryDocs = state.editors[primary].documents;
|
||||
delete secondaryDocs[tabToIsolate];
|
||||
|
||||
|
@ -472,47 +562,67 @@ function getNewEditor(): Editor {
|
|||
activeDocumentId: null,
|
||||
documents: {},
|
||||
recentTabs: [],
|
||||
tabOrder: []
|
||||
tabOrder: [],
|
||||
};
|
||||
}
|
||||
|
||||
/** Removes all trace of a document from a tab group and returns
|
||||
* the updated state, or a new editor if the tab group has no documents (empty)
|
||||
*/
|
||||
export function removeDocumentFromTabGroup(tabGroup: Editor, documentId: string): Editor {
|
||||
const newTabOrder = [...tabGroup.tabOrder].filter(docId => docId !== documentId);
|
||||
const newRecentTabs = [...tabGroup.recentTabs].filter(docId => docId !== documentId);
|
||||
const newDocs = Object.assign({}, tabGroup.documents);
|
||||
export function removeDocumentFromTabGroup(
|
||||
tabGroup: Editor,
|
||||
documentId: string
|
||||
): Editor {
|
||||
const newTabOrder = [...tabGroup.tabOrder].filter(
|
||||
docId => docId !== documentId
|
||||
);
|
||||
const newRecentTabs = [...tabGroup.recentTabs].filter(
|
||||
docId => docId !== documentId
|
||||
);
|
||||
const newDocs = { ...tabGroup.documents };
|
||||
delete newDocs[documentId];
|
||||
const newActiveDocumentId = newRecentTabs[0] || null;
|
||||
|
||||
const newTabGroup: Editor = Object.keys(newDocs).length === 0 ? getNewEditor() : {
|
||||
...tabGroup,
|
||||
activeDocumentId: newActiveDocumentId,
|
||||
documents: newDocs,
|
||||
recentTabs: newRecentTabs,
|
||||
tabOrder: newTabOrder
|
||||
};
|
||||
const newTabGroup: Editor =
|
||||
Object.keys(newDocs).length === 0
|
||||
? getNewEditor()
|
||||
: {
|
||||
...tabGroup,
|
||||
activeDocumentId: newActiveDocumentId,
|
||||
documents: newDocs,
|
||||
recentTabs: newRecentTabs,
|
||||
tabOrder: newTabOrder,
|
||||
};
|
||||
return newTabGroup;
|
||||
}
|
||||
|
||||
export function setEditorState(editorKey: string, editorState: Editor, state: EditorState): EditorState {
|
||||
let newState = deepCopySlow(state);
|
||||
export function setEditorState(
|
||||
editorKey: string,
|
||||
editorState: Editor,
|
||||
state: EditorState
|
||||
): EditorState {
|
||||
const newState = deepCopySlow(state);
|
||||
|
||||
newState.editors[editorKey] = editorState;
|
||||
return newState;
|
||||
}
|
||||
|
||||
export function setActiveEditor(editorKey: string, state: EditorState): EditorState {
|
||||
let newState = deepCopySlow(state);
|
||||
export function setActiveEditor(
|
||||
editorKey: string,
|
||||
state: EditorState
|
||||
): EditorState {
|
||||
const newState = deepCopySlow(state);
|
||||
|
||||
newState.activeEditor = editorKey;
|
||||
return newState;
|
||||
}
|
||||
|
||||
/** Sets a new primary editor, and resets the secondary editor */
|
||||
export function setNewPrimaryEditor(newPrimaryEditor: Editor, state: EditorState): EditorState {
|
||||
let newState = deepCopySlow(state);
|
||||
export function setNewPrimaryEditor(
|
||||
newPrimaryEditor: Editor,
|
||||
state: EditorState
|
||||
): EditorState {
|
||||
const newState = deepCopySlow(state);
|
||||
|
||||
newState.editors[Constants.EDITOR_KEY_SECONDARY] = getNewEditor();
|
||||
newState.editors[Constants.EDITOR_KEY_PRIMARY] = newPrimaryEditor;
|
||||
|
@ -520,8 +630,11 @@ export function setNewPrimaryEditor(newPrimaryEditor: Editor, state: EditorState
|
|||
return newState;
|
||||
}
|
||||
|
||||
export function setDraggingTab(dragging: boolean, state: EditorState): EditorState {
|
||||
let newState = deepCopySlow(state);
|
||||
export function setDraggingTab(
|
||||
dragging: boolean,
|
||||
state: EditorState
|
||||
): EditorState {
|
||||
const newState = deepCopySlow(state);
|
||||
|
||||
newState.draggingTab = dragging;
|
||||
return newState;
|
||||
|
@ -529,13 +642,20 @@ export function setDraggingTab(dragging: boolean, state: EditorState): EditorSta
|
|||
|
||||
/** Sets the secondary tab group as the primary if the primary is now empty */
|
||||
export function fixupTabGroups(state: EditorState): EditorState {
|
||||
if (!tabGroupHasDocuments(state.editors[Constants.EDITOR_KEY_PRIMARY])
|
||||
&& tabGroupHasDocuments(state.editors[Constants.EDITOR_KEY_SECONDARY])) {
|
||||
state = setNewPrimaryEditor(state.editors[Constants.EDITOR_KEY_SECONDARY], state);
|
||||
if (
|
||||
!tabGroupHasDocuments(state.editors[Constants.EDITOR_KEY_PRIMARY]) &&
|
||||
tabGroupHasDocuments(state.editors[Constants.EDITOR_KEY_SECONDARY])
|
||||
) {
|
||||
state = setNewPrimaryEditor(
|
||||
state.editors[Constants.EDITOR_KEY_SECONDARY],
|
||||
state
|
||||
);
|
||||
}
|
||||
|
||||
if (state.activeEditor === Constants.EDITOR_KEY_SECONDARY
|
||||
&& !tabGroupHasDocuments(state.editors[Constants.EDITOR_KEY_SECONDARY])) {
|
||||
if (
|
||||
state.activeEditor === Constants.EDITOR_KEY_SECONDARY &&
|
||||
!tabGroupHasDocuments(state.editors[Constants.EDITOR_KEY_SECONDARY])
|
||||
) {
|
||||
state = setActiveEditor(Constants.EDITOR_KEY_PRIMARY, state);
|
||||
}
|
||||
|
||||
|
@ -543,8 +663,11 @@ export function fixupTabGroups(state: EditorState): EditorState {
|
|||
}
|
||||
|
||||
/** Sets the list of docs with pending changes */
|
||||
export function setDocsWithPendingChanges(docs: string[], state: EditorState): EditorState {
|
||||
let newState: EditorState = deepCopySlow(state);
|
||||
export function setDocsWithPendingChanges(
|
||||
docs: string[],
|
||||
state: EditorState
|
||||
): EditorState {
|
||||
const newState: EditorState = deepCopySlow(state);
|
||||
|
||||
newState.docsWithPendingChanges = docs;
|
||||
return newState;
|
||||
|
|
|
@ -32,12 +32,13 @@
|
|||
//
|
||||
|
||||
import { ExplorerAction, showExplorer } from '../action/explorerActions';
|
||||
|
||||
import { explorer, ExplorerState } from './explorer';
|
||||
|
||||
describe('Explorer reducer tests', () => {
|
||||
const DEFAULT_STATE: ExplorerState = {
|
||||
showing: false,
|
||||
sortSelectionByPanelId: {}
|
||||
sortSelectionByPanelId: {},
|
||||
};
|
||||
|
||||
it('should return unaltered state for non-matching action type', () => {
|
||||
|
|
|
@ -35,7 +35,7 @@ import {
|
|||
CONNECTED_SERVICES_PANEL_ID,
|
||||
ExplorerAction,
|
||||
ExplorerActions,
|
||||
ExplorerPayload
|
||||
ExplorerPayload,
|
||||
} from '../action/explorerActions';
|
||||
|
||||
export interface ExplorerState {
|
||||
|
@ -47,25 +47,26 @@ export declare type SortCriteria = string;
|
|||
|
||||
const DEFAULT_STATE: ExplorerState = {
|
||||
showing: true,
|
||||
sortSelectionByPanelId: {[CONNECTED_SERVICES_PANEL_ID]: 'name'}
|
||||
sortSelectionByPanelId: { [CONNECTED_SERVICES_PANEL_ID]: 'name' },
|
||||
};
|
||||
|
||||
export function explorer(state: ExplorerState = DEFAULT_STATE, action: ExplorerAction<ExplorerPayload>)
|
||||
: ExplorerState {
|
||||
|
||||
export function explorer(
|
||||
state: ExplorerState = DEFAULT_STATE,
|
||||
action: ExplorerAction<ExplorerPayload>
|
||||
): ExplorerState {
|
||||
switch (action.type) {
|
||||
|
||||
case ExplorerActions.Show:
|
||||
state = { ...state, showing: action.payload.show };
|
||||
break;
|
||||
|
||||
case ExplorerActions.Sort:
|
||||
case ExplorerActions.Sort: {
|
||||
const sortSelectionByPanelId = {
|
||||
...state.sortSelectionByPanelId,
|
||||
...action.payload.sortSelectionByPanelId
|
||||
...action.payload.sortSelectionByPanelId,
|
||||
};
|
||||
state = { ...state, sortSelectionByPanelId };
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -32,11 +32,12 @@
|
|||
//
|
||||
|
||||
import { NavBarAction, select } from '../action/navBarActions';
|
||||
|
||||
import { navBar, NavBarState } from './navBar';
|
||||
|
||||
describe('NavBar reducer unit tests', () => {
|
||||
const DEFAULT_STATE: NavBarState = {
|
||||
selection: null
|
||||
selection: null,
|
||||
};
|
||||
|
||||
it('should return unaltered state for non-matching action type', () => {
|
||||
|
|
|
@ -39,15 +39,18 @@ export interface NavBarState {
|
|||
}
|
||||
|
||||
const DEFAULT_STATE: NavBarState = {
|
||||
selection: constants.NAVBAR_BOT_EXPLORER
|
||||
selection: constants.NAVBAR_BOT_EXPLORER,
|
||||
};
|
||||
|
||||
export function navBar(state: NavBarState = DEFAULT_STATE, action: NavBarAction): NavBarState {
|
||||
export function navBar(
|
||||
state: NavBarState = DEFAULT_STATE,
|
||||
action: NavBarAction
|
||||
): NavBarState {
|
||||
switch (action.type) {
|
||||
case NavBarActions.select: {
|
||||
state = {
|
||||
...state,
|
||||
selection: action.payload.selection
|
||||
selection: action.payload.selection,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -31,16 +31,23 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import notification, { NotificationState } from './notification';
|
||||
import { finishAdd, finishRemove, finishClear, NotificationAction } from '../action/notificationActions';
|
||||
import { newNotification } from '@bfemulator/app-shared';
|
||||
|
||||
import {
|
||||
finishAdd,
|
||||
finishRemove,
|
||||
finishClear,
|
||||
NotificationAction,
|
||||
} from '../action/notificationActions';
|
||||
|
||||
import notification, { NotificationState } from './notification';
|
||||
|
||||
describe('Notification reducer tests', () => {
|
||||
let defaultState: NotificationState;
|
||||
|
||||
beforeEach(() => {
|
||||
defaultState = {
|
||||
allIds: []
|
||||
allIds: [],
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -68,7 +75,7 @@ describe('Notification reducer tests', () => {
|
|||
test('finishRemove', () => {
|
||||
const idToRemove = 'id1';
|
||||
const startingState: NotificationState = {
|
||||
allIds: [idToRemove, 'id2']
|
||||
allIds: [idToRemove, 'id2'],
|
||||
};
|
||||
const action: NotificationAction = finishRemove(idToRemove);
|
||||
let endingState = notification(startingState, action);
|
||||
|
@ -85,7 +92,7 @@ describe('Notification reducer tests', () => {
|
|||
|
||||
test('finishClear', () => {
|
||||
const startingState: NotificationState = {
|
||||
allIds: ['id1', 'id2', 'id3']
|
||||
allIds: ['id1', 'id2', 'id3'],
|
||||
};
|
||||
const action: NotificationAction = finishClear();
|
||||
const endingState = notification(startingState, action);
|
||||
|
|
|
@ -31,17 +31,23 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { NotificationAction, NotificationActions } from '../action/notificationActions';
|
||||
import {
|
||||
NotificationAction,
|
||||
NotificationActions,
|
||||
} from '../action/notificationActions';
|
||||
|
||||
export interface NotificationState {
|
||||
allIds: string[];
|
||||
}
|
||||
|
||||
const DEFAULT_STATE: NotificationState = {
|
||||
allIds: []
|
||||
allIds: [],
|
||||
};
|
||||
|
||||
export function notification(state: NotificationState = DEFAULT_STATE, action: NotificationAction): NotificationState {
|
||||
export function notification(
|
||||
state: NotificationState = DEFAULT_STATE,
|
||||
action: NotificationAction
|
||||
): NotificationState {
|
||||
switch (action.type) {
|
||||
case NotificationActions.finishAdd: {
|
||||
const { id: idToAdd } = action.payload.notification;
|
||||
|
@ -52,7 +58,7 @@ export function notification(state: NotificationState = DEFAULT_STATE, action: N
|
|||
allIds = state.allIds;
|
||||
}
|
||||
state = {
|
||||
allIds
|
||||
allIds,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
@ -61,14 +67,14 @@ export function notification(state: NotificationState = DEFAULT_STATE, action: N
|
|||
const { id: idToRemove } = action.payload;
|
||||
const allIds = state.allIds.filter(id => id !== idToRemove);
|
||||
state = {
|
||||
allIds
|
||||
allIds,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case NotificationActions.finishClear: {
|
||||
state = {
|
||||
allIds: []
|
||||
allIds: [],
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -31,27 +31,31 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { disable, enable, PresentationAction } from '../action/presentationActions';
|
||||
import {
|
||||
disable,
|
||||
enable,
|
||||
PresentationAction,
|
||||
} from '../action/presentationActions';
|
||||
|
||||
import { presentation, PresentationState } from './presentation';
|
||||
jest.mock('../../ui/dialogs', () => ({
|
||||
AzureLoginPromptDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
AzureLoginSuccessDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
BotCreationDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
DialogService: { showDialog: () => Promise.resolve(true) },
|
||||
SecretPromptDialog: function mock() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
));
|
||||
AzureLoginPromptDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
AzureLoginSuccessDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
BotCreationDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
DialogService: { showDialog: () => Promise.resolve(true) },
|
||||
SecretPromptDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
}));
|
||||
describe('Presentation reducer tests', () => {
|
||||
const DEFAULT_STATE: PresentationState = {
|
||||
enabled: null
|
||||
enabled: null,
|
||||
};
|
||||
|
||||
it('should return unaltered state for non-matching action type', () => {
|
||||
|
|
|
@ -31,20 +31,26 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { PresentationAction, PresentationActions } from '../action/presentationActions';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import {
|
||||
PresentationAction,
|
||||
PresentationActions,
|
||||
} from '../action/presentationActions';
|
||||
|
||||
export interface PresentationState {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_STATE: PresentationState = {
|
||||
enabled: false
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
export const presentation = (state: PresentationState = DEFAULT_STATE, action: PresentationAction)
|
||||
: PresentationState => {
|
||||
export const presentation = (
|
||||
state: PresentationState = DEFAULT_STATE,
|
||||
action: PresentationAction
|
||||
): PresentationState => {
|
||||
switch (action.type) {
|
||||
case PresentationActions.disable:
|
||||
state = setEnabled(false, state);
|
||||
|
@ -61,11 +67,17 @@ export const presentation = (state: PresentationState = DEFAULT_STATE, action: P
|
|||
return state;
|
||||
};
|
||||
|
||||
function setEnabled(enabled: boolean, state: PresentationState): PresentationState {
|
||||
let newState = Object.assign({}, state);
|
||||
function setEnabled(
|
||||
enabled: boolean,
|
||||
state: PresentationState
|
||||
): PresentationState {
|
||||
const newState = { ...state };
|
||||
newState.enabled = enabled;
|
||||
|
||||
CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.SetFullscreen, enabled);
|
||||
CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.SetFullscreen,
|
||||
enabled
|
||||
);
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,40 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import {
|
||||
CANCEL_CURRENT_PROCESS,
|
||||
ProgressIndicatorAction,
|
||||
ProgressIndicatorPayload,
|
||||
UPDATE_PROGRESS_INDICATOR
|
||||
UPDATE_PROGRESS_INDICATOR,
|
||||
} from '../action/progressIndicatorActions';
|
||||
|
||||
export interface ProgressIndicatorState {
|
||||
|
@ -14,20 +46,21 @@ export interface ProgressIndicatorState {
|
|||
export const initialState: ProgressIndicatorState = {
|
||||
progress: 0,
|
||||
label: '',
|
||||
canceled: false
|
||||
canceled: false,
|
||||
};
|
||||
|
||||
export function progressIndicator(
|
||||
state: ProgressIndicatorState = initialState,
|
||||
action: ProgressIndicatorAction<ProgressIndicatorPayload>): ProgressIndicatorState {
|
||||
|
||||
action: ProgressIndicatorAction<ProgressIndicatorPayload>
|
||||
): ProgressIndicatorState {
|
||||
switch (action.type) {
|
||||
case UPDATE_PROGRESS_INDICATOR:
|
||||
case UPDATE_PROGRESS_INDICATOR: {
|
||||
const { label, progress } = action.payload;
|
||||
return { ...state, label, progress };
|
||||
}
|
||||
|
||||
case CANCEL_CURRENT_PROCESS:
|
||||
return {...state, canceled: true};
|
||||
return { ...state, canceled: true };
|
||||
|
||||
default:
|
||||
return state;
|
||||
|
|
|
@ -1,11 +1,44 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { IFileService } from 'botframework-config/lib/schema';
|
||||
|
||||
import {
|
||||
CHAT_FILES_UPDATED,
|
||||
CHATS_DIRECTORY_UPDATED,
|
||||
EDIT_RESOURCE,
|
||||
ResourcesAction,
|
||||
TRANSCRIPTS_UPDATED,
|
||||
TRANSCRIPTS_DIRECTORY_UPDATED,
|
||||
CHATS_DIRECTORY_UPDATED
|
||||
TRANSCRIPTS_UPDATED,
|
||||
} from '../action/resourcesAction';
|
||||
|
||||
export interface ResourcesState {
|
||||
|
@ -21,12 +54,17 @@ const initialState: ResourcesState = {
|
|||
transcriptsPath: '',
|
||||
chats: [],
|
||||
chatsPath: '',
|
||||
resourceToRename: null
|
||||
resourceToRename: null,
|
||||
};
|
||||
|
||||
declare type ResourceActionType = ResourcesAction<IFileService | IFileService[] | string>;
|
||||
declare type ResourceActionType = ResourcesAction<
|
||||
IFileService | IFileService[] | string
|
||||
>;
|
||||
|
||||
export function resources(state: ResourcesState = initialState, action: ResourceActionType): ResourcesState {
|
||||
export function resources(
|
||||
state: ResourcesState = initialState,
|
||||
action: ResourceActionType
|
||||
): ResourcesState {
|
||||
switch (action.type) {
|
||||
case TRANSCRIPTS_UPDATED:
|
||||
return { ...state, transcripts: action.payload as IFileService[] };
|
||||
|
|
|
@ -1,3 +1,35 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { SwitchThemePayload, ThemeAction } from '../action/themeActions';
|
||||
|
||||
export interface ThemeState {
|
||||
|
@ -6,11 +38,17 @@ export interface ThemeState {
|
|||
themeComponents: string[];
|
||||
}
|
||||
|
||||
export const initialState: ThemeState = { themeName: null, themeHref: null, themeComponents: [] };
|
||||
export const initialState: ThemeState = {
|
||||
themeName: null,
|
||||
themeHref: null,
|
||||
themeComponents: [],
|
||||
};
|
||||
|
||||
export function theme(state: ThemeState = initialState, action: ThemeAction<SwitchThemePayload>): ThemeState {
|
||||
export function theme(
|
||||
state: ThemeState = initialState,
|
||||
action: ThemeAction<SwitchThemePayload>
|
||||
): ThemeState {
|
||||
switch (action.type) {
|
||||
|
||||
case 'switchTheme':
|
||||
return { ...state, ...action.payload };
|
||||
|
||||
|
|
|
@ -1,39 +1,79 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { ServiceTypes } from 'botframework-config/lib/schema';
|
||||
|
||||
import { store } from '../store';
|
||||
import {
|
||||
azureArmTokenDataChanged,
|
||||
beginAzureAuthWorkflow,
|
||||
} from '../action/azureAuthActions';
|
||||
import {
|
||||
AzureLoginFailedDialogContainer,
|
||||
AzureLoginPromptDialogContainer,
|
||||
AzureLoginSuccessDialogContainer,
|
||||
DialogService,
|
||||
} from '../../ui/dialogs';
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { registerCommands } from '../../commands/uiCommands';
|
||||
|
||||
import { azureAuthSagas } from './azureAuthSaga';
|
||||
|
||||
jest.mock('../../ui/dialogs', () => ({
|
||||
AzureLoginPromptDialogContainer: () => undefined,
|
||||
AzureLoginSuccessDialogContainer: () => undefined,
|
||||
BotCreationDialog: () => undefined,
|
||||
DialogService: { showDialog: () => Promise.resolve(true) },
|
||||
PostMigrationDialogContainer: () => undefined,
|
||||
SecretPromptDialog: () => undefined
|
||||
SecretPromptDialog: () => undefined,
|
||||
}));
|
||||
|
||||
jest.mock('../../platform/commands/commandServiceImpl', () => ({
|
||||
CommandServiceImpl: {
|
||||
remoteCall: () => Promise.resolve(true)
|
||||
}
|
||||
remoteCall: () => Promise.resolve(true),
|
||||
},
|
||||
}));
|
||||
|
||||
import { store } from '../store';
|
||||
import { azureArmTokenDataChanged, beginAzureAuthWorkflow } from '../action/azureAuthActions';
|
||||
import { azureAuthSagas } from './azureAuthSaga';
|
||||
import {
|
||||
AzureLoginFailedDialogContainer,
|
||||
AzureLoginPromptDialogContainer,
|
||||
AzureLoginSuccessDialogContainer,
|
||||
DialogService
|
||||
} from '../../ui/dialogs';
|
||||
import { CommandRegistryImpl } from '@bfemulator/sdk-shared';
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { registerCommands } from '../../commands/uiCommands';
|
||||
import { ServiceTypes } from 'botframework-config/lib/schema';
|
||||
|
||||
describe('The azureAuthSaga', () => {
|
||||
it('should contain a single step if the token in the store is valid', () => {
|
||||
store.dispatch(azureArmTokenDataChanged('a valid access_token'));
|
||||
const it = azureAuthSagas().next().value.FORK.args[1]();
|
||||
const it = azureAuthSagas()
|
||||
.next()
|
||||
.value.FORK.args[1]();
|
||||
let val = undefined;
|
||||
let ct = 0;
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const next = it.next(val);
|
||||
if (next.done) {
|
||||
|
@ -58,18 +98,21 @@ describe('The azureAuthSaga', () => {
|
|||
|
||||
it('should contain just 2 steps when the Azure login dialog prompt is canceled', async () => {
|
||||
store.dispatch(azureArmTokenDataChanged(''));
|
||||
// @ts-ignore
|
||||
DialogService.showDialog = () => Promise.resolve(false);
|
||||
const it = azureAuthSagas()
|
||||
.next()
|
||||
.value
|
||||
.FORK
|
||||
.args[1](beginAzureAuthWorkflow(
|
||||
AzureLoginPromptDialogContainer,
|
||||
{ serviceType: ServiceTypes.Luis },
|
||||
AzureLoginSuccessDialogContainer,
|
||||
AzureLoginFailedDialogContainer));
|
||||
.value.FORK.args[1](
|
||||
beginAzureAuthWorkflow(
|
||||
AzureLoginPromptDialogContainer,
|
||||
{ serviceType: ServiceTypes.Luis },
|
||||
AzureLoginSuccessDialogContainer,
|
||||
AzureLoginFailedDialogContainer
|
||||
)
|
||||
);
|
||||
let val = undefined;
|
||||
let ct = 0;
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const next = it.next(val);
|
||||
if (next.done) {
|
||||
|
@ -91,20 +134,23 @@ describe('The azureAuthSaga', () => {
|
|||
|
||||
it('should contain 4 steps when the Azure login dialog prompt is confirmed but auth fails', async () => {
|
||||
store.dispatch(azureArmTokenDataChanged(''));
|
||||
// @ts-ignore
|
||||
DialogService.showDialog = () => Promise.resolve(1);
|
||||
(CommandServiceImpl as any).remoteCall = () => Promise.resolve(false);
|
||||
const it = azureAuthSagas()
|
||||
.next()
|
||||
.value
|
||||
.FORK
|
||||
.args[1](beginAzureAuthWorkflow(
|
||||
AzureLoginPromptDialogContainer,
|
||||
{ serviceType: ServiceTypes.Luis },
|
||||
AzureLoginSuccessDialogContainer,
|
||||
AzureLoginFailedDialogContainer));
|
||||
.value.FORK.args[1](
|
||||
beginAzureAuthWorkflow(
|
||||
AzureLoginPromptDialogContainer,
|
||||
{ serviceType: ServiceTypes.Luis },
|
||||
AzureLoginSuccessDialogContainer,
|
||||
AzureLoginFailedDialogContainer
|
||||
)
|
||||
);
|
||||
let val = undefined;
|
||||
let ct = 0;
|
||||
const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall');
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const next = it.next(val);
|
||||
if (next.done) {
|
||||
|
@ -127,7 +173,9 @@ describe('The azureAuthSaga', () => {
|
|||
if (ct === 2) {
|
||||
// Login was unsuccessful
|
||||
expect(val).toBe(false);
|
||||
expect(remoteCallSpy).toHaveBeenCalledWith([SharedConstants.Commands.Azure.RetrieveArmToken]);
|
||||
expect(remoteCallSpy).toHaveBeenCalledWith([
|
||||
SharedConstants.Commands.Azure.RetrieveArmToken,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,10 +186,12 @@ describe('The azureAuthSaga', () => {
|
|||
|
||||
it('should contain 6 steps when the Azure login dialog prompt is confirmed and auth succeeds', async () => {
|
||||
store.dispatch(azureArmTokenDataChanged(''));
|
||||
// @ts-ignore
|
||||
DialogService.showDialog = () => Promise.resolve(1);
|
||||
(CommandServiceImpl as any).remoteCall = args => {
|
||||
switch (args[0]) {
|
||||
case SharedConstants.Commands.Azure.RetrieveArmToken:
|
||||
// eslint-disable-next-line typescript/camelcase
|
||||
return Promise.resolve({ access_token: 'a valid access_token' });
|
||||
|
||||
case SharedConstants.Commands.Azure.PersistAzureLoginChanged:
|
||||
|
@ -153,16 +203,18 @@ describe('The azureAuthSaga', () => {
|
|||
};
|
||||
const it = azureAuthSagas()
|
||||
.next()
|
||||
.value
|
||||
.FORK
|
||||
.args[1](beginAzureAuthWorkflow(
|
||||
AzureLoginPromptDialogContainer,
|
||||
{ serviceType: ServiceTypes.Luis },
|
||||
AzureLoginSuccessDialogContainer,
|
||||
AzureLoginFailedDialogContainer));
|
||||
.value.FORK.args[1](
|
||||
beginAzureAuthWorkflow(
|
||||
AzureLoginPromptDialogContainer,
|
||||
{ serviceType: ServiceTypes.Luis },
|
||||
AzureLoginSuccessDialogContainer,
|
||||
AzureLoginFailedDialogContainer
|
||||
)
|
||||
);
|
||||
let val = undefined;
|
||||
let ct = 0;
|
||||
const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall');
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const next = it.next(val);
|
||||
if (next.done) {
|
||||
|
@ -185,10 +237,15 @@ describe('The azureAuthSaga', () => {
|
|||
if (ct === 2) {
|
||||
// Login was successful
|
||||
expect(val.access_token).toBe('a valid access_token');
|
||||
expect(remoteCallSpy).toHaveBeenCalledWith([SharedConstants.Commands.Azure.RetrieveArmToken]);
|
||||
expect(remoteCallSpy).toHaveBeenCalledWith([
|
||||
SharedConstants.Commands.Azure.RetrieveArmToken,
|
||||
]);
|
||||
} else if (ct === 4) {
|
||||
expect(val.persistLogin).toBe(true);
|
||||
expect(remoteCallSpy).toHaveBeenCalledWith([SharedConstants.Commands.Azure.PersistAzureLoginChanged, 1]);
|
||||
expect(remoteCallSpy).toHaveBeenCalledWith([
|
||||
SharedConstants.Commands.Azure.PersistAzureLoginChanged,
|
||||
1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else if ('PUT' in val) {
|
||||
|
@ -197,7 +254,9 @@ describe('The azureAuthSaga', () => {
|
|||
ct++;
|
||||
}
|
||||
expect(ct).toBe(6);
|
||||
expect(store.getState().azureAuth.access_token).toBe('a valid access_token');
|
||||
expect(store.getState().azureAuth.access_token).toBe(
|
||||
'a valid access_token'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,35 +30,56 @@
|
|||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { call, ForkEffect, put, select, takeEvery } from 'redux-saga/effects';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { DialogService } from '../../ui/dialogs';
|
||||
import {
|
||||
AZURE_BEGIN_AUTH_WORKFLOW,
|
||||
azureArmTokenDataChanged,
|
||||
AzureAuthAction,
|
||||
AzureAuthWorkflow
|
||||
AzureAuthWorkflow,
|
||||
} from '../action/azureAuthActions';
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { RootState } from '../store';
|
||||
import { DialogService } from '../../ui/dialogs';
|
||||
import { AzureAuthState } from '../reducer/azureAuthReducer';
|
||||
import { RootState } from '../store';
|
||||
|
||||
import { call, ForkEffect, put, select, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
const getArmTokenFromState = (state: RootState) => state.azureAuth;
|
||||
|
||||
export function* getArmToken(action: AzureAuthAction<AzureAuthWorkflow>): IterableIterator<any> {
|
||||
export function* getArmToken(
|
||||
action: AzureAuthAction<AzureAuthWorkflow>
|
||||
): IterableIterator<any> {
|
||||
let azureAuth: AzureAuthState = yield select(getArmTokenFromState);
|
||||
if (azureAuth.access_token) {
|
||||
return azureAuth;
|
||||
}
|
||||
const result = yield DialogService.showDialog(action.payload.promptDialog, action.payload.promptDialogProps);
|
||||
if (result !== 1) { // Result must be 1 which is a confirmation to sign in to Azure
|
||||
const result = yield DialogService.showDialog(
|
||||
action.payload.promptDialog,
|
||||
action.payload.promptDialogProps
|
||||
);
|
||||
if (result !== 1) {
|
||||
// Result must be 1 which is a confirmation to sign in to Azure
|
||||
return result;
|
||||
}
|
||||
const { RetrieveArmToken, PersistAzureLoginChanged } = SharedConstants.Commands.Azure;
|
||||
azureAuth = yield call(CommandServiceImpl.remoteCall.bind(CommandServiceImpl), RetrieveArmToken);
|
||||
const {
|
||||
RetrieveArmToken,
|
||||
PersistAzureLoginChanged,
|
||||
} = SharedConstants.Commands.Azure;
|
||||
azureAuth = yield call(
|
||||
CommandServiceImpl.remoteCall.bind(CommandServiceImpl),
|
||||
RetrieveArmToken
|
||||
);
|
||||
if (azureAuth && !('error' in azureAuth)) {
|
||||
const persistLogin = yield DialogService.showDialog(action.payload.loginSuccessDialog, azureAuth);
|
||||
yield call(CommandServiceImpl.remoteCall.bind(CommandServiceImpl), PersistAzureLoginChanged, persistLogin);
|
||||
const persistLogin = yield DialogService.showDialog(
|
||||
action.payload.loginSuccessDialog,
|
||||
azureAuth
|
||||
);
|
||||
yield call(
|
||||
CommandServiceImpl.remoteCall.bind(CommandServiceImpl),
|
||||
PersistAzureLoginChanged,
|
||||
persistLogin
|
||||
);
|
||||
} else {
|
||||
yield DialogService.showDialog(action.payload.loginFailedDialog);
|
||||
}
|
||||
|
|
|
@ -31,31 +31,32 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { BotActions, botHashGenerated, SetActiveBotAction } from '../action/botActions';
|
||||
import { BotConfigWithPath } from '@bfemulator/sdk-shared';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
|
||||
import {
|
||||
BotActions,
|
||||
botHashGenerated,
|
||||
SetActiveBotAction,
|
||||
} from '../action/botActions';
|
||||
import { generateBotHash } from '../botHelpers';
|
||||
|
||||
import {
|
||||
botSagas,
|
||||
browseForBot,
|
||||
editorSelector,
|
||||
generateHashForActiveBot
|
||||
} from './botSagas';
|
||||
import {
|
||||
call,
|
||||
put,
|
||||
select,
|
||||
takeEvery,
|
||||
takeLatest
|
||||
} from 'redux-saga/effects';
|
||||
import { generateBotHash } from '../botHelpers';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
generateHashForActiveBot,
|
||||
} from './botSagas';
|
||||
import { refreshConversationMenu } from './sharedSagas';
|
||||
|
||||
import { call, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||
|
||||
jest.mock('../../ui/dialogs', () => ({}));
|
||||
|
||||
jest.mock('../store', () => ({
|
||||
get store() {
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
}));
|
||||
|
||||
const mockSharedConstants = SharedConstants;
|
||||
|
@ -68,63 +69,51 @@ jest.mock('../../platform/commands/commandServiceImpl', () => ({
|
|||
mockLocalCommandsCalled.push({ commandName, args: args });
|
||||
|
||||
switch (commandName) {
|
||||
case mockSharedConstants.Commands.Bot.OpenBrowse:
|
||||
case mockSharedConstants.Commands.Bot.OpenBrowse:
|
||||
return Promise.resolve(true);
|
||||
default:
|
||||
default:
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
remoteCall: async (commandName: string, ... args: any[]) => {
|
||||
mockRemoteCommandsCalled.push({ commandName, args: args});
|
||||
remoteCall: async (commandName: string, ...args: any[]) => {
|
||||
mockRemoteCommandsCalled.push({ commandName, args: args });
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('The botSagas', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mockRemoteCommandsCalled = [];
|
||||
mockLocalCommandsCalled = [];
|
||||
});
|
||||
|
||||
it('should initialize the root saga', () => {
|
||||
let gen = botSagas();
|
||||
const gen = botSagas();
|
||||
|
||||
const browseForBotYield = gen.next().value;
|
||||
|
||||
expect(browseForBotYield).toEqual(
|
||||
takeEvery(
|
||||
BotActions.browse,
|
||||
browseForBot
|
||||
)
|
||||
takeEvery(BotActions.browse, browseForBot)
|
||||
);
|
||||
|
||||
const generateBotHashYield = gen.next().value;
|
||||
|
||||
expect(generateBotHashYield).toEqual(
|
||||
takeEvery(
|
||||
BotActions.setActive,
|
||||
generateHashForActiveBot
|
||||
)
|
||||
takeEvery(BotActions.setActive, generateHashForActiveBot)
|
||||
);
|
||||
|
||||
const refreshConversationMenuYield = gen.next().value;
|
||||
|
||||
expect(refreshConversationMenuYield).toEqual(
|
||||
takeLatest(
|
||||
[
|
||||
BotActions.setActive,
|
||||
BotActions.load,
|
||||
BotActions.close
|
||||
],
|
||||
[BotActions.setActive, BotActions.load, BotActions.close],
|
||||
refreshConversationMenu
|
||||
)
|
||||
);
|
||||
|
||||
expect(gen.next().done).toBe(true);
|
||||
|
||||
});
|
||||
|
||||
it('should generate a hash for an active bot', () => {
|
||||
|
@ -134,14 +123,14 @@ describe('The botSagas', () => {
|
|||
padlock: null,
|
||||
services: [],
|
||||
path: '/some/Path/something',
|
||||
version: '0.1'
|
||||
version: '0.1',
|
||||
};
|
||||
|
||||
|
||||
const setActiveBotAction: SetActiveBotAction = {
|
||||
type: BotActions.setActive,
|
||||
payload: {
|
||||
bot: botConfigPath
|
||||
}
|
||||
bot: botConfigPath,
|
||||
},
|
||||
};
|
||||
const gen = generateHashForActiveBot(setActiveBotAction);
|
||||
const generatedHash = gen.next().value;
|
||||
|
|
|
@ -31,13 +31,26 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { call, ForkEffect, put, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { BotActions, botHashGenerated, SetActiveBotAction } from '../action/botActions';
|
||||
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import {
|
||||
BotActions,
|
||||
botHashGenerated,
|
||||
SetActiveBotAction,
|
||||
} from '../action/botActions';
|
||||
import { generateBotHash } from '../botHelpers';
|
||||
|
||||
import { refreshConversationMenu } from './sharedSagas';
|
||||
|
||||
import {
|
||||
call,
|
||||
ForkEffect,
|
||||
put,
|
||||
takeEvery,
|
||||
takeLatest,
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
/** Opens up native open file dialog to browse for a .bot file */
|
||||
export function* browseForBot(): IterableIterator<any> {
|
||||
yield CommandServiceImpl.call(SharedConstants.Commands.Bot.OpenBrowse)
|
||||
|
@ -45,7 +58,9 @@ export function* browseForBot(): IterableIterator<any> {
|
|||
.catch(_err => null);
|
||||
}
|
||||
|
||||
export function* generateHashForActiveBot(action: SetActiveBotAction): IterableIterator<any> {
|
||||
export function* generateHashForActiveBot(
|
||||
action: SetActiveBotAction
|
||||
): IterableIterator<any> {
|
||||
const { bot } = action.payload;
|
||||
const generatedHash = yield call(generateBotHash, bot);
|
||||
yield put(botHashGenerated(generatedHash));
|
||||
|
@ -55,11 +70,7 @@ export function* botSagas(): IterableIterator<ForkEffect> {
|
|||
yield takeEvery(BotActions.browse, browseForBot);
|
||||
yield takeEvery(BotActions.setActive, generateHashForActiveBot);
|
||||
yield takeLatest(
|
||||
[
|
||||
BotActions.setActive,
|
||||
BotActions.load,
|
||||
BotActions.close
|
||||
],
|
||||
[BotActions.setActive, BotActions.load, BotActions.close],
|
||||
refreshConversationMenu
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,16 +31,23 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { call, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||
import { checkActiveDocForPendingChanges, editorSagas, promptUserToReloadDocument } from './editorSagas';
|
||||
import { EditorActions, removeDocPendingChange } from '../action/editorActions';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
|
||||
import { EditorActions, removeDocPendingChange } from '../action/editorActions';
|
||||
|
||||
import {
|
||||
checkActiveDocForPendingChanges,
|
||||
editorSagas,
|
||||
promptUserToReloadDocument,
|
||||
} from './editorSagas';
|
||||
import { refreshConversationMenu, editorSelector } from './sharedSagas';
|
||||
|
||||
import { call, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||
|
||||
jest.mock('../store', () => ({
|
||||
get store() {
|
||||
return {};
|
||||
}
|
||||
get store() {
|
||||
return {};
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../ui/dialogs', () => ({}));
|
||||
|
@ -48,145 +55,150 @@ jest.mock('../../ui/dialogs', () => ({}));
|
|||
const mockSharedConstants = SharedConstants;
|
||||
let mockRemoteCommandsCalled = [];
|
||||
let mockLocalCommandsCalled = [];
|
||||
let mockMessageResponse = false;
|
||||
const mockMessageResponse = false;
|
||||
|
||||
jest.mock('../../platform/commands/commandServiceImpl', () => ({
|
||||
CommandServiceImpl: {
|
||||
call: async (commandName: string, ...args: any[]) => {
|
||||
mockLocalCommandsCalled.push({ commandName, args: args });
|
||||
},
|
||||
remoteCall: async (commandName: string, ...args: any[]) => {
|
||||
mockRemoteCommandsCalled.push({ commandName, args: args });
|
||||
CommandServiceImpl: {
|
||||
call: async (commandName: string, ...args: any[]) => {
|
||||
mockLocalCommandsCalled.push({ commandName, args: args });
|
||||
},
|
||||
remoteCall: async (commandName: string, ...args: any[]) => {
|
||||
mockRemoteCommandsCalled.push({ commandName, args: args });
|
||||
|
||||
switch (commandName) {
|
||||
case mockSharedConstants.Commands.Electron.ShowMessageBox:
|
||||
if (mockMessageResponse) {
|
||||
return Promise.resolve(true);
|
||||
} else {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
default:
|
||||
return Promise.resolve(true);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (commandName) {
|
||||
case mockSharedConstants.Commands.Electron.ShowMessageBox:
|
||||
if (mockMessageResponse) {
|
||||
return Promise.resolve(true);
|
||||
} else {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
default:
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('The Editor Sagas', () => {
|
||||
beforeEach(() => {
|
||||
mockRemoteCommandsCalled = [];
|
||||
mockLocalCommandsCalled = [];
|
||||
});
|
||||
beforeEach(() => {
|
||||
mockRemoteCommandsCalled = [];
|
||||
mockLocalCommandsCalled = [];
|
||||
});
|
||||
|
||||
it('should check the active doc for pending changes', () => {
|
||||
const gen = checkActiveDocForPendingChanges();
|
||||
const stateData = gen.next().value;
|
||||
it('should check the active doc for pending changes', () => {
|
||||
const gen = checkActiveDocForPendingChanges();
|
||||
const stateData = gen.next().value;
|
||||
|
||||
expect(stateData).toEqual(select(editorSelector));
|
||||
expect(stateData).toEqual(select(editorSelector));
|
||||
|
||||
const mockActiveDocId = 'doc1';
|
||||
const mockEditorState = {
|
||||
editors: {
|
||||
someEditor: {
|
||||
activeDocumentId: mockActiveDocId
|
||||
}
|
||||
},
|
||||
activeEditor: 'someEditor',
|
||||
docsWithPendingChanges: [mockActiveDocId]
|
||||
};
|
||||
// should return the inner generator that we delegate to
|
||||
const innerGen = gen.next(mockEditorState).value;
|
||||
expect(innerGen).toEqual(call(promptUserToReloadDocument, mockActiveDocId));
|
||||
const mockActiveDocId = 'doc1';
|
||||
const mockEditorState = {
|
||||
editors: {
|
||||
someEditor: {
|
||||
activeDocumentId: mockActiveDocId,
|
||||
},
|
||||
},
|
||||
activeEditor: 'someEditor',
|
||||
docsWithPendingChanges: [mockActiveDocId],
|
||||
};
|
||||
// should return the inner generator that we delegate to
|
||||
const innerGen = gen.next(mockEditorState).value;
|
||||
expect(innerGen).toEqual(call(promptUserToReloadDocument, mockActiveDocId));
|
||||
|
||||
expect(gen.next().done).toBe(true);
|
||||
});
|
||||
expect(gen.next().done).toBe(true);
|
||||
});
|
||||
|
||||
it('should prompt the user to reload the document when the file is chatdown', () => {
|
||||
const mockChatFileName = 'doc1.chat';
|
||||
const options = {
|
||||
buttons: ['Cancel', 'Reload'],
|
||||
title: 'File change detected',
|
||||
message: 'We have detected a change in this file on disk. Would you like to reload it in the Emulator?'
|
||||
};
|
||||
const gen = promptUserToReloadDocument(mockChatFileName);
|
||||
it('should prompt the user to reload the document when the file is chatdown', () => {
|
||||
const mockChatFileName = 'doc1.chat';
|
||||
const options = {
|
||||
buttons: ['Cancel', 'Reload'],
|
||||
title: 'File change detected',
|
||||
message:
|
||||
'We have detected a change in this file on disk. Would you like to reload it in the Emulator?',
|
||||
};
|
||||
const gen = promptUserToReloadDocument(mockChatFileName);
|
||||
|
||||
gen.next();
|
||||
gen.next();
|
||||
|
||||
const { ShowMessageBox } = SharedConstants.Commands.Electron;
|
||||
expect(mockRemoteCommandsCalled).toHaveLength(1);
|
||||
expect(mockRemoteCommandsCalled[0].commandName).toEqual(ShowMessageBox);
|
||||
expect(mockRemoteCommandsCalled[0].args[0]).toEqual(options);
|
||||
expect(gen.next(true).value).toEqual(put(removeDocPendingChange(mockChatFileName)));
|
||||
|
||||
gen.next();
|
||||
|
||||
const { OpenChatFile } = SharedConstants.Commands.Emulator;
|
||||
const { ShowMessageBox } = SharedConstants.Commands.Electron;
|
||||
expect(mockRemoteCommandsCalled).toHaveLength(1);
|
||||
expect(mockRemoteCommandsCalled[0].commandName).toEqual(ShowMessageBox);
|
||||
expect(mockRemoteCommandsCalled[0].args[0]).toEqual(options);
|
||||
expect(gen.next(true).value).toEqual(
|
||||
put(removeDocPendingChange(mockChatFileName))
|
||||
);
|
||||
|
||||
expect(mockLocalCommandsCalled).toHaveLength(1);
|
||||
expect(mockLocalCommandsCalled[0].commandName).toEqual(OpenChatFile);
|
||||
expect(mockLocalCommandsCalled[0].args[0]).toBe(mockChatFileName);
|
||||
expect(mockLocalCommandsCalled[0].args[1]).toBe(true);
|
||||
expect(gen.next().done).toBe(true);
|
||||
});
|
||||
gen.next();
|
||||
|
||||
it('should prompt the user to reload the document when the file is a transcript', () => {
|
||||
const mockTranscriptFile = 'doc2.transcript';
|
||||
const options = {
|
||||
buttons: ['Cancel', 'Reload'],
|
||||
title: 'File change detected',
|
||||
message: 'We have detected a change in this file on disk. Would you like to reload it in the Emulator?'
|
||||
};
|
||||
const gen = promptUserToReloadDocument(mockTranscriptFile);
|
||||
const { OpenChatFile } = SharedConstants.Commands.Emulator;
|
||||
|
||||
gen.next();
|
||||
expect(mockLocalCommandsCalled).toHaveLength(1);
|
||||
expect(mockLocalCommandsCalled[0].commandName).toEqual(OpenChatFile);
|
||||
expect(mockLocalCommandsCalled[0].args[0]).toBe(mockChatFileName);
|
||||
expect(mockLocalCommandsCalled[0].args[1]).toBe(true);
|
||||
expect(gen.next().done).toBe(true);
|
||||
});
|
||||
|
||||
const { ShowMessageBox } = SharedConstants.Commands.Electron;
|
||||
expect(mockRemoteCommandsCalled).toHaveLength(1);
|
||||
expect(mockRemoteCommandsCalled[0].commandName).toEqual(ShowMessageBox);
|
||||
expect(mockRemoteCommandsCalled[0].args[0]).toEqual(options);
|
||||
expect(gen.next(true).value).toEqual(put(removeDocPendingChange(mockTranscriptFile)));
|
||||
gen.next();
|
||||
|
||||
const { ReloadTranscript } = SharedConstants.Commands.Emulator;
|
||||
it('should prompt the user to reload the document when the file is a transcript', () => {
|
||||
const mockTranscriptFile = 'doc2.transcript';
|
||||
const options = {
|
||||
buttons: ['Cancel', 'Reload'],
|
||||
title: 'File change detected',
|
||||
message:
|
||||
'We have detected a change in this file on disk. Would you like to reload it in the Emulator?',
|
||||
};
|
||||
const gen = promptUserToReloadDocument(mockTranscriptFile);
|
||||
|
||||
expect(mockLocalCommandsCalled).toHaveLength(1);
|
||||
expect(mockLocalCommandsCalled[0].commandName).toEqual(ReloadTranscript);
|
||||
expect(mockLocalCommandsCalled[0].args[0]).toBe(mockTranscriptFile);
|
||||
expect(gen.next().done).toBe(true);
|
||||
});
|
||||
gen.next();
|
||||
|
||||
it('should initialize the root saga', () => {
|
||||
let gen = editorSagas();
|
||||
|
||||
const checkActiveDocsYield = gen.next().value;
|
||||
|
||||
expect(checkActiveDocsYield).toEqual(
|
||||
takeEvery(
|
||||
[
|
||||
EditorActions.addDocPendingChange,
|
||||
EditorActions.setActiveEditor,
|
||||
EditorActions.setActiveTab,
|
||||
EditorActions.open
|
||||
],
|
||||
checkActiveDocForPendingChanges
|
||||
)
|
||||
);
|
||||
const { ShowMessageBox } = SharedConstants.Commands.Electron;
|
||||
expect(mockRemoteCommandsCalled).toHaveLength(1);
|
||||
expect(mockRemoteCommandsCalled[0].commandName).toEqual(ShowMessageBox);
|
||||
expect(mockRemoteCommandsCalled[0].args[0]).toEqual(options);
|
||||
expect(gen.next(true).value).toEqual(
|
||||
put(removeDocPendingChange(mockTranscriptFile))
|
||||
);
|
||||
gen.next();
|
||||
|
||||
const refreshConversationMenuYield = gen.next().value;
|
||||
const { ReloadTranscript } = SharedConstants.Commands.Emulator;
|
||||
|
||||
expect(refreshConversationMenuYield).toEqual(
|
||||
takeLatest(
|
||||
[
|
||||
EditorActions.close,
|
||||
EditorActions.open,
|
||||
EditorActions.setActiveEditor,
|
||||
EditorActions.setActiveTab
|
||||
],
|
||||
refreshConversationMenu
|
||||
)
|
||||
);
|
||||
expect(mockLocalCommandsCalled).toHaveLength(1);
|
||||
expect(mockLocalCommandsCalled[0].commandName).toEqual(ReloadTranscript);
|
||||
expect(mockLocalCommandsCalled[0].args[0]).toBe(mockTranscriptFile);
|
||||
expect(gen.next().done).toBe(true);
|
||||
});
|
||||
|
||||
expect(gen.next().done).toBe(true);
|
||||
});
|
||||
it('should initialize the root saga', () => {
|
||||
const gen = editorSagas();
|
||||
|
||||
const checkActiveDocsYield = gen.next().value;
|
||||
|
||||
expect(checkActiveDocsYield).toEqual(
|
||||
takeEvery(
|
||||
[
|
||||
EditorActions.addDocPendingChange,
|
||||
EditorActions.setActiveEditor,
|
||||
EditorActions.setActiveTab,
|
||||
EditorActions.open,
|
||||
],
|
||||
checkActiveDocForPendingChanges
|
||||
)
|
||||
);
|
||||
|
||||
const refreshConversationMenuYield = gen.next().value;
|
||||
|
||||
expect(refreshConversationMenuYield).toEqual(
|
||||
takeLatest(
|
||||
[
|
||||
EditorActions.close,
|
||||
EditorActions.open,
|
||||
EditorActions.setActiveEditor,
|
||||
EditorActions.setActiveTab,
|
||||
],
|
||||
refreshConversationMenu
|
||||
)
|
||||
);
|
||||
|
||||
expect(gen.next().done).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,27 +31,47 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { call, ForkEffect, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||
import {
|
||||
isChatFile,
|
||||
isTranscriptFile,
|
||||
SharedConstants,
|
||||
} from '@bfemulator/app-shared';
|
||||
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { EditorActions, removeDocPendingChange } from '../action/editorActions';
|
||||
import { isChatFile, isTranscriptFile, SharedConstants } from '@bfemulator/app-shared';
|
||||
|
||||
import { editorSelector, refreshConversationMenu } from './sharedSagas';
|
||||
|
||||
export function* promptUserToReloadDocument(filename: string): IterableIterator<any> {
|
||||
import {
|
||||
call,
|
||||
ForkEffect,
|
||||
put,
|
||||
select,
|
||||
takeEvery,
|
||||
takeLatest,
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
export function* promptUserToReloadDocument(
|
||||
filename: string
|
||||
): IterableIterator<any> {
|
||||
const { Commands } = SharedConstants;
|
||||
const options = {
|
||||
buttons: ['Cancel', 'Reload'],
|
||||
title: 'File change detected',
|
||||
message: 'We have detected a change in this file on disk. Would you like to reload it in the Emulator?'
|
||||
message:
|
||||
'We have detected a change in this file on disk. Would you like to reload it in the Emulator?',
|
||||
};
|
||||
const confirmation = yield CommandServiceImpl.remoteCall(Commands.Electron.ShowMessageBox, options);
|
||||
const confirmation = yield CommandServiceImpl.remoteCall(
|
||||
Commands.Electron.ShowMessageBox,
|
||||
options
|
||||
);
|
||||
|
||||
// clear the doc of pending changes
|
||||
yield put(removeDocPendingChange(filename));
|
||||
|
||||
// reload the file, otherwise proceed without reloading
|
||||
const { OpenChatFile, ReloadTranscript } = SharedConstants.Commands.Emulator;
|
||||
|
||||
|
||||
if (confirmation) {
|
||||
if (isChatFile(filename)) {
|
||||
yield CommandServiceImpl.call(OpenChatFile, filename, true);
|
||||
|
@ -65,7 +85,8 @@ export function* checkActiveDocForPendingChanges(): IterableIterator<any> {
|
|||
const stateData = yield select(editorSelector);
|
||||
|
||||
// if currently active document has pending changes, prompt the user to reload it
|
||||
const activeDocId = stateData.editors[stateData.activeEditor].activeDocumentId;
|
||||
const activeDocId =
|
||||
stateData.editors[stateData.activeEditor].activeDocumentId;
|
||||
if (stateData.docsWithPendingChanges.some(doc => doc === activeDocId)) {
|
||||
// TODO: active document ID is not always the filename
|
||||
yield call(promptUserToReloadDocument, activeDocId);
|
||||
|
@ -81,7 +102,7 @@ export function* editorSagas(): IterableIterator<ForkEffect> {
|
|||
EditorActions.addDocPendingChange,
|
||||
EditorActions.setActiveEditor,
|
||||
EditorActions.setActiveTab,
|
||||
EditorActions.open
|
||||
EditorActions.open,
|
||||
],
|
||||
checkActiveDocForPendingChanges
|
||||
);
|
||||
|
|
|
@ -1,27 +1,65 @@
|
|||
import { bot } from '../reducer/bot';
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { applyMiddleware, combineReducers, createStore } from 'redux';
|
||||
import sagaMiddlewareFactory from 'redux-saga';
|
||||
import { endpointSagas } from './endpointSagas';
|
||||
import { load, setActive } from '../action/botActions';
|
||||
import { launchEndpointEditor, openEndpointExplorerContextMenu } from '../action/endpointServiceActions';
|
||||
import { Component } from 'react';
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { DialogService } from '../../ui/dialogs/service';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
|
||||
const sagaMiddleWare = sagaMiddlewareFactory();
|
||||
const mockStore = createStore(combineReducers({ bot }), {}, applyMiddleware(sagaMiddleWare));
|
||||
sagaMiddleWare.run(endpointSagas);
|
||||
const mockComponentClass = class extends Component<{}, {}> {
|
||||
import { bot } from '../reducer/bot';
|
||||
import { load, setActive } from '../action/botActions';
|
||||
import {
|
||||
launchEndpointEditor,
|
||||
openEndpointExplorerContextMenu,
|
||||
} from '../action/endpointServiceActions';
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { DialogService } from '../../ui/dialogs/service';
|
||||
|
||||
};
|
||||
jest.mock('../store', () => ({
|
||||
get store() {
|
||||
return mockStore;
|
||||
}
|
||||
})
|
||||
import { endpointSagas } from './endpointSagas';
|
||||
|
||||
const sagaMiddleWare = sagaMiddlewareFactory();
|
||||
const mockStore = createStore(
|
||||
combineReducers({ bot }),
|
||||
{},
|
||||
applyMiddleware(sagaMiddleWare)
|
||||
);
|
||||
let mockBot = JSON.parse(`{
|
||||
sagaMiddleWare.run(endpointSagas);
|
||||
const mockComponentClass = class extends Component<{}, {}> {};
|
||||
jest.mock('../store', () => ({
|
||||
get store() {
|
||||
return mockStore;
|
||||
},
|
||||
}));
|
||||
const mockBot = JSON.parse(`{
|
||||
"name": "TestBot",
|
||||
"description": "",
|
||||
"padlock": "",
|
||||
|
@ -45,26 +83,24 @@ let mockBot = JSON.parse(`{
|
|||
}`);
|
||||
|
||||
jest.mock('../../ui/dialogs', () => ({
|
||||
AzureLoginPromptDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
AzureLoginSuccessDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
BotCreationDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
DialogService: {
|
||||
showDialog: () => Promise.resolve(mockBot.services)
|
||||
},
|
||||
SecretPromptDialog: function mock() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
));
|
||||
AzureLoginPromptDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
AzureLoginSuccessDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
BotCreationDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
DialogService: {
|
||||
showDialog: () => Promise.resolve(mockBot.services),
|
||||
},
|
||||
SecretPromptDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
}));
|
||||
|
||||
describe('The endpoint sagas', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mockStore.dispatch(load([mockBot]));
|
||||
mockStore.dispatch(setActive(mockBot));
|
||||
|
@ -72,11 +108,21 @@ describe('The endpoint sagas', () => {
|
|||
|
||||
it('should launch the endpoint editor and execute a command to save the edited services', async () => {
|
||||
const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall');
|
||||
const dialogServiceSpy = jest.spyOn(DialogService, 'showDialog').mockResolvedValue(mockBot.services);
|
||||
await mockStore.dispatch(launchEndpointEditor(mockComponentClass, mockBot.services[0]));
|
||||
const dialogServiceSpy = jest
|
||||
.spyOn(DialogService, 'showDialog')
|
||||
.mockResolvedValue(mockBot.services);
|
||||
await mockStore.dispatch(
|
||||
launchEndpointEditor(mockComponentClass, mockBot.services[0])
|
||||
);
|
||||
const { AddOrUpdateService } = SharedConstants.Commands.Bot;
|
||||
expect(dialogServiceSpy).toHaveBeenCalledWith(mockComponentClass, { endpointService: mockBot.services[0] });
|
||||
expect(remoteCallSpy).toHaveBeenCalledWith(AddOrUpdateService, 'abs', mockBot.services[1]);
|
||||
expect(dialogServiceSpy).toHaveBeenCalledWith(mockComponentClass, {
|
||||
endpointService: mockBot.services[0],
|
||||
});
|
||||
expect(remoteCallSpy).toHaveBeenCalledWith(
|
||||
AddOrUpdateService,
|
||||
'abs',
|
||||
mockBot.services[1]
|
||||
);
|
||||
});
|
||||
|
||||
describe(' openEndpointContextMenu', () => {
|
||||
|
@ -84,28 +130,54 @@ describe('The endpoint sagas', () => {
|
|||
{ label: 'Open in Emulator', id: 'open' },
|
||||
{ label: 'Open in portal', id: 'absLink', enabled: jasmine.any(Boolean) },
|
||||
{ label: 'Edit configuration', id: 'edit' },
|
||||
{ label: 'Remove', id: 'forget' }
|
||||
{ label: 'Remove', id: 'forget' },
|
||||
];
|
||||
|
||||
const { DisplayContextMenu, ShowMessageBox } = SharedConstants.Commands.Electron;
|
||||
const {
|
||||
DisplayContextMenu,
|
||||
ShowMessageBox,
|
||||
} = SharedConstants.Commands.Electron;
|
||||
const { NewLiveChat } = SharedConstants.Commands.Emulator;
|
||||
it('should launch the endpoint editor when that menu option is chosen', () => {
|
||||
const commandServiceSpy = jest.spyOn(CommandServiceImpl, 'remoteCall').mockResolvedValue({ id: 'edit' });
|
||||
const dialogServiceSpy = jest.spyOn(DialogService, 'showDialog').mockResolvedValue(mockBot.services);
|
||||
mockStore.dispatch(openEndpointExplorerContextMenu(mockComponentClass, mockBot.services[0]));
|
||||
const commandServiceSpy = jest
|
||||
.spyOn(CommandServiceImpl, 'remoteCall')
|
||||
.mockResolvedValue({ id: 'edit' });
|
||||
const dialogServiceSpy = jest
|
||||
.spyOn(DialogService, 'showDialog')
|
||||
.mockResolvedValue(mockBot.services);
|
||||
mockStore.dispatch(
|
||||
openEndpointExplorerContextMenu(mockComponentClass, mockBot.services[0])
|
||||
);
|
||||
|
||||
expect(commandServiceSpy).toHaveBeenCalledWith(DisplayContextMenu, menuItems);
|
||||
expect(dialogServiceSpy).toHaveBeenCalledWith(mockComponentClass, { endpointService: mockBot.services[0] });
|
||||
expect(commandServiceSpy).toHaveBeenCalledWith(
|
||||
DisplayContextMenu,
|
||||
menuItems
|
||||
);
|
||||
expect(dialogServiceSpy).toHaveBeenCalledWith(mockComponentClass, {
|
||||
endpointService: mockBot.services[0],
|
||||
});
|
||||
});
|
||||
|
||||
it('should open a deep link when that menu option is chosen', async () => {
|
||||
const commandServiceRemoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall')
|
||||
const commandServiceRemoteCallSpy = jest
|
||||
.spyOn(CommandServiceImpl, 'remoteCall')
|
||||
.mockResolvedValue({ id: 'open' });
|
||||
const commandServiceCallSpy = jest.spyOn(CommandServiceImpl, 'call').mockResolvedValue(true);
|
||||
const commandServiceCallSpy = jest
|
||||
.spyOn(CommandServiceImpl, 'call')
|
||||
.mockResolvedValue(true);
|
||||
|
||||
await mockStore.dispatch(openEndpointExplorerContextMenu(mockComponentClass, mockBot.services[0]));
|
||||
expect(commandServiceRemoteCallSpy).toHaveBeenCalledWith(DisplayContextMenu, menuItems);
|
||||
expect(commandServiceCallSpy).toHaveBeenCalledWith(NewLiveChat, mockBot.services[0], false);
|
||||
await mockStore.dispatch(
|
||||
openEndpointExplorerContextMenu(mockComponentClass, mockBot.services[0])
|
||||
);
|
||||
expect(commandServiceRemoteCallSpy).toHaveBeenCalledWith(
|
||||
DisplayContextMenu,
|
||||
menuItems
|
||||
);
|
||||
expect(commandServiceCallSpy).toHaveBeenCalledWith(
|
||||
NewLiveChat,
|
||||
mockBot.services[0],
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('should forget the service when that menu item is chosen', async () => {
|
||||
|
@ -118,28 +190,32 @@ describe('The endpoint sagas', () => {
|
|||
return true;
|
||||
};
|
||||
const { RemoveService } = SharedConstants.Commands.Bot;
|
||||
await mockStore.dispatch(openEndpointExplorerContextMenu(mockComponentClass, mockBot.services[0]));
|
||||
await mockStore.dispatch(
|
||||
openEndpointExplorerContextMenu(mockComponentClass, mockBot.services[0])
|
||||
);
|
||||
await Promise.resolve();
|
||||
expect(remoteCallArgs[0]).toEqual({ commandName: DisplayContextMenu, args: [menuItems] });
|
||||
expect(remoteCallArgs[0]).toEqual({
|
||||
commandName: DisplayContextMenu,
|
||||
args: [menuItems],
|
||||
});
|
||||
expect(remoteCallArgs[1]).toEqual({
|
||||
commandName: ShowMessageBox, args: [
|
||||
commandName: ShowMessageBox,
|
||||
args: [
|
||||
true,
|
||||
{
|
||||
'buttons': [
|
||||
'Cancel',
|
||||
'OK'
|
||||
],
|
||||
'cancelId': 0,
|
||||
'defaultId': 1,
|
||||
'message': 'Remove endpoint https://testbot.botframework.com/api/messagesv3. Are you sure?',
|
||||
'type': 'question'
|
||||
}
|
||||
buttons: ['Cancel', 'OK'],
|
||||
cancelId: 0,
|
||||
defaultId: 1,
|
||||
message:
|
||||
'Remove endpoint https://testbot.botframework.com/api/messagesv3. Are you sure?',
|
||||
type: 'question',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(remoteCallArgs[2]).toEqual({
|
||||
commandName: RemoveService,
|
||||
args: [mockBot.services[0].type, mockBot.services[0].id]
|
||||
args: [mockBot.services[0].type, mockBot.services[0].id],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,9 +32,13 @@
|
|||
//
|
||||
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { IBotService, IEndpointService, ServiceTypes } from 'botframework-config/lib/schema';
|
||||
import {
|
||||
IBotService,
|
||||
IEndpointService,
|
||||
ServiceTypes,
|
||||
} from 'botframework-config/lib/schema';
|
||||
import { ComponentClass } from 'react';
|
||||
import { call, ForkEffect, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { DialogService } from '../../ui/dialogs/service';
|
||||
import { openServiceDeepLink } from '../action/connectedServiceActions';
|
||||
|
@ -44,20 +48,36 @@ import {
|
|||
EndpointServicePayload,
|
||||
LAUNCH_ENDPOINT_EDITOR,
|
||||
OPEN_ENDPOINT_CONTEXT_MENU,
|
||||
OPEN_ENDPOINT_IN_EMULATOR
|
||||
OPEN_ENDPOINT_IN_EMULATOR,
|
||||
} from '../action/endpointServiceActions';
|
||||
import { RootState } from '../store';
|
||||
|
||||
import {
|
||||
call,
|
||||
ForkEffect,
|
||||
put,
|
||||
select,
|
||||
takeEvery,
|
||||
takeLatest,
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
const getConnectedAbs = (state: RootState, endpointAppId: string) => {
|
||||
return (state.bot.activeBot.services || []).find(service => {
|
||||
return service.type === ServiceTypes.Bot && (service as IBotService).appId === endpointAppId;
|
||||
return (
|
||||
service.type === ServiceTypes.Bot &&
|
||||
(service as IBotService).appId === endpointAppId
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
function* launchEndpointEditor(action: EndpointServiceAction<EndpointEditorPayload>): IterableIterator<any> {
|
||||
function* launchEndpointEditor(
|
||||
action: EndpointServiceAction<EndpointEditorPayload>
|
||||
): IterableIterator<any> {
|
||||
const { endpointEditorComponent, endpointService = {} } = action.payload;
|
||||
const servicesToUpdate = yield DialogService.showDialog<ComponentClass<any>, IEndpointService[]>
|
||||
(endpointEditorComponent, { endpointService });
|
||||
const servicesToUpdate = yield DialogService.showDialog<
|
||||
ComponentClass<any>,
|
||||
IEndpointService[]
|
||||
>(endpointEditorComponent, { endpointService });
|
||||
|
||||
if (servicesToUpdate) {
|
||||
const { AddOrUpdateService, RemoveService } = SharedConstants.Commands.Bot;
|
||||
|
@ -68,25 +88,43 @@ function* launchEndpointEditor(action: EndpointServiceAction<EndpointEditorPaylo
|
|||
if (service.type === ServiceTypes.Bot) {
|
||||
// Since we could end up with an invalid ABS
|
||||
// naively validate and remove it if all fields are missing
|
||||
const { serviceName, resourceGroup, subscriptionId, tenantId } = service as IBotService;
|
||||
shouldBeRemoved = !serviceName && !resourceGroup && !subscriptionId && !tenantId;
|
||||
const {
|
||||
serviceName,
|
||||
resourceGroup,
|
||||
subscriptionId,
|
||||
tenantId,
|
||||
} = service as IBotService;
|
||||
shouldBeRemoved =
|
||||
!serviceName && !resourceGroup && !subscriptionId && !tenantId;
|
||||
}
|
||||
yield CommandServiceImpl.remoteCall(shouldBeRemoved ? RemoveService : AddOrUpdateService, service.type, service);
|
||||
yield CommandServiceImpl.remoteCall(
|
||||
shouldBeRemoved ? RemoveService : AddOrUpdateService,
|
||||
service.type,
|
||||
service
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function* openEndpointContextMenu(action: EndpointServiceAction<EndpointServicePayload | EndpointEditorPayload>)
|
||||
: IterableIterator<any> {
|
||||
const connectedAbs = yield select<RootState, string>(getConnectedAbs, action.payload.endpointService.appId);
|
||||
function* openEndpointContextMenu(
|
||||
action: EndpointServiceAction<EndpointServicePayload | EndpointEditorPayload>
|
||||
): IterableIterator<any> {
|
||||
const connectedAbs = yield select<RootState, string>(
|
||||
getConnectedAbs,
|
||||
action.payload.endpointService.appId
|
||||
);
|
||||
const menuItems = [
|
||||
{ label: 'Open in Emulator', id: 'open' },
|
||||
{ label: 'Open in portal', id: 'absLink', enabled: !!connectedAbs },
|
||||
{ label: 'Edit configuration', id: 'edit' },
|
||||
{ label: 'Remove', id: 'forget' }
|
||||
{ label: 'Remove', id: 'forget' },
|
||||
];
|
||||
const { DisplayContextMenu } = SharedConstants.Commands.Electron;
|
||||
const response = yield call(CommandServiceImpl.remoteCall.bind(CommandServiceImpl), DisplayContextMenu, menuItems);
|
||||
const response = yield call(
|
||||
CommandServiceImpl.remoteCall.bind(CommandServiceImpl),
|
||||
DisplayContextMenu,
|
||||
menuItems
|
||||
);
|
||||
switch (response.id) {
|
||||
case 'edit':
|
||||
yield* launchEndpointEditor(action);
|
||||
|
@ -104,27 +142,47 @@ function* openEndpointContextMenu(action: EndpointServiceAction<EndpointServiceP
|
|||
yield* removeEndpointServiceFromActiveBot(action.payload.endpointService);
|
||||
break;
|
||||
|
||||
default: // canceled context menu
|
||||
default:
|
||||
// canceled context menu
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function* openEndpointInEmulator(action: EndpointServiceAction<EndpointServicePayload>): IterableIterator<any> {
|
||||
const { endpointService, focusExistingChatIfAvailable: focusExisting = false } = action.payload;
|
||||
return CommandServiceImpl.call(SharedConstants.Commands.Emulator.NewLiveChat, endpointService, focusExisting);
|
||||
// eslint-disable-next-line require-yield
|
||||
function* openEndpointInEmulator(
|
||||
action: EndpointServiceAction<EndpointServicePayload>
|
||||
): IterableIterator<any> {
|
||||
const {
|
||||
endpointService,
|
||||
focusExistingChatIfAvailable: focusExisting = false,
|
||||
} = action.payload;
|
||||
return CommandServiceImpl.call(
|
||||
SharedConstants.Commands.Emulator.NewLiveChat,
|
||||
endpointService,
|
||||
focusExisting
|
||||
);
|
||||
}
|
||||
|
||||
function* removeEndpointServiceFromActiveBot(endpointService: IEndpointService): IterableIterator<any> {
|
||||
const result = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.ShowMessageBox, true, {
|
||||
type: 'question',
|
||||
buttons: ['Cancel', 'OK'],
|
||||
defaultId: 1,
|
||||
message: `Remove endpoint ${endpointService.name}. Are you sure?`,
|
||||
cancelId: 0,
|
||||
});
|
||||
function* removeEndpointServiceFromActiveBot(
|
||||
endpointService: IEndpointService
|
||||
): IterableIterator<any> {
|
||||
const result = yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.ShowMessageBox,
|
||||
true,
|
||||
{
|
||||
type: 'question',
|
||||
buttons: ['Cancel', 'OK'],
|
||||
defaultId: 1,
|
||||
message: `Remove endpoint ${endpointService.name}. Are you sure?`,
|
||||
cancelId: 0,
|
||||
}
|
||||
);
|
||||
if (result) {
|
||||
yield CommandServiceImpl
|
||||
.remoteCall(SharedConstants.Commands.Bot.RemoveService, endpointService.type, endpointService.id);
|
||||
yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Bot.RemoveService,
|
||||
endpointService.type,
|
||||
endpointService.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,14 +31,14 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { azureAuthSagas } from './azureAuthSaga';
|
||||
import { botSagas } from './botSagas';
|
||||
import { editorSagas } from './editorSagas';
|
||||
import { endpointSagas } from './endpointSagas';
|
||||
import { servicesExplorerSagas } from './servicesExplorerSagas';
|
||||
import { navBarSagas } from './navBarSagas';
|
||||
import { notificationSagas } from './notificationSagas';
|
||||
import { azureAuthSagas } from './azureAuthSaga';
|
||||
import { resourceSagas } from './resourcesSagas';
|
||||
import { servicesExplorerSagas } from './servicesExplorerSagas';
|
||||
import { welcomePageSagas } from './welcomePageSagas';
|
||||
|
||||
export const applicationSagas = [
|
||||
|
@ -50,5 +50,5 @@ export const applicationSagas = [
|
|||
navBarSagas,
|
||||
notificationSagas,
|
||||
resourceSagas,
|
||||
welcomePageSagas
|
||||
welcomePageSagas,
|
||||
];
|
||||
|
|
|
@ -31,31 +31,32 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { markNotificationsAsRead } from './navBarSagas';
|
||||
import { select } from '../action/navBarActions';
|
||||
import * as Constants from '../../constants';
|
||||
import { markAllAsRead } from '../action/notificationActions';
|
||||
|
||||
import { markNotificationsAsRead } from './navBarSagas';
|
||||
|
||||
import { put } from 'redux-saga/effects';
|
||||
jest.mock('../../ui/dialogs', () => ({
|
||||
AzureLoginPromptDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
AzureLoginSuccessDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
BotCreationDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
DialogService: { showDialog: () => Promise.resolve(true) },
|
||||
SecretPromptDialog: function mock() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
));
|
||||
AzureLoginPromptDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
AzureLoginSuccessDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
BotCreationDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
DialogService: { showDialog: () => Promise.resolve(true) },
|
||||
SecretPromptDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
}));
|
||||
jest.mock('../../platform/commands/commandServiceImpl', () => ({
|
||||
CommandServiceImpl: {
|
||||
remoteCall: () => Promise.resolve(true)
|
||||
}
|
||||
remoteCall: () => Promise.resolve(true),
|
||||
},
|
||||
}));
|
||||
describe('Nav bar sagas', () => {
|
||||
test('markNotificationsAsRead()', () => {
|
||||
|
|
|
@ -31,13 +31,16 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { ForkEffect, put, takeEvery, } from 'redux-saga/effects';
|
||||
import * as Constants from '../../constants';
|
||||
import { NavBarActions, SelectNavBarAction } from '../action/navBarActions';
|
||||
import { markAllAsRead } from '../action/notificationActions';
|
||||
import * as Constants from '../../constants';
|
||||
|
||||
import { ForkEffect, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
/** Marks all notifications as read if the notifications pane is opened */
|
||||
export function* markNotificationsAsRead(action: SelectNavBarAction): IterableIterator<any> {
|
||||
export function* markNotificationsAsRead(
|
||||
action: SelectNavBarAction
|
||||
): IterableIterator<any> {
|
||||
const navBarSelection = action.payload.selection;
|
||||
if (navBarSelection === Constants.NAVBAR_NOTIFICATIONS) {
|
||||
yield put(markAllAsRead());
|
||||
|
|
|
@ -31,23 +31,26 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { put } from 'redux-saga/effects';
|
||||
import { newNotification, NotificationType } from '@bfemulator/app-shared';
|
||||
|
||||
import { NotificationManager } from '../../notificationManager';
|
||||
import {
|
||||
beginAdd,
|
||||
finishAdd,
|
||||
finishClear,
|
||||
beginRemove,
|
||||
finishRemove
|
||||
finishRemove,
|
||||
} from '../action/notificationActions';
|
||||
|
||||
import {
|
||||
addNotification,
|
||||
clearNotifications,
|
||||
removeNotification,
|
||||
markAllAsRead
|
||||
markAllAsRead,
|
||||
} from './notificationSagas';
|
||||
|
||||
import { put } from 'redux-saga/effects';
|
||||
|
||||
describe('Notification sagas', () => {
|
||||
test('addNotification()', () => {
|
||||
const notification = newNotification('someMessage', NotificationType.Info);
|
||||
|
|
|
@ -34,18 +34,21 @@
|
|||
import { NotificationManager } from '../../notificationManager';
|
||||
import {
|
||||
BeginAddNotificationAction,
|
||||
NotificationActions,
|
||||
BeginRemoveNotificationAction,
|
||||
finishAdd,
|
||||
finishClear,
|
||||
finishRemove
|
||||
finishRemove,
|
||||
NotificationActions,
|
||||
} from '../action/notificationActions';
|
||||
import { ForkEffect, takeEvery, put } from 'redux-saga/effects';
|
||||
|
||||
import { ForkEffect, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
/** Adds a notification to the notification manager then
|
||||
* adds it to the state store
|
||||
*/
|
||||
export function* addNotification(action: BeginAddNotificationAction): IterableIterator<any> {
|
||||
export function* addNotification(
|
||||
action: BeginAddNotificationAction
|
||||
): IterableIterator<any> {
|
||||
const { notification } = action.payload;
|
||||
NotificationManager.set(notification.id, notification);
|
||||
yield put(finishAdd(notification));
|
||||
|
@ -62,7 +65,9 @@ export function* clearNotifications(): IterableIterator<any> {
|
|||
/** Removes a single notification from the notification manager then
|
||||
* removes it from the state store
|
||||
*/
|
||||
export function* removeNotification(action: BeginRemoveNotificationAction): IterableIterator<any> {
|
||||
export function* removeNotification(
|
||||
action: BeginRemoveNotificationAction
|
||||
): IterableIterator<any> {
|
||||
const { id: notificationId } = action.payload;
|
||||
NotificationManager.delete(notificationId);
|
||||
yield put(finishRemove(notificationId));
|
||||
|
|
|
@ -1,32 +1,71 @@
|
|||
import { resources } from '../reducer/resourcesReducer';
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { applyMiddleware, combineReducers, createStore } from 'redux';
|
||||
import { resourceSagas } from './resourcesSagas';
|
||||
import { BotConfigWithPathImpl } from '@bfemulator/sdk-shared';
|
||||
import { ServiceTypes } from 'botframework-config/lib/schema';
|
||||
import sagaMiddlewareFactory from 'redux-saga';
|
||||
import { SharedConstants } from '@bfemulator/app-shared/built';
|
||||
import { Component } from 'react';
|
||||
|
||||
import {
|
||||
openContextMenuForResource,
|
||||
openResource,
|
||||
openResourcesSettings,
|
||||
renameResource
|
||||
renameResource,
|
||||
} from '../action/resourcesAction';
|
||||
import { SharedConstants } from '@bfemulator/app-shared/built';
|
||||
import { Component } from 'react';
|
||||
import { resources } from '../reducer/resourcesReducer';
|
||||
|
||||
import { resourceSagas } from './resourcesSagas';
|
||||
|
||||
const sagaMiddleWare = sagaMiddlewareFactory();
|
||||
const mockStore = createStore(combineReducers({ resources }), {}, applyMiddleware(sagaMiddleWare));
|
||||
const mockStore = createStore(
|
||||
combineReducers({ resources }),
|
||||
{},
|
||||
applyMiddleware(sagaMiddleWare)
|
||||
);
|
||||
sagaMiddleWare.run(resourceSagas);
|
||||
|
||||
jest.mock('../store', () => ({
|
||||
get store() {
|
||||
return mockStore;
|
||||
}
|
||||
},
|
||||
}));
|
||||
jest.mock('../../ui/dialogs/service', () => ({
|
||||
DialogService: {
|
||||
showDialog: () => Promise.resolve(true),
|
||||
hideDialog: () => Promise.resolve({ path: 'somePath' }),
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const mockRemoteCommandsCalled = [];
|
||||
|
@ -50,12 +89,11 @@ jest.mock('../../platform/commands/commandServiceImpl', () => ({
|
|||
},
|
||||
call: async (commandName: string, ...args: any[]) => {
|
||||
mockLocalCommandsCalled.push({ commandName, args: args });
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('The ResourceSagas', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mockRemoteCommandsCalled.length = 0;
|
||||
mockLocalCommandsCalled.length = 0;
|
||||
|
@ -67,7 +105,7 @@ describe('The ResourceSagas', () => {
|
|||
mockResource = BotConfigWithPathImpl.serviceFromJSON({
|
||||
type: ServiceTypes.File,
|
||||
path: 'the/file/path',
|
||||
name: 'testChat'
|
||||
name: 'testChat',
|
||||
} as any);
|
||||
});
|
||||
|
||||
|
@ -77,31 +115,31 @@ describe('The ResourceSagas', () => {
|
|||
expect(mockRemoteCommandsCalled.length).toBe(2);
|
||||
[
|
||||
{
|
||||
'commandName': 'electron:display-context-menu',
|
||||
'args': [
|
||||
commandName: 'electron:display-context-menu',
|
||||
args: [
|
||||
[
|
||||
{
|
||||
'label': 'Open file location',
|
||||
'id': 0
|
||||
label: 'Open file location',
|
||||
id: 0,
|
||||
},
|
||||
{
|
||||
'label': 'Rename',
|
||||
'id': 1
|
||||
label: 'Rename',
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
'label': 'Delete',
|
||||
'id': 2
|
||||
}
|
||||
]
|
||||
]
|
||||
label: 'Delete',
|
||||
id: 2,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
'commandName': 'shell:open-file-location',
|
||||
'args': [
|
||||
'the/file/path'
|
||||
]
|
||||
}
|
||||
].forEach((command, index) => expect(mockRemoteCommandsCalled[index]).toEqual(command));
|
||||
commandName: 'shell:open-file-location',
|
||||
args: ['the/file/path'],
|
||||
},
|
||||
].forEach((command, index) =>
|
||||
expect(mockRemoteCommandsCalled[index]).toEqual(command)
|
||||
);
|
||||
});
|
||||
|
||||
it('and put the resource in the store as the "resourceToRename" property when "edit" is chosen', async () => {
|
||||
|
@ -118,25 +156,40 @@ describe('The ResourceSagas', () => {
|
|||
expect(mockRemoteCommandsCalled.length).toBe(3);
|
||||
[
|
||||
{
|
||||
'commandName': 'electron:display-context-menu',
|
||||
'args': [[{ 'label': 'Open file location', 'id': 0 }, { 'label': 'Rename', 'id': 1 }, {
|
||||
'label': 'Delete',
|
||||
'id': 2
|
||||
}]]
|
||||
}, {
|
||||
'commandName': 'shell:showExplorer-message-box',
|
||||
'args': [true, {
|
||||
'type': 'info',
|
||||
'title': 'Delete this file',
|
||||
'buttons': ['Cancel', 'Delete'],
|
||||
'defaultId': 1,
|
||||
'message': 'This action cannot be undone. Are you sure you want to delete testChat?',
|
||||
'cancelId': 0
|
||||
}]
|
||||
}, {
|
||||
'commandName': 'shell:unlink-file',
|
||||
'args': ['the/file/path']
|
||||
}].forEach((command, index) => expect(mockRemoteCommandsCalled[index]).toEqual(command));
|
||||
commandName: 'electron:display-context-menu',
|
||||
args: [
|
||||
[
|
||||
{ label: 'Open file location', id: 0 },
|
||||
{ label: 'Rename', id: 1 },
|
||||
{
|
||||
label: 'Delete',
|
||||
id: 2,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
commandName: 'shell:showExplorer-message-box',
|
||||
args: [
|
||||
true,
|
||||
{
|
||||
type: 'info',
|
||||
title: 'Delete this file',
|
||||
buttons: ['Cancel', 'Delete'],
|
||||
defaultId: 1,
|
||||
message:
|
||||
'This action cannot be undone. Are you sure you want to delete testChat?',
|
||||
cancelId: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
commandName: 'shell:unlink-file',
|
||||
args: ['the/file/path'],
|
||||
},
|
||||
].forEach((command, index) =>
|
||||
expect(mockRemoteCommandsCalled[index]).toEqual(command)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -146,7 +199,7 @@ describe('The ResourceSagas', () => {
|
|||
mockResource = BotConfigWithPathImpl.serviceFromJSON({
|
||||
type: ServiceTypes.File,
|
||||
path: 'the/file/path',
|
||||
name: 'testChat'
|
||||
name: 'testChat',
|
||||
} as any);
|
||||
});
|
||||
|
||||
|
@ -155,15 +208,18 @@ describe('The ResourceSagas', () => {
|
|||
await mockStore.dispatch(renameResource(mockResource));
|
||||
expect(mockRemoteCommandsCalled.length).toBe(1);
|
||||
expect(mockRemoteCommandsCalled[0]).toEqual({
|
||||
'commandName': 'shell:showExplorer-message-box',
|
||||
'args': [true, {
|
||||
'type': 'error',
|
||||
'title': 'Invalid file name',
|
||||
'buttons': ['Ok'],
|
||||
'defaultId': 1,
|
||||
'message': 'A valid file name must be used',
|
||||
'cancelId': 0
|
||||
}]
|
||||
commandName: 'shell:showExplorer-message-box',
|
||||
args: [
|
||||
true,
|
||||
{
|
||||
type: 'error',
|
||||
title: 'Invalid file name',
|
||||
buttons: ['Ok'],
|
||||
defaultId: 1,
|
||||
message: 'A valid file name must be used',
|
||||
cancelId: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -171,11 +227,14 @@ describe('The ResourceSagas', () => {
|
|||
await mockStore.dispatch(renameResource(mockResource));
|
||||
expect(mockRemoteCommandsCalled.length).toBe(1);
|
||||
expect(mockRemoteCommandsCalled[0]).toEqual({
|
||||
'args': [{
|
||||
'name': 'testChat',
|
||||
'path': 'the/file/path',
|
||||
'type': 'file'
|
||||
}], 'commandName': 'shell:rename-file'
|
||||
args: [
|
||||
{
|
||||
name: 'testChat',
|
||||
path: 'the/file/path',
|
||||
type: 'file',
|
||||
},
|
||||
],
|
||||
commandName: 'shell:rename-file',
|
||||
});
|
||||
const { resourceToRename } = (mockStore.getState() as any).resources;
|
||||
expect(resourceToRename).toBeNull();
|
||||
|
@ -188,36 +247,39 @@ describe('The ResourceSagas', () => {
|
|||
mockResource = BotConfigWithPathImpl.serviceFromJSON({
|
||||
type: ServiceTypes.File,
|
||||
path: 'the/file/path/chat.chat',
|
||||
name: 'testChat'
|
||||
name: 'testChat',
|
||||
} as any);
|
||||
});
|
||||
|
||||
it('should open a chat file', async () => {
|
||||
await mockStore.dispatch(openResource(mockResource as any));
|
||||
expect(mockLocalCommandsCalled).toEqual([{
|
||||
'commandName': 'chat:open',
|
||||
'args': ['the/file/path/chat.chat', true]
|
||||
}]);
|
||||
expect(mockLocalCommandsCalled).toEqual([
|
||||
{
|
||||
commandName: 'chat:open',
|
||||
args: ['the/file/path/chat.chat', true],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should open a transcript file', async () => {
|
||||
mockResource.path = 'the/file/path/transcript.transcript';
|
||||
await mockStore.dispatch(openResource(mockResource as any));
|
||||
expect(mockLocalCommandsCalled).toEqual([{
|
||||
'commandName': 'transcript:open',
|
||||
'args': ['the/file/path/transcript.transcript']
|
||||
}]);
|
||||
expect(mockLocalCommandsCalled).toEqual([
|
||||
{
|
||||
commandName: 'transcript:open',
|
||||
args: ['the/file/path/transcript.transcript'],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should open the resource settings dialog and process the results as expected', async () => {
|
||||
const mockClass = class extends Component {
|
||||
};
|
||||
const mockClass = class extends Component {};
|
||||
await mockStore.dispatch(openResourcesSettings({ dialog: mockClass }));
|
||||
await Promise.resolve();
|
||||
|
||||
expect(mockRemoteCommandsCalled).toEqual(
|
||||
[{ commandName: 'bot:list:patch', args: [undefined, true] }]
|
||||
);
|
||||
expect(mockRemoteCommandsCalled).toEqual([
|
||||
{ commandName: 'bot:list:patch', args: [undefined, true] },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,31 +1,79 @@
|
|||
import { ForkEffect, put, takeEvery } from 'redux-saga/effects';
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import {
|
||||
BotInfo,
|
||||
isChatFile,
|
||||
isTranscriptFile,
|
||||
NotificationType,
|
||||
SharedConstants,
|
||||
} from '@bfemulator/app-shared';
|
||||
import { newNotification } from '@bfemulator/app-shared/built';
|
||||
import { IFileService } from 'botframework-config/lib/schema';
|
||||
import { ComponentClass } from 'react';
|
||||
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { DialogService } from '../../ui/dialogs/service';
|
||||
import { beginAdd } from '../action/notificationActions';
|
||||
import {
|
||||
editResource,
|
||||
OPEN_CONTEXT_MENU_FOR_RESOURCE,
|
||||
OPEN_RESOURCE,
|
||||
OPEN_RESOURCE_SETTINGS,
|
||||
RENAME_RESOURCE,
|
||||
ResourcesAction
|
||||
ResourcesAction,
|
||||
} from '../action/resourcesAction';
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { BotInfo, isChatFile, isTranscriptFile, NotificationType, SharedConstants } from '@bfemulator/app-shared';
|
||||
import { IFileService } from 'botframework-config/lib/schema';
|
||||
import { ComponentClass } from 'react';
|
||||
import { DialogService } from '../../ui/dialogs/service';
|
||||
import { beginAdd } from '../action/notificationActions';
|
||||
import { newNotification } from '@bfemulator/app-shared/built';
|
||||
|
||||
function* openContextMenuForResource(action: ResourcesAction<IFileService>): IterableIterator<any> {
|
||||
import { ForkEffect, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
function* openContextMenuForResource(
|
||||
action: ResourcesAction<IFileService>
|
||||
): IterableIterator<any> {
|
||||
const menuItems = [
|
||||
{ label: 'Open file location', id: 0 },
|
||||
{ label: 'Rename', id: 1 },
|
||||
{ label: 'Delete', id: 2 }
|
||||
{ label: 'Delete', id: 2 },
|
||||
];
|
||||
|
||||
const result = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.DisplayContextMenu, menuItems);
|
||||
const result = yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.DisplayContextMenu,
|
||||
menuItems
|
||||
);
|
||||
switch (result.id) {
|
||||
case 0:
|
||||
yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.OpenFileLocation, action.payload.path);
|
||||
yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.OpenFileLocation,
|
||||
action.payload.path
|
||||
);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
|
@ -42,7 +90,9 @@ function* openContextMenuForResource(action: ResourcesAction<IFileService>): Ite
|
|||
}
|
||||
}
|
||||
|
||||
function* deleteFile(action: ResourcesAction<IFileService>): IterableIterator<any> {
|
||||
function* deleteFile(
|
||||
action: ResourcesAction<IFileService>
|
||||
): IterableIterator<any> {
|
||||
const { name, path } = action.payload;
|
||||
const { ShowMessageBox, UnlinkFile } = SharedConstants.Commands.Electron;
|
||||
const result = yield CommandServiceImpl.remoteCall(ShowMessageBox, true, {
|
||||
|
@ -75,7 +125,9 @@ function* doRename(action: ResourcesAction<IFileService>) {
|
|||
yield put(editResource(null));
|
||||
}
|
||||
|
||||
function* doOpenResource(action: ResourcesAction<IFileService>): IterableIterator<any> {
|
||||
function* doOpenResource(
|
||||
action: ResourcesAction<IFileService>
|
||||
): IterableIterator<any> {
|
||||
const { OpenChatFile, OpenTranscript } = SharedConstants.Commands.Emulator;
|
||||
const { path } = action.payload;
|
||||
if (isChatFile(path)) {
|
||||
|
@ -86,13 +138,24 @@ function* doOpenResource(action: ResourcesAction<IFileService>): IterableIterato
|
|||
// unknown types just fall into the abyss
|
||||
}
|
||||
|
||||
function* launchResourcesSettingsModal(action: ResourcesAction<{ dialog: ComponentClass<any> }>) {
|
||||
const result: Partial<BotInfo> = yield DialogService.showDialog(action.payload.dialog);
|
||||
function* launchResourcesSettingsModal(
|
||||
action: ResourcesAction<{ dialog: ComponentClass<any> }>
|
||||
) {
|
||||
const result: Partial<BotInfo> = yield DialogService.showDialog(
|
||||
action.payload.dialog
|
||||
);
|
||||
if (result) {
|
||||
try {
|
||||
yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Bot.PatchBotList, result.path, result);
|
||||
yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Bot.PatchBotList,
|
||||
result.path,
|
||||
result
|
||||
);
|
||||
} catch (e) {
|
||||
const notification = newNotification('Unable to save resource settings', NotificationType.Error);
|
||||
const notification = newNotification(
|
||||
'Unable to save resource settings',
|
||||
NotificationType.Error
|
||||
);
|
||||
yield put(beginAdd(notification));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,46 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { ServiceCodes, SharedConstants } from '@bfemulator/app-shared';
|
||||
import { ServiceTypes } from 'botframework-config/lib/schema';
|
||||
import { applyMiddleware, combineReducers, createStore } from 'redux';
|
||||
import sagaMiddlewareFactory from 'redux-saga';
|
||||
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import {
|
||||
AzureLoginFailedDialogContainer,
|
||||
AzureLoginSuccessDialogContainer,
|
||||
ConnectServicePromptDialogContainer,
|
||||
GetStartedWithCSDialogContainer
|
||||
GetStartedWithCSDialogContainer,
|
||||
} from '../../ui/dialogs';
|
||||
import { DialogService } from '../../ui/dialogs/service'; // β£β£ careful! β£β£
|
||||
import { ConnectedServicePickerContainer } from '../../ui/shell/explorer/servicesExplorer';
|
||||
|
@ -21,62 +54,71 @@ import {
|
|||
launchConnectedServicePicker,
|
||||
openAddServiceContextMenu,
|
||||
openContextMenuForConnectedService,
|
||||
openServiceDeepLink
|
||||
openServiceDeepLink,
|
||||
} from '../action/connectedServiceActions';
|
||||
import { azureAuth } from '../reducer/azureAuthReducer';
|
||||
import { bot } from '../reducer/bot';
|
||||
|
||||
import { servicesExplorerSagas } from './servicesExplorerSagas';
|
||||
|
||||
const sagaMiddleWare = sagaMiddlewareFactory();
|
||||
const mockStore = createStore(combineReducers({ azureAuth, bot }), {}, applyMiddleware(sagaMiddleWare));
|
||||
const mockStore = createStore(
|
||||
combineReducers({ azureAuth, bot }),
|
||||
{},
|
||||
applyMiddleware(sagaMiddleWare)
|
||||
);
|
||||
sagaMiddleWare.run(servicesExplorerSagas);
|
||||
|
||||
const mockArmToken = 'bm90aGluZw==.eyJ1cG4iOiJnbGFzZ293QHNjb3RsYW5kLmNvbSJ9.7gjdshgfdsk98458205jfds9843fjds';
|
||||
const mockArmToken =
|
||||
'bm90aGluZw==.eyJ1cG4iOiJnbGFzZ293QHNjb3RsYW5kLmNvbSJ9.7gjdshgfdsk98458205jfds9843fjds';
|
||||
|
||||
jest.mock('../../ui/dialogs', () => ({
|
||||
AzureLoginPromptDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
AzureLoginSuccessDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
BotCreationDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
DialogService: {
|
||||
showDialog: () => Promise.resolve(true)
|
||||
},
|
||||
SecretPromptDialog: function mock() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
jest.mock('../../ui/shell/explorer/servicesExplorer/connectedServiceEditor', () => ({
|
||||
ConnectedServiceEditorContainer: function mock() {
|
||||
AzureLoginPromptDialogContainer: function mock() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
AzureLoginSuccessDialogContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
BotCreationDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
DialogService: {
|
||||
showDialog: () => Promise.resolve(true),
|
||||
},
|
||||
SecretPromptDialog: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'../../ui/shell/explorer/servicesExplorer/connectedServiceEditor',
|
||||
() => ({
|
||||
ConnectedServiceEditorContainer: function mock() {
|
||||
return undefined;
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
jest.mock('../../ui/shell/explorer/servicesExplorer', () => ({
|
||||
ConnectedServicePicker: function mock() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../store', () => ({
|
||||
get store() {
|
||||
return mockStore;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('./azureAuthSaga', () => ({
|
||||
getArmToken: function* () {
|
||||
getArmToken: function*() {
|
||||
// eslint-disable-next-line typescript/camelcase
|
||||
yield { access_token: mockArmToken };
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
CommandServiceImpl.remoteCall = async function (type: string) {
|
||||
CommandServiceImpl.remoteCall = async function(type: string) {
|
||||
switch (type) {
|
||||
case SharedConstants.Commands.ConnectedService.GetConnectedServicesByType:
|
||||
return { services: [{ id: 'a luis service' }], code: ServiceCodes.OK };
|
||||
|
@ -96,24 +138,29 @@ describe('The ServiceExplorerSagas', () => {
|
|||
azureAuthWorkflowComponents: {
|
||||
loginFailedDialog: AzureLoginFailedDialogContainer,
|
||||
loginSuccessDialog: AzureLoginSuccessDialogContainer,
|
||||
promptDialog: ConnectServicePromptDialogContainer
|
||||
promptDialog: ConnectServicePromptDialogContainer,
|
||||
},
|
||||
getStartedDialog: GetStartedWithCSDialogContainer,
|
||||
editorComponent: ConnectedServiceEditorContainer,
|
||||
pickerComponent: ConnectedServicePickerContainer,
|
||||
};
|
||||
launchConnectedServicePickerGen = servicesExplorerSagas().next().value.FORK.args[1];
|
||||
launchConnectedServicePickerGen = servicesExplorerSagas().next().value
|
||||
.FORK.args[1];
|
||||
mockStore.dispatch(azureArmTokenDataChanged(mockArmToken));
|
||||
});
|
||||
|
||||
it('should retrieve the arm token from the store', () => {
|
||||
const token = launchConnectedServicePickerGen().next().value.SELECT.selector(mockStore.getState());
|
||||
const token = launchConnectedServicePickerGen()
|
||||
.next()
|
||||
.value.SELECT.selector(mockStore.getState());
|
||||
expect(token.access_token).toBe(mockArmToken);
|
||||
});
|
||||
|
||||
it('should prompt the user to login if the armToken does not exist in the store', () => {
|
||||
mockStore.dispatch(azureArmTokenDataChanged(''));
|
||||
const it = launchConnectedServicePickerGen(launchConnectedServicePicker(payload));
|
||||
const it = launchConnectedServicePickerGen(
|
||||
launchConnectedServicePicker(payload)
|
||||
);
|
||||
let token = it.next().value.SELECT.selector(mockStore.getState());
|
||||
expect(token.access_token).toBe('');
|
||||
token = it.next().value;
|
||||
|
@ -133,7 +180,8 @@ describe('The ServiceExplorerSagas', () => {
|
|||
});
|
||||
|
||||
it('should launch the luis models picklist after the luis models are retrieved', async () => {
|
||||
DialogService.showDialog = () => Promise.resolve([{ id: 'a new service to add' }]) as any;
|
||||
DialogService.showDialog = () =>
|
||||
Promise.resolve([{ id: 'a new service to add' }]) as any;
|
||||
const action = launchConnectedServicePicker(payload);
|
||||
const it = launchConnectedServicePickerGen(action);
|
||||
let token = it.next().value.SELECT.selector(mockStore.getState());
|
||||
|
@ -162,7 +210,8 @@ describe('The ServiceExplorerSagas', () => {
|
|||
mockStore.dispatch(load([mockBot]));
|
||||
mockStore.dispatch(setActive(mockBot));
|
||||
|
||||
DialogService.showDialog = () => Promise.resolve([{ id: 'a new service to add' }]) as any;
|
||||
DialogService.showDialog = () =>
|
||||
Promise.resolve([{ id: 'a new service to add' }]) as any;
|
||||
const action = launchConnectedServicePicker(payload);
|
||||
const it = launchConnectedServicePickerGen(action);
|
||||
let token = it.next().value.SELECT.selector(mockStore.getState());
|
||||
|
@ -171,10 +220,12 @@ describe('The ServiceExplorerSagas', () => {
|
|||
const luisModels = await it.next(token).value;
|
||||
|
||||
const newModels = await it.next(luisModels).value;
|
||||
const botConfig = it.next(newModels).value.SELECT.selector(mockStore.getState());
|
||||
const botConfig = it
|
||||
.next(newModels)
|
||||
.value.SELECT.selector(mockStore.getState());
|
||||
let _type;
|
||||
let _args;
|
||||
CommandServiceImpl.remoteCall = function (type: string, ...args: any[]) {
|
||||
CommandServiceImpl.remoteCall = function(type: string, ...args: any[]) {
|
||||
_type = type;
|
||||
_args = args;
|
||||
return Promise.resolve(true);
|
||||
|
@ -202,8 +253,9 @@ describe('The ServiceExplorerSagas', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
const sagaIt = servicesExplorerSagas();
|
||||
action = openContextMenuForConnectedService<ConnectedServiceAction<ConnectedServicePayload>>
|
||||
(ConnectedServiceEditorContainer, mockService);
|
||||
action = openContextMenuForConnectedService<
|
||||
ConnectedServiceAction<ConnectedServicePayload>
|
||||
>(ConnectedServiceEditorContainer, mockService);
|
||||
let i = 4;
|
||||
while (i--) {
|
||||
contextMenuGen = sagaIt.next().value.FORK.args[1];
|
||||
|
@ -222,7 +274,9 @@ describe('The ServiceExplorerSagas', () => {
|
|||
await it.next(result).value;
|
||||
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
`https://luis.ai/applications/${ mockService.appId }/versions/${ mockService.version }/build`
|
||||
`https://luis.ai/applications/${mockService.appId}/versions/${
|
||||
mockService.version
|
||||
}/build`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -369,7 +423,7 @@ describe('The ServiceExplorerSagas', () => {
|
|||
azureAuthWorkflowComponents: {
|
||||
loginFailedDialog: AzureLoginFailedDialogContainer,
|
||||
loginSuccessDialog: AzureLoginSuccessDialogContainer,
|
||||
promptDialog: ConnectServicePromptDialogContainer
|
||||
promptDialog: ConnectServicePromptDialogContainer,
|
||||
},
|
||||
getStartedDialog: GetStartedWithCSDialogContainer,
|
||||
editorComponent: ConnectedServiceEditorContainer,
|
||||
|
@ -396,7 +450,12 @@ describe('The ServiceExplorerSagas', () => {
|
|||
});
|
||||
|
||||
describe('openConnectedServiceDeepLink', () => {
|
||||
const mockModel = { type: ServiceTypes.Luis, appId: '1234', version: '0.1', region: 'westeurope' };
|
||||
const mockModel = {
|
||||
type: ServiceTypes.Luis,
|
||||
appId: '1234',
|
||||
version: '0.1',
|
||||
region: 'westeurope',
|
||||
};
|
||||
let openConnectedServiceGen;
|
||||
beforeEach(() => {
|
||||
const sagaIt = servicesExplorerSagas();
|
||||
|
@ -435,61 +494,67 @@ describe('The ServiceExplorerSagas', () => {
|
|||
it('should open the correct service url for CosmosDB services', () => {
|
||||
window.open = jest.fn();
|
||||
const mock = {
|
||||
'type': ServiceTypes.CosmosDB,
|
||||
'collection': 'fdsa',
|
||||
'database': 'fsa',
|
||||
'endpoint': 'fsda',
|
||||
'id': '206',
|
||||
'name': 'Cosmos',
|
||||
'resourceGroup': 'db-service',
|
||||
'serviceName': 'cosmosdb',
|
||||
'subscriptionId': '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd',
|
||||
'tenantId': 'microsoft.com'
|
||||
type: ServiceTypes.CosmosDB,
|
||||
collection: 'fdsa',
|
||||
database: 'fsa',
|
||||
endpoint: 'fsda',
|
||||
id: '206',
|
||||
name: 'Cosmos',
|
||||
resourceGroup: 'db-service',
|
||||
serviceName: 'cosmosdb',
|
||||
subscriptionId: '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd',
|
||||
tenantId: 'microsoft.com',
|
||||
};
|
||||
|
||||
const action = openServiceDeepLink(mock as any);
|
||||
openConnectedServiceGen(action).next();
|
||||
expect(window.open).toHaveBeenCalledWith('https://ms.portal.azure.com/#@microsoft.com/resource/subscriptions/' +
|
||||
'0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd/resourceGroups/db-service/providers/Microsoft.DocumentDb/' +
|
||||
'databaseAccounts/cosmosdb/overview');
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'https://ms.portal.azure.com/#@microsoft.com/resource/subscriptions/' +
|
||||
'0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd/resourceGroups/db-service/providers/Microsoft.DocumentDb/' +
|
||||
'databaseAccounts/cosmosdb/overview'
|
||||
);
|
||||
});
|
||||
|
||||
it('should open the correct service url for BlobStorage services', () => {
|
||||
window.open = jest.fn();
|
||||
const mock = {
|
||||
'type': ServiceTypes.BlobStorage,
|
||||
'id': '206',
|
||||
'name': 'Blob',
|
||||
'resourceGroup': 'blob-service',
|
||||
'serviceName': 'blob',
|
||||
'subscriptionId': '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd',
|
||||
'tenantId': 'microsoft.com'
|
||||
type: ServiceTypes.BlobStorage,
|
||||
id: '206',
|
||||
name: 'Blob',
|
||||
resourceGroup: 'blob-service',
|
||||
serviceName: 'blob',
|
||||
subscriptionId: '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd',
|
||||
tenantId: 'microsoft.com',
|
||||
};
|
||||
|
||||
const action = openServiceDeepLink(mock as any);
|
||||
openConnectedServiceGen(action).next();
|
||||
expect(window.open).toHaveBeenCalledWith('https://ms.portal.azure.com/#@microsoft.com/resource/subscriptions/' +
|
||||
'0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd/resourceGroups/blob-service/providers/Microsoft.DocumentDB/' +
|
||||
'storageAccounts/blob/overview');
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'https://ms.portal.azure.com/#@microsoft.com/resource/subscriptions/' +
|
||||
'0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd/resourceGroups/blob-service/providers/Microsoft.DocumentDB/' +
|
||||
'storageAccounts/blob/overview'
|
||||
);
|
||||
});
|
||||
|
||||
it('should open the correct service url for AppInsights services', () => {
|
||||
window.open = jest.fn();
|
||||
const mock = {
|
||||
'type': ServiceTypes.AppInsights,
|
||||
'id': '206',
|
||||
'name': 'appInsights',
|
||||
'resourceGroup': 'appInsights-service',
|
||||
'serviceName': 'appInsights',
|
||||
'subscriptionId': '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd',
|
||||
'tenantId': 'microsoft.com'
|
||||
type: ServiceTypes.AppInsights,
|
||||
id: '206',
|
||||
name: 'appInsights',
|
||||
resourceGroup: 'appInsights-service',
|
||||
serviceName: 'appInsights',
|
||||
subscriptionId: '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd',
|
||||
tenantId: 'microsoft.com',
|
||||
};
|
||||
|
||||
const action = openServiceDeepLink(mock as any);
|
||||
openConnectedServiceGen(action).next();
|
||||
expect(window.open).toHaveBeenCalledWith('https://ms.portal.azure.com/#@microsoft.com/resource/subscriptions/' +
|
||||
'0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd/resourceGroups/appInsights-service/providers/microsoft.insights/' +
|
||||
'components/appInsights/overview');
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'https://ms.portal.azure.com/#@microsoft.com/resource/subscriptions/' +
|
||||
'0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd/resourceGroups/appInsights-service/providers/microsoft.insights/' +
|
||||
'components/appInsights/overview'
|
||||
);
|
||||
});
|
||||
|
||||
it('should open the correct service URL for qnaMaker services', () => {
|
||||
|
@ -499,7 +564,9 @@ describe('The ServiceExplorerSagas', () => {
|
|||
|
||||
const action = openServiceDeepLink(mockModel as any);
|
||||
openConnectedServiceGen(action).next();
|
||||
expect(window.open).toHaveBeenCalledWith('https://qnamaker.ai/Edit/KnowledgeBase?kbid=45432');
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'https://qnamaker.ai/Edit/KnowledgeBase?kbid=45432'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -40,13 +40,16 @@ import {
|
|||
IGenericService,
|
||||
ILuisService,
|
||||
IQnAService,
|
||||
ServiceTypes
|
||||
ServiceTypes,
|
||||
} from 'botframework-config/lib/schema';
|
||||
import { ForkEffect, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { DialogService } from '../../ui/dialogs/service';
|
||||
import { serviceTypeLabels } from '../../utils/serviceTypeLables';
|
||||
import { ArmTokenData, beginAzureAuthWorkflow } from '../action/azureAuthActions';
|
||||
import {
|
||||
ArmTokenData,
|
||||
beginAzureAuthWorkflow,
|
||||
} from '../action/azureAuthActions';
|
||||
import {
|
||||
ConnectedServiceAction,
|
||||
ConnectedServicePayload,
|
||||
|
@ -56,35 +59,57 @@ import {
|
|||
OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU,
|
||||
OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU,
|
||||
OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE,
|
||||
OPEN_SERVICE_DEEP_LINK
|
||||
OPEN_SERVICE_DEEP_LINK,
|
||||
} from '../action/connectedServiceActions';
|
||||
import { sortExplorerContents } from '../action/explorerActions';
|
||||
import { SortCriteria } from '../reducer/explorer';
|
||||
import { RootState } from '../store';
|
||||
|
||||
import { getArmToken } from './azureAuthSaga';
|
||||
|
||||
declare type ServicesPayload = { services: IConnectedService[], code: ServiceCodes };
|
||||
import {
|
||||
ForkEffect,
|
||||
put,
|
||||
select,
|
||||
takeEvery,
|
||||
takeLatest,
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
const getArmTokenFromState = (state: RootState): ArmTokenData => state.azureAuth;
|
||||
const geBotConfigFromState = (state: RootState): BotConfigWithPath => state.bot.activeBot;
|
||||
const getSortSelection = (state: RootState): { [paneldId: string]: SortCriteria } =>
|
||||
declare interface ServicesPayload {
|
||||
services: IConnectedService[];
|
||||
code: ServiceCodes;
|
||||
}
|
||||
|
||||
const getArmTokenFromState = (state: RootState): ArmTokenData =>
|
||||
state.azureAuth;
|
||||
const geBotConfigFromState = (state: RootState): BotConfigWithPath =>
|
||||
state.bot.activeBot;
|
||||
const getSortSelection = (
|
||||
state: RootState
|
||||
): { [paneldId: string]: SortCriteria } =>
|
||||
state.explorer.sortSelectionByPanelId;
|
||||
|
||||
function* launchConnectedServicePicker(action: ConnectedServiceAction<ConnectedServicePickerPayload>)
|
||||
: IterableIterator<any> {
|
||||
function* launchConnectedServicePicker(
|
||||
action: ConnectedServiceAction<ConnectedServicePickerPayload>
|
||||
): IterableIterator<any> {
|
||||
// To retrieve azure services, luis models and KBs,
|
||||
// we must have the authoring key.
|
||||
// To get the authoring key, we need the arm token.
|
||||
let armTokenData: ArmTokenData & number = yield select(getArmTokenFromState);
|
||||
if (!armTokenData || !armTokenData.access_token) {
|
||||
const { promptDialog, loginSuccessDialog, loginFailedDialog } = action.payload.azureAuthWorkflowComponents;
|
||||
const {
|
||||
promptDialog,
|
||||
loginSuccessDialog,
|
||||
loginFailedDialog,
|
||||
} = action.payload.azureAuthWorkflowComponents;
|
||||
armTokenData = yield* getArmToken(
|
||||
beginAzureAuthWorkflow(
|
||||
promptDialog,
|
||||
{ serviceType: action.payload.serviceType },
|
||||
loginSuccessDialog,
|
||||
loginFailedDialog
|
||||
));
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 2 means the user has chosen to manually enter the connected service
|
||||
|
@ -97,12 +122,15 @@ function* launchConnectedServicePicker(action: ConnectedServiceAction<ConnectedS
|
|||
}
|
||||
// Add the authenticated user to the action since we now have the token
|
||||
const pJson = JSON.parse(atob(armTokenData.access_token.split('.')[1]));
|
||||
action.payload.authenticatedUser = (pJson.upn || pJson.unique_name || pJson.name || pJson.email);
|
||||
action.payload.authenticatedUser =
|
||||
pJson.upn || pJson.unique_name || pJson.name || pJson.email;
|
||||
const { serviceType, progressIndicatorComponent } = action.payload;
|
||||
if (progressIndicatorComponent) {
|
||||
DialogService.showDialog(progressIndicatorComponent).catch();
|
||||
}
|
||||
let payload: ServicesPayload = yield* retrieveServicesByServiceType(serviceType);
|
||||
const payload: ServicesPayload = yield* retrieveServicesByServiceType(
|
||||
serviceType
|
||||
);
|
||||
|
||||
if (progressIndicatorComponent) {
|
||||
DialogService.hideDialog();
|
||||
|
@ -113,7 +141,7 @@ function* launchConnectedServicePicker(action: ConnectedServiceAction<ConnectedS
|
|||
const result = yield DialogService.showDialog(getStartedDialog, {
|
||||
serviceType,
|
||||
authenticatedUser,
|
||||
showNoModelsFoundContent: !payload.services.length
|
||||
showNoModelsFoundContent: !payload.services.length,
|
||||
});
|
||||
// Sign up with XXXX
|
||||
if (result === 1) {
|
||||
|
@ -124,7 +152,11 @@ function* launchConnectedServicePicker(action: ConnectedServiceAction<ConnectedS
|
|||
yield* launchConnectedServiceEditor(action);
|
||||
}
|
||||
} else {
|
||||
const servicesToAdd = yield* launchConnectedServicePickList(action, payload.services, serviceType);
|
||||
const servicesToAdd = yield* launchConnectedServicePickList(
|
||||
action,
|
||||
payload.services,
|
||||
serviceType
|
||||
);
|
||||
if (servicesToAdd) {
|
||||
const botFile: BotConfigWithPath = yield select(geBotConfigFromState);
|
||||
botFile.services.push(...servicesToAdd);
|
||||
|
@ -139,18 +171,21 @@ function* launchConnectedServicePickList(
|
|||
availableServices: IConnectedService[],
|
||||
serviceType: ServiceTypes
|
||||
): IterableIterator<any> {
|
||||
|
||||
const { pickerComponent, authenticatedUser, serviceType: type } = action.payload;
|
||||
const {
|
||||
pickerComponent,
|
||||
authenticatedUser,
|
||||
serviceType: type,
|
||||
} = action.payload;
|
||||
let result = yield DialogService.showDialog(pickerComponent, {
|
||||
availableServices,
|
||||
authenticatedUser,
|
||||
serviceType
|
||||
serviceType,
|
||||
});
|
||||
|
||||
if (result === 1) {
|
||||
action.payload.connectedService = BotConfigurationBase.serviceFromJSON({
|
||||
type,
|
||||
hostname: '' /* defect workaround */
|
||||
hostname: '' /* defect workaround */,
|
||||
} as any);
|
||||
result = yield* launchConnectedServiceEditor(action);
|
||||
}
|
||||
|
@ -158,35 +193,58 @@ function* launchConnectedServicePickList(
|
|||
return result;
|
||||
}
|
||||
|
||||
function* retrieveServicesByServiceType(serviceType: ServiceTypes): IterableIterator<any> {
|
||||
let armTokenData: ArmTokenData = yield select(getArmTokenFromState);
|
||||
function* retrieveServicesByServiceType(
|
||||
serviceType: ServiceTypes
|
||||
): IterableIterator<any> {
|
||||
const armTokenData: ArmTokenData = yield select(getArmTokenFromState);
|
||||
if (!armTokenData || !armTokenData.access_token) {
|
||||
throw new Error('Auth credentials do not exist.');
|
||||
}
|
||||
const { GetConnectedServicesByType } = SharedConstants.Commands.ConnectedService;
|
||||
const {
|
||||
GetConnectedServicesByType,
|
||||
} = SharedConstants.Commands.ConnectedService;
|
||||
let payload: ServicesPayload;
|
||||
try {
|
||||
payload = yield CommandServiceImpl.remoteCall(GetConnectedServicesByType, armTokenData.access_token, serviceType);
|
||||
payload = yield CommandServiceImpl.remoteCall(
|
||||
GetConnectedServicesByType,
|
||||
armTokenData.access_token,
|
||||
serviceType
|
||||
);
|
||||
} catch (e) {
|
||||
payload = { services: [], code: ServiceCodes.Error };
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
function* openConnectedServiceDeepLink(action: ConnectedServiceAction<ConnectedServicePayload>): IterableIterator<any> {
|
||||
// eslint-disable-next-line require-yield
|
||||
function* openConnectedServiceDeepLink(
|
||||
action: ConnectedServiceAction<ConnectedServicePayload>
|
||||
): IterableIterator<any> {
|
||||
const { connectedService } = action.payload;
|
||||
switch (connectedService.type) {
|
||||
case ServiceTypes.AppInsights:
|
||||
return openAzureProviderDeepLink('microsoft.insights/components', connectedService as IAzureService);
|
||||
return openAzureProviderDeepLink(
|
||||
'microsoft.insights/components',
|
||||
connectedService as IAzureService
|
||||
);
|
||||
|
||||
case ServiceTypes.BlobStorage:
|
||||
return openAzureProviderDeepLink('Microsoft.DocumentDB/storageAccounts', connectedService as IAzureService);
|
||||
return openAzureProviderDeepLink(
|
||||
'Microsoft.DocumentDB/storageAccounts',
|
||||
connectedService as IAzureService
|
||||
);
|
||||
|
||||
case ServiceTypes.Bot:
|
||||
return openAzureProviderDeepLink('Microsoft.BotService/botServices', connectedService as IAzureService);
|
||||
return openAzureProviderDeepLink(
|
||||
'Microsoft.BotService/botServices',
|
||||
connectedService as IAzureService
|
||||
);
|
||||
|
||||
case ServiceTypes.CosmosDB:
|
||||
return openAzureProviderDeepLink('Microsoft.DocumentDb/databaseAccounts', connectedService as IAzureService);
|
||||
return openAzureProviderDeepLink(
|
||||
'Microsoft.DocumentDb/databaseAccounts',
|
||||
connectedService as IAzureService
|
||||
);
|
||||
|
||||
case ServiceTypes.Generic:
|
||||
return window.open((connectedService as IGenericService).url);
|
||||
|
@ -202,14 +260,18 @@ function* openConnectedServiceDeepLink(action: ConnectedServiceAction<ConnectedS
|
|||
}
|
||||
}
|
||||
|
||||
function* openContextMenuForService(action: ConnectedServiceAction<ConnectedServicePayload>)
|
||||
: IterableIterator<any> {
|
||||
function* openContextMenuForService(
|
||||
action: ConnectedServiceAction<ConnectedServicePayload>
|
||||
): IterableIterator<any> {
|
||||
const menuItems = [
|
||||
{ label: 'Manage service', id: 'open' },
|
||||
{ label: 'Edit configuration', id: 'edit' },
|
||||
{ label: 'Disconnect this service', id: 'forget' }
|
||||
{ label: 'Disconnect this service', id: 'forget' },
|
||||
];
|
||||
const response = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.DisplayContextMenu, menuItems);
|
||||
const response = yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.DisplayContextMenu,
|
||||
menuItems
|
||||
);
|
||||
const { connectedService } = action.payload;
|
||||
action.payload.serviceType = connectedService.type;
|
||||
switch (response.id) {
|
||||
|
@ -225,13 +287,15 @@ function* openContextMenuForService(action: ConnectedServiceAction<ConnectedServ
|
|||
yield* removeServiceFromActiveBot(connectedService);
|
||||
break;
|
||||
|
||||
default: // canceled context menu
|
||||
default:
|
||||
// canceled context menu
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function* openAddConnectedServiceContextMenu(action: ConnectedServiceAction<ConnectedServicePickerPayload>)
|
||||
: IterableIterator<any> {
|
||||
function* openAddConnectedServiceContextMenu(
|
||||
action: ConnectedServiceAction<ConnectedServicePickerPayload>
|
||||
): IterableIterator<any> {
|
||||
const menuItems = [
|
||||
{ label: 'Add Language Understanding (LUIS)', id: ServiceTypes.Luis },
|
||||
{ label: 'Add QnA Maker', id: ServiceTypes.QnA },
|
||||
|
@ -244,69 +308,119 @@ function* openAddConnectedServiceContextMenu(action: ConnectedServiceAction<Conn
|
|||
{ label: 'Add other service β¦', id: ServiceTypes.Generic },
|
||||
];
|
||||
|
||||
const response = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.DisplayContextMenu, menuItems);
|
||||
const response = yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.DisplayContextMenu,
|
||||
menuItems
|
||||
);
|
||||
const { id: serviceType } = response;
|
||||
action.payload.serviceType = serviceType;
|
||||
if (serviceType === ServiceTypes.Generic ||
|
||||
serviceType === ServiceTypes.AppInsights) {
|
||||
if (
|
||||
serviceType === ServiceTypes.Generic ||
|
||||
serviceType === ServiceTypes.AppInsights
|
||||
) {
|
||||
yield* launchConnectedServiceEditor(action);
|
||||
} else {
|
||||
yield* launchConnectedServicePicker(action);
|
||||
}
|
||||
}
|
||||
|
||||
function* openSortContextMenu(action: ConnectedServiceAction<ConnectedServicePayload>): IterableIterator<any> {
|
||||
function* openSortContextMenu(
|
||||
action: ConnectedServiceAction<ConnectedServicePayload>
|
||||
): IterableIterator<any> {
|
||||
const sortSelectionByPanelId = yield select(getSortSelection);
|
||||
const currentSort = sortSelectionByPanelId[action.payload.panelId];
|
||||
const menuItems = [
|
||||
{ label: 'Sort by name', id: 'name', type: 'checkbox', checked: currentSort === 'name' },
|
||||
{ label: 'Sort by type', id: 'type', type: 'checkbox', checked: currentSort === 'type' },
|
||||
{
|
||||
label: 'Sort by name',
|
||||
id: 'name',
|
||||
type: 'checkbox',
|
||||
checked: currentSort === 'name',
|
||||
},
|
||||
{
|
||||
label: 'Sort by type',
|
||||
id: 'type',
|
||||
type: 'checkbox',
|
||||
checked: currentSort === 'type',
|
||||
},
|
||||
];
|
||||
const response = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.DisplayContextMenu, menuItems);
|
||||
yield response.id ? put(sortExplorerContents(action.payload.panelId, response.id)) : null;
|
||||
const response = yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.DisplayContextMenu,
|
||||
menuItems
|
||||
);
|
||||
yield response.id
|
||||
? put(sortExplorerContents(action.payload.panelId, response.id))
|
||||
: null;
|
||||
}
|
||||
|
||||
function* removeServiceFromActiveBot(connectedService: IConnectedService): IterableIterator<any> {
|
||||
function* removeServiceFromActiveBot(
|
||||
connectedService: IConnectedService
|
||||
): IterableIterator<any> {
|
||||
// TODO - localization
|
||||
const result = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.ShowMessageBox, true, {
|
||||
type: 'question',
|
||||
buttons: ['Cancel', 'OK'],
|
||||
defaultId: 1,
|
||||
message: `Remove ${ serviceTypeLabels[connectedService.type] } service: ${ connectedService.name }. Are you sure?`,
|
||||
cancelId: 0,
|
||||
});
|
||||
const result = yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.ShowMessageBox,
|
||||
true,
|
||||
{
|
||||
type: 'question',
|
||||
buttons: ['Cancel', 'OK'],
|
||||
defaultId: 1,
|
||||
message: `Remove ${serviceTypeLabels[connectedService.type]} service: ${
|
||||
connectedService.name
|
||||
}. Are you sure?`,
|
||||
cancelId: 0,
|
||||
}
|
||||
);
|
||||
if (result) {
|
||||
const { RemoveService } = SharedConstants.Commands.Bot;
|
||||
yield CommandServiceImpl.remoteCall(RemoveService, connectedService.type, connectedService.id);
|
||||
yield CommandServiceImpl.remoteCall(
|
||||
RemoveService,
|
||||
connectedService.type,
|
||||
connectedService.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function* launchConnectedServiceEditor(action: ConnectedServiceAction<ConnectedServicePayload>)
|
||||
: IterableIterator<any> {
|
||||
const { editorComponent, authenticatedUser, connectedService, serviceType } = action.payload;
|
||||
const servicesToUpdate: IConnectedService[] = yield DialogService.showDialog(editorComponent, {
|
||||
connectedService,
|
||||
function* launchConnectedServiceEditor(
|
||||
action: ConnectedServiceAction<ConnectedServicePayload>
|
||||
): IterableIterator<any> {
|
||||
const {
|
||||
editorComponent,
|
||||
authenticatedUser,
|
||||
serviceType
|
||||
});
|
||||
connectedService,
|
||||
serviceType,
|
||||
} = action.payload;
|
||||
const servicesToUpdate: IConnectedService[] = yield DialogService.showDialog(
|
||||
editorComponent,
|
||||
{
|
||||
connectedService,
|
||||
authenticatedUser,
|
||||
serviceType,
|
||||
}
|
||||
);
|
||||
|
||||
if (servicesToUpdate) {
|
||||
let i = servicesToUpdate.length;
|
||||
while (i--) {
|
||||
const service = servicesToUpdate[i];
|
||||
yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Bot.AddOrUpdateService, service.type, service);
|
||||
yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Bot.AddOrUpdateService,
|
||||
service.type,
|
||||
service
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function openAzureProviderDeepLink(provider: string, azureService: IAzureService): void {
|
||||
function openAzureProviderDeepLink(
|
||||
provider: string,
|
||||
azureService: IAzureService
|
||||
): void {
|
||||
const { tenantId, subscriptionId, resourceGroup, serviceName } = azureService;
|
||||
const bits = [
|
||||
`https://ms.portal.azure.com/#@${ tenantId }/resource/`,
|
||||
`subscriptions/${ subscriptionId }/`,
|
||||
`resourceGroups/${ encodeURI(resourceGroup) }/`,
|
||||
`providers/${ provider }/${ encodeURI(serviceName) }/overview`
|
||||
`https://ms.portal.azure.com/#@${tenantId}/resource/`,
|
||||
`subscriptions/${subscriptionId}/`,
|
||||
`resourceGroups/${encodeURI(resourceGroup)}/`,
|
||||
`providers/${provider}/${encodeURI(serviceName)}/overview`,
|
||||
];
|
||||
|
||||
window.open(bits.join(''));
|
||||
|
@ -328,23 +442,49 @@ function openLuisDeepLink(luisService: ILuisService) {
|
|||
regionPrefix = '';
|
||||
break;
|
||||
}
|
||||
const linkArray = ['https://', `${ encodeURI(regionPrefix) }`, 'luis.ai/applications/'];
|
||||
linkArray.push(`${ encodeURI(appId) }`, '/versions/', `${ encodeURI(version) }`, '/build');
|
||||
const linkArray = [
|
||||
'https://',
|
||||
`${encodeURI(regionPrefix)}`,
|
||||
'luis.ai/applications/',
|
||||
];
|
||||
linkArray.push(
|
||||
`${encodeURI(appId)}`,
|
||||
'/versions/',
|
||||
`${encodeURI(version)}`,
|
||||
'/build'
|
||||
);
|
||||
const link = linkArray.join('');
|
||||
window.open(link);
|
||||
}
|
||||
|
||||
function openQnaMakerDeepLink(service: IQnAService) {
|
||||
const { kbId } = service;
|
||||
const link = `https://qnamaker.ai/Edit/KnowledgeBase?kbid=${ encodeURIComponent(kbId) }`;
|
||||
const link = `https://qnamaker.ai/Edit/KnowledgeBase?kbid=${encodeURIComponent(
|
||||
kbId
|
||||
)}`;
|
||||
window.open(link);
|
||||
}
|
||||
|
||||
export function* servicesExplorerSagas(): IterableIterator<ForkEffect> {
|
||||
yield takeLatest(LAUNCH_CONNECTED_SERVICE_PICKER, launchConnectedServicePicker);
|
||||
yield takeLatest(LAUNCH_CONNECTED_SERVICE_EDITOR, launchConnectedServiceEditor);
|
||||
yield takeLatest(
|
||||
LAUNCH_CONNECTED_SERVICE_PICKER,
|
||||
launchConnectedServicePicker
|
||||
);
|
||||
yield takeLatest(
|
||||
LAUNCH_CONNECTED_SERVICE_EDITOR,
|
||||
launchConnectedServiceEditor
|
||||
);
|
||||
yield takeEvery(OPEN_SERVICE_DEEP_LINK, openConnectedServiceDeepLink);
|
||||
yield takeEvery(OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE, openContextMenuForService);
|
||||
yield takeEvery(OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU, openAddConnectedServiceContextMenu);
|
||||
yield takeEvery(OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU, openSortContextMenu);
|
||||
yield takeEvery(
|
||||
OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE,
|
||||
openContextMenuForService
|
||||
);
|
||||
yield takeEvery(
|
||||
OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU,
|
||||
openAddConnectedServiceContextMenu
|
||||
);
|
||||
yield takeEvery(
|
||||
OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU,
|
||||
openSortContextMenu
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,24 +31,29 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { editorSelector, refreshConversationMenu } from './sharedSagas';
|
||||
import { RootState } from '../store';
|
||||
import { select } from 'redux-saga/effects';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
|
||||
import { RootState } from '../store';
|
||||
|
||||
import { editorSelector, refreshConversationMenu } from './sharedSagas';
|
||||
|
||||
import { select } from 'redux-saga/effects';
|
||||
|
||||
let mockRemoteCommandsCalled = [];
|
||||
jest.mock('../../platform/commands/commandServiceImpl', () => ({
|
||||
CommandServiceImpl: {
|
||||
remoteCall: async (commandName: string, ...args: any[]) => {
|
||||
mockRemoteCommandsCalled.push({ commandName, args: args });
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('The sharedSagas', () => {
|
||||
const editorState = { activeEditor: 'primary' };
|
||||
|
||||
beforeEach(() => { mockRemoteCommandsCalled = []; });
|
||||
beforeEach(() => {
|
||||
mockRemoteCommandsCalled = [];
|
||||
});
|
||||
|
||||
it('should select the editor state from the store', () => {
|
||||
const state: RootState = { editor: editorState };
|
||||
|
@ -65,7 +70,9 @@ describe('The sharedSagas', () => {
|
|||
gen.next(editorState);
|
||||
expect(mockRemoteCommandsCalled).toHaveLength(1);
|
||||
const refreshConversationCall = mockRemoteCommandsCalled[0];
|
||||
expect(refreshConversationCall.commandName).toBe(SharedConstants.Commands.Electron.UpdateConversationMenu);
|
||||
expect(refreshConversationCall.commandName).toBe(
|
||||
SharedConstants.Commands.Electron.UpdateConversationMenu
|
||||
);
|
||||
expect(refreshConversationCall.args).toHaveLength(1);
|
||||
expect(refreshConversationCall.args[0]).toEqual(editorState);
|
||||
|
||||
|
|
|
@ -31,10 +31,12 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { select } from 'redux-saga/effects';
|
||||
import { RootState } from '../store';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { RootState } from '../store';
|
||||
|
||||
import { select } from 'redux-saga/effects';
|
||||
|
||||
export function editorSelector(state: RootState) {
|
||||
return state.editor;
|
||||
|
@ -42,5 +44,8 @@ export function editorSelector(state: RootState) {
|
|||
|
||||
export function* refreshConversationMenu(): IterableIterator<any> {
|
||||
const stateData = yield select(editorSelector);
|
||||
yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.UpdateConversationMenu, stateData);
|
||||
yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.UpdateConversationMenu,
|
||||
stateData
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,48 +1,83 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import sagaMiddlewareFactory from 'redux-saga';
|
||||
import { applyMiddleware, combineReducers, createStore } from 'redux';
|
||||
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { openContextMenuForBot } from '../action/welcomePageActions';
|
||||
import { bot } from '../reducer/bot';
|
||||
import notification from '../reducer/notification';
|
||||
|
||||
import { notificationSagas } from './notificationSagas';
|
||||
import { welcomePageSagas } from './welcomePageSagas';
|
||||
import sagaMiddlewareFactory from 'redux-saga';
|
||||
import { applyMiddleware, combineReducers, createStore } from 'redux';
|
||||
|
||||
const mockBot = {
|
||||
path: '/some/path.bot',
|
||||
displayName: 'AuthBot',
|
||||
secret: 'secret'
|
||||
secret: 'secret',
|
||||
};
|
||||
jest.mock('../../platform/commands/commandServiceImpl', () => ({
|
||||
CommandServiceImpl: {
|
||||
remoteCall: async () => null
|
||||
}
|
||||
remoteCall: async () => null,
|
||||
},
|
||||
}));
|
||||
|
||||
const sagaMiddleWare = sagaMiddlewareFactory();
|
||||
const mockStore = createStore(combineReducers({ bot, notification }), {
|
||||
bot: { botFiles: [mockBot] }
|
||||
}, applyMiddleware(sagaMiddleWare));
|
||||
const mockStore = createStore(
|
||||
combineReducers({ bot, notification }),
|
||||
{
|
||||
bot: { botFiles: [mockBot] },
|
||||
},
|
||||
applyMiddleware(sagaMiddleWare)
|
||||
);
|
||||
|
||||
jest.mock('../store', () => ({
|
||||
get store() {
|
||||
return mockStore;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
sagaMiddleWare.run(welcomePageSagas);
|
||||
sagaMiddleWare.run(notificationSagas);
|
||||
|
||||
describe('The WelcomePageSagas', () => {
|
||||
|
||||
describe(', when invoking a context menu over a bot in the list', () => {
|
||||
|
||||
it('should call the series of commands that move the bot file to a new location.', async () => {
|
||||
const remoteCalls = [];
|
||||
CommandServiceImpl.remoteCall = async function (...args: any[]) {
|
||||
CommandServiceImpl.remoteCall = async function(...args: any[]) {
|
||||
remoteCalls.push(args);
|
||||
switch (args[0]) {
|
||||
|
||||
case SharedConstants.Commands.Electron.DisplayContextMenu:
|
||||
return { id: 0 };
|
||||
|
||||
|
@ -60,58 +95,55 @@ describe('The WelcomePageSagas', () => {
|
|||
'electron:display-context-menu',
|
||||
[
|
||||
{
|
||||
'label': 'Move...',
|
||||
'id': 0
|
||||
label: 'Move...',
|
||||
id: 0,
|
||||
},
|
||||
{
|
||||
'label': 'Open file location',
|
||||
'id': 1
|
||||
label: 'Open file location',
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
'label': 'Forget this bot',
|
||||
'id': 2
|
||||
}
|
||||
]
|
||||
label: 'Forget this bot',
|
||||
id: 2,
|
||||
},
|
||||
],
|
||||
]);
|
||||
expect(remoteCalls[1]).toEqual([
|
||||
'shell:showExplorer-save-dialog',
|
||||
{
|
||||
'defaultPath': '/some/path.bot',
|
||||
'buttonLabel': 'Move',
|
||||
'nameFieldLabel': 'Name',
|
||||
'filters': [
|
||||
defaultPath: '/some/path.bot',
|
||||
buttonLabel: 'Move',
|
||||
nameFieldLabel: 'Name',
|
||||
filters: [
|
||||
{
|
||||
'extensions': [
|
||||
'.bot'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
extensions: ['.bot'],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(remoteCalls[2]).toEqual([
|
||||
'shell:rename-file',
|
||||
{
|
||||
'path': '/some/path.bot',
|
||||
'newPath': 'this/is/a/new/location.bot'
|
||||
}
|
||||
path: '/some/path.bot',
|
||||
newPath: 'this/is/a/new/location.bot',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(remoteCalls[3]).toEqual([
|
||||
'bot:list:patch',
|
||||
'/some/path.bot',
|
||||
{
|
||||
'path': 'this/is/a/new/location.bot',
|
||||
'displayName': 'AuthBot',
|
||||
'secret': 'secret'
|
||||
}
|
||||
path: 'this/is/a/new/location.bot',
|
||||
displayName: 'AuthBot',
|
||||
secret: 'secret',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should add a notification if a remote command fails when moving a bot file', async () => {
|
||||
CommandServiceImpl.remoteCall = async function (...args: any[]) {
|
||||
CommandServiceImpl.remoteCall = async function(...args: any[]) {
|
||||
switch (args[0]) {
|
||||
|
||||
case SharedConstants.Commands.Electron.DisplayContextMenu:
|
||||
return { id: 0 };
|
||||
|
||||
|
@ -134,14 +166,13 @@ describe('The WelcomePageSagas', () => {
|
|||
|
||||
it('should call the appropriate command when opening the bot file location', async () => {
|
||||
let openFileLocationArgs;
|
||||
CommandServiceImpl.remoteCall = async function (...args: any[]) {
|
||||
CommandServiceImpl.remoteCall = async function(...args: any[]) {
|
||||
switch (args[0]) {
|
||||
|
||||
case SharedConstants.Commands.Electron.DisplayContextMenu:
|
||||
return { id: 1 };
|
||||
|
||||
case SharedConstants.Commands.Electron.OpenFileLocation:
|
||||
return openFileLocationArgs = args;
|
||||
return (openFileLocationArgs = args);
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
@ -150,19 +181,21 @@ describe('The WelcomePageSagas', () => {
|
|||
|
||||
await mockStore.dispatch(openContextMenuForBot(mockBot));
|
||||
await Promise.resolve(true);
|
||||
expect(openFileLocationArgs).toEqual(['shell:open-file-location', 'this/is/a/new/location.bot']);
|
||||
expect(openFileLocationArgs).toEqual([
|
||||
'shell:open-file-location',
|
||||
'this/is/a/new/location.bot',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call the appropriate command when removing a bot from the list', async () => {
|
||||
let removeBotFromListArgs;
|
||||
CommandServiceImpl.remoteCall = async function (...args: any[]) {
|
||||
CommandServiceImpl.remoteCall = async function(...args: any[]) {
|
||||
switch (args[0]) {
|
||||
|
||||
case SharedConstants.Commands.Electron.DisplayContextMenu:
|
||||
return { id: 2 };
|
||||
|
||||
case SharedConstants.Commands.Bot.RemoveFromBotList:
|
||||
return removeBotFromListArgs = args;
|
||||
return (removeBotFromListArgs = args);
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
@ -170,7 +203,10 @@ describe('The WelcomePageSagas', () => {
|
|||
};
|
||||
await mockStore.dispatch(openContextMenuForBot(mockBot));
|
||||
await Promise.resolve(true);
|
||||
expect(removeBotFromListArgs).toEqual(['bot:list:remove', 'this/is/a/new/location.bot']);
|
||||
expect(removeBotFromListArgs).toEqual([
|
||||
'bot:list:remove',
|
||||
'this/is/a/new/location.bot',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,28 +1,80 @@
|
|||
import { BotInfo, newNotification, SharedConstants } from '@bfemulator/app-shared';
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Framework Emulator Github:
|
||||
// https://github.com/Microsoft/BotFramwork-Emulator
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
import {
|
||||
BotInfo,
|
||||
newNotification,
|
||||
SharedConstants,
|
||||
} from '@bfemulator/app-shared';
|
||||
|
||||
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
|
||||
import { beginAdd } from '../action/notificationActions';
|
||||
import { OPEN_CONTEXT_MENU_FOR_BOT, WelcomePageAction } from '../action/welcomePageActions';
|
||||
import {
|
||||
OPEN_CONTEXT_MENU_FOR_BOT,
|
||||
WelcomePageAction,
|
||||
} from '../action/welcomePageActions';
|
||||
|
||||
import { ForkEffect, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
function* openContextMenuForBot(action: WelcomePageAction<BotInfo>): IterableIterator<any> {
|
||||
function* openContextMenuForBot(
|
||||
action: WelcomePageAction<BotInfo>
|
||||
): IterableIterator<any> {
|
||||
const menuItems = [
|
||||
{ label: 'Move...', id: 0 },
|
||||
{ label: 'Open file location', id: 1 },
|
||||
{ label: 'Forget this bot', id: 2 }
|
||||
{ label: 'Forget this bot', id: 2 },
|
||||
];
|
||||
|
||||
const result = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.DisplayContextMenu, menuItems);
|
||||
const result = yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.DisplayContextMenu,
|
||||
menuItems
|
||||
);
|
||||
switch (result.id) {
|
||||
case 0:
|
||||
yield* moveBotToNewLocation(action.payload);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.OpenFileLocation, action.payload.path);
|
||||
yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.OpenFileLocation,
|
||||
action.payload.path
|
||||
);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Bot.RemoveFromBotList, action.payload.path);
|
||||
yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Bot.RemoveFromBotList,
|
||||
action.payload.path
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -32,20 +84,30 @@ function* openContextMenuForBot(action: WelcomePageAction<BotInfo>): IterableIte
|
|||
}
|
||||
|
||||
function* moveBotToNewLocation(bot: BotInfo): IterableIterator<any> {
|
||||
const newPath = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.ShowSaveDialog, {
|
||||
defaultPath: bot.path,
|
||||
buttonLabel: 'Move',
|
||||
nameFieldLabel: 'Name',
|
||||
filters: [{ extensions: ['.bot'] }]
|
||||
});
|
||||
const newPath = yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.ShowSaveDialog,
|
||||
{
|
||||
defaultPath: bot.path,
|
||||
buttonLabel: 'Move',
|
||||
nameFieldLabel: 'Name',
|
||||
filters: [{ extensions: ['.bot'] }],
|
||||
}
|
||||
);
|
||||
if (!newPath) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { path: oldPath } = bot;
|
||||
bot.path = newPath;
|
||||
yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.RenameFile, { path: oldPath, newPath });
|
||||
yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Bot.PatchBotList, oldPath, bot);
|
||||
yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Electron.RenameFile,
|
||||
{ path: oldPath, newPath }
|
||||
);
|
||||
yield CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.Bot.PatchBotList,
|
||||
oldPath,
|
||||
bot
|
||||
);
|
||||
} catch (e) {
|
||||
const errMsg = `Error occurred while moving the bot file: ${e}`;
|
||||
const notification = newNotification(errMsg);
|
||||
|
|
|
@ -31,25 +31,28 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { ClientAwareSettings } from '@bfemulator/app-shared';
|
||||
import { applyMiddleware, combineReducers, createStore, Store } from 'redux';
|
||||
import promiseMiddleware from 'redux-promise-middleware';
|
||||
import sagaMiddlewareFactory from 'redux-saga';
|
||||
|
||||
import { azureAuth, AzureAuthState } from './reducer/azureAuthReducer';
|
||||
import { bot, BotState } from './reducer/bot';
|
||||
import { chat, ChatState } from './reducer/chat';
|
||||
import { clientAwareSettings } from './reducer/clientAwareSettingsReducer';
|
||||
import { dialog, DialogState } from './reducer/dialog';
|
||||
import { editor, EditorState } from './reducer/editor';
|
||||
import { explorer, ExplorerState } from './reducer/explorer';
|
||||
import { azureAuth, AzureAuthState } from './reducer/azureAuthReducer';
|
||||
import { navBar, NavBarState } from './reducer/navBar';
|
||||
import { notification, NotificationState } from './reducer/notification';
|
||||
import { presentation, PresentationState } from './reducer/presentation';
|
||||
import { progressIndicator, ProgressIndicatorState } from './reducer/progressIndicator';
|
||||
import {
|
||||
progressIndicator,
|
||||
ProgressIndicatorState,
|
||||
} from './reducer/progressIndicator';
|
||||
import { resources, ResourcesState } from './reducer/resourcesReducer';
|
||||
import { theme, ThemeState } from './reducer/themeReducer';
|
||||
import { clientAwareSettings } from './reducer/clientAwareSettingsReducer';
|
||||
|
||||
import { applicationSagas } from './sagas';
|
||||
import { ClientAwareSettings } from '@bfemulator/app-shared';
|
||||
|
||||
export interface RootState {
|
||||
azureAuth?: AzureAuthState;
|
||||
|
@ -70,28 +73,28 @@ export interface RootState {
|
|||
const sagaMiddleWare = sagaMiddlewareFactory();
|
||||
const DEFAULT_STATE: RootState = {};
|
||||
|
||||
const configureStore = (initialState: RootState = DEFAULT_STATE): Store<RootState> => createStore<RootState>(
|
||||
combineReducers({
|
||||
azureAuth,
|
||||
bot,
|
||||
chat,
|
||||
clientAwareSettings,
|
||||
dialog,
|
||||
editor,
|
||||
explorer,
|
||||
navBar,
|
||||
notification,
|
||||
presentation,
|
||||
progressIndicator,
|
||||
resources,
|
||||
theme,
|
||||
}),
|
||||
initialState,
|
||||
applyMiddleware(
|
||||
sagaMiddleWare,
|
||||
promiseMiddleware()
|
||||
)
|
||||
);
|
||||
const configureStore = (
|
||||
initialState: RootState = DEFAULT_STATE
|
||||
): Store<RootState> =>
|
||||
createStore<RootState>(
|
||||
combineReducers({
|
||||
azureAuth,
|
||||
bot,
|
||||
chat,
|
||||
clientAwareSettings,
|
||||
dialog,
|
||||
editor,
|
||||
explorer,
|
||||
navBar,
|
||||
notification,
|
||||
presentation,
|
||||
progressIndicator,
|
||||
resources,
|
||||
theme,
|
||||
}),
|
||||
initialState,
|
||||
applyMiddleware(sagaMiddleWare, promiseMiddleware())
|
||||
);
|
||||
|
||||
const store = configureStore();
|
||||
applicationSagas.forEach(saga => sagaMiddleWare.run(saga));
|
||||
|
|
|
@ -31,29 +31,30 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import {
|
||||
CommandRegistryImpl,
|
||||
CommandService,
|
||||
CommandServiceImpl,
|
||||
ExtensionConfig,
|
||||
ExtensionInspector
|
||||
ExtensionInspector,
|
||||
} from '@bfemulator/sdk-shared';
|
||||
|
||||
import { ElectronIPC } from './ipc';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
|
||||
// =============================================================================
|
||||
export class Extension {
|
||||
private _ext: CommandService;
|
||||
|
||||
get unid(): string {
|
||||
public get unid(): string {
|
||||
return this._unid;
|
||||
}
|
||||
|
||||
get config(): ExtensionConfig {
|
||||
public get config(): ExtensionConfig {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
constructor(private _config: ExtensionConfig, private _unid: string) {
|
||||
public constructor(private _config: ExtensionConfig, private _unid: string) {
|
||||
this._ext = new CommandServiceImpl(ElectronIPC, `ext-${this._unid}`);
|
||||
/*
|
||||
this._ext.remoteCall('ext-ping')
|
||||
|
@ -64,11 +65,15 @@ export class Extension {
|
|||
|
||||
public inspectorForObject(obj: any): GetInspectorResult | null {
|
||||
const inspectors = this.config.client.inspectors || [];
|
||||
const inspector = inspectors.find(inspectorArg => InspectorAPI.canInspect(inspectorArg, obj));
|
||||
return inspector ? {
|
||||
extension: this,
|
||||
inspector
|
||||
} : null;
|
||||
const inspector = inspectors.find(inspectorArg =>
|
||||
InspectorAPI.canInspect(inspectorArg, obj)
|
||||
);
|
||||
return inspector
|
||||
? {
|
||||
extension: this,
|
||||
inspector,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
public call(commandName: string, ...args: any[]): Promise<any> {
|
||||
|
@ -129,7 +134,10 @@ export class InspectorAPI {
|
|||
}
|
||||
}
|
||||
|
||||
export function getValueFromPath(source: { [prop: string]: any }, path: string): any {
|
||||
export function getValueFromPath(
|
||||
source: { [prop: string]: any },
|
||||
path: string
|
||||
): any {
|
||||
const parts = path.split('.');
|
||||
let val = source;
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
|
@ -158,15 +166,19 @@ export interface ExtensionManager {
|
|||
|
||||
getExtensions(): Extension[];
|
||||
|
||||
inspectorForObject(obj: any, defaultToJson: boolean): GetInspectorResult | null;
|
||||
inspectorForObject(
|
||||
obj: any,
|
||||
defaultToJson: boolean
|
||||
): GetInspectorResult | null;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
export const ExtensionManager = new class implements ExtensionManager {
|
||||
class EmulatorExtensionManager implements ExtensionManager {
|
||||
private extensions: { [unid: string]: Extension } = {};
|
||||
|
||||
public addExtension(config: ExtensionConfig, unid: string) {
|
||||
this.removeExtension(unid);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`adding extension ${config.name}`);
|
||||
const ext = new Extension(config, unid);
|
||||
this.extensions[unid] = ext;
|
||||
|
@ -174,30 +186,40 @@ export const ExtensionManager = new class implements ExtensionManager {
|
|||
|
||||
public removeExtension(unid: string) {
|
||||
if (this.extensions[unid]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`removing extension ${this.extensions[unid].config.name}`);
|
||||
delete this.extensions[unid];
|
||||
}
|
||||
}
|
||||
|
||||
public findExtension(name: string): Extension {
|
||||
return this.getExtensions().find(extension => extension.config.name === name);
|
||||
return this.getExtensions().find(
|
||||
extension => extension.config.name === name
|
||||
);
|
||||
}
|
||||
|
||||
public getExtensions(): Extension[] {
|
||||
return Object.keys(this.extensions).map(key => this.extensions[key]) || [];
|
||||
}
|
||||
|
||||
public inspectorForObject(obj: any, defaultToJson: boolean): GetInspectorResult | null {
|
||||
public inspectorForObject(
|
||||
obj: any,
|
||||
defaultToJson: boolean
|
||||
): GetInspectorResult | null {
|
||||
let result = this.getExtensions()
|
||||
.map(extension => extension.inspectorForObject(obj))
|
||||
.filter(resultArg => !!resultArg).shift();
|
||||
.filter(resultArg => !!resultArg)
|
||||
.shift();
|
||||
if (!result && defaultToJson) {
|
||||
// Default to the JSON inspector
|
||||
// eslint-disable-next-line typescript/no-use-before-define
|
||||
const jsonExtension = ExtensionManager.findExtension('JSON');
|
||||
if (jsonExtension) {
|
||||
result = {
|
||||
extension: jsonExtension,
|
||||
inspector: jsonExtension.config.client.inspectors ? jsonExtension.config.client.inspectors[0] : null
|
||||
inspector: jsonExtension.config.client.inspectors
|
||||
? jsonExtension.config.client.inspectors[0]
|
||||
: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -207,11 +229,15 @@ export const ExtensionManager = new class implements ExtensionManager {
|
|||
public registerCommands(commandRegistry: CommandRegistryImpl) {
|
||||
const { Connect, Disconnect } = SharedConstants.Commands.Extension;
|
||||
commandRegistry.registerCommand(Connect, (config: ExtensionConfig) => {
|
||||
// eslint-disable-next-line typescript/no-use-before-define
|
||||
ExtensionManager.addExtension(config, config.location);
|
||||
});
|
||||
|
||||
commandRegistry.registerCommand(Disconnect, (location: string) => {
|
||||
// eslint-disable-next-line typescript/no-use-before-define
|
||||
ExtensionManager.removeExtension(location);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const ExtensionManager = new EmulatorExtensionManager();
|
||||
|
|
|
@ -31,12 +31,14 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import * as URL from 'url';
|
||||
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
import { uniqueId } from '@bfemulator/sdk-shared';
|
||||
|
||||
import { CommandServiceImpl } from './platform/commands/commandServiceImpl';
|
||||
const Electron = (window as any).require('electron');
|
||||
const { shell } = Electron;
|
||||
import { uniqueId } from '@bfemulator/sdk-shared';
|
||||
import { CommandServiceImpl } from './platform/commands/commandServiceImpl';
|
||||
import * as URL from 'url';
|
||||
import { SharedConstants } from '@bfemulator/app-shared';
|
||||
|
||||
export function navigate(url: string) {
|
||||
try {
|
||||
|
@ -55,14 +57,21 @@ export function navigate(url: string) {
|
|||
|
||||
function navigateEmulatedOAuthUrl(oauthParam: string) {
|
||||
const { Commands } = SharedConstants;
|
||||
let parts = oauthParam.split('&&&');
|
||||
CommandServiceImpl
|
||||
.remoteCall(Commands.OAuth.SendTokenResponse, parts[0], parts[1], 'emulatedToken_' + uniqueId())
|
||||
.catch();
|
||||
const parts = oauthParam.split('&&&');
|
||||
CommandServiceImpl.remoteCall(
|
||||
Commands.OAuth.SendTokenResponse,
|
||||
parts[0],
|
||||
parts[1],
|
||||
'emulatedToken_' + uniqueId()
|
||||
).catch();
|
||||
}
|
||||
|
||||
function navigateOAuthUrl(oauthParam: string) {
|
||||
const { Commands } = SharedConstants;
|
||||
let parts = oauthParam.split('&&&');
|
||||
CommandServiceImpl.remoteCall(Commands.OAuth.CreateOAuthWindow, parts[0], parts[1]).catch();
|
||||
const parts = oauthParam.split('&&&');
|
||||
CommandServiceImpl.remoteCall(
|
||||
Commands.OAuth.CreateOAuthWindow,
|
||||
parts[0],
|
||||
parts[1]
|
||||
).catch();
|
||||
}
|
||||
|
|
|
@ -32,17 +32,18 @@
|
|||
//
|
||||
// for hot reloading
|
||||
import { Provider } from 'react-redux';
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import * as React from 'react';
|
||||
import { newNotification, SharedConstants } from '@bfemulator/app-shared';
|
||||
|
||||
import { LogService } from './platform/log/logService';
|
||||
import interceptError from './interceptError';
|
||||
import interceptHyperlink from './interceptHyperlink';
|
||||
import Main from './ui/shell/mainContainer';
|
||||
import { store } from './data/store';
|
||||
import { CommandServiceImpl } from './platform/commands/commandServiceImpl';
|
||||
import { LogService } from './platform/log/logService';
|
||||
import { showWelcomePage } from './data/editorHelpers';
|
||||
import { CommandRegistry, registerAllCommands } from './commands';
|
||||
import { SharedConstants, newNotification } from '@bfemulator/app-shared';
|
||||
import { beginAdd } from './data/action/notificationActions';
|
||||
import { globalHandlers } from './utils/eventHandlers';
|
||||
import './ui/styles/globals.scss';
|
||||
|
@ -66,7 +67,9 @@ CommandServiceImpl.remoteCall(SharedConstants.Commands.ClientInit.Loaded)
|
|||
.then(() => {
|
||||
showWelcomePage();
|
||||
// do actions on main side that might open a document, so that they will be active over the welcome screen
|
||||
CommandServiceImpl.remoteCall(SharedConstants.Commands.ClientInit.PostWelcomeScreen);
|
||||
CommandServiceImpl.remoteCall(
|
||||
SharedConstants.Commands.ClientInit.PostWelcomeScreen
|
||||
);
|
||||
window.addEventListener('keydown', globalHandlers);
|
||||
})
|
||||
.catch(err => {
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
// eslint-disable-next-line typescript/no-var-requires
|
||||
const { ipcRenderer, remote } = require('electron');
|
||||
|
||||
ipcRenderer.on('inspect', (sender, obj) => {
|
||||
|
@ -42,7 +43,7 @@ ipcRenderer.on('bot-updated', (sender, bot) => {
|
|||
window.host.dispatch('bot-updated', bot);
|
||||
});
|
||||
|
||||
ipcRenderer.on('toggle-dev-tools', (sender) => {
|
||||
ipcRenderer.on('toggle-dev-tools', () => {
|
||||
remote.getCurrentWebContents().toggleDevTools();
|
||||
});
|
||||
|
||||
|
@ -55,29 +56,35 @@ ipcRenderer.on('theme', (sender, ...args) => {
|
|||
});
|
||||
|
||||
window.host = {
|
||||
handlers: {
|
||||
'inspect': [],
|
||||
'bot-updated': [],
|
||||
'accessory-click': [],
|
||||
'theme': []
|
||||
},
|
||||
bot: {},
|
||||
handlers: {
|
||||
'accessory-click': [],
|
||||
'bot-updated': [],
|
||||
inspect: [],
|
||||
theme: [],
|
||||
},
|
||||
logger: {
|
||||
error: function(message) {
|
||||
ipcRenderer.sendToHost('logger.error', message);
|
||||
},
|
||||
log: function(message) {
|
||||
ipcRenderer.sendToHost('logger.log', message);
|
||||
},
|
||||
error: function(message) {
|
||||
ipcRenderer.sendToHost('logger.error', message);
|
||||
}
|
||||
},
|
||||
|
||||
on: function(event, handler) {
|
||||
if (handler && Array.isArray(this.handlers[event]) && !this.handlers[event].includes(handler)) {
|
||||
if (
|
||||
handler &&
|
||||
Array.isArray(this.handlers[event]) &&
|
||||
!this.handlers[event].includes(handler)
|
||||
) {
|
||||
this.handlers[event].push(handler);
|
||||
}
|
||||
return () => {
|
||||
this.handlers[event] = this.handlers[event].filter(item => item !== handler);
|
||||
}
|
||||
this.handlers[event] = this.handlers[event].filter(
|
||||
item => item !== handler
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
enableAccessory: function(id, enabled) {
|
||||
|
|
|
@ -34,13 +34,19 @@
|
|||
// import * as log from './v1/log';
|
||||
|
||||
export default function interceptError() {
|
||||
(process as NodeJS.EventEmitter).on('uncaughtException', _error => {
|
||||
// log.error('[err-client]', error.message, error.stack);
|
||||
});
|
||||
(process as NodeJS.EventEmitter).on('uncaughtException', _error => {
|
||||
// log.error('[err-client]', error.message, error.stack);
|
||||
});
|
||||
|
||||
window.onerror = (_message: string, _filename?: string, _lineno?: number, _colno?: number, _error?: Error) => {
|
||||
// log.error('[err-client]', message, filename, lineno, colno, error);
|
||||
window.onerror = (
|
||||
_message: string,
|
||||
_filename?: string,
|
||||
_lineno?: number,
|
||||
_colno?: number,
|
||||
_error?: Error
|
||||
) => {
|
||||
// log.error('[err-client]', message, filename, lineno, colno, error);
|
||||
|
||||
return true; // prevent default handler
|
||||
};
|
||||
return true; // prevent default handler
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,11 +31,11 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
const electron = (window as any).require('electron');
|
||||
|
||||
import { Channel, Disposable, IPC } from '@bfemulator/sdk-shared';
|
||||
|
||||
export const ElectronIPC = new class extends IPC {
|
||||
const electron = (window as any).require('electron');
|
||||
|
||||
class ElectronIPCImpl extends IPC {
|
||||
constructor() {
|
||||
super();
|
||||
electron.ipcRenderer.on('ipc:message', (_sender: any, ...args: any[]) => {
|
||||
|
@ -47,11 +47,13 @@ export const ElectronIPC = new class extends IPC {
|
|||
});
|
||||
}
|
||||
|
||||
send(...args: any[]): void {
|
||||
public send(...args: any[]): void {
|
||||
electron.ipcRenderer.send('ipc:message', ...args);
|
||||
}
|
||||
|
||||
registerChannel(channel: Channel): Disposable {
|
||||
public registerChannel(channel: Channel): Disposable {
|
||||
return super.registerChannel(channel);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const ElectronIPC = new ElectronIPCImpl();
|
||||
|
|
|
@ -36,35 +36,48 @@ import {
|
|||
CommandService,
|
||||
CommandServiceImpl as InternalSharedService,
|
||||
Disposable,
|
||||
DisposableImpl
|
||||
DisposableImpl,
|
||||
} from '@bfemulator/sdk-shared';
|
||||
|
||||
import { CommandRegistry } from '../../commands';
|
||||
import { ElectronIPC } from '../../ipc';
|
||||
|
||||
export const CommandServiceImpl = new class extends DisposableImpl implements CommandService {
|
||||
|
||||
class CServiceImpl extends DisposableImpl implements CommandService {
|
||||
private readonly _service: InternalSharedService;
|
||||
|
||||
init() { return null; }
|
||||
public init() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public get registry() { return this._service.registry; }
|
||||
public get registry() {
|
||||
return this._service.registry;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._service = new InternalSharedService(ElectronIPC, 'command-service', CommandRegistry);
|
||||
this._service = new InternalSharedService(
|
||||
ElectronIPC,
|
||||
'command-service',
|
||||
CommandRegistry
|
||||
);
|
||||
super.toDispose(this._service);
|
||||
}
|
||||
|
||||
call(commandName: string, ...args: any[]): Promise<any> {
|
||||
public call(commandName: string, ...args: any[]): Promise<any> {
|
||||
return this._service.call(commandName, ...args);
|
||||
}
|
||||
|
||||
remoteCall(commandName: string, ...args: any[]): Promise<any> {
|
||||
public remoteCall(commandName: string, ...args: any[]): Promise<any> {
|
||||
return this._service.remoteCall(commandName, ...args);
|
||||
}
|
||||
|
||||
on(event: string, handler?: CommandHandler): Disposable;
|
||||
on(event: 'command-not-found', handler?: (commandName: string, ...args: any[]) => any) {
|
||||
public on(event: string, handler?: CommandHandler): Disposable;
|
||||
public on(
|
||||
event: 'command-not-found',
|
||||
handler?: (commandName: string, ...args: any[]) => any
|
||||
) {
|
||||
return this._service.on(event, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const CommandServiceImpl = new CServiceImpl();
|
||||
|
|
ΠΠ΅ΠΊΠΎΡΠΎΡΡΠ΅ ΡΠ°ΠΉΠ»Ρ Π½Π΅ Π±ΡΠ»ΠΈ ΠΏΠΎΠΊΠ°Π·Π°Π½Ρ ΠΈΠ·-Π·Π° ΡΠ»ΠΈΡΠΊΠΎΠΌ Π±ΠΎΠ»ΡΡΠΎΠ³ΠΎ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²Π° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½Π½ΡΡ ΡΠ°ΠΉΠ»ΠΎΠ² ΠΠΎΠΊΠ°Π·Π°ΡΡ Π±ΠΎΠ»ΡΡΠ΅
ΠΠ°Π³ΡΡΠ·ΠΊΠ°β¦
Π‘ΡΡΠ»ΠΊΠ° Π² Π½ΠΎΠ²ΠΎΠΉ Π·Π°Π΄Π°ΡΠ΅