Skip to content

Commit

Permalink
update ui, improve logic, bug fixes (#319)
Browse files Browse the repository at this point in the history
* update ui, improve logic, bug fixes

* add space before link

* ux review fixes

* test fix
slavik-lvovsky authored Jul 13, 2020

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 224f0f4 commit e5e7126
Showing 11 changed files with 219 additions and 115 deletions.
2 changes: 1 addition & 1 deletion backend/.nycrc.json
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
"temp-dir": "./reports/.nyc_output",
"report-dir": "./reports/coverage",
"check-coverage": true,
"branches": 81,
"branches": 82,
"lines": 88,
"functions": 85,
"statements": 88
4 changes: 2 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "yeoman-ui",
"version": "1.1.1",
"version": "1.1.2",
"displayName": "Application Wizard",
"publisher": "SAPOS",
"author": {
@@ -53,7 +53,7 @@
},
{
"command": "exploreGenerators",
"title": "Explore Generators"
"title": "Explore and Install Generators"
}
],
"menus": {
19 changes: 16 additions & 3 deletions backend/src/exploregens.ts
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ export class ExploreGens {
public static getInstallationLocation(wsConfig: any) {
return _.trim(wsConfig.get(ExploreGens.INSTALLATION_LOCATION));
}

private static readonly INSTALLATION_LOCATION = "Explore Generators.installationLocation";

private logger: IChildLogger;
@@ -22,7 +22,9 @@ export class ExploreGens {
private cachedInstalledGeneratorsPromise: Promise<string[]>;
private context: any;
private vscode: any;
private isInTheiaCached: boolean;

private readonly theiaCommands: string[] = ["theia.open", "preferences:open", "keymaps:open", "workspace:openRecent"];
private readonly LAST_AUTO_UPDATE_DATE = "Explore Generators.lastAutoUpdateDate";
private readonly SEARCH_QUERY = "Explore Generators.searchQuery";
private readonly AUTO_UPDATE = "Explore Generators.autoUpdate"
@@ -80,6 +82,7 @@ export class ExploreGens {
this.rpc.registerMethod({ func: this.uninstall, thisArg: this });
this.rpc.registerMethod({ func: this.isInstalled, thisArg: this });
this.rpc.registerMethod({ func: this.getRecommendedQuery, thisArg: this });
this.rpc.registerMethod({ func: this.isInTheia, thisArg: this });
}

private async updateAllInstalledGenerators() {
@@ -245,7 +248,7 @@ export class ExploreGens {
if (_.isEmpty(customLocation)) {
return env.getNpmPaths();
}

return [path.join(customLocation, this.NODE_MODULES)];
}

@@ -257,5 +260,15 @@ export class ExploreGens {
return packagePath.substring(nodeModulesIndex + this.NODE_MODULES.length + 1);
})
resolve(_.uniq(gensFullNames));
}
}

private async isInTheia() {
if (_.isNil(this.isInTheiaCached)) {
const commands = await this.vscode.commands.getCommands(true);
const foundCommands = _.intersection(commands, this.theiaCommands);
this.isInTheiaCached = !_.isEmpty(foundCommands);
}

return this.isInTheiaCached;
}
}
10 changes: 3 additions & 7 deletions backend/src/panels/AbstractWebviewPanel.ts
Original file line number Diff line number Diff line change
@@ -25,13 +25,9 @@ export abstract class AbstractWebviewPanel {
protected readonly defaultNpmPaths: string[] = Environment.createEnv().getNpmPaths();

public loadWebviewPanel(uiOptions?: any) {
if (this.webViewPanel && _.isEmpty(uiOptions)) {
this.webViewPanel.reveal();
} else {
this.disposeWebviewPanel();
const webViewPanel = this.createWebviewPanel();
this.setWebviewPanel(webViewPanel, uiOptions);
}
this.disposeWebviewPanel();
const webViewPanel = this.createWebviewPanel();
this.setWebviewPanel(webViewPanel, uiOptions);
}

protected constructor(context: vscode.ExtensionContext) {
12 changes: 10 additions & 2 deletions backend/src/panels/ExploreGensPanel.ts
Original file line number Diff line number Diff line change
@@ -17,13 +17,21 @@ export class ExploreGensPanel extends AbstractWebviewPanel {
public constructor(context: vscode.ExtensionContext) {
super(context);
this.viewType = "exploreGens";
this.viewTitle = "Explore Generators";
this.viewTitle = "Explore and Install Generators";
this.focusedKey = "exploreGens.Focused";
this.htmlFileName = path.join("exploregens", "index.html");
}

public loadWebviewPanel() {
if (this.webViewPanel) {
this.webViewPanel.reveal();
} else {
super.loadWebviewPanel();
}
}

public disposeWebviewPanel() {
super.disposeWebviewPanel();
this.exploreGens = null;
}
}
}
2 changes: 1 addition & 1 deletion backend/src/panels/YeomanUIPanel.ts
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ export class YeomanUIPanel extends AbstractWebviewPanel {
vscodeYouiEvents,
this.outputChannel,
this.logger,
{ genFilter: this.genFilter, messages: this.messages, defaultNpmPaths: this.getDefaultPaths() },
{ genFilter: this.genFilter, messages: this.messages, data: _.get(uiOptions, "data"), defaultNpmPaths: this.getDefaultPaths() },
_.get(vscode, "workspace.workspaceFolders[0].uri.fsPath"));
this.yeomanui.registerCustomQuestionEventHandler("file-browser", "getFilePath", this.showOpenFileDialog.bind(this));
this.yeomanui.registerCustomQuestionEventHandler("folder-browser", "getPath", this.showOpenFolderDialog.bind(this));
34 changes: 31 additions & 3 deletions backend/tests/exploregens.spec.ts
Original file line number Diff line number Diff line change
@@ -47,7 +47,8 @@ const testVscode = {
globalState
},
commands: {
executeCommand: () => true
executeCommand: () => Promise.reject("not implemented"),
getCommands: () => Promise.reject("not implemented")
}
};

@@ -150,18 +151,43 @@ describe('exploregens unit test', () => {
vscodeCommandsMock.verify();
});

describe("isInTheia", () => {
it("use cached value", async () => {
exploregens["isInTheiaCached"] = true;
vscodeCommandsMock.expects("getCommands").never();
const res = await exploregens["isInTheia"]();
expect(res).to.be.true;
});

it("returns true", async () => {
exploregens["isInTheiaCached"] = undefined;
vscodeCommandsMock.expects("getCommands").withExactArgs(true).resolves(["theia.open", "preferences:open"]);
const res = await exploregens["isInTheia"]();
expect(res).to.be.true;
});

it("returns false", async () => {
exploregens["isInTheiaCached"] = undefined;
vscodeCommandsMock.expects("getCommands").withExactArgs(true).resolves(["workbench.action.openGlobalKeybindings"]);
const res = await exploregens["isInTheia"]();
expect(res).to.be.false;
});
});

describe("NPM", () => {
it("win32 platform", () => {
const stub = sinon.stub(process, 'platform').value("win32");
const exploregens1 = new ExploreGens(rpc, null, testVscode.context, testVscode);
expect(exploregens1["NPM"]).to.be.equal("npm.cmd");
const res = exploregens1["NPM"];
expect(res).to.be.equal("npm.cmd");
stub.restore();
});

it("linux platfrom", () => {
const stub = sinon.stub(process, 'platform').value("linux");
const exploregens2 = new ExploreGens(rpc, null, testVscode.context, testVscode);
expect(exploregens2["NPM"]).to.be.equal("npm");
const res = exploregens2["NPM"];
expect(res).to.be.equal("npm");
stub.restore();
});
});
@@ -172,6 +198,7 @@ describe('exploregens unit test', () => {
rpcMock.expects("registerMethod").withExactArgs({ func: exploregens["install"], thisArg: exploregens });
rpcMock.expects("registerMethod").withExactArgs({ func: exploregens["uninstall"], thisArg: exploregens });
rpcMock.expects("registerMethod").withExactArgs({ func: exploregens["isInstalled"], thisArg: exploregens });
rpcMock.expects("registerMethod").withExactArgs({ func: exploregens["isInTheia"], thisArg: exploregens });
rpcMock.expects("registerMethod").withExactArgs({ func: exploregens["getRecommendedQuery"], thisArg: exploregens });

const customLocation = path.join("home", "user", "projects");
@@ -186,6 +213,7 @@ describe('exploregens unit test', () => {
rpcMock.expects("registerMethod").withExactArgs({ func: exploregens["install"], thisArg: exploregens });
rpcMock.expects("registerMethod").withExactArgs({ func: exploregens["uninstall"], thisArg: exploregens });
rpcMock.expects("registerMethod").withExactArgs({ func: exploregens["isInstalled"], thisArg: exploregens });
rpcMock.expects("registerMethod").withExactArgs({ func: exploregens["isInTheia"], thisArg: exploregens });
rpcMock.expects("registerMethod").withExactArgs({ func: exploregens["getRecommendedQuery"], thisArg: exploregens });

workspaceConfigMock.expects("get").withExactArgs(ExploreGens["INSTALLATION_LOCATION"]).returns("");
107 changes: 63 additions & 44 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
@@ -6,10 +6,10 @@
:height="64"
:width="64"
:color="isLoadingColor"
background-color="transparent"
background-color="transparent"
loader="spinner"
></loading>

<Header
v-if="prompts.length"
:headerTitle="headerTitle"
@@ -22,7 +22,12 @@

<v-row class="main-row ma-0 pa-0">
<v-col class="left-col ma-0 pa-0" cols="3">
<Navigation v-if="prompts.length" :promptIndex="promptIndex" :prompts="prompts" @onGotoStep="gotoStep" />
<Navigation
v-if="prompts.length"
:promptIndex="promptIndex"
:prompts="prompts"
@onGotoStep="gotoStep"
/>
</v-col>
<v-col cols="9" class="right-col">
<v-row class="prompts-col">
@@ -49,21 +54,21 @@
/>
</v-col>
</v-row>
<v-row
v-if="prompts.length > 0 && !isDone"
style="height: 4rem; margin: 0;"
sm="auto"
>
<div class="bottom-right-col" style="flex:1;">
</div>
<div class="diagonal">
</div>
<v-row v-if="prompts.length > 0 && !isDone" style="height: 4rem; margin: 0;" sm="auto">
<div class="bottom-right-col" style="flex:1;"></div>
<div class="diagonal"></div>
<div class="bottom-buttons-col" style="display:flex;align-items: center;">
<v-btn id="back" :disabled="promptIndex<1 || isReplaying" @click="back" v-show="promptIndex > 0">
<v-btn
id="back"
:disabled="promptIndex<1 || isReplaying"
@click="back"
v-show="promptIndex > 0"
>
<v-icon left>mdi-chevron-left</v-icon>Back
</v-btn>
<v-btn id="next" :disabled="!stepValidated" @click="next">
Next<v-icon right>mdi-chevron-right</v-icon>
Next
<v-icon right>mdi-chevron-right</v-icon>
</v-btn>
</div>
</v-row>
@@ -149,7 +154,9 @@ export default {
);
},
headerTitle() {
const titleSuffix = _.isEmpty(this.generatorPrettyName) ? "" : ` - ${this.generatorPrettyName}`;
const titleSuffix = _.isEmpty(this.generatorPrettyName)
? ""
: ` - ${this.generatorPrettyName}`;
return `${this.messages.yeoman_ui_title}${titleSuffix}`;
},
currentPrompt() {
@@ -158,8 +165,10 @@ export default {
isNoGenerators() {
const promptName = _.get(this.currentPrompt, "name");
const message = _.get(this.messages, "select_generator_name", "");
const noChoices = _.isEmpty(_.get(this.currentPrompt, "questions[0].choices"));
return (promptName === message) && noChoices;
const noChoices = _.isEmpty(
_.get(this.currentPrompt, "questions[0].choices")
);
return promptName === message && noChoices;
}
},
watch: {
@@ -183,14 +192,16 @@ export default {
setBusyIndicator() {
this.showBusyIndicator =
_.isEmpty(this.prompts) ||
(this.currentPrompt
&& (this.currentPrompt.status === PENDING || this.currentPrompt.status === EVALUATING)
&& !this.isDone);
(this.currentPrompt &&
(this.currentPrompt.status === PENDING ||
this.currentPrompt.status === EVALUATING) &&
!this.isDone);
},
back() {
this.gotoStep(1); // go 1 step back
this.gotoStep(1); // go 1 step back
},
gotoStep(numOfSteps) { // go numOfSteps step back
gotoStep(numOfSteps) {
// go numOfSteps step back
try {
this.isReplaying = true;
this.numOfSteps = numOfSteps;
@@ -275,20 +286,21 @@ export default {
if (question[prop] === FUNCTION) {
const that = this;
question[prop] = async (...args) => {
if (this.currentPrompt) {
this.currentPrompt.status = EVALUATING;
}
if (this.currentPrompt) {
this.currentPrompt.status = EVALUATING;
}
try {
return await that.rpc.invoke("evaluateMethod", [
args,
question.name,
prop
]);
} catch(e) {
that.showBusyIndicator = false;
throw(e);
}};
try {
return await that.rpc.invoke("evaluateMethod", [
args,
question.name,
prop
]);
} catch (e) {
that.showBusyIndicator = false;
throw e;
}
};
}
}
}
@@ -321,7 +333,10 @@ export default {
promptDescription = this.messages.select_generator_description;
promptName = this.messages.select_generator_name;
} else {
const promptToDisplay = _.get(this.promptsInfoToDisplay, "[" + (this.promptIndex - 1) +"]");
const promptToDisplay = _.get(
this.promptsInfoToDisplay,
"[" + (this.promptIndex - 1) + "]"
);
promptDescription = _.get(promptToDisplay, "description", "");
promptName = _.get(promptToDisplay, "name", name);
}
@@ -365,8 +380,8 @@ export default {
if (!window.vscode) {
// eslint-disable-next-line
window.vscode = acquireVsCodeApi();
}
}
return window.vscode;
}
},
@@ -400,13 +415,12 @@ export default {
});
});
this.displayGeneratorsPrompt();
this.displayGeneratorsPrompt();
},
async setMessagesAndSaveState() {
const uiOptions = await this.rpc.invoke("getState");
this.messages = uiOptions.messages;
const genFilter = uiOptions.genFilter;
this.isGeneric = _.isEmpty(_.get(genFilter, "types")) && _.isEmpty(_.get(genFilter, "categories"));
this.isGeneric = _.get(this.messages, "panel_title") === "Yeoman UI";
const vscodeApi = this.getVsCodeApi();
if (vscodeApi) {
vscodeApi.setState(uiOptions);
@@ -443,7 +457,7 @@ export default {
dataObj.rpc = this.rpc;
Object.assign(this.$data, dataObj);
this.init();
this.displayGeneratorsPrompt();
}
},
@@ -497,7 +511,12 @@ div.consoleClassVisible .v-footer {
}
.diagonal {
width: 80px;
background: linear-gradient(120deg, var(--vscode-editor-background, #1e1e1e) 0%, var(--vscode-editor-background, #1e1e1e) 50%, transparent 50%);
background: linear-gradient(
120deg,
var(--vscode-editor-background, #1e1e1e) 0%,
var(--vscode-editor-background, #1e1e1e) 50%,
transparent 50%
);
background-color: var(--vscode-editorWidget-background, #252526);
}
.bottom-right-col {
@@ -510,6 +529,6 @@ div.consoleClassVisible .v-footer {
padding-right: 25px;
}
.bottom-buttons-col > .v-btn:not(:last-child) {
margin-right: 10px !important;
margin-right: 10px !important;
}
</style>
129 changes: 85 additions & 44 deletions frontend/src/ExploreGensApp.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<template>
<v-app id="exploregens" class="exploregens-main">
<div class="explore-generators">
<v-app-bar class="elevation-0">
<v-app-bar class="pa-0 ma-0">
<v-toolbar-title>{{messages.title}}</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-expansion-panels class="explore-generators-description" flat>
<v-expansion-panel>
<v-expansion-panels v-if="isInTheia" flat>
<v-expansion-panel class="explore-generators-panel">
<v-expansion-panel-header disable-icon-rotate>
{{messages.description}}
<template v-slot:actions>
@@ -15,33 +15,31 @@
</v-expansion-panel-header>
<v-expansion-panel-content>
<v-row>
<v-col class="pa-0" :cols="1">
<v-icon color="blue">mdi-information-outline</v-icon>
</v-col>
<v-col class="pa-0" :cols="11">
<v-icon class="mr-3" color="blue">mdi-information-outline</v-icon>
<v-col class="pa-2">
<v-text>{{messages.legal_note}}</v-text>
</v-col>
</v-row>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</div>
<v-row class="prompts-col">
<v-row class="mt-3">
<v-col :cols="10">
<v-text-field
class="explore-generators-search-gens pa-2"
:label="messages.search"
v-model="query"
hide-details="auto"
@input="onQueryChange"
rounded
clearable
@click:clear="onQueryChange"
background-color="var(--vscode-input-background, #3c3c3c)"
/>
</v-col>
<v-col :cols="2">
<v-select
rounded
background-color="var(--vscode-input-background, #3c3c3c)"
class="explore-generators-search-gens pa-2"
hide-details="auto"
:items="items"
v-model="recommended"
:label="messages.recommended"
@@ -53,49 +51,59 @@
<v-row class="explore-generators-search">
<v-card-title>{{searchResults}}</v-card-title>
<v-icon v-if="refineSearch" color="blue">mdi-information-outline</v-icon>
<v-card-title class="pa-0" v-if="refineSearch">{{messages.refine_search}}</v-card-title>
<v-card-title class="pa-0 ml-2" v-if="refineSearch">{{messages.refine_search}}</v-card-title>
</v-row>

<v-slide-x-transition>
<v-row class="explore-generators-cards">
<v-col
v-for="(gen, i) in gens"
:key="i"
cols="12"
md="4"
sm="6"
class="pa-3 d-flex flex-column"
v-for="(gen, i) in gens"
:key="i"
class="pb-2 d-flex flex-column"
>
<v-card width="430" class="d-flex flex-column mx-auto" height="280" tile elevation="2">
<v-card-title primary-title>
<h3 class="headline mb-0">{{ gen.package.name }}</h3>
</v-card-title>
<v-card-text style="overflow-y: auto; height:200px" v-text="gen.package.description" />
<v-card-text v-text="gen.package.version" />
<v-card-text class="homepage">
<a :href="gen.package.links.npm">{{messages.more_info}}</a>
</v-card-text>
<v-card-actions>
<div class="ma-2">
<v-btn
raised
elevation="5"
:disabled="gen.disabledToHandle"
:color="gen.color"
@click="onAction(gen)"
>{{gen.action}}</v-btn>
<v-progress-linear v-if="gen.disabledToHandle" indeterminate color="primary"></v-progress-linear>
</div>
</v-card-actions>
</v-card>
<v-item>
<v-card
width="500"
class="d-flex flex-column mx-auto"
height="250"
tile
hover
flat
dark
elevation="2"
>
<v-card-title>{{genDisplayName(gen)}}</v-card-title>
<v-card-text scrollable class="description">{{gen.package.description}}</v-card-text>
<v-spacer></v-spacer>
<v-card-text class="homepage">
<a :href="gen.package.links.npm">{{messages.more_info}}</a>
</v-card-text>
<v-card-actions>
<div class="ma-2">
<v-btn
min-width="140px"
raised
elevation="5"
:disabled="gen.disabledToHandle"
:color="gen.color"
@click="onAction(gen)"
>{{gen.action}}</v-btn>
<v-progress-linear v-if="gen.disabledToHandle" indeterminate color="primary"></v-progress-linear>
</div>
</v-card-actions>
</v-card>
</v-item>
</v-col>
</v-row>
</v-slide-x-transition>
</v-app>
</template>

<script>
const ALL_GENS = "-----";
const ALL_GENS = "all";
import * as _ from "lodash";
import { RpcBrowser } from "@sap-devx/webview-rpc/out.browser/rpc-browser";
@@ -112,7 +120,8 @@ export default {
total: 0,
query: "",
recommended: ALL_GENS,
messages
messages,
isInTheia: false
};
},
computed: {
@@ -133,6 +142,9 @@ export default {
}
},
methods: {
genDisplayName(gen) {
return `${gen.package.name} ${gen.package.version}`;
},
actionName(gen) {
if (gen.disabledToHandle) {
return gen.installed
@@ -156,9 +168,10 @@ export default {
});
if (currentGen) {
gen.disabledToHandle = false;
currentGen.action = this.actionName(gen);
currentGen.color = this.actionColor(gen);
currentGen.disabledToHandle = false;
currentGen.installed = gen.installed;
currentGen.action = this.actionName(currentGen);
currentGen.color = this.actionColor(currentGen);
}
},
onQueryChange() {
@@ -184,6 +197,9 @@ export default {
this.items = await this.rpc.invoke("getRecommendedQuery");
this.items.unshift(ALL_GENS);
},
async setIsInTheia() {
this.isInTheia = await this.rpc.invoke("isInTheia");
},
async updateBeingHandledGenerator(genName, isBeingHandled) {
const gen = _.find(this.gens, gen => {
return gen.package.name === genName;
@@ -230,7 +246,8 @@ export default {
await this.setupRpc();
await Promise.all([
this.getRecommendedQuery(),
this.getFilteredGenerators()
this.getFilteredGenerators(),
this.setIsInTheia()
]);
}
};
@@ -273,6 +290,30 @@ export default {
margin: 0px;
height: calc(100% - 4rem);
}
.v-card__title {
word-wrap: break-word;
word-break: normal;
}
.description.v-card__text {
text-overflow: ellipsis;
overflow: hidden;
}
.homepage.v-card__text {
padding-bottom: 0;
}
a {
font-size: 11px;
}
.explore-generators-panel {
background-color: var(--vscode-editor-background, #1e1e1e) !important;
box-shadow: none;
text-align: justify;
}
.explore-generators-search-gens {
background-color: var(--vscode-editorWidget-background, #252526);
}
.explore-generators-search .v-card__title {
font-size: 14px;
}
2 changes: 1 addition & 1 deletion frontend/src/components/Header.vue
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
<v-app-bar class="elevation-0">
<v-toolbar-title>{{headerTitle}}</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn v-if="isGeneric" text small color="primary" class="ma-2" @click="openExploreGenerators">Explore Generators ...</v-btn>
<v-btn v-if="isGeneric" text x-small color="primary" class="ma-2" @click="openExploreGenerators">Explore and Install Generators...</v-btn>
<v-btn v-if="!isInVsCode" class="ma-2" icon @click="collapseOutput">
<v-card-text>
<v-icon>mdi-console</v-icon>
13 changes: 6 additions & 7 deletions frontend/src/exploreGensMessages.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
export default {
title: "Explore Generators",
title: "Explore and Install Generators",
description: "This view enables the exploration and installation of external open source Yeoman generators.",
legal_note: `SAP provides you with a mechanism to access third party sites to view and download open-source, 3rd party or its own tools, libraries, or software components (“Extensions”).
legal_note: `SAP provides you with a mechanism to access third party sites to view and download open-source, 3rd party or its own tools, libraries, or software components (“Extensions”) to dev spaces in SAP Business Application Studio.
Using this mechanism, you can view and install Yeoman Generators from the public npm registry at your own risk.
SAP does not certify or endorse any of the third-party sites or Extensions.
SAP does not certify or endorse any of the third-party sites or Extensions, and they are not part of SAP Business Application Studio.
You must ensure that you have and maintain all the necessary rights to use these Extensions.
You are responsible for all aspects of the Extensions, including maintenance, management, security, and support.
You are responsible for all aspects of the Extensions, including maintenance, management, security, and support.
You will assume all responsibility for any negative effects caused by or relating to the Extensions.
SAP may elect, in its sole discretion, to disable any functionality that includes Extensions that cause system malfunction or that SAP reasonably believes may cause harm to SAP systems.
`,
SAP may elect, in its sole discretion, to disable any dev space that includes Extensions that cause system malfunction or that SAP reasonably believes may cause harm to SAP systems.`,
search: "Search for Generators",
recommended: "Search Shortcuts",
refine_search: "Refine your search to decrease the number of results",
refine_search: "To get better results, refine your search ",
results_out_of_total: (gensQuantity, totalGensQuantity) => `Showing ${gensQuantity} out of ${totalGensQuantity} results.`,
results: (totalGensQuantity) => `Showing ${totalGensQuantity} results.`,
install: "Install",

0 comments on commit e5e7126

Please sign in to comment.