diff --git a/front-end/pom.xml b/front-end/pom.xml index 1a067cd27..b3234b717 100644 --- a/front-end/pom.xml +++ b/front-end/pom.xml @@ -11,7 +11,7 @@ pom apicurio-studio-fe - app + studio servlet \ No newline at end of file diff --git a/front-end/servlet/pom.xml b/front-end/servlet/pom.xml index 0f08ae611..3661e113e 100644 --- a/front-end/servlet/pom.xml +++ b/front-end/servlet/pom.xml @@ -59,7 +59,7 @@ ${project.groupId} - apicurio-studio-fe-app + apicurio-studio-fe-studio ${project.version} provided diff --git a/front-end/studio/.angular-cli.json b/front-end/studio/.angular-cli.json new file mode 100644 index 000000000..deb06a774 --- /dev/null +++ b/front-end/studio/.angular-cli.json @@ -0,0 +1,37 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "name": "studio" + }, + "apps": [ + { + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "keycloak.json", + "keycloak.js", + "version.js", + "config.js" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "tsconfig": "tsconfig.app.json", + "prefix": "app", + "styles": [ + "styles.css" + ], + "scripts": [], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "defaults": { + "styleExt": "css", + "component": {} + } +} diff --git a/front-end/studio/.gitignore b/front-end/studio/.gitignore new file mode 100644 index 000000000..46223375c --- /dev/null +++ b/front-end/studio/.gitignore @@ -0,0 +1,47 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +/deploy-prod.sh +/serve.sh +/node + +# compiled output +/dist +/dist-server +/tmp +/out-tsc + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +testem.log +/typings + +# e2e +/e2e/*.js +/e2e/*.map + +# System Files +.DS_Store +Thumbs.db diff --git a/front-end/studio/README.md b/front-end/studio/README.md new file mode 100644 index 000000000..541caecd3 --- /dev/null +++ b/front-end/studio/README.md @@ -0,0 +1,16 @@ +# Apicurio Studio + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.6.8. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically +reload if you change any of the source files. + +To run on a different port, try `ng serve --port 8888` for example. And if you want AOT enabled, +you can run `ng serve --aot --port 8888`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. +Use the `-prod` flag for a production build. diff --git a/front-end/studio/package.json b/front-end/studio/package.json new file mode 100644 index 000000000..f11619198 --- /dev/null +++ b/front-end/studio/package.json @@ -0,0 +1,43 @@ +{ + "name": "apicurio-design-studio", + "version": "1.0.0", + "description": "A web application to help design restful APIs.", + "author": "Eric Wittmann", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/apicurio/apicurio-studio" + }, + "scripts": { + "ng": "ng", + "start": "ng serve --port 8888 --aot", + "build": "ng build -prod" + }, + "private": true, + "dependencies": { + "@angular/animations": "^5.2.0", + "@angular/common": "^5.2.0", + "@angular/compiler": "^5.2.0", + "@angular/core": "^5.2.0", + "@angular/forms": "^5.2.0", + "@angular/platform-browser": "^5.2.0", + "@angular/platform-browser-dynamic": "^5.2.0", + "@angular/router": "^5.2.0", + "oai-ts-core": "0.2.9", + "oai-ts-commands": "0.2.18", + "core-js": "^2.4.1", + "ngx-bootstrap": "2.0.2", + "ng2-ace-editor": "0.3.4", + "yamljs": "0.3.0", + "rxjs": "^5.5.6", + "zone.js": "^0.8.19" + }, + "devDependencies": { + "@angular/cli": "1.6.8", + "@angular/compiler-cli": "^5.2.0", + "@angular/language-service": "^5.2.0", + "@types/node": "~6.0.60", + "ts-node": "~4.1.0", + "typescript": "~2.5.3" + } +} diff --git a/front-end/studio/pom.xml b/front-end/studio/pom.xml new file mode 100644 index 000000000..f67a93dfe --- /dev/null +++ b/front-end/studio/pom.xml @@ -0,0 +1,84 @@ + + 4.0.0 + + io.apicurio + apicurio-studio-fe + 0.2.3-SNAPSHOT + ../pom.xml + + apicurio-studio-fe-studio + jar + apicurio-studio-fe-studio + + + + + com.github.eirslett + frontend-maven-plugin + + + + install node and yarn + + install-node-and-yarn + + + v6.11.2 + v1.3.2 + + + + + yarn install + + yarn + + + install + + + + + ng build + + yarn + + generate-resources + + build + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-dist + process-resources + + copy-resources + + + ${project.build.outputDirectory} + + + ${basedir}/dist + false + + config.js + keycloak.js + keycloak.json + version.js + + + + + + + + + + diff --git a/front-end/studio/src/.gitignore b/front-end/studio/src/.gitignore new file mode 100644 index 000000000..1a5bd6424 --- /dev/null +++ b/front-end/studio/src/.gitignore @@ -0,0 +1,3 @@ +keycloak.* +config.js + diff --git a/front-end/studio/src/app/app-routing.module.ts b/front-end/studio/src/app/app-routing.module.ts new file mode 100644 index 000000000..ddee3aec7 --- /dev/null +++ b/front-end/studio/src/app/app-routing.module.ts @@ -0,0 +1,108 @@ +/** + * @license + * Copyright 2018 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {DashboardPageComponent} from './pages/dashboard/dashboard.page'; +import {AuthenticationCanActivateGuard} from './guards/auth.guard'; +import {SettingsPageComponent} from './pages/settings/settings'; +import {ProfilePageComponent} from './pages/settings/profile/profile.page'; +import {LinkedAccountsPageComponent} from './pages/settings/accounts/accounts.page'; +import {CreatedLinkedAccountPageComponent} from './pages/settings/accounts/{accountType}/created/created.page'; +import {ApisPageComponent} from './pages/apis/apis.page'; +import {CreateApiPageComponent} from './pages/apis/create/create.page'; +import {ImportApiPageComponent} from './pages/apis/import/import.page'; +import {ApiDetailPageComponent} from './pages/apis/{apiId}/api-detail.page'; +import {ApiCollaborationPageComponent} from './pages/apis/{apiId}/collaboration/api-collaboration.page'; +import {ApiAcceptPageComponent} from './pages/apis/{apiId}/collaboration/accept/api-accept.page'; +import {ApiEditorPageComponent, ApiEditorPageGuard} from './pages/apis/{apiId}/editor/api-editor.page'; + +const routes: Routes = [ + { + path: '', + component: DashboardPageComponent, + canActivate: [ AuthenticationCanActivateGuard ] + }, + { + path: "settings", + component: SettingsPageComponent, + canActivate: [ AuthenticationCanActivateGuard ] + }, + { + path: "settings/profile", + component: ProfilePageComponent, + canActivate: [ AuthenticationCanActivateGuard ] + }, + { + path: "settings/accounts", + component: LinkedAccountsPageComponent, + canActivate: [ AuthenticationCanActivateGuard ] + }, + { + path: "settings/accounts/:accountType/created", + component: CreatedLinkedAccountPageComponent, + canActivate: [ AuthenticationCanActivateGuard ] + }, + { + path: "apis", + component: ApisPageComponent, + canActivate: [ AuthenticationCanActivateGuard ] + }, + { + path: "apis/create", + component: CreateApiPageComponent, + canActivate: [ AuthenticationCanActivateGuard ] + }, + { + path: "apis/import", + component: ImportApiPageComponent, + canActivate: [ AuthenticationCanActivateGuard ] + }, + { + path: "apis/:apiId", + component: ApiDetailPageComponent, + canActivate: [ AuthenticationCanActivateGuard ] + }, + { + path: "apis/:apiId/collaboration", + component: ApiCollaborationPageComponent, + canActivate: [ AuthenticationCanActivateGuard ] + }, + { + path: "apis/:apiId/collaboration/accept/:inviteId", + component: ApiAcceptPageComponent, + canActivate: [ AuthenticationCanActivateGuard ] + }, + { + path: "apis/:apiId/editor", + component: ApiEditorPageComponent, + canActivate: [ AuthenticationCanActivateGuard ] + }, +]; + + +@NgModule({ + imports: [ + RouterModule.forRoot(routes) + ], + exports: [ + RouterModule + ], + declarations: [] +}) +export class AppRoutingModule { +} diff --git a/front-end/studio/src/app/app.component.css b/front-end/studio/src/app/app.component.css new file mode 100644 index 000000000..c7b9e62aa --- /dev/null +++ b/front-end/studio/src/app/app.component.css @@ -0,0 +1,3 @@ +.studio-component { + color: inherit; +} diff --git a/front-end/studio/src/app/app.component.html b/front-end/studio/src/app/app.component.html new file mode 100644 index 000000000..0a4190d27 --- /dev/null +++ b/front-end/studio/src/app/app.component.html @@ -0,0 +1,7 @@ +
+ + +
+ +
+
diff --git a/front-end/studio/src/app/app.component.ts b/front-end/studio/src/app/app.component.ts new file mode 100644 index 000000000..b2fb47f80 --- /dev/null +++ b/front-end/studio/src/app/app.component.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component } from '@angular/core'; +import {IAuthenticationService} from './services/auth.service'; + +@Component({ + moduleId: module.id, + selector: "apicurio-studio", + templateUrl: "app.component.html", + styleUrls: [ "app.component.css" ] +}) +export class AppComponent { + + public routerOutletWrapperId: string; + public routerOutletWrapperClass: string; + + constructor(public authService: IAuthenticationService) { + this.routerOutletWrapperId = "api-page-body"; + this.routerOutletWrapperClass = ""; + + authService.isAuthenticated().subscribe(authed => { + if (authed) { + this.routerOutletWrapperId = "api-page-body"; + this.routerOutletWrapperClass = ""; + } else { + this.routerOutletWrapperId = "login-form"; + this.routerOutletWrapperClass = "login-pf"; + } + }); + } + +} diff --git a/front-end/studio/src/app/app.module.ts b/front-end/studio/src/app/app.module.ts new file mode 100644 index 000000000..11c12d5ff --- /dev/null +++ b/front-end/studio/src/app/app.module.ts @@ -0,0 +1,131 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BrowserModule} from '@angular/platform-browser'; +import {NgModule} from '@angular/core'; +import {AppComponent} from './app.component'; +import {FormsModule} from '@angular/forms'; +import {HttpClientModule} from '@angular/common/http'; +import {ApisServiceProvider} from './services/apis.service.provider'; +import {LinkedAccountsServiceProvider} from './services/accounts.service.provider'; +import {AuthenticationServiceProvider} from './services/auth.service.provider'; +import {ConfigService} from './services/config.service'; +import {AuthenticationCanActivateGuard} from './guards/auth.guard'; +import {DashboardPageComponent} from './pages/dashboard/dashboard.page'; +import {BreadcrumbsComponent} from './components/breadcrumbs/breadcrumbs.component'; +import {BreadcrumbComponent} from './components/breadcrumbs/breadcrumb.component'; +import {PageErrorComponent} from './components/page-error.component'; +import {VerticalNavComponent} from './components/vertical-nav.component'; +import {NavHeaderComponent} from './components/nav-header.component'; +import {AppRoutingModule} from './app-routing.module'; +import {ConfirmDeleteDialogComponent} from './components/dialogs/confirm-delete.component'; +import {CopyUrlDialogComponent} from './components/dialogs/copy-url.component'; +import {BsDropdownModule, ModalModule} from 'ngx-bootstrap'; +import {DivAutoHeight, TextAreaAutosize, TextBoxAutosize} from './directives/autosize.directive'; +import {NotFoundPageComponent} from './pages/404.page'; +import {SettingsNavComponent} from './pages/settings/_components/settings-nav.component'; +import {CreatedLinkedAccountPageComponent} from './pages/settings/accounts/{accountType}/created/created.page'; +import {LinkedAccountsPageComponent} from './pages/settings/accounts/accounts.page'; +import {ProfilePageComponent} from './pages/settings/profile/profile.page'; +import {SettingsPageComponent} from './pages/settings/settings'; +import {ApisPageComponent} from './pages/apis/apis.page'; +import {CreateApiPageComponent} from './pages/apis/create/create.page'; +import {ImportApiPageComponent} from './pages/apis/import/import.page'; +import {ImportApiFormComponent} from './pages/apis/import/_components/import-form.component'; +import {CreateApiFormComponent} from './pages/apis/create/_components/create-form.component'; +import {ApisListComponent} from './pages/apis/_components/apis-list.component'; +import {ApisCardsComponent} from './pages/apis/_components/apis-cards.component'; +import {DropDownComponent} from './components/common/drop-down.component'; +import {AceEditorModule} from 'ng2-ace-editor'; +import {ActivityItemComponent} from './pages/apis/{apiId}/_components/activity-item.component'; +import {ApiCollaborationPageComponent} from './pages/apis/{apiId}/collaboration/api-collaboration.page'; +import {ApiAcceptPageComponent} from './pages/apis/{apiId}/collaboration/accept/api-accept.page'; +import {ApiDetailPageComponent} from './pages/apis/{apiId}/api-detail.page'; +import {ValidationIconComponent} from './pages/apis/{apiId}/editor/_components/common/validation-icon.component'; +import {ServerUrlComponent} from './pages/apis/{apiId}/editor/_components/common/server-url.component'; +import {SearchComponent} from './pages/apis/{apiId}/editor/_components/common/search.component'; +import {InlineTextEditorComponent} from './pages/apis/{apiId}/editor/_components/common/inline-text-editor.component'; +import {SchemaTypeComponent} from './pages/apis/{apiId}/editor/_components/common/schema-type.component'; +import {ResponseItemComponent} from './pages/apis/{apiId}/editor/_components/common/response-item.component'; +import {PathItemComponent} from './pages/apis/{apiId}/editor/_components/common/path-item.component'; +import {InlineTextAreaComponent} from './pages/apis/{apiId}/editor/_components/common/inline-textarea-editor.component'; +import {ContextHelpComponent} from './pages/apis/{apiId}/editor/_components/common/context-help.component'; +import {SecurityScheme30DialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/security-scheme-30.component'; +import {SetLicenseDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/set-license.component'; +import {SetContactDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/set-contact.component'; +import {SecurityScheme20DialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/security-scheme-20.component'; +import {AddTagDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/add-tag.component'; +import {AddSchemaPropertyDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/add-schema-property.component'; +import {EditorDisconnectedDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/editor-disconnected.component'; +import {AddServerDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/add-server.component'; +import {CloneDefinitionDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/clone-definition.component'; +import {ClonePathDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/clone-path.component'; +import {ResponseRow30Component} from './pages/apis/{apiId}/editor/_components/forms/operation/response-row-30.component'; +import {ResponseRowComponent} from './pages/apis/{apiId}/editor/_components/forms/operation/response-row.component'; +import {ParamRowComponent} from './pages/apis/{apiId}/editor/_components/forms/operation/param-row.component'; +import {OperationFormComponent} from './pages/apis/{apiId}/editor/_components/forms/operation-form.component'; +import {ContentComponent} from './pages/apis/{apiId}/editor/_components/forms/operation/content.component'; +import {Operation30FormComponent} from './pages/apis/{apiId}/editor/_components/forms/operation-30-form.component'; +import {PropertyRowComponent} from './pages/apis/{apiId}/editor/_components/forms/definition/property-row.component'; +import {ServersSectionComponent} from './pages/apis/{apiId}/editor/_components/forms/shared/servers.component'; +import {DefinitionItemComponent} from './pages/apis/{apiId}/editor/_components/forms/definition-item.component'; +import {DefinitionFormComponent} from './pages/apis/{apiId}/editor/_components/forms/definition-form.component'; +import {ProblemFormComponent} from './pages/apis/{apiId}/editor/_components/forms/problem-form.component'; +import {PathFormComponent} from './pages/apis/{apiId}/editor/_components/forms/path-form.component'; +import {EditorMasterComponent} from './pages/apis/{apiId}/editor/_components/master.component'; +import {ApiEditorPageComponent} from './pages/apis/{apiId}/editor/api-editor.page'; +import {ApiEditorComponent} from './pages/apis/{apiId}/editor/editor.component'; +import {AddQueryParamDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/add-query-param.component'; +import {AddPathDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/add-path.component'; +import {AddResponseDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/add-response.component'; +import {AddFormDataParamDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/add-formData-param.component'; +import {AddDefinitionDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/add-definition.component'; +import {Main20FormComponent, Main30FormComponent} from './pages/apis/{apiId}/editor/_components/forms/main-form.component'; +import {AddMediaTypeDialogComponent} from './pages/apis/{apiId}/editor/_components/dialogs/add-media-type.component'; + + +@NgModule({ + imports: [ + BrowserModule, FormsModule, HttpClientModule, AppRoutingModule, ModalModule.forRoot(), BsDropdownModule.forRoot(), + AceEditorModule + ], + declarations: [ + AppComponent, DashboardPageComponent, BreadcrumbsComponent, BreadcrumbComponent, PageErrorComponent, + VerticalNavComponent, NavHeaderComponent, ConfirmDeleteDialogComponent, CopyUrlDialogComponent, + TextAreaAutosize, DivAutoHeight, TextBoxAutosize, NotFoundPageComponent, SettingsNavComponent, + CreatedLinkedAccountPageComponent, LinkedAccountsPageComponent, ProfilePageComponent, SettingsPageComponent, + ApisPageComponent, CreateApiPageComponent, ImportApiPageComponent, ImportApiFormComponent, CreateApiFormComponent, + ApisListComponent, ApisCardsComponent, DropDownComponent, ActivityItemComponent, ApiCollaborationPageComponent, + ApiAcceptPageComponent, ApiDetailPageComponent, ValidationIconComponent, ServerUrlComponent, SearchComponent, + SchemaTypeComponent, ResponseItemComponent, PathItemComponent, InlineTextAreaComponent, InlineTextEditorComponent, + ContextHelpComponent, SetLicenseDialogComponent, SetContactDialogComponent, SecurityScheme30DialogComponent, + SecurityScheme20DialogComponent, EditorDisconnectedDialogComponent, ClonePathDialogComponent, CloneDefinitionDialogComponent, + AddTagDialogComponent, AddServerDialogComponent, AddSchemaPropertyDialogComponent, ResponseRow30Component, ResponseRowComponent, + ParamRowComponent, ContentComponent, PropertyRowComponent, ServersSectionComponent, ProblemFormComponent, PathFormComponent, + OperationFormComponent, Operation30FormComponent, DefinitionItemComponent, DefinitionFormComponent, EditorMasterComponent, + ApiEditorPageComponent, ApiEditorComponent, AddQueryParamDialogComponent, AddPathDialogComponent, AddResponseDialogComponent, + AddFormDataParamDialogComponent, AddDefinitionDialogComponent, AddMediaTypeDialogComponent, Main20FormComponent, + Main30FormComponent + ], + providers: [ + ApisServiceProvider, LinkedAccountsServiceProvider, AuthenticationServiceProvider, ConfigService, + AuthenticationCanActivateGuard + ], + bootstrap: [AppComponent] +}) +export class AppModule { +} diff --git a/front-end/studio/src/app/components/breadcrumbs/breadcrumb.component.css b/front-end/studio/src/app/components/breadcrumbs/breadcrumb.component.css new file mode 100644 index 000000000..11cd498be --- /dev/null +++ b/front-end/studio/src/app/components/breadcrumbs/breadcrumb.component.css @@ -0,0 +1,3 @@ +.api-breadcrumb { + color: inherit; +} diff --git a/front-end/studio/src/app/components/breadcrumbs/breadcrumb.component.html b/front-end/studio/src/app/components/breadcrumbs/breadcrumb.component.html new file mode 100644 index 000000000..708dbf0fa --- /dev/null +++ b/front-end/studio/src/app/components/breadcrumbs/breadcrumb.component.html @@ -0,0 +1,2 @@ +{{ label }} +{{ label }} diff --git a/front-end/studio/src/app/components/breadcrumbs/breadcrumb.component.ts b/front-end/studio/src/app/components/breadcrumbs/breadcrumb.component.ts new file mode 100644 index 000000000..69c82b2df --- /dev/null +++ b/front-end/studio/src/app/components/breadcrumbs/breadcrumb.component.ts @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Input} from "@angular/core"; + + +@Component({ + moduleId: module.id, + selector: "[breadcrumb]", + templateUrl: "breadcrumb.component.html", + styleUrls: ["breadcrumb.component.css"] +}) +export class BreadcrumbComponent { + + @Input() label: string; + @Input() icon: string; + @Input() route: string[]; + +} diff --git a/front-end/studio/src/app/components/breadcrumbs/breadcrumbs.component.css b/front-end/studio/src/app/components/breadcrumbs/breadcrumbs.component.css new file mode 100644 index 000000000..132e58f1f --- /dev/null +++ b/front-end/studio/src/app/components/breadcrumbs/breadcrumbs.component.css @@ -0,0 +1,8 @@ + +.api-breadcrumbs { + float: left; +} +.api-breadcrumbs .breadcrumb { + margin-bottom: 0; + float: left; +} diff --git a/front-end/studio/src/app/components/breadcrumbs/breadcrumbs.component.html b/front-end/studio/src/app/components/breadcrumbs/breadcrumbs.component.html new file mode 100644 index 000000000..56bc66223 --- /dev/null +++ b/front-end/studio/src/app/components/breadcrumbs/breadcrumbs.component.html @@ -0,0 +1,7 @@ +
+
+ +
+
diff --git a/front-end/studio/src/app/components/breadcrumbs/breadcrumbs.component.ts b/front-end/studio/src/app/components/breadcrumbs/breadcrumbs.component.ts new file mode 100644 index 000000000..68c27a8c8 --- /dev/null +++ b/front-end/studio/src/app/components/breadcrumbs/breadcrumbs.component.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component} from "@angular/core"; + + +@Component({ + moduleId: module.id, + selector: "breadcrumbs", + templateUrl: "breadcrumbs.component.html", + styleUrls: ["breadcrumbs.component.css"] +}) +export class BreadcrumbsComponent { + +} diff --git a/front-end/studio/src/app/components/common/drop-down.component.html b/front-end/studio/src/app/components/common/drop-down.component.html new file mode 100644 index 000000000..51c588e95 --- /dev/null +++ b/front-end/studio/src/app/components/common/drop-down.component.html @@ -0,0 +1,10 @@ + diff --git a/front-end/studio/src/app/components/common/drop-down.component.ts b/front-end/studio/src/app/components/common/drop-down.component.ts new file mode 100644 index 000000000..385a25bfa --- /dev/null +++ b/front-end/studio/src/app/components/common/drop-down.component.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Input, Output} from "@angular/core"; + + +export class DropDownOption { + name?: string; + value?: string; + divider?: boolean; +} + + +@Component({ + moduleId: module.id, + selector: "drop-down", + templateUrl: "drop-down.component.html" +}) +export class DropDownComponent { + + public _open: boolean = false; + + @Input() id: string; + @Input() value: string; + @Input() options: DropDownOption[]; + @Input() noSelectionLabel: string = "No Selection"; + + @Output() onValueChange: EventEmitter = new EventEmitter(); + + public toggle(): void { + this._open = !this._open; + } + + public open(): void { + this._open = true; + } + + public close(): void { + this._open = false; + } + + public isOpen(): boolean { + return this._open; + } + + public hasValue(): boolean { + for (let option of this.options) { + if (!option.divider && option.value === this.value) { + return true; + } + } + return false; + } + + public displayValue(): string { + for (let option of this.options) { + if (!option.divider && option.value === this.value) { + return option.name; + } + } + return null; + } + + public setValue(value: string): void { + this.value = value; + //this.close(); + this.onValueChange.emit(this.value); + } + +} diff --git a/front-end/studio/src/app/components/dialogs/confirm-delete.component.html b/front-end/studio/src/app/components/dialogs/confirm-delete.component.html new file mode 100644 index 000000000..c82a3d3d4 --- /dev/null +++ b/front-end/studio/src/app/components/dialogs/confirm-delete.component.html @@ -0,0 +1,20 @@ + diff --git a/front-end/studio/src/app/components/dialogs/confirm-delete.component.ts b/front-end/studio/src/app/components/dialogs/confirm-delete.component.ts new file mode 100644 index 000000000..5f256bf54 --- /dev/null +++ b/front-end/studio/src/app/components/dialogs/confirm-delete.component.ts @@ -0,0 +1,77 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Output, EventEmitter, ViewChildren, QueryList} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; + + +@Component({ + moduleId: module.id, + selector: "confirm-delete-dialog", + templateUrl: "confirm-delete.component.html" +}) +export class ConfirmDeleteDialogComponent { + + @Output() onDelete: EventEmitter = new EventEmitter(); + + @ViewChildren("confirmDeleteModal") confirmDeleteModal: QueryList; + + protected _isOpen: boolean = false; + + /** + * Called to open the dialog. + */ + public open(): void { + this._isOpen = true; + this.confirmDeleteModal.changes.subscribe( thing => { + if (this.confirmDeleteModal.first) { + this.confirmDeleteModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + } + + /** + * Called when the user clicks "Yes". + */ + protected delete(): void { + this.onDelete.emit(true); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.confirmDeleteModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + +} diff --git a/front-end/studio/src/app/components/dialogs/copy-url.component.html b/front-end/studio/src/app/components/dialogs/copy-url.component.html new file mode 100644 index 000000000..2230a1895 --- /dev/null +++ b/front-end/studio/src/app/components/dialogs/copy-url.component.html @@ -0,0 +1,19 @@ + diff --git a/front-end/studio/src/app/components/dialogs/copy-url.component.ts b/front-end/studio/src/app/components/dialogs/copy-url.component.ts new file mode 100644 index 000000000..b5aee0b49 --- /dev/null +++ b/front-end/studio/src/app/components/dialogs/copy-url.component.ts @@ -0,0 +1,67 @@ +/** + * @license + * Copyright 2018 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; + + +@Component({ + moduleId: module.id, + selector: "copy-url-dialog", + templateUrl: "copy-url.component.html" +}) +export class CopyUrlDialogComponent { + + @ViewChildren("copyUrlModal") copyUrlModal: QueryList; + + protected _isOpen: boolean = false; + + /** + * Called to open the dialog. + */ + public open(): void { + this._isOpen = true; + this.copyUrlModal.changes.subscribe( thing => { + if (this.copyUrlModal.first) { + this.copyUrlModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.copyUrlModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + +} diff --git a/front-end/studio/src/app/components/dimensions.less b/front-end/studio/src/app/components/dimensions.less new file mode 100644 index 000000000..2eab7c0d9 --- /dev/null +++ b/front-end/studio/src/app/components/dimensions.less @@ -0,0 +1,2 @@ +@navbar-height: 48px; +@navbar-logo-size: 30px; diff --git a/front-end/studio/src/app/components/nav-header.component.css b/front-end/studio/src/app/components/nav-header.component.css new file mode 100644 index 000000000..90c6b590f --- /dev/null +++ b/front-end/studio/src/app/components/nav-header.component.css @@ -0,0 +1,54 @@ + +.navbar { + height: 48px; +} +.navbar .navbar-header { + height: 45px; +} + +.navbar .navbar-brand { + padding: 0; +} +.navbar .navbar-brand .navbar-brand-logo { + line-height: 45px; + font-family: 'Lato', sans-serif; + font-size: 30px; + font-weight: 300; + color: #DCD7D7; + padding-left: 40px; + background-image: url('../../assets/apicurio_icon_darkbkg_32px.png'); + background-repeat: no-repeat; + background-position-y: 6px; +} +.navbar .navbar-brand .navbar-brand-logo:hover { + color: rgb(203, 37, 38); +} + +.navbar a.dropdown-toggle { + height: 45px; + line-height: 34px; + font-size: 13px; + vertical-align: middle; +} +.navbar a.dropdown-toggle .pficon { + position: initial; +} + +.navbar .dropdown-menu a { + cursor: pointer; +} +.navbar .dropdown-menu a.disabled { + cursor: not-allowed; + color: #999; +} + +.product-versions-pf table tr td { + color: white; + font-size: 13px; + padding-right: 10px; +} + +.about-title { + font-size: 32px; + font-weight: 600; +} diff --git a/front-end/studio/src/app/components/nav-header.component.html b/front-end/studio/src/app/components/nav-header.component.html new file mode 100644 index 000000000..9d45e9b27 --- /dev/null +++ b/front-end/studio/src/app/components/nav-header.component.html @@ -0,0 +1,61 @@ + + diff --git a/front-end/studio/src/app/components/nav-header.component.ts b/front-end/studio/src/app/components/nav-header.component.ts new file mode 100644 index 000000000..1369c83db --- /dev/null +++ b/front-end/studio/src/app/components/nav-header.component.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, OnInit} from "@angular/core"; +import {User} from "../models/user.model"; +import {IAuthenticationService} from "../services/auth.service"; +import {Observable} from "rxjs"; + +@Component({ + moduleId: module.id, + selector: "nav-header", + templateUrl: "nav-header.component.html", + styleUrls: [ "nav-header.component.css" ] +}) +export class NavHeaderComponent implements OnInit { + + version: string = "N/A"; + builtOn: Date = new Date(); + projectUrl: string = "http://www.apicur.io/"; + + constructor(protected authService: IAuthenticationService) { + let w: any = window; + if (w["ApicurioStudioInfo"]) { + console.info("[NavHeaderComponent] Found app info: %o", w["ApicurioStudioInfo"]); + this.version = w["ApicurioStudioInfo"].version; + this.builtOn = new Date(w["ApicurioStudioInfo"].builtOn); + this.projectUrl = w["ApicurioStudioInfo"].url; + } else { + console.info("[NavHeaderComponent] App info not found."); + } + } + + ngOnInit(): void { + } + + public user(): Observable { + return this.authService.getAuthenticatedUser(); + } + + public logout(): void { + this.authService.logout(); + } + +} diff --git a/front-end/studio/src/app/components/page-base.component.ts b/front-end/studio/src/app/components/page-base.component.ts new file mode 100644 index 000000000..bbd894be3 --- /dev/null +++ b/front-end/studio/src/app/components/page-base.component.ts @@ -0,0 +1,95 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {OnDestroy, OnInit} from "@angular/core"; +import {ActivatedRoute} from "@angular/router"; +import {Observable} from "rxjs/Observable"; + +export class DataMap { + [key: string]: boolean; +} + +export abstract class AbstractPageComponent implements OnInit, OnDestroy { + + public dataLoaded: DataMap = new DataMap(); + public pageError: any; + + /** + * C'tor. + * @param {ActivatedRoute} route + */ + constructor(protected route: ActivatedRoute) { + } + + /** + * Called when the page is initialized. Triggers the loading of asynchronous + * page data. + */ + public ngOnInit(): void { + // Extract route params and query params and pass them to "loadAsyncPageData" + let combined = Observable.combineLatest(this.route.params, this.route.queryParams, (params, qparams) => ({params, qparams})); + combined.subscribe( ap => { + this.loadAsyncPageData(ap.params, ap.qparams); + }); + } + + /** + * Called when the page is destroyed. + */ + public ngOnDestroy(): void { + } + + /** + * Called to kick off loading the page's async data. Subclasses should + * override to provide page-specific data loading. + * @param pathParams + * @param queryParams + */ + public loadAsyncPageData(pathParams: any, queryParams: any): void { + } + + /** + * Called by a subclass (page) to report an error during loading of data. + * @param error + */ + public error(error: any): void { + console.error(" Error: %o", error); + this.pageError = error; + } + + /** + * Called when page data has been loaded. + * @param key + */ + public loaded(key: string): void { + this.dataLoaded[key] = true; + } + + /** + * Called to determine whether some page data has been loaded yet. + * @param key + * @return {boolean} + */ + public isLoaded(key: string): boolean { + if (this.dataLoaded[key]) { + return true; + } else { + return false; + } + } + +} diff --git a/front-end/studio/src/app/components/page-error.component.css b/front-end/studio/src/app/components/page-error.component.css new file mode 100644 index 000000000..4b01f5de1 --- /dev/null +++ b/front-end/studio/src/app/components/page-error.component.css @@ -0,0 +1,14 @@ +.card-pf-error { + border-top-color: red; +} + +.card-pf-error .card-pf-title .fa { + color: red; +} + +.card-pf-error .card-pf-body .details { +} +.card-pf-error .card-pf-body .details pre { + max-height: 350px; + overflow-y: auto; +} diff --git a/front-end/studio/src/app/components/page-error.component.html b/front-end/studio/src/app/components/page-error.component.html new file mode 100644 index 000000000..cd340601d --- /dev/null +++ b/front-end/studio/src/app/components/page-error.component.html @@ -0,0 +1,121 @@ +
+
+
+
+ + +
+
+

+ + Server Error Encountered +

+
+
+

+ Uh oh! It looks like there was an error either receiving data from the server or + sending data to it. You have a couple options at this point. You can reload the + page or we can shoot you back to the Dashboard. Your choice! +

+
+ Back to Dashboard + +
+
+
+

Server Error Details

+
{{ stackTrace() }}
+
+
+ +
+ + +
+
+

+ + Page Not Found +

+
+
+

+ You attempted to navigate to a page that does not exist! I guess you should either + hit the back button or go back to the Dashboard (button below). +

+ +
+
+ + +
+
+

+ + Data Already Exists +

+
+
+

+ It appears you attempted to add or create something that already exists + in Apicurio Studio. Instead of adding it again, try finding the resource + that is already there and editing it! +

+
+ Back to Dashboard + +
+
+
+ + +
+
+

+ + Connection Error + (Could Not Reach Server) +

+
+
+

+ We couldn't reach the Apicurio Studio server! Perhaps try reloading this + page in a few minutes? Maybe check your internet connection? +

+
+ +
+
+
+ + +
+
+

+ + Unexpected Error on Page + ( + {{ errorMessage() }} + ) +

+
+
+

+ Something unexpected happened and we aren't sure how to deal with it. + You could try reloading the page, or you could just go back to the + Dashboard. See the buttons below... +

+
+ Back to Dashboard + +
+
+
+ +
+
+
\ No newline at end of file diff --git a/front-end/studio/src/app/components/page-error.component.ts b/front-end/studio/src/app/components/page-error.component.ts new file mode 100644 index 000000000..142c3eaa6 --- /dev/null +++ b/front-end/studio/src/app/components/page-error.component.ts @@ -0,0 +1,89 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Input} from "@angular/core"; + + +@Component({ + moduleId: module.id, + selector: "page-error", + templateUrl: "page-error.component.html", + styleUrls: [ "page-error.component.css" ] +}) +export class PageErrorComponent { + + @Input() error: any; + + private eobj: any = null; + private showDetails: boolean = false; + + /** + * Called to reload the browser page. + */ + public reloadPage(): void { + window.location.reload(); + } + + /** + * Called to toggle the error details. + */ + public toggleDetails(): void { + this.showDetails = !this.showDetails; + } + + /** + * Returns the error code. + */ + public errorCode(): number { + return this.error.status; + } + + /** + * Returns the error type. Only valid when the error code is 500. + * @return {()=>string} + */ + public errorMessage(): string { + return this.error.statusText; + } + + /** + * Returns the error stack trace. Only valid when the error code is 500. + */ + public stackTrace(): string { + return this.errorObj().trace; + } + + /** + * Returns the error type. Only valid when the error code is 500. + * @return {()=>string} + */ + public errorType(): string { + return this.errorObj().errorType; + } + + /** + * Returns the parsed error body. + * @return {any} + */ + private errorObj(): any { + if (this.eobj === null) { + this.eobj = this.error.json(); + } + return this.eobj; + } + +} diff --git a/front-end/studio/src/app/components/vertical-nav.component.css b/front-end/studio/src/app/components/vertical-nav.component.css new file mode 100644 index 000000000..79cd95c08 --- /dev/null +++ b/front-end/studio/src/app/components/vertical-nav.component.css @@ -0,0 +1,88 @@ + +#api-vertical-nav { + position: absolute; + left: 0; + width: 120px; + top: 48px; + bottom: 0; + background-color: white; + overflow: hidden; + z-index: 100; +} +#api-vertical-nav .api-nav-item { + color: #666; + border-bottom: 1px solid #ddd; + padding: 15px 10px 15px 10px; + text-align: center; + border-right: 1px solid #ddd; +} +#api-vertical-nav .api-nav-item-filler { + border-right: 1px solid #ddd; + height: 100%; +} +#api-vertical-nav .api-nav-item:hover { + background-color: rgba(0, 0, 55, .05); + cursor: pointer; +} +#api-vertical-nav .api-nav-item.active { + color: #0088ce; +} +#api-vertical-nav .api-nav-item.selected { + border-right: 1px solid white; + color: black; + background-color: white; +} +#api-vertical-nav .api-nav-item > span { + font-size: 22px; + display: inline-block; +} +#api-vertical-nav .api-nav-item > div { + font-size: 14px; +} + +#api-vertical-nav-details { + position: absolute; + left: -160px; + width: 280px; + top: 48px; + bottom: 0; + background-color: white; + border-right: 1px solid #ddd; + z-index: 99; + padding: 15px; + -webkit-transition: left 200ms; + -moz-transition: left 200ms; + -ms-transition: left 200ms; + -o-transition: left 200ms; + transition: left 200ms; +} +#api-vertical-nav-details.out { + left: 120px; +} +#api-vertical-nav-details .api-nav-detail-item h3 { + margin-top: 0; + padding-bottom: 3px; + border-bottom: 1px solid #ddd; +} +#api-vertical-nav-details .api-nav-detail-item p.description { + line-height: 17px; + padding: 3px; +} +#api-vertical-nav-details .api-nav-detail-item h4 { + font-weight: bold; + font-size: 12px; + margin-top: 20px; +} +#api-vertical-nav-details .api-nav-detail-item .action { + padding-left: 8px; +} + +#api-nav-menu-shade { + position: absolute; + left: 120px; + right: 0; + top: 48px; + bottom: 0; + z-index: 80; + background-color: rgba(0, 0, 0, .2); +} diff --git a/front-end/studio/src/app/components/vertical-nav.component.html b/front-end/studio/src/app/components/vertical-nav.component.html new file mode 100644 index 000000000..205bcef97 --- /dev/null +++ b/front-end/studio/src/app/components/vertical-nav.component.html @@ -0,0 +1,68 @@ +
+ +
+
+ +
+
+ +
Dashboard
+
+
+ +
APIs
+
+
+ +
Settings
+
+
+
+
+ +
+ +
+

Dashboard

+

Click below to navigate back to the default Apicurio Studio dashboard.

+ +
+ +
+

APIs

+

Choose from the actions below or jump directly to one of your starred/favorited APIs.

+

API Actions

+ + + + +

Recent APIs

+ +
+ +
+

Settings

+

Select one of the common Settings pages listed below.

+ + +
+
+ +
diff --git a/front-end/studio/src/app/components/vertical-nav.component.ts b/front-end/studio/src/app/components/vertical-nav.component.ts new file mode 100644 index 000000000..c5b963b95 --- /dev/null +++ b/front-end/studio/src/app/components/vertical-nav.component.ts @@ -0,0 +1,103 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, OnInit, Inject} from "@angular/core"; +import {Router, NavigationStart} from "@angular/router"; +import {IApisService} from "../services/apis.service"; + +/** + * Models the sub-menus off the main left-hand vertical nav. + */ +export enum VerticalNavSubMenuType { + None, Dashboard, APIs, Settings +} + + +@Component({ + moduleId: module.id, + selector: "vertical-nav", + templateUrl: "vertical-nav.component.html", + styleUrls: ["vertical-nav.component.css"] +}) +export class VerticalNavComponent implements OnInit { + + public subMenuTypes: any = VerticalNavSubMenuType; + public currentSubMenu: VerticalNavSubMenuType = VerticalNavSubMenuType.None; + public subMenuOut: boolean = false; + + constructor(private router: Router, @Inject(IApisService) private apis: IApisService) { + } + + ngOnInit(): void { + console.log("Subscribing to router events."); + this.router.events.subscribe(event => { + if (event instanceof NavigationStart) { + this.onShadeClick(); + } + }); + } + + /** + * Returns true if the currently active route is the dashboard. + * @returns {boolean} + */ + isDashboardRoute(): boolean { + return this.router.isActive("/", true); + } + + /** + * Returns true if the currently active route is /apis/* + * @returns {boolean} + */ + isAPIsRoute(): boolean { + return this.router.isActive("/apis", false); + } + + /** + * Returns true if the currently active route is /settings/* + * @returns {boolean} + */ + isSettingsRoute(): boolean { + return this.router.isActive("/settings", false); + } + + /** + * Called when the user clicks the vertical menu shade (the grey shaded area behind the submenu div that + * is displayed when a sub-menu is selected). Clicking the shade makes the sub-menu div go away. + */ + onShadeClick(): void { + this.subMenuOut = false; + setTimeout(() => { + this.currentSubMenu = VerticalNavSubMenuType.None; + }, 180); + } + + /** + * Toggles a sub-menu off the main vertical left-hand menu bar. If the sub-menu is + * already selected, it de-selects it. + * @param subMenu the sub-menu to toggle + */ + toggleSubMenu(subMenu: VerticalNavSubMenuType): void { + if (this.subMenuOut && this.currentSubMenu === subMenu) { + this.onShadeClick(); + } else { + this.currentSubMenu = subMenu; + this.subMenuOut = true; + } + } + +} diff --git a/front-end/studio/src/app/directives/autosize.directive.ts b/front-end/studio/src/app/directives/autosize.directive.ts new file mode 100644 index 000000000..ce8b60220 --- /dev/null +++ b/front-end/studio/src/app/directives/autosize.directive.ts @@ -0,0 +1,97 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {AfterContentChecked, AfterViewChecked, Directive, ElementRef, HostListener, Input} from "@angular/core"; + +@Directive({ + selector: "textarea[autosize]" +}) +export class TextAreaAutosize implements AfterContentChecked { + + @HostListener("input", ["$event.target"]) + onInput(textArea: HTMLTextAreaElement): void { + this.adjust(); + } + + constructor(public element: ElementRef) {} + + ngAfterContentChecked(): void { + this.adjust(); + } + + adjust(): void { + this.element.nativeElement.style.overflow = "hidden"; + this.element.nativeElement.style.height = "auto"; + this.element.nativeElement.style.height = this.element.nativeElement.scrollHeight + "px"; + } +} + + +@Directive({ + selector: "div[autoheight]" +}) +export class DivAutoHeight implements AfterViewChecked { + + lastHeight: number = -1; + + @Input() maxHeight: number; + + constructor(public element: ElementRef) {} + + ngAfterViewChecked(): void { + this.adjust(); + } + + adjust(): void { + let height: number = this.element.nativeElement.scrollHeight; + if (this.maxHeight && height > this.maxHeight) { + height = this.maxHeight; + } + if (Math.abs(height - this.lastHeight) > 5) { + this.element.nativeElement.style.height = height + "px"; + this.lastHeight = height; + if (height == this.maxHeight) { + this.element.nativeElement.style.overflowY = "auto"; + } else { + this.element.nativeElement.style.overflowY = "visible"; + } + } + } +} + + +@Directive({ + selector: "input[autosize]" +}) +export class TextBoxAutosize implements AfterContentChecked { + + @HostListener("input", ["$event.target"]) + onInput(textInput: HTMLInputElement): void { + this.adjust(); + } + + constructor(public element: ElementRef) {} + + ngAfterContentChecked(): void { + this.adjust(); + } + + adjust(): void { + this.element.nativeElement.style.width = "auto"; + this.element.nativeElement.style.width = this.element.nativeElement.scrollWidth + "px"; + } +} diff --git a/front-end/studio/src/app/guards/auth.guard.ts b/front-end/studio/src/app/guards/auth.guard.ts new file mode 100644 index 000000000..223516f3f --- /dev/null +++ b/front-end/studio/src/app/guards/auth.guard.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Injectable} from "@angular/core"; +import {CanActivate, Router} from "@angular/router"; +import {IAuthenticationService} from "../services/auth.service"; +import {Subscription} from "rxjs"; + +@Injectable() +export class AuthenticationCanActivateGuard implements CanActivate { + + private isAuthenticated: boolean; + private sub: Subscription; + + constructor(protected authService: IAuthenticationService, private router: Router) { + this.sub = authService.isAuthenticated().subscribe(value => { + this.isAuthenticated = value; + }); + } + + canActivate() { + if (!this.isAuthenticated) { + let path: string = location.pathname; + let query: string = location.search.substring(1); + + sessionStorage.setItem("apicurio.studio.pages.login.redirect-to.path", path); + sessionStorage.setItem("apicurio.studio.pages.login.redirect-to.query", query); + + this.router.navigate(["/login"]); + } + return this.isAuthenticated; + } +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/ack.model.ts b/front-end/studio/src/app/models/ack.model.ts new file mode 100644 index 000000000..3e4188751 --- /dev/null +++ b/front-end/studio/src/app/models/ack.model.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class ApiDesignCommandAck { + + commandId: number; + contentVersion: number; + +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/api-collaborator.ts b/front-end/studio/src/app/models/api-collaborator.ts new file mode 100644 index 000000000..a0e830adb --- /dev/null +++ b/front-end/studio/src/app/models/api-collaborator.ts @@ -0,0 +1,31 @@ +/** + * @license + * Copyright 2018 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +export class ApiCollaborator { + + userId: string; + userName: string; + role: string; + + constructor() { + this.userId = ""; + this.userName = ""; + this.role = ""; + } +} + diff --git a/front-end/studio/src/app/models/api-contributors.ts b/front-end/studio/src/app/models/api-contributors.ts new file mode 100644 index 000000000..e5fb7a1d8 --- /dev/null +++ b/front-end/studio/src/app/models/api-contributors.ts @@ -0,0 +1,37 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +export class ApiContributor { + name: string; + edits: number; + + constructor() { + this.name = ""; + this.edits = 0; + } +} + +export class ApiContributors { + public totalEdits: number; + public contributors: ApiContributor[]; + + constructor() { + this.totalEdits = 0; + this.contributors = []; + } +} diff --git a/front-end/studio/src/app/models/api-design-change.ts b/front-end/studio/src/app/models/api-design-change.ts new file mode 100644 index 000000000..9e41752b2 --- /dev/null +++ b/front-end/studio/src/app/models/api-design-change.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +export class ApiDesignChange { + + version: number; + type: string; + data: string; + by: string; + on: Date; + +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/api.model.ts b/front-end/studio/src/app/models/api.model.ts new file mode 100644 index 000000000..1d4542ff0 --- /dev/null +++ b/front-end/studio/src/app/models/api.model.ts @@ -0,0 +1,85 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +export class Api { + + id: string; + name: string; + description: string; + createdOn: Date; + createdBy: string; + tags: string[]; + + constructor() { + this.id = ""; + this.name = ""; + this.description = ""; + this.createdOn = new Date(); + this.createdBy = ""; + this.tags = null; + } + +} + + +export class ApiDefinition extends Api { + + spec: any; + + constructor() { + super(); + this.spec = {}; + } + + public static fromApi(api: Api): ApiDefinition { + let apiDef: ApiDefinition = new ApiDefinition(); + apiDef.id = api.id; + apiDef.name = api.name; + apiDef.description = api.description; + apiDef.createdOn = api.createdOn; + apiDef.createdBy = api.createdBy; + + return apiDef; + } + +} + + +export class EditableApiDefinition extends ApiDefinition { + + editingSessionUuid: string; + contentVersion: number; + + constructor() { + super(); + this.editingSessionUuid = null; + this.contentVersion = 0; + } + + public static fromApi(api: Api): EditableApiDefinition { + let apiDef: EditableApiDefinition = new EditableApiDefinition(); + apiDef.id = api.id; + apiDef.name = api.name; + apiDef.description = api.description; + apiDef.createdOn = api.createdOn; + apiDef.createdBy = api.createdBy; + + return apiDef; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/bitbucket-repository.ts b/front-end/studio/src/app/models/bitbucket-repository.ts new file mode 100644 index 000000000..f51bac976 --- /dev/null +++ b/front-end/studio/src/app/models/bitbucket-repository.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class BitbucketRepository { + + name: string; + slug: string; + uuid: boolean; + + constructor() { + this.name = null; + this.slug = null; + this.uuid = false; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/bitbucket-team.ts b/front-end/studio/src/app/models/bitbucket-team.ts new file mode 100644 index 000000000..4fc6f66bc --- /dev/null +++ b/front-end/studio/src/app/models/bitbucket-team.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class BitbucketTeam { + + displayName: string; + username: string; + uuid: boolean; + + constructor() { + this.displayName = null; + this.username = null; + this.uuid = false; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/complete-linked-account.ts b/front-end/studio/src/app/models/complete-linked-account.ts new file mode 100644 index 000000000..7b4081b32 --- /dev/null +++ b/front-end/studio/src/app/models/complete-linked-account.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class CompleteLinkedAccount { + + nonce: string; + + constructor() { + this.nonce = null; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/create-linked-account.ts b/front-end/studio/src/app/models/create-linked-account.ts new file mode 100644 index 000000000..b947e386c --- /dev/null +++ b/front-end/studio/src/app/models/create-linked-account.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class CreateLinkedAccount { + + type: string; + redirectUrl: string; + + constructor() { + this.type = null; + this.redirectUrl = null; + } +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/editor-user.model.ts b/front-end/studio/src/app/models/editor-user.model.ts new file mode 100644 index 000000000..6309dbd70 --- /dev/null +++ b/front-end/studio/src/app/models/editor-user.model.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Represents a single user currently connected to the editor. The local user + * is not represented by one of these - only remote collaborators. + */ +export class ApiEditorUser { + public userId: string; + public userName: string; + public attributes: any = {}; +} + diff --git a/front-end/studio/src/app/models/github-organization.ts b/front-end/studio/src/app/models/github-organization.ts new file mode 100644 index 000000000..2b103f429 --- /dev/null +++ b/front-end/studio/src/app/models/github-organization.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class GitHubOrganization { + + id: string; + userOrg: boolean; + + constructor() { + this.id = null; + this.userOrg = false; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/github-repository.ts b/front-end/studio/src/app/models/github-repository.ts new file mode 100644 index 000000000..87aa7a88b --- /dev/null +++ b/front-end/studio/src/app/models/github-repository.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class GitHubRepository { + + name: string; + priv: boolean; + + constructor() { + this.name = null; + this.priv = false; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/gitlab-group.ts b/front-end/studio/src/app/models/gitlab-group.ts new file mode 100644 index 000000000..bd521959f --- /dev/null +++ b/front-end/studio/src/app/models/gitlab-group.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class GitLabGroup { + + id: number; + name: string; + path: string; + + constructor() { + this.id = null; + this.name = null; + this.path = null; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/gitlab-project.ts b/front-end/studio/src/app/models/gitlab-project.ts new file mode 100644 index 000000000..087fd7fa5 --- /dev/null +++ b/front-end/studio/src/app/models/gitlab-project.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class GitLabProject { + + id: number; + name: string; + path: string; + + constructor() { + this.id = null; + this.name = null; + this.path = null; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/import-api.model.ts b/front-end/studio/src/app/models/import-api.model.ts new file mode 100644 index 000000000..64f41ca1d --- /dev/null +++ b/front-end/studio/src/app/models/import-api.model.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class ImportApi { + + url: string; + data: string; + + constructor() { + this.url = null; + this.data = null; + } + +} diff --git a/front-end/studio/src/app/models/initiated-linked-account.ts b/front-end/studio/src/app/models/initiated-linked-account.ts new file mode 100644 index 000000000..642cc0298 --- /dev/null +++ b/front-end/studio/src/app/models/initiated-linked-account.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +export class InitiatedLinkedAccount { + + authUrl: string; + nonce: string; + + constructor() { + this.authUrl = null; + this.nonce = null; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/invitation.ts b/front-end/studio/src/app/models/invitation.ts new file mode 100644 index 000000000..f6cf3dbb3 --- /dev/null +++ b/front-end/studio/src/app/models/invitation.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2018 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +export class Invitation { + + inviteId: string; + designId: string; + createdBy: string; + createdOn: Date; + status: string; + modifiedBy: string; + modifiedOn: Date; + role: string; + subject: string; + + constructor() { + this.inviteId = ""; + this.designId = ""; + this.createdBy = ""; + this.createdOn = new Date(); + this.status = ""; + this.role = ""; + this.subject = ""; + } +} + diff --git a/front-end/studio/src/app/models/linked-account.ts b/front-end/studio/src/app/models/linked-account.ts new file mode 100644 index 000000000..046f8222c --- /dev/null +++ b/front-end/studio/src/app/models/linked-account.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class LinkedAccount { + + type: string; + linkedOn: Date; + usedOn: Date; + + constructor() { + this.type = null; + this.linkedOn = null; + this.usedOn = null; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/models/new-api.model.ts b/front-end/studio/src/app/models/new-api.model.ts new file mode 100644 index 000000000..b2358868c --- /dev/null +++ b/front-end/studio/src/app/models/new-api.model.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class NewApi { + + specVersion: string; + name: string; + description: string; + + constructor() { + this.specVersion = null; + this.name = ""; + this.description = ""; + } + +} diff --git a/front-end/studio/src/app/models/user.model.ts b/front-end/studio/src/app/models/user.model.ts new file mode 100644 index 000000000..cc6671acc --- /dev/null +++ b/front-end/studio/src/app/models/user.model.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +export class User { + login: string; + id: number; + name: string; + email: string; + avatar: string; + + constructor() { + this.login = ""; + this.name = ""; + this.email = ""; + } + +} diff --git a/front-end/studio/src/app/pages/404.page.html b/front-end/studio/src/app/pages/404.page.html new file mode 100644 index 000000000..42405df94 --- /dev/null +++ b/front-end/studio/src/app/pages/404.page.html @@ -0,0 +1,5 @@ +
+ +

Page Not Found

+ +
\ No newline at end of file diff --git a/front-end/studio/src/app/pages/404.page.ts b/front-end/studio/src/app/pages/404.page.ts new file mode 100644 index 000000000..5d433a5c3 --- /dev/null +++ b/front-end/studio/src/app/pages/404.page.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component} from "@angular/core"; +import {AbstractPageComponent} from "../components/page-base.component"; +import {ActivatedRoute} from "@angular/router"; + +/** + * The Not Found (404) Page component - shown when navigating to a route that does not exist. + */ +@Component({ + moduleId: module.id, + selector: "not-found-page", + templateUrl: "404.page.html" +}) +export class NotFoundPageComponent extends AbstractPageComponent { + + /** + * C'tor. + * @param {ActivatedRoute} route + */ + constructor(route: ActivatedRoute) { + super(route); + } + + /** + * @see AbstractPageComponent.loadAsyncPageData + */ + public loadAsyncPageData(): void { + } + +} diff --git a/front-end/studio/src/app/pages/apis/_components/apis-cards.component.css b/front-end/studio/src/app/pages/apis/_components/apis-cards.component.css new file mode 100644 index 000000000..262f1d612 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/_components/apis-cards.component.css @@ -0,0 +1,65 @@ +.container-cards-pf { + padding: 0; + margin-top: 0; +} + +.row-cards-pf { + padding: 0; +} + + +.api-card-icon { + border: 2px solid #39a5dc; + border-radius: 50%; + padding: 5px; + margin-right: 10px; + width: 32px; +} + +.api-card .api-card-title { + font-size: 16px; + font-weight: bold; +} + +.api-card { + -webkit-transition: background-color 300ms; + -moz-transition: background-color 300ms; + -ms-transition: background-color 300ms; + -o-transition: background-color 300ms; + transition: background-color 300ms; + height: 220px; +} +.api-card:hover { + background-color: rgb(237, 237, 237); +} + +.api-card.active { + background-color: rgb(221, 234, 255); +} + +.api-description { + font-size: 13px; + overflow-y: auto; +} + +.api-card .api-tags { + margin-bottom: 8px; +} +.api-card .api-tags .api-tags-label { + font-weight: bold; + margin-right: 5px; +} +.api-card .api-tags .api-tag { + margin-right: 5px; + border: 1px solid #ccc; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + padding: 2px 4px; +} +.api-card .api-tags .api-tag:hover { + cursor: pointer; + background-color: #0088ce; + border-color: #00659c; + color: white; +} \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/_components/apis-cards.component.html b/front-end/studio/src/app/pages/apis/_components/apis-cards.component.html new file mode 100644 index 000000000..1675e4649 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/_components/apis-cards.component.html @@ -0,0 +1,22 @@ +
+
+ +
+
+

+ + {{ api.name }} +

+
+
+
+ Tags: + {{ tag }} +
+

{{api.description}}

+
+
+
+ +
+
\ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/_components/apis-cards.component.ts b/front-end/studio/src/app/pages/apis/_components/apis-cards.component.ts new file mode 100644 index 000000000..069d70622 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/_components/apis-cards.component.ts @@ -0,0 +1,59 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Output, Input} from "@angular/core"; +import {Api} from "../../../models/api.model"; + + +@Component({ + moduleId: module.id, + selector: "apis-cards", + templateUrl: "apis-cards.component.html", + styleUrls: ["apis-cards.component.css"] +}) +export class ApisCardsComponent { + + @Input() apis: Api[]; + @Input() selectedApis: Api[]; + @Output() onApiSelected: EventEmitter = new EventEmitter(); + @Output() onApiDeselected: EventEmitter = new EventEmitter(); + @Output() onTagSelected: EventEmitter = new EventEmitter(); + + /** + * Constructor. + */ + constructor() {} + + public toggleApiSelected(api: Api): void { + if (this.isSelected(api)) { + this.onApiDeselected.emit(api); + } else { + this.onApiSelected.emit(api); + } + } + + public isSelected(api: Api): boolean { + return this.selectedApis.indexOf(api) != -1; + } + + public selectTag(tag: string, event: MouseEvent): void { + event.stopPropagation(); + event.preventDefault(); + this.onTagSelected.emit(tag); + } + +} diff --git a/front-end/studio/src/app/pages/apis/_components/apis-list.component.css b/front-end/studio/src/app/pages/apis/_components/apis-list.component.css new file mode 100644 index 000000000..d237e5fac --- /dev/null +++ b/front-end/studio/src/app/pages/apis/_components/apis-list.component.css @@ -0,0 +1,36 @@ + +.list-group-item { + -webkit-transition: background-color 300ms; + -moz-transition: background-color 300ms; + -ms-transition: background-color 300ms; + -o-transition: background-color 300ms; + transition: background-color 300ms; +} +.list-group-item, .list-group-item:first-child { + margin-bottom: 5px; + border-top: 1px solid rgb(57, 165, 220); +} +.list-group-item.active { + background-color: #def3ff; +} + +.list-group-item .api-tags { +} +.list-group-item .api-tags .api-tags-label { + font-weight: bold; + margin-right: 5px; +} +.list-group-item .api-tags .api-tag { + margin-right: 5px; + border: 1px solid #ccc; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + padding: 2px 4px; +} +.list-group-item .api-tags .api-tag:hover { + cursor: pointer; + background-color: #0088ce; + border-color: #00659c; + color: white; +} \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/_components/apis-list.component.html b/front-end/studio/src/app/pages/apis/_components/apis-list.component.html new file mode 100644 index 000000000..3187300e0 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/_components/apis-list.component.html @@ -0,0 +1,23 @@ +
+
+
+
+ +
+
+
+ +
{{ api.description }}
+
+
+
+ Tags: + {{ tag }} +
+
+
+
+
+
\ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/_components/apis-list.component.ts b/front-end/studio/src/app/pages/apis/_components/apis-list.component.ts new file mode 100644 index 000000000..105e648d8 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/_components/apis-list.component.ts @@ -0,0 +1,59 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Output, Input} from "@angular/core"; +import {Api} from "../../../models/api.model"; + + +@Component({ + moduleId: module.id, + selector: "apis-list", + templateUrl: "apis-list.component.html", + styleUrls: ["apis-list.component.css"] +}) +export class ApisListComponent { + + @Input() apis: Api[]; + @Input() selectedApis: Api[]; + @Output() onApiSelected: EventEmitter = new EventEmitter(); + @Output() onApiDeselected: EventEmitter = new EventEmitter(); + @Output() onTagSelected: EventEmitter = new EventEmitter(); + + /** + * Constructor. + */ + constructor() {} + + public toggleApiSelected(api: Api): void { + if (this.isSelected(api)) { + this.onApiDeselected.emit(api); + } else { + this.onApiSelected.emit(api); + } + } + + public isSelected(api: Api): boolean { + return this.selectedApis.indexOf(api) != -1; + } + + public selectTag(tag: string, event: MouseEvent): void { + event.stopPropagation(); + event.preventDefault(); + this.onTagSelected.emit(tag); + } + +} diff --git a/front-end/studio/src/app/pages/apis/apis.page.css b/front-end/studio/src/app/pages/apis/apis.page.css new file mode 100644 index 000000000..4a32ff5e5 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/apis.page.css @@ -0,0 +1,57 @@ +.toolbar-pf { + margin-top: 5px; +} + +.toolbar-pf form { + margin-bottom: 0; +} + +.toolbar-pf-results { + margin-top: 0; +} + +.api-list-items { + margin-top: 10px +} + +.api-list-apis .toolbar-pf { + background-color: transparent; +} + +.api-list-apis .toolbar-pf .toolbar-pf-results { + background-color: white; +} + +a.clear-filters { + cursor: pointer; +} + +.toolbar-pf-view-selector { + float: right; +} + +.toolbar-pf-view-selector li a { + cursor: pointer; + color: #333; +} + +.toolbar-pf-view-selector li a:hover { + color: #0088ce; +} + +.api-list-items .empty-state { + text-align: center; +} + +.blank-slate-pf { + background-color: white; + width: 50%; + min-width: 500px; + margin-left: auto; + margin-right: auto; + margin-top: 15px; +} + +.api-list-items .none-matched-state .alert { + background-color: white; +} diff --git a/front-end/studio/src/app/pages/apis/apis.page.html b/front-end/studio/src/app/pages/apis/apis.page.html new file mode 100644 index 000000000..59f226ca3 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/apis.page.html @@ -0,0 +1,118 @@ +
+ +
  • +
  • +
    +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + +
    + +
    +
      +
    • +
    • +
    +
    +
    +
    +
    +
    {{ filteredApis.length }} APIs found (out of {{ allApis.length }} total)
    +
    Deleting {{ numApisToDelete }} APIs, please wait...
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    No APIs Found

    +

    + It seems you have no APIs in the Studio yet. This is probably the first time you've used it and haven't + created or improted any APIs yet! +

    +

    + Click below to either create a new API or import an existing API to the Studio. +

    +
    +
    + Create New API + + +
    +
    +
    +
    +
    +
    + + No APIs matched the filter! Try changing your filter criteria - there is definitely at least one API you can work on... +
    +
    + + +
    +
    +
    +
    +
    +

    +

    Loading API Designs...

    +

    +
    +
    +
    +
    +
    + + +
    + + +
    +
    +
    + +

    Do you really want to delete the selected API?

    +

    Do you really want to delete the {{ selectedApis.length }} selected APIs?

    + +
    + + NOTE: + This will permanently delete the API definition from Apicurio Studio. This operation cannot be undone. +
    +
    diff --git a/front-end/studio/src/app/pages/apis/apis.page.ts b/front-end/studio/src/app/pages/apis/apis.page.ts new file mode 100644 index 000000000..c1fbdc8c3 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/apis.page.ts @@ -0,0 +1,215 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, OnInit, Inject, OnDestroy} from "@angular/core"; +import {ActivatedRoute, Router} from "@angular/router"; + +import {IApisService} from "../../services/apis.service"; +import {Api} from "../../models/api.model"; +import {ArrayUtils} from "../../util/common"; +import {AbstractPageComponent} from "../../components/page-base.component"; + + +const API_FILTERS_KEY = "apicurio.studio.pages.apis.filters"; + + +class Filters { + nameFilter: string; + sortDirection: string; + layout: string; + + constructor(params?: any) { + this.reset(); + if (params) { + for (let key in params) { + let value: string = params[key]; + this[key] = value; + } + } + } + + public accepts(api: Api): boolean { + let name: string = api.name.toLocaleLowerCase(); + let namef: string = this.nameFilter.toLocaleLowerCase(); + if (name.indexOf(namef) >= 0) { + return true; + } + if (api.tags && api.tags.length > 0) { + return api.tags.map(tag => { + return tag.toLocaleLowerCase() == namef; + }).reduce( (v1, v2) => { + return v1 || v2; + }); + } + return false; + } + + public reset(): void { + this.nameFilter = ""; + this.sortDirection = "ASC"; + this.layout = "card"; + } +} + + +@Component({ + moduleId: module.id, + selector: "apis-page", + templateUrl: "apis.page.html", + styleUrls: ["apis.page.css"] +}) +export class ApisPageComponent extends AbstractPageComponent implements OnDestroy { + + public allApis: Api[] = []; + public filteredApis: Api[]; + public selectedApis: Api[]; + public filters: Filters = new Filters(); + + public numApisToDelete: number = 0; + + /** + * C'tor. + * @param {IApisService} apis + * @param {ActivatedRoute} route + */ + constructor(@Inject(IApisService) private apis: IApisService, route: ActivatedRoute) { + super(route); + this.filteredApis = []; + this.selectedApis = []; + + let fsaved: string = sessionStorage.getItem(API_FILTERS_KEY); + if (fsaved) { + this.filters = new Filters(JSON.parse(fsaved)); + } + } + + /** + * Called to asynchronously load data needed by the page. + */ + public loadAsyncPageData(): void { + console.log("[ApisPageComponent] loadAsyncPageData") + this.apis.getApis().then( apis => { + console.info("APIS: %O", apis); + this.allApis = apis; + this.filterApis(); + this.loaded("apis"); + }).catch( error => { + console.error("[ApisPageComponent] Error fetching API list."); + this.error(error); + }); + } + + ngOnDestroy(): void { + sessionStorage.setItem(API_FILTERS_KEY, JSON.stringify(this.filters)); + } + + /** + * Filters and sorts the list of apis based on the user's + */ + private filterApis(): Api[] { + // Clear the array first. + this.filteredApis.splice(0, this.filteredApis.length); + for (let api of this.allApis) { + if (this.filters.accepts(api)) { + this.filteredApis.push(api); + } + } + this.filteredApis.sort( (a1:Api, a2:Api) => { + let rval: number = a1.name.localeCompare(a2.name); + if (this.filters.sortDirection === "DESC") { + rval *= -1; + } + return rval; + }); + + this.selectedApis = ArrayUtils.intersect(this.selectedApis, this.filteredApis); + + return this.filteredApis; + } + + public isFiltered(): boolean { + return this.allApis.length !== this.filteredApis.length; + } + + public toggleSortDirection(): void { + if (this.filters.sortDirection === "ASC") { + this.filters.sortDirection = "DESC"; + } else { + this.filters.sortDirection = "ASC"; + } + this.filterApis(); + } + + public clearFilters(): void { + this.filters.nameFilter = ""; + this.filterApis(); + } + + public onSelected(api: Api): void { + console.info("[ApisPageComponent] Caught the onApiSelected event! Data: %o", api); + this.selectedApis.push(api); + } + + public onDeselected(api: Api): void { + console.info("[ApisPageComponent] Caught the onApiDeselected event! Data: %o", api); + this.selectedApis.splice(this.selectedApis.indexOf(api), 1); + } + + public onTagSelected(tag: string): void { + this.filters.nameFilter = tag; + this.filterApis(); + } + + /** + * Called to delete all selected APIs. + */ + public deleteApis(): void { + // TODO deleting the APIs is done asynchronously - we need some sort of visual status (spinner) to watch progress, and then close the dialog when it's done + + // Note: we can only delete selected items that we can see in the UI. + let itemsToDelete: Api[] = ArrayUtils.intersect(this.selectedApis, this.filteredApis); + console.log("[ApisPageComponent] Deleting %s selected APIs.", itemsToDelete.length); + this.numApisToDelete = itemsToDelete.length; + for (let api of itemsToDelete) { + this.apis.deleteApi(api).then( () => { + this.removeApiFromList(api); + this.filterApis(); + this.numApisToDelete--; + }).catch( error => { + console.error("[ApisPageComponent] Error deleting an API with ID %s", api.id); + this.error(error); + }); + } + this.selectedApis = []; + } + + public onListLayout(): void { + this.filters.layout = "list"; + } + + public onCardLayout(): void { + this.filters.layout = "card"; + } + + public onReset(): void { + this.filters.reset(); + } + + private removeApiFromList(api: Api) { + this.allApis.splice(this.allApis.indexOf(api), 1); + } +} diff --git a/front-end/studio/src/app/pages/apis/create/_components/create-form.component.css b/front-end/studio/src/app/pages/apis/create/_components/create-form.component.css new file mode 100644 index 000000000..331bd6fc4 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/create/_components/create-form.component.css @@ -0,0 +1,60 @@ +select { + width: auto; +} + +.createapi-form-panel.dragging { + border: 1px dashed #39a5dc; + background-color: #eef; +} + +.createapi-form-panel { + padding-top: 25px; +} + +span.disabled { + color: #999; +} + +.dropdown ul { + max-height: 250px; + overflow: auto; +} + +div.spinner { + display: inline-block; + margin-right: 5px; +} + +.platform-toggle { + display: inline-block; + text-align: center; + cursor: pointer; + border: 1px solid transparent; + padding: 5px; + margin-right: 8px; + border-radius: 3px; +} +.platform-toggle:hover { + border: 1px solid #bee1f4; + background-color: #def3ff; +} +.platform-toggle.selected { + background-color: #0088ce; + color: white; + cursor: default; + border: 1px solid transparent; +} +.platform-toggle.disabled { + opacity: 0.6; + cursor: not-allowed; +} +.platform-toggle span.fa { + font-size: 32px; +} +.platform-toggle span.lbl { + font-size: 14px; +} + +.account-link-warning { + margin-top: 10px; +} \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/create/_components/create-form.component.html b/front-end/studio/src/app/pages/apis/create/_components/create-form.component.html new file mode 100644 index 000000000..4c8e74d0d --- /dev/null +++ b/front-end/studio/src/app/pages/apis/create/_components/create-form.component.html @@ -0,0 +1,40 @@ +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    diff --git a/front-end/studio/src/app/pages/apis/create/_components/create-form.component.ts b/front-end/studio/src/app/pages/apis/create/_components/create-form.component.ts new file mode 100644 index 000000000..7d13719d5 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/create/_components/create-form.component.ts @@ -0,0 +1,93 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Inject, Output} from "@angular/core"; +import {IApisService} from "../../../../services/apis.service"; +import {User} from "../../../../models/user.model"; +import {IAuthenticationService} from "../../../../services/auth.service"; +import {NewApi} from "../../../../models/new-api.model"; +import {ILinkedAccountsService} from "../../../../services/accounts.service"; +import {DropDownOption} from '../../../../components/common/drop-down.component'; + + +@Component({ + moduleId: module.id, + selector: "createapi-form", + templateUrl: "create-form.component.html", + styleUrls: ["create-form.component.css"] +}) +export class CreateApiFormComponent { + + @Output() onCreateApi = new EventEmitter(); + + model: any = { + type: "3.0.1", + name: null, + description: null + }; + creatingApi: boolean = false; + error: string; + + private _user: User; + + public ngOnInit(): void { + } + + /** + * Constructor. + * @param {IApisService} apisService + * @param {IAuthenticationService} authService + * @param {ILinkedAccountsService} accountsService + */ + constructor(@Inject(IApisService) private apisService: IApisService, + @Inject(IAuthenticationService) private authService: IAuthenticationService, + @Inject(ILinkedAccountsService) private accountsService: ILinkedAccountsService) + { + this.creatingApi = false; + + authService.getAuthenticatedUser().subscribe( user => { + this._user = user; + }); + } + + public typeOptions(): DropDownOption[] { + return [ + { name: "Open API 2.0 (Swagger)", value: "2.0"}, + { name: "Open API 3.0.1", value: "3.0.1"} + ]; + } + + public changeType(value: string): void { + this.model.type = value; + } + + + /** + * Called when the user clicks the "Create API" submit button on the form. + */ + public createApi(): void { + let api: NewApi = new NewApi(); + api.specVersion = this.model.type; + api.name = this.model.name; + api.description = this.model.description; + + console.info("[CreateApiFormComponent] Firing 'create-api' event: %o", api); + + this.creatingApi = true; + this.onCreateApi.emit(api); + } +} diff --git a/front-end/studio/src/app/pages/apis/create/create.page.css b/front-end/studio/src/app/pages/apis/create/create.page.css new file mode 100644 index 000000000..a72f9c8e2 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/create/create.page.css @@ -0,0 +1,12 @@ +.createapi-form { + padding: 15px +} + +.createapi-form .form-instructions { + font-size: 15px; + padding-top: 10px +} + +.createapi-form .form-instructions ol { + padding-left: 20px; +} diff --git a/front-end/studio/src/app/pages/apis/create/create.page.html b/front-end/studio/src/app/pages/apis/create/create.page.html new file mode 100644 index 000000000..312b2af1f --- /dev/null +++ b/front-end/studio/src/app/pages/apis/create/create.page.html @@ -0,0 +1,28 @@ +
    + +
  • +
  • +
  • +
    +
    + +
    +
    +
    +
    + + What does this page do? +

    + Here you can create a brand new API by telling us the name (and description) of the new API. +

    +
    +
      +
    1. Choose a name (Required)
    2. +
    3. Set the description
    4. +
    +
    +
    + +
    +
    +
    diff --git a/front-end/studio/src/app/pages/apis/create/create.page.ts b/front-end/studio/src/app/pages/apis/create/create.page.ts new file mode 100644 index 000000000..0f35fc662 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/create/create.page.ts @@ -0,0 +1,64 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, OnInit, Inject, ViewChild} from "@angular/core"; +import {ActivatedRoute, Router} from "@angular/router"; + +import {IApisService} from "../../../services/apis.service"; +import {Api} from "../../../models/api.model"; +import {CreateApiFormComponent} from "./_components/create-form.component"; +import {NewApi} from "../../../models/new-api.model"; +import {AbstractPageComponent} from "../../../components/page-base.component"; + +@Component({ + moduleId: module.id, + selector: "createapi-page", + templateUrl: "create.page.html", + styleUrls: ["create.page.css"] +}) +export class CreateApiPageComponent extends AbstractPageComponent { + + @ViewChild("createApiForm") form: CreateApiFormComponent; + + /** + * Constructor. + * @param {Router} router + * @param {ActivatedRoute} route + * @param {IApisService} apis + */ + constructor(private router: Router, route: ActivatedRoute, @Inject(IApisService) private apis: IApisService) { + super(route); + } + + /** + * Called when the Create API form (component) emits a "create-api" event. This is bound to + * from the createapi.page.html template. + * @param {NewApi} newApi + */ + public onCreateApi(newApi: NewApi) { + console.log("[CreateApiPageComponent] onCreateApi(): " + JSON.stringify(newApi)) + this.apis.createApi(newApi).then(api => { + let link: string[] = [ "/apis", api.id ]; + console.info("[CreateApiPageComponent] Navigating to: %o", link); + this.router.navigate(link); + }).catch( error => { + console.error("[CreateApiPageComponent] Error creating an API"); + this.error(error); + }) + } + +} diff --git a/front-end/studio/src/app/pages/apis/import/_components/import-form.component.css b/front-end/studio/src/app/pages/apis/import/_components/import-form.component.css new file mode 100644 index 000000000..055def26b --- /dev/null +++ b/front-end/studio/src/app/pages/apis/import/_components/import-form.component.css @@ -0,0 +1,12 @@ +select { + width: auto; +} + +.importapi-form-panel.dragging { + border: 1px dashed #39a5dc; + background-color: #eef; +} + +.importapi-form-panel { + padding-top: 25px; +} diff --git a/front-end/studio/src/app/pages/apis/import/_components/import-form.component.html b/front-end/studio/src/app/pages/apis/import/_components/import-form.component.html new file mode 100644 index 000000000..c754868e1 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/import/_components/import-form.component.html @@ -0,0 +1,45 @@ +
    +
    +
    +
     
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    diff --git a/front-end/studio/src/app/pages/apis/import/_components/import-form.component.ts b/front-end/studio/src/app/pages/apis/import/_components/import-form.component.ts new file mode 100644 index 000000000..62f1f41cd --- /dev/null +++ b/front-end/studio/src/app/pages/apis/import/_components/import-form.component.ts @@ -0,0 +1,139 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Inject, Output} from "@angular/core"; +import {IApisService} from "../../../../services/apis.service"; +import {ImportApi} from "../../../../models/import-api.model"; +import {DropDownOption} from '../../../../components/common/drop-down.component'; + + +@Component({ + moduleId: module.id, + selector: "importapi-form", + templateUrl: "import-form.component.html", + styleUrls: ["import-form.component.css"] +}) +export class ImportApiFormComponent { + + @Output() onImportApi = new EventEmitter(); + + importType: string; + textMode: string = "yaml"; + model: any; + importingApi: boolean; + dragging: boolean; + error: string; + + /** + * Constructor. + * @param apis + */ + constructor(@Inject(IApisService) private apis: IApisService) { + this.importType = "from-url"; + this.model = { + url: "", + data: "" + }; + this.importingApi = false; + this.dragging = false; + this.error = null; + } + + /** + * Called when the user clicks the "Import API" submit button on the form. + */ + public importApi(): void { + this.error = null; + + let importApi: ImportApi = new ImportApi(); + if (this.model.url) { + importApi.url = this.model.url; + } else if (this.model.data) { + importApi.data = btoa(this.model.data); + } + + this.importingApi = true; + this.onImportApi.emit(importApi); + } + + public onDragOver(event: DragEvent): void { + if (!this.dragging) { + this.dragging = true; + } + event.preventDefault(); + } + + public onDrop(event: DragEvent): void { + console.info("DROP DATA: %o", event.dataTransfer); + this.dragging = false; + event.preventDefault(); + + let files: FileList = event.dataTransfer.files; + let dropData: string = event.dataTransfer.getData("text"); + if (files && files.length == 1) { + let isJson: boolean = files[0].type === "application/json"; + let isYaml: boolean = files[0].name.endsWith(".yaml") || files[0].name.endsWith(".yml"); + if (isJson || isYaml) { + let reader: FileReader = new FileReader(); + reader.onload = (fileLoadedEvent) => { + let content: string = fileLoadedEvent.target["result"]; + this.model.data = content; + this.model.url = null; + this.importType = "from-text"; + this.textMode = "json"; + if (isYaml) { + this.textMode = "yaml"; + } + }; + reader.readAsText(files[0]); + } + } else if (dropData && dropData.startsWith("http")) { + this.model.url = dropData; + this.model.data = null; + this.importType = "from-url"; + if (this.model.url.indexOf("github.com") > 0 || this.model.url.indexOf("gitlab.com") || this.model.url.indexOf("bitbucket.org") > 0) { + this.importType = "from-scs"; + } + } + } + + public onDragEnd(event: DragEvent): void { + this.dragging = false; + } + + public importTypeOptions(): DropDownOption[] { + return [ + { name: "Import From URL", value: "from-url"}, + { name: "Import From Source Control", value: "from-scs"}, + { name: "Import From File/Clipboard", value: "from-text"} + ]; + } + + public changeImportType(value: string): void { + this.importType = value; + this.model.url = null; + this.model.data = null; + } + + public urlPlaceholder(): string { + if (this.importType === "from-url") { + return "https://gist.githubusercontent.com/Tim/94445d/raw/5dba00/oai-import.json"; + } else { + return "https://github.com/ORG/REPO/blob/master/path/to/open-api-doc.json"; + } + } +} diff --git a/front-end/studio/src/app/pages/apis/import/import.page.css b/front-end/studio/src/app/pages/apis/import/import.page.css new file mode 100644 index 000000000..f29c2618f --- /dev/null +++ b/front-end/studio/src/app/pages/apis/import/import.page.css @@ -0,0 +1,12 @@ +.importapi-form { + padding: 15px +} + +.importapi-form .form-instructions { + font-size: 15px; + padding-top: 10px +} + +.importapi-form .form-instructions ol { + padding-left: 20px; +} diff --git a/front-end/studio/src/app/pages/apis/import/import.page.html b/front-end/studio/src/app/pages/apis/import/import.page.html new file mode 100644 index 000000000..abc49b78c --- /dev/null +++ b/front-end/studio/src/app/pages/apis/import/import.page.html @@ -0,0 +1,33 @@ +
    + +
  • +
  • +
  • +
    +
    + +
    +
    +
    +
    + + What is this page? +

    + Hey there! Here you can import an existing API to the Studio by either giving us the OpenAPI document or + telling us where we can get it. +

    +

    + Choose from the following options: +

    +
      +
    • Import from Source Control URL
    • +
    • Import raw content from URL
    • +
    • Copy/Paste raw content directly
    • +
    +
    +
    +
    + +
    +
    +
    diff --git a/front-end/studio/src/app/pages/apis/import/import.page.ts b/front-end/studio/src/app/pages/apis/import/import.page.ts new file mode 100644 index 000000000..3a5678a42 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/import/import.page.ts @@ -0,0 +1,60 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Inject} from "@angular/core"; +import {ActivatedRoute, Router} from "@angular/router"; + +import {IApisService} from "../../../services/apis.service"; +import {AbstractPageComponent} from "../../../components/page-base.component"; +import {ImportApi} from "../../../models/import-api.model"; + +@Component({ + moduleId: module.id, + selector: "importapi-page", + templateUrl: "import.page.html", + styleUrls: ["import.page.css"] +}) +export class ImportApiPageComponent extends AbstractPageComponent { + + /** + * Constructor. + * @param {Router} router + * @param {ActivatedRoute} route + * @param {IApisService} apis + */ + constructor(private router: Router, route: ActivatedRoute, @Inject(IApisService) private apis: IApisService) { + super(route); + } + + /** + * Called when the Import API form (component) emits a "import-api" event. This is bound to + * from the importapi.page.html template. + * @param {ImportApi} api + */ + public onImportApi(api: ImportApi) { + console.log("[ImportApiPageComponent] onImportApi(): " + JSON.stringify(api)) + this.apis.importApi(api).then(updatedApi => { + let link: string[] = [ "/apis", updatedApi.id ]; + console.info("[ImportApiPageComponent] Navigating to: %o", link); + this.router.navigate(link); + }).catch( error => { + console.error("[ImportApiPageComponent] Error importing API: %o", error); + this.error(error); + }) + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/_components/activity-item.component.css b/front-end/studio/src/app/pages/apis/{apiId}/_components/activity-item.component.css new file mode 100644 index 000000000..177d85928 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/_components/activity-item.component.css @@ -0,0 +1,26 @@ +.activity-item { + background: transparent; + border-bottom: 1px dashed #dde; + padding-top: 5px; + padding-bottom: 5px; +} +.activity-item:hover { + background-color: #ffe065; + background-image: linear-gradient(to bottom, #ffe065 0, #ffe580 100%); +} + +.activity-item .activity-item-icon { + font-size: 24px; +} +.activity-item .activity-item-description { + padding-top: 5px; + line-height: 14px; +} +.activity-item .activity-item-description .by { + color: #3D4255; +} +.activity-item .activity-item-date { + padding-top: 5px; + color: #646484; + font-size: 11px; +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/_components/activity-item.component.html b/front-end/studio/src/app/pages/apis/{apiId}/_components/activity-item.component.html new file mode 100644 index 000000000..c35a215f6 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/_components/activity-item.component.html @@ -0,0 +1,5 @@ +
    +
    +
    {{ item.by }} {{ description() }}
    +
    {{ item.on | date }}
    +
    diff --git a/front-end/studio/src/app/pages/apis/{apiId}/_components/activity-item.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/_components/activity-item.component.ts new file mode 100644 index 000000000..805d95a96 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/_components/activity-item.component.ts @@ -0,0 +1,353 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Input} from "@angular/core"; +import {ApiDesignChange} from "../../../../models/api-design-change"; +import {ICommand, MarshallUtils} from "oai-ts-commands"; + + +@Component({ + moduleId: module.id, + selector: "activity-item", + templateUrl: "activity-item.component.html", + styleUrls: ["activity-item.component.css"] +}) +export class ActivityItemComponent { + + @Input() item: ApiDesignChange; + _command: ICommand = null; + + /** + * Constructor. + */ + constructor() {} + + /** + * Get the command for this change. + * @return {any} + */ + protected command(): ICommand { + if (this._command == null) { + this._command = MarshallUtils.unmarshallCommand(JSON.parse(this.item.data)); + } + return this._command; + } + + /** + * Returns an appropriate icon for the activity item, based on its type. + * @return {string} + */ + public icon(): string { + let rval: string = "user"; + switch (this.command()["type"]()) { + case "AddPathItemCommand_20": + case "AddPathItemCommand_30": + case "AddSchemaDefinitionCommand_20": + case "AddSchemaDefinitionCommand_30": + rval = "plus"; + break; + case "ChangeContactCommand_20": + case "ChangeContactCommand_30": + rval = "id-card-o"; + break; + case "ChangeDescriptionCommand_20": + case "ChangeDescriptionCommand_30": + rval = "pencil-square-o"; + break; + case "ChangeLicenseCommand_20": + case "ChangeLicenseCommand_30": + rval = "copyright"; + break; + case "ChangeMediaTypeTypeCommand": + case "ChangeParameterDefinitionTypeCommand_20": + case "ChangeParameterDefinitionTypeCommand_30": + case "ChangeParameterTypeCommand_20": + case "ChangeParameterTypeCommand_30": + case "ChangePropertyTypeCommand_20": + case "ChangePropertyTypeCommand_30": + case "ChangeResponseTypeCommand_20": + case "ChangeResponseDefinitionTypeCommand_20": + rval = "info"; + break; + case "ChangePropertyCommand_20": + case "ChangePropertyCommand_30": + case "ChangeSecuritySchemeCommand_20": + case "ChangeSecuritySchemeCommand_30": + case "ChangeServerCommand": + case "ChangeTitleCommand_20": + case "ChangeTitleCommand_30": + case "ChangeVersionCommand_20": + case "ChangeVersionCommand_30": + rval = "pencil"; + break; + case "DeleteAllParametersCommand_20": + case "DeleteAllParametersCommand_30": + case "DeleteAllPropertiesCommand_20": + case "DeleteAllPropertiesCommand_30": + case "DeleteMediaTypeCommand": + case "DeleteOperationCommand_20": + case "DeleteOperationCommand_30": + case "DeleteParameterCommand_20": + case "DeleteParameterCommand_30": + case "DeletePathCommand_20": + case "DeletePathCommand_30": + case "DeletePropertyCommand_20": + case "DeletePropertyCommand_30": + case "DeleteResponseCommand_20": + case "DeleteResponseCommand_30": + case "DeleteSchemaDefinitionCommand_20": + case "DeleteSchemaDefinitionCommand_30": + case "DeleteSecuritySchemeCommand_20": + case "DeleteSecuritySchemeCommand_30": + case "DeleteServerCommand": + case "DeleteTagCommand_20": + case "DeleteTagCommand_30": + case "DeleteRequestBodyCommand_30": + case "DeleteAllResponsesCommand_20": + case "DeleteAllResponsesCommand_30": + case "DeleteContactCommand_20": + case "DeleteContactCommand_30": + case "DeleteLicenseCommand_20": + case "DeleteLicenseCommand_30": + rval = "trash-o"; + break; + case "NewMediaTypeCommand": + case "NewOperationCommand_20": + case "NewOperationCommand_30": + case "NewParamCommand_20": + case "NewParamCommand_30": + case "NewPathCommand_20": + case "NewPathCommand_30": + case "NewRequestBodyCommand_20": + case "NewRequestBodyCommand_30": + case "NewResponseCommand_20": + case "NewResponseCommand_30": + case "NewSchemaDefinitionCommand_20": + case "NewSchemaDefinitionCommand_30": + case "NewSchemaPropertyCommand_20": + case "NewSchemaPropertyCommand_30": + case "NewSecuritySchemeCommand_20": + case "NewSecuritySchemeCommand_30": + rval = "plus"; + break; + case "NewServerCommand": + rval = "server"; + break; + case "NewTagCommand_20": + case "NewTagCommand_30": + rval = "tag"; + break; + case "ReplaceOperationCommand_20": + case "ReplaceOperationCommand_30": + case "ReplacePathItemCommand_20": + case "ReplacePathItemCommand_30": + case "ReplaceSchemaDefinitionCommand_20": + case "ReplaceSchemaDefinitionCommand_30": + rval = "code"; + break; + default: + rval = "question"; + } + return rval; + } + + /** + * Returns an appropriate description for the activity item, based on its type. + * @return {string} + */ + public description(): string { + let rval: string = "user"; + switch (this.command()["type"]()) { + case "AddPathItemCommand_20": + case "AddPathItemCommand_30": + rval = "added a Path Item named " + this.command()["_newPathItemName"] + "."; + break; + case "AddSchemaDefinitionCommand_20": + case "AddSchemaDefinitionCommand_30": + rval = "added a Schema Definition named " + this.command()["_newDefinitionName"] + "."; + break; + case "ChangeContactCommand_20": + case "ChangeContactCommand_30": + rval = "altered the API's Contact information."; + break; + case "ChangeDescriptionCommand_20": + case "ChangeDescriptionCommand_30": + rval = "altered the API's description."; + break; + case "ChangeLicenseCommand_20": + case "ChangeLicenseCommand_30": + rval = "changed the API's license to " + this.command()["_newLicenseUrl"] + "."; + break; + case "ChangeMediaTypeTypeCommand": + rval = "modified a Media Type (for node " + this.command()["_mediaTypePath"] + ")."; + break; + case "ChangeParameterDefinitionTypeCommand_20": + case "ChangeParameterDefinitionTypeCommand_30": + case "ChangeParameterTypeCommand_20": + case "ChangeParameterTypeCommand_30": + rval = "changed the type of a Parameter at location " + this.command()["_paramPath"] + "."; + break; + case "ChangePropertyTypeCommand_20": + case "ChangePropertyTypeCommand_30": + rval = "changed the type of the Schema Property named '" + this.command()["_propName"] + "' at location " + this.command()["_propPath"] + "."; + break; + case "ChangeResponseTypeCommand_20": + case "ChangeResponseDefinitionTypeCommand_20": + rval = "changed the type of an operation Response at location " + this.command()["_responsePath"] + "."; + break; + case "ChangePropertyCommand_20": + case "ChangePropertyCommand_30": + rval = "changed the value of property '" + this.command()["_property"] + "' at location " + this.command()["_nodePath"] + "."; + break; + case "ChangeSecuritySchemeCommand_20": + case "ChangeSecuritySchemeCommand_30": + rval = "modified the details of Security Scheme named '" + this.command()["_schemeName"] + "'."; + break; + case "ChangeServerCommand": + rval = "modified the details of Server '" + this.command()["_serverUrl"] + "' at location " + this.command()["_parentPath"] + "."; + break; + case "ChangeTitleCommand_20": + case "ChangeTitleCommand_30": + rval = "altered the API's title to '" + this.command()["_newTitle"] + "'"; + break; + case "ChangeVersionCommand_20": + case "ChangeVersionCommand_30": + rval = "altered the API's version to '" + this.command()["_newVersion"] + "'"; + break; + case "DeleteAllParametersCommand_20": + case "DeleteAllParametersCommand_30": + rval = "deleted all of the " + this.command()["_paramType"] + " style parameters at location " + this.command()["_parentPath"] + "."; + break; + case "DeleteAllPropertiesCommand_20": + case "DeleteAllPropertiesCommand_30": + rval = "deleted all of the Schema properties at location " + this.command()["_schemaPath"] + "."; + break; + case "DeleteMediaTypeCommand": + rval = "deleted Media Type '" + this.command()["_mediaTypeName"] + "' at location " + this.command()["_mediaTypePath"] + "."; + break; + case "DeleteOperationCommand_20": + case "DeleteOperationCommand_30": + rval = "deleted the '" + this.command()["_property"] + "' Operation at location " + this.command()["_parentPath"] + "."; + break; + case "DeleteParameterCommand_20": + case "DeleteParameterCommand_30": + rval = "deleted a parameter at location " + this.command()["_parameterPath"] + "."; + break; + case "DeletePathCommand_20": + case "DeletePathCommand_30": + rval = "deleted a Path Item named '" + this.command()["_path"] + "'."; + break; + case "DeletePropertyCommand_20": + case "DeletePropertyCommand_30": + rval = "deleted a Property named '" + this.command()["_propertyName"] + "' at location " + this.command()["_propertyPath"] + "."; + break; + case "DeleteResponseCommand_20": + case "DeleteResponseCommand_30": + rval = "deleted a Response with code '" + this.command()["_responseCode"] + "' at location " + this.command()["_responsePath"] + "."; + break; + case "DeleteSchemaDefinitionCommand_20": + case "DeleteSchemaDefinitionCommand_30": + rval = "deleted the Schema Definition named '" + this.command()["_definitionName"] + "'."; + break; + case "DeleteSecuritySchemeCommand_20": + case "DeleteSecuritySchemeCommand_30": + rval = "deleted the Security Scheme named '" + this.command()["_schemeName"] + "'."; + break; + case "DeleteServerCommand": + rval = "deleted a Server with url '" + this.command()["_serverUrl"] + "' at location " + this.command()["_parentPath"] + "."; + break; + case "DeleteTagCommand_20": + case "DeleteTagCommand_30": + rval = "deleted the global Tag definition with name '" + this.command()["_tagName"] + "'."; + break; + case "DeleteRequestBodyCommand_30": + rval = "deleted the global Tag definition with name '" + this.command()["_tagName"] + "'."; + break; + case "DeleteAllResponsesCommand_20": + case "DeleteAllResponsesCommand_30": + rval = "deleted all of the Responses at location " + this.command()["_parentPath"] + "."; + break; + case "DeleteContactCommand_20": + case "DeleteContactCommand_30": + rval = "deleted the API's Contact information."; + break; + case "DeleteLicenseCommand_20": + case "DeleteLicenseCommand_30": + rval = "deleted the API's License information."; + break; + case "NewMediaTypeCommand": + rval = "added a new Media Type named '" + this.command()["_newMediaType"] + "' at location " + this.command()["_nodePath"] + "."; + break; + case "NewOperationCommand_20": + case "NewOperationCommand_30": + rval = "added a new Operation named '" + this.command()["_method"] + "' at location " + this.command()["_path"] + "."; + break; + case "NewParamCommand_20": + case "NewParamCommand_30": + rval = "added a new Parameter named '" + this.command()["_paramName"] + "' at location " + this.command()["_parentPath"] + "."; + break; + case "NewPathCommand_20": + case "NewPathCommand_30": + rval = "added a new Path named '" + this.command()["_newPath"] + "'."; + break; + case "NewRequestBodyCommand_20": + case "NewRequestBodyCommand_30": + rval = "added a Request Body for Operation at location " + this.command()["_operationPath"] + "."; + break; + case "NewResponseCommand_20": + case "NewResponseCommand_30": + rval = "added a new Response for response code '" + this.command()["_statusCode"] + "' for Operation at location " + this.command()["_operationPath"] + "."; + break; + case "NewSchemaDefinitionCommand_20": + case "NewSchemaDefinitionCommand_30": + rval = "added a new Schema Definition for response code '" + this.command()["_statusCode"] + "' for Operation at location " + this.command()["_operationPath"] + "."; + break; + case "NewSchemaPropertyCommand_20": + case "NewSchemaPropertyCommand_30": + rval = "added a new Schema Property named '" + this.command()["_propertyName"] + "' at location " + this.command()["_schemaPath"] + "."; + break; + case "NewSecuritySchemeCommand_20": + case "NewSecuritySchemeCommand_30": + rval = "added a new Security Scheme named '" + this.command()["_schemeName"] + "'."; + break; + case "NewServerCommand": + rval = "added a new Server with url '" + this.command()["_server"].url + "' at location " + this.command()["_parentPath"] + "."; + break; + case "NewTagCommand_20": + case "NewTagCommand_30": + rval = "added a new Security Scheme named '" + this.command()["_tagName"] + "'."; + break; + case "ReplaceOperationCommand_20": + case "ReplaceOperationCommand_30": + rval = "fully replaced the source for Operation '" + this.command()["_method"] + "' at location " + this.command()["_path"] + "."; + break; + case "ReplacePathItemCommand_20": + case "ReplacePathItemCommand_30": + rval = "fully replaced the source for Path '" + this.command()["_pathName"] + "'."; + break; + case "ReplaceSchemaDefinitionCommand_20": + case "ReplaceSchemaDefinitionCommand_30": + rval = "fully replaced the source for Schema Definition '" + this.command()["_defName"] + "'."; + break; + default: + console.info("[ActivityItemComponent] WARNING - unhandled change item type: %s", this.command()["type"]()); + rval = "performed some unknown action..."; + } + return rval; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/api-detail.page.css b/front-end/studio/src/app/pages/apis/{apiId}/api-detail.page.css new file mode 100644 index 000000000..5d9af96bc --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/api-detail.page.css @@ -0,0 +1,71 @@ + +.api-tags { + margin-top: 15px; +} +.api-tags .api-tags-label { + font-weight: bold; + margin-right: 5px; +} +.api-tags .api-tag { + margin-right: 5px; + border: 1px solid #ccc; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + padding: 2px 4px; +} +.api-tags .api-tag:hover { + background-color: #0088ce; + border-color: #00659c; + color: white; +} + +.api-description { + margin-top: 30px; + margin-bottom: 25px; +} + +.api-created-info { + margin-left: 15px +} +.api-created-info .info-left { + display: inline-block; + width: 40%; +} +.api-created-info .info-right { + display: inline; + width: 50%; +} + +.api-meta-data { + margin-top: 15px +} + +.api-meta-data label { + text-decoration: underline; +} + +.api-resource-url-value { +} + +.api-action-buttons { + margin-top: 20px +} + +.api-action-buttons a.pull-right { + margin-left: 10px; +} + +.dropdown-menu a.disabled { + cursor: not-allowed; + color: #999; +} + +.card-activity { +} +.card-activity .actions { + padding-top: 10px; +} +.card-activity .actions button { + font-size: 11px; +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/api-detail.page.html b/front-end/studio/src/app/pages/apis/{apiId}/api-detail.page.html new file mode 100644 index 000000000..a047f73f9 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/api-detail.page.html @@ -0,0 +1,172 @@ +
    + +
  • +
  • +
  • +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +

    +

    Loading API information...

    +

    +
    +
    + + +
    +
    +

    + + {{ api.name }} + +

    +
    +
    +

    {{ api.description }}

    +
    +
    + + Created on {{ api.createdOn | date }} +
    +
    + + Created by {{ api.createdBy }} +
    +
    +
    + Tags: + {{ tag }} +
    + +
    +
    +
    +
    +
    +
    +
    +

    Top Contributors

    +
    +
    +
    +

    Loading contributors data...

    +
    +
    +
    +
    {{ c.name }}
    +
    +
    + {{ c.edits }} of {{ contributors.totalEdits }} edits +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    +

    +

    Loading API activity...

    +

    +
    +
    + + +
    +
    +

    + + Activity Log +

    +
    +
    + +
    + +
    +
    + + No Activity Found! No changes have been made to this API yet. +
    +
    +
    +
    +
    +
    +
    +
    + +

    Do you really want to delete this API?

    +
    + + NOTE: + This will permanently delete the API definition from Apicurio Studio. This operation cannot be undone. +
    +
    diff --git a/front-end/studio/src/app/pages/apis/{apiId}/api-detail.page.ts b/front-end/studio/src/app/pages/apis/{apiId}/api-detail.page.ts new file mode 100644 index 000000000..0291a082f --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/api-detail.page.ts @@ -0,0 +1,116 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Inject} from "@angular/core"; +import {ActivatedRoute, Router} from "@angular/router"; + +import {IApisService} from "../../../services/apis.service"; +import {Api} from "../../../models/api.model"; +import {ApiContributors} from "../../../models/api-contributors"; +import {AbstractPageComponent} from "../../../components/page-base.component"; +import {ApiDesignChange} from "../../../models/api-design-change"; + +@Component({ + moduleId: module.id, + selector: "api-detail-page", + templateUrl: "api-detail.page.html", + styleUrls: ["api-detail.page.css"] +}) +export class ApiDetailPageComponent extends AbstractPageComponent { + + public api: Api; + public contributors: ApiContributors; + public activity: ApiDesignChange[] = []; + public activityStart: number = 0; + public activityEnd: number = 10; + public hasMoreActivity: boolean = false; + public gettingMoreActivity: boolean = false; + + /** + * Constructor. + * @param {Router} router + * @param {ActivatedRoute} route + * @param {IApisService} apis + */ + constructor(private router: Router, route: ActivatedRoute, + @Inject(IApisService) private apis: IApisService) { + super(route); + this.api = new Api(); + } + + /** + * Called to kick off loading the page's async data. + * @param params + */ + public loadAsyncPageData(params: any): void { + console.info("[ApiDetailPageComponent] Loading async page data"); + let apiId: string = params["apiId"]; + this.apis.getApi(apiId).then(api => { + this.api = api; + this.dataLoaded["api"] = true; + }).catch(error => { + console.error("[ApiDetailPageComponent] Error getting API"); + this.error(error); + }); + this.apis.getContributors(apiId).then(contributors => { + console.info("[ApiDetailPageComponent] Contributors data loaded: %o", contributors); + this.contributors = contributors; + this.dataLoaded["contributors"] = true; + }).catch(error => { + console.error("[ApiDetailPageComponent] Error getting API contributors"); + this.error(error); + }); + this.apis.getActivity(apiId, this.activityStart, this.activityEnd).then(activity => { + console.info("[ApiDetailPageComponent] Activity data loaded: %o", activity); + this.activity = activity; + this.dataLoaded["activity"] = true; + this.hasMoreActivity = activity && activity.length >= 10; + }).catch(error => { + console.error("[ApiDetailPageComponent] Error getting API activity"); + this.error(error); + }); + } + + /** + * Called to delete the API from the studio (unmanage it). + */ + public deleteApi(): void { + // TODO need a visual indicator that we're working on the delete + this.apis.deleteApi(this.api).then(() => { + this.router.navigate([ "" ]); + }).catch( reason => { + this.error(reason); + }); + } + + /** + * Called when the user wishes to see more activity. + */ + public showMoreActivity(): void { + this.activityStart += 10; + this.activityEnd += 10; + + this.apis.getActivity(this.api.id, this.activityStart, this.activityEnd).then(activity => { + this.activity = this.activity.concat(activity); + this.hasMoreActivity = activity && activity.length >= 10; + }).catch(error => { + console.error("[ApiDetailPageComponent] Error getting API activity"); + this.error(error); + }); + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/collaboration/accept/api-accept.page.css b/front-end/studio/src/app/pages/apis/{apiId}/collaboration/accept/api-accept.page.css new file mode 100644 index 000000000..010406761 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/collaboration/accept/api-accept.page.css @@ -0,0 +1,23 @@ + + +.api-invitation { + margin-top: 30px; + background-color: white; + box-shadow: 0 6px 12px rgba(0,0,0,.175); +} + +.api-invitation .accepting-spinner { + margin-top: 35px; +} +.api-invitation .accepting-spinner .spinner-label { + font-size: 18px; +} + +.api-invitation .subject { + margin-top: 32px; + font-weight: bold; +} + +.api-invitation .created-on { + font-size: 11px; +} \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/{apiId}/collaboration/accept/api-accept.page.html b/front-end/studio/src/app/pages/apis/{apiId}/collaboration/accept/api-accept.page.html new file mode 100644 index 000000000..38330cbf2 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/collaboration/accept/api-accept.page.html @@ -0,0 +1,38 @@ +
    +
    + +
    +
    +
    + +
    +

    Loading invitation, please wait...

    +
    + +
    +
    + +
    +

    Invitation to Collaborate

    +

    "{{ invitation.subject }}"

    +

    + You have been invited by {{ invitation.createdBy }} to collaborate on an + API Design. Would you like to accept? +

    +

    + (Invitation created on {{ invitation.createdOn | date }}) +

    +
    + +
    +
    + +
    +
    +

    Accepting invitation, please wait...

    +
    +
    + +
    +
    +
    diff --git a/front-end/studio/src/app/pages/apis/{apiId}/collaboration/accept/api-accept.page.ts b/front-end/studio/src/app/pages/apis/{apiId}/collaboration/accept/api-accept.page.ts new file mode 100644 index 000000000..790180f76 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/collaboration/accept/api-accept.page.ts @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2018 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Inject} from "@angular/core"; +import {ActivatedRoute, Router} from "@angular/router"; + +import {IApisService} from "../../../../../services/apis.service"; +import {AbstractPageComponent} from "../../../../../components/page-base.component"; +import {Invitation} from "../../../../../models/invitation"; + +@Component({ + moduleId: module.id, + selector: "api-accept-page", + templateUrl: "api-accept.page.html", + styleUrls: ["api-accept.page.css"] +}) +export class ApiAcceptPageComponent extends AbstractPageComponent { + + public invitation: Invitation; + public _accepting: boolean = false; + + /** + * Constructor. + * @param {Router} router + * @param {ActivatedRoute} route + * @param {IApisService} apis + */ + constructor(private router: Router, route: ActivatedRoute, + @Inject(IApisService) private apis: IApisService) { + super(route); + } + + /** + * Called to kick off loading the page's async data. + * @param params + */ + public loadAsyncPageData(params: any): void { + console.info("[ApiAcceptPageComponent] Loading async page data"); + let apiId: string = params["apiId"]; + let inviteId: string = params["inviteId"]; + + this.apis.getInvitation(apiId, inviteId).then(invitation => { + this.invitation = invitation; + if (this.invitation.status != "pending") { + // If the invitation is valid but its status is not "pending", throw a 404 error + this.error({ + status: 404, + statusText: "Invitation Not Found", + json: function() { + return {}; + } + }); + } else { + this.dataLoaded["invitation"] = true; + } + }).catch(error => { + console.error("[ApiAcceptPageComponent] Error getting API"); + this.error(error); + }); + } + + /** + * Called to accept the invite. + */ + public acceptInvitation(): void { + this._accepting = true; + this.apis.acceptInvitation(this.invitation.designId, this.invitation.inviteId).then( () => { + let link: string[] = [ "/apis", this.invitation.designId ]; + console.info("[ApiAcceptPageComponent] Navigating to: %o", link); + this.router.navigate(link); + }).catch( error => { + this.error(error); + }); + } + + /** + * Called to reject the invite. + */ + public rejectInvitation(): void { + // this.apis.rejectInvitation(this.invitation.designId, this.invitation.inviteId); + let link: string[] = [ "/" ]; + this.router.navigate(link); + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/collaboration/api-collaboration.page.css b/front-end/studio/src/app/pages/apis/{apiId}/collaboration/api-collaboration.page.css new file mode 100644 index 000000000..f03d5f9fc --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/collaboration/api-collaboration.page.css @@ -0,0 +1,42 @@ + +.collaborator-list-group, .invitation-list-group { + border-top: none; +} + +.collaborator-list-item, .invitation-list-item { + background-color: #f8f8f8; + border-bottom: 1px solid transparent; +} +.collaborator-list-item { + border-left: 2px solid transparent; +} +.collaborator-list-item.owner { + border-left: 2px solid #0088ce; +} + +.collaborator-list-item .list-view-pf-description, .invitation-list-item .list-view-pf-description { + width: inherit; +} +.collaborator-list-item .list-view-pf-description .list-group-item-heading, .invitation-list-item .list-view-pf-description .list-group-item-heading { + margin-right: 0; +} +.collaborator-list-item .list-view-pf-actions button, .invitation-list-item .list-view-pf-actions button { + margin-left: 0; +} + +.invitation-list-item .list-view-pf-main-info .list-view-pf-icon-sm { + border: none; +} +.invitation-list-item.accepted .list-view-pf-main-info .list-view-pf-icon-sm { + color: #3f9c35; +} +.invitation-list-item.rejected .list-view-pf-main-info .list-view-pf-icon-sm { + color: #cc0000; +} + +.collaborator-list-item .list-view-pf-main-info .list-view-pf-body .list-view-pf-description .list-group-item-heading small { + font-size: 12px; +} +.invitation-list-item .list-view-pf-main-info .list-view-pf-body .list-view-pf-description .list-group-item-heading small { + font-size: 12px; +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/collaboration/api-collaboration.page.html b/front-end/studio/src/app/pages/apis/{apiId}/collaboration/api-collaboration.page.html new file mode 100644 index 000000000..b51ad75cd --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/collaboration/api-collaboration.page.html @@ -0,0 +1,149 @@ +
    + +
  • +
  • +
  • +
  • +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +

    +

    Loading collaborator information...

    +

    +
    +
    + + +
    +
    +

    + + Collaborators +

    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    + {{ collaborator.userName }} + {{ collaborator.role }} +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +

    +

    Loading invitations...

    +

    +
    +
    + + +
    +
    +

    + + Invitations + +

    +
    +
    +
    + + No Invitations Found +
    + +
    +
    +
    + + +
    +
    +
    + + + + + +
    +
    +
    +
    + Created on {{ invitation.createdOn | date }} by {{ invitation.createdBy }} + Creating, please wait... + Deleting invitation, please wait... + Pending (copy the link and send it to someone!) + Accepted by {{ invitation.modifiedBy }} on {{ invitation.modifiedOn | date }} + Rejected by {{ invitation.modifiedBy }} on {{ invitation.modifiedOn | date }} +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +

    Do you really want to remove this collaborator?

    +
    + + NOTE: + + This will make it so the user can no longer access this API design. If you wish to collaborate with the user again + in the future you will need to re-invite them. + +
    +
    + +

    Copy the following link to your clipboard and then send it to someone you want to collaborate with!

    +
    + +
    +
    diff --git a/front-end/studio/src/app/pages/apis/{apiId}/collaboration/api-collaboration.page.ts b/front-end/studio/src/app/pages/apis/{apiId}/collaboration/api-collaboration.page.ts new file mode 100644 index 000000000..9793ac148 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/collaboration/api-collaboration.page.ts @@ -0,0 +1,187 @@ +/** + * @license + * Copyright 2018 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Inject} from "@angular/core"; +import {ActivatedRoute, Router} from "@angular/router"; + +import {IApisService} from "../../../../services/apis.service"; +import {Api} from "../../../../models/api.model"; +import {AbstractPageComponent} from "../../../../components/page-base.component"; +import {ApiCollaborator} from "../../../../models/api-collaborator"; +import {Invitation} from "../../../../models/invitation"; +import {IAuthenticationService} from "../../../../services/auth.service"; +import {User} from "../../../../models/user.model"; + +@Component({ + moduleId: module.id, + selector: "api-collaboration-page", + templateUrl: "api-collaboration.page.html", + styleUrls: ["api-collaboration.page.css"] +}) +export class ApiCollaborationPageComponent extends AbstractPageComponent { + + public api: Api; + public collaborators: ApiCollaborator[]; + public invitations: Invitation[]; + + private _isOwner: boolean = false; + public _copyLink: string = ""; + + + /** + * Constructor. + * @param {Router} router + * @param {ActivatedRoute} route + * @param {IApisService} apis + * @param {IAuthenticationService} authService + */ + constructor(private router: Router, route: ActivatedRoute, + @Inject(IApisService) private apis: IApisService, + @Inject(IAuthenticationService) private authService: IAuthenticationService) { + super(route); + this.api = new Api(); + } + + /** + * Called to kick off loading the page's async data. + * @param params + */ + public loadAsyncPageData(params: any): void { + console.info("[ApiCollaborationPageComponent] Loading async page data"); + let apiId: string = params["apiId"]; + this.apis.getApi(apiId).then(api => { + this.api = api; + this.dataLoaded["api"] = true; + }).catch(error => { + console.error("[ApiCollaborationPageComponent] Error getting API"); + this.error(error); + }); + this.apis.getCollaborators(apiId).then(collaborators => { + console.info("[ApiCollaborationPageComponent] Collaborators data loaded: %o", collaborators); + this.collaborators = collaborators; + this.collaborators.sort( (c1, c2) => { + if (c1.role === "owner" && c2.role != "owner") { + return -1; + } + if (c1.role != "owner" && c2.role === "owner") { + return 1; + } + return c1.userName.toLowerCase().localeCompare(c1.userName.toLowerCase()); + }); + this.setIsOwner(); + this.dataLoaded["collaborators"] = true; + }).catch(error => { + console.error("[ApiCollaborationPageComponent] Error getting API collaborators"); + this.error(error); + }); + this.apis.getInvitations(apiId).then(invitations => { + console.info("[ApiCollaborationPageComponent] Invitations data loaded: %o", invitations); + this.invitations = invitations; + this.invitations.sort( (i1, i2) => { + if (i1.createdOn > i2.createdOn) { + return -1; + } + if (i1.createdOn < i2.createdOn) { + return 1; + } + return i1.inviteId.localeCompare(i2.inviteId); + }); + this.dataLoaded["invitations"] = true; + }).catch(error => { + console.error("[ApiCollaborationPageComponent] Error getting API invitations"); + this.error(error); + }); + } + + /** + * Called when the user clicks the "Create Invitation" button. + */ + public createInvitation(): void { + let newInvitation: Invitation = new Invitation(); + newInvitation.createdBy = this.authService.getAuthenticatedUserNow().login; + newInvitation.createdOn = new Date(); + newInvitation.designId = this.api.id; + newInvitation.status = "creating"; + newInvitation.inviteId = "12345"; + this.invitations.unshift(newInvitation); + this.apis.createInvitation(this.api.id).then( (invitation) => { + newInvitation.createdBy = invitation.createdBy; + newInvitation.createdOn = invitation.createdOn; + newInvitation.status = invitation.status; + newInvitation.inviteId = invitation.inviteId; + newInvitation.role = invitation.role; + }).catch( error => { + console.error("[ApiCollaborationPageComponent] Error creating invitation"); + this.error(error); + }); + } + + /** + * Called to remove a collaborator. The user will no longer have access to the API. + * @param {ApiCollaborator} collaborator + */ + public removeCollaborator(collaborator: ApiCollaborator): void { + } + + /** + * Called to cancel a pending invitation. + */ + public cancelInvitation(invite: Invitation): void { + invite.status = "rejecting"; + this.apis.rejectInvitation(this.api.id, invite.inviteId).then( () => { + invite.status = "rejected"; + invite.modifiedOn = new Date(); + invite.modifiedBy = this.authService.getAuthenticatedUserNow().login; + }).catch( error => { + this.error(error); + }); + } + + /** + * Called to copy the invitation link to the clipboard. + * @param {Invitation} invite + */ + public copyInvitationLink(invite: Invitation): void { + this._copyLink = window.location.toString() + "/accept/" + invite.inviteId; + } + + /** + * Returns true if the currently logged-in user is an Owner of the API. + * @return {boolean} + */ + public isOwner(): boolean { + return this._isOwner; + } + + /** + * Set the isOwner property. + */ + private setIsOwner(): void { + this._isOwner = false; + + let user: User = this.authService.getAuthenticatedUserNow(); + if (user != null) { + for (let collaborator of this.collaborators) { + if (collaborator.userId == user.login) { + this._isOwner = collaborator.role === "owner"; + return; + } + } + } + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/context-help.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/context-help.component.html new file mode 100644 index 000000000..1a27d0ec9 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/context-help.component.html @@ -0,0 +1,6 @@ + + +
    + +
    +
    \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/context-help.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/context-help.component.ts new file mode 100644 index 000000000..13acba2af --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/context-help.component.ts @@ -0,0 +1,67 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, ViewChildren, QueryList, ElementRef, HostListener} from "@angular/core"; + +@Component({ + moduleId: module.id, + selector: "context-help", + templateUrl: "context-help.component.html" +}) +export class ContextHelpComponent { + + private _open: boolean = false; + + private left: string; + private top: string; + + @ViewChildren("helppanel") helppanel: QueryList; + + @HostListener("document:click", ["$event"]) + public onDocumentClick(event: MouseEvent): void { + if (this._open) { + this.close(); + } + } + + public open(event: MouseEvent): void { + this.left = event.clientX + "px"; + this.top = event.clientY + "px"; + event.preventDefault(); + event.stopPropagation(); + this._open = true; + } + + public close(): void { + this._open = false; + } + + public isOpen(): boolean { + return this._open; + } + + /** + * Called whenever the user presses a key. + * @param event + */ + public onGlobalKeyDown(event: KeyboardEvent): void { + if (event.key === "Escape" && !event.metaKey && !event.altKey && !event.ctrlKey) { + this.close(); + } + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-editor.base.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-editor.base.ts new file mode 100644 index 000000000..3d18ab8d3 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-editor.base.ts @@ -0,0 +1,277 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + EventEmitter, Output, Input, AfterViewInit, ViewChildren, QueryList, ElementRef, + ViewChild, HostListener +} from "@angular/core"; +import {TimerObservable} from "rxjs/observable/TimerObservable"; +import {Subscription} from "rxjs"; + + +/** + * Base class for all inline editors. + */ +export abstract class AbstractInlineEditor { + + static s_activeEditor: any = null; + + @Input() enabled = true; + @Input() labelClass = ""; + @Input() inputClass = ""; + @Input() formClass = ""; + @Input() topIncrement: number = 0; + @Input() leftIncrement: number = 0; + @Input() rightIncrement: number = 0; + @Input() bottomIncrement: number = 0; + + @Output() onChange: EventEmitter = new EventEmitter(); + + @ViewChildren("clickcontainer") clickcontainer: QueryList; + + private _mousein: boolean = false; + private _hoverSub: Subscription; + private _hoverElem: any; + + public hovering: boolean = false; + public editing: boolean = false; + public canClose: boolean = false; + public hoverDims: any = { + left: 0, + top: 0, + width: 0, + height: 0 + }; + + public inputHover: boolean = false; + public inputFocus: boolean = false; + + public evalue: T; + + @HostListener("document:click", ["$event"]) + public onDocumentClick(event: MouseEvent): void { + if (this.clickcontainer && this.clickcontainer.first && this.clickcontainer.first.nativeElement) { + if (!this.clickcontainer.first.nativeElement.contains(event.target) && this.canClose) { + this.onCancel(); + } else { + } + } + } + + public onMouseIn(event: MouseEvent): void { + if (this.editing || !this.enabled) { + return; + } + this._mousein = true; + this._hoverElem = event.currentTarget; + this._hoverSub = TimerObservable.create(100).subscribe(() => { + if (this._mousein) { + let target: any = this._hoverElem; + let targetRect: any = target.getBoundingClientRect(); + this.hoverDims = this.calcHoverDimensions(targetRect); + this.hovering = true; + } + }); + } + + public hoverTop(): string { + return this.hoverDims.top + "px"; + } + + public hoverLeft(): string { + return this.hoverDims.left + "px"; + } + + public hoverWidth(): string { + return this.hoverDims.width + "px"; + } + + public hoverHeight(): string { + return this.hoverDims.height + "px"; + } + + public onMouseOut(): void { + if (this.editing) { + return; + } + if (this._mousein && !this.hovering) { + this.hovering = false; + } + if (this._hoverSub) { + this._hoverSub.unsubscribe(); + this._hoverSub = null; + } + this._mousein = false; + } + + public onOverlayOut(): void { + if (this.hovering) { + this.hovering = false; + this._mousein = false; + } + } + + public onStartEditing(): void { + this.canClose = false; + this.evalue = this.initialValueForEditing(); + this.hovering = false; + this._mousein = false; + this.inputFocus = true; + this.inputHover = true; + this.editing = true; + + if (AbstractInlineEditor.s_activeEditor != null && AbstractInlineEditor.s_activeEditor !== this) { + AbstractInlineEditor.s_activeEditor.onCancel(); + } + AbstractInlineEditor.s_activeEditor = this; + + // TODO watch for changes to children rather than simply wait 250ms?? + TimerObservable.create(250).subscribe(() => { + this.canClose = true; + }); + } + + protected abstract initialValueForEditing(): T; + + public onSave(): void { + this.onChange.emit(this.evalue); + this.editing = false; + AbstractInlineEditor.s_activeEditor = this; + } + + public onCancel(): void { + this.editing = false; + AbstractInlineEditor.s_activeEditor = this; + this.evalue = this.initialValueForEditing(); + } + + public onInputKeypress(event: KeyboardEvent): void { + if (event.key === "Escape") { + this.onCancel(); + } + } + + public onInputFocus(isFocus: boolean): void { + if (this.inputFocus !== isFocus) { + this.inputFocus = isFocus; + } + } + + public onInputIn(isIn: boolean): void { + if (this.inputHover !== isIn) { + this.inputHover = isIn; + } + } + + protected calcHoverDimensions(targetRect: any): any { + return { + left: targetRect.left - 5 - this.leftIncrement, + top: targetRect.top - this.topIncrement, + width: targetRect.right - targetRect.left + 10 + 20 + this.leftIncrement + this.rightIncrement, + height: targetRect.bottom - targetRect.top + 3 + this.topIncrement + this.bottomIncrement + } + } + +} + + + +/** + * Base class for any inline editor that edits a single value of an arbitrary type. + */ +export abstract class AbstractInlineValueEditor extends AbstractInlineEditor { + + @Input() value: T; + @Input() noValueMessage: string; + + protected displayValue(): string { + if (this.isEmpty()) { + return this.noValueMessage; + } + return this.formatValue(this.value); + } + + protected abstract formatValue(value: T): string; + + protected isEmpty(): boolean { + return this.value === undefined || this.value === null; + } + + protected initialValueForEditing(): T { + return this.value; + } + +} + +/** + * Base class for any inline editor that is built on a single text input element. The template + * must include an 'input' element named #newvalue. + */ +export abstract class TextInputEditorComponent extends AbstractInlineValueEditor implements AfterViewInit { + + @ViewChildren("newvalue") input: QueryList; + + ngAfterViewInit(): void { + this.input.changes.subscribe(changes => { + if (changes.last) { + changes.last.nativeElement.focus(); + changes.last.nativeElement.select(); + } + }); + } + + protected isEmpty(): boolean { + return super.isEmpty() || this.value.length === 0; + } + + protected formatValue(value: string): string { + return value; + } + +} + + +/** + * Base class for any inline editor that is built on a single textarea element. The template + * must include a 'textarea' element named #newvalue. + */ +export abstract class TextAreaEditorComponent extends TextInputEditorComponent { + + public onInputKeypress(event: KeyboardEvent): void { + super.onInputKeypress(event); + + if (event.ctrlKey && event.key === "Enter" && this.isValid()) { + this.onSave(); + } + } + + /** + * Subclasses can override this to provide validation status of the current value. + * @return {boolean} + */ + protected isValid(): boolean { + return true; + } + + protected calcHoverDimensions(targetRect: any): any { + let dims: any = super.calcHoverDimensions(targetRect); + dims.top = dims.top - 2; + dims.height += 2; + return dims; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-text-editor.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-text-editor.component.html new file mode 100644 index 000000000..74f19dedc --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-text-editor.component.html @@ -0,0 +1,27 @@ +{{ displayValue() }} +
    +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-text-editor.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-text-editor.component.ts new file mode 100644 index 000000000..99bb0a913 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-text-editor.component.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Component, ViewEncapsulation +} from "@angular/core"; +import {TextInputEditorComponent} from "./inline-editor.base"; + + +@Component({ + moduleId: module.id, + selector: "inline-text-editor", + templateUrl: "inline-text-editor.component.html", + encapsulation: ViewEncapsulation.None +}) +export class InlineTextEditorComponent extends TextInputEditorComponent { + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-textarea-editor.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-textarea-editor.component.html new file mode 100644 index 000000000..02a330b94 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-textarea-editor.component.html @@ -0,0 +1,28 @@ +
    {{ displayValue() }}
    +
    +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-textarea-editor.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-textarea-editor.component.ts new file mode 100644 index 000000000..1445f7e4c --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/inline-textarea-editor.component.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Component, ViewEncapsulation +} from "@angular/core"; +import {TextAreaEditorComponent} from "./inline-editor.base"; + + +@Component({ + moduleId: module.id, + selector: "inline-textarea-editor", + templateUrl: "inline-textarea-editor.component.html", + encapsulation: ViewEncapsulation.None +}) +export class InlineTextAreaComponent extends TextAreaEditorComponent { + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/path-item.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/path-item.component.html new file mode 100644 index 000000000..2a50138be --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/path-item.component.html @@ -0,0 +1 @@ +{{ pathSegment }} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/path-item.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/path-item.component.ts new file mode 100644 index 000000000..5894c0901 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/path-item.component.ts @@ -0,0 +1,39 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Input, ViewEncapsulation} from "@angular/core"; + + +@Component({ + moduleId: module.id, + selector: "[path-item]", + templateUrl: "path-item.component.html", + encapsulation: ViewEncapsulation.None +}) +export class PathItemComponent { + + @Input() path: string; + + public pathSegments(): string[] { + let rval: string[] = this.path.split("/"); + if (rval[0] === "") { + rval = rval.splice(1); + } + return rval; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/response-item.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/response-item.component.html new file mode 100644 index 000000000..ddc8e5aa0 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/response-item.component.html @@ -0,0 +1,2 @@ + +{{ name }} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/response-item.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/response-item.component.ts new file mode 100644 index 000000000..49b198b9e --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/response-item.component.ts @@ -0,0 +1,31 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Input, ViewEncapsulation} from "@angular/core"; + + +@Component({ + moduleId: module.id, + selector: "[response-item]", + templateUrl: "response-item.component.html", + encapsulation: ViewEncapsulation.None +}) +export class ResponseItemComponent { + + @Input() name: string; + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/schema-type.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/schema-type.component.html new file mode 100644 index 000000000..850fee908 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/schema-type.component.html @@ -0,0 +1 @@ +{{ displayType() }} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/schema-type.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/schema-type.component.ts new file mode 100644 index 000000000..79058306d --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/schema-type.component.ts @@ -0,0 +1,63 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Input} from "@angular/core"; +import {ObjectUtils} from "../../_util/object.util"; +import {SimplifiedType} from "oai-ts-commands"; + +@Component({ + moduleId: module.id, + selector: "schema-type", + templateUrl: "schema-type.component.html" +}) +export class SchemaTypeComponent { + + @Input() type: SimplifiedType; + + public displayType(): string { + if (ObjectUtils.isNullOrUndefined(this.type) || ObjectUtils.isNullOrUndefined(this.type.type)) { + return "No Type"; + } + if (this.type.isArray()) { + if (this.type.of && this.type.of.as) { + return "Array of: " + this.type.of.type + " as " + this.type.of.as; + } + if (this.type.of && this.type.of.isSimpleType()) { + return "Array of: " + this.type.of.type; + } + if (this.type.of && this.type.of.isRef()) { + return "Array of: " + this.type.of.type.substr(this.type.of.type.lastIndexOf('/') + 1); + } + } + if (this.type.isSimpleType()) { + if (this.type.as) { + return this.type.type + " as " + this.type.as; + } else { + return this.type.type; + } + } + if (this.type.isRef()) { + return this.type.type.substr(this.type.type.lastIndexOf('/') + 1); + } + return "Unknown Type"; + } + + public hasType(): boolean { + return !ObjectUtils.isNullOrUndefined(this.type.type); + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/search.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/search.component.html new file mode 100644 index 000000000..d5f127906 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/search.component.html @@ -0,0 +1,13 @@ +
    +
    +
    + + + +
    +
    +
    + +
    +
    diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/search.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/search.component.ts new file mode 100644 index 000000000..527108806 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/search.component.ts @@ -0,0 +1,56 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Input, Output, EventEmitter} from "@angular/core"; +import {BehaviorSubject} from "rxjs/BehaviorSubject"; + +@Component({ + moduleId: module.id, + selector: "search", + templateUrl: "search.component.html" +}) +export class SearchComponent { + + @Input() initialValue: string; + @Input() placeholder: string; + @Input() searchId: string; + + @Output() onSearch: EventEmitter = new EventEmitter(); + + value: string; + private _valueObs: BehaviorSubject = new BehaviorSubject(null); + + constructor() { + this._valueObs.debounceTime(200).subscribe( value => { + this.onSearch.emit(value); + }); + } + + public changeValue(newValue: string): void { + this.value = newValue; + this._valueObs.next(newValue); + } + + public clear(): void { + this.value = null; + this.onSearch.emit(null); + } + + public hasValue(): boolean { + return this.value && this.value.length > 0; + } +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/server-url.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/server-url.component.html new file mode 100644 index 000000000..9a401461f --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/server-url.component.html @@ -0,0 +1 @@ +{{ segment }} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/server-url.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/server-url.component.ts new file mode 100644 index 000000000..efd61a41d --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/server-url.component.ts @@ -0,0 +1,55 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Input, ViewEncapsulation} from "@angular/core"; + + +@Component({ + moduleId: module.id, + selector: "[server-url]", + templateUrl: "server-url.component.html", + encapsulation: ViewEncapsulation.None +}) +export class ServerUrlComponent { + + @Input() url: string; + + public segments(): string[] { + let segments: string[] = []; + let i: number = 0; + let sep: string = '{'; + while (i < this.url.length) { + let from: number = i; + let to: number = this.url.indexOf(sep, from + 1); + if (to == -1) { + to = this.url.length; + } else { + if (sep === '{') { + sep = '}'; + } else { + to++; + sep = '{'; + } + } + let segment: string = this.url.substring(from, to); + segments.push(segment); + i = to; + } + return segments; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/validation-icon.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/validation-icon.component.html new file mode 100644 index 000000000..36ccdeeac --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/validation-icon.component.html @@ -0,0 +1,3 @@ + ({{ numErrors() }}) \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/validation-icon.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/validation-icon.component.ts new file mode 100644 index 000000000..fa5feba93 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/common/validation-icon.component.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Input, Output} from "@angular/core"; +import {OasValidationError} from "oai-ts-core"; + +@Component({ + moduleId: module.id, + selector: "validation-icon", + templateUrl: "validation-icon.component.html" +}) +export class ValidationIconComponent { + + @Input() validationErrors: OasValidationError[] = []; + @Output() onClick: EventEmitter = new EventEmitter(); + + public message(): string { + if (this.hasErrors()) { + return "Found " + this.numErrors() + " validation problems." + } else { + return "No validation errors/warnings found."; + } + } + + public hasErrors(): boolean { + return this.validationErrors.length > 0; + } + + public numErrors(): number { + return this.validationErrors.length; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-definition.component.css b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-definition.component.css new file mode 100644 index 000000000..35f2cb2fe --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-definition.component.css @@ -0,0 +1,3 @@ +.example-section .example button { + margin-top: 5px; +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-definition.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-definition.component.html new file mode 100644 index 000000000..ca86f1dac --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-definition.component.html @@ -0,0 +1,62 @@ + +
    + +
    diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-definition.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-definition.component.ts new file mode 100644 index 000000000..c0f130945 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-definition.component.ts @@ -0,0 +1,171 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; +import {AceEditorDirective} from "ng2-ace-editor"; +import {Subject} from "rxjs/Subject"; +import {Oas20SchemaDefinition, Oas30SchemaDefinition, OasDocument, OasLibraryUtils, OasVisitorUtil} from "oai-ts-core"; +import {FindSchemaDefinitionsVisitor} from "../../_visitors/schema-definitions.visitor"; + + +@Component({ + moduleId: module.id, + selector: "add-definition-dialog", + templateUrl: "add-definition.component.html", + styleUrls: ["add-definition.component.css"] +}) +export class AddDefinitionDialogComponent { + + @Output() onAdd: EventEmitter = new EventEmitter(); + + @ViewChildren("addDefinitionModal") addDefinitionModal: QueryList; + @ViewChildren("exampleEditor") exampleEditor: QueryList; + + protected _isOpen: boolean = false; + + protected name: string = ""; + + protected example: string = ""; + protected exampleValid: boolean = true; + protected exampleFormattable: boolean = false; + + protected defChanged: Subject = new Subject(); + protected defs: string[] = []; + protected defExists: boolean = false; + + /** + * Called to open the dialog. + */ + public open(document: OasDocument): void { + this._isOpen = true; + this.name = ""; + this.example = ""; + this.exampleValid = true; + this.exampleFormattable = false; + + this.addDefinitionModal.changes.subscribe( () => { + if (this.addDefinitionModal.first) { + this.addDefinitionModal.first.show(); + } + }); + + this.defs = []; + this.defExists = false; + let definitions: (Oas20SchemaDefinition | Oas30SchemaDefinition)[] = this.getDefinitions(document); + definitions.forEach( definition => { + this.defs.push(FindSchemaDefinitionsVisitor.definitionName(definition)); + }); + this.defChanged + .debounceTime(300) + .distinctUntilChanged() + .subscribe( def => { + this.defExists = this.defs.indexOf(def) != -1; + }); + } + + private getDefinitions(document: OasDocument): (Oas20SchemaDefinition | Oas30SchemaDefinition)[] { + let vizzy: FindSchemaDefinitionsVisitor = new FindSchemaDefinitionsVisitor(null); + OasVisitorUtil.visitTree(document, vizzy); + return vizzy.getSortedSchemaDefinitions() + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + } + + /** + * Called when the user clicks "add". + */ + protected add(): void { + let data: any = { + name: this.name, + example: this.example + }; + this.onAdd.emit(data); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.addDefinitionModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + + /** + * Called when the user changes the example definition in the Add Definition modal dialog. + * @param definition + */ + public setExampleDefinition(definition: string): void { + this.example = definition; + if (this.example === "") { + this.exampleValid = true; + this.exampleFormattable = false; + } else { + try { + JSON.parse(this.example); + this.exampleValid = true; + this.exampleFormattable = true; + } catch (e) { + this.exampleValid = false; + this.exampleFormattable = false; + } + } + } + + /** + * Returns true if it's possible to format the example definition (it must be non-null and + * syntactically valid). + * @return {boolean} + */ + public isExampleDefinitionFormattable(): boolean { + return this.exampleFormattable; + } + + /** + * Returns true if the example definition added by the user in the Add Definition modal + * dialog is valid (syntactically). + * @return {boolean} + */ + public isExampleDefinitionValid(): boolean { + return this.exampleValid; + } + + /** + * Called to format the example definition. + */ + public formatExampleDefinition(): void { + if (this.exampleEditor.first) { + let jsObj: any = JSON.parse(this.example); + let nsrcStr: string = JSON.stringify(jsObj, null, 4); + this.exampleEditor.first.setText(nsrcStr); + } + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-formData-param.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-formData-param.component.html new file mode 100644 index 000000000..f44eb77e6 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-formData-param.component.html @@ -0,0 +1,31 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-formData-param.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-formData-param.component.ts new file mode 100644 index 000000000..8c8ae0b8b --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-formData-param.component.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; + + +@Component({ + moduleId: module.id, + selector: "add-formData-param-dialog", + templateUrl: "add-formData-param.component.html" +}) +export class AddFormDataParamDialogComponent { + + @Output() onAdd: EventEmitter = new EventEmitter(); + + @ViewChildren("addFormDataParamModal") addFormDataParamModal: QueryList; + + protected _isOpen: boolean = false; + + protected name: string = ""; + + /** + * Called to open the dialog. + */ + public open(name?: string): void { + this.name = name; + if (!name) { + this.name = ""; + } + this._isOpen = true; + this.addFormDataParamModal.changes.subscribe( thing => { + if (this.addFormDataParamModal.first) { + this.addFormDataParamModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + this.name = ""; + } + + /** + * Called when the user clicks "add". + */ + protected add(): void { + this.onAdd.emit(this.name); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.addFormDataParamModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-media-type.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-media-type.component.html new file mode 100644 index 000000000..04df9e0d3 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-media-type.component.html @@ -0,0 +1,31 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-media-type.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-media-type.component.ts new file mode 100644 index 000000000..944ec8588 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-media-type.component.ts @@ -0,0 +1,93 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, ElementRef, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; + + +@Component({ + moduleId: module.id, + selector: "add-media-type-dialog", + templateUrl: "add-media-type.component.html" +}) +export class AddMediaTypeDialogComponent { + + @Output() onAdd: EventEmitter = new EventEmitter(); + + @ViewChildren("addMediaTypeModal") addMediaTypeModal: QueryList; + @ViewChildren("addMediaTypeInput") addMediaTypeInput: QueryList; + + protected _isOpen: boolean = false; + + protected mediaType: string = ""; + + /** + * Called to open the dialog. + */ + public open(mediaType?: string): void { + this.mediaType = mediaType; + if (!mediaType) { + this.mediaType = "application/json"; + } + this._isOpen = true; + this.addMediaTypeModal.changes.subscribe( thing => { + if (this.addMediaTypeModal.first) { + this.addMediaTypeModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + this.mediaType = ""; + } + + /** + * Called when the user clicks "add". + */ + protected add(): void { + this.onAdd.emit(this.mediaType); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.addMediaTypeModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + + /** + * Called to initialize the selection/focus to the addMediaTypeInput field. + */ + public doSelect(): void { + this.addMediaTypeInput.first.nativeElement.focus(); + this.addMediaTypeInput.first.nativeElement.selectionStart = this.addMediaTypeInput.first.nativeElement.selectionEnd = this.mediaType.length + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-path.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-path.component.html new file mode 100644 index 000000000..c17792f0b --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-path.component.html @@ -0,0 +1,32 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-path.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-path.component.ts new file mode 100644 index 000000000..73d7dc8ef --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-path.component.ts @@ -0,0 +1,119 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, ElementRef, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; +import {OasDocument, OasLibraryUtils} from "oai-ts-core"; +import {FormControl} from "@angular/forms"; +import {Subject} from "rxjs/Subject"; + + +@Component({ + moduleId: module.id, + selector: "add-path-dialog", + templateUrl: "add-path.component.html" +}) +export class AddPathDialogComponent { + + @Output() onAdd: EventEmitter = new EventEmitter(); + + @ViewChildren("addPathModal") addPathModal: QueryList; + @ViewChildren("addPathInput") addPathInput: QueryList; + + protected _isOpen: boolean = false; + + protected path: string = ""; + + protected pathChanged: Subject = new Subject(); + protected paths: string[] = []; + protected pathExists: boolean = false; + + /** + * Called to open the dialog. + * @param {OasDocument} document + * @param {string} path + */ + public open(document: OasDocument, path?: string): void { + this.path = path; + if (!path) { + this.path = ""; + } + if (!this.path.endsWith("/")) { + this.path = this.path + "/"; + } + this._isOpen = true; + this.addPathModal.changes.subscribe( thing => { + if (this.addPathModal.first) { + this.addPathModal.first.show(); + } + }); + + this.paths = []; + this.pathExists = false; + if (document.paths) { + document.paths.pathItems().forEach( pathItem => { + this.paths.push(pathItem.path()); + }); + this.pathChanged + .debounceTime(300) + .distinctUntilChanged() + .subscribe( path => { + this.pathExists = this.paths.indexOf(path) != -1; + }); + } + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + this.path = ""; + } + + /** + * Called when the user clicks "add". + */ + protected add(): void { + this.onAdd.emit(this.path); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.addPathModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + + /** + * Called to initialize the selection/focus to the addPathInput field. + */ + public doSelect(): void { + this.addPathInput.first.nativeElement.focus(); + this.addPathInput.first.nativeElement.selectionStart = this.addPathInput.first.nativeElement.selectionEnd = this.path.length + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-query-param.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-query-param.component.html new file mode 100644 index 000000000..f0bc6acc1 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-query-param.component.html @@ -0,0 +1,31 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-query-param.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-query-param.component.ts new file mode 100644 index 000000000..70c9332f7 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-query-param.component.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; + + +@Component({ + moduleId: module.id, + selector: "add-query-param-dialog", + templateUrl: "add-query-param.component.html" +}) +export class AddQueryParamDialogComponent { + + @Output() onAdd: EventEmitter = new EventEmitter(); + + @ViewChildren("addQueryParamModal") addQueryParamModal: QueryList; + + protected _isOpen: boolean = false; + + protected name: string = ""; + + /** + * Called to open the dialog. + */ + public open(name?: string): void { + this.name = name; + if (!name) { + this.name = ""; + } + this._isOpen = true; + this.addQueryParamModal.changes.subscribe( thing => { + if (this.addQueryParamModal.first) { + this.addQueryParamModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + this.name = ""; + } + + /** + * Called when the user clicks "add". + */ + protected add(): void { + this.onAdd.emit(this.name); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.addQueryParamModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-response.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-response.component.html new file mode 100644 index 000000000..4d560dd1d --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-response.component.html @@ -0,0 +1,117 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-response.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-response.component.ts new file mode 100644 index 000000000..c0d86edb3 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-response.component.ts @@ -0,0 +1,93 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; + + +@Component({ + moduleId: module.id, + selector: "add-response-dialog", + templateUrl: "add-response.component.html" +}) +export class AddResponseDialogComponent { + + @Output() onAdd: EventEmitter = new EventEmitter(); + + @ViewChildren("addResponseModal") addResponseModal: QueryList; + + protected _isOpen: boolean = false; + + protected statusCode: string = ""; + + /** + * Called to open the dialog. + */ + public open(statusCode?: string): void { + this.statusCode = statusCode; + if (!statusCode) { + this.statusCode = ""; + } + this._isOpen = true; + this.addResponseModal.changes.subscribe( thing => { + if (this.addResponseModal.first) { + this.addResponseModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + this.statusCode = ""; + } + + /** + * Called when the user clicks "add". + */ + protected add(): void { + this.onAdd.emit(this.statusCode); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.addResponseModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + + /** + * Returns true if today is the first of April. (teapot related) + * @return {boolean} + */ + public isAprilFirst(): boolean { + let d: Date = new Date(); + return d.getMonth() === 3 && d.getDate() === 1; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-schema-property.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-schema-property.component.html new file mode 100644 index 000000000..a6d605d06 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-schema-property.component.html @@ -0,0 +1,31 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-schema-property.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-schema-property.component.ts new file mode 100644 index 000000000..57abf8194 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-schema-property.component.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; + + +@Component({ + moduleId: module.id, + selector: "add-schema-property-dialog", + templateUrl: "add-schema-property.component.html" +}) +export class AddSchemaPropertyDialogComponent { + + @Output() onAdd: EventEmitter = new EventEmitter(); + + @ViewChildren("addSchemaPropertyModal") addSchemaPropertyModal: QueryList; + + protected _isOpen: boolean = false; + + protected name: string = ""; + + /** + * Called to open the dialog. + */ + public open(name?: string): void { + this.name = name; + if (!name) { + this.name = ""; + } + this._isOpen = true; + this.addSchemaPropertyModal.changes.subscribe( thing => { + if (this.addSchemaPropertyModal.first) { + this.addSchemaPropertyModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + this.name = ""; + } + + /** + * Called when the user clicks "add". + */ + protected add(): void { + this.onAdd.emit(this.name); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.addSchemaPropertyModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-server.component.css b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-server.component.css new file mode 100644 index 000000000..8795f7a13 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-server.component.css @@ -0,0 +1,3 @@ +#addServerModal .modal-dialog .modal-body form textarea { + min-height: 60px; +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-server.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-server.component.html new file mode 100644 index 000000000..0a8784bac --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-server.component.html @@ -0,0 +1,80 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-server.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-server.component.ts new file mode 100644 index 000000000..b21ba6ac4 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-server.component.ts @@ -0,0 +1,199 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; +import {Oas30Server} from "oai-ts-core"; + + +export interface ServerVariableData { + default: string; + description: string; + enum: string[]; +} + +export interface ServerEventData { + url: string; + description: string; + variables: any; // map of string to ServerVariableData +} + + +@Component({ + moduleId: module.id, + selector: "add-server-dialog", + templateUrl: "add-server.component.html", + styleUrls: ["add-server.component.css"] +}) +export class AddServerDialogComponent { + + @Output() onAdd: EventEmitter = new EventEmitter(); + @Output() onChange: EventEmitter = new EventEmitter(); + + @ViewChildren("addServerModal") addServerModal: QueryList; + + protected mode: string; + + protected _isOpen: boolean = false; + + protected model: ServerEventData = { + url: "", + description: "", + variables: {} + }; + + protected _varSelected: string = null; + + /** + * Called to open the dialog. + * @param {Oas30Server} server + */ + public open(server?: Oas30Server): void { + if (server) { + this.mode = "edit"; + this.model.url = server.url; + this.model.description = server.description; + this.model.variables = {}; + server.getServerVariables().forEach( variable => { + this.model.variables[variable.name()] = { + "default": variable.default, + "description": variable.description, + "enum": variable.enum + }; + }); + this.updateVariables(); + } else { + this.mode = "create"; + this.model = { + url: "", + description: "", + variables: {} + }; + } + this._isOpen = true; + this.addServerModal.changes.subscribe( () => { + if (this.addServerModal.first) { + this.addServerModal.first.show(); + } + }); + } + + /** + * Called when the URL changes. The URL will be parsed to find any variable substitutions + * present in the URL spec. The map of variables is then updated to reflect whatever is found. + */ + public updateVariables(): void { + console.info("[AddServerDialogComponent] Updating variables for URL: %s", this.model.url); + + let url = this.model.url; + if (!url) { + this.model.variables = {}; + } + + console.info(" Executing regex against: %s", url); + let regex:RegExp = /{([^}]+)}/gi; + let result: RegExpExecArray; + let varNames: string[] = []; + while ( (result = regex.exec(url)) ) { + varNames.push(result[1]); + } + console.info(" Variable names found: %o", varNames); + + let newVariables: any = {}; + + varNames.forEach( varName => { + if (this.model.variables[varName]) { + newVariables[varName] = this.model.variables[varName]; + } else { + newVariables[varName] = {}; + } + }); + + this.model.variables = newVariables; + + this._varSelected = null; + if (varNames.length > 0) { + this._varSelected = varNames[0]; + } + } + + /** + * Returns true if there are any variables to be configured. + * @return {boolean} + */ + public hasVariables(): boolean { + let rval: boolean = false; + if (this.model.variables) { + for (let v in this.model.variables) { + rval = true; + } + } + return rval; + } + + /** + * Returns the names of the variables. + * @return {string[]} + */ + public variableNames(): string[] { + let rval: string[] = []; + if (this.model.variables) { + for (let v in this.model.variables) { + rval.push(v); + } + } + return rval; + + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + this.model.url = ""; + this.model.description = ""; + } + + /** + * Called when the user clicks "ok". + */ + protected ok(): void { + if (this.mode === "create") { + this.onAdd.emit(this.model); + } else { + this.onChange.emit(this.model); + } + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.addServerModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-tag.component.css b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-tag.component.css new file mode 100644 index 000000000..90fdf0d08 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-tag.component.css @@ -0,0 +1,3 @@ +#addTagModal .modal-dialog .modal-body form textarea { + min-height: 60px; +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-tag.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-tag.component.html new file mode 100644 index 000000000..c886149ce --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-tag.component.html @@ -0,0 +1,38 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-tag.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-tag.component.ts new file mode 100644 index 000000000..b762fc9b8 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/add-tag.component.ts @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, ElementRef, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; + + +@Component({ + moduleId: module.id, + selector: "add-tag-dialog", + templateUrl: "add-tag.component.html", + styleUrls: ["add-tag.component.css"] +}) +export class AddTagDialogComponent { + + @Output() onAdd: EventEmitter = new EventEmitter(); + + @ViewChildren("addTagModal") addTagModal: QueryList; + @ViewChildren("addTagInput") addTagInput: QueryList; + + protected _isOpen: boolean = false; + + protected tag: string = ""; + protected description: string = ""; + + /** + * Called to open the dialog. + */ + public open(tag?: string): void { + this.tag = tag; + if (!tag) { + this.tag = ""; + } + this._isOpen = true; + this.addTagModal.changes.subscribe( thing => { + if (this.addTagModal.first) { + this.addTagModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + this.tag = ""; + this.description = ""; + } + + /** + * Called when the user clicks "add". + */ + protected add(): void { + let tagInfo: any = { + name: this.tag, + description: this.description + }; + this.onAdd.emit(tagInfo); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.addTagModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + + /** + * Called to initialize the selection/focus to the addTagInput field. + */ + public doSelect(): void { + this.addTagInput.first.nativeElement.focus(); + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/clone-definition.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/clone-definition.component.html new file mode 100644 index 000000000..6e1064f6a --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/clone-definition.component.html @@ -0,0 +1,36 @@ + +
    + +
    diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/clone-definition.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/clone-definition.component.ts new file mode 100644 index 000000000..39d2b61cb --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/clone-definition.component.ts @@ -0,0 +1,119 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; +import {Oas20SchemaDefinition, Oas30SchemaDefinition, OasDocument, OasVisitorUtil} from "oai-ts-core"; +import {Subject} from "rxjs/Subject"; +import {FindSchemaDefinitionsVisitor} from "../../_visitors/schema-definitions.visitor"; + + +@Component({ + moduleId: module.id, + selector: "clone-definition-dialog", + templateUrl: "clone-definition.component.html" +}) +export class CloneDefinitionDialogComponent { + + @Output() onClone: EventEmitter = new EventEmitter(); + + @ViewChildren("cloneDefinitionModal") cloneDefinitionModal: QueryList; + + protected _isOpen: boolean = false; + + protected name: string = ""; + protected definition: Oas20SchemaDefinition | Oas30SchemaDefinition; + + protected defChanged: Subject = new Subject(); + protected defs: string[] = []; + protected defExists: boolean = false; + + /** + * Called to open the dialog. + * @param {Oas20SchemaDefinition | Oas30SchemaDefinition} definition + */ + public open(document: OasDocument, definition: Oas20SchemaDefinition | Oas30SchemaDefinition): void { + this._isOpen = true; + this.definition = definition; + this.name = "CloneOf"; + if (this.definition.ownerDocument().getSpecVersion() === "2.0") { + this.name += (definition as Oas20SchemaDefinition).definitionName(); + } else { + this.name += (definition as Oas30SchemaDefinition).name(); + } + + this.cloneDefinitionModal.changes.subscribe( () => { + if (this.cloneDefinitionModal.first) { + this.cloneDefinitionModal.first.show(); + } + }); + + this.defs = []; + this.defExists = false; + let definitions: (Oas20SchemaDefinition | Oas30SchemaDefinition)[] = this.getDefinitions(document); + definitions.forEach( definition => { + this.defs.push(FindSchemaDefinitionsVisitor.definitionName(definition)); + }); + this.defChanged + .debounceTime(300) + .distinctUntilChanged() + .subscribe( def => { + this.defExists = this.defs.indexOf(def) != -1; + }); + } + + private getDefinitions(document: OasDocument): (Oas20SchemaDefinition | Oas30SchemaDefinition)[] { + let vizzy: FindSchemaDefinitionsVisitor = new FindSchemaDefinitionsVisitor(null); + OasVisitorUtil.visitTree(document, vizzy); + return vizzy.getSortedSchemaDefinitions() + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + } + + /** + * Called when the user clicks "clone". + */ + protected clone(): void { + let data: any = { + name: this.name, + definition: this.definition + }; + this.onClone.emit(data); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.cloneDefinitionModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/clone-path.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/clone-path.component.html new file mode 100644 index 000000000..db083b8ca --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/clone-path.component.html @@ -0,0 +1,32 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/clone-path.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/clone-path.component.ts new file mode 100644 index 000000000..95efaaee4 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/clone-path.component.ts @@ -0,0 +1,121 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, ElementRef, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; +import {OasDocument, OasPathItem} from "oai-ts-core"; +import {Subject} from "rxjs/Subject"; + + +@Component({ + moduleId: module.id, + selector: "clone-path-dialog", + templateUrl: "clone-path.component.html" +}) +export class ClonePathDialogComponent { + + @Output() onClone: EventEmitter = new EventEmitter(); + + @ViewChildren("clonePathModal") clonePathModal: QueryList; + @ViewChildren("clonePathInput") clonePathInput: QueryList; + + protected _isOpen: boolean = false; + + protected path: string = ""; + protected object: OasPathItem; + + protected pathChanged: Subject = new Subject(); + protected paths: string[] = []; + protected pathExists: boolean = false; + + /** + * Called to open the dialog. + * @param {OasDocument} document + * @param {OasPathItem} path + */ + public open(document: OasDocument, path: OasPathItem): void { + this.object = path; + this.path = path.path(); + if (!this.path.endsWith("/")) { + this.path = this.path + "/"; + } + this._isOpen = true; + this.clonePathModal.changes.subscribe( () => { + if (this.clonePathModal.first) { + this.clonePathModal.first.show(); + } + }); + + this.paths = []; + this.pathExists = false; + if (document.paths) { + document.paths.pathItems().forEach( pathItem => { + this.paths.push(pathItem.path()); + }); + this.pathChanged + .debounceTime(300) + .distinctUntilChanged() + .subscribe( path => { + this.pathExists = this.paths.indexOf(path) != -1; + }); + } + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + this.path = ""; + } + + /** + * Called when the user clicks "clone". + */ + protected clone(): void { + let modalData: any = { + path: this.path, + object: this.object + }; + this.onClone.emit(modalData); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.clonePathModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + + /** + * Called to initialize the selection/focus to the clonePathInput field. + */ + public doSelect(): void { + this.clonePathInput.first.nativeElement.focus(); + this.clonePathInput.first.nativeElement.selectionStart = this.clonePathInput.first.nativeElement.selectionEnd = this.path.length + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/editor-disconnected.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/editor-disconnected.component.html new file mode 100644 index 000000000..5f48fae49 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/editor-disconnected.component.html @@ -0,0 +1,23 @@ + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/editor-disconnected.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/editor-disconnected.component.ts new file mode 100644 index 000000000..f8d0deded --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/editor-disconnected.component.ts @@ -0,0 +1,67 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, ViewChildren, QueryList} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; + + +@Component({ + moduleId: module.id, + selector: "editor-disconnected-dialog", + templateUrl: "editor-disconnected.component.html" +}) +export class EditorDisconnectedDialogComponent { + + @ViewChildren("editorDisconnectedModal") editorDisconnectedModal: QueryList; + + protected _isOpen: boolean = false; + + /** + * Called to open the dialog. + */ + public open(): void { + this._isOpen = true; + this.editorDisconnectedModal.changes.subscribe( thing => { + if (this.editorDisconnectedModal.first) { + this.editorDisconnectedModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + } + + /** + * Called when the user clicks "Reload Page". + */ + protected reload(): void { + window.location.reload(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/security-scheme-20.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/security-scheme-20.component.html new file mode 100644 index 000000000..008fb565d --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/security-scheme-20.component.html @@ -0,0 +1,165 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/security-scheme-20.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/security-scheme-20.component.ts new file mode 100644 index 000000000..026ae0681 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/security-scheme-20.component.ts @@ -0,0 +1,233 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Output, EventEmitter, ViewChildren, QueryList} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; +import {Oas20Scopes, Oas20SecurityScheme} from "oai-ts-core"; +import {ObjectUtils} from "../../_util/object.util"; + + +export interface Scope { + name: string; + description: string; +} + + +export interface SecurityScheme20EventData { + schemeName: string; + description: string; + type: string; + name: string; + in: string; + flow: string; + authorizationUrl: string; + tokenUrl: string; + scopes: Scope[] +} + + + +@Component({ + moduleId: module.id, + selector: "security-scheme-20-dialog", + templateUrl: "security-scheme-20.component.html" +}) +export class SecurityScheme20DialogComponent { + + @Output() onSchemeAdded: EventEmitter = new EventEmitter(); + @Output() onSchemeChanged: EventEmitter = new EventEmitter(); + + @ViewChildren("securitySchemeModal") securitySchemeModal: QueryList; + + protected _isOpen: boolean = false; + + protected model: SecurityScheme20EventData; + protected mode: string; + + /** + * Called to open the dialog. + */ + public open(scheme?: Oas20SecurityScheme): void { + this._isOpen = true; + this.model = { + schemeName: null, + description: null, + type: "basic", + name: null, + in: null, + flow: null, + authorizationUrl: null, + tokenUrl: null, + scopes: [] + }; + this.mode = "create"; + if (scheme) { + this.model.schemeName = scheme.schemeName(); + this.model.description = scheme.description; + this.model.type = scheme.type; + this.model.in = scheme.in; + this.model.name = scheme.name; + this.model.flow = scheme.flow; + this.model.authorizationUrl = scheme.authorizationUrl; + this.model.tokenUrl = scheme.tokenUrl; + this.model.scopes = this.toScopesArray(scheme.scopes); + this.mode = "edit"; + } + + this.securitySchemeModal.changes.subscribe( thing => { + if (this.securitySchemeModal.first) { + this.securitySchemeModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + } + + /** + * Called when the user clicks "add". + */ + protected ok(): void { + if (this.mode === "create") { + this.onSchemeAdded.emit(this.model); + } else { + this.onSchemeChanged.emit(this.model); + } + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.securitySchemeModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + + /** + * Sets the type. + * @param type + */ + public setType(type: string): void { + this.model.type = type; + + this.model.name = null; + this.model.in = null; + this.model.flow = null; + this.model.authorizationUrl = null; + this.model.tokenUrl = null; + this.model.scopes = []; + + if (type === "basic") { + } + if (type === "apiKey") { + this.model.in = "header"; + } + if (type === "oauth2") { + this.model.flow = "implicit"; + } + } + + /** + * Sets the flow. + * @param flow + */ + public setFlow(flow: string): void { + this.model.flow = flow; + if (flow === "implicit") { + this.model.tokenUrl = null; + } + if (flow === "password") { + this.model.authorizationUrl = null; + } + if (flow === "accessCode") { + } + if (flow === "application") { + this.model.authorizationUrl = null; + } + } + + /** + * Gets the array of scopes. + */ + public scopes(): Scope[] { + return this.model.scopes; + } + + /** + * Called when the user clicks the "Add Scope" button. + */ + public addScope(): void { + this.model.scopes.push({ + name: "", + description: "" + }); + } + + /** + * Called to delete a scope. + * @param scope + */ + public deleteScope(scope: Scope): void { + this.model.scopes.splice(this.model.scopes.indexOf(scope), 1); + } + + /** + * Converts from OAS20 scopes to an array of scope objects. + * @param scopes + * @return {Scope[]} + */ + private toScopesArray(scopes: Oas20Scopes): Scope[] { + let rval: Scope[] = []; + if (scopes) { + for (let sk of scopes.scopes()) { + let sd: string = scopes.getScopeDescription(sk); + rval.push({ + name: sk, + description: sd + }); + } + } + return rval; + } + + /** + * Returns true only if all the defined scopes are valid (have names). + * @return {boolean} + */ + public scopesAreValid(): boolean { + if (this.model.type === "oauth2") { + for (let scope of this.model.scopes) { + if (ObjectUtils.isNullOrUndefined(scope.name) || scope.name.length === 0) { + return false; + } + } + } + return true; + } +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/security-scheme-30.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/security-scheme-30.component.html new file mode 100644 index 000000000..05011bb45 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/security-scheme-30.component.html @@ -0,0 +1,403 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/security-scheme-30.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/security-scheme-30.component.ts new file mode 100644 index 000000000..a47cde683 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/security-scheme-30.component.ts @@ -0,0 +1,280 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; +import {Oas30OAuthFlow, Oas30SecurityScheme} from "oai-ts-core"; +import {Scope} from "./security-scheme-20.component"; +import {ObjectUtils} from "../../_util/object.util"; + + +export interface Flow { + enabled: boolean; + authorizationUrl: string; + tokenUrl: string; + refreshUrl: string; + scopes: Scope[]; +} + +export interface Flows { + implicit: Flow; + password: Flow; + clientCredentials: Flow; + authorizationCode: Flow; +} + + +export interface SecurityScheme30EventData { + schemeName: string; + // apiKey, http, oauth2, openIdConnect + type: string; + description: string; + // *apiKey* + name: string; + // *apiKey* - query, header, cookie + in: string; + // *http* - Basic, Bearer, Digest + scheme: string; + // *http* - JWT, OAuth + bearerFormat: string; + // *openIdConnect* + openIdConnectUrl: string; + // *oauth2* - implicit, password, clientCredentials, authorizationCode + flows: Flows; +} + + + +@Component({ + moduleId: module.id, + selector: "security-scheme-30-dialog", + templateUrl: "security-scheme-30.component.html" +}) +export class SecurityScheme30DialogComponent { + + @Output() onSchemeAdded: EventEmitter = new EventEmitter(); + @Output() onSchemeChanged: EventEmitter = new EventEmitter(); + + @ViewChildren("securitySchemeModal") securitySchemeModal: QueryList; + + protected _isOpen: boolean = false; + + protected model: SecurityScheme30EventData; + protected mode: string; + protected oauthTab: string; + + /** + * Called to open the dialog. + */ + public open(scheme?: Oas30SecurityScheme): void { + this._isOpen = true; + this.oauthTab = "implicit"; + this.model = { + schemeName: null, + type: null, + description: null, + name: null, + in: null, + scheme: null, + bearerFormat: null, + openIdConnectUrl: null, + flows: { + implicit: { + enabled: false, + authorizationUrl: null, + tokenUrl: null, + refreshUrl: null, + scopes: [] + }, + password: { + enabled: false, + authorizationUrl: null, + tokenUrl: null, + refreshUrl: null, + scopes: [] + }, + clientCredentials: { + enabled: false, + authorizationUrl: null, + tokenUrl: null, + refreshUrl: null, + scopes: [] + }, + authorizationCode: { + enabled: false, + authorizationUrl: null, + tokenUrl: null, + refreshUrl: null, + scopes: [] + } + } + }; + this.mode = "create"; + if (scheme) { + this.model.schemeName = scheme.schemeName(); + this.model.description = scheme.description; + this.model.type = scheme.type; + this.model.in = scheme.in; + this.model.name = scheme.name; + this.model.scheme = scheme.scheme; + this.model.bearerFormat = scheme.bearerFormat; + this.model.openIdConnectUrl = scheme.openIdConnectUrl; + this.readFlows(scheme); + this.mode = "edit"; + + if (!ObjectUtils.isNullOrUndefined(scheme.flows)) { + if (!ObjectUtils.isNullOrUndefined(scheme.flows.authorizationCode)) { + this.oauthTab = "authorizationCode"; + } + if (!ObjectUtils.isNullOrUndefined(scheme.flows.clientCredentials)) { + this.oauthTab = "clientCredentials"; + } + if (!ObjectUtils.isNullOrUndefined(scheme.flows.password)) { + this.oauthTab = "password"; + } + if (!ObjectUtils.isNullOrUndefined(scheme.flows.implicit)) { + this.oauthTab = "implicit"; + } + } + + } + + this.securitySchemeModal.changes.subscribe( () => { + if (this.securitySchemeModal.first) { + this.securitySchemeModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + } + + /** + * Called when the user clicks "add". + */ + protected ok(): void { + if (this.mode === "create") { + this.onSchemeAdded.emit(this.model); + } else { + this.onSchemeChanged.emit(this.model); + } + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.securitySchemeModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + + /** + * Sets the type. + * @param type + */ + public setType(type: string): void { + this.model.type = type; + } + + /** + * Called when the user clicks the "Add Scope" button. + * @param {string} flow + */ + public addScope(flow: string): void { + this.model.flows[flow].scopes.push({ + name: "", + description: "" + }); + } + + /** + * Called to delete a scope. + * @param {string} flow + * @param {Scope} scope + */ + public deleteScope(flow: string, scope: Scope): void { + this.model.flows[flow].scopes.splice(this.model.flows[flow].scopes.indexOf(scope), 1); + } + + /** + * Reads the flow information from the security scheme and copies it to the model. + * @param {Oas30SecurityScheme} scheme + */ + private readFlows(scheme: Oas30SecurityScheme) { + if (!ObjectUtils.isNullOrUndefined(scheme.flows)) { + if (!ObjectUtils.isNullOrUndefined(scheme.flows.implicit)) { + this.readFlowInto(scheme.flows.implicit, this.model.flows.implicit); + } + if (!ObjectUtils.isNullOrUndefined(scheme.flows.password)) { + this.readFlowInto(scheme.flows.password, this.model.flows.password); + } + if (!ObjectUtils.isNullOrUndefined(scheme.flows.authorizationCode)) { + this.readFlowInto(scheme.flows.authorizationCode, this.model.flows.authorizationCode); + } + if (!ObjectUtils.isNullOrUndefined(scheme.flows.clientCredentials)) { + this.readFlowInto(scheme.flows.clientCredentials, this.model.flows.clientCredentials); + } + } + } + + /** + * Reads flow information from the data model into the local UI model. + * @param {Oas30OAuthFlow} flowModel + * @param {Flow} flow + */ + private readFlowInto(flowModel: Oas30OAuthFlow, flow: Flow) { + flow.enabled = true; + flow.authorizationUrl = flowModel.authorizationUrl; + flow.tokenUrl = flowModel.tokenUrl; + flow.refreshUrl = flowModel.refreshUrl; + flow.scopes = this.toScopesArray(flowModel.scopes); + } + + /** + * Converts from OAS30 scopes to an array of scope objects. + * @param scopes + * @return {Scope[]} + */ + private toScopesArray(scopes: any): Scope[] { + console.info("toScopesArray: %o", scopes); + let rval: Scope[] = []; + if (scopes) { + for (let sk in scopes) { + console.info(" " + sk); + let sd: string = scopes[sk] + rval.push({ + name: sk, + description: sd + }); + } + } + return rval; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/set-contact.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/set-contact.component.html new file mode 100644 index 000000000..a5c571338 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/set-contact.component.html @@ -0,0 +1,45 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/set-contact.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/set-contact.component.ts new file mode 100644 index 000000000..442c5f26c --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/set-contact.component.ts @@ -0,0 +1,104 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Output, QueryList, ViewChildren} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; +import {OasContact} from "oai-ts-core"; + + +export class ContactInfo { + public name: string; + public email: string; + public url: string; +} + + +@Component({ + moduleId: module.id, + selector: "set-contact-dialog", + templateUrl: "set-contact.component.html" +}) +export class SetContactDialogComponent { + + @Output() onContact: EventEmitter = new EventEmitter(); + + @ViewChildren("setContactModal") setContactModal: QueryList; + + protected _isOpen: boolean = false; + + protected name: string = ""; + protected email: string = ""; + protected url: string = ""; + + /** + * Called to open the dialog. + * @param {OasContact} contact + */ + public open(contact?: OasContact): void { + this._isOpen = true; + this.name = ""; + this.email = ""; + this.url = ""; + if (contact) { + this.name = contact.name; + this.email = contact.email; + this.url = contact.url; + } + + this.setContactModal.changes.subscribe( thing => { + if (this.setContactModal.first) { + this.setContactModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + } + + /** + * Called when the user clicks "add". + */ + protected ok(): void { + let contact: ContactInfo = { + name: this.name, + email: this.email, + url: this.url + }; + this.onContact.emit(contact); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.setContactModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/set-license.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/set-license.component.html new file mode 100644 index 000000000..44fe82407 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/set-license.component.html @@ -0,0 +1,52 @@ + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/set-license.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/set-license.component.ts new file mode 100644 index 000000000..596b5a4f5 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/dialogs/set-license.component.ts @@ -0,0 +1,106 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Output, EventEmitter, ViewChildren, QueryList} from "@angular/core"; +import {ModalDirective} from "ngx-bootstrap"; +import {LicenseService, ILicense} from "../../_services/license.service"; + + +@Component({ + moduleId: module.id, + selector: "set-license-dialog", + templateUrl: "set-license.component.html" +}) +export class SetLicenseDialogComponent { + + private static licenseService: LicenseService = new LicenseService(); + + @Output() onLicenseChosen: EventEmitter = new EventEmitter(); + + @ViewChildren("setLicenseModal") setLicenseModal: QueryList; + + protected _isOpen: boolean = false; + + /** + * Called to open the dialog. + */ + public open(): void { + this._isOpen = true; + this.setLicenseModal.changes.subscribe( thing => { + if (this.setLicenseModal.first) { + this.setLicenseModal.first.show(); + } + }); + } + + /** + * Called to close the dialog. + */ + public close(): void { + this._isOpen = false; + } + + /** + * Called when the user clicks "add". + */ + protected setLicense(): void { + this.onLicenseChosen.emit({}); + this.cancel(); + } + + /** + * Called when the user clicks "cancel". + */ + protected cancel(): void { + this.setLicenseModal.first.hide(); + } + + /** + * Returns true if the dialog is open. + * @return {boolean} + */ + public isOpen(): boolean { + return this._isOpen; + } + + /** + * Returns a list of possible licenses. + */ + public licenses(): ILicense[] { + return SetLicenseDialogComponent.licenseService.getLicenses(); + } + + /** + * Returns the license service. + */ + public licenseService(): LicenseService { + return SetLicenseDialogComponent.licenseService; + } + + /** + * Called when the user picks a license. + * @param license + */ + public chooseLicense(license: ILicense): void { + this.onLicenseChosen.emit({ + name: license.name, + url: license.url + }); + this.cancel(); + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition-form.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition-form.component.html new file mode 100644 index 000000000..9f80d0405 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition-form.component.html @@ -0,0 +1,104 @@ +
    +
    + + {{ definitionName() }} +
    + +
    +
    + +
    + + +
    + + + + +
    +
    + +
    + + +
    +
    +
    +
    + + +
    +
    + + + + +

    + Use this section to define the properties that make up this definition. +

    +
    + + +
    +
    + +
    + + None Found + No schema properties have been defined. + +
    + + +
    + +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition-form.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition-form.component.ts new file mode 100644 index 000000000..7b1318be8 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition-form.component.ts @@ -0,0 +1,152 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Component, EventEmitter, Input, Output, ViewChild, ViewEncapsulation} from "@angular/core"; +import {Oas20Document, Oas20SchemaDefinition, Oas30Document, Oas30SchemaDefinition, OasSchema} from "oai-ts-core"; +import { + createAddSchemaDefinitionCommand, + createChangePropertyCommand, + createChangePropertyTypeCommand, + createDeleteAllPropertiesCommand, + createDeletePropertyCommand, + createDeleteSchemaDefinitionCommand, + createNewSchemaPropertyCommand, + createReplaceSchemaDefinitionCommand, ICommand, + SimplifiedPropertyType +} from "oai-ts-commands"; + +import "brace/theme/eclipse"; +import "brace/mode/json"; +import {SourceFormComponent} from "./source-form.base"; +import {AddSchemaPropertyDialogComponent} from "../dialogs/add-schema-property.component"; +import {CloneDefinitionDialogComponent} from "../dialogs/clone-definition.component"; + + +@Component({ + moduleId: module.id, + selector: "definition-form", + templateUrl: "definition-form.component.html", + encapsulation: ViewEncapsulation.None +}) +export class DefinitionFormComponent extends SourceFormComponent { + + private _definition: Oas20SchemaDefinition | Oas30SchemaDefinition; + @Input() + set definition(definition: Oas20SchemaDefinition | Oas30SchemaDefinition) { + this._definition = definition; + this.sourceNode = definition; + } + + get definition(): Oas20SchemaDefinition | Oas30SchemaDefinition { + return this._definition; + } + + @Output() onDeselect: EventEmitter = new EventEmitter(); + + @ViewChild("addSchemaPropertyDialog") public addSchemaPropertyDialog: AddSchemaPropertyDialogComponent; + @ViewChild("cloneDefinitionDialog") cloneDefinitionDialog: CloneDefinitionDialogComponent; + + public definitionName(): string { + if (this.definition.ownerDocument().getSpecVersion() === "2.0") { + return (this.definition as Oas20SchemaDefinition).definitionName(); + } else { + return (this.definition as Oas30SchemaDefinition).name(); + } + } + + protected createEmptyNodeForSource(): Oas20SchemaDefinition | Oas30SchemaDefinition { + if (this.definition.ownerDocument().getSpecVersion() === "2.0") { + return (this.definition.ownerDocument() as Oas20Document).definitions.createSchemaDefinition(this.definitionName()); + } else { + return (this.definition.ownerDocument() as Oas30Document).components.createSchemaDefinition(this.definitionName()); + } + } + + protected createReplaceNodeCommand(node: Oas20SchemaDefinition | Oas30SchemaDefinition): ICommand { + return createReplaceSchemaDefinitionCommand(node.ownerDocument(), this.definition, node); + } + + public openAddSchemaPropertyModal(): void { + this.addSchemaPropertyDialog.open(); + } + + public hasProperties(): boolean { + return this.properties().length > 0; + } + + public properties(): OasSchema[] { + let rval: OasSchema[] = []; + this.definition.propertyNames().sort((left, right) => { + return left.localeCompare(right); + }).forEach(name => rval.push(this.definition.property(name))); + + return rval; + } + + public changePropertyDescription(property: OasSchema, newDescription: string): void { + let command: ICommand = createChangePropertyCommand(property.ownerDocument(), property, "description", newDescription); + this.onCommand.emit(command); + } + + public changePropertyType(property: OasSchema, newType: SimplifiedPropertyType): void { + let command: ICommand = createChangePropertyTypeCommand(property.ownerDocument(), property as any, newType); + this.onCommand.emit(command); + } + + public deleteProperty(property: OasSchema): void { + let command: ICommand = createDeletePropertyCommand(property.ownerDocument(), property as any); + this.onCommand.emit(command); + } + + public addSchemaProperty(name: string): void { + let command: ICommand = createNewSchemaPropertyCommand(this.definition.ownerDocument(), this.definition, name); + this.onCommand.emit(command); + } + + public deleteAllSchemaProperties(): void { + let command: ICommand = createDeleteAllPropertiesCommand(this.definition.ownerDocument(), this.definition); + this.onCommand.emit(command); + } + + public delete(): void { + console.info("[DefinitionFormComponent] Deleting schema definition."); + let command: ICommand = createDeleteSchemaDefinitionCommand(this.definition.ownerDocument(), this.definitionName()); + this.onCommand.emit(command); + console.info("AAA"); + this.onDeselect.emit(true); + } + + public clone(modalData?: any): void { + if (undefined === modalData || modalData === null) { + this.cloneDefinitionDialog.open(this.definition.ownerDocument(), this.definition); + } else { + let definition: Oas20SchemaDefinition | Oas30SchemaDefinition = modalData.definition; + console.info("[DefinitionFormComponent] Clone definition: %s", modalData.name); + let cloneSrcObj: any = this.oasLibrary().writeNode(definition); + let command: ICommand = createAddSchemaDefinitionCommand(this.definition.ownerDocument(), modalData.name, cloneSrcObj); + this.onCommand.emit(command); + } + } + + public formType(): string { + return "definition"; + } + + public enableSourceMode(): void { + this.sourceNode = this.definition; + super.enableSourceMode(); + } +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition-item.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition-item.component.html new file mode 100644 index 000000000..449552fab --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition-item.component.html @@ -0,0 +1,2 @@ + +{{ name }} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition-item.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition-item.component.ts new file mode 100644 index 000000000..941f316f1 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition-item.component.ts @@ -0,0 +1,31 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Input, ViewEncapsulation} from "@angular/core"; + + +@Component({ + moduleId: module.id, + selector: "[definition-item]", + templateUrl: "definition-item.component.html", + encapsulation: ViewEncapsulation.None +}) +export class DefinitionItemComponent { + + @Input() name: string; + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition/property-row.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition/property-row.component.html new file mode 100644 index 000000000..6f56e21af --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition/property-row.component.html @@ -0,0 +1,56 @@ +
    +
    +
    {{ property.propertyName() }}
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition/property-row.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition/property-row.component.ts new file mode 100644 index 000000000..914e40066 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/definition/property-row.component.ts @@ -0,0 +1,123 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Input, ViewEncapsulation} from "@angular/core"; +import {SimplifiedPropertyType} from "oai-ts-commands"; +import { + Oas20PropertySchema, + Oas20SchemaDefinition, + Oas30PropertySchema, + Oas30SchemaDefinition, + OasVisitorUtil +} from "oai-ts-core"; +import {AbstractTypedItemComponent} from "../operation/typed-item.component"; +import {FindSchemaDefinitionsVisitor} from "../../../_visitors/schema-definitions.visitor"; +import {DropDownOption} from '../../../../../../../components/common/drop-down.component'; + + +@Component({ + moduleId: module.id, + selector: "property-row", + templateUrl: "property-row.component.html", + encapsulation: ViewEncapsulation.None +}) +export class PropertyRowComponent extends AbstractTypedItemComponent { + + @Input() property: Oas20PropertySchema | Oas30PropertySchema; + @Input() propertyClass: string = ""; + @Input() canDelete: boolean = true; + + protected modelForEditing(): SimplifiedPropertyType { + return SimplifiedPropertyType.fromPropertySchema(this.property); + } + + protected modelForViewing(): SimplifiedPropertyType { + return SimplifiedPropertyType.fromPropertySchema(this.property); + } + + public isRequired(): boolean { + let required: string[] = this.property.parent()["required"]; + if (required && required.length > 0) { + return required.indexOf(this.property.propertyName()) != -1; + } + return false; + } + + public required(): string { + return this.model.required ? "required" : "not-required"; + } + + public requiredOptions(): DropDownOption[] { + return [ + { name: "Required", value: "required" }, + { name: "Not Required", value: "not-required" } + ]; + } + + public changeRequired(newValue: string): void { + this.model.required = newValue === "required"; + } + + public typeOptions(): DropDownOption[] { + let options: DropDownOption[] = super.typeOptions(); + let refPrefix: string = "#/components/schemas/"; + if (this.property.ownerDocument().getSpecVersion() === "2.0") { + refPrefix = "#/definitions/"; + } + + let viz: FindSchemaDefinitionsVisitor = new FindSchemaDefinitionsVisitor(null); + OasVisitorUtil.visitTree(this.property.ownerDocument(), viz); + let defs: (Oas20SchemaDefinition | Oas30SchemaDefinition)[] = viz.getSortedSchemaDefinitions(); + if (defs.length > 0) { + options.push({ divider: true }); + defs.forEach( def => { + let defName: string = (def.ownerDocument().getSpecVersion() === "2.0") ? (def as Oas20SchemaDefinition).definitionName() : (def as Oas30SchemaDefinition).name(); + options.push({ + value: refPrefix + defName, + name: defName + }); + }); + } + + return options; + } + + public typeOfOptions(): DropDownOption[] { + let options: DropDownOption[] = super.typeOfOptions(); + let refPrefix: string = "#/components/schemas/"; + if (this.property.ownerDocument().getSpecVersion() === "2.0") { + refPrefix = "#/definitions/"; + } + + let viz: FindSchemaDefinitionsVisitor = new FindSchemaDefinitionsVisitor(null); + OasVisitorUtil.visitTree(this.property.ownerDocument(), viz); + let defs: (Oas20SchemaDefinition | Oas30SchemaDefinition)[] = viz.getSortedSchemaDefinitions(); + if (defs.length > 0) { + options.push({ divider: true }); + defs.forEach( def => { + let defName: string = (def.ownerDocument().getSpecVersion() === "2.0") ? (def as Oas20SchemaDefinition).definitionName() : (def as Oas30SchemaDefinition).name(); + options.push({ + value: refPrefix + defName, + name: defName + }); + }); + } + + return options; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/main-form.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/main-form.component.html new file mode 100644 index 000000000..6b2da05e8 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/main-form.component.html @@ -0,0 +1,279 @@ +
    +
    + +
    + +
    + + +
    +
    + + + + +

    This section allows you to modify the Name, Version, and Description of the API.

    +
    +
    +
    +
    + +   + +
    + +
    +
    + + +
    +
    + + + + +

    + Update the information contained in this section to provide users of the API details + about how to contact you should they need help. +

    +
    + +
    +
    + +
    + + None Found + No contact info has been configured for this API. + +
    + +
    +

    + For more information about this API, users can contact {{ contactName() }} or visit {{ contactUrl() }}. +

    + +
    + +
    +
    + + +
    +
    + + + + +

    + Is your API licensed? + Configure that here! We'll help by showing you the most common licenses + you can choose from (and even describe each one a little bit!). +

    +
    + +
    +
    + +
    + + None Found + No license has been configured for this API. + +
    + +
    +
    +
    +

    {{ licenseName() }}

    +

    You have configured a license that we are not familiar with. For information about the license, click the link above! Or else click the button below to choose a different license...

    + +
    +
    +
    + +
    +
    +
    +

    {{ license().name }}

    +

    {{ license().description }}

    + + View full {{ license().fullName }} » +
    +
    +

    Permissions

    +
      +
    • {{ licenseService().permissionName(permission) }}
    • +
    +
    +
    +

    Conditions

    +
      +
    • {{ licenseService().conditionName(condition) }}
    • +
    +
    +
    +

    Limitations

    +
      +
    • {{ licenseService().limitationName(limitation) }}
    • +
    +
    +
    +
    + +
    +
    + + +
    +
    + + + + +

    + Configure tags for your API in this section. Once defined, tags can be used to organize + your API's operations by arbitrary criteria. +

    +
    + +
    +
    +
    + + None Found + No tags have been configured. + +
    + + + + + + + + + + + + + + + +
    TagDescription
    {{ tag.name }} +
    + +
    +
    +
    +
    + + + + + +
    +
    + + + + +

    + Use this section to configure your API's security options. There are three types of + security you may choose from (each with its own specific configuration details): +

    + +
    + +
    +
    + +
    + + None Found + No security options have been configured for this API. + +
    + + + + + + + + + + + + + + + + + + +
    NameSecurity TypeDescription
    {{ scheme.schemeName() }}{{ scheme.type }} +
    + + +
    +
    +
    +
    + + + + + + + +
    \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/main-form.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/main-form.component.ts new file mode 100644 index 000000000..03ece1b8c --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/main-form.component.ts @@ -0,0 +1,616 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Input, Output, ViewChild, ViewEncapsulation} from "@angular/core"; +import { + Oas20Document, + Oas20SecurityDefinitions, + Oas20SecurityScheme, + Oas30Document, + Oas30SecurityScheme, + OasContact, + OasDocument, + OasSecurityScheme, + OasTag +} from "oai-ts-core"; +import { + createChangeContactCommand, + createChangeDescriptionCommand, + createChangeLicenseCommand, + createChangePropertyCommand, + createChangeSecuritySchemeCommand, + createChangeTitleCommand, + createChangeVersionCommand, + createDeleteContactCommand, + createDeleteLicenseCommand, + createDeleteSecuritySchemeCommand, + createDeleteTagCommand, + createNewSecuritySchemeCommand, + createNewTagCommand, + ICommand +} from "oai-ts-commands"; +import {ILicense, LicenseService} from "../../_services/license.service"; +import { + Scope, + SecurityScheme20DialogComponent, + SecurityScheme20EventData +} from "../dialogs/security-scheme-20.component"; +import {ObjectUtils} from "../../_util/object.util"; +import {ContactInfo} from "../dialogs/set-contact.component"; +import {SecurityScheme30DialogComponent, SecurityScheme30EventData} from "../dialogs/security-scheme-30.component"; + + +export abstract class MainFormComponent { + + // TODO should be injected rather than instantiated? + private static licenseService: LicenseService = new LicenseService(); + + @Input() document: OasDocument; + @Output() onCommand: EventEmitter = new EventEmitter(); + + /** + * returns the title. + */ + public title(): string { + if (this.document.info) { + return this.document.info.title; + } else { + return null; + } + } + + /** + * returns the version. + */ + public version(): string { + if (this.document.info) { + return this.document.info.version; + } else { + return null; + } + } + + /** + * returns the description. + */ + public description(): string { + if (this.document.info) { + return this.document.info.description; + } else { + return null; + } + } + + /** + * returns the terms of service. + */ + public tos(): string { + if (this.document.info) { + return this.document.info.termsOfService; + } else { + return ""; + } + } + + /** + * Returns the current contact object. + * @return {OasContact} + */ + public contact(): OasContact { + if (this.hasContact()) { + return this.document.info.contact; + } else { + return new OasContact(); + } + } + + /** + * returns the contact name. + */ + public contactName(): string { + if (this.document.info && this.document.info.contact && this.document.info.contact.name) { + return this.document.info.contact.name; + } else { + return this.contactEmail(); + } + } + + /** + * returns the contact email. + */ + public contactEmail(): string { + if (this.document.info && this.document.info.contact && this.document.info.contact.email) { + return this.document.info.contact.email; + } else { + return ""; + } + } + + /** + * returns the contact url. + */ + public contactUrl(): string { + if (this.document.info && this.document.info.contact) { + return this.document.info.contact.url; + } else { + return ""; + } + } + + /** + * Called when the user changes the title. + * @param newTitle + */ + public onTitleChange(newTitle: string): void { + console.info("[MainFormComponent] User changed the title to: " + newTitle); + let command: ICommand = createChangeTitleCommand(this.document, newTitle); + this.onCommand.emit(command); + } + + /** + * Called when the user changes the title. + * @param newVersion + */ + public onVersionChange(newVersion: string): void { + console.info("[MainFormComponent] User changed the version to: " + newVersion); + let command: ICommand = createChangeVersionCommand(this.document, newVersion); + this.onCommand.emit(command); + } + + /** + * Called when the user changes the description. + * @param newDescription + */ + public onDescriptionChange(newDescription: string): void { + console.info("[MainFormComponent] User changed the description."); + let command: ICommand = createChangeDescriptionCommand(this.document, newDescription); + this.onCommand.emit(command); + } + + /** + * Returns the list of tags defined in the document. + * @return {OasTag[]} + */ + public tags(): OasTag[] { + let tags: OasTag[] = this.document.tags; + if (ObjectUtils.isNullOrUndefined(tags)) { + tags = []; + } + // Clone the array + tags = tags.slice(0); + // Sort it + tags.sort( (obj1, obj2) => { + return obj1.name.toLowerCase().localeCompare(obj2.name.toLowerCase()); + }); + return tags; + } + + /** + * Called when the user changes the description of a tag. + * @param tag + * @param description + */ + public changeTagDescription(tag: OasTag, description: string): void { + let command: ICommand = createChangePropertyCommand(this.document, tag, "description", description); + this.onCommand.emit(command); + } + + /** + * Called when the user chooses to delete a tag. + * @param tag + */ + public deleteTag(tag: OasTag): void { + let command: ICommand = createDeleteTagCommand(this.document, tag.name); + this.onCommand.emit(command); + } + + /** + * Called when the user clicks 'Add' on the Add Tag modal dialog. + * @param tag + */ + public addTag(tag: any): void { + let command: ICommand = createNewTagCommand(this.document, tag.name, tag.description); + this.onCommand.emit(command); + } + + /** + * Returns true if a license has been configured for this API. + */ + public hasLicense(): boolean { + if (this.document.info && this.document.info.license) { + return true; + } + return false; + } + + /** + * Returns the resolved license or null if not found. + */ + public license(): ILicense { + return MainFormComponent.licenseService.findLicense(this.licenseUrl()); + } + + /** + * returns the license name. + */ + public licenseName(): string { + if (this.document.info && this.document.info.license) { + return this.document.info.license.name; + } else { + return ""; + } + } + + /** + * returns the license url. + */ + public licenseUrl(): string { + if (this.document.info && this.document.info.license) { + return this.document.info.license.url; + } else { + return ""; + } + } + + /** + * Returns the license service. + * @return {LicenseService} + */ + public licenseService(): LicenseService { + return MainFormComponent.licenseService; + } + + /** + * Called when the user chooses a new license in the Choose License dialog. + * @param licenseInfo + */ + public setLicense(licenseInfo: any): void { + let command: ICommand = createChangeLicenseCommand(this.document, licenseInfo.name, licenseInfo.url); + this.onCommand.emit(command); + } + + /** + * Returns true if the API has Contact Info defined. + * @return {boolean} + */ + public hasContact(): boolean { + if (this.document.info && this.document.info.contact) { + if (this.document.info.contact.email || this.document.info.contact.url) { + return true; + } + } + return false; + } + + /** + * Called to change the document's contact information. + * @param contactInfo + */ + public setContactInfo(contactInfo: ContactInfo): void { + let command: ICommand = createChangeContactCommand(this.document, contactInfo.name, contactInfo.email, contactInfo.url); + this.onCommand.emit(command); + } + + /** + * Called when the user chooses to remove the contact info. + */ + public deleteContact(): void { + let command: ICommand = createDeleteContactCommand(this.document); + this.onCommand.emit(command); + } + + /** + * Called when the user chooses to remove the license. + */ + public deleteLicense(): void { + let command: ICommand = createDeleteLicenseCommand(this.document); + this.onCommand.emit(command); + } + + /** + * Returns true if there is at least one security scheme defined. + * @return {boolean} + */ + public hasSecurity(): boolean { + return this.securitySchemes().length > 0; + } + + /** + * Returns all defined security schemes. + * @return {OasSecurityScheme[]} + */ + public abstract securitySchemes(): OasSecurityScheme[]; + + /** + * Called when the user changes the description of a security scheme in the table of schemes. + * @param {OasSecurityScheme} scheme + * @param {string} description + */ + public changeSecuritySchemeDescription(scheme: OasSecurityScheme, description: string): void { + let command: ICommand = createChangePropertyCommand(this.document, scheme, "description", description); + this.onCommand.emit(command); + } + + /** + * Called when the user adds a new security scheme. + * @param {SecurityScheme20EventData | SecurityScheme30EventData} event + */ + public abstract addSecurityScheme(event: SecurityScheme20EventData | SecurityScheme30EventData): void; + + /** + * Called when the user changes an existing security scheme. + * @param {SecurityScheme20EventData | SecurityScheme30EventData} event + */ + public abstract changeSecurityScheme(event: SecurityScheme20EventData | SecurityScheme30EventData): void; + + /** + * Deletes a security scheme. + * @param {Oas20SecurityScheme | Oas30SecurityScheme} scheme + */ + public deleteSecurityScheme(scheme: Oas20SecurityScheme | Oas30SecurityScheme): void { + let command: ICommand = createDeleteSecuritySchemeCommand(this.document, scheme.schemeName()); + this.onCommand.emit(command); + } + + /** + * Opens the security scheme dialog for adding or editing a security scheme. + * @param {OasSecurityScheme} scheme + */ + public abstract openSecuritySchemeDialog(scheme?: OasSecurityScheme); + + /** + * Returns true if the form is for an OpenAPI 3.x document. + * @return {boolean} + */ + public is3xForm(): boolean { + return false; + } +} + + +/** + * The OAI 2.0 version of the main form. + */ +@Component({ + moduleId: module.id, + selector: "main-20-form", + templateUrl: "main-form.component.html", + encapsulation: ViewEncapsulation.None +}) +export class Main20FormComponent extends MainFormComponent { + + @ViewChild("securityScheme20Dialog") securitySchemeDialog: SecurityScheme20DialogComponent; + + /** + * Opens the security scheme dialog. + * @param {Oas20SecurityScheme} scheme + */ + public openSecuritySchemeDialog(scheme?: Oas20SecurityScheme): void { + this.securitySchemeDialog.open(scheme); + } + + /** + * Returns all defined security schemes. + * @return {OasSecurityScheme[]} + */ + public securitySchemes(): OasSecurityScheme[] { + let secdefs: Oas20SecurityDefinitions = (this.document as Oas20Document).securityDefinitions; + if (secdefs) { + return secdefs.securitySchemes().sort( (scheme1, scheme2) => { + return scheme1.schemeName().localeCompare(scheme2.schemeName()); + }); + } + return []; + } + + /** + * Called when the user adds a new security scheme. + * @param {SecurityScheme20EventData} event + */ + public addSecurityScheme(event: SecurityScheme20EventData): void { + console.info("[MainFormComponent] Adding a security scheme: %s", event.schemeName); + let scheme: Oas20SecurityScheme = (this.document as Oas20Document).createSecurityDefinitions().createSecurityScheme(event.schemeName); + scheme.description = event.description; + scheme.type = event.type; + // TODO set values in the Oas20SecurityScheme only if necessary based on the type - avoid potential of leaking info from the dialog into the data model + scheme.name = event.name; + scheme.in = event.in; + scheme.flow = event.flow; + scheme.authorizationUrl = event.authorizationUrl; + scheme.tokenUrl = event.tokenUrl; + if (scheme.type === "oauth2") { + scheme.scopes = scheme.createScopes(); + if (event.scopes) { + for (let s of event.scopes) { + scheme.scopes.addScope(s.name, s.description); + } + } + } + + + let command: ICommand = createNewSecuritySchemeCommand(this.document, scheme); + this.onCommand.emit(command); + } + + /** + * Called when the user changes an existing security scheme. + * @param {SecurityScheme20EventData} event + */ + public changeSecurityScheme(event: SecurityScheme20EventData): void { + console.info("[MainFormComponent] Changing a security scheme: %s", event.schemeName); + let scheme: Oas20SecurityScheme = (this.document as Oas20Document).createSecurityDefinitions().createSecurityScheme(event.schemeName); + scheme.description = event.description; + scheme.type = event.type; + scheme.name = event.name; + scheme.in = event.in; + scheme.flow = event.flow; + scheme.authorizationUrl = event.authorizationUrl; + scheme.tokenUrl = event.tokenUrl; + if (scheme.type === "oauth2") { + if (event.scopes) { + scheme.scopes = scheme.createScopes(); + for (let s of event.scopes) { + scheme.scopes.addScope(s.name, s.description); + } + } + } + + let command: ICommand = createChangeSecuritySchemeCommand(this.document, scheme); + this.onCommand.emit(command); + } + +} + + +/** + * The OAI 3.0.x version of the main form. + */ +@Component({ + moduleId: module.id, + selector: "main-30-form", + templateUrl: "main-form.component.html", + encapsulation: ViewEncapsulation.None +}) +export class Main30FormComponent extends MainFormComponent { + + @ViewChild("securityScheme30Dialog") securitySchemeDialog: SecurityScheme30DialogComponent; + + /** + * Opens the security scheme dialog. + * @param {Oas30SecurityScheme} scheme + */ + public openSecuritySchemeDialog(scheme?: Oas30SecurityScheme): void { + this.securitySchemeDialog.open(scheme); + } + + /** + * Returns all defined security schemes. + * @return {OasSecurityScheme[]} + */ + public securitySchemes(): OasSecurityScheme[] { + let doc: Oas30Document = this.document as Oas30Document; + if (doc.components) { + let schemes: Oas30SecurityScheme[] = doc.components.getSecuritySchemes(); + return schemes.sort( (scheme1, scheme2) => { + return scheme1.schemeName().localeCompare(scheme2.schemeName()); + }); + } + return []; + } + + /** + * Called when the user adds a new security scheme. + * @param {SecurityScheme30EventData} event + */ + public addSecurityScheme(event: SecurityScheme30EventData): void { + console.info("[MainFormComponent] Adding a security scheme: %s", event.schemeName); + + let scheme: Oas30SecurityScheme = (this.document as Oas30Document).createComponents().createSecurityScheme(event.schemeName); + this.copySchemeToModel(event, scheme); + + let command: ICommand = createNewSecuritySchemeCommand(this.document, scheme); + this.onCommand.emit(command); + } + + /** + * Called when the user changes an existing security scheme. + * @param {SecurityScheme30EventData} event + */ + public changeSecurityScheme(event: SecurityScheme30EventData): void { + console.info("[MainFormComponent] Changing a security scheme: %s", event.schemeName); + + let scheme: Oas30SecurityScheme = (this.document as Oas30Document).createComponents().createSecurityScheme(event.schemeName); + this.copySchemeToModel(event, scheme); + + let command: ICommand = createChangeSecuritySchemeCommand(this.document, scheme); + this.onCommand.emit(command); + } + + /** + * Converts from array of scopes to scopes object for data model. + * @param {Scope[]} scopes + */ + private toScopes(scopes: Scope[]): any { + let rval: any = {}; + scopes.forEach( scope => { + rval[scope.name] = scope.description; + }); + return rval; + } + + /** + * Copy the event data to the data model. + * @param {SecurityScheme30EventData} event + * @param {Oas30SecurityScheme} scheme + */ + private copySchemeToModel(event: SecurityScheme30EventData, scheme: Oas30SecurityScheme) { + scheme.description = event.description; + scheme.type = event.type; + if (scheme.type === "http") { + scheme.scheme = event.scheme; + if (scheme.scheme === "Bearer") { + scheme.bearerFormat = event.bearerFormat; + } + } + if (scheme.type === "apiKey") { + scheme.in = event.in; + scheme.name = event.name; + } + if (scheme.type === "oauth2") { + scheme.flows = scheme.createOAuthFlows(); + if (event.flows.implicit.enabled) { + scheme.flows.implicit = scheme.flows.createImplicitOAuthFlow(); + scheme.flows.implicit.authorizationUrl = event.flows.implicit.authorizationUrl; + scheme.flows.implicit.tokenUrl = event.flows.implicit.tokenUrl; + scheme.flows.implicit.refreshUrl = event.flows.implicit.refreshUrl; + scheme.flows.implicit.scopes = this.toScopes(event.flows.implicit.scopes); + } + if (event.flows.password.enabled) { + scheme.flows.password = scheme.flows.createPasswordOAuthFlow(); + scheme.flows.password.authorizationUrl = event.flows.password.authorizationUrl; + scheme.flows.password.tokenUrl = event.flows.password.tokenUrl; + scheme.flows.password.refreshUrl = event.flows.password.refreshUrl; + scheme.flows.password.scopes = this.toScopes(event.flows.password.scopes); + } + if (event.flows.clientCredentials.enabled) { + scheme.flows.clientCredentials = scheme.flows.createClientCredentialsOAuthFlow(); + scheme.flows.clientCredentials.authorizationUrl = event.flows.clientCredentials.authorizationUrl; + scheme.flows.clientCredentials.tokenUrl = event.flows.clientCredentials.tokenUrl; + scheme.flows.clientCredentials.refreshUrl = event.flows.clientCredentials.refreshUrl; + scheme.flows.clientCredentials.scopes = this.toScopes(event.flows.clientCredentials.scopes); + } + if (event.flows.authorizationCode.enabled) { + scheme.flows.authorizationCode = scheme.flows.createAuthorizationCodeOAuthFlow(); + scheme.flows.authorizationCode.authorizationUrl = event.flows.authorizationCode.authorizationUrl; + scheme.flows.authorizationCode.tokenUrl = event.flows.authorizationCode.tokenUrl; + scheme.flows.authorizationCode.refreshUrl = event.flows.authorizationCode.refreshUrl; + scheme.flows.authorizationCode.scopes = this.toScopes(event.flows.authorizationCode.scopes); + } + } + if (scheme.type === "openIdConnect") { + scheme.openIdConnectUrl = event.openIdConnectUrl; + } + } + + /** + * Returns true if the form is for an OpenAPI 3.x document. + * @return {boolean} + */ + public is3xForm(): boolean { + return true; + } +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation-30-form.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation-30-form.component.html new file mode 100644 index 000000000..6bbd8a823 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation-30-form.component.html @@ -0,0 +1,270 @@ +
    +
    +
    + {{ operation.method() }} +
    +
    + +
    +
    +
    + +
    + + +
    + + + + +
    +
    + +
    + + +
    +
    +
    +
    + + +
    +
    + + + + +

    + In this section you can easily edit the Name and + Description of the Operation. +

    +
    +
    +
    +

    + +

    + +
    +
    + + + + + +
    +
    + + + + +

    + This section is visible because the path/endpoint this operation belongs to has dynamic parameters. + Configure the parameters' descriptions and types below. +

    +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    + + + + +

    + Use this section to configure the operation's Request Body. Typically a + request body is used for PUT and POST only. Other operation + types usually do not include a body. +

    +
    + +
    +
    + +
    + + None Found + No request body has been declared. + +
    + +
    +
    + + Not Recommended + It is unusual and not recommended to define a request body for a GET operation. +
    + +
    + It is + + that clients include the request body. +
    +
    + +
    +
    +
    +
    + + +
    +
    + + + + +

    + An operation may, optionally, allow additional options to be sent via URL query + parameters. This section allows you to document what query parameters are + accepted/expected by this operation. +

    +
    + + +
    +
    + +
    + + None Found + No query parameters have been declared. + +
    + + +
    + +
    +
    +
    + + +
    +
    + + + + +

    + Every operation must include at least one Response. Multiple responses (with + different HTTP response codes) are possible for each operation - so add as many as you like. + Some common choices for HTTP responses are: +

    +
    +
      +
    • 200 OK - successful GET (retrieve data) operations
    • +
    • 201 Created - successful POST (create) operations
    • +
    • 204 No Content - successful PUT (update) operations
    • +
    • 400 Bad Request - failed to PUT/POST something due to incorrect input
    • +
    • 404 Not Found - failed to GET something
    • +
    +
    + + +
    +
    + +
    + + None Found + No responses have been declared. + +
    + + +
    + +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation-30-form.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation-30-form.component.ts new file mode 100644 index 000000000..12f7543f5 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation-30-form.component.ts @@ -0,0 +1,387 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Input, Output, ViewChild, ViewEncapsulation} from "@angular/core"; +import {Oas30MediaType, Oas30Operation, Oas30Parameter, Oas30PathItem, Oas30Response, OasPathItem} from "oai-ts-core"; +import { + createChangeMediaTypeTypeCommand, + createChangeParameterTypeCommand, + createChangePropertyCommand, + createDeleteAllParametersCommand, + createDeleteMediaTypeCommand, + createDeleteParameterCommand, + createDeleteResponseCommand, + createNewMediaTypeCommand, + createNewParamCommand, + createNewRequestBodyCommand, + createNewResponseCommand, + createReplaceOperationCommand, + createDeleteOperationCommand, + createDeleteAllResponsesCommand, + createDeleteRequestBodyCommand, + SimplifiedParameterType, ICommand +} from "oai-ts-commands"; +import {AddQueryParamDialogComponent} from "../dialogs/add-query-param.component"; +import {AddResponseDialogComponent} from "../dialogs/add-response.component"; +import {SourceFormComponent} from "./source-form.base"; +import {ModelUtils} from "../../_util/model.util"; +import {ObjectUtils} from "../../_util/object.util"; +import {MediaTypeChangeEvent} from "./operation/content.component"; +import {DropDownOption} from '../../../../../../components/common/drop-down.component'; + + +@Component({ + moduleId: module.id, + selector: "operation-30-form", + templateUrl: "operation-30-form.component.html", + encapsulation: ViewEncapsulation.None +}) +export class Operation30FormComponent extends SourceFormComponent { + + protected _operation: Oas30Operation; + @Input() + set operation(operation: Oas30Operation) { + this._operation = operation; + this.sourceNode = operation; + } + get operation(): Oas30Operation { + return this._operation; + } + + @Output() onDeselect: EventEmitter = new EventEmitter(); + + @ViewChild("addQueryParamDialog") public addQueryParamDialog: AddQueryParamDialogComponent; + @ViewChild("addResponseDialog") public addResponseDialog: AddResponseDialogComponent; + + protected createEmptyNodeForSource(): Oas30Operation { + return (this.operation.parent()).createOperation(this.operation.method()); + } + + protected createReplaceNodeCommand(node: Oas30Operation): ICommand { + return createReplaceOperationCommand(this.operation.ownerDocument(), this.operation, node); + } + + public summary(): string { + if (this.operation.summary) { + return this.operation.summary; + } else { + return null; + } + } + + public hasSummary(): boolean { + if (this.operation.summary) { + return true; + } else { + return false; + } + } + + public description(): string { + if (this.operation.description) { + return this.operation.description; + } else { + return null; + } + } + + public hasDescription(): boolean { + if (this.operation.description) { + return true; + } else { + return false; + } + } + + public bodyDescription(): string { + if (this.operation.requestBody) { + return this.operation.requestBody.description; + } + return null; + } + + public parameters(paramType: string): Oas30Parameter[] { + let params: Oas30Parameter[] = this.operation.getParameters(paramType) as Oas30Parameter[]; + return params.sort((param1, param2) => { + return param1.name.localeCompare(param2.name); + }); + } + + public pathParam(paramName: string): Oas30Parameter { + let param: Oas30Parameter = this.operation.parameter("path", paramName) as Oas30Parameter; + + if (param === null) { + param = this.operation.createParameter(); + param.in = "path"; + param.name = paramName; + param.required = true; + param.n_attribute("missing", true); + } + + return param; + } + + public pathParameters(): Oas30Parameter[] { + let pathParamNames: string[] = ModelUtils.detectPathParamNames((this.operation.parent()).path()); + return pathParamNames.map( pname => { + return this.pathParam(pname); + }); + } + + public queryParameters(): Oas30Parameter[] { + let opParams: Oas30Parameter[] = this.parameters("query"); + let piParams: Oas30Parameter[] = (this.operation.parent()).getParameters("query") as Oas30Parameter[]; + let hasOpParam = function(param: Oas30Parameter): boolean { + var found: boolean = false; + opParams.forEach( opParam => { + if (opParam.name === param.name) { + found = true; + } + }); + return found; + }; + piParams.forEach( param => { + if (!hasOpParam(param)) { + let missingParam: Oas30Parameter = this.operation.createParameter(); + missingParam.in = "query"; + missingParam.name = param.name; + missingParam.required = true; + missingParam.n_attribute("missing", true); + opParams.push(missingParam); + } + }); + return opParams.sort((param1, param2) => { + return param1.name.localeCompare(param2.name); + }); + } + + public headerParameters(): Oas30Parameter[] { + return this.parameters("header"); + } + + public formDataParameters(): Oas30Parameter[] { + return this.parameters("formData"); + } + + public hasFormDataParams(): boolean { + return this.hasParameters("formData"); + } + + public hasQueryParameters(): boolean { + return this.operation.getParameters("query").length > 0 || + (this.operation.parent()).getParameters("query").length > 0; + } + + public canHavePathParams(): boolean { + return (this.operation.parent()).path().indexOf("{") !== -1; + } + + public hasParameters(type: string): boolean { + if (!this.operation.parameters) { + return false; + } + return this.operation.parameters.filter((value) => { + return value.in === type; + }).length > 0; + } + + public responses(): Oas30Response[] { + if (!this.operation.responses) { + return []; + } + let rval: Oas30Response[] = []; + for (let scode of this.operation.responses.responseStatusCodes()) { + let response: Oas30Response = this.operation.responses.response(scode) as Oas30Response; + rval.push(response); + } + return rval.sort((a, b) => { + return a.statusCode().localeCompare(b.statusCode()); + }); + } + + public hasRequestBody(): boolean { + return !ObjectUtils.isNullOrUndefined(this.operation.requestBody); + } + + public hasResponses(): boolean { + if (!this.operation.responses) { + return false; + } + if (this.operation.responses.responseStatusCodes().length === 0) { + return false; + } + + return true; + } + + public changeSummary(newSummary: string): void { + let command: ICommand = createChangePropertyCommand(this.operation.ownerDocument(), this.operation,"summary", newSummary); + this.onCommand.emit(command); + } + + public changeDescription(newDescription: string): void { + let command: ICommand = createChangePropertyCommand(this.operation.ownerDocument(), this.operation, "description", newDescription); + this.onCommand.emit(command); + } + + public changeBodyDescription(newBodyDescription: string): void { + let command: ICommand = createChangePropertyCommand(this.operation.ownerDocument(), this.operation.requestBody,"description", newBodyDescription); + this.onCommand.emit(command); + } + + public changeParamDescription(param: Oas30Parameter, newParamDescription: string): void { + let command: ICommand = createChangePropertyCommand(this.operation.ownerDocument(), param, "description", newParamDescription); + this.onCommand.emit(command); + } + + public changeParamType(param: Oas30Parameter, newType: SimplifiedParameterType): void { + let command: ICommand = createChangeParameterTypeCommand(this.operation.ownerDocument(), param, newType); + this.onCommand.emit(command); + } + + public changeResponseDescription(response: Oas30Response, newDescription: string): void { + let command: ICommand = createChangePropertyCommand(this.operation.ownerDocument(), response, "description", newDescription); + this.onCommand.emit(command); + } + + public delete(): void { + let command: ICommand = createDeleteOperationCommand(this.operation.ownerDocument(), this.operation.method(), + this.operation.parent() as OasPathItem); + this.onCommand.emit(command); + this.onDeselect.emit(true); + } + + public deleteAllQueryParams(): void { + let command: ICommand = createDeleteAllParametersCommand(this.operation.ownerDocument(), this.operation, "query"); + this.onCommand.emit(command); + } + + public deleteAllResponses(): void { + let command: ICommand = createDeleteAllResponsesCommand(this.operation.ownerDocument(), this.operation); + this.onCommand.emit(command); + } + + public deleteParam(parameter: Oas30Parameter): void { + let command: ICommand = createDeleteParameterCommand(this.operation.ownerDocument(), parameter); + this.onCommand.emit(command); + } + + public deleteResponse(response: Oas30Response): void { + let command: ICommand = createDeleteResponseCommand(this.operation.ownerDocument(), response); + this.onCommand.emit(command); + } + + public openAddQueryParamModal(): void { + this.addQueryParamDialog.open(); + } + + public addQueryParam(name: string): void { + let command: ICommand = createNewParamCommand(this.operation.ownerDocument(), this.operation, name, "query"); + this.onCommand.emit(command); + } + + public addRequestBody(): void { + let command: ICommand = createNewRequestBodyCommand(this.operation.ownerDocument(), this.operation); + this.onCommand.emit(command); + } + + public deleteRequestBody(): void { + let command: ICommand = createDeleteRequestBodyCommand(this.operation.ownerDocument(), this.operation); + this.onCommand.emit(command); + } + + public createRequestBodyMediaType(mediaType: string): void { + console.info("[Operation30FormComponent] Creating request body media type: " + mediaType); + let command: ICommand = createNewMediaTypeCommand(this.operation.ownerDocument(), this.operation.requestBody, mediaType); + this.onCommand.emit(command); + } + + public deleteRequestBodyMediaType(mediaType: string): void { + console.info("[Operation30FormComponent] Deleting request body media type: " + mediaType); + let mt: Oas30MediaType = this.operation.requestBody.getMediaType(mediaType); + let command: ICommand = createDeleteMediaTypeCommand(this.operation.ownerDocument(), mt); + this.onCommand.emit(command); + } + + public changeRequestBodyMediaType(event: MediaTypeChangeEvent): void { + console.info("[Operation30FormComponent] Changing request body media type: " + event.name); + let mt: Oas30MediaType = this.operation.requestBody.getMediaType(event.name); + let command: ICommand = createChangeMediaTypeTypeCommand(this.operation.ownerDocument(), mt, event.type); + this.onCommand.emit(command); + } + + public createResponseMediaType(response: Oas30Response, mediaType: string): void { + console.info("[Operation30FormComponent] Creating response media type: " + mediaType); + let command: ICommand = createNewMediaTypeCommand(this.operation.ownerDocument(), response, mediaType); + this.onCommand.emit(command); + } + + public deleteResponseMediaType(response: Oas30Response, mediaType: string): void { + console.info("[Operation30FormComponent] Deleting response media type: " + mediaType); + let mt: Oas30MediaType = response.getMediaType(mediaType); + let command: ICommand = createDeleteMediaTypeCommand(this.operation.ownerDocument(), mt); + this.onCommand.emit(command); + } + + public changeResponseMediaType(response: Oas30Response, event: MediaTypeChangeEvent): void { + console.info("[Operation30FormComponent] Changing response media type: " + event.name); + let mt: Oas30MediaType = response.getMediaType(event.name); + let command: ICommand = createChangeMediaTypeTypeCommand(this.operation.ownerDocument(), mt, event.type); + this.onCommand.emit(command); + } + + public openAddResponseModal(): void { + this.addResponseDialog.open("200"); + } + + public addResponse(statusCode: string): void { + let command: ICommand = createNewResponseCommand(this.operation.ownerDocument(), this.operation, statusCode); + this.onCommand.emit(command); + } + + public createPathParam(paramName: string): void { + let command: ICommand = createNewParamCommand(this.operation.ownerDocument(), this.operation, paramName, "path"); + this.onCommand.emit(command); + } + + public formType(): string { + return "operation"; + } + + public enableSourceMode(): void { + this.sourceNode = this.operation; + super.enableSourceMode(); + } + + public requestBodyRequiredOptions(): DropDownOption[] { + return [ + { name: "Required", value: "required" }, + { name: "Not Required", value: "not-required" } + ]; + } + + public changeRequestBodyRequired(value: string): void { + let isRequired: boolean = value === "required"; + let command: ICommand = createChangePropertyCommand(this.operation.ownerDocument(), this.operation.requestBody, "required", isRequired); + this.onCommand.emit(command); + } + + public parentPath() { + return (this.operation.parent() as OasPathItem).path() + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation-form.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation-form.component.html new file mode 100644 index 000000000..2ef599a4d --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation-form.component.html @@ -0,0 +1,295 @@ +
    +
    +
    + {{ operation.method() }} +
    +
    + +
    +
    +
    + +
    + + +
    + + + + +
    +
    + +
    + + +
    +
    +
    +
    + + +
    +
    + + + + +

    + In this section you can easily edit the Name and + Description of the Operation. +

    +
    +
    +
    +

    + +

    + +
    +
    + + +
    +
    + + + + +

    + This section is visible because the path/endpoint this operation belongs to has dynamic parameters. + Configure the parameters' descriptions and types below. +

    +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    + + + + +

    + Use this section to configure the operation's Request Body. Typically a + request body is used for PUT and POST only. Other operation + types usually do not include a body. +

    +
    + + +
    +
    + +
    + + None Found + No request body has been declared. + + +
    + + +
    +
    + + Not Recommended + It is unusual and not recommended to define a request body for a GET operation. +
    + +
    + Request Body Type: + + of + + as + +
    +
    + + +
    +
    + + Form Data Parameters Needed + You have indicated a desire to pass data via Form Data parameters but none have been defined. + +
    + +
    + +
    +
    +
    +
    + + +
    +
    + + + + +

    + An operation may, optionally, allow additional options to be sent via URL query + parameters. This section allows you to document what query parameters are + accepted/expected by this operation. +

    +
    + + +
    +
    + +
    + + None Found + No query parameters have been declared. + +
    + + +
    + +
    +
    +
    + + +
    +
    + + + + +

    + Every operation must include at least one Response. Multiple responses (with + different HTTP response codes) are possible for each operation - so add as many as you like. + Some common choices for HTTP responses are: +

    +
    +
      +
    • 200 OK - successful GET (retrieve data) operations
    • +
    • 201 Created - successful POST (create) operations
    • +
    • 204 No Content - successful PUT (update) operations
    • +
    • 400 Bad Request - failed to PUT/POST something due to incorrect input
    • +
    • 404 Not Found - failed to GET something
    • +
    +
    + + +
    +
    + +
    + + None Found + No responses have been declared. + +
    + + +
    + +
    +
    +
    +
    +
    +
    +
    + + + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation-form.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation-form.component.ts new file mode 100644 index 000000000..3b35b1d15 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation-form.component.ts @@ -0,0 +1,567 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Input, Output, ViewChild, ViewEncapsulation} from "@angular/core"; +import {Oas20Document, Oas20Operation, Oas20Parameter, Oas20PathItem, Oas20Response, OasPathItem} from "oai-ts-core"; +import { + createChangeParameterTypeCommand, + createChangePropertyCommand, + createChangeResponseTypeCommand, + createDeleteAllParametersCommand, + createDeleteParameterCommand, + createDeleteResponseCommand, + createNewParamCommand, + createNewRequestBodyCommand, + createNewResponseCommand, + createReplaceOperationCommand, + createDeleteOperationCommand, + createDeleteAllResponsesCommand, + SimplifiedParameterType, ICommand +} from "oai-ts-commands"; +import {AddQueryParamDialogComponent} from "../dialogs/add-query-param.component"; +import {AddResponseDialogComponent} from "../dialogs/add-response.component"; +import {SourceFormComponent} from "./source-form.base"; +import {ObjectUtils} from "../../_util/object.util"; +import {SimplifiedType} from "oai-ts-commands"; +import {AddFormDataParamDialogComponent} from "../dialogs/add-formData-param.component"; +import {ModelUtils} from "../../_util/model.util"; +import {DropDownOption} from '../../../../../../components/common/drop-down.component'; + + +@Component({ + moduleId: module.id, + selector: "operation-form", + templateUrl: "operation-form.component.html", + encapsulation: ViewEncapsulation.None +}) +export class OperationFormComponent extends SourceFormComponent { + + protected _operation: Oas20Operation; + @Input() + set operation(operation: Oas20Operation) { + this._operation = operation; + this.sourceNode = operation; + } + get operation(): Oas20Operation { + return this._operation; + } + + @Output() onDeselect: EventEmitter = new EventEmitter(); + + @ViewChild("addFormDataParamDialog") public addFormDataParamDialog: AddFormDataParamDialogComponent; + @ViewChild("addQueryParamDialog") public addQueryParamDialog: AddQueryParamDialogComponent; + @ViewChild("addResponseDialog") public addResponseDialog: AddResponseDialogComponent; + + protected createEmptyNodeForSource(): Oas20Operation { + return (this.operation.parent()).createOperation(this.operation.method()); + } + + protected createReplaceNodeCommand(node: Oas20Operation): ICommand { + return createReplaceOperationCommand(this.operation.ownerDocument(), this.operation, node); + } + + public summary(): string { + if (this.operation.summary) { + return this.operation.summary; + } else { + return null; + } + } + + public hasSummary(): boolean { + if (this.operation.summary) { + return true; + } else { + return false; + } + } + + public description(): string { + if (this.operation.description) { + return this.operation.description; + } else { + return null; + } + } + + public hasDescription(): boolean { + if (this.operation.description) { + return true; + } else { + return false; + } + } + + public bodyParam(): Oas20Parameter { + let params: Oas20Parameter[] = this.operation.parameters as Oas20Parameter[]; + if (params) { + for (let param of params) { + if (param.in === "body") { + return param; + } + } + } + return null; + } + + public requestBodyType(): string { + let bodyParam: Oas20Parameter = this.bodyParam(); + if (bodyParam && bodyParam.schema) { + return SimplifiedType.fromSchema(bodyParam.schema).type; + } + return null; + } + + public requestBodyTypeOf(): string { + let bodyParam: Oas20Parameter = this.bodyParam(); + if (bodyParam && bodyParam.schema) { + let st: SimplifiedType = SimplifiedType.fromSchema(bodyParam.schema); + if (st.of) { + return st.of.type; + } + } + return null; + } + + public requestBodyTypeAs(): string { + let bodyParam: Oas20Parameter = this.bodyParam(); + if (bodyParam && bodyParam.schema) { + let st: SimplifiedType = SimplifiedType.fromSchema(bodyParam.schema); + if (st.isArray() && st.of) { + return st.of.as; + } + if (st.isSimpleType()) { + return st.as; + } + } + return null; + } + + public hasBodyParam(): boolean { + if (this.bodyParam() !== null) { + return true; + } else { + return false; + } + } + + public requestBodyTypeOptions(): DropDownOption[] { + let options: DropDownOption[] = [ + { value: "array", name: "Array" }, + { divider: true }, + { value: "string", name: "String" }, + { value: "integer", name: "Integer" }, + { value: "boolean", name: "Boolean" }, + { value: "number", name: "Number" } + ]; + + let doc: Oas20Document = (this.operation.ownerDocument()); + if (doc.definitions) { + let co: DropDownOption[] = doc.definitions.definitions().sort( (def1, def2) => { + return def1.definitionName().toLocaleLowerCase().localeCompare(def2.definitionName().toLocaleLowerCase()); + }).map( def => { + return { + value: "#/definitions/" + def.definitionName(), + name: def.definitionName() + }; + }); + if (co && co.length > 0) { + options.push({ divider: true }); + co.forEach( o => options.push(o) ); + } + } + + return options; + } + + public requestBodyTypeOfOptions(): DropDownOption[] { + let options: DropDownOption[] = [ + { value: "string", name: "String" }, + { value: "integer", name: "Integer" }, + { value: "boolean", name: "Boolean" }, + { value: "number", name: "Number" } + ]; + + let doc: Oas20Document = (this.operation.ownerDocument()); + if (doc.definitions) { + let co: DropDownOption[] = doc.definitions.definitions().sort( (def1, def2) => { + return def1.definitionName().toLocaleLowerCase().localeCompare(def2.definitionName().toLocaleLowerCase()); + }).map( def => { + return { + value: "#/definitions/" + def.definitionName(), + name: def.definitionName() + }; + }); + if (co && co.length > 0) { + options.push({ divider: true }); + co.forEach( o => options.push(o) ); + } + } + + return options; + } + + public requestBodyTypeAsOptions(): DropDownOption[] { + let bodyParam: Oas20Parameter = this.bodyParam(); + if (ObjectUtils.isNullOrUndefined(bodyParam)) { + return []; + } + if (ObjectUtils.isNullOrUndefined(bodyParam.schema)) { + return []; + } + let options: DropDownOption[] = []; + let st: SimplifiedType = SimplifiedType.fromSchema(this.bodyParam().schema); + if (st.isArray() && st.of && st.of.isSimpleType()) { + st = st.of; + } + if (st.type === "string") { + options = [ + { value: null, name: "String" }, + { value: "byte", name: "Byte" }, + { value: "binary", name: "Binary" }, + { value: "date", name: "Date" }, + { value: "date-time", name: "DateTime" }, + { value: "password", name: "Password" } + ]; + } else if (st.type === "integer") { + options = [ + { value: null, name: "Integer" }, + { value: "int32", name: "32-Bit Integer" }, + { value: "int64", name: "64-Bit Integer" } + ]; + } else if (st.type === "number") { + options = [ + { value: null, name: "Number" }, + { value: "float", name: "Float" }, + { value: "double", name: "Double" } + ]; + } + return options; + } + + public shouldShowRequestBodyTypeOf(): boolean { + let bodyParam: Oas20Parameter = this.bodyParam(); + if (!bodyParam) { + return false; + } + let nt: SimplifiedType = SimplifiedType.fromSchema(bodyParam.schema); + return nt.isArray(); + } + + public shouldShowRequestBodyTypeAs(): boolean { + let bodyParam: Oas20Parameter = this.bodyParam(); + if (!bodyParam) { + return false; + } + let nt: SimplifiedType = SimplifiedType.fromSchema(bodyParam.schema); + return (nt.isSimpleType() && nt.type !== "boolean") || + (nt.isArray() && nt.of && nt.of.isSimpleType() && nt.of.type !== "boolean"); + } + + public changeRequestBodyType(newType: string): void { + let bodyParam: Oas20Parameter = this.bodyParam(); + let nt: SimplifiedParameterType = new SimplifiedParameterType(); + nt.type = newType; + let command: ICommand = createChangeParameterTypeCommand(this.operation.ownerDocument(), bodyParam, nt); + this.onCommand.emit(command); + } + + public changeRequestBodyTypeOf(newOf: string): void { + let bodyParam: Oas20Parameter = this.bodyParam(); + let newType: SimplifiedParameterType = SimplifiedParameterType.fromParameter(bodyParam); + newType.of = new SimplifiedType(); + newType.of.type = newOf; + newType.as = null; + let command: ICommand = createChangeParameterTypeCommand(this.operation.ownerDocument(), bodyParam, newType); + this.onCommand.emit(command); + } + + public changeRequestBodyTypeAs(newAs: string): void { + let bodyParam: Oas20Parameter = this.bodyParam(); + let newType: SimplifiedParameterType = SimplifiedParameterType.fromParameter(bodyParam); + if (newType.isSimpleType()) { + newType.as = newAs; + } + if (newType.isArray() && newType.of) { + newType.of.as = newAs; + } + let command: ICommand = createChangeParameterTypeCommand(this.operation.ownerDocument(), bodyParam, newType); + this.onCommand.emit(command); + } + + public bodyDescription(): string { + let bodyParam: Oas20Parameter = this.bodyParam(); + if (bodyParam === null) { + return ""; + } + if (bodyParam.description) { + return bodyParam.description; + } else { + return null; + } + } + + public parameters(paramType: string): Oas20Parameter[] { + let params: Oas20Parameter[] = this.operation.getParameters(paramType) as Oas20Parameter[]; + return params.sort((param1, param2) => { + return param1.name.localeCompare(param2.name); + }); + } + + public pathParam(paramName: string): Oas20Parameter { + let param: Oas20Parameter = this.operation.parameter("path", paramName) as Oas20Parameter; + + if (param === null) { + param = this.operation.createParameter(); + param.in = "path"; + param.name = paramName; + param.required = true; + param.n_attribute("missing", true); + } + + return param; + } + + public pathParameters(): Oas20Parameter[] { + let pathParamNames: string[] = ModelUtils.detectPathParamNames((this.operation.parent()).path()); + return pathParamNames.map( pname => { + return this.pathParam(pname); + }); + } + + public queryParameters(): Oas20Parameter[] { + let opParams: Oas20Parameter[] = this.parameters("query"); + let piParams: Oas20Parameter[] = (this.operation.parent()).getParameters("query") as Oas20Parameter[]; + let hasOpParam = function(param: Oas20Parameter): boolean { + var found: boolean = false; + opParams.forEach( opParam => { + if (opParam.name === param.name) { + found = true; + } + }); + return found; + }; + piParams.forEach( param => { + if (!hasOpParam(param)) { + let missingParam: Oas20Parameter = this.operation.createParameter(); + missingParam.in = "query"; + missingParam.name = param.name; + missingParam.required = true; + missingParam.n_attribute("missing", true); + opParams.push(missingParam); + } + }); + return opParams.sort((param1, param2) => { + return param1.name.localeCompare(param2.name); + }); + } + + public headerParameters(): Oas20Parameter[] { + return this.parameters("header"); + } + + public formDataParameters(): Oas20Parameter[] { + return this.parameters("formData"); + } + + public hasFormDataParams(): boolean { + return this.hasParameters("formData"); + } + + public hasQueryParameters(): boolean { + return this.operation.getParameters("query").length > 0 || + (this.operation.parent()).getParameters("query").length > 0; + } + + public canHavePathParams(): boolean { + return (this.operation.parent()).path().indexOf("{") !== -1; + } + + public hasParameters(type: string): boolean { + if (!this.operation.parameters) { + return false; + } + return this.operation.parameters.filter((value) => { + return value.in === type; + }).length > 0; + } + + public responses(): Oas20Response[] { + if (!this.operation.responses) { + return []; + } + let rval: Oas20Response[] = []; + for (let scode of this.operation.responses.responseStatusCodes()) { + let response: Oas20Response = this.operation.responses.response(scode) as Oas20Response; + rval.push(response); + } + return rval.sort((a, b) => { + return a.statusCode().localeCompare(b.statusCode()); + }); + } + + public hasResponses(): boolean { + if (!this.operation.responses) { + return false; + } + if (this.operation.responses.responseStatusCodes().length === 0) { + return false; + } + + return true; + } + + public hasDefinitions(): boolean { + if (this.definitionNames()) { + return true; + } else { + return false; + } + } + + public definitionNames(): string[] { + let doc: Oas20Document = this.operation.ownerDocument(); + if (ObjectUtils.isNullOrUndefined(doc.definitions)) { + return []; + } else { + return doc.definitions.getItemNames().sort(); + } + } + + public changeSummary(newSummary: string): void { + let command: ICommand = createChangePropertyCommand(this.operation.ownerDocument(), this.operation,"summary", newSummary); + this.onCommand.emit(command); + } + + public changeDescription(newDescription: string): void { + let command: ICommand = createChangePropertyCommand(this.operation.ownerDocument(), this.operation, "description", newDescription); + this.onCommand.emit(command); + } + + public changeBodyDescription(newBodyDescription: string): void { + let bodyParam: Oas20Parameter = this.bodyParam(); + let command: ICommand = createChangePropertyCommand(this.operation.ownerDocument(), bodyParam,"description", newBodyDescription); + this.onCommand.emit(command); + } + + public changeParamDescription(param: Oas20Parameter, newParamDescription: string): void { + let command: ICommand = createChangePropertyCommand(this.operation.ownerDocument(), param, "description", newParamDescription); + this.onCommand.emit(command); + } + + public changeParamType(param: Oas20Parameter, newType: SimplifiedParameterType): void { + let command: ICommand = createChangeParameterTypeCommand(this.operation.ownerDocument(), param, newType); + this.onCommand.emit(command); + } + + public changeResponseType(response: Oas20Response, newType: SimplifiedType): void { + let command: ICommand = createChangeResponseTypeCommand(this.operation.ownerDocument(), response, newType); + this.onCommand.emit(command); + } + + public changeResponseDescription(response: Oas20Response, newDescription: string): void { + let command: ICommand = createChangePropertyCommand(this.operation.ownerDocument(), response, "description", newDescription); + this.onCommand.emit(command); + } + + public createRequestBody(): void { + let command: ICommand = createNewRequestBodyCommand(this.operation.ownerDocument(), this.operation); + this.onCommand.emit(command); + } + + public delete(): void { + let command: ICommand = createDeleteOperationCommand(this.operation.ownerDocument(), this.operation.method(), + this.operation.parent() as OasPathItem); + this.onCommand.emit(command); + this.onDeselect.emit(true); + } + + public deleteRequestBody(): void { + if (this.hasBodyParam()) { + let command: ICommand = createDeleteAllParametersCommand(this.operation.ownerDocument(), this.operation, "body"); + this.onCommand.emit(command); + } else { + let command: ICommand = createDeleteAllParametersCommand(this.operation.ownerDocument(), this.operation, "formData"); + this.onCommand.emit(command); + } + } + + public deleteAllQueryParams(): void { + let command: ICommand = createDeleteAllParametersCommand(this.operation.ownerDocument(), this.operation, "query"); + this.onCommand.emit(command); + } + + public deleteAllResponses(): void { + let command: ICommand = createDeleteAllResponsesCommand(this.operation.ownerDocument(), this.operation); + this.onCommand.emit(command); + } + + public deleteParam(parameter: Oas20Parameter): void { + let command: ICommand = createDeleteParameterCommand(this.operation.ownerDocument(), parameter); + this.onCommand.emit(command); + } + + public deleteResponse(response: Oas20Response): void { + let command: ICommand = createDeleteResponseCommand(this.operation.ownerDocument(), response); + this.onCommand.emit(command); + } + + public openAddQueryParamModal(): void { + this.addQueryParamDialog.open(); + } + + public openAddFormDataParamModal(): void { + this.addFormDataParamDialog.open(); + } + + public addQueryParam(name: string): void { + let command: ICommand = createNewParamCommand(this.operation.ownerDocument(), this.operation, name, "query"); + this.onCommand.emit(command); + } + + public addFormDataParam(name: string): void { + let command: ICommand = createNewParamCommand(this.operation.ownerDocument(), this.operation, name, "formData"); + this.onCommand.emit(command); + } + + public openAddResponseModal(): void { + this.addResponseDialog.open("200"); + } + + public addResponse(statusCode: string): void { + let command: ICommand = createNewResponseCommand(this.operation.ownerDocument(), this.operation, statusCode); + this.onCommand.emit(command); + } + + public createPathParam(paramName: string): void { + let command: ICommand = createNewParamCommand(this.operation.ownerDocument(), this.operation, paramName, "path"); + this.onCommand.emit(command); + } + + public formType(): string { + return "operation"; + } + + public enableSourceMode(): void { + this.sourceNode = this.operation; + super.enableSourceMode(); + } + + public parentPath() { + return (this.operation.parent() as OasPathItem).path() + } +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/content.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/content.component.html new file mode 100644 index 000000000..7bcf0562c --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/content.component.html @@ -0,0 +1,55 @@ +
    + +
    +
    + +
    + Type: + + of + + as + +
    + +
    +
    +
    + +
    +
    + + No Media Types + You must define and then configure at least one media type. + +
    +
    + + \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/content.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/content.component.ts new file mode 100644 index 000000000..eea79193f --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/content.component.ts @@ -0,0 +1,289 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation} from "@angular/core"; +import {Oas30Document, Oas30MediaType, Oas30RequestBodyContent, Oas30ResponseContent} from "oai-ts-core"; +import {SimplifiedType} from "oai-ts-commands"; +import {ObjectUtils} from "../../../_util/object.util"; +import {DropDownOption} from '../../../../../../../components/common/drop-down.component'; + +export interface MediaTypeChangeEvent { + name: string; + type: SimplifiedType; +} + + +@Component({ + moduleId: module.id, + selector: "content", + templateUrl: "content.component.html", + encapsulation: ViewEncapsulation.None +}) +export class ContentComponent implements OnInit { + + @Input() content: Oas30ResponseContent | Oas30RequestBodyContent; + @Input() document: Oas30Document; + + @Output() onNewMediaType: EventEmitter = new EventEmitter(); + @Output() onRemoveMediaType: EventEmitter = new EventEmitter(); + @Output() onMediaTypeChange: EventEmitter = new EventEmitter(); + + protected mediaTypeName: string; + + /** + * Called when the page is initialized. + */ + public ngOnInit(): void { + this.selectDefaultMediaType(); + } + + public selectDefaultMediaType(): void { + this.mediaTypeName = null; + if (this.content) { + for (let key in this.content) { + this.mediaTypeName = key; + break; + } + } + } + + public hasMediaTypes(): boolean { + if (this.content) { + return Object.keys(this.content).length > 0; + } + return false; + } + + public mediaTypeNames(): string[] { + if (this.content) { + return Object.keys(this.content); + } + return []; + } + + public selectMediaType(typeName: string): void { + this.mediaTypeName = typeName; + } + + public mediaType(): Oas30MediaType { + return this.content[this.mediaTypeName]; + } + + public mediaTypeType(): string { + let mt: Oas30MediaType = this.mediaType(); + if (mt) { + return SimplifiedType.fromSchema(mt.schema).type; + } + return null; + } + + public mediaTypeTypeOf(): string { + let mt: Oas30MediaType = this.mediaType(); + if (mt) { + let st: SimplifiedType = SimplifiedType.fromSchema(mt.schema); + if (st.of) { + return st.of.type; + } + } + return null; + } + + public mediaTypeTypeAs(): string { + let mt: Oas30MediaType = this.mediaType(); + if (mt) { + let st: SimplifiedType = SimplifiedType.fromSchema(mt.schema); + if (st.isArray() && st.of) { + return st.of.as; + } + if (st.isSimpleType()) { + return st.as; + } + } + return null; + } + + public mediaTypeTypeOptions(): DropDownOption[] { + let options: DropDownOption[] = [ + { value: "array", name: "Array" }, + { divider: true }, + { value: "string", name: "String" }, + { value: "integer", name: "Integer" }, + { value: "boolean", name: "Boolean" }, + { value: "number", name: "Number" } + ]; + + if (this.document.components) { + let co: DropDownOption[] = this.document.components.getSchemaDefinitions().sort( (def1, def2) => { + return def1.name().toLocaleLowerCase().localeCompare(def2.name().toLocaleLowerCase()); + }).map( def => { + return { + value: "#/components/schemas/" + def.name(), + name: def.name() + }; + }); + if (co && co.length > 0) { + options.push({ divider: true }); + co.forEach( o => options.push(o) ); + } + } + + return options; + } + + public mediaTypeTypeOfOptions(): DropDownOption[] { + let options: DropDownOption[] = [ + { value: "string", name: "String" }, + { value: "integer", name: "Integer" }, + { value: "boolean", name: "Boolean" }, + { value: "number", name: "Number" } + ]; + + if (this.document.components) { + let co: DropDownOption[] = this.document.components.getSchemaDefinitions().sort( (def1, def2) => { + return def1.name().toLocaleLowerCase().localeCompare(def2.name().toLocaleLowerCase()); + }).map( def => { + return { + value: "#/components/schemas/" + def.name(), + name: def.name() + }; + }); + if (co && co.length > 0) { + options.push({ divider: true }); + co.forEach( o => options.push(o) ); + } + } + + return options; + } + + public mediaTypeTypeAsOptions(): DropDownOption[] { + let mt: Oas30MediaType = this.mediaType(); + if (ObjectUtils.isNullOrUndefined(mt)) { + return []; + } + if (ObjectUtils.isNullOrUndefined(mt.schema)) { + return []; + } + + let options: DropDownOption[] = []; + let st: SimplifiedType = SimplifiedType.fromSchema(mt.schema); + if (st.isArray() && st.of && st.of.isSimpleType()) { + st = st.of; + } + if (st.type === "string") { + options = [ + { value: null, name: "String" }, + { value: "byte", name: "Byte" }, + { value: "binary", name: "Binary" }, + { value: "date", name: "Date" }, + { value: "date-time", name: "DateTime" }, + { value: "password", name: "Password" } + ]; + } else if (st.type === "integer") { + options = [ + { value: null, name: "Integer" }, + { value: "int32", name: "32-Bit Integer" }, + { value: "int64", name: "64-Bit Integer" } + ]; + } else if (st.type === "number") { + options = [ + { value: null, name: "Number" }, + { value: "float", name: "Float" }, + { value: "double", name: "Double" } + ]; + } + return options; + } + + public changeMediaTypeType(newType: string): void { + let nt: SimplifiedType = new SimplifiedType(); + nt.type = newType; + + this.onMediaTypeChange.emit({ + name: this.mediaTypeName, + type: nt + }); + } + + public changeMediaTypeTypeOf(newOf: string): void { + let mt: Oas30MediaType = this.mediaType(); + let newType: SimplifiedType = SimplifiedType.fromSchema(mt.schema); + newType.of = new SimplifiedType(); + newType.of.type = newOf; + newType.as = null; + + this.onMediaTypeChange.emit({ + name: this.mediaTypeName, + type: newType + }); + } + + public changeMediaTypeTypeAs(newAs: string): void { + let mt: Oas30MediaType = this.mediaType(); + let newType: SimplifiedType = SimplifiedType.fromSchema(mt.schema); + if (newType.isSimpleType()) { + newType.as = newAs; + } + if (newType.isArray() && newType.of) { + newType.of.as = newAs; + } + + this.onMediaTypeChange.emit({ + name: this.mediaTypeName, + type: newType + }); + } + + public shouldShowMediaTypeTypeOf(): boolean { + let mt: Oas30MediaType = this.mediaType(); + if (!mt) { + return false; + } + let nt: SimplifiedType = SimplifiedType.fromSchema(mt.schema); + return nt.isArray(); + } + + public shouldShowMediaTypeTypeAs(): boolean { + let mt: Oas30MediaType = this.mediaType(); + if (!mt) { + return false; + } + let nt: SimplifiedType = SimplifiedType.fromSchema(mt.schema); + return (nt.isSimpleType() && nt.type !== "boolean") || + (nt.isArray() && nt.of && nt.of.isSimpleType() && nt.of.type !== "boolean"); + } + + public addMediaType(mediaType: string): void { + this.onNewMediaType.emit(mediaType); + this.mediaTypeName = mediaType; + } + + public removeMediaType(mtName: string): void { + this.onRemoveMediaType.emit(mtName); + if (mtName === this.mediaTypeName) { + this.selectDefaultMediaType(); + } + } + + public deleteAllMediaTypes(): void { + // TODO fire a separate "delete all" command so that we delete all media types in a single undoable command + this.mediaTypeNames().forEach( mtName => { + this.onRemoveMediaType.emit(mtName); + }); + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/param-row.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/param-row.component.html new file mode 100644 index 000000000..29dd66031 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/param-row.component.html @@ -0,0 +1,73 @@ +
    + +
    +
    + + {{ param.name }} +
    +
    +
    {{ paramDescription() }}
    +
    (Parameter not defined)
    +
    +
    + +
    +
    + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + + +
    +
    +
    +
    + +
    +
    +
    \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/param-row.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/param-row.component.ts new file mode 100644 index 000000000..555aa8e31 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/param-row.component.ts @@ -0,0 +1,142 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Input, Output, ViewEncapsulation} from "@angular/core"; +import {SimplifiedParameterType} from "oai-ts-commands"; +import {OasOperation, OasParameterBase, OasPathItem} from "oai-ts-core"; +import {AbstractTypedItemComponent} from "./typed-item.component"; +import {AbstractCombinedVisitorAdapter} from "../../../_visitors/base.visitor"; +import {DropDownOption} from '../../../../../../../components/common/drop-down.component'; + + +@Component({ + moduleId: module.id, + selector: "param-row", + templateUrl: "param-row.component.html", + encapsulation: ViewEncapsulation.None +}) +export class ParamRowComponent extends AbstractTypedItemComponent { + + private _param: OasParameterBase; + private _overriddenParam: OasParameterBase; + @Input() + set param(param: OasParameterBase) { + this._param = param; + this.missingFlag = this.param.n_attribute("missing") === true; + this._overriddenParam = this.getOverriddenParam(param); + this.overrideFlag = this._overriddenParam !== null; + } + get param(): OasParameterBase { + return this._param; + } + + @Input() parameterClass: string = ""; + + @Output() onCreate: EventEmitter = new EventEmitter(); + + private missingFlag: boolean; + private overrideFlag: boolean; + + protected modelForEditing(): SimplifiedParameterType { + return this.paramToSimplifiedType(this.param); + } + + protected modelForViewing(): SimplifiedParameterType { + if (this.missingFlag && this._overriddenParam !== null) { + return this.paramToSimplifiedType(this._overriddenParam); + } else { + return this.paramToSimplifiedType(this.param); + } + } + + public isRequired(): boolean { + return this.param.required; + } + + public isPathParam(): boolean { + return this.param.in === "path"; + } + + public required(): string { + return this.model.required ? "required" : "not-required"; + } + + public requiredOptions(): DropDownOption[] { + return [ + { name: "Required", value: "required" }, + { name: "Not Required", value: "not-required" } + ]; + } + + public changeRequired(newValue: string): void { + this.model.required = newValue === "required"; + } + + protected paramDescription(): string { + if (this.missingFlag && this._overriddenParam !== null) { + return this._overriddenParam.description; + } else { + return this._param.description; + } + } + + public isMissing(): boolean { + return this.missingFlag && !this.overrideFlag; + } + + public isExists(): boolean { + return !this.missingFlag; + } + + public isOverride(): boolean { + return !this.missingFlag && this.overrideFlag; + } + + public isOverridable(): boolean { + return this.missingFlag && this.overrideFlag; + } + + public create(): void { + this.onCreate.emit(true); + } + + public getOverriddenParam(param: OasParameterBase): OasParameterBase { + let viz: DetectOverrideVisitor = new DetectOverrideVisitor(param); + param.parent().accept(viz); + return viz.overriddenParam; + } + + private paramToSimplifiedType(param: OasParameterBase): SimplifiedParameterType { + return SimplifiedParameterType.fromParameter(param as any); + } + +} + + +class DetectOverrideVisitor extends AbstractCombinedVisitorAdapter { + + public overriddenParam: OasParameterBase = null; + + constructor(private param: OasParameterBase) { + super(); + } + + public visitOperation(node: OasOperation): void { + this.overriddenParam = (node.parent()).parameter(this.param.in, this.param.name) as OasParameterBase; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/response-row-30.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/response-row-30.component.html new file mode 100644 index 000000000..b1f01a86e --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/response-row-30.component.html @@ -0,0 +1,31 @@ +
    +
    +
    {{ response.statusCode() }} {{ statusCodeLine(response.statusCode()) }}
    +
    +
    +
    + {{ responseTypeInfo() }} +
    +
    + + +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/response-row-30.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/response-row-30.component.ts new file mode 100644 index 000000000..fd65aad83 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/response-row-30.component.ts @@ -0,0 +1,140 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Input, Output, ViewEncapsulation} from "@angular/core"; +import {HttpCode, HttpCodeService} from "../../../_services/httpcode.service"; +import {Oas30Document, Oas30Response} from "oai-ts-core"; +import {MediaTypeChangeEvent} from "./content.component"; + + +@Component({ + moduleId: module.id, + selector: "response-row-30", + templateUrl: "response-row-30.component.html", + encapsulation: ViewEncapsulation.None +}) +export class ResponseRow30Component { + + private static httpCodes: HttpCodeService = new HttpCodeService(); + + @Input() document: Oas30Document; + @Input() response: Oas30Response; + + @Output() onDescriptionChange: EventEmitter = new EventEmitter(); + @Output() onCreateMediaType: EventEmitter = new EventEmitter(); + @Output() onDeleteMediaType: EventEmitter = new EventEmitter(); + @Output() onMediaTypeChange: EventEmitter = new EventEmitter(); + @Output() onDelete: EventEmitter = new EventEmitter(); + + protected _editing: boolean = false; + + public statusCodeLine(code: string): string { + let httpCode: HttpCode = ResponseRow30Component.httpCodes.getCode(code); + if (httpCode) { + return httpCode.line; + } + return ""; + } + + public statusCodeType(code: string): string { + if (code === "default") { + return ""; + } + + var icode: number = parseInt(code); + if (icode >= 200 && icode < 300) { + return "success"; + } + + if (icode >= 300 && icode < 400) { + return "redirect"; + } + + if (icode >= 400 && icode < 500) { + return "problem"; + } + + if (icode >= 500 && icode < 600) { + return "error"; + } + + return ""; + } + + public isEditing(): boolean { + return this._editing; + } + + public edit(): void { + this._editing = true; + } + + public ok(): void { + this._editing = false; + } + + public cancel(): void { + this._editing = false; + } + + public delete(): void { + this.onDelete.emit(true); + } + + public isValid(): boolean { + return true; + } + + public responseTypeInfo(): string { + if (!this.response.content) { + return "No response media types defined."; + } + let numMediaTypes: number = Object.keys(this.response.content).length; + if (numMediaTypes === 0) { + return "No response media types defined."; + } + + if (numMediaTypes === 1) { + return "Media Type: " + this.response.getMediaTypes()[0].name(); + } + + return "" + numMediaTypes + " response media types supported."; + } + + public setDescription(description: string): void { + this.onDescriptionChange.emit(description); + } + + public createResponseMediaType(mediaType: string): void { + this.onCreateMediaType.emit(mediaType); + } + + public deleteResponseMediaType(mediaType: string): void { + this.onDeleteMediaType.emit(mediaType); + } + + public changeResponseMediaType(event: MediaTypeChangeEvent): void { + this.onMediaTypeChange.emit(event); + } + + public onGlobalKeyDown(event: KeyboardEvent): void { + if (event.key === "Escape" && !event.metaKey && !event.altKey && !event.ctrlKey) { + this.cancel(); + } + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/response-row.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/response-row.component.html new file mode 100644 index 000000000..78dac36f1 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/response-row.component.html @@ -0,0 +1,48 @@ +
    +
    +
    {{ response.statusCode() }} {{ statusCodeLine(response.statusCode()) }}
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + + +
    +
    +
    +
    + +
    +
    +
    diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/response-row.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/response-row.component.ts new file mode 100644 index 000000000..3ef51a853 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/response-row.component.ts @@ -0,0 +1,124 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, ViewEncapsulation, Input} from "@angular/core"; +import { + Oas20Document, Oas20Response +} from "oai-ts-core"; +import {HttpCode, HttpCodeService} from "../../../_services/httpcode.service"; +import {SimplifiedType} from "oai-ts-commands"; +import {AbstractTypedItemComponent} from "./typed-item.component"; +import {DropDownOption} from '../../../../../../../components/common/drop-down.component'; + + +@Component({ + moduleId: module.id, + selector: "response-row", + templateUrl: "response-row.component.html", + encapsulation: ViewEncapsulation.None +}) +export class ResponseRowComponent extends AbstractTypedItemComponent { + + private static httpCodes: HttpCodeService = new HttpCodeService(); + + @Input() document: Oas20Document; + @Input() response: Oas20Response; + + public statusCodeLine(code: string): string { + let httpCode: HttpCode = ResponseRowComponent.httpCodes.getCode(code); + if (httpCode) { + return httpCode.line; + } + return ""; + } + + public statusCodeType(code: string): string { + if (code === "default") { + return ""; + } + + var icode: number = parseInt(code); + if (icode >= 200 && icode < 300) { + return "success"; + } + + if (icode >= 300 && icode < 400) { + return "redirect"; + } + + if (icode >= 400 && icode < 500) { + return "problem"; + } + + if (icode >= 500 && icode < 600) { + return "error"; + } + + return ""; + } + + protected modelForEditing(): SimplifiedType { + return SimplifiedType.fromSchema(this.response.schema); + } + + protected modelForViewing(): SimplifiedType { + return SimplifiedType.fromSchema(this.response.schema); + } + + public typeOptions(): DropDownOption[] { + let options: DropDownOption[] = super.typeOptions(); + + if (this.document.definitions) { + let co: DropDownOption[] = this.document.definitions.definitions().sort( (def1, def2) => { + return def1.definitionName().toLocaleLowerCase().localeCompare(def2.definitionName().toLocaleLowerCase()); + }).map( def => { + return { + value: "#/definitions/" + def.definitionName(), + name: def.definitionName() + }; + }); + if (co && co.length > 0) { + options.push({ divider: true }); + co.forEach( o => options.push(o) ); + } + } + + return options; + } + + public typeOfOptions(): DropDownOption[] { + let options: DropDownOption[] = super.typeOfOptions(); + + if (this.document.definitions) { + let co: DropDownOption[] = this.document.definitions.definitions().sort( (def1, def2) => { + return def1.definitionName().toLocaleLowerCase().localeCompare(def2.definitionName().toLocaleLowerCase()); + }).map( def => { + return { + value: "#/definitions/" + def.definitionName(), + name: def.definitionName() + }; + }); + if (co && co.length > 0) { + options.push({ divider: true }); + co.forEach( o => options.push(o) ); + } + } + + return options; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/typed-item.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/typed-item.component.ts new file mode 100644 index 000000000..d02881d47 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/operation/typed-item.component.ts @@ -0,0 +1,194 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Output, EventEmitter} from "@angular/core"; +import {SimplifiedType} from "oai-ts-commands"; +import {ObjectUtils} from "../../../_util/object.util"; +import {DropDownOption} from '../../../../../../../components/common/drop-down.component'; + + +export abstract class AbstractTypedItemComponent { + + @Output() onDescriptionChange: EventEmitter = new EventEmitter(); + @Output() onTypeChange: EventEmitter = new EventEmitter(); + @Output() onDelete: EventEmitter = new EventEmitter(); + + protected _editing: boolean = false; + + protected model: T; + + public type(): string { + if (!ObjectUtils.isNullOrUndefined(this.model)) { + return this.model.type; + } + return null; + } + + public typeOptions(): DropDownOption[] { + let options: DropDownOption[] = [ + { value: "array", name: "Array" }, + { divider: true }, + { value: "string", name: "String" }, + { value: "integer", name: "Integer" }, + { value: "boolean", name: "Boolean" }, + { value: "number", name: "Number" } + ]; + return options; + } + + public typeOf(): string { + if (this.model && this.model.of) { + return this.model.of.type; + } + return null; + } + + public typeAs(): string { + if (ObjectUtils.isNullOrUndefined(this.model)) { + return null; + } + if (this.model.isArray() && this.model.of && this.model.of.isSimpleType()) { + return this.model.of.as; + } + if (this.model.isSimpleType()) { + return this.model.as; + } + return null; + } + + public typeOfOptions(): DropDownOption[] { + let options: DropDownOption[] = [ + { value: "string", name: "String" }, + { value: "integer", name: "Integer" }, + { value: "boolean", name: "Boolean" }, + { value: "number", name: "Number" } + ]; + + return options; + } + + public typeAsOptions(): DropDownOption[] { + let options: DropDownOption[]; + let st: SimplifiedType = this.model; + if (this.model && this.model.isArray() && this.model.of && this.model.of.isSimpleType()) { + st = this.model.of; + } + if (st.type === "string") { + options = [ + { value: null, name: "String" }, + { value: "byte", name: "Byte" }, + { value: "binary", name: "Binary" }, + { value: "date", name: "Date" }, + { value: "date-time", name: "DateTime" }, + { value: "password", name: "Password" } + ]; + } else if (st.type === "integer") { + options = [ + { value: null, name: "Integer" }, + { value: "int32", name: "32-Bit Integer" }, + { value: "int64", name: "64-Bit Integer" } + ]; + } else if (st.type === "number") { + options = [ + { value: null, name: "Number" }, + { value: "float", name: "Float" }, + { value: "double", name: "Double" } + ]; + } + return options; + } + + public changeType(type: string): void { + this.model.type = type; + this.model.of = null; + this.model.as = null; + } + + public changeTypeOf(typeOf: string): void { + this.model.of = new SimplifiedType(); + this.model.of.type = typeOf; + this.model.as = null; + } + + public changeTypeAs(typeAs: string): void { + if (this.model.isArray() && this.model.of && this.model.of.isSimpleType()) { + this.model.of.as = typeAs; + } + if (this.model.isSimpleType()) { + this.model.as = typeAs; + } + } + + public setDescription(description: string): void { + this.onDescriptionChange.emit(description); + } + + public isEditing(): boolean { + return this._editing; + } + + protected abstract modelForEditing(): T; + + public edit(): void { + this.model = this.modelForEditing(); + this._editing = true; + } + + protected abstract modelForViewing(): T; + + public displayType(): T { + return this.modelForViewing(); + } + + public cancel(): void { + this._editing = false; + } + + public shouldShowFormattedAs(): boolean { + let st: SimplifiedType = this.model; + if (this.model && this.model.isArray() && this.model.of && this.model.of.isSimpleType()) { + st = this.model.of; + } + return st && st.isSimpleType() && (st.type !== "boolean"); + } + + public isValid(): boolean { + if (ObjectUtils.isNullOrUndefined(this.model)) { + return false; + } + if (this.model.isArray() && !ObjectUtils.isNullOrUndefined(this.model.of)) { + return this.model.of.isSimpleType() || this.model.of.isRef(); + } + return this.model.isSimpleType() || this.model.isRef(); + } + + public onGlobalKeyDown(event: KeyboardEvent): void { + if (event.key === "Escape" && !event.metaKey && !event.altKey && !event.ctrlKey) { + this.cancel(); + } + } + + public ok(): void { + this.onTypeChange.emit(this.model); + this._editing = false; + } + + public delete(): void { + this.onDelete.emit(true); + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/path-form.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/path-form.component.html new file mode 100644 index 000000000..a06d65a19 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/path-form.component.html @@ -0,0 +1,296 @@ +
    +
    + +
    +
    + +
    + + +
    + + + + +
    +
    + +
    + + +
    + +
    +
    +
    + + + + + +
    +
    + + + + +

    + This section is visible because this path has at least one dynamic path parameter. + Here you can configure information about the path parameter, such as its description + and type. This information will be inherited by all operations in the path (but can + optionally be overridden by each respective operation). +

    +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    + + + + +

    + Use this section to define HTTP Query Parameters for all of the + Operations in this path. These query parameters will apply to all operations and + can be overridden (though not removed) at the operation level. +

    +
    + + +
    +
    + +
    + + None Found + No query parameters have been declared. + +
    + + +
    + +
    +
    +
    + + +
    +
    + + + + +

    + Below you will find a list of all possible Operations that could be + supported by this path. You can quickly indicate that this path supports these operations + by clicking the appropriate Create Operation button. You can also quickly + edit the Name and Description of each operation directly + from this list. +

    +
    +

    + Note: to configure a single operation (inputs, outputs, etc) simply + click the colored operation name such as GET or PUT. +

    +
    +
    +
    + +
    +
    +
    GET
    + +
    + +
    + +
    +
    +
    +
    +
    PUT
    + +
    + +
    + +
    +
    +
    +
    +
    POST
    + +
    + +
    + +
    +
    +
    +
    +
    DELETE
    + +
    + +
    + +
    +
    +
    +
    +
    OPTIONS
    + +
    + +
    + +
    +
    +
    +
    +
    HEAD
    + +
    + +
    + +
    +
    +
    +
    +
    PATCH
    + +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + + + + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/path-form.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/path-form.component.ts new file mode 100644 index 000000000..38b9047e3 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/path-form.component.ts @@ -0,0 +1,318 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Input, Output, ViewChild, ViewEncapsulation} from "@angular/core"; +import {OasDocument, OasOperation, OasParameterBase, OasPathItem, OasPaths} from "oai-ts-core"; +import {SourceFormComponent} from "./source-form.base"; +import {ModelUtils} from "../../_util/model.util"; +import { + createAddPathItemCommand, + createChangeParameterTypeCommand, + createChangePropertyCommand, + createDeleteAllParametersCommand, + createDeleteParameterCommand, + createDeletePathCommand, + createNewOperationCommand, + createNewParamCommand, + createNewPathCommand, + createReplacePathItemCommand, ICommand, + SimplifiedParameterType +} from "oai-ts-commands"; +import {AddQueryParamDialogComponent} from "../dialogs/add-query-param.component"; +import {ClonePathDialogComponent} from "../dialogs/clone-path.component"; +import {AddPathDialogComponent} from "../dialogs/add-path.component"; +import {NodeSelectionEvent} from "../../_events/node-selection.event"; + + +@Component({ + moduleId: module.id, + selector: "path-form", + templateUrl: "path-form.component.html", + encapsulation: ViewEncapsulation.None +}) +export class PathFormComponent extends SourceFormComponent { + + protected _path: OasPathItem; + @Input() + set path(path: OasPathItem) { + this._path = path; + this.sourceNode = path; + } + get path(): OasPathItem { + return this._path; + } + + @Output() onDeselect: EventEmitter = new EventEmitter(); + + @ViewChild("addQueryParamDialog") public addQueryParamDialog: AddQueryParamDialogComponent; + @ViewChild("clonePathDialog") clonePathDialog: ClonePathDialogComponent; + @ViewChild("addPathDialog") addPathDialog: AddPathDialogComponent; + + protected createEmptyNodeForSource(): OasPathItem { + return (this.path.parent()).createPathItem(this.path.path()); + } + + protected createReplaceNodeCommand(node: OasPathItem) { + return createReplacePathItemCommand(this.path.ownerDocument(), this.path as any, node as any); + } + + public document(): OasDocument { + return this.path.ownerDocument(); + } + + public hasGet(): boolean { + return this.path.get !== undefined && this.path.get !== null; + } + public hasPut(): boolean { + return this.path.put !== undefined && this.path.put !== null; + } + public hasPost(): boolean { + return this.path.post !== undefined && this.path.post !== null; + } + public hasDelete(): boolean { + return this.path.delete !== undefined && this.path.delete !== null; + } + public hasOptions(): boolean { + return this.path.options !== undefined && this.path.options !== null; + } + public hasHead(): boolean { + return this.path.head !== undefined && this.path.head !== null; + } + public hasPatch(): boolean { + return this.path.patch !== undefined && this.path.patch !== null; + } + + public getSummary(): string { + return this.summary(this.path.get); + } + + public putSummary(): string { + return this.summary(this.path.put); + } + + public postSummary(): string { + return this.summary(this.path.post); + } + + public deleteSummary(): string { + return this.summary(this.path.delete); + } + + public optionsSummary(): string { + return this.summary(this.path.options); + } + + public patchSummary(): string { + return this.summary(this.path.patch); + } + + public headSummary(): string { + return this.summary(this.path.head); + } + + private summary(operation: OasOperation): string { + if (operation === null || operation === undefined) { + return "Not Supported"; + } else { + if (operation.summary) { + return operation.summary; + } else { + return null; + } + } + } + + public getDescription(): string { + return this.description(this.path.get); + } + + public putDescription(): string { + return this.description(this.path.put); + } + + public postDescription(): string { + return this.description(this.path.post); + } + + public deleteDescription(): string { + return this.description(this.path.delete); + } + + public optionsDescription(): string { + return this.description(this.path.options); + } + + public patchDescription(): string { + return this.description(this.path.patch); + } + + public headDescription(): string { + return this.description(this.path.head); + } + + private description(operation: OasOperation): string { + if (operation === null || operation === undefined) { + return "Not Supported"; + } else { + if (operation.description) { + return operation.description; + } else { + return null; + } + } + } + + public selectOperation(operation: OasOperation): void { + this.onNodeSelected.emit(new NodeSelectionEvent(operation, "operation")); + } + + public createOperation(operationType: string): void { + let command: ICommand = createNewOperationCommand(this.path.ownerDocument(), this.path.path(), operationType); + this.onCommand.emit(command); + } + + public changeSummary(newSummary: string, operation: OasOperation): void { + let command: ICommand = createChangePropertyCommand(this.path.ownerDocument(), operation, "summary", newSummary); + this.onCommand.emit(command); + } + + public changeDescription(newDescription: string, operation: OasOperation): void { + let command: ICommand = createChangePropertyCommand(this.path.ownerDocument(), operation, "description", newDescription); + this.onCommand.emit(command); + } + + public delete(): void { + let command: ICommand = createDeletePathCommand(this.path.ownerDocument(), this.path.path()); + this.onCommand.emit(command); + this.onDeselect.emit(true); + } + + public newPath(): void { + this.addPathDialog.open(this.path.ownerDocument(), this.path.path()); + } + + public addPath(path: string): void { + let command: ICommand = createNewPathCommand(this.path.ownerDocument(), path); + this.onCommand.emit(command); + } + + public clone(modalData?: any): void { + if (undefined === modalData || modalData === null) { + this.clonePathDialog.open(this.path.ownerDocument(), this.path); + } else { + let pathItem: OasPathItem = modalData.object; + console.info("[PathFormComponent] Clone path item: %s", modalData.path); + let cloneSrcObj: any = this.oasLibrary().writeNode(pathItem); + let command: ICommand = createAddPathItemCommand(this.path.ownerDocument(), modalData.path, cloneSrcObj); + this.onCommand.emit(command); + } + } + + public canHavePathParams(): boolean { + return this.path.path().indexOf('{') != -1; + } + + public pathParam(paramName: string): OasParameterBase { + let param: OasParameterBase = this.path.parameter("path", paramName) as OasParameterBase; + + if (param === null) { + param = this.path.createParameter(); + param.in = "path"; + param.name = paramName; + param.required = true; + param.n_attribute("missing", true); + } + + return param; + } + + public pathParameters(): OasParameterBase[] { + let pathParamNames: string[] = ModelUtils.detectPathParamNames(this.path.path()); + return pathParamNames.map( pname => { + return this.pathParam(pname); + }); + } + + public hasParameters(type: string): boolean { + if (!this.path.parameters) { + return false; + } + return this.path.parameters.filter((value) => { + return value.in === type; + }).length > 0; + } + + public parameters(paramType: string): OasParameterBase[] { + if (!this.path.parameters) { + return []; + } + let params: OasParameterBase[] = this.path.parameters; + return params.filter( value => { + return value.in === paramType; + }).sort((param1, param2) => { + return param1.name.localeCompare(param2.name); + }); + } + + public createPathParam(paramName: string): void { + let command: ICommand = createNewParamCommand(this.path.ownerDocument(), this.path as any, paramName, "path"); + this.onCommand.emit(command); + } + + public changeParamDescription(param: OasParameterBase, newParamDescription: string): void { + let command: ICommand = createChangePropertyCommand(this.path.ownerDocument(), param, "description", newParamDescription); + this.onCommand.emit(command); + } + + public changeParamType(param: OasParameterBase, newType: SimplifiedParameterType): void { + let command: ICommand = createChangeParameterTypeCommand(this.path.ownerDocument(), param as any, newType); + this.onCommand.emit(command); + } + + public queryParameters(): OasParameterBase[] { + return this.parameters("query"); + } + + public deleteParam(parameter: OasParameterBase): void { + let command: ICommand = createDeleteParameterCommand(this.path.ownerDocument(), parameter as any); + this.onCommand.emit(command); + } + + public openAddQueryParamModal(): void { + this.addQueryParamDialog.open(); + } + + public addQueryParam(name: string): void { + let command: ICommand = createNewParamCommand(this.path.ownerDocument(), this.path as any, name, "query"); + this.onCommand.emit(command); + } + + public deleteAllQueryParams(): void { + let command: ICommand = createDeleteAllParametersCommand(this.path.ownerDocument(), this.path as any, "query"); + this.onCommand.emit(command); + } + + public formType(): string { + return "path"; + } + + public enableSourceMode(): void { + this.sourceNode = this.path; + super.enableSourceMode(); + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/problem-form.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/problem-form.component.html new file mode 100644 index 000000000..1d4f7a772 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/problem-form.component.html @@ -0,0 +1,49 @@ +
    +
    + + {{ problem.errorCode }} +
    + +
    +
    + + +
    +
    + + + + +

    This section provides additional information about the Validation Problem we found.

    +
    +
    +
    +

    {{ problem.message }}

    + +
    {{ problem.nodePath.toString() }}
    + +
    +
    {{ explanation() }}
    +
    +
    + +
    +
    +
    +
    diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/problem-form.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/problem-form.component.ts new file mode 100644 index 000000000..bf4e32bee --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/problem-form.component.ts @@ -0,0 +1,45 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Component, EventEmitter, Input, Output, ViewEncapsulation} from "@angular/core"; +import {OasValidationError} from "oai-ts-core"; +import {ProblemsService} from "../../_services/problems.service"; +import {ICommand} from "oai-ts-commands"; + + +@Component({ + moduleId: module.id, + selector: "problem-form", + templateUrl: "problem-form.component.html", + encapsulation: ViewEncapsulation.None +}) +export class ProblemFormComponent { + + private static problems: ProblemsService = new ProblemsService(); + + @Input() problem: OasValidationError; + @Output() onCommand: EventEmitter = new EventEmitter(); + @Output() onGoToProblem: EventEmitter = new EventEmitter(); + + explanation(): string { + return ProblemFormComponent.problems.explanation(this.problem); + } + + goToProblem(): void { + this.onGoToProblem.emit(true); + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/shared/servers.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/shared/servers.component.html new file mode 100644 index 000000000..50402f75a --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/shared/servers.component.html @@ -0,0 +1,46 @@ +
    +
    + + + + +

    {{ description }}

    +
    + +
    +
    +
    + + None Found + No servers have been defined. + +
    + + + + + + + + + + + + + + + +
    ServerDescription
    +
    + + +
    +
    +
    +
    + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/shared/servers.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/shared/servers.component.ts new file mode 100644 index 000000000..d57297c37 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/shared/servers.component.ts @@ -0,0 +1,128 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, Input, Output, ViewEncapsulation} from "@angular/core"; +import {Oas30Document, Oas30Operation, Oas30PathItem, Oas30Server, Oas30ServerVariable} from "oai-ts-core"; +import { + createChangePropertyCommand, createChangeServerCommand, createDeleteServerCommand, createNewServerCommand, + ICommand +} from "oai-ts-commands"; +import {ObjectUtils} from "../../../_util/object.util"; +import {ServerEventData} from "../../dialogs/add-server.component"; + + +@Component({ + moduleId: module.id, + selector: "servers-section", + templateUrl: "servers.component.html", + encapsulation: ViewEncapsulation.None +}) +export class ServersSectionComponent { + + @Input() parent: Oas30Document | Oas30PathItem | Oas30Operation; + @Input() description: string; + + @Output() onCommand: EventEmitter = new EventEmitter(); + + /** + * Returns the list of global servers defined in the document. + * @return {Oas30Server[]} + */ + public servers(): Oas30Server[] { + let servers: Oas30Server[] = this.parent.servers; + if (ObjectUtils.isNullOrUndefined(servers)) { + servers = []; + } + // Clone the array + servers = servers.slice(0); + // Sort it + servers.sort( (obj1, obj2) => { + return obj1.url.toLowerCase().localeCompare(obj2.url.toLowerCase()); + }); + return servers; + } + + /** + * Called when the user changes the description of a server. + * @param server + * @param description + */ + public changeServerDescription(server: Oas30Server, description: string): void { + // TODO create a new ChangeServerDescription command as it's a special case when used in a multi-user editing environment + let command: ICommand = createChangePropertyCommand(this.parent.ownerDocument(), server, "description", description); + this.onCommand.emit(command); + } + + /** + * Called when the user chooses to delete a server. + * @param server + */ + public deleteServer(server: Oas30Server): void { + let command: ICommand = createDeleteServerCommand(this.parent.ownerDocument(), server); + this.onCommand.emit(command); + } + + /** + * Called when the user adds a new server. + * @param {Server30EventData} event + */ + public addServer(event: ServerEventData): void { + console.info("[MainFormComponent] Adding a server: %s", event.url); + + let newServer: Oas30Server = this.parent.createServer(); + + this.copyServerToModel(event, newServer); + + let command: ICommand = createNewServerCommand(this.parent.ownerDocument(), this.parent, newServer); + this.onCommand.emit(command); + } + + /** + * Called when the user edits an existing server. + * @param {ServerEventData} event + */ + public changeServer(event: ServerEventData): void { + console.info("[MainFormComponent] Editing a server: %s", event.url); + + let newServer: Oas30Server = this.parent.createServer(); + + this.copyServerToModel(event, newServer); + + let command: ICommand = createChangeServerCommand(this.parent.ownerDocument(), newServer); + this.onCommand.emit(command); + } + + /** + * Copies the data from the event to the new server model. + * @param {ServerEventData} fromData + * @param {Oas30Server} toServer + */ + private copyServerToModel(fromData: ServerEventData, toServer: Oas30Server): void { + toServer.url = fromData.url; + toServer.description = fromData.description; + if (fromData.variables) { + for (let varName in fromData.variables) { + let serverVar: Oas30ServerVariable = toServer.createServerVariable(varName); + serverVar.default = fromData.variables[varName].default; + serverVar.description = fromData.variables[varName].description; + serverVar.enum = fromData.variables[varName].enum; + toServer.addServerVariable(varName, serverVar); + } + } + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/source-form.base.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/source-form.base.ts new file mode 100644 index 000000000..1520f9ec6 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/forms/source-form.base.ts @@ -0,0 +1,180 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {EventEmitter, Output, ViewChild} from "@angular/core"; +import {OasLibraryUtils, OasNode} from "oai-ts-core"; + +import "brace/theme/eclipse"; +import "brace/mode/json"; +import "brace/mode/yaml"; +import {AceEditorDirective} from "ng2-ace-editor"; +import {ObjectUtils} from "../../_util/object.util"; +import * as YAML from "yamljs"; +import {NodeSelectionEvent} from "../../_events/node-selection.event"; +import {ICommand} from "oai-ts-commands"; + + +/** + * Base class for all forms that support a "Source" tab. + */ +export abstract class SourceFormComponent { + + private static library: OasLibraryUtils = new OasLibraryUtils(); + + @Output() onCommand: EventEmitter = new EventEmitter(); + @Output() onNodeSelected: EventEmitter = new EventEmitter(); + + private _mode: string = "design"; + private _sourceFormat: string = "yaml"; + private _sourceNode: T; + private _sourceJsObj: any = null; + set sourceNode(node: T) { + this._sourceNode = node; + this._sourceJsObj = null; + } + get sourceNode(): T { + return this._sourceNode; + } + + @ViewChild("sourceEditor") sourceEditor: AceEditorDirective; + + private _source: any = { + dirty: false, + valid: false, + value: null + }; + + public sourceJs(): any { + if (this._sourceJsObj === null) { + this._sourceJsObj = SourceFormComponent.library.writeNode(this.sourceNode); + } + return this._sourceJsObj; + } + + public source(): string { + if (this._sourceFormat === "yaml") { + return YAML.stringify(this.sourceJs(), 100, 4); + } else { + return JSON.stringify(this.sourceJs(), null, 4); + } + } + + public updateSource(newSource: any): void { + try { + let newJsObject: any; + if (this.sourceFormat() === "yaml") { + newJsObject = YAML.parse(newSource); + } else { + newJsObject = JSON.parse(newSource); + } + let currentJsObj: any = this.sourceJs(); + this._source.dirty = !ObjectUtils.objectEquals(currentJsObj, newJsObject); + this._source.value = SourceFormComponent.library.readNode(newJsObject, this.createEmptyNodeForSource()); + this._source.valid = true; + } catch (e) { + this._source.value = null; + this._source.valid = false; + this._source.dirty = true; + } + } + + protected abstract createEmptyNodeForSource(): T; + + public canFormatSource(): boolean { + return this._source.valid; + } + + public canRevertSource(): boolean { + return this._source.dirty; + } + + public canSaveSource(): boolean { + return this._source.dirty && this._source.valid; + } + + public revertSource(): void { + this.sourceEditor.setText(this.source()); + this._source.dirty = false; + this._source.value = null; + this._source.valid = false; + } + + public saveSource(): void { + let command: ICommand = this.createReplaceNodeCommand(this._source.value); + this.onCommand.emit(command); + this.onNodeSelected.emit(new NodeSelectionEvent(this._source.value, this.formType())); + this.sourceNode = this._source.value; + this._source.dirty = false; + this._source.value = null; + this._source.valid = true; + } + + public abstract formType(): string; + + public sourceFormat(): string { + return this._sourceFormat; + } + + public toggleSourceFormat(): void { + if (this._sourceFormat === "yaml") { + this.setSourceFormat("json"); + } else { + this.setSourceFormat("yaml"); + } + } + + public setSourceFormat(sourceFormat: string): void { + this._sourceFormat = sourceFormat; + } + + public formatSource(): void { + let nsrc: any = SourceFormComponent.library.writeNode(this._source.value); + let nsrcStr: string; + if (this._sourceFormat === "yaml") { + nsrcStr = YAML.stringify(this.sourceJs(), 100, 4); + } else { + nsrcStr = JSON.stringify(this.sourceJs(), null, 4); + } + this.sourceEditor.setText(nsrcStr); + } + + protected abstract createReplaceNodeCommand(node: T): ICommand; + + public isDesignMode(): boolean { + return this._mode === "design"; + } + + public isSourceMode(): boolean { + return this._mode === "source"; + } + + public enableDesignMode(): void { + // Cannot change tabs if the source editor is dirty. + if (this._source.dirty) { + return; + } + this._mode = "design"; + } + + public enableSourceMode(): void { + this._mode = "source"; + } + + public oasLibrary(): OasLibraryUtils { + return SourceFormComponent.library; + } +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/master.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/master.component.html new file mode 100644 index 000000000..4a1fe4cca --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/master.component.html @@ -0,0 +1,204 @@ +
    + + + + + + + + + + +
    +

    {{ document.info.title }}

    +
    + + + + + +
    + +
    + + No validation problems found! +
    + +
    + + {{ problem.errorCode }} + {{ problem.message }} +
    +
    + + +
    + +
    +
    + + + + +

    + The core of any REST API is the set of resources/paths it exposes. Each path is of + the form /path/to/resource and can support a number of operations such + as GET, POST, and DELETE. +

    +
    +

    + Note that paths can have parameters, which are defined using bracket syntax, like this: +

    +
    /root/resources/{{ '{' }}resourceId}/subresource/{{ '{' }}subresourceId}
    +

    + The result is a path with two dynamic components: resourceId and subresourceId. +

    +
    + +
    +
    +
    +
    +
    + GET + PUT + POST + DELETE + OPTIONS + HEAD + PATCH + TRACE +   +
    +
    +
    + + No Paths Found! + APIs need at least one path. +   + +
    +
    +
    + + +
    +
    + + + + +

    + It is often necessary for many operations to return the same type of data + (or a list of that same data type). Rather than define the same data type + multiple times within each operation, you can create named Definitions + in this section. These can then be referenced in the Request Body + and also Responses of any Operation in the API. +

    +
    + +
    +
    +
    +
    + + None Found + Reusable types are useful! +   + +
    +
    +
    + + + +
    + + + + + + +
    diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/master.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/master.component.ts new file mode 100644 index 000000000..faf9c46f1 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_components/master.component.ts @@ -0,0 +1,805 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, EventEmitter, HostListener, Input, Output, ViewChild} from "@angular/core"; +import { + Oas20Document, + Oas20ResponseDefinition, + Oas20SchemaDefinition, + Oas30Document, + Oas30ResponseDefinition, + Oas30SchemaDefinition, + OasDocument, + OasLibraryUtils, + OasNode, OasNodePath, + OasOperation, + OasPathItem, + OasValidationError, + OasVisitorUtil +} from "oai-ts-core"; +import {AddPathDialogComponent} from "./dialogs/add-path.component"; +import {ClonePathDialogComponent} from "./dialogs/clone-path.component"; +import {CloneDefinitionDialogComponent} from "./dialogs/clone-definition.component"; +import {FindPathItemsVisitor} from "../_visitors/path-items.visitor"; +import {FindSchemaDefinitionsVisitor} from "../_visitors/schema-definitions.visitor"; +import {ObjectUtils} from "../_util/object.util"; +import {AllNodeVisitor} from "../_visitors/base.visitor"; +import {NodeSelectionEvent} from "../_events/node-selection.event"; +import { + createAddPathItemCommand, + createAddSchemaDefinitionCommand, + createDeletePathCommand, + createDeleteSchemaDefinitionCommand, + createNewPathCommand, + createNewSchemaDefinitionCommand, + createDeleteOperationCommand, ICommand +} from "oai-ts-commands"; +import {ModelUtils} from "../_util/model.util"; +import {ApiEditorUser} from "../../../../../models/editor-user.model"; + + +/** + * The component that models the master view of the API editor. This is the + * left-hand side of the editor, which lists things like Paths and Definitions. + * Users will select an item in this master panel which will result in a form + * being displayed in the detail panel. + */ +@Component({ + moduleId: module.id, + selector: "master", + templateUrl: "master.component.html" +}) +export class EditorMasterComponent { + + @Input() document: OasDocument; + @Input() validationErrors: OasValidationError[]; + @Output() onCommand: EventEmitter = new EventEmitter(); + @Output() onNodeSelected: EventEmitter = new EventEmitter(); + + private _library: OasLibraryUtils = new OasLibraryUtils(); + + selectedItem: any = null; + selectedType: string = "main"; + currentExternalSelections: ExternalSelections = new ExternalSelections(); + + contextMenuItem: any = null; + contextMenuType: string = null; + contextMenuPos: any = { + left: "0px", + top: "0px" + }; + + @ViewChild("addPathDialog") addPathDialog: AddPathDialogComponent; + @ViewChild("clonePathDialog") clonePathDialog: ClonePathDialogComponent; + @ViewChild("cloneDefinitionDialog") cloneDefinitionDialog: CloneDefinitionDialogComponent; + + filterCriteria: string = null; + + validationPanelOpen = false; + + public isOAI30(): boolean { + return this.document.getSpecVersion().indexOf("3.0") === 0; + } + + public isSwagger2(): boolean { + return this.document.getSpecVersion() === "2.0"; + } + + /** + * Returns an array of paths that match the filter criteria and are sorted alphabetically. + * @return {OasPathItem[]} + */ + public paths(): OasPathItem[] { + let viz: FindPathItemsVisitor = new FindPathItemsVisitor(this.filterCriteria); + OasVisitorUtil.visitTree(this.document, viz); + return viz.getSortedPathItems(); + } + + /** + * Returns the array of definitions, filtered by search criteria and sorted. + * @return {(Oas20SchemaDefinition | Oas30SchemaDefinition)[]} + */ + public definitions(): (Oas20SchemaDefinition | Oas30SchemaDefinition)[] { + let viz: FindSchemaDefinitionsVisitor = new FindSchemaDefinitionsVisitor(this.filterCriteria); + OasVisitorUtil.visitTree(this.document, viz); + return viz.getSortedSchemaDefinitions(); + } + + /** + * Returns an array of responses filtered by the search criteria and sorted. + * @return {(Oas20ResponseDefinition | Oas30ResponseDefinition)[]} + */ + public responses(): (Oas20ResponseDefinition | Oas30ResponseDefinition)[] { + return []; + // if (this.document.responses) { + // return this.document.responses.responses().filter( response => { + // if (this.acceptThroughFilter(response.name())) { + // return response; + // } else { + // return null; + // } + // }).sort( (response1, response2) => { + // return response1.name().localeCompare(response2.name()); + // }); + // } else { + // return []; + // } + } + + /** + * Validates the current selected item. If it does not exist, we'll force-select + * 'main' instead. + */ + public validateSelection(): void { + if (this.selectedType === "path") { + let pathItem: OasPathItem = this.selectedItem as OasPathItem; + if (!this.isValidPathItem(pathItem)) { + this.selectMain(); + } + } else if (this.selectedType === "operation") { + let operation: OasOperation = this.selectedItem as OasOperation; + if (!this.isValidOperation(operation)) { + this.selectMain(); + } + } else if (this.selectedType === "definition") { + let definition: Oas20SchemaDefinition | Oas30SchemaDefinition = this.selectedItem; + if (!this.isValidDefinition(definition)) { + this.selectMain(); + } + } else if (this.selectedType === "response") { + let response: Oas20ResponseDefinition | Oas30ResponseDefinition = this.selectedItem; + if (!this.isValidResponse(response)) { + this.selectMain(); + } + } else if (this.selectedType === "problem") { + if (!(this.validationErrors && this.validationErrors.indexOf(this.selectedItem) !== -1)) { + this.selectMain(); + } + } + } + + /** + * Returns true if the given item is a valid path in the current document. + * @param {OasPathItem} pathItem + * @return {boolean} + */ + protected isValidPathItem(pathItem: OasPathItem): boolean { + if (ObjectUtils.isNullOrUndefined(pathItem)) { + return false; + } + if (ObjectUtils.isNullOrUndefined(this.document.paths)) { + return false; + } + let pi: any = this.document.paths.pathItem(pathItem.path()); + return pi === pathItem; + } + + /** + * Returns true if the given operation is a valid operation contained within the + * current document. + * @param {OasOperation} operation + * @return {boolean} + */ + protected isValidOperation(operation: OasOperation): boolean { + let pathItem: OasPathItem = operation.parent() as OasPathItem; + + if (ObjectUtils.isNullOrUndefined(operation)) { + return false; + } + + if (!this.isValidPathItem(pathItem)) { + return false; + } + + let pi: any = this.document.paths.pathItem(pathItem.path()); + let op: any = pi[operation.method()]; + + return op === operation; + } + + /** + * Returns true if the given schema definition is valid and contained within the + * current document. + * @param {Oas20SchemaDefinition | Oas30SchemaDefinition} definition + * @return {boolean} + */ + protected isValidDefinition(definition: Oas20SchemaDefinition | Oas30SchemaDefinition): boolean { + if (ObjectUtils.isNullOrUndefined(definition)) { + return false; + } + return this.definitions().indexOf(definition) !== -1; + } + + /** + * Returns true if the given response is valid and contained within the + * current document. + * @param {Oas20ResponseDefinition | Oas30ResponseDefinition} response + * @return {boolean} + */ + protected isValidResponse(response: Oas20ResponseDefinition | Oas30ResponseDefinition): boolean { + if (ObjectUtils.isNullOrUndefined(response)) { + return false; + } + return this.responses().indexOf(response) !== -1; + } + + /** + * Called when the user selects the main/default element from the master area. + */ + public selectMain(): void { + this.selectedItem = null; + this.selectedType = "main"; + this.fireNodeSelectedEvent(); + } + + /** + * Called when the user selects a path from the master area. + * @param {OasPathItem} path + */ + public selectPath(path: OasPathItem): void { + this.selectedItem = path; + this.selectedType = "path"; + this.fireNodeSelectedEvent(); + } + + /** + * Called to deselect the currently selected path. + */ + public deselectPath(): void { + this.selectMain(); + } + + /** + * Called when the user clicks an operation. + * @param operation + */ + public selectOperation(operation: OasOperation): void { + // Possible de-select the operation if it's clicked on but already selected. + if (this.selectedType === "operation" && this.selectedItem === operation) { + this.selectPath(operation.parent() as OasPathItem); + } else { + this.selectedItem = operation; + this.selectedType = "operation"; + this.fireNodeSelectedEvent(); + } + } + + /** + * Called to deselect the currently selected operation. + */ + public deselectOperation(): void { + if (this.selectedType !== "operation") { + return; + } + this.selectedItem = this.selectedItem.parent(); + this.selectedType = "path"; + this.fireNodeSelectedEvent(); + } + + /** + * Called when the user does something to cause the selection to change. + * @param event + */ + public selectNode(event: NodeSelectionEvent): void { + this.selectedItem = event.node; + this.selectedType = event.type; + this.fireNodeSelectedEvent(); + } + + /** + * Called when the user selects a definition from the master area. + * @param def + */ + public selectDefinition(def: Oas20SchemaDefinition | Oas30SchemaDefinition): void { + this.selectedItem = def; + this.selectedType = "definition"; + this.fireNodeSelectedEvent(); + } + + /** + * Called when the user selects a validation problem from the master + * area. + * @param problem + */ + public selectProblem(problem: OasValidationError): void { + this.selectedItem = problem; + this.selectedType = "problem"; + this.fireNodeSelectedEvent(); + } + + /** + * Deselects the currently selected definition. + */ + public deselectDefinition(): void { + console.info("[EditorMasterComponent] Deselecting the current definition (selecting main)."); + this.selectMain(); + } + + /** + * Called when the user selects a response from the master area. + * @param {Oas20ResponseDefinition | Oas30ResponseDefinition} response + */ + public selectResponse(response: Oas20ResponseDefinition | Oas30ResponseDefinition): void { + this.selectedItem = response; + this.selectedType = "response"; + this.fireNodeSelectedEvent(); + } + + /** + * Deselects the currently selected response. + */ + public deselectResponse(): void { + this.selectMain(); + } + + /** + * Called whenever the user presses a key. + * @param event + */ + public onGlobalKeyDown(event: KeyboardEvent): void { + if (event.key === "Escape" && !event.metaKey && !event.altKey && !event.ctrlKey) { + this.closeContextMenu(); + } + } + + /** + * Called to return the currently selected path (if one is selected). If not, returns "/". + */ + public getCurrentPathSelection(): string { + if (this.selectedType === "path") { + return (this.selectedItem as OasPathItem).path(); + } + if (this.selectedType === "operation") { + return ((this.selectedItem as OasOperation).parent() as OasPathItem).path(); + } + return "/"; + } + + /** + * Called to update the selection state of the given remote API editor (i.e. an active collaborator). + * @param {ApiEditorUser} user + * @param {string} selection + */ + public updateCollaboratorSelection(user: ApiEditorUser, selection: string): void { + this.currentExternalSelections.setSelection(user, selection, this.document); + } + + /** + * Returns the selection style to use for the given (potentially selected) node. + * @param {OasNode} item + * @param {string} nodeType + * @return {string} + */ + public collaboratorSelectionClasses(item: OasNode, nodeType: string): string { + if (item) { + let user: ApiEditorUser = ModelUtils.isSelectedByCollaborator(item); + if (user != null && user.attributes["id"]) { + return user.attributes["id"]; + } + // pathItems - if a child operation is selected then the path is too + if (nodeType === "pathItem") { + let operationNames: string[] = [ "get", "put", "post", "delete", "options", "head", "patch", "trace" ]; + for (let opName of operationNames) { + let operation: OasOperation = item[opName] as OasOperation; + if (operation) { + user = ModelUtils.isSelectedByCollaborator(operation); + if (user != null && user.attributes["id"]) { + return user.attributes["id"]; + } + } + } + } + } + return ""; + } + + /** + * Called when the user fills out the Add Path modal dialog and clicks Add. + * @param {string} path + */ + public addPath(path: string): void { + let command: ICommand = createNewPathCommand(this.document, path); + this.onCommand.emit(command); + this.selectPath(this.document.paths.pathItem(path) as OasPathItem); + } + + /** + * Called to test whether the given resource path has an operation of the given type defined. + * @param {OasPathItem} pathItem + * @param {string} operation + * @return {boolean} + */ + public hasOperation(pathItem: OasPathItem, operation: string): boolean { + let op: OasOperation = pathItem[operation]; + return !ObjectUtils.isNullOrUndefined(op); + } + + /** + * Returns true if the given path is the currently selected item *or* is the parent + * of the currently selected item. + * @param {OasPathItem} pathItem + * @return {boolean} + */ + public isPathSelected(pathItem: OasPathItem): boolean { + return this.selectedItem && (this.selectedItem === pathItem || (this.selectedItem.parent && this.selectedItem.parent() === pathItem)); + } + + /** + * Returns true if the given path is the current context menu item *or* is the parent + * of the current context menu item. + * @param {OasPathItem} pathItem + * @return {boolean} + */ + public isPathContexted(pathItem: OasPathItem): boolean { + return this.contextMenuItem && (this.contextMenuItem === pathItem || (this.contextMenuItem.parent && this.contextMenuItem.parent() === pathItem)); + } + + /** + * Returns true if the given path item has at least one operation. + * @param {OasPathItem} pathItem + * @return {boolean} + */ + public hasAtLeastOneOperation(pathItem: OasPathItem): boolean { + if (pathItem) { + if (pathItem.get) { + return true; + } + if (pathItem.put) { + return true; + } + if (pathItem.post) { + return true; + } + if (pathItem.delete) { + return true; + } + if (pathItem.options) { + return true; + } + if (pathItem.head) { + return true; + } + if (pathItem.patch) { + return true; + } + } + return false; + } + + /** + * Called when the user fills out the Add Definition modal dialog and clicks Add. + */ + public addDefinition(modalData: any): void { + let example: string = (modalData.example === "") ? null : modalData.example; + let command: ICommand = createNewSchemaDefinitionCommand(this.document, modalData.name, example); + this.onCommand.emit(command); + this.selectDefinition(this.getDefinitionByName(modalData.name)); + } + + /** + * Gets a definition by its name. + * @param {string} name + * @return {Oas20SchemaDefinition | Oas30SchemaDefinition} + */ + protected getDefinitionByName(name: string): Oas20SchemaDefinition | Oas30SchemaDefinition { + if (this.document.getSpecVersion() === "2.0") { + return (this.document as Oas20Document).definitions.definition(name); + } else { + return (this.document as Oas30Document).components.getSchemaDefinition(name); + } + } + + /** + * Called when the user searches in the master area. + * @param criteria + */ + public filterAll(criteria: string): void { + console.info("[EditorMasterComponent] Filtering master items: %s", criteria); + this.filterCriteria = criteria; + if (this.filterCriteria !== null) { + this.filterCriteria = this.filterCriteria.toLowerCase(); + } + } + + /** + * Called when the user right-clicks on a path. + * @param event + * @param pathItem + */ + public showPathContextMenu(event: MouseEvent, pathItem: OasPathItem): void { + event.preventDefault(); + event.stopPropagation(); + this.contextMenuPos.left = event.clientX + "px"; + this.contextMenuPos.top = event.clientY + "px"; + this.contextMenuType = "path"; + this.contextMenuItem = pathItem; + } + + /** + * Called when the user right-clicks on an operation. + * @param {MouseEvent} event + * @param {OasOperation} operation + */ + public showOperationContextMenu(event: MouseEvent, operation: OasOperation): void { + event.preventDefault(); + event.stopPropagation(); + this.contextMenuPos.left = event.clientX + "px"; + this.contextMenuPos.top = event.clientY + "px"; + this.contextMenuType = "operation"; + this.contextMenuItem = operation; + } + + /** + * Called when the user clicks somewhere in the document. Used to close the context + * menu if it is open. + */ + @HostListener("document:click", ["$event"]) + public onDocumentClick(): void { + this.closeContextMenu(); + } + + /** + * Closes the context menu. + */ + private closeContextMenu(): void { + this.contextMenuItem = null; + this.contextMenuType = null; + } + + /** + * Called when the user clicks "New Path" in the context-menu for a path. + */ + public newPath(): void { + this.addPathDialog.open(this.document, (this.contextMenuItem as OasPathItem).path()); + this.closeContextMenu(); + } + + /** + * Called when the user clicks "Delete Path" in the context-menu for a path. + */ + public deletePath(): void { + let command: ICommand = createDeletePathCommand(this.document, (this.contextMenuItem as OasPathItem).path()); + this.onCommand.emit(command); + if (this.contextMenuItem === this.selectedItem) { + this.selectMain(); + } + this.closeContextMenu(); + } + + /** + * Called when the user clicks "Clone Path" in the context-menu for a path item. + */ + public clonePath(modalData?: any): void { + if (undefined === modalData || modalData === null) { + this.clonePathDialog.open(this.document, this.contextMenuItem as OasPathItem); + } else { + let pathItem: OasPathItem = modalData.object; + console.info("[EditorMasterComponent] Clone path item: %s", modalData.path); + let cloneSrcObj: any = this._library.writeNode(pathItem); + let command: ICommand = createAddPathItemCommand(this.document, modalData.path, cloneSrcObj); + this.onCommand.emit(command); + } + } + + /** + * Called when the user clicks "Delete Operation" in the context-menu for a operation. + */ + public deleteOperation(): void { + let operation: OasOperation = this.contextMenuItem as OasOperation; + let command: ICommand = createDeleteOperationCommand(this.document, operation.method(), operation.parent() as OasPathItem); + this.onCommand.emit(command); + if (this.contextMenuItem === this.selectedItem) { + this.selectPath((this.selectedItem as OasOperation).parent() as OasPathItem); + } + this.closeContextMenu(); + } + + /** + * Called when the user right-clicks on a path. + * @param event + * @param definition + */ + public showDefinitionContextMenu(event: MouseEvent, definition: Oas20SchemaDefinition | Oas30SchemaDefinition): void { + event.preventDefault(); + event.stopPropagation(); + this.contextMenuPos.left = event.clientX + "px"; + this.contextMenuPos.top = event.clientY + "px"; + this.contextMenuType = "definition"; + this.contextMenuItem = definition; + } + + /** + * Called when the user clicks the "Delete Definition" item in the context-menu for a definition. + */ + public deleteDefinition(): void { + let schemaDefName: string = null; + if (this.document.getSpecVersion() === "2.0") { + schemaDefName = (this.contextMenuItem as Oas20SchemaDefinition).definitionName(); + } else { + schemaDefName = (this.contextMenuItem as Oas30SchemaDefinition).name(); + } + let command: ICommand = createDeleteSchemaDefinitionCommand(this.document, schemaDefName); + this.onCommand.emit(command); + if (this.contextMenuItem === this.selectedItem) { + this.selectMain(); + } + this.closeContextMenu(); + } + + /** + * Called when the user clicks "Clone Definition" in the context-menu for a definition. + */ + public cloneDefinition(modalData?: any): void { + if (undefined === modalData || modalData === null) { + this.cloneDefinitionDialog.open(this.document, this.contextMenuItem as any); + } else { + let definition: OasNode = modalData.definition; + console.info("[EditorMasterComponent] Clone definition: %s", modalData.name); + let cloneSrcObj: any = this._library.writeNode(definition); + let command: ICommand = createAddSchemaDefinitionCommand(this.document, modalData.name, cloneSrcObj); + this.onCommand.emit(command); + } + } + + /** + * Called to toggle the visibility of the validation panel (the section that + * displays validation errors). + */ + public toggleValidationPanel(): void { + this.validationPanelOpen = !this.validationPanelOpen; + } + + /** + * Returns the name of the definition. + * @param {Oas20SchemaDefinition | Oas30SchemaDefinition} definition + * @return {string} + */ + public definitionName(definition: Oas20SchemaDefinition | Oas30SchemaDefinition): string { + return definition.ownerDocument().getSpecVersion() === "2.0" ? + (definition as Oas20SchemaDefinition).definitionName() : + (definition as Oas30SchemaDefinition).name(); + } + + /** + * Called to determine whether there is a validation problem associated with the given + * node (either directly on the node or any descendant node). + * @param node + */ + public hasValidationProblem(node: OasNode): boolean { + let viz: HasProblemVisitor = new HasProblemVisitor(); + OasVisitorUtil.visitTree(node, viz); + return viz.problemsFound; + } + + /** + * Called to fire a "selection changed" event. + */ + private fireNodeSelectedEvent() { + let event: NodeSelectionEvent = new NodeSelectionEvent(this.selectedItem, this.selectedType); + this.onNodeSelected.emit(event); + } + + /** + * Returns the classes that should be applied to the path item in the master view. + * @param {OasPathItem} node + * @return {string} + */ + public pathClasses(node: OasPathItem): string { + let classes: string[] = []; + if (this.hasValidationProblem(node)) { + classes.push("problem-marker"); + } + if (this.isPathContexted(node)) { + classes.push("contexted"); + } + if (this.isPathSelected(node)) { + classes.push("selected"); + } + return classes.join(' ') + " " + this.collaboratorSelectionClasses(node, 'pathItem'); + } + + /** + * Returns the classes that should be applied to the operation in the master view. + * @param {OasOperation} node + * @return {string} + */ + public operationClasses(node: OasOperation): string { + let classes: string[] = []; + if (this.hasValidationProblem(node)) { + classes.push("problem-marker"); + } + if (this.contextMenuType === 'operation' && this.contextMenuItem === node) { + classes.push("contexted"); + } + if (this.selectedItem === node) { + classes.push("selected"); + } + return classes.join(' ') + " " + this.collaboratorSelectionClasses(node, 'operation'); + } + + /** + * Returns the classes that should be applied to the schema definition in the master view. + * @param {OasNode} node + * @return {string} + */ + public definitionClasses(node: OasNode): string { + let classes: string[] = []; + if (this.hasValidationProblem(node)) { + classes.push("problem-marker"); + } + if (this.contextMenuType === 'definition' && this.contextMenuItem === node) { + classes.push("contexted"); + } + if (this.selectedItem === node) { + classes.push("selected"); + } + return classes.join(' ') + " " + this.collaboratorSelectionClasses(node, 'schema'); + } +} + + +/** + * Visitor used to search through the entire data model for validation problems. + */ +class HasProblemVisitor extends AllNodeVisitor { + + public problemsFound: boolean = false; + + protected doVisitNode(node: OasNode): void { + let errors: OasValidationError[] = node.n_attribute("validation-errors"); + if (!ObjectUtils.isNullOrUndefined(errors)) { + if (errors.length > 0) { + this.problemsFound = true; + } + } + } + +} + + +class ExternalSelections { + + private selections: any = {}; + + /** + * Sets the selection for a given active collaborator. Returns the user's previous selection. + * @param {ApiEditorUser} user + * @param {string} selection + * @return {OasNode | OasValidationError} + */ + public setSelection(user: ApiEditorUser, selection: string, document: OasDocument): OasNode | OasValidationError { + let previousSelection: OasNode | OasValidationError = this.selections[user.userId]; + if (previousSelection != null) { + console.info("[ExternalSelections] Clearing previous selection: %o", previousSelection); + ModelUtils.clearCollaboratorSelection(user, previousSelection); + } + // TODO support selecting validation problems + if (selection) { + let nodePath: OasNodePath = new OasNodePath(selection); + let newSelection: OasNode = nodePath.resolve(document); + if (newSelection != null) { + console.info("[ExternalSelections] Setting selection for user %o to node %o", user, newSelection); + ModelUtils.setCollaboratorSelection(user, newSelection); + this.selections[user.userId] = newSelection; + } + return previousSelection; + } else { + console.info("[ExternalSelections] Selection is null, skipping."); + } + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_events/node-selection.event.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_events/node-selection.event.ts new file mode 100644 index 000000000..6bcd06b05 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_events/node-selection.event.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {OasNode, OasValidationError} from "oai-ts-core"; + + +export class NodeSelectionEvent { + public node: OasNode | OasValidationError; + public type: string; + + constructor(node: OasNode | OasValidationError, type: string) { + this.node = node; + this.type = type; + } +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_services/httpcode.service.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_services/httpcode.service.ts new file mode 100644 index 000000000..db78ab8f3 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_services/httpcode.service.ts @@ -0,0 +1,114 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var HTTP_CODE_DATA = [ + { code: "100", line: "Continue" }, + { code: "101", line: "Switching Protocols" }, + { code: "102", line: "Processing" }, + { code: "200", line: "OK" }, + { code: "201", line: "Created" }, + { code: "202", line: "Accepted" }, + { code: "203", line: "Non_Authoritative" }, + { code: "204", line: "No Content" }, + { code: "205", line: "Reset Content" }, + { code: "206", line: "Partial Content" }, + { code: "207", line: "Multi-Status" }, + { code: "208", line: "Already Reported" }, + { code: "226", line: "IM Used" }, + { code: "300", line: "Multiple Choices" }, + { code: "301", line: "Moved Permanently" }, + { code: "302", line: "Found" }, + { code: "303", line: "See Other" }, + { code: "304", line: "Not Modified" }, + { code: "305", line: "Use Proxy" }, + { code: "306", line: "Switch Proxy" }, + { code: "307", line: "Temporary Redirect" }, + { code: "308", line: "Permanent Redirect" }, + { code: "400", line: "Bad Request" }, + { code: "401", line: "Unauthorized" }, + { code: "402", line: "Payment Required" }, + { code: "403", line: "Forbidden" }, + { code: "404", line: "Not Found" }, + { code: "405", line: "Method Not Allowed" }, + { code: "406", line: "Not Acceptable" }, + { code: "407", line: "Proxy Authentication Required" }, + { code: "408", line: "Request Time-Out" }, + { code: "409", line: "Conflict" }, + { code: "410", line: "Gone" }, + { code: "411", line: "Length Required" }, + { code: "412", line: "Precondition Failed" }, + { code: "413", line: "Payload Too Large" }, + { code: "414", line: "URI Too Long" }, + { code: "415", line: "Unsupported Media Type" }, + { code: "416", line: "Range Not Satisfiable" }, + { code: "417", line: "Expectation Failed" }, + { code: "418", line: "I'm a teapot!" }, + { code: "421", line: "Misdirected Request" }, + { code: "422", line: "Unprocessable Entity" }, + { code: "423", line: "Locked" }, + { code: "424", line: "Failed Dependency" }, + { code: "426", line: "Upgrade Required" }, + { code: "428", line: "Precondition Required" }, + { code: "429", line: "Too Many Requests" }, + { code: "431", line: "Request Header Fields Too Large" }, + { code: "451", line: "Unavailable For Legal Reasons" }, + { code: "500", line: "Internal Server Error" }, + { code: "501", line: "Not Implemented" }, + { code: "502", line: "Bad Gateway" }, + { code: "503", line: "Service Unavailable" }, + { code: "504", line: "Gateway Time-Out" }, + { code: "505", line: "HTTP Version Not Supported" }, + { code: "506", line: "Variant Also Negotiates" }, + { code: "507", line: "Insufficient Storage" }, + { code: "508", line: "Loop Detected" }, + { code: "510", line: "Not Extended" }, + { code: "511", line: "Network Authentication Required" } +]; + +export interface HttpCode { + code: string; + line: string; +} + +/** + * A simple service providing convenient access to information about HTTP + * response codes. + */ +export class HttpCodeService { + + /** + * Returns a list of all codes. + */ + public getCodes(): HttpCode[] { + return HTTP_CODE_DATA; + } + + /** + * Resolves a single code (returns the HttpCode object for a given response code). + * @param code + * @return {any} + */ + public getCode(code: string): HttpCode { + for (let c of this.getCodes()) { + if (c.code === code) { + return c; + } + } + return null; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_services/license.service.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_services/license.service.ts new file mode 100644 index 000000000..32570e56f --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_services/license.service.ts @@ -0,0 +1,336 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var LICENSE_DATA = [ + { + id: "agpl-3.0", + name: "GNU AGPLv3", + fullName: "GNU Affero General Public License v3.0", + description: "Permissions of this strongest copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. When a modified version is used to provide a service over a network, the complete source code of the modified version must be made available.", + url: "https://www.gnu.org/licenses/agpl.txt", + urls: [ + "https://www.gnu.org/licenses/agpl.html", + "https://www.gnu.org/licenses/agpl.txt", + "https://www.gnu.org/licenses/agpl.dbk", + "https://www.gnu.org/licenses/agpl.texi", + "https://www.gnu.org/licenses/agpl.tex", + "https://www.gnu.org/licenses/agpl.md", + "https://www.gnu.org/licenses/agpl.odt", + "https://www.gnu.org/licenses/agpl.rtf" + ], + moreInfoUrl: "https://choosealicense.com/licenses/agpl-3.0/", + permissions: [ + "commercial_use", "distribution", "modification", "patent_use", "private_use" + ], + conditions: [ + "disclose_source", "license_and_copyright_notice", "network_use_is_distribution", "same_license", "state_changes" + ], + limitations: [ + "liability", "warranty" + ] + }, + + { + id: "gpl-3.0", + name: "GNU GPLv3", + fullName: "GNU General Public License v3.0", + description: "Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights.", + url: "https://www.gnu.org/licenses/gpl.txt", + urls: [ + "https://www.gnu.org/licenses/gpl.html", + "https://www.gnu.org/licenses/gpl.txt", + "https://www.gnu.org/licenses/gpl.dbk", + "https://www.gnu.org/licenses/gpl.texi", + "https://www.gnu.org/licenses/gpl.tex", + "https://www.gnu.org/licenses/gpl.md", + "https://www.gnu.org/licenses/gpl.odt", + "https://www.gnu.org/licenses/gpl.rtf" + ], + moreInfoUrl: "https://choosealicense.com/licenses/gpl-3.0/", + permissions: [ + "commercial_use", "distribution", "modification", "patent_use", "private_use" + ], + conditions: [ + "disclose_source", "license_and_copyright_notice", "same_license", "state_changes" + ], + limitations: [ + "liability", "warranty" + ] + }, + + { + id: "lgpl-3.0", + name: "GNU LGPLv3", + fullName: "GNU Lesser General Public License v3.0", + description: "Permissions of this copyleft license are conditioned on making available complete source code of licensed works and modifications under the same license or the GNU GPLv3. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. However, a larger work using the licensed work through interfaces provided by the licensed work may be distributed under different terms and without source code for the larger work.", + url: "https://www.gnu.org/licenses/lgpl.txt", + urls: [ + "https://www.gnu.org/licenses/lgpl.html", + "https://www.gnu.org/licenses/lgpl.txt", + "https://www.gnu.org/licenses/lgpl.dbk", + "https://www.gnu.org/licenses/lgpl.texi", + "https://www.gnu.org/licenses/lgpl.tex", + "https://www.gnu.org/licenses/lgpl.md", + "https://www.gnu.org/licenses/lgpl.odt", + "https://www.gnu.org/licenses/lgpl.rtf" + ], + moreInfoUrl: "https://choosealicense.com/licenses/lgpl-3.0/", + permissions: [ + "commercial_use", "distribution", "modification", "patent_use", "private_use" + ], + conditions: [ + "disclose_source", "license_and_copyright_notice", "same_license_library", "state_changes" + ], + limitations: [ + "liability", "warranty" + ] + }, + + { + id: "mpl-2.0", + name: "Mozilla 2.0", + fullName: "Mozilla Public License 2.0", + description: "Permissions of this weak copyleft license are conditioned on making available source code of licensed files and modifications of those files under the same license (or in certain cases, one of the GNU licenses). Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. However, a larger work using the licensed work may be distributed under different terms and without source code for files added in the larger work.", + url: "https://www.mozilla.org/en-US/MPL/2.0/", + urls: [ + "https://www.mozilla.org/en-US/MPL/2.0/", + "https://www.mozilla.org/media/MPL/2.0/index.815ca599c9df.txt" + ], + moreInfoUrl: "https://choosealicense.com/licenses/mpl-2.0/", + permissions: [ + "commercial_use", "distribution", "modification", "patent_use", "private_use" + ], + conditions: [ + "disclose_source", "license_and_copyright_notice", "same_license_file" + ], + limitations: [ + "liability", "trademark_use", "warranty" + ] + }, + + { + id: "apache-2.0", + name: "Apache 2.0", + fullName: "Apache License 2.0", + description: "A permissive license whose main conditions require preservation of copyright and license notices. Contributors provide an express grant of patent rights. Licensed works, modifications, and larger works may be distributed under different terms and without source code.", + url: "https://www.apache.org/licenses/LICENSE-2.0", + urls: [ + "https://www.apache.org/licenses/LICENSE-2.0", + "https://www.apache.org/licenses/LICENSE-2.0.txt", + "http://www.apache.org/licenses/LICENSE-2.0.html" + ], + moreInfoUrl: "https://choosealicense.com/licenses/apache-2.0/", + permissions: [ + "commercial_use", "distribution", "modification", "patent_use", "private_use" + ], + conditions: [ + "license_and_copyright_notice", "state_changes" + ], + limitations: [ + "liability", "trademark_use", "warranty" + ] + }, + + { + id: "mit", + name: "MIT License", + fullName: "MIT License", + description: "A short and simple permissive license with conditions only requiring preservation of copyright and license notices. Licensed works, modifications, and larger works may be distributed under different terms and without source code.", + url: "https://opensource.org/licenses/MIT", + urls: [ + "https://opensource.org/licenses/MIT" + ], + moreInfoUrl: "https://choosealicense.com/licenses/mit/", + permissions: [ + "commercial_use", "distribution", "modification", "private_use" + ], + conditions: [ + "license_and_copyright_notice" + ], + limitations: [ + "liability", "warranty" + ] + }, + + { + id: "unlicense", + name: "The Unlicense", + fullName: "The Unlicense", + description: "A license with no conditions whatsoever which dedicates works to the public domain. Unlicensed works, modifications, and larger works may be distributed under different terms and without source code.", + url: "http://unlicense.org", + urls: [ + "http://unlicense.org" + ], + moreInfoUrl: "https://choosealicense.com/licenses/unlicense/", + permissions: [ + "commercial_use", "distribution", "modification", "private_use" + ], + conditions: [], + limitations: [ + "liability", "warranty" + ] + } + +]; + +var PERMISSIONS = { + "commercial_use": { + "name": "Commercial Use", + "description": "This software and derivatives may be used for commercial purposes." + }, + "distribution": {"name": "Distribution", "description": "You may distribute this software."}, + "modification": {"name": "Modification", "description": "This software may be modified."}, + "patent_use": { + "name": "Patent Use", + "description": "This license provides an express grant of patent rights from the contributor to the recipient." + }, + "private_use": { + "name": "Private Use", + "description": "You may use and modify the software without distributing it." + } +}; + +var CONDITIONS = { + "disclose_source": { + "name": "Disclose source", + "description": "Source code must be made available when distributing the software." + }, + "license_and_copyright_notice": { + "name": "License and copyright notice", + "description": "Include a copy of the license and copyright notice with the software." + }, + "network_use_is_distribution": { + "name": "Network use is distribution", + "description": "Users who interact with the software via network are given the right to receive a copy of the corresponding source code." + }, + "same_license": { + "name": "Same License", + "description": "Modifications must be released under the same license when distributing the software. In some cases a similar or related license may be used." + }, + "same_license_library": { + "name": "Same License (library)", + "description": "Modifications must be released under the same license when distributing the software. In some cases a similar or related license may be used, or this condition may not apply to works that use the software as a library." + }, + "same_license_file": { + "name": "Same License (file)", + "description": "Modifications of existing files must be released under the same license when distributing the software. In some cases a similar or related license may be used." + }, + "state_changes": {"name": "State changes", "description": "Indicate changes made to the code."} +}; + +var LIMITATIONS = { + "liability": {"name": "Liability", "description": "This license includes a limitation of liability."}, + "trademark_use": { + "name": "Trademark use", + "description": "This license explicitly states that it does NOT grant you trademark rights, even though licenses without such a statement probably do not grant you any implicit trademark rights." + }, + "warranty": { + "name": "Warranty", + "description": "The license explicitly states that it does NOT provide any warranty." + } +}; + +export interface ILicense { + id: string; + name: string; + fullName: string; + description: string; + url: string; + moreInfoUrl: string; + permissions: string[]; + conditions: string[]; + limitations: string[]; +} + +/** + * A simple service providing convenient access to information about different + * licenses commonly used to license APIs. + */ +export class LicenseService { + + /** + * Returns a list of all licenses. + * @return {({id: string, name: string, description: string, url: string, moreInfoUrl: string, permissions: (string|string|string|string|string)[], conditions: (string|string|string|string|string)[], limitations: (string|string)[]}|{id: string, name: string, description: string, url: string, moreInfoUrl: string, permissions: (string|string|string|string|string)[], conditions: (string|string|string|string)[], limitations: (string|string)[]}|{id: string, name: string, description: string, url: string, moreInfoUrl: string, permissions: (string|string|string|string|string)[], conditions: (string|string|string|string)[], limitations: (string|string)[]}|{id: string, name: string, description: string, url: string, moreInfoUrl: string, permissions: (string|string|string|string|string)[], conditions: (string|string|string)[], limitations: (string|string|string)[]}|{id: string, name: string, description: string, url: string, moreInfoUrl: string, permissions: (string|string|string|string|string)[], conditions: (string|string)[], limitations: (string|string|string)[]}|{id: string, name: string, description: string, url: string, moreInfoUrl: string, permissions: (string|string|string|string)[], conditions: string[], limitations: (string|string)[]}|{id: string, name: string, description: string, url: string, moreInfoUrl: string, permissions: (string|string|string|string)[], conditions: Array, limitations: (string|string)[]})[]} + */ + public getLicenses(): ILicense[] { + return LICENSE_DATA; + } + + /** + * Resolves a permission code into a name. + * @param permission + */ + public permissionName(permission: string): string { + return PERMISSIONS[permission].name; + } + + /** + * Resolves a permission code into a description. + * @param permission + */ + public permissionDescription(permission: string): string { + return PERMISSIONS[permission].description; + } + + /** + * Resolves a condition code into a name. + * @param condition + */ + public conditionName(condition: string): string { + return CONDITIONS[condition].name; + } + + /** + * Resolves a condition code into a description. + * @param condition + */ + public conditionDescription(condition: string): string { + return CONDITIONS[condition].description; + } + + /** + * Resolves a limitation code into a name. + * @param limitation + */ + public limitationName(limitation: string): string { + return LIMITATIONS[limitation].name; + } + + /** + * Resolves a limitation code into a description. + * @param limitation + */ + public limitationDescription(limitation: string): string { + return LIMITATIONS[limitation].description; + } + + /** + * Finds a license by its URL. Multiple URLs may resolve to the same + * license. + * @param url + * @return {any} + */ + public findLicense(url: string): ILicense { + for (let license of LICENSE_DATA) { + if (license.urls.indexOf(url) > -1) { + return license; + } + } + return null; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_services/problems.service.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_services/problems.service.ts new file mode 100644 index 000000000..c12753804 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_services/problems.service.ts @@ -0,0 +1,146 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {Injectable} from "@angular/core"; +import {OasValidationError} from "oai-ts-core"; + + + +var PROBLEM_EXPLANATIONS = { + + "CTC-001" : "If a URL is specified for the Contact information, it must be a valid URL format. Double check the value of the URL and make sure there isn't a typo!", + "CTC-002" : "If an email address is specified for the Contact, it must be a valid email format. Make sure the value supplied is formatted properly.", + "ED-002" : "The description of the External Documentation must be in either plain text or Github-Flavored Markdown format. Have a look at the value to make sure it's not something else (asciidoc, html, etc...).", + "ED-003" : "If a URL is specified for the External Documentation, it must be a valid URL format. Double check the value of the URL and make sure there isn't a typo!", + "HEAD-005" : "The default value provided for the Header does not match its type. For example, if the type of the Header is \"integer\", any default value must actually be a valid integer.", + "INF-003" : "The description of the API must be in either plain text or Github-Flavored Markdown format. Have a look at the value to make sure it's not something else (asciidoc, html, etc...).", + "IT-007" : "The default value provided for the parameter's items does not match its type. For example, if the type of the type is \"integer\", any default value must actually be a valid integer.", + "LIC-002" : "If a URL is specified for the License, it must be a valid URL format. Double check the value of the URL and make sure there isn't a typo!", + "OP-002" : "The description of the Operation must be in either plain text or Github-Flavored Markdown format. Have a look at the value to make sure it's not something else (asciidoc, html, etc...).", + "OP-005" : "When you indicate that an Operation consumes a particular type of data, you must provide a valid mime-type. Examples of valid mime types include: text/plain, application/json, application/x-www-form-urlencoded.", + "OP-006" : "When you indicate that an Operation produces a particular type of data, you must provide a valid mime-type. Examples of valid mime types include: text/plain, application/json, application/pdf.", + "PAR-010" : "The description of the Parameter must be in either plain text or Github-Flavored Markdown format. Have a look at the value to make sure it's not something else (asciidoc, html, etc...).", + "R-004" : "The provided Host information was invalid. Only the host name (and optionally port) should be included. An IP address is not allowed, nor is a URL. Examples of a valid host include \"localhost\", \"api.example.org\", and \"api.example.org:8080\".", + "R-005" : "When providing a Base Path for the API, it must start with a '/' character. The Base Path is appended to the Scheme and Host information to form a full URL to the API.", + "SS-011" : "An OAuth Security Scheme defintion may include an \"Authorization URL\". When included, it must be a valid URL format (including scheme, host, port, and path).", + "SS-012" : "An OAuth Security Scheme defintion may include a \"Token URL\". When included, it must be a valid URL format (including scheme, host, port, and path).", + "TAG-002" : "The description of the Tag must be in either plain text or Github-Flavored Markdown format. Have a look at the value to make sure it's not something else (asciidoc, html, etc...).", + "XML-001" : "When defining the XML format of a definition, the Namespace must be a valid XML URI/URL. Check the value and make sure it's a valid XML Namespace URL format.", + "EX-001" : "When defining examples for an Operation Response, each example must correspond to one of the mime-types defined by the Operation. Note that the Operation can declare its own mime-types (via the \"produces\" property of the Operation) OR it can inherit the mime-types from the API's global \"produces\" property.", + "PATH-005" : "Every path defined by the API must begin with a '/' character. Paths are appended to the API URL to uniquely identify an endpoint for the API.", + "PDEF-001" : "", + "RDEF-001" : "", + "RES-003" : "All Responses declared for an Operation must correspond to a valid HTTP response status code. Valid status codes are things like 200, 404, 500. A full list of status codes can be found here: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml", + "SCPS-001" : "", + "SDEF-001" : "", + "SS-013" : "", + "HEAD-003" : "When declaring a Header you must identify the Header's type. Valid types for Headers include: string, number, integer, boolean, array. Any other value (or no value at all) is not allowed.", + "HEAD-004" : "When declaring a Header, the Header type can be further refined by indicating a \"format\". Valid formats for Headers include: int32, int64, float, double, byte, binary, date, date-time, passworld. Not all formats are valid for all types. For more detailed information go here: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#dataTypeFormat", + "HEAD-006" : "Only Headers that are defined as Array type can specify a Collection Format. For other types (such as string or number) the collection format does not make sense (as these types are not collections).", + "HEAD-007" : "When indicating a Collection Format for a Header, the only valid values are: csv, ssv, tsv, pipes", + "IT-003" : "For Parameters that are of type \"array\", the declarating of the Parameter's Items must indicate a type. This is required so that consumers know what type of data each item of the array must be. Valid values are: string, number, integer, boolean, array", + "IT-004" : "When defining the type of an array-type parameter's items, the type can be further refined by indicating a \"format\". Valid formats for parameter Items include: int32, int64, float, double, byte, binary, date, date-time, passworld. Not all formats are valid for all types. For more detailed information go here: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#dataTypeFormat", + "IT-005" : "When indicating a Collection Format for an array-style Parameter, the only valid values are: csv, ssv, tsv, pipes", + "IT-006" : "Only Parameters that are defined as Array type can specify a Collection Format. For other types (such as string or number) the collection format does not make sense (as these types are not collections).", + "OP-001" : "When defining a summary for an Operation, it should be short and descriptive, limited to 120 characters.", + "OP-004" : "Every operation may optionally have an operationId property. If declared, it should follow standard software programming style (e.g. camelCase). It must also be unique over all Operations in the API.", + "OP-010" : "When declaring the valid schemes for an Operation, the only valid options are: http, https, ws, wss", + "PAR-007" : "All Path Parameters must have a name that maps to one of the dynamic elements of the path's template. For example, if the path template is \"/items/{itemId}/widgets/{widgetId}\" then the only valid values for Path Paramter names are \"itemId\" and \"widgetId\".", + "PAR-008" : "When using Form Data as the input to an Operation, the Operation must indicate that it can consume form data by listing either \"application/x-www-form-urlencoded\" or \"multipart/form-data\" in its list of Consumes mime-types.", + "PAR-009" : "Every parameter must be located in one of the following locations: URL Query Params, HTTP Headers, the Path Template, Form Data, or the HTTP Request Body. Therefore, the \"in\" property for any Parameter must be one of: query, header, path, formData, body. Make sure your parameter doesn't mistakenly indicate some other value.", + "PAR-011" : "When declaring a Parameter you must identify its type. Valid types for Parameters include: string, number, integer, boolean, array, file. Any other value (or no value at all) is not allowed.", + "PAR-012" : "When defining a Parameter's type, it can be further refined by indicating a \"format\". Valid formats for parameters include: int32, int64, float, double, byte, binary, date, date-time, passworld. Not all formats are valid for all types. For more detailed information go here: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#dataTypeFormat", + "PAR-013" : "You can only indicate that empty values are allowed for Parameters that are in the query or in form data. Other parameters (such as in headers or in the path) cannot be empty.", + "PAR-014" : "Only Parameters that are defined as Array type can specify a Collection Format. For other types (such as string or number) the collection format does not make sense (as these types are not collections).", + "PAR-015" : "When indicating a Collection Format for an array-style Parameter, the only valid values are: csv, ssv, tsv, pipes, multi", + "PAR-016" : "When specifying \"multi\" as the collection format for a Parameter, the Parameter must be either a Query Param or a Parameter in the Form Data. This is because only those types of parameters support passing multiple values for a single name.", + "PAR-017" : "If a Parameter is marked as \"required\", then no default value is allowed. Because a default value is only used when the API consumer invokes the Operation without the Parameter, default values don't make sense for required Parameters.", + "R-006" : "When indicating the default schemes supported by the API, only the following are valid choices: http, https, ws, wss", + "R-007" : "When indicating the default data formats that the API consumes, the values must be valid mime-types. Examples of valid mime types include: text/plain, application/json, application/x-www-form-urlencoded.", + "R-008" : "When indicating the default data formats that the API produces, the values must be valid mime-types. Examples of valid mime types include: text/plain, application/json, application/pdf.", + "SREQ-002" : "Security Requirements can be specified for basic, apiKey, or oauth. When using basic or apiKey authentication, the security requirement must NOT provide a list of scopes (the list of scopes must be empty). In other words, scopes are only valid when using OAuth 2 authentication.", + "SREQ-003" : "Security Requirements can be specified for basic, apiKey, or oauth. When using OAuth 2 authentication, the security requirement MUST provide the list of scopes required for the caller to successfully access the API.", + "SS-008" : "The OpenAPI specification only supports the following authentication types: Basic, API Key, and OAuth 2. Any other authentication types are not valid. As a result, the value of \"in\" for a Security Scheme must be one of: basic, apiKey, oauth2", + "SS-009" : "The only valid values for the \"in\" property of a Security Schema are: query, header. This property indicates where in the Request the security token can be found (either in HTTP Request Headers or in the URL Query Parameters).", + "SS-010" : "When using OAuth 2 authentication, the OAuth flow must be defined and it must be one of the possible supported OAuth flows. The supported flows are: implicit, password, application, and accessCode", + "XML-002" : "When defining the XML representation of a definition, one of the options is to declare an element must be wrapped (by another XML element). However, this is only relevant for \"array\" type properties, since array properties may have multiple values that should be contained within a parent (wrapper) XML element.", + "PAR-018" : "The definition of the Parameter attempts to reference a Parameter found elsewhere (typically globally declared in the same API document) but the reference could not be resolved. Perhaps the global Parameter was deleted, or there is a typo in the reference.", + "PATH-001" : "A Path Item was defined as a reference to an external document, but that document could not be found (or the referenced Path Item within it could not be found).", + "RES-002" : "The definition of the Response attempts to reference a Response found elsewhere (typically globally declared in the same API document) but the reference could not be resolved. Perhaps the global Response was deleted, or there is a typo in the reference.", + "SCH-001" : "The definition of the Schema attempts to reference a type Definition found elsewhere (typically globally declared in the same API document) but the reference could not be resolved. Perhaps the global Definition was deleted, or there is a typo in the reference.", + "SREQ-001" : "The names of each security requirement declared for an Operation (or declared globally) must match the name of a globally defined Security Scheme. Check to make sure that the names of the requirement match up with security schemes previously defined.", + "OP-009" : "It is not possible to use Body and Form Data parameters in the same Operation. These input types are mutually exclusive, since both are sent via the HTTP Request's body.", + "PATH-004" : "It is not possible to use Body and Form Data parameters in the same Operation. These input types are mutually exclusive, since both are sent via the HTTP Request's body.", + "ED-001" : "When defining your External Documentation, you must provide a URL!", + "HEAD-001" : "You need to specify a type when defining a Header. Valid Header types include: string, number, integer, boolean, array.", + "INF-001" : "The API must have a title provided.", + "INF-002" : "The API must have a version provided.", + "IT-001" : "When defining the items of an array-type Parameter, the type of the items MUST be indicated. Please make sure you define the type of items for your array-type Parameter.", + "LIC-001" : "When including License information for the API, a Name of the License is required (cannot be blank).", + "OP-007" : "When declaring an Operation (e.g. GET, PUT, POST, etc...) at least one Response MUST be included. Typically at least a 20x (success) response should be defined.", + "PAR-001" : "All Parameters, regardless of location (query, path, form data) MUST include a name. Parameters are uniquely identified by the combination of \"in\" (what kind of parameter it is such as query or path) and \"name\".", + "PAR-002" : "Every Parameter must indicate what kind it is, by providing a value for the \"in\" property. Value values include: query, formData, path, body.", + "R-001" : "Every OpenAPI (version 2.0) document MUST include the root \"swagger\" property, and its value MUST be \"2.0\".", + "R-002" : "Every OpenAPI document MUST include some basic information such as Name and Version. This meta-data is contained in an \"info\" root property. Please make sure to add this information to the API.", + "R-003" : "The OpenAPI document must have at least one path defined. Without any paths, consumers have no endpoints/operations to invoke. Make sure to add at least one Path. For example: \"/items/{itemId}\"", + "RES-001" : "Every Response (in each Operation) must have a description. Please make sure to add a helpful description to your Responses.", + "SS-001" : "When defining a Security Scheme, a type must be provided. Possible security scheme types include: basic, apiKey, oauth2", + "TAG-001" : "Tags defined in the OpenAPI document must each have a name (and the name must be unique). Please make sure each tag defined has a name.", + "HEAD-002" : "Whenever a Header is declared to be of type \"array\", the array's items must also be identified. This manifests as a \"items\" property in the specification. Please make sure to indicate what type the Header's array items must be.", + "IT-002" : "Whenever a Parameter is declared to be of type \"array\", the array's items must also be identified. This manifests as a \"items\" property in the specification. Please make sure to indicate what type the Parameter's array items must be.", + "PAR-003" : "Path style Parameters (dynamic parameters found in the path template of an endpoint) are always required. Therefore the \"required\" property must be included for all Path Parameters, and its value must be true.", + "PAR-004" : "When declaring a request body style Parameter, a schema must be provided in order to declare the type of data expected in the body.", + "PAR-005" : "You must define the type of the parameter! Possible parameter types include string, number, integer, boolean, array.", + "PAR-006" : "Whenever a Parameter is declared to be of type \"array\", the array's items must also be identified. This manifests as a \"items\" property in the specification. Please make sure to indicate what type the Parameter's array items must be.", + "SS-002" : "When using API Key style authentication, the \"name\" property must be declared to indicate where to find the key information. The API Key must be passed in the request, either in an HTTP Header or as a Query Parameter. In both cases, the name of the Header or Parameter must be declared.", + "SS-003" : "When using API Key style authentication, the \"name\" property must be declared to indicate where to find the key information. The API Key must be passed in the request, either in an HTTP Header or as a Query Parameter. Therefore, possible values are: query, header", + "SS-004" : "When using OAuth 2 as the authentication style, the \"flow\" property must be defined. This property is used to indicate the precise OAuth 2 flow supported by the API. Valid values for this property are: implicit, password, application, accessCode", + "SS-005" : "When using OAuth 2 authentication's Implicit or Access Code flows, you must provide a valid Authorization URL. The Authorization URL is required in order to properly implement the Implicit or Access Code OAuth 2 flows.", + "SS-006" : "When using OAuth 2 authentication's Password, Application, or Access Code flows, you must provide a valid Token URL. The Token URL is required in order to properly implement any of these flows.", + "SS-007" : "Whenever OAuth 2 is used as the authentication method, a set of scopes must be defined. Make sure you have a list of scopes configured for all OAuth 2 security schemes.", + "OP-003" : "Each operation may have an optional \"operationId\" defined for it. This ID must be unique across all operations in the API.", + "PAR-019" : "Parameters are uniquely defined by their \"name\" and their \"location\" (path, query, formData, etc). You cannot have two Parameters with the same combination of name and location. Please make sure that all of your parameters are appropriately unique based on these two criteria.", + "PAR-020" : "Only a single \"body\" parameter may be defined for an Operation. A body parameter indicates that the operation expects to receive input (typically some JSON data) in the body of the HTTP request. Since there is only one request body, only one paramet of this type can be specified.", + "TAG-003" : "It was found that two (or more) tags have the same name. Every tag in the document must have a unique name (different from all other tag names)." + +}; + + + +/** + * A simple service providing information and functionality about validation problems. + * This includes providing more information (explanation) about any given problem type + * as well as "quick fix" functionality. + */ +@Injectable() +export class ProblemsService { + + /** + * Returns a full human-readable explanation for the given validation problem or + * null if it doesn't have more information about it. + * @param problem + */ + public explanation(problem: OasValidationError): string { + let explanation: string = PROBLEM_EXPLANATIONS[problem.errorCode]; + if (!explanation) { + explanation = "No additional information found."; + } + return explanation; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_util/model.util.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_util/model.util.ts new file mode 100644 index 000000000..51800e6f3 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_util/model.util.ts @@ -0,0 +1,122 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Oas20Parameter, Oas20PathItem, OasNode, OasOperation, OasValidationError} from "oai-ts-core"; +import {ObjectUtils} from "./object.util"; +import {ApiEditorUser} from "../../../../../models/editor-user.model"; + +export class ModelUtils { + + /** + * Detects the appropriate path parameter names from a path. For example, if the + * string "/resources/{fooId}/subresources/{barId}" is passed in, the following + * string array will be returned: [ "fooId", "barId" ] + * @param path + * @return {string[]} + */ + public static detectPathParamNames(path: string): string[] { + let segments: string[] = path.split("/"); + let pnames: string[] = segments.filter(segment => { + return segment.startsWith("{") && segment.endsWith("}"); + }).map(segment => { + return segment.substring(1, segment.length - 1); + }); + return pnames; + } + + /** + * Goes through all of the operations defined on the given path item, then returns an array + * of the named path param (if it exists) for each operation. This is useful when changing + * the description and/or type simultaneously for all path parameters with the same name + * for a given path. + * @param path + * @param paramName + * @return {Oas20Parameter[]} + */ + public static getAllPathParams(path: Oas20PathItem, paramName: string): Oas20Parameter[] { + let operations: OasOperation[] = [ + path.get, path.put, path.post, path.delete, path.options, path.head, path.patch + ]; + let params: Oas20Parameter[] = []; + operations.filter(operation => { + return !ObjectUtils.isNullOrUndefined(operation); + }).forEach( operation => { + if (operation.parameters) { + operation.parameters.forEach( parameter => { + if (parameter.name === paramName && parameter.in === "path") { + params.push(parameter as Oas20Parameter); + } + }); + } + }); + return params; + } + + /** + * Clears any possible selection that may exist on the given node for the given user. + * @param {ApiEditorUser} user + * @param {OasNode | OasValidationError} selection + */ + public static clearCollaboratorSelection(user: ApiEditorUser, selection: OasNode | OasValidationError): void { + // TODO support validation errors as well + let node: OasNode = selection as OasNode; + let selections: any = node.n_attribute("collaborator-selections"); + if (selections) { + delete selections[user.userId]; + } + } + + /** + * Sets the collaborator selection for the given user on the node. Essentially this marks + * the node as "selected" by the external (active) collaborator. + * @param {ApiEditorUser} user + * @param {OasNode} selection + */ + public static setCollaboratorSelection(user: ApiEditorUser, selection: OasNode | OasValidationError): void { + // TODO support validation errors as well + let node: OasNode = selection as OasNode; + let selections: any = node.n_attribute("collaborator-selections"); + if (!selections) { + selections = {}; + node.n_attribute("collaborator-selections", selections); + } + selections[user.userId] = user; + } + + /** + * Checks whether the given item is selected by an external collaborator. Returns the collaborator + * information if the item is selected or else null. + * @param {OasNode | OasValidationError} node + * @return {ApiEditorUser} + */ + public static isSelectedByCollaborator(node: OasNode | OasValidationError): ApiEditorUser { + // TODO support validation errors as well + let selections: any = (node as OasNode).n_attribute("collaborator-selections"); + if (selections) { + let rval: ApiEditorUser = null; + for (let key in selections) { + if (selections.hasOwnProperty(key)) { + rval = selections[key]; + } + } + return rval; + } else { + return null; + } + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_util/object.util.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_util/object.util.ts new file mode 100644 index 000000000..de41ed3e7 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_util/object.util.ts @@ -0,0 +1,116 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {ElementRef} from "@angular/core"; + + +export class ArrayUtils { + + /** + * Returns the intersection of two arrays. + * @param a1 + * @param a2 + */ + public static intersect(a1: any[], a2: any[]): any[] { + let rval: any[] = []; + for (let item of a1) { + if (ArrayUtils.contains(a2, item)) { + rval.push(item); + } + } + return rval; + } + + /** + * Returns true if the given item is contained in the given array. + * @param a + * @param item + * @return {boolean} + */ + public static contains(a: any[], item: any): boolean { + for (let aitem of a) { + if (aitem === item) { + return true; + } + } + return false; + } + +} + +export class ObjectUtils { + + public static isNullOrUndefined(object: any): boolean { + return object === undefined || object === null; + } + + public static objectEquals(x:any, y:any): boolean { + if (x === null || x === undefined || y === null || y === undefined) { return x === y; } + // after this just checking type of one would be enough + if (x.constructor !== y.constructor) { return false; } + // if they are functions, they should exactly refer to same one (because of closures) + if (x instanceof Function) { return x === y; } + // if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES) + if (x instanceof RegExp) { return x === y; } + if (x === y || x.valueOf() === y.valueOf()) { return true; } + if (Array.isArray(x) && x.length !== y.length) { return false; } + + // if they are dates, they must had equal valueOf + if (x instanceof Date) { return false; } + + // if they are strictly equal, they both need to be object at least + if (!(x instanceof Object)) { return false; } + if (!(y instanceof Object)) { return false; } + + var p = Object.keys(x); + return Object.keys(y).every(function (i) { return p.indexOf(i) !== -1; }) && + p.every(function (i) { return ObjectUtils.objectEquals(x[i], y[i]); }); + } + +} + + +export class DomUtils { + + public static elementBounds(element: ElementRef, x: number, y: number): boolean { + if (element && element.nativeElement) { + let rect: any = element.nativeElement.getBoundingClientRect(); + return (x >= rect.left) && + (x <= rect.right) && + (y >= rect.top) && + (y <= rect.bottom); + } + return false; + } + + public static elementContains(parent: any, descendant: any): boolean { + let done: boolean = false; + let node: any = descendant; + while (!done) { + if (parent === node) { + return true; + } + node = node["parentNode"]; + if (!node) { + done = true; + } + } + return false; + + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_visitors/base.visitor.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_visitors/base.visitor.ts new file mode 100644 index 000000000..fc39897ad --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_visitors/base.visitor.ts @@ -0,0 +1,523 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + IOas20NodeVisitor, + IOas30NodeVisitor, + Oas20AdditionalPropertiesSchema, + Oas20AllOfSchema, + Oas20Definitions, + Oas20Example, + Oas20Headers, + Oas20Items, + Oas20ItemsSchema, + Oas20Parameter, + Oas20ParameterDefinition, + Oas20ParametersDefinitions, + Oas20PropertySchema, + Oas20Response, + Oas20ResponseDefinition, + Oas20ResponsesDefinitions, + Oas20SchemaDefinition, + Oas20Scopes, + Oas20SecurityDefinitions, + Oas30AdditionalPropertiesSchema, + Oas30AllOfSchema, + Oas30AnyOfSchema, + Oas30AuthorizationCodeOAuthFlow, + Oas30Callback, + Oas30CallbackDefinition, + Oas30CallbackPathItem, + Oas30ClientCredentialsOAuthFlow, + Oas30Components, + Oas30Discriminator, + Oas30Encoding, + Oas30Example, + Oas30ExampleDefinition, + Oas30HeaderDefinition, + Oas30ImplicitOAuthFlow, + Oas30ItemsSchema, + Oas30Link, + Oas30LinkDefinition, + Oas30LinkParameterExpression, + Oas30LinkRequestBodyExpression, + Oas30LinkServer, + Oas30MediaType, + Oas30NotSchema, + Oas30OAuthFlows, + Oas30OneOfSchema, + Oas30Parameter, + Oas30ParameterDefinition, + Oas30PasswordOAuthFlow, + Oas30PropertySchema, + Oas30RequestBody, + Oas30RequestBodyDefinition, + Oas30Response, + Oas30ResponseDefinition, + Oas30SchemaDefinition, + Oas30Server, + Oas30ServerVariable, + OasContact, + OasDocument, + OasExtension, + OasExternalDocumentation, + OasHeader, + OasInfo, + OasLicense, OasNode, + OasOperation, + OasPathItem, + OasPaths, + OasResponses, + OasSchema, + OasSecurityRequirement, + OasSecurityScheme, + OasTag, + OasXML, +} from "oai-ts-core"; + +/** + * Base class for visitors that are cabable of visiting both a 2.0 and 3.0 + * data model. + * + * TODO replace with visitors added to oai-ts-core in version 0.2.6 + */ +export class AbstractCombinedVisitorAdapter implements IOas20NodeVisitor, IOas30NodeVisitor { + + visitDocument(node: OasDocument): void { + } + + visitInfo(node: OasInfo): void { + } + + visitContact(node: OasContact): void { + } + + visitLicense(node: OasLicense): void { + } + + visitPaths(node: OasPaths): void { + } + + visitPathItem(node: OasPathItem): void { + } + + visitOperation(node: OasOperation): void { + } + + visitParameter(node: Oas20Parameter | Oas30Parameter): void { + } + + visitParameterDefinition(node: Oas20ParameterDefinition | Oas30ParameterDefinition): void { + } + + visitExternalDocumentation(node: OasExternalDocumentation): void { + } + + visitSecurityRequirement(node: OasSecurityRequirement): void { + } + + visitResponses(node: OasResponses): void { + } + + visitResponse(node: Oas20Response | Oas30Response): void { + } + + visitResponseDefinition(node: Oas20ResponseDefinition | Oas30ResponseDefinition): void { + } + + visitSchema(node: OasSchema): void { + } + + visitHeaders(node: Oas20Headers): void { + } + + visitHeader(node: OasHeader): void { + } + + visitExample(node: Oas20Example | Oas30Example): void { + } + + visitItems(node: Oas20Items): void { + } + + visitTag(node: OasTag): void { + } + + visitSecurityDefinitions(node: Oas20SecurityDefinitions): void { + } + + visitSecurityScheme(node: OasSecurityScheme): void { + } + + visitScopes(node: Oas20Scopes): void { + } + + visitXML(node: OasXML): void { + } + + visitSchemaDefinition(node: Oas20SchemaDefinition | Oas30SchemaDefinition): void { + } + + visitPropertySchema(node: Oas20PropertySchema | Oas30PropertySchema): void { + } + + visitAdditionalPropertiesSchema(node: Oas20AdditionalPropertiesSchema | Oas30AdditionalPropertiesSchema): void { + } + + visitAllOfSchema(node: Oas20AllOfSchema | Oas30AllOfSchema): void { + } + + visitItemsSchema(node: Oas20ItemsSchema | Oas30ItemsSchema): void { + } + + visitDefinitions(node: Oas20Definitions): void { + } + + visitParametersDefinitions(node: Oas20ParametersDefinitions): void { + } + + visitResponsesDefinitions(node: Oas20ResponsesDefinitions): void { + } + + visitExtension(node: OasExtension): void { + } + + visitMediaType(node: Oas30MediaType): void { + } + + visitEncoding(node: Oas30Encoding): void { + } + + visitLink(node: Oas30Link): void { + } + + visitLinkParameterExpression(node: Oas30LinkParameterExpression): void { + } + + visitLinkRequestBodyExpression(node: Oas30LinkRequestBodyExpression): void { + } + + visitLinkServer(node: Oas30LinkServer): void { + } + + visitRequestBody(node: Oas30RequestBody): void { + } + + visitCallback(node: Oas30Callback): void { + } + + visitCallbackPathItem(node: Oas30CallbackPathItem): void { + } + + visitServer(node: Oas30Server): void { + } + + visitServerVariable(node: Oas30ServerVariable): void { + } + + visitAnyOfSchema(node: Oas30AnyOfSchema): void { + } + + visitOneOfSchema(node: Oas30OneOfSchema): void { + } + + visitNotSchema(node: Oas30NotSchema): void { + } + + visitComponents(node: Oas30Components): void { + } + + visitExampleDefinition(node: Oas30ExampleDefinition): void { + } + + visitRequestBodyDefinition(node: Oas30RequestBodyDefinition): void { + } + + visitHeaderDefinition(node: Oas30HeaderDefinition): void { + } + + visitOAuthFlows(node: Oas30OAuthFlows): void { + } + + visitImplicitOAuthFlow(node: Oas30ImplicitOAuthFlow): void { + } + + visitPasswordOAuthFlow(node: Oas30PasswordOAuthFlow): void { + } + + visitClientCredentialsOAuthFlow(node: Oas30ClientCredentialsOAuthFlow): void { + } + + visitAuthorizationCodeOAuthFlow(node: Oas30AuthorizationCodeOAuthFlow): void { + } + + visitLinkDefinition(node: Oas30LinkDefinition): void { + } + + visitCallbackDefinition(node: Oas30CallbackDefinition): void { + } + + visitDiscriminator(node: Oas30Discriminator): void { + } + +} + + +/** + * Base class for visitors that simply want to get called for *every* node + * in the model. + */ +export abstract class AllNodeVisitor extends AbstractCombinedVisitorAdapter { + + protected abstract doVisitNode(node: OasNode): void; + + visitDocument(node: OasDocument): void { + this.doVisitNode(node); + } + + visitInfo(node: OasInfo): void { + this.doVisitNode(node); + } + + visitContact(node: OasContact): void { + this.doVisitNode(node); + } + + visitLicense(node: OasLicense): void { + this.doVisitNode(node); + } + + visitPaths(node: OasPaths): void { + this.doVisitNode(node); + } + + visitPathItem(node: OasPathItem): void { + this.doVisitNode(node); + } + + visitOperation(node: OasOperation): void { + this.doVisitNode(node); + } + + visitParameter(node: Oas20Parameter | Oas30Parameter): void { + this.doVisitNode(node); + } + + visitParameterDefinition(node: Oas20ParameterDefinition | Oas30ParameterDefinition): void { + this.doVisitNode(node); + } + + visitExternalDocumentation(node: OasExternalDocumentation): void { + this.doVisitNode(node); + } + + visitSecurityRequirement(node: OasSecurityRequirement): void { + this.doVisitNode(node); + } + + visitResponses(node: OasResponses): void { + this.doVisitNode(node); + } + + visitResponse(node: Oas20Response | Oas30Response): void { + this.doVisitNode(node); + } + + visitResponseDefinition(node: Oas20ResponseDefinition | Oas30ResponseDefinition): void { + this.doVisitNode(node); + } + + visitSchema(node: OasSchema): void { + this.doVisitNode(node); + } + + visitHeaders(node: Oas20Headers): void { + this.doVisitNode(node); + } + + visitHeader(node: OasHeader): void { + this.doVisitNode(node); + } + + visitExample(node: Oas20Example | Oas30Example): void { + this.doVisitNode(node); + } + + visitItems(node: Oas20Items): void { + this.doVisitNode(node); + } + + visitTag(node: OasTag): void { + this.doVisitNode(node); + } + + visitSecurityDefinitions(node: Oas20SecurityDefinitions): void { + this.doVisitNode(node); + } + + visitSecurityScheme(node: OasSecurityScheme): void { + this.doVisitNode(node); + } + + visitScopes(node: Oas20Scopes): void { + this.doVisitNode(node); + } + + visitXML(node: OasXML): void { + this.doVisitNode(node); + } + + visitSchemaDefinition(node: Oas20SchemaDefinition | Oas30SchemaDefinition): void { + this.doVisitNode(node); + } + + visitPropertySchema(node: Oas20PropertySchema | Oas30PropertySchema): void { + this.doVisitNode(node); + } + + visitAdditionalPropertiesSchema(node: Oas20AdditionalPropertiesSchema | Oas30AdditionalPropertiesSchema): void { + this.doVisitNode(node); + } + + visitAllOfSchema(node: Oas20AllOfSchema | Oas30AllOfSchema): void { + this.doVisitNode(node); + } + + visitItemsSchema(node: Oas20ItemsSchema | Oas30ItemsSchema): void { + this.doVisitNode(node); + } + + visitDefinitions(node: Oas20Definitions): void { + this.doVisitNode(node); + } + + visitParametersDefinitions(node: Oas20ParametersDefinitions): void { + this.doVisitNode(node); + } + + visitResponsesDefinitions(node: Oas20ResponsesDefinitions): void { + this.doVisitNode(node); + } + + visitExtension(node: OasExtension): void { + this.doVisitNode(node); + } + + visitMediaType(node: Oas30MediaType): void { + this.doVisitNode(node); + } + + visitEncoding(node: Oas30Encoding): void { + this.doVisitNode(node); + } + + visitLink(node: Oas30Link): void { + this.doVisitNode(node); + } + + visitLinkParameterExpression(node: Oas30LinkParameterExpression): void { + this.doVisitNode(node); + } + + visitLinkRequestBodyExpression(node: Oas30LinkRequestBodyExpression): void { + this.doVisitNode(node); + } + + visitLinkServer(node: Oas30LinkServer): void { + this.doVisitNode(node); + } + + visitRequestBody(node: Oas30RequestBody): void { + this.doVisitNode(node); + } + + visitCallback(node: Oas30Callback): void { + this.doVisitNode(node); + } + + visitCallbackPathItem(node: Oas30CallbackPathItem): void { + this.doVisitNode(node); + } + + visitServer(node: Oas30Server): void { + this.doVisitNode(node); + } + + visitServerVariable(node: Oas30ServerVariable): void { + this.doVisitNode(node); + } + + visitAnyOfSchema(node: Oas30AnyOfSchema): void { + this.doVisitNode(node); + } + + visitOneOfSchema(node: Oas30OneOfSchema): void { + this.doVisitNode(node); + } + + visitNotSchema(node: Oas30NotSchema): void { + this.doVisitNode(node); + } + + visitComponents(node: Oas30Components): void { + this.doVisitNode(node); + } + + visitExampleDefinition(node: Oas30ExampleDefinition): void { + this.doVisitNode(node); + } + + visitRequestBodyDefinition(node: Oas30RequestBodyDefinition): void { + this.doVisitNode(node); + } + + visitHeaderDefinition(node: Oas30HeaderDefinition): void { + this.doVisitNode(node); + } + + visitOAuthFlows(node: Oas30OAuthFlows): void { + this.doVisitNode(node); + } + + visitImplicitOAuthFlow(node: Oas30ImplicitOAuthFlow): void { + this.doVisitNode(node); + } + + visitPasswordOAuthFlow(node: Oas30PasswordOAuthFlow): void { + this.doVisitNode(node); + } + + visitClientCredentialsOAuthFlow(node: Oas30ClientCredentialsOAuthFlow): void { + this.doVisitNode(node); + } + + visitAuthorizationCodeOAuthFlow(node: Oas30AuthorizationCodeOAuthFlow): void { + this.doVisitNode(node); + } + + visitLinkDefinition(node: Oas30LinkDefinition): void { + this.doVisitNode(node); + } + + visitCallbackDefinition(node: Oas30CallbackDefinition): void { + this.doVisitNode(node); + } + + visitDiscriminator(node: Oas30Discriminator): void { + this.doVisitNode(node); + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_visitors/path-items.visitor.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_visitors/path-items.visitor.ts new file mode 100644 index 000000000..694fa8d25 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_visitors/path-items.visitor.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {OasPathItem} from "oai-ts-core"; +import {AbstractCombinedVisitorAdapter} from "./base.visitor"; + +/** + * Visitor used to find path items. + */ +export class FindPathItemsVisitor extends AbstractCombinedVisitorAdapter { + + public pathItems: OasPathItem[] = []; + + /** + * C'tor. + * @param {string} filterCriteria + */ + constructor(private filterCriteria: string) { + super(); + } + + /** + * Called when a path item is visited. + * @param {OasPathItem} node + */ + visitPathItem(node: OasPathItem): void { + if (this.acceptThroughFilter(node.path())) { + this.pathItems.push(node); + } + } + + /** + * Sorts and returns the path items. + */ + public getSortedPathItems() { + return this.pathItems.sort( (pathItem1, pathItem2) => { + return pathItem1.path().localeCompare(pathItem2.path()); + }); + } + + /** + * Returns true if the given name is accepted by the current filter criteria. + * @param name + * @return {boolean} + */ + private acceptThroughFilter(name: string): boolean { + if (this.filterCriteria === null) { + return true; + } + return name.toLowerCase().indexOf(this.filterCriteria) != -1; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/_visitors/schema-definitions.visitor.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/_visitors/schema-definitions.visitor.ts new file mode 100644 index 000000000..dad4e3616 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/_visitors/schema-definitions.visitor.ts @@ -0,0 +1,85 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Oas20SchemaDefinition, Oas30SchemaDefinition, OasPathItem} from "oai-ts-core"; +import {AbstractCombinedVisitorAdapter} from "./base.visitor"; + +/** + * Visitor used to find schema definitions. + */ +export class FindSchemaDefinitionsVisitor extends AbstractCombinedVisitorAdapter { + + schemaDefinitions: (Oas20SchemaDefinition|Oas30SchemaDefinition)[] = []; + + /** + * C'tor. + * @param {string} filterCriteria + */ + constructor(private filterCriteria: string) { + super(); + } + + /** + * Called when a schema definition is visited. + * @param {Oas20SchemaDefinition | Oas30SchemaDefinition} node + */ + visitSchemaDefinition(node: Oas20SchemaDefinition | Oas30SchemaDefinition): void { + let name: string = FindSchemaDefinitionsVisitor.definitionName(node); + if (this.acceptThroughFilter(name)) { + this.schemaDefinitions.push(node); + } + } + + /** + * Sorts and returns the path items. + */ + public getSortedSchemaDefinitions(): (Oas20SchemaDefinition | Oas30SchemaDefinition)[] { + return this.schemaDefinitions.sort( (def1, def2) => { + let name1: string = FindSchemaDefinitionsVisitor.definitionName(def1); + let name2: string = FindSchemaDefinitionsVisitor.definitionName(def2); + return name1.localeCompare(name2); + }); + } + + /** + * Figures out the definition name regardless of the version of the model. + * @param {Oas20SchemaDefinition | Oas30SchemaDefinition} definition + * @return {string} + */ + public static definitionName(definition: Oas20SchemaDefinition|Oas30SchemaDefinition): string { + let name: string; + if (definition['definitionName']) { + name = (definition as Oas20SchemaDefinition).definitionName(); + } else { + name = (definition as Oas30SchemaDefinition).name(); + } + return name; + } + + /** + * Returns true if the given name is accepted by the current filter criteria. + * @param name + * @return {boolean} + */ + private acceptThroughFilter(name: string): boolean { + //console.info("Accepting: %s through filter: %s", name, this.filterCriteria); + if (this.filterCriteria === null) { + return true; + } + return name.toLowerCase().indexOf(this.filterCriteria) != -1; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/api-editor.page.css b/front-end/studio/src/app/pages/apis/{apiId}/editor/api-editor.page.css new file mode 100644 index 000000000..2e92bce9b --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/api-editor.page.css @@ -0,0 +1,36 @@ +#api-editor { + position: absolute; + top: 85px; + left: 120px; + right: 0; + bottom: 0; +} +.activeCollaboratorIcons { + margin-right: 50px; +} +.activeCollaboratorIcons .activeCollaboratorIcon { + font-size: 26px; + color: #00659c; + padding-top: 6px; +} +.activeCollaboratorIcons .activeCollaboratorIcon.tree { + color: #50A162; +} +.activeCollaboratorIcons .activeCollaboratorIcon.bus { + color: #B65B80; +} +.activeCollaboratorIcons .activeCollaboratorIcon.bomb { + color: #D94C4C; +} +.activeCollaboratorIcons .activeCollaboratorIcon.university { + color: #477187; +} +.activeCollaboratorIcons .activeCollaboratorIcon.rocket { + color: #D4786A; +} +.activeCollaboratorIcons .activeCollaboratorIcon.taxi { + color: #B10DC9; +} +.activeCollaboratorIcons .activeCollaboratorIcon:hover { + opacity: .8; +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/api-editor.page.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/api-editor.page.html new file mode 100644 index 000000000..4925260d2 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/api-editor.page.html @@ -0,0 +1,53 @@ +
    + +
  • +
  • +
  • +
  • +
    +
    + + +
    +
    + +
    + + +
    +
    +
    +
    +
    +

    +

    Loading API definition...

    +

    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +

    +

    Opening an API Design editing session...

    +

    +
    +
    +
    +
    +
    + + + +
    + diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/api-editor.page.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/api-editor.page.ts new file mode 100644 index 000000000..635678c39 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/api-editor.page.ts @@ -0,0 +1,259 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Component, Inject, ViewChild, Injectable, ViewChildren, QueryList, + AfterViewInit, NgZone +} from "@angular/core"; +import {ActivatedRoute, Router, CanDeactivate} from "@angular/router"; +import {EditableApiDefinition} from "../../../../models/api.model"; +import {IApiEditingSession, IApisService} from "../../../../services/apis.service"; +import {ApiEditorComponent} from "./editor.component"; +import {AbstractPageComponent} from "../../../../components/page-base.component"; +import {ICommand, OtCommand} from "oai-ts-commands"; +import {BehaviorSubject} from "rxjs/BehaviorSubject"; +import {Observable} from "rxjs/Observable"; +import {EditorDisconnectedDialogComponent} from "./_components/dialogs/editor-disconnected.component"; +import {ApiDesignCommandAck} from "../../../../models/ack.model"; +import {ApiEditorUser} from "../../../../models/editor-user.model"; + +@Component({ + moduleId: module.id, + selector: "api-editor-page", + templateUrl: "api-editor.page.html", + styleUrls: ["api-editor.page.css"] +}) +export class ApiEditorPageComponent extends AbstractPageComponent implements AfterViewInit { + + public apiDefinition: EditableApiDefinition; + + protected isDirty: boolean = false; + protected isSaving: boolean = false; + + @ViewChildren("apiEditor") _apiEditor: QueryList; + @ViewChild("editorDisconnectedModal") editorDisconnectedModal: EditorDisconnectedDialogComponent; + + private editingSession: IApiEditingSession; + public activeCollaborators: ApiEditorUser[] = []; + private _activeCollaboratorIds: string[] = [ + "tree", "bus", "bomb", "university", "rocket", "taxi" + ]; + + private pendingCommands: OtCommand[] = []; + private _pendingCommandsSubject: BehaviorSubject = new BehaviorSubject([]); + private _pendingCommands: Observable = this._pendingCommandsSubject.asObservable(); + + private currentEditorSelection: string; + + /** + * Constructor. + * @param {Router} router + * @param {ActivatedRoute} route + * @param {NgZone} zone + * @param {IApisService} apis + */ + constructor(private router: Router, route: ActivatedRoute, private zone: NgZone, + @Inject(IApisService) private apis: IApisService) { + super(route); + this.apiDefinition = new EditableApiDefinition(); + } + + /** + * Called to kick off loading the page's async data. + * @param params + */ + public loadAsyncPageData(params: any): void { + let __component: ApiEditorPageComponent = this; + + console.info("[ApiEditorPageComponent] Loading async page data"); + let apiId: string = params["apiId"]; + this.apiDefinition.name = apiId; + this.apiDefinition.id = apiId; + + this.apis.editApi(apiId).then( def => { + console.info("[ApiEditorPageComponent] Definition loaded. Opening editing session."); + this.apiDefinition = def; + this.loaded("def"); + this.editingSession = this.apis.openEditingSession(def); + this.editingSession.commandHandler({ + onCommand: (command) => { + this.zone.run(() => { + __component.executeCommand(command); + }); + }, + onAck: (ack) => { + this.zone.run(() => { + __component.finalizeCommand(ack); + }); + } + }); + this.editingSession.activityHandler( { + onJoin: (user) => { + this.zone.run(() => { + if (this._activeCollaboratorIds.length === 0) { + user.attributes["id"] = "user-secret"; + } else { + user.attributes["id"] = this._activeCollaboratorIds.splice(0, 1)[0]; + } + this.activeCollaborators.push(user); + this.activeCollaborators.sort((c1, c2) => { + return c1.userName.localeCompare(c2.userName); + }); + __component.editingSession.sendSelection(__component.currentEditorSelection); + }); + }, + onLeave: (user) => { + this.zone.run(() => { + if (user.attributes["id"]) { + this._activeCollaboratorIds.push(user.attributes["id"]); + } + console.info("User left the session, clearing their selection."); + __component.updateSelection(user, null); + for (let idx = this.activeCollaborators.length - 1; idx >= 0; idx--) { + if (this.activeCollaborators[idx].userId === user.userId) { + this.activeCollaborators.splice(idx, 1); + return; + } + } + }); + }, + onSelection: (user, selection) => { + console.info("User %s selection changed to: %s", user.userName, selection); + this.zone.run(() => { + __component.updateSelection(user, selection); + }); + } + }); + this.editingSession.connect({ + onConnected: () => { + console.info("[ApiEditorPageComponent] Editing session connected. Marking 'session' as loaded."); + this.zone.run(() => { + this.loaded("session"); + }); + }, + onClosed: () => { + console.info("[ApiEditorPageComponent] **Notice** editing session disconnected normally."); + }, + onDisconnected: (code) => { + // TODO what to do when an unexpected disconnect event happens?? + console.info("[ApiEditorPageComponent] **Notice** editing session DROPPED! Reason code: %o", code); + this.zone.run(() => { + this.editorDisconnectedModal.open(); + }); + } + }); + }).catch(error => { + console.error("[ApiEditorPageComponent] Error editing API design."); + this.error(error); + }); + } + + public ngAfterViewInit(): void { + this._apiEditor.changes.subscribe( () => { + if (this._apiEditor.first) { + console.info("Editor is available!"); + this._pendingCommands.subscribe( commands => { + this.pendingCommands = []; + commands.forEach( command => { + this._apiEditor.first.executeCommand(command); + }); + }); + } + }); + } + + public loadingState(): string { + if (this.isLoaded("session")) { + return "loaded"; + } + if (this.isLoaded("def")) { + return "loading-session"; + } + return "loading-def"; + } + + /** + * Called when the page is destroyed. + */ + public ngOnDestroy(): void { + this.editingSession.close(); + } + + /** + * Called when the editor fires this event. + * @param {ICommand} command + */ + public onCommandExecuted(command: OtCommand): void { + this.editingSession.sendCommand(command); + } + + /** + * Called when the user's selection changes. + * @param {string} selection + */ + public onSelectionChanged(selection: string): void { + this.currentEditorSelection = selection; + this.editingSession.sendSelection(selection); + } + + /** + * Executes the given command in the editor. + * @param {ICommand} command + */ + protected executeCommand(command: OtCommand): void { + this.pendingCommands.push(command); + this._pendingCommandsSubject.next(this.pendingCommands); + } + + /** + * Finalizes a given command after receiving an ack from the server. + * @param {ApiDesignCommandAck} ack + */ + protected finalizeCommand(ack: ApiDesignCommandAck): void { + this._apiEditor.first.finalizeCommand(ack); + } + + /** + * Updates the selection state for the given user. + * @param {ApiEditorUser} user + * @param {string} selection + */ + protected updateSelection(user: ApiEditorUser, selection: string) { + // TODO convert this to a pubsub model like pending commands - I think selection update events may arrive before the editor is initialized. + if (this._apiEditor.first) { + this._apiEditor.first.updateCollaboratorSelection(user, selection); + } + } +} + + +/** + * Guards against the user losing changes to the editor. + */ +@Injectable() +export class ApiEditorPageGuard implements CanDeactivate { + + /** + * Called by angular 2 to determine whether the user is allowed to navigate away from the + * editor. + * @param component + */ + public canDeactivate(component: ApiEditorPageComponent): Promise | boolean { + return true; + } + +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.css b/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.css new file mode 100644 index 000000000..19f1e5845 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.css @@ -0,0 +1,1198 @@ +.api-editor .dropdown.empty > button.btn > span { + color: #bbb; +} +.api-editor.light span.empty, +.api-editor.light div.empty, +.api-editor.light button.empty { + color: #bbb; +} +.api-editor.light span.path-param, +.api-editor.light span.server-variable { + color: #ff6a7c; +} +.api-editor.light span.path-segment::before { + color: #363636; +} +.api-editor.light .context-help .context-help-panel p > a { + color: #0088ce; +} +.api-editor.light .context-help .context-help-panel p > a:hover { + color: #00659c; +} +.api-editor.light div.api-item-description { + border-left: 2px solid #39a5dc; +} +.api-editor.light div.api-item-description.empty { + border-left: 2px solid #bbb; + color: #bbb; +} +.api-editor.light .editor-master { + background-color: white; + border-right: 1px solid #ddd; +} +.api-editor.light .editor-detail { + background-color: white; +} +.api-editor.light .editor-detail .detail-actionbar { + border-bottom-color: #ddd; +} +.api-editor.light .editor-detail .hover-overlay { + border-color: #39a5dc; +} +.api-editor.light .editor-detail .hover-overlay .overlay-actions { + background: #39a5dc; + border-left-color: #39a5dc; +} +.api-editor.light .editor-detail .hover-overlay .overlay-actions .overlay-action { + background-color: #39a5dc; + color: white; + -webkit-transition: color 500ms; + -moz-transition: color 500ms; + -ms-transition: color 500ms; + -o-transition: color 500ms; + transition: color 500ms; +} +.api-editor.light .editor-detail .hover-overlay .overlay-actions .overlay-action:hover { + color: gold; +} +.api-editor.light .editor-detail .edit-overlay { + border-color: #bbb; + background-color: white; +} +.api-editor.light .editor-detail .edit-overlay.focus { + box-shadow: 3px 3px 3px rgba(0, 136, 206, 0.3), -3px 3px 3px rgba(0, 136, 206, 0.3); + border-color: #0088ce; +} +.api-editor.light .editor-detail .edit-overlay.hover { + border-color: #7dc3e8; +} +.api-editor.light .editor-detail table tbody tr:hover { + background-color: #efefef; +} +.api-editor.light .editor-master .editor-main { + background-color: #eee; + -webkit-transition: background-color 200ms; + -moz-transition: background-color 200ms; + -ms-transition: background-color 200ms; + -o-transition: background-color 200ms; + transition: background-color 200ms; +} +.api-editor.light .editor-master .editor-main:hover { + background-color: #d1eaf7; +} +.api-editor.light .editor-master .editor-main.selected { + background-color: #a5d6ef; +} +.api-editor.light .editor-master .editor-search { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} +.api-editor.light .editor-master .editor-search input { + background-color: #eee; + color: #333; +} +.api-editor.light .editor-master .editor-validation.expanded { + border-bottom: 1px solid #ddd; +} +.api-editor.light .editor-master .editor-validation.expanded .validation-problem.selected { + background-color: #a5d6ef; +} +.api-editor.light .section .section-header { + border-bottom: 1px dotted #ddd; +} +.api-editor.light .section .section-header a { + color: #333; +} +.api-editor.light .editor-master .editor-outline .api-path { + -webkit-transition: background-color 200ms; + -moz-transition: background-color 200ms; + -ms-transition: background-color 200ms; + -o-transition: background-color 200ms; + transition: background-color 200ms; +} +.api-editor.light .editor-master .editor-outline .api-path:hover { + background-color: #d1eaf7; +} +.api-editor.light .editor-master .editor-outline .api-path.selected { + background-color: #a5d6ef; + border-left: 2px solid #39a5dc; +} +.api-editor.light .editor-master .editor-outline .api-path.contexted, +.api-editor .editor-master .editor-outline .api-path.contexted:hover { + background-color: #FFCC87; + border-left: 2px dotted #E89F3D; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label { + -webkit-transition: border-color 350ms, box-shadow 350ms; + -moz-transition: border-color 350ms, box-shadow 350ms; + -ms-transition: border-color 350ms, box-shadow 350ms; + -o-transition: border-color 350ms, box-shadow 350ms; + transition: border-color 350ms, box-shadow 350ms; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label:hover { + box-shadow: 0 0 3px #646464; + border: 1px solid #666; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.tree { + border: 2px dashed #50A162; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.bus { + border: 2px dashed #B65B80; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.bomb { + border: 2px dashed #D94C4C; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.university { + border: 2px dashed #477187; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.rocket { + border: 2px dashed #D4786A; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.taxi { + border: 2px dashed #B10DC9; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.selected { + border: 1px solid black; + box-shadow: 0 0 3px #141414; +} +.api-editor.light .editor-master .editor-outline .api-definition { + -webkit-transition: background-color 200ms; + -moz-transition: background-color 200ms; + -ms-transition: background-color 200ms; + -o-transition: background-color 200ms; + transition: background-color 200ms; +} +.api-editor.light .editor-master .editor-outline .api-definition:hover { + background-color: #d1eaf7; +} +.api-editor.light .editor-master .editor-outline .api-definition.selected { + background-color: #a5d6ef; + border-left: 2px solid #39a5dc; +} +.api-editor.light .editor-master .editor-outline .api-definition.contexted, +.api-editor .editor-master .editor-outline .api-definition.contexted:hover { + background-color: #FFCC87; + border-left: 2px dotted #E89F3D; +} +.api-editor.light .editor-master .editor-outline .api-response { + -webkit-transition: background-color 200ms; + -moz-transition: background-color 200ms; + -ms-transition: background-color 200ms; + -o-transition: background-color 200ms; + transition: background-color 200ms; +} +.api-editor.light .editor-master .editor-outline .api-response:hover { + background-color: #d1eaf7; +} +.api-editor.light .editor-master .editor-outline .api-response.selected { + background-color: #a5d6ef; + border-left: 2px solid #39a5dc; +} +.api-editor.light .editor-detail .detail-title { + border-bottom: 1px solid #ddd; +} +.api-editor.light .editor-detail .detail-title .detail-actions { + border-left: 1px solid #ddd; +} +.api-editor.light .editor-detail .detail-title .detail-actions .dropdown .btn { + color: #333; +} +.api-editor.light .editor-detail .detail-title .detail-actions:hover { + background-color: #d1eaf7; +} +.api-editor.light .editor-detail .detail-content .info-section .api-md-entry .md-label { + color: #333; +} +.api-editor.light td.path-parameter, +.api-editor.light div.path-parameter, +.api-editor.light span.path-parameter { + color: #ff6a7c; +} +.api-editor.light td.query-parameter, +.api-editor.light div.query-parameter, +.api-editor.light span.query-parameter { + color: #39a5dc; +} +.api-editor.light div.statusCode.success { + color: green; +} +.api-editor.light div.statusCode.redirect { + color: blue; +} +.api-editor.light div.statusCode.problem { + color: orange; +} +.api-editor.light div.statusCode.error { + color: red; +} +.api-editor.light .api-operation-parameters td.icons, +.api-details table td.icons { + color: #666; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation { + border-bottom: 1px solid #ddd; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation .type .label { + -webkit-transition: border-color 200ms, box-shadow 200ms; + -moz-transition: border-color 200ms, box-shadow 200ms; + -ms-transition: border-color 200ms, box-shadow 200ms; + -o-transition: border-color 200ms, box-shadow 200ms; + transition: border-color 200ms, box-shadow 200ms; + box-shadow: none; + border: 1px solid transparent; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation .type .label:hover { + border-color: #666; + box-shadow: 0px 0px 8px #666; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation .name.empty { + color: #bbb; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation.empty { + color: #999; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation.empty .type .label { + background-color: #ccc; + border-color: transparent; + box-shadow: none; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation.empty .type .label:hover { + background-color: #ccc; + border-color: transparent; + box-shadow: none; +} +.api-editor.dark span.path-param { + color: #ff6a7c; +} +.api-editor.dark span.path-segment::before { + color: white; +} +.api-editor.dark div.api-item-description { + border-left: 2px solid #356c8c; +} +.api-editor.dark { + background-color: #1D2225; + color: #eee; +} +.api-editor.dark .editor-master { + background-color: #1D2225; + border-right: 1px solid #bbb; +} +.api-editor.dark .editor-detail { + background-color: #1D2225; +} +.api-editor.dark .editor-detail .hover-overlay { + border-color: #356c8c; +} +.api-editor.dark .editor-detail .hover-overlay .overlay-actions { + background: #356c8c; + border-left-color: #356c8c; +} +.api-editor.dark .editor-detail .hover-overlay .overlay-actions .overlay-action { + background-color: #356c8c; + color: white; +} +.api-editor.dark .editor-detail .edit-overlay { + border-color: #bbb; + background-color: white; +} +.api-editor.dark .editor-detail .edit-overlay.focus { + box-shadow: 3px 3px 3px rgba(0, 136, 206, 0.3), -3px 3px 3px rgba(0, 136, 206, 0.3); + border-color: #0088ce; +} +.api-editor.dark .editor-detail .edit-overlay.hover { + border-color: #7dc3e8; +} +.api-editor.dark .editor-master .editor-main { + background-color: #3f4950; +} +.api-editor.dark .editor-master .editor-main:hover { + background-color: #274f67; +} +.api-editor.dark .editor-master .editor-main.selected { + background-color: #356c8c; +} +.api-editor.dark .editor-master .editor-search { + border-top: 1px solid #bbb; + border-bottom: 1px solid #bbb; +} +.api-editor.dark .section .section-header { + border-bottom: 1px dotted #bbb; +} +.api-editor.dark .section .section-header a { + color: #fff; +} +.api-editor.dark .editor-master .editor-outline .api-path:hover { + background-color: #274f67; +} +.api-editor.dark .editor-master .editor-outline .api-path.selected { + background-color: #356c8c; + border-left: 2px solid #76accb; +} +.api-editor.dark .editor-master .editor-outline .api-path.selected .label.empty { + background-color: #ccc; +} +.api-editor.dark .editor-master .editor-outline .api-path.selected .label:hover { + border: 1px solid #274f67; +} +.api-editor.dark .editor-master .editor-outline .api-path.selected .label.selected { + border: 1px solid white; +} +.api-editor.dark .editor-master .editor-outline .api-definition:hover { + background-color: #274f67; +} +.api-editor.dark .editor-master .editor-outline .api-definition.selected { + background-color: #356c8c; + border-left: 2px solid #76accb; +} +.api-editor.dark .editor-master .editor-outline .api-response:hover { + background-color: #274f67; +} +.api-editor.dark .editor-master .editor-outline .api-response.selected { + background-color: #356c8c; + border-left: 2px solid #76accb; +} +.api-editor.dark .editor-detail .detail-title { + border-bottom: 1px solid #bbb; +} +.api-editor.dark .editor-detail .detail-title .detail-actions { + border-left: 1px solid #bbb; +} +.api-editor.dark .editor-detail .detail-title .detail-actions .dropdown .btn { + color: #fff; +} +.api-editor.dark .editor-detail .detail-title .detail-actions:hover { + background-color: #274f67; +} +.api-editor.dark .editor-detail .detail-tabs ul { + border-bottom-color: #bbb; +} +.api-editor.dark .editor-detail .detail-content .info-section .api-md-entry .md-label { + color: #fff; +} +.api-editor.dark .api-operation-parameters td.parameter { + color: #ff6a7c; +} +.api-editor.dark .api-operation-parameters td.icons, +.api-details table td.icons { + color: #666; +} +.api-editor.dark .editor-detail .api-path-detail .operations-section .api-operation { + border-bottom: 1px solid #bbb; +} +.api-editor.dark .editor-detail .api-path-detail .operations-section .api-operation.empty { + color: #888; +} +.api-editor.dark .editor-detail .api-path-detail .operations-section .api-operation.empty .type .label { + color: #888; + background-color: #444; +} +.modal.in .modal-dialog { + -ms-transform: none; + transform: none; + margin-top: 50px; +} +.modal.fade .modal-dialog { + -ms-transform: none; + transform: none; + transition: margin-top 0.3s ease-out; +} +.modal-dialog { + width: 650px; + margin-top: 0px; + margin-left: auto; +} +.api-editor span.path-param, +.api-editor span.server-variable { + font-weight: 400; +} +.api-editor span.path-segment:before { + content: "/"; +} +.api-editor .context-help a { + cursor: pointer; +} +.api-editor .context-help a .context-help-icon { + -webkit-transition: opacity 300ms; + -moz-transition: opacity 300ms; + -ms-transition: opacity 300ms; + -o-transition: opacity 300ms; + transition: opacity 300ms; +} +.api-editor .context-help .context-help-panel { + position: fixed; + margin: 0; + padding: 10px; + border: 1px solid #bbb; + border-radius: 3px; + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + z-index: 150; + background: white; + font-size: 14px; + font-weight: normal; + max-width: 450px; + cursor: default; +} +.api-editor .context-help .context-help-panel p { + padding: 3px; + margin: 0; + line-height: 18px; +} +.api-editor .context-help .context-help-panel pre { + margin-top: 10px; +} +.api-editor .context-help .context-help-panel p > a:hover { + text-decoration: underline; +} +.api-editor div.tab-panel { + border-top: none; +} +.api-editor div.tab-panel { + border-color: #dddddd; +} +.api-editor .nav-tabs > li > a { + cursor: pointer; +} +.api-editor .nav-tabs > li > a.active { + cursor: default; +} +.api-editor .nav-tabs > li > a > button { + visibility: hidden; +} +.api-editor .nav-tabs > li > a:hover > button { + visibility: visible; +} +.api-editor .api-item-description { + font-size: 16px; + padding-left: 5px; + margin-right: 30px; +} +.api-editor .api-item-title { + font-weight: 500; +} +.api-editor .editor-master { + position: absolute; + top: 0; + left: 0; + right: 70%; + bottom: 0; + overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; +} +.api-editor .editor-detail { + position: absolute; + left: 30%; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; +} +.api-editor .editor-detail .editor-detail-form { + display: flex; + height: 100%; + flex-direction: column; + flex: 1; + overflow: hidden; +} +.api-editor .editor-master .editor-main:hover { + cursor: pointer; +} +.api-editor .editor-master .editor-main.selected { + cursor: default; +} +.api-editor .editor-master .editor-main h2 { + padding: 10px; + margin: 0; +} +.api-editor .editor-master .editor-main.oai30 { + padding-left: 36px; + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QkFEREX5uyfVAAABYBJREFUWMO9l39MU1cUxz/3vdJWi7/wB0qQqKBx82dlhjhkDghMQeuWbMZRY+JfbrpmLptzJktmzDTTGaMS3MxitriAzl8ZVZyJvtaJhpERQQPq0FXU+QOhCNpiSqFvf/hKKpa2boknaV7fPefe7znnfe+55wpeguRk544BdgCLABk4CaxzOJWr4iWAJwDuftQJ0ktIwHcRdDW6aLNtZbkUFymh7ylA2r1zj6q6Oron2UtrL0ZZYkkE3QRdDMCJtrLcEmA2kNJroKoTgDqL1YyW4r+AzfbS2ooXSU8EDtiFrWznSSA/jDJwr7IjtetRz40wur+BNfbS2uMaBw4B7/YDUh3Wgd3VWcskwfL6a3qfECx8QQcAWo3DPGNbG1LyAHs/Np3AkF4Saqlk19m5hcDPAZW8CWP9O/8D6doNgzvHtDak5PcBrwRatN8Zh1MxOZxKty4Ibi+txWI17zq9x7uk4JP45YEe9hkN6n5gjbaHYxEPMMp9NbkQ+DVkfJrDqdSHmyABQfB0wAYkVmz3fDVwqFQAjHgltWsE0BYDeIuQAwmeuwkL+4DPdjiV+kVL08NOkixWMxar2QTU9DJTkHpkw6NtpmHSAuDLAUa1MAp4k2n0w6THt0csAI6GjGcnml0XLFZzs5AD5rAO2EtrAVY/tz0kXj284VGxrBPLxif7t6gq5X0yF+SPa9TMG2nNF1IL4BmbvKGp96u9zUPvAKM0DoTNgAxsCbtHBWkV2x9v7fZzbNJ4/17gMvA+YLxZ0eYCsgxDOqe4TqQX9gHPGpZ271z3E70LGK2NmSxWc1Y4DhRESW/Sqd2ezwxG6oqLlCmN+znQ8ANxg1LHD/bcTahyX0nODwFXAbPDqZzzdxqaQsCD8lE4B/JjINjGlbPO3s7Jzh3u99ACtAMPgSd9CDfD4VTqLFbzAyAxlrIsLFZzDZAexQG9526C0IqH3I/NnCHjHtT0dOnuAiMjrJVqL611hWYgLQr4IXtprR/YGwH8hsOp/NHTpUuMAg4wMfRFF+ihKeJZofKL9n9qBLvxAPEphvtCEqsjnDEycP0ZBxZ/McgZITKhqniPP3XBF40ow6eZ4oCSKGZ3tAPrqQN+n7oESIowIQ+YDBwGMvqxOas9p8dA6Ht9d0FDxPNacBrA4VS2AUo/ZhbteSIGBy73dcDRD3BDW4ec2XBNP89WlntNVVUkXU+eBuYE6oD1wGCHU+mwleV+DgyPAt5aXKS0P8OBMFH5VJWV9Y36mbLMeSFAN0CyLbbOKokbrG767UjdMeBYb4E48IZImp6e6bnluxafYogW/Y6wHdHu6iw3kCBJ/FjfqC8XgoOAHkDI4usb5e5MIcgeZ477cHKWYanGh06tTfsgziD2lH/zeMbIWaZP45MN5yPVAMAV2mMGe8KcZrcstbXLR4RgRciEXU12d7YQZKoBGmfMN07wedV5wLzQVf0+deXkufo5jVXeKlRmx481VIWsHZRTxUWKK2w/sCqj8mJbu1we3M9a5JtuVrSlA5kA3vbAap9XXdtvaBn6fWqAHe5L3j89t32ZfdQq8J6tLDd8Q6LJZKAHnn7zpnL360FwgLfXD8qM9HHVABPzV5uuAF3uS94qzz++N0PU+cVFSkdo6p9zoLhI6QSm6AZIK64fbClEkB3UdXepm/UDxZJoDJPjRIms4x1Actd5j3nvdC0CthQXKafDRf9MWx40cB1tTZMN0mUgrveIm2/MGDNJVx1LUyjp+On4t56pksxrqKyzl9VufaF7gcVqFoBJq25moHn+x/G3UJkda1t886K/4MrvPimWS0rYQyOkS84RAu9btvgzgDHGrvj7VRmVa//3zSjohFYnjMA6YJXW34VKD3AQ2Ag0rsqoDLzIJeJfWj/2rgdFrTIAAAAASUVORK5CYII=') no-repeat 10px 5px; +} +.api-editor .editor-master .editor-main.oai20 { + padding-left: 36px; + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QkFEREdBjl2SgAAB/1JREFUWMONl2twVVcVx39rn/u+N7khT0hCQiABS0l5BGhHagsUilAsAo6v9pN+0AqtVes4VPvJaamPqm0Rxuc4jjPqdEBbMGUA6dgWlZZXiOX9SkgCISE3j5vc1zl7++GeXC40AfaXc+7cs/dae63/+q//Em6zjDGISO4dUIAF+ICo+zRAGhhwnxrQ+ftG38daMp5hABHBGCNAGKgBVgErgNlAWd4WDfQAh4FmYC/QISIjt553RwduuXUIaAKeAj4Pxkqkh0hm4qQzwzhOGgDL8uP3hgh4Cwj4CgBSwB+B3wCtIpIcLxpyG+O1wNeAZ7TR4Z7+81y+fpzzXf/hcm8rsXgHGTsJgM8TZEKkmpryudRPeoCqklmUF00FpB/4KfB7EbkylhMyjvG5wMvAo9eH2mm58A8On9tB27WjaG1jKQ+iLMTdbjBo7WT/szzUVSxkfsM67qt7jKLwJIAdwAsicuJWWzKG8TnAFmDRua5/s+/YFk5e3o82DpbyjAebG1HE4OgMHuVj1pQVPDL7G9RVLADYBzwjIifzbd7kgDFmCvBrYHnLxWaaP3yZK32nEaXyQnZnB9wXDJrq0kZWL3yemZOXAmwHnhaRK6M2VZ7xMPA0sPxM53vs+uAlrsSyxjN2EqMdcNF8O/MYg9Y2tpNEiaKjt5WdB1/k/NWDAOuBrxpjAq5NPHml1gRsuD7Uxj9bttIdO4MoC2MMqxdu4t6aZaTtJNuav4jjZD6GZmM0BcEyvvLo71CiOHphJ++0bEOURWdvK++0bKWkYDJF4crngLeBwyLCaGwjwDcdbfuPnHuTk5f3I8rCdlI01a9l6X1PUVM+h3CgeNwEGAxKLAqCpdSWN/Hped+msW4VjpNGxOJ/bXtpudCMMSYKfN0tcZR7+wZgXXf/WQ6d244xGkHQ2mZKRRMBXwG7Dm7mle3LcbQzJqEosRhK9rD5jcXsP76VoD9KXUUTjrYREWyd5tC57fQMnAf4MjDJGINy6XWNMZq2a0fo6GnFUl43rBDyRwHoip3C1hmXecenbttJ0TtwCYCAN5L73lJe2roP09HbCpgQsBxQCvAAK0dSA5zpfO8Git3AusyGEsXdLEGIJ3pzDDl6nCBknDRnuw6QSA/h0rpSLs/PTGWG6Og5nnd7DQghbzYCgyM9GKPR2sndShsHbZycs1rbGAwDI91ZYAWKMbmzwGN5uNx7nGTWgTmATwGlQDiVGSYW70SJwhhNKDCBWbXLKI3WMpzsIzbUTmF4IlWl92IpL0osygrrKC2sRUThsfxUlzZSECwlFu8k46SoLL6H6VUPEvRHs7gSi9hQJ2k7AVABRBQQAsg4STJOGsgCprF2BU8seZ1ouJK9R18jnuxj9f3P8+Ti15hSMZ9oeCLrF73I4/e/QCRYQkPlIp5c+jor5n2LePI6+1t+SUlhLU8seZWGykXYThpBSGaGcHQGt5UHPDe1YHGjawwey4fPGwIgnRnJ4sEbwe+LoJQHJRYBXyTLZigs5SXoi+LzhsFokuk4AEFfYS6tN1pzDmcixpjpwOmuvhO88reV2HYqm79gKTVls/nSwz/DYPjxG4/g9QYpCJZzpe8ExmjKonVorekdvIRleaksnkl/vBNjDJu+8C6J1CB//tezdFz/iJFkDIMhHCjm2TVvUVHUkAEmK+A6kPB7wkRDk9BGI6IYSvRy7MJOrvWfJxqaSEm0lr7Bdtq6D2E7KRxt09V3iqv9Z9DGIW0nuHj1A/rinZQVTSXoK+RK7BStbXsYScYQF1sTwpX4PEGAa0BcAXHgRMBXwOTSWehsflwwGkZSAwAUBisQUai8jqjEQomVK7NsahQhfxEAidQAgiBuCTs6Q1VpI35vAUALkFaADewO+qNMr/pUTj7d1GAAj+XD3AUPGAze7A1J2yN5e7JU3VD5SYK+Qtx+4ChXz72pxKK2fB6TSu7B0XaOVkZSMQAmRKru0IhHdyiKI9UADCdjuT2Otqkpn0tN2WxEJAXsAbQSEQOcAnZNnDCD+fXrczrHUh4uXTtCKjPMqgXf4zvrdmMpzxhRypJNJFjCd9fvYeX850imh2jvacl+j0GJoql+LeVF9QB/ATrzu2Ec+LnH8tnz6tdSX7kIRzt4LD8HT/+V/576E72DF9HaGTcNxsWNiOLawHneP/EHjl9sxrJ8aO3wieolzJn6GZRYcWAbMAwgeYIkAmwGNn7UvpftB75P72AbgsJ2kgT9UZRYoyw2dvhFEfCGyTgpRlIDeD0BjNFUFDXwuUUvMaP6IYAfAptFJGGM+Zgqngb8Flh86Nx23v7wJ/QMXECUlVNDcoempI2TlW0iGK2pmNDA6oWbmF33GMBbwAYR6RhTE7q5XAiyBVhwon0f+45t4WzXAUBcUXrn5egMIooZ1Q+zbM5GZlQ9BPAusFFEWj8mSsdQxguBHwGLu2NnOXLh7xw+u4OuvpNuvXuz7XlUmBiDNnqU46kubWR+w3rmTnucssI6gF3AD0SkZUxZPo4TU4ENwAbbSfu7+89wqfswZzrf53LvcQaGr5K2Ewjg84YoCk9ictlsplc9SG35PCqKGrCUNw78AviViHTcdjAZx4kw8ICrltdo45BIDZBID5JMD5FxkoDg8/gJeAsJ+AoJ+aOIKO2W2lbgiIgk7mo0u81wGgGmAZ91h9NGV8jkr0HgGLAb2AlcAuKj8vuuh9OxnMj7bbkaMgSUuE4Yl0f6gITLrM7djuf/B+0TxoO0ygiXAAAAAElFTkSuQmCC') no-repeat 10px 7px; +} +.api-editor .editor-master .editor-search { + padding: 10px; + display: table; + width: 100%; +} +.api-editor .editor-master .editor-search > search { + display: table-cell; + width: 100%; + padding-right: 8px; +} +.api-editor .editor-master .editor-search .validation-icon { + font-size: 15px; + float: right; + cursor: pointer; +} +.api-editor .editor-master .editor-search .validation-icon.has-errors { + color: #c00; +} +.api-editor .editor-master .editor-search .validation-icon.clean { + color: #3f9c35; +} +.api-editor .editor-master .editor-validation { + line-height: 18px; + font-size: 14px; + display: none; +} +.api-editor .editor-master .editor-validation.expanded { + display: block; +} +.api-editor .editor-master .editor-validation.expanded .validation-problem { + padding: 5px; +} +.api-editor .editor-master .editor-validation.expanded .validation-problem:hover { + background-color: #eee; + cursor: pointer; +} +.api-editor .editor-master .editor-validation.expanded .validation-problem.selected { + cursor: default; +} +.api-editor .editor-master .editor-validation .validation-problem .icon { + color: #c00; +} +.api-editor .editor-master .editor-validation .validation-problem .code { + font-weight: bold; + color: #666; +} +.api-editor .editor-master .editor-validation.expanded .validation-no-problem { + padding: 5px; +} +.api-editor .editor-master .editor-validation .validation-no-problem .icon { + color: #3f9c35; +} +.api-editor .editor-master .editor-outline { + padding: 5px; + height: 100%; + overflow-y: auto; +} +.api-editor .problem-marker:before { + content: "âš "; + text-rendering: auto; + color: #c00; + position: absolute; + top: 2px; + left: 1px; + font-size: 13px; +} +.api-editor .section { + padding: 0; + margin: 0; +} +.api-editor .section .section-header { + font-size: 15px; + padding-bottom: 3px; + margin-bottom: 5px; + line-height: inherit; +} +.api-editor .section .section-header a { + cursor: pointer; + text-decoration: none; +} +.api-editor .section .section-header a:before { + display: inline-block; + font-size: 13px; + margin-left: 5px; + margin-right: 0; + text-align: center; + vertical-align: 0; + font-weight: 300; +} +.api-editor .section .section-header a span.section-label { + font-weight: 600; +} +.api-editor .section .section-header .context-help a span.fa { + opacity: 0; + font-size: 14px; +} +.api-editor .section .section-header:hover .context-help a span.fa { + opacity: 255; +} +.api-editor .section .section-header .context-help a:hover span.fa { + color: #356c8c; +} +.api-editor .section .section-header .btn { + margin-top: 1px; +} +.api-editor .section .section-body { + margin-bottom: 20px; +} +.api-editor .editor-master .editor-outline .api-path { + position: relative; + padding-left: 15px; + font-size: 14px; + border-left: 2px solid transparent; +} +.api-editor .editor-master .editor-outline .api-path.tree { + border-left: 2px dotted #50A162; + background-color: #daedde; +} +.api-editor .editor-master .editor-outline .api-path.bus { + border-left: 2px dotted #B65B80; + background-color: #f8eff3; +} +.api-editor .editor-master .editor-outline .api-path.bomb { + border-left: 2px dotted #D94C4C; + background-color: #fffdfd; +} +.api-editor .editor-master .editor-outline .api-path.university { + border-left: 2px dotted #477187; + background-color: #c4d6e0; +} +.api-editor .editor-master .editor-outline .api-path.rocket { + border-left: 2px dotted #D4786A; + background-color: #ffffff; +} +.api-editor .editor-master .editor-outline .api-path.taxi { + border-left: 2px dotted #B10DC9; + background-color: #f1b2fa; +} +.api-editor .editor-master .editor-outline .api-path:hover { + cursor: pointer; +} +.api-editor .editor-master .editor-outline .api-path.selected { + cursor: default; +} +.api-editor .editor-master .editor-outline .api-path.selected .label { + font-size: 12px; + cursor: pointer; + border: 1px solid transparent; + border-radius: 2px; + padding: 2px 8px 2px 8px; +} +.api-editor .editor-master .editor-outline .api-path.problem-marker:before { + top: 2px; + left: 1px; + font-size: 12px; +} +.api-editor .editor-master .editor-outline .api-path .api-operations { + margin-top: 5px; + padding-bottom: 10px; +} +.api-editor .editor-master .editor-outline .api-path .api-operations .label { + position: relative; +} +.api-editor .editor-master .editor-outline .api-path .api-operations .label.problem-marker:before { + color: yellow; + top: -4px; + left: -2px; + font-size: 12px; +} +.api-editor .editor-master .editor-outline .api-path.contexted { + cursor: default; +} +.api-editor .editor-master .editor-outline .api-definition { + padding-left: 15px; + font-size: 14px; + border-left: 2px solid transparent; + position: relative; +} +.api-editor .editor-master .editor-outline .api-definition:hover { + cursor: pointer; +} +.api-editor .editor-master .editor-outline .api-definition.tree { + border-left: 2px dotted #50A162; + background-color: #daedde; +} +.api-editor .editor-master .editor-outline .api-definition.bus { + border-left: 2px dotted #B65B80; + background-color: #f8eff3; +} +.api-editor .editor-master .editor-outline .api-definition.bomb { + border-left: 2px dotted #D94C4C; + background-color: #fffdfd; +} +.api-editor .editor-master .editor-outline .api-definition.university { + border-left: 2px dotted #477187; + background-color: #c4d6e0; +} +.api-editor .editor-master .editor-outline .api-definition.rocket { + border-left: 2px dotted #D4786A; + background-color: #ffffff; +} +.api-editor .editor-master .editor-outline .api-definition.taxi { + border-left: 2px dotted #B10DC9; + background-color: #f1b2fa; +} +.api-editor .editor-master .editor-outline .api-definition.selected { + cursor: default; +} +.api-editor .editor-master .editor-outline .api-definition.problem:before { + top: 4px; + left: 1px; +} +.api-editor .editor-master .editor-outline .api-response { + padding-left: 15px; + font-size: 14px; +} +.api-editor .editor-master .editor-outline .api-response:hover { + cursor: pointer; +} +.api-editor .editor-master .editor-outline .api-response.selected { + cursor: default; +} +.api-editor .editor-detail .section { + padding-top: 8px; +} +.api-editor .editor-detail .api-inline-editor-form { + display: inline-block; + position: relative; +} +.api-editor .editor-detail .api-description-editor-form { + width: 100%; +} +.api-editor .editor-detail .api-inline-editor-form input { + font-size: inherit; + display: inline; + height: auto; +} +.api-editor .editor-detail .api-inline-editor-form textarea { + width: 100%; + min-height: 50px; +} +.api-editor .editor-detail .api-inline-editor-form .inline-text-editor-input { + min-width: 90px; +} +.api-editor .editor-detail .hover-overlay { + border: 1px solid; + border-radius: 1px; + background-color: transparent; + position: fixed; + padding: 0; + margin: 0; + cursor: text; + -webkit-transition: opacity 500ms; + -moz-transition: opacity 500ms; + -ms-transition: opacity 500ms; + -o-transition: opacity 500ms; + transition: opacity 500ms; + opacity: 0; + z-index: 100; +} +.api-editor .editor-detail .hover-overlay:hover { + opacity: 100; +} +.api-editor .editor-detail .hover-overlay .overlay-actions { + float: right; + width: 20px; + height: 100%; + padding: 0; + margin: 0; + border-left: 1px solid; +} +.api-editor .editor-detail .hover-overlay .overlay-actions .overlay-action { + width: 19px; + height: 100%; + border: none; + padding: 0; + margin: 0; + font-size: 13px; +} +.api-editor .editor-detail .edit-overlay { + position: absolute; + left: 0; + top: 100%; + margin: -1px 0 0 0; + padding: 0; + border: 1px solid #bbb; + border-top: 0px; + border-radius: 1px; + padding: 3px; + -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + -moz-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + -ms-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + z-index: 150; +} +.api-editor .editor-detail .edit-overlay .overlay-action { + margin: 2px; +} +.api-editor .editor-detail .detail-title { + height: 45px; +} +.api-editor .editor-detail .detail-title .detail-label { + float: left; + line-height: 44px; + font-size: 18px; + font-weight: 500; + padding-left: 10px; +} +.api-editor .editor-detail .detail-title .detail-icon { + float: left; + line-height: 44px; + height: 44px; + text-transform: uppercase; +} +.api-editor .editor-detail .detail-title .detail-icon .label { + border-radius: 2px; + padding: 2px 8px 2px 8px; +} +.api-editor .editor-detail .detail-title .detail-actions { + float: right; +} +.api-editor .editor-detail .detail-title .detail-actions .dropdown-menu { + left: auto; + right: 0; +} +.api-editor .editor-detail .detail-title .detail-actions .dropdown .btn { + padding-left: 20px; + padding-right: 20px; + line-height: 38px; + font-size: 18px; + height: 44px; +} +.api-editor .editor-detail .detail-title .detail-actions:hover { + cursor: pointer; +} +.api-editor .editor-detail .editor-detail-form .detail-tabs ul { + padding-left: 10px; +} +.api-editor .editor-detail .editor-detail-form .detail-tabs ul li a { + cursor: pointer; +} +.api-editor .editor-detail .editor-detail-form .detail-tabs ul li.disabled a { + cursor: not-allowed; +} +.api-editor .editor-detail .detail-actionbar { + text-align: right; + padding: 5px 25px 5px 5px; + border-bottom: 1px solid; +} +.api-editor .editor-detail .detail-content { + height: 100%; + overflow-y: auto; + padding: 10px; +} +.api-editor .editor-detail .detail-content .section .section-body { + padding-left: 15px; + padding-right: 15px; +} +.api-editor .editor-detail .detail-content .info-section .api-title-and-version { + margin-top: 10px; + margin-bottom: 5px; +} +.api-editor .editor-detail .detail-content .info-section .api-title-and-version .api-title { + font-size: 26px; + font-weight: 300; +} +.api-editor .editor-detail .detail-content .info-section .api-title-and-version .api-version { + font-size: 20px; +} +.api-editor .editor-detail .detail-content .info-section .api-title-and-version div.api-description { + margin-top: 15px; + margin-bottom: 15px; + margin-right: 30px; +} +.api-editor .editor-detail .detail-content .info-section .api-meta-data { + margin-top: 10px; +} +.api-editor .editor-detail .detail-content .info-section .api-md-entry { + font-size: 13px; +} +.api-editor .editor-detail .detail-content .info-section .api-md-entry .md-label { + margin-top: 5px; + font-weight: 600; + margin-bottom: -5px; +} +.api-editor .editor-detail .detail-content .info-section .explanation { + font-size: 15px; +} +.api-editor #securitySchemeModal .nav-tabs > li.enabled > a { + font-weight: bold; +} +.api-editor .editor-detail .detail-content .set-license-modal .modal-dialog { + width: 950px; +} +.api-editor .editor-detail .detail-content .set-license-modal .modal-dialog .modal-body { + max-height: 500px; + overflow-y: auto; + overflow-x: hidden; +} +.api-editor .editor-detail .detail-content .container-fluid { + padding: 0; +} +.api-editor .editor-detail .detail-content .license h2 { + font-size: 17px; + font-weight: bold; +} +.api-editor .editor-detail .detail-content .license h2 span.fa { + font-size: 14px; + margin-left: 5px; +} +.api-editor .editor-detail .detail-content .license h3 { + font-size: 14px; + font-weight: bold; +} +.api-editor .editor-detail .detail-content .license ul { + margin: 0; + padding: 0 0 0 12px; +} +.api-editor .editor-detail .detail-content .license ul li > span { + color: #1D2225; + cursor: pointer; +} +.api-editor .editor-detail .detail-content .license ul li > span:hover { + border-bottom: 1px dotted; +} +.api-editor .editor-detail .detail-title .label { + margin-left: 10px; + font-size: 13px; +} +.api-editor .path-description { + margin-top: 15px; + margin-bottom: 20px; +} +.api-editor .api-operation-parameters td.parameter { + width: 1%; + white-space: nowrap; +} +.api-editor .api-operation-parameters td.icons, +.api-details table td.icons { + width: 1%; + white-space: nowrap; +} +.api-editor .api-operation-parameters td.icons i:hover { + cursor: pointer; +} +.api-editor .api-response .tlabel { + padding: 2px 10px 2px 2px; + text-align: right; +} +.api-editor .api-response .tlabel span { + font-weight: bold; +} +.api-editor .editor-detail .api-path-detail .operations-section .api-operation { + margin-top: 5px; + margin-bottom: 5px; + padding-top: 5px; + padding-bottom: 5px; +} +.api-editor .editor-detail .api-path-detail .operations-section .api-operation .type { + display: inline-table; + margin-right: 5px; +} +.api-editor .editor-detail .api-path-detail .operations-section .api-operation .type .label { + border-radius: 2px; + cursor: pointer; + padding: 2px 8px 2px 8px; +} +.api-editor .editor-detail .api-path-detail .operations-section .api-operation .name { + display: inline-table; + font-size: 15px; + font-weight: 600; +} +.api-editor .editor-detail .api-path-detail .operations-section .api-operation .description { + margin-top: 5px; + font-size: 14px; +} +.api-editor .editor-detail .api-path-detail .operations-section .api-operation div.description { + margin-right: 30px; +} +.api-editor .editor-detail .api-path-detail .operations-section .api-operation .actions { + margin-top: 5px; +} +.api-editor .editor-detail .api-path-detail .operations-section .api-operation.empty .type .label { + cursor: default; +} +.api-editor .editor-detail .api-operation-detail .request-body-section .request-body-name { + font-size: 15px; + margin-left: 10px; +} +.api-editor .editor-detail .api-operation-detail .request-body-section .description { + margin-top: 8px; + font-size: 14px; +} +.api-editor .editor-detail .api-operation-detail .request-body-section .request-body-type { + margin-top: 20px; +} +.api-editor .editor-detail .api-operation-detail .request-body-section .request-body-type .strong { + font-weight: 600; +} +.api-editor .editor-detail .api-operation-detail .request-body-section .request-body-type .dropdown { + display: inline-block; + margin-left: 3px; + margin-right: 3px; +} +.api-editor .editor-detail .api-operation-detail .request-body-section .request-body-required { + margin-top: 15px; +} +.api-editor .editor-detail .api-operation-detail .request-body-section .request-body-required .dropdown { + display: inline-block; + margin-left: 3px; + margin-right: 3px; +} +.api-editor .editor-detail .api-operation-detail .request-body-section .request-body-content { + margin-top: 15px; +} +.api-editor .editor-detail .api-operation-detail .media-type .strong { + font-weight: 600; +} +.api-editor .editor-detail .api-operation-detail .media-type .dropdown { + display: inline-block; + margin-left: 3px; + margin-right: 3px; +} +.api-editor .editor-detail .property.required, +.api-editor .editor-detail .parameter.required { + box-shadow: 1px 1px 4px #aae; +} +.api-editor .editor-detail .row.property, +.api-editor .editor-detail .row.parameter, +.api-editor .editor-detail .row.response { + margin-left: 0; + margin-right: 0; +} +.api-editor .editor-detail .property-type .api-property-required .dropdown { + display: inline-block; +} +.api-editor .editor-detail table { + font-size: 13px; +} +.api-editor .editor-detail table tbody tr td.actions > div { + visibility: hidden; +} +.api-editor .editor-detail table tbody tr:hover td.actions > div { + visibility: visible; +} +.api-editor .editor-detail .type-editor-dropdown-menu { + right: 0; + left: auto; + min-width: 400px; +} +.api-editor .editor-detail table .inline-type-editor-label { + margin-right: 36px; +} +.api-editor .editor-detail .type-editor-dropdown-menu .dropdown { + display: inline; + margin-left: 2px; +} +.api-editor .editor-detail .type-editor-dropdown-menu .dropdown-menu { + top: inherit; +} +.api-editor .editor-detail .type-editor-dropdown-menu .typeinfo { + padding: 15px; +} +.api-editor .editor-detail .type-editor-dropdown-menu .actions { + border-top: 1px solid #bbb; + text-align: right; + padding: 5px; +} +.api-editor .editor-detail .type-editor-dropdown-menu .actions .overlay-action-save { + margin-right: 3px; +} +#path-context-menu, +#definition-context-menu, +#operation-context-menu, +#response-context-menu { + display: block; + position: fixed; +} +.api-editor .editor-detail .section .typed-item-list .typed-item { + padding: 5px; + border-bottom: 1px solid #ddd; + border-right: 1px solid #ddd; + vertical-align: middle; + margin-top: 5px; + position: relative; +} +.api-editor .editor-detail .section .typed-item-list .typed-item.missing { + opacity: .4; +} +.api-editor .editor-detail .section .typed-item-list .typed-item.missing .description { + font-size: 12px; + line-height: 20px; +} +.api-editor .editor-detail .section .typed-item-list .typed-item.overridable { + background-color: #eeeeef; +} +.api-editor .editor-detail .section .typed-item-list .typed-item.editing { + border-bottom: none; + border-right: 1px solid #ccc; + background-color: #efefef; + box-shadow: 1px 1px 5px #ccc; + position: relative; +} +.api-editor .editor-detail .section .typed-item-list .typed-item .typed-item-icon { + cursor: help; + color: inherit; +} +.api-editor .editor-detail .section .typed-item-list .typed-item .typed-item-icon:hover { + color: inherit; + filter: brightness(80%); +} +.api-editor .editor-detail .section .typed-item-list .type-row { + padding-top: 12px; + padding-bottom: 10px; + border: 1px solid #ccc; + position: relative; + background-color: white; + box-shadow: 1px 1px 5px #ccc; + margin-left: 0; + margin-right: 0; +} +.api-editor .editor-detail .section .typed-item-list .typed-item .metainfo { + padding-left: 0px; +} +.api-editor .editor-detail .section .typed-item-list .typed-item .actions { + text-align: right; + padding-right: 10px; +} +.api-editor .editor-detail .section .typed-item-list .typed-item .actions button { + opacity: .25; +} +.api-editor .editor-detail .section .typed-item-list .typed-item:hover { + background-color: #eee; + box-shadow: 1px 1px 4px #ddd; +} +.api-editor .editor-detail .section .typed-item-list .typed-item.editing:hover { + box-shadow: 1px 1px 5px #ccc; + background-color: #efefef; +} +.api-editor .editor-detail .section .typed-item-list .typed-item:hover .actions button { + opacity: 1.0; +} +.api-editor .editor-detail .section .typed-item-list .typed-item.editing .actions button { + display: none; +} +.api-editor .editor-detail .section .typed-item-list .typed-item.editing .type { + visibility: hidden; +} +.api-editor .editor-detail .section .typed-item-list .typed-item .name { + font-weight: 600; + font-size: 15px; + line-height: 18px; +} +.api-editor .editor-detail .section .typed-item-list .typed-item .description { + font-size: 14px; +} +.api-editor .editor-detail .section .typed-item-list .typed-item .type { + line-height: 40px; +} +.api-editor .editor-detail .section .typed-item-list .typed-item .type .empty { + color: #999; +} +.api-editor .editor-detail .section .typed-item-list .typed-item .actions { + line-height: 40px; +} +.api-editor .editor-detail .section .typed-item-list .type-row .form-group { + margin: 5px; +} +.api-editor .editor-detail .section .typed-item-list .type-row .form-group-actions { + padding-top: 5px; +} +.api-editor .editor-detail .problem-form .actions { + margin-top: 15px; +} +.api-editor form .validation-error-label { + color: #cc0000; + font-size: 12px; +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.dark.css b/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.dark.css new file mode 100644 index 000000000..fe74c8022 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.dark.css @@ -0,0 +1,126 @@ +.api-editor.dark span.path-param { + color: #ff6a7c; +} +.api-editor.dark span.path-segment::before { + color: white; +} +.api-editor.dark div.api-item-description { + border-left: 2px solid #356c8c; +} +.api-editor.dark { + background-color: #1D2225; + color: #eee; +} +.api-editor.dark .editor-master { + background-color: #1D2225; + border-right: 1px solid #bbb; +} +.api-editor.dark .editor-detail { + background-color: #1D2225; +} +.api-editor.dark .editor-detail .hover-overlay { + border-color: #356c8c; +} +.api-editor.dark .editor-detail .hover-overlay .overlay-actions { + background: #356c8c; + border-left-color: #356c8c; +} +.api-editor.dark .editor-detail .hover-overlay .overlay-actions .overlay-action { + background-color: #356c8c; + color: white; +} +.api-editor.dark .editor-detail .edit-overlay { + border-color: #bbb; + background-color: white; +} +.api-editor.dark .editor-detail .edit-overlay.focus { + box-shadow: 3px 3px 3px rgba(0, 136, 206, 0.3), -3px 3px 3px rgba(0, 136, 206, 0.3); + border-color: #0088ce; +} +.api-editor.dark .editor-detail .edit-overlay.hover { + border-color: #7dc3e8; +} +.api-editor.dark .editor-master .editor-main { + background-color: #3f4950; +} +.api-editor.dark .editor-master .editor-main:hover { + background-color: #274f67; +} +.api-editor.dark .editor-master .editor-main.selected { + background-color: #356c8c; +} +.api-editor.dark .editor-master .editor-search { + border-top: 1px solid #bbb; + border-bottom: 1px solid #bbb; +} +.api-editor.dark .section .section-header { + border-bottom: 1px dotted #bbb; +} +.api-editor.dark .section .section-header a { + color: #fff; +} +.api-editor.dark .editor-master .editor-outline .api-path:hover { + background-color: #274f67; +} +.api-editor.dark .editor-master .editor-outline .api-path.selected { + background-color: #356c8c; + border-left: 2px solid #76accb; +} +.api-editor.dark .editor-master .editor-outline .api-path.selected .label.empty { + background-color: #ccc; +} +.api-editor.dark .editor-master .editor-outline .api-path.selected .label:hover { + border: 1px solid #274f67; +} +.api-editor.dark .editor-master .editor-outline .api-path.selected .label.selected { + border: 1px solid white; +} +.api-editor.dark .editor-master .editor-outline .api-definition:hover { + background-color: #274f67; +} +.api-editor.dark .editor-master .editor-outline .api-definition.selected { + background-color: #356c8c; + border-left: 2px solid #76accb; +} +.api-editor.dark .editor-master .editor-outline .api-response:hover { + background-color: #274f67; +} +.api-editor.dark .editor-master .editor-outline .api-response.selected { + background-color: #356c8c; + border-left: 2px solid #76accb; +} +.api-editor.dark .editor-detail .detail-title { + border-bottom: 1px solid #bbb; +} +.api-editor.dark .editor-detail .detail-title .detail-actions { + border-left: 1px solid #bbb; +} +.api-editor.dark .editor-detail .detail-title .detail-actions .dropdown .btn { + color: #fff; +} +.api-editor.dark .editor-detail .detail-title .detail-actions:hover { + background-color: #274f67; +} +.api-editor.dark .editor-detail .detail-tabs ul { + border-bottom-color: #bbb; +} +.api-editor.dark .editor-detail .detail-content .info-section .api-md-entry .md-label { + color: #fff; +} +.api-editor.dark .api-operation-parameters td.parameter { + color: #ff6a7c; +} +.api-editor.dark .api-operation-parameters td.icons, +.api-details table td.icons { + color: #666; +} +.api-editor.dark .editor-detail .api-path-detail .operations-section .api-operation { + border-bottom: 1px solid #bbb; +} +.api-editor.dark .editor-detail .api-path-detail .operations-section .api-operation.empty { + color: #888; +} +.api-editor.dark .editor-detail .api-path-detail .operations-section .api-operation.empty .type .label { + color: #888; + background-color: #444; +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.html b/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.html new file mode 100644 index 000000000..3104f0418 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.html @@ -0,0 +1,59 @@ +
    + + + +
    + + + + + + + + + + + + + + +
    + +
    diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.light.css b/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.light.css new file mode 100644 index 000000000..055b63b38 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.light.css @@ -0,0 +1,263 @@ +.api-editor .dropdown.empty > button.btn > span { + color: #bbb; +} +.api-editor.light span.empty, +.api-editor.light div.empty, +.api-editor.light button.empty { + color: #bbb; +} +.api-editor.light span.path-param, +.api-editor.light span.server-variable { + color: #ff6a7c; +} +.api-editor.light span.path-segment::before { + color: #363636; +} +.api-editor.light .context-help .context-help-panel p > a { + color: #0088ce; +} +.api-editor.light .context-help .context-help-panel p > a:hover { + color: #00659c; +} +.api-editor.light div.api-item-description { + border-left: 2px solid #39a5dc; +} +.api-editor.light div.api-item-description.empty { + border-left: 2px solid #bbb; + color: #bbb; +} +.api-editor.light .editor-master { + background-color: white; + border-right: 1px solid #ddd; +} +.api-editor.light .editor-detail { + background-color: white; +} +.api-editor.light .editor-detail .detail-actionbar { + border-bottom-color: #ddd; +} +.api-editor.light .editor-detail .hover-overlay { + border-color: #39a5dc; +} +.api-editor.light .editor-detail .hover-overlay .overlay-actions { + background: #39a5dc; + border-left-color: #39a5dc; +} +.api-editor.light .editor-detail .hover-overlay .overlay-actions .overlay-action { + background-color: #39a5dc; + color: white; + -webkit-transition: color 500ms; + -moz-transition: color 500ms; + -ms-transition: color 500ms; + -o-transition: color 500ms; + transition: color 500ms; +} +.api-editor.light .editor-detail .hover-overlay .overlay-actions .overlay-action:hover { + color: gold; +} +.api-editor.light .editor-detail .edit-overlay { + border-color: #bbb; + background-color: white; +} +.api-editor.light .editor-detail .edit-overlay.focus { + box-shadow: 3px 3px 3px rgba(0, 136, 206, 0.3), -3px 3px 3px rgba(0, 136, 206, 0.3); + border-color: #0088ce; +} +.api-editor.light .editor-detail .edit-overlay.hover { + border-color: #7dc3e8; +} +.api-editor.light .editor-detail table tbody tr:hover { + background-color: #efefef; +} +.api-editor.light .editor-master .editor-main { + background-color: #eee; + -webkit-transition: background-color 200ms; + -moz-transition: background-color 200ms; + -ms-transition: background-color 200ms; + -o-transition: background-color 200ms; + transition: background-color 200ms; +} +.api-editor.light .editor-master .editor-main:hover { + background-color: #d1eaf7; +} +.api-editor.light .editor-master .editor-main.selected { + background-color: #a5d6ef; +} +.api-editor.light .editor-master .editor-search { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} +.api-editor.light .editor-master .editor-search input { + background-color: #eee; + color: #333; +} +.api-editor.light .editor-master .editor-validation.expanded { + border-bottom: 1px solid #ddd; +} +.api-editor.light .editor-master .editor-validation.expanded .validation-problem.selected { + background-color: #a5d6ef; +} +.api-editor.light .section .section-header { + border-bottom: 1px dotted #ddd; +} +.api-editor.light .section .section-header a { + color: #333; +} +.api-editor.light .editor-master .editor-outline .api-path { + -webkit-transition: background-color 200ms; + -moz-transition: background-color 200ms; + -ms-transition: background-color 200ms; + -o-transition: background-color 200ms; + transition: background-color 200ms; +} +.api-editor.light .editor-master .editor-outline .api-path:hover { + background-color: #d1eaf7; +} +.api-editor.light .editor-master .editor-outline .api-path.selected { + background-color: #a5d6ef; + border-left: 2px solid #39a5dc; +} +.api-editor.light .editor-master .editor-outline .api-path.contexted, +.api-editor .editor-master .editor-outline .api-path.contexted:hover { + background-color: #FFCC87; + border-left: 2px dotted #E89F3D; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label { + -webkit-transition: border-color 350ms, box-shadow 350ms; + -moz-transition: border-color 350ms, box-shadow 350ms; + -ms-transition: border-color 350ms, box-shadow 350ms; + -o-transition: border-color 350ms, box-shadow 350ms; + transition: border-color 350ms, box-shadow 350ms; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label:hover { + box-shadow: 0 0 3px #646464; + border: 1px solid #666; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.tree { + border: 2px dashed #50A162; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.bus { + border: 2px dashed #B65B80; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.bomb { + border: 2px dashed #D94C4C; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.university { + border: 2px dashed #477187; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.rocket { + border: 2px dashed #D4786A; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.taxi { + border: 2px dashed #B10DC9; +} +.api-editor.light .editor-master .editor-outline .api-path.selected .label.selected { + border: 1px solid black; + box-shadow: 0 0 3px #141414; +} +.api-editor.light .editor-master .editor-outline .api-definition { + -webkit-transition: background-color 200ms; + -moz-transition: background-color 200ms; + -ms-transition: background-color 200ms; + -o-transition: background-color 200ms; + transition: background-color 200ms; +} +.api-editor.light .editor-master .editor-outline .api-definition:hover { + background-color: #d1eaf7; +} +.api-editor.light .editor-master .editor-outline .api-definition.selected { + background-color: #a5d6ef; + border-left: 2px solid #39a5dc; +} +.api-editor.light .editor-master .editor-outline .api-definition.contexted, +.api-editor .editor-master .editor-outline .api-definition.contexted:hover { + background-color: #FFCC87; + border-left: 2px dotted #E89F3D; +} +.api-editor.light .editor-master .editor-outline .api-response { + -webkit-transition: background-color 200ms; + -moz-transition: background-color 200ms; + -ms-transition: background-color 200ms; + -o-transition: background-color 200ms; + transition: background-color 200ms; +} +.api-editor.light .editor-master .editor-outline .api-response:hover { + background-color: #d1eaf7; +} +.api-editor.light .editor-master .editor-outline .api-response.selected { + background-color: #a5d6ef; + border-left: 2px solid #39a5dc; +} +.api-editor.light .editor-detail .detail-title { + border-bottom: 1px solid #ddd; +} +.api-editor.light .editor-detail .detail-title .detail-actions { + border-left: 1px solid #ddd; +} +.api-editor.light .editor-detail .detail-title .detail-actions .dropdown .btn { + color: #333; +} +.api-editor.light .editor-detail .detail-title .detail-actions:hover { + background-color: #d1eaf7; +} +.api-editor.light .editor-detail .detail-content .info-section .api-md-entry .md-label { + color: #333; +} +.api-editor.light td.path-parameter, +.api-editor.light div.path-parameter, +.api-editor.light span.path-parameter { + color: #ff6a7c; +} +.api-editor.light td.query-parameter, +.api-editor.light div.query-parameter, +.api-editor.light span.query-parameter { + color: #39a5dc; +} +.api-editor.light div.statusCode.success { + color: green; +} +.api-editor.light div.statusCode.redirect { + color: blue; +} +.api-editor.light div.statusCode.problem { + color: orange; +} +.api-editor.light div.statusCode.error { + color: red; +} +.api-editor.light .api-operation-parameters td.icons, +.api-details table td.icons { + color: #666; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation { + border-bottom: 1px solid #ddd; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation .type .label { + -webkit-transition: border-color 200ms, box-shadow 200ms; + -moz-transition: border-color 200ms, box-shadow 200ms; + -ms-transition: border-color 200ms, box-shadow 200ms; + -o-transition: border-color 200ms, box-shadow 200ms; + transition: border-color 200ms, box-shadow 200ms; + box-shadow: none; + border: 1px solid transparent; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation .type .label:hover { + border-color: #666; + box-shadow: 0px 0px 8px #666; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation .name.empty { + color: #bbb; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation.empty { + color: #999; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation.empty .type .label { + background-color: #ccc; + border-color: transparent; + box-shadow: none; +} +.api-editor.light .editor-detail .api-path-detail .operations-section .api-operation.empty .type .label:hover { + background-color: #ccc; + border-color: transparent; + box-shadow: none; +} diff --git a/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.ts b/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.ts new file mode 100644 index 000000000..32f661872 --- /dev/null +++ b/front-end/studio/src/app/pages/apis/{apiId}/editor/editor.component.ts @@ -0,0 +1,351 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, + ViewChild, + ViewEncapsulation +} from "@angular/core"; +import {ApiDefinition} from "../../../../models/api.model"; +import { + Oas20ResponseDefinition, + Oas20SchemaDefinition, + Oas30ResponseDefinition, + Oas30SchemaDefinition, + OasDocument, + OasLibraryUtils, + OasNode, + OasOperation, + OasPathItem, + OasTraverserDirection, + OasValidationError, + OasVisitorUtil +} from "oai-ts-core"; +import {EditorMasterComponent} from "./_components/master.component"; +import {AbstractCombinedVisitorAdapter, AllNodeVisitor} from "./_visitors/base.visitor"; +import {NodeSelectionEvent} from "./_events/node-selection.event"; +import {ICommand, OtCommand, OtEngine} from "oai-ts-commands"; +import {ApiDesignCommandAck} from "../../../../models/ack.model"; +import {ApiEditorUser} from "../../../../models/editor-user.model"; + + +@Component({ + moduleId: module.id, + selector: "api-editor", + templateUrl: "editor.component.html", + styleUrls: ["editor.component.css", "editor.component.light.css", "editor.component.dark.css"], + encapsulation: ViewEncapsulation.None +}) +export class ApiEditorComponent implements OnChanges { + + @Input() api: ApiDefinition; + @Output() onCommandExecuted: EventEmitter = new EventEmitter(); + @Output() onSelectionChanged: EventEmitter = new EventEmitter(); + + private _library: OasLibraryUtils = new OasLibraryUtils(); + private _document: OasDocument = null; + private _otEngine: OtEngine = null; + + theme: string = "light"; + + private currentSelection: NodeSelectionEvent; + public validationErrors: OasValidationError[] = []; + + @ViewChild("master") master: EditorMasterComponent; + + formType: string; + + /** + * Constructor. + */ + constructor() {} + + /** + * Called when the @Input changes. + * @param {SimpleChanges} changes + */ + ngOnChanges(changes: SimpleChanges): void { + this._document = null; + if (this.document().getSpecVersion() === "2.0") { + this.formType = "main_20"; + } else { + this.formType = "main_30"; + } + console.info("****** just set the form type to: %s", this.formType); + } + + /** + * Gets the OpenAPI spec as a document. + */ + public document(): OasDocument { + if (this._document === null) { + this._document = this._library.createDocument(this.api.spec); + this.validateModel(); + } + return this._document; + } + + /** + * Lazy getter for the OtEngine. + * @return {OtEngine} + */ + public otEngine(): OtEngine { + if (this._otEngine === null) { + this._otEngine = new OtEngine(this.document()); + } + return this._otEngine; + } + + /** + * Called whenever the user presses a key. + * @param event + */ + public onGlobalKeyDown(event: KeyboardEvent): void { + // TODO skip any event that was sent to an input field (e.g. input, textarea, etc) + if (event.ctrlKey && event.key === 'z' && !event.metaKey && !event.altKey) { + console.info("[ApiEditorComponent] User wants to 'undo' the last command (not implemented)."); + // this._commands.undoLastCommand(this.document()); + // this.master.validateSelection(); + // this.validateModel(); + } + if (event.ctrlKey && event.key === 'y' && !event.metaKey && !event.altKey) { + console.info("[ApiEditorComponent] User wants to 'redo' the last command (not implemented)."); + // this._commands.redoLastCommand(this.document()); + // this.master.validateSelection(); + // this.validateModel(); + } + } + + /** + * Called when an editor component creates a command that should be executed. + * @param command + */ + public onCommand(command: ICommand): void { + let otCmd: OtCommand = new OtCommand(); + otCmd.command = command; + otCmd.contentVersion = Date.now(); + this.otEngine().executeCommand(otCmd, true); + this.onCommandExecuted.emit(otCmd); + + // After changing the model, we should re-validate it + this.validateModel(); + } + + /** + * Executes a command. + * @param {ICommand} command + */ + public executeCommand(command: OtCommand): void { + console.info("[ApiEditorComponent] Executing a command."); + this.otEngine().executeCommand(command); + } + + /** + * Finalizes a command. + * @param {ApiDesignCommandAck} ack + */ + public finalizeCommand(ack: ApiDesignCommandAck): void { + this.otEngine().finalizeCommand(ack.commandId, ack.contentVersion); + } + + /** + * Called to update the selection state of the given remote API editor (i.e. an active collaborator). + * @param {ApiEditorUser} user + * @param {string} selection + */ + public updateCollaboratorSelection(user: ApiEditorUser, selection: string): void { + this.master.updateCollaboratorSelection(user, selection); + } + + /** + * Called when the user selects a node in some way. + * @param {NodeSelectionEvent} event + */ + public onNodeSelected(event: NodeSelectionEvent): void { + console.info("[ApiEditorComponent] Selection changed. %s node selected", event.type); + this.currentSelection = event; + + this.formType = this.currentSelection.type + "_"; + if (this.document().getSpecVersion() === "2.0") { + this.formType += "20"; + } else { + this.formType += "30"; + } + console.info("[ApiEditorComponent] Showing form: %s", this.formType); + + let selection: string = ""; + if (event.type != "problem" && event.node != null) { + selection = this._library.createNodePath(event.node as OasNode).toString(); + } + console.info("[ApiEditorComponent] Firing selection changed event: %s", selection); + this.onSelectionChanged.emit(selection); + } + + /** + * Called to validate the model. + */ + public validateModel(): void { + let doc: OasDocument = this.document(); + let resetVisitor: ResetProblemsVisitor = new ResetProblemsVisitor(); + OasVisitorUtil.visitTree(doc, resetVisitor); + + if (doc.is2xDocument()) { + this.validationErrors = this._library.validate(doc, true); + } else if (doc.is3xDocument()) { + // TODO also validate 3.0.x documents once the oai-ts-core library supports that + } + } + + /** + * Returns the currently selected path item. + * @return {OasPathItem} + */ + public selectedPath(): OasPathItem { + if (this.currentSelection.type === "path") { + return this.currentSelection.node as OasPathItem; + } else { + return null; + } + } + + /** + * Returns the currently selected operation. + */ + public selectedOperation(): OasOperation { + if (this.currentSelection.type === "operation") { + return this.currentSelection.node as OasOperation; + } else { + return null; + } + } + + /** + * Returns the currently selected definition. + * @return {Oas20SchemaDefinition} + */ + public selectedDefinition(): Oas20SchemaDefinition | Oas30SchemaDefinition { + if (this.currentSelection.type === "definition") { + return this.currentSelection.node as Oas20SchemaDefinition; + } else { + return null; + } + } + + /** + * Returns the currently selected definition. + * @return {OasValidationError} + */ + public selectedProblem(): OasValidationError { + if (this.currentSelection.type === "problem") { + return this.currentSelection.node as OasValidationError; + } else { + return null; + } + } + + /** + * Called when the user does something to cause the selection to change. + * @param event + */ + public selectNode(event: NodeSelectionEvent): void { + this.master.selectNode(event); + } + + /** + * Deselects the currently selected definition. + */ + public deselectDefinition(): void { + console.info("[ApiEditorComponent] Deselecting the current definition (selecting main)."); + this.master.selectMain(); + } + + /** + * Called when the user clicks the "Go To Problem" button for a particular problem. + */ + public goToProblem(): void { + // 1. get the node path from the problem + // 2. resolve to a node + // 3. use a visitor to reverse-traverse until one of the following is found: + // 3a. Path Item + // 3b. Operation + // 3c. Definition + // 3d. Response (tbd) + + let problem: OasValidationError = this.currentSelection.node as OasValidationError; + let node: OasNode = problem.nodePath.resolve(this.document()); + if (node === null) { + return; + } + let selectionVisitor: SelectedItemVisitor = new SelectedItemVisitor(); + OasVisitorUtil.visitTree(node, selectionVisitor, OasTraverserDirection.up); + + let event: NodeSelectionEvent = new NodeSelectionEvent(selectionVisitor.selectedItem, selectionVisitor.selectedType); + this.master.selectNode(event); + } + +} + + +/** + * Visitor used to clear out all the validation errors found in all nodes + * in the model. + */ +class ResetProblemsVisitor extends AllNodeVisitor { + + protected doVisitNode(node: OasNode): void { + node.n_attribute("validation-errors", null); + } + +} + + +/** + * Visitor used to determine what type of thing is selected. Visits the node OAS + * and sets the selectedItem and selectedType. + */ +class SelectedItemVisitor extends AbstractCombinedVisitorAdapter { + public selectedItem: any = null; + public selectedType: string = null; + + visitPathItem(node: OasPathItem): void { + if (this.selectedType === null) { + this.selectedItem = node; + this.selectedType = "path"; + } + } + + visitOperation(node: OasOperation): void { + this.selectedType = "operation"; + this.selectedItem = node; + } + + visitResponseDefinition(node: Oas20ResponseDefinition|Oas30ResponseDefinition): void { + this.selectedType = "response"; + this.selectedItem = node; + } + + visitDefinitionSchema(node: Oas20SchemaDefinition|Oas30SchemaDefinition): void { + this.selectedType = "definition"; + this.selectedItem = node; + } +} + diff --git a/front-end/studio/src/app/pages/dashboard/dashboard.page.css b/front-end/studio/src/app/pages/dashboard/dashboard.page.css new file mode 100644 index 000000000..277b5da88 --- /dev/null +++ b/front-end/studio/src/app/pages/dashboard/dashboard.page.css @@ -0,0 +1,16 @@ +.api-recent-apis .api-item { + font-size: 15px; +} +.api-recent-apis .api-item p { + font-size: 13px; + padding-left: 20px; +} + +.blank-slate-card-pf { + padding: 0; + border-top: 0; +} +.blank-slate-card-pf .blank-slate-pf { + border: 0; + background-color: white; +} diff --git a/front-end/studio/src/app/pages/dashboard/dashboard.page.html b/front-end/studio/src/app/pages/dashboard/dashboard.page.html new file mode 100644 index 000000000..4cd57fa9a --- /dev/null +++ b/front-end/studio/src/app/pages/dashboard/dashboard.page.html @@ -0,0 +1,91 @@ +
    +
    +
    +
    +
    +
    +

    + + Welcome to the Apicurio Studio Dashboard! +

    +
    +
    +

    + Hey {{ (user() | async).name }}, thanks for logging into Apicurio Studio. Hopefully + you're finding it easy to use this tool to design and collaborate on new (and existing) APIs. +

    + +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    + +
    +

    No APIs Found

    +

    + We couldn't find any APIs for you - it's possible that you have not yet created/imported any APIs to the studio. + Or perhaps you have some APIs but haven't worked on them recently. Pick an action below that makes sense + for you! +

    +
    +
    + Create New API + + +
    + List All APIs +
    +
    +
    + +
    +
    +

    Your Recent APIs

    +
    +
    +
    + + {{ api.name }} + +

    {{api.description}}

    +
    +
    + +
    +
    + +
    +
    \ No newline at end of file diff --git a/front-end/studio/src/app/pages/dashboard/dashboard.page.ts b/front-end/studio/src/app/pages/dashboard/dashboard.page.ts new file mode 100644 index 000000000..8a1eef975 --- /dev/null +++ b/front-end/studio/src/app/pages/dashboard/dashboard.page.ts @@ -0,0 +1,91 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Inject} from "@angular/core"; +import {ActivatedRoute} from "@angular/router"; + +import {Api} from "../../models/api.model"; +import {IAuthenticationService} from "../../services/auth.service"; +import {Observable} from "rxjs"; +import {User} from "../../models/user.model"; +import {IApisService} from "../../services/apis.service"; +import {AbstractPageComponent} from "../../components/page-base.component"; +import {ILinkedAccountsService} from "../../services/accounts.service"; +import {LinkedAccount} from "../../models/linked-account"; + +/** + * The Dashboard Page component - models the logical root path of the application. + */ +@Component({ + moduleId: module.id, + selector: "dashboard-page", + templateUrl: "dashboard.page.html", + styleUrls: ["dashboard.page.css"] +}) +export class DashboardPageComponent extends AbstractPageComponent { + + accounts: LinkedAccount[]; + + /** + * C'tor. + * @param {IApisService} apis + * @param {ActivatedRoute} route + * @param {ILinkedAccountsService} accountsService + * @param {IAuthenticationService} authService + */ + constructor(@Inject(IApisService) private apis: IApisService, route: ActivatedRoute, + @Inject(ILinkedAccountsService) private accountsService: ILinkedAccountsService, + protected authService: IAuthenticationService) { + super(route); + } + + /** + * @see AbstractPageComponent.loadAsyncPageData + */ + public loadAsyncPageData(): void { + console.log("[DashboardPageComponent] loadAsyncPageData") + this.apis.getApis().then( apis => { + this.loaded("apis"); + }).catch( error => { + console.error("[DashboardPageComponent] Error fetching API list."); + this.error(error); + }); + this.accountsService.getLinkedAccounts().then( accounts => { + this.loaded("accounts"); + this.accounts = accounts; + }).catch( error => { + console.error("[DashboardPageComponent] Error fetching linked accounts."); + this.error(error); + }); + } + + /** + * Gets the authenticated user. + * @return {Observable} + */ + public user(): Observable { + return this.authService.getAuthenticatedUser(); + } + + /** + * Gets the user's "recent APIs". + * @return {Observable} + */ + public recentApis(): Observable { + return this.apis.getRecentApis(); + } +} diff --git a/front-end/studio/src/app/pages/settings/_components/settings-nav.component.css b/front-end/studio/src/app/pages/settings/_components/settings-nav.component.css new file mode 100644 index 000000000..9dbd4fb25 --- /dev/null +++ b/front-end/studio/src/app/pages/settings/_components/settings-nav.component.css @@ -0,0 +1,17 @@ +.panel-settings { +} +.panel-settings .panel-body { + padding: 0px; +} +.panel-settings .panel-body .nav-settings { +} +.panel-settings .panel-body .nav-settings li { +} +.panel-settings .panel-body .nav-settings li a { +} +.panel-settings .panel-body .nav-settings li.active { +} +.panel-settings .panel-body .nav-settings li.active a { + color: white; + background-color: #0088ce; +} diff --git a/front-end/studio/src/app/pages/settings/_components/settings-nav.component.html b/front-end/studio/src/app/pages/settings/_components/settings-nav.component.html new file mode 100644 index 000000000..1e877d2c3 --- /dev/null +++ b/front-end/studio/src/app/pages/settings/_components/settings-nav.component.html @@ -0,0 +1,11 @@ +
    +
    +

    Settings

    +
    + +
    diff --git a/front-end/studio/src/app/pages/settings/_components/settings-nav.component.ts b/front-end/studio/src/app/pages/settings/_components/settings-nav.component.ts new file mode 100644 index 000000000..1c840787b --- /dev/null +++ b/front-end/studio/src/app/pages/settings/_components/settings-nav.component.ts @@ -0,0 +1,36 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Input} from "@angular/core"; + + +@Component({ + moduleId: module.id, + selector: "settings-nav", + templateUrl: "settings-nav.component.html", + styleUrls: ["settings-nav.component.css"] +}) +export class SettingsNavComponent { + + @Input() tab: string; + + /** + * Constructor. + */ + constructor() {} + +} diff --git a/front-end/studio/src/app/pages/settings/accounts/accounts.page.css b/front-end/studio/src/app/pages/settings/accounts/accounts.page.css new file mode 100644 index 000000000..0fd09a1c9 --- /dev/null +++ b/front-end/studio/src/app/pages/settings/accounts/accounts.page.css @@ -0,0 +1,45 @@ + +.account-link { + margin-bottom: 5px; +} +.account-link.not-created { + opacity: 0.5; +} +.account-link.not-created:hover { + opacity: 1.0; +} +.account-link.created { +} +.account-link.initiated { + border: 2px dashed grey; +} +.account-link .account-link-icon span.fa { + font-size: 38px; +} +.account-link .list-view-pf-additional-info { + font-size: 14px; +} +.account-link .account-link-date { + margin-left: 5px; + font-style: italic; +} +.account-link .list-view-pf-actions button { + min-width: 60px; +} + + +.account-link.list-view-pf-expand-active { + opacity: 1.0; + box-shadow: 0 2px 6px rgba(3,3,3,.2); +} +.account-link.list-view-pf-expand-active, .account-link.list-view-pf-expand-active .list-group-item-container { + border-color: #bbb; +} +.account-link.list-view-pf-expand-active .list-group-item-container .row { + padding: 10px; +} +.account-link.list-view-pf-expand-active .list-group-item-container .row .icon { + text-align: center; + font-size: 36px; +} + diff --git a/front-end/studio/src/app/pages/settings/accounts/accounts.page.html b/front-end/studio/src/app/pages/settings/accounts/accounts.page.html new file mode 100644 index 000000000..101f831af --- /dev/null +++ b/front-end/studio/src/app/pages/settings/accounts/accounts.page.html @@ -0,0 +1,142 @@ +
    + +
  • +
  • +
  • +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    +

    Loading linked accounts...

    +

    +
    +
    +
    + +
    + + + + + + +
    +
    +
    +
    diff --git a/front-end/studio/src/app/pages/settings/accounts/accounts.page.ts b/front-end/studio/src/app/pages/settings/accounts/accounts.page.ts new file mode 100644 index 000000000..785884f8d --- /dev/null +++ b/front-end/studio/src/app/pages/settings/accounts/accounts.page.ts @@ -0,0 +1,162 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Inject} from "@angular/core"; +import {AbstractPageComponent} from "../../../components/page-base.component"; +import {ILinkedAccountsService} from "../../../services/accounts.service"; +import {LinkedAccount} from "../../../models/linked-account"; +import {ActivatedRoute} from "@angular/router"; + +export const ACCOUNT_LINK_NONCE_KEY = "apicurio.studio.linked-accounts.nonces"; + +/** + * The Settings/Accounts Page component. + */ +@Component({ + moduleId: module.id, + selector: "accounts-page", + templateUrl: "accounts.page.html", + styleUrls: ["accounts.page.css"] +}) +export class LinkedAccountsPageComponent extends AbstractPageComponent { + + private accounts: LinkedAccount[]; + private linksInProgress: any = {}; + + /** + * C'tor. + * @param {ILinkedAccountsService} accountsApi + * @param {ActivatedRoute} route + */ + constructor(@Inject(ILinkedAccountsService) private accountsApi: ILinkedAccountsService, route: ActivatedRoute) { + super(route); + } + + /** + * @see AbstractPageComponent.loadAsyncPageData() + */ + public loadAsyncPageData(): void { + console.log("[LinkedAccountsPageComponent] loadAsyncPageData") + this.accountsApi.getLinkedAccounts().then( accounts => { + this.accounts = accounts; + this.loaded("accounts"); + }).catch( error => { + console.error("[LinkedAccountsPageComponent] Error fetching linked accounts."); + this.error(error); + }); + } + + /** + * Returns true if an account of the given type has been created. + * @param {string} type + * @return {boolean} + */ + public isAccountCreated(type: string): boolean { + let account: LinkedAccount = this.account(type); + if (account && account.linkedOn) { + return true; + } + return false; + } + + /** + * Returns true if an account is missing entirely from the list. + * @param {string} type + * @return {boolean} + */ + public isAccountMissing(type: string): boolean { + let account: LinkedAccount = this.account(type); + if (!account) { + return true; + } + return false; + } + + /** + * Returns true if an account of the given type has been initiated but not completed. + * @param {string} type + * @return {boolean} + */ + public isAccountInitiated(type: string): boolean { + let account: LinkedAccount = this.account(type); + if (account && !account.linkedOn) { + return true; + } + return false; + } + + /** + * Returns true if the account is actively being linked or deleted. + * @param {string} type + * @return {boolean} + */ + public isAccountInProgress(type: string): boolean { + return this.linksInProgress[type]; + } + + /** + * Called to initiate linking of a particular account. + * @param {string} type + */ + public createLinkedAccount(type: string): void { + this.linksInProgress[type] = true; + let redirectUrl: string = location.href + "/" + type + "/created"; + this.accountsApi.createLinkedAccount(type, redirectUrl).then( ila => { + let account: LinkedAccount = new LinkedAccount(); + account.type = type; + this.accounts.push(account); + this.linksInProgress[type] = false; + console.info("Redirecting to: %s", ila.authUrl); + // Save the nonce in local storage - we'll need it later when the redirect back to Apicurio happens. + localStorage.setItem(ACCOUNT_LINK_NONCE_KEY + "." + type, ila.nonce); + // Send the user to the Account Link auth URL (Keycloak account linking URL) + location.replace(ila.authUrl); + }).catch( error => { + this.error(error); + }); + } + + /** + * Called to delete a (or cancel an in-progress) linked account. + * @param {string} type + */ + public deleteLinkedAccount(type: string): void { + this.linksInProgress[type] = true; + this.accountsApi.deleteLinkedAccount(type).then( () => { + let account: LinkedAccount = this.account(type); + this.accounts.splice(this.accounts.indexOf(account), 1); + this.linksInProgress[type] = false; + }).catch( error => { + this.error(error); + }); + } + + /** + * Returns the linked account of the given type or null if not found. + * @param {string} type + * @return {LinkedAccount} + */ + protected account(type: string): LinkedAccount { + for (let account of this.accounts) { + if (account.type === type) { + return account; + } + } + return null; + } + +} diff --git a/front-end/studio/src/app/pages/settings/accounts/{accountType}/created/created.page.css b/front-end/studio/src/app/pages/settings/accounts/{accountType}/created/created.page.css new file mode 100644 index 000000000..0c8536d7a --- /dev/null +++ b/front-end/studio/src/app/pages/settings/accounts/{accountType}/created/created.page.css @@ -0,0 +1,10 @@ +.card-pf-error { + border-top-color: red; +} +.card-pf-error .card-pf-title .fa { + color: red; +} + +p.in-progress { + font-size: 17px; +} \ No newline at end of file diff --git a/front-end/studio/src/app/pages/settings/accounts/{accountType}/created/created.page.html b/front-end/studio/src/app/pages/settings/accounts/{accountType}/created/created.page.html new file mode 100644 index 000000000..bd993fa76 --- /dev/null +++ b/front-end/studio/src/app/pages/settings/accounts/{accountType}/created/created.page.html @@ -0,0 +1,87 @@ +
    + +
  • +
  • +
  • +
    +
    + +
    +
    +
    +
    + +
    +
    +

    + + Account Linking Error +

    +
    +
    +
    +

    + Account linking has failed because you were not logged in. This has caused + the process to fail, so you must go back to the Accounts page and try again. +

    +

    + Account linking has failed because no Identity Provider for accounts of type + {{ accountType }} has been configured. Please contact your + system administrator to resolve this configuration problem. +

    +

    + An unexpected error occurred while trying to create a linked account. The process + has failed, so you must go back and try again. +

    +
    + +
    +
    + + +
    +
    +

    + + Finalizing Linked Account +

    +
    +
    +

    + You've successfully authorized Apicurio Studio with + GitHub + GitLab + Bitbucket + and we are now finalizing the link. This should only take a + moment, after which you will be redirected back to the + Settings >> Accounts page. +

    +

    Finalizing account link, please wait...

    +
    +
    +
    +
    + + +
    +
    +

    + + Action Already Completed +

    +
    +
    +

    + This account has already been successfully linked. Click the button below + to return to the Settings page. +

    + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/front-end/studio/src/app/pages/settings/accounts/{accountType}/created/created.page.ts b/front-end/studio/src/app/pages/settings/accounts/{accountType}/created/created.page.ts new file mode 100644 index 000000000..b526e4161 --- /dev/null +++ b/front-end/studio/src/app/pages/settings/accounts/{accountType}/created/created.page.ts @@ -0,0 +1,102 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Inject} from "@angular/core"; +import {AbstractPageComponent} from "../../../../../components/page-base.component"; +import {ILinkedAccountsService} from "../../../../../services/accounts.service"; +import {ActivatedRoute, Router} from "@angular/router"; +import {ACCOUNT_LINK_NONCE_KEY} from "../../accounts.page"; + +/** + * The page used to finalize the creation of an account link. + */ +@Component({ + moduleId: module.id, + selector: "created-linked-account-page", + templateUrl: "created.page.html", + styleUrls: [ "created.page.css" ] +}) +export class CreatedLinkedAccountPageComponent extends AbstractPageComponent { + + accountType: string; + linkError: string; + nonce: string; + alreadyCompleted: boolean = false; + + /** + * C'tor. + * @param {Router} router + * @param {ActivatedRoute} route + * @param {ILinkedAccountsService} accountsApi + */ + constructor(private router: Router, route: ActivatedRoute, + @Inject(ILinkedAccountsService) private accountsApi: ILinkedAccountsService) { + super(route); + } + + /** + * @see AbstractPageComponent.loadAsyncPageData + */ + public loadAsyncPageData(pathParams: any, queryParams: any): void { + console.info("[CreatedLinkedAccountPageComponent] loadAsyncPageData") + + this.accountType = pathParams["accountType"]; + this.linkError = queryParams["link_error"]; + this.nonce = localStorage.getItem(ACCOUNT_LINK_NONCE_KEY + "." + this.accountType) + + console.info("Account Type: %s", this.accountType); + console.info("Link Error: %s", this.linkError); + console.info("Nonce: %s", this.nonce); + + if (!this.isLinkError()) { + if (this.nonce) { + this.accountsApi.completeLinkedAccount(this.accountType, this.nonce).then(() => { + localStorage.removeItem(ACCOUNT_LINK_NONCE_KEY + "." + this.accountType); + this.router.navigate(["/settings/accounts"]); + }).catch(error => this.error(error)); + } else { + this.alreadyCompleted = true; + } + } else { + this.accountsApi.deleteLinkedAccount(this.accountType).then( () => { + console.info("[CreatedLinkedAccountPageComponent] Deleted the (failed) linked account."); + }).catch(error => { + console.info("[CreatedLinkedAccountPageComponent] Tried to delete the (failed) linked account but got an error: %s", error.toString()); + }); + } + } + + public isGitHub(): boolean { + return this.accountType === "GitHub"; + } + + public isGitLab(): boolean { + return this.accountType === "GitLab"; + } + + public isBitbucket(): boolean { + return this.accountType === "Bitbucket"; + } + + public isLinkError(): boolean { + if (this.linkError) { + return true; + } + return false; + } + +} diff --git a/front-end/studio/src/app/pages/settings/profile/profile.page.css b/front-end/studio/src/app/pages/settings/profile/profile.page.css new file mode 100644 index 000000000..31dbdf152 --- /dev/null +++ b/front-end/studio/src/app/pages/settings/profile/profile.page.css @@ -0,0 +1,6 @@ + +.user-info-label { + min-width: 60px; + font-weight: bold; + display: inline-block; +} diff --git a/front-end/studio/src/app/pages/settings/profile/profile.page.html b/front-end/studio/src/app/pages/settings/profile/profile.page.html new file mode 100644 index 000000000..42bcc9773 --- /dev/null +++ b/front-end/studio/src/app/pages/settings/profile/profile.page.html @@ -0,0 +1,42 @@ +
    + +
  • +
  • +
  • +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +

    + + {{(user() | async).name }} +

    +
    +
    + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/front-end/studio/src/app/pages/settings/profile/profile.page.ts b/front-end/studio/src/app/pages/settings/profile/profile.page.ts new file mode 100644 index 000000000..de998a85b --- /dev/null +++ b/front-end/studio/src/app/pages/settings/profile/profile.page.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Inject} from "@angular/core"; + +import {Observable} from "rxjs"; +import {IAuthenticationService} from "../../../services/auth.service"; +import {AbstractPageComponent} from "../../../components/page-base.component"; +import {User} from "../../../models/user.model"; +import {ActivatedRoute} from "@angular/router"; + +/** + * The Settings/Profile Page component. + */ +@Component({ + moduleId: module.id, + selector: "profile-page", + templateUrl: "profile.page.html", + styleUrls: ["profile.page.css"] +}) +export class ProfilePageComponent extends AbstractPageComponent { + + /** + * C'tor. + * @param {IAuthenticationService} authService + * @param {ActivatedRoute} route + */ + constructor(@Inject(IAuthenticationService) private authService: IAuthenticationService, route: ActivatedRoute) { + super(route); + } + + public loadAsyncPageData(): void { + console.log("[ProfilePageComponent] loadAsyncPageData") + } + + public user(): Observable { + return this.authService.getAuthenticatedUser(); + } + +} diff --git a/front-end/studio/src/app/pages/settings/settings.ts b/front-end/studio/src/app/pages/settings/settings.ts new file mode 100644 index 000000000..7c9de1baa --- /dev/null +++ b/front-end/studio/src/app/pages/settings/settings.ts @@ -0,0 +1,48 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component} from "@angular/core"; +import {ActivatedRoute, Router} from "@angular/router"; +import {AbstractPageComponent} from "../../components/page-base.component"; + +/** + * The Settings Page component. + */ +@Component({ + moduleId: module.id, + selector: "settings-page", + template: "
    " +}) +export class SettingsPageComponent extends AbstractPageComponent { + + /** + * C'tor. + * @param {Router} router + * @param {ActivatedRoute} route + */ + constructor(private router: Router, route: ActivatedRoute) { + super(route); + } + + /** + * @see AbstractPageComponent.loadAsyncPageData() + */ + public loadAsyncPageData(): void { + console.info("[SettingsPageComponent] Redirecting to 'profile'"); + this.router.navigate([ "/settings/profile" ]); + } +} diff --git a/front-end/studio/src/app/services/accounts-hub.service.ts b/front-end/studio/src/app/services/accounts-hub.service.ts new file mode 100644 index 000000000..d2be5a250 --- /dev/null +++ b/front-end/studio/src/app/services/accounts-hub.service.ts @@ -0,0 +1,200 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import 'rxjs/Rx'; +import {IAuthenticationService} from "./auth.service"; +import {ConfigService} from "./config.service"; + +import {ILinkedAccountsService} from "./accounts.service"; +import {LinkedAccount} from "../models/linked-account"; +import {InitiatedLinkedAccount} from "../models/initiated-linked-account"; +import {CreateLinkedAccount} from "../models/create-linked-account"; +import {CompleteLinkedAccount} from "../models/complete-linked-account"; +import {GitHubOrganization} from "../models/github-organization"; +import {GitHubRepository} from "../models/github-repository"; +import {GitLabGroup} from "../models/gitlab-group"; +import {GitLabProject} from "../models/gitlab-project"; +import {BitbucketRepository} from "../models/bitbucket-repository"; +import {BitbucketTeam} from "../models/bitbucket-team"; +import {HttpClient} from "@angular/common/http"; + + +/** + * An implementation of the Linked Accounts service that uses the Apicurio Studio back-end (Hub API) service + * to store and retrieve relevant information for the user. + */ +export class HubLinkedAccountsService extends ILinkedAccountsService { + + /** + * Constructor. + * @param {HttpClient} http + * @param {IAuthenticationService} authService + * @param {ConfigService} config + */ + constructor(http: HttpClient, authService: IAuthenticationService, config: ConfigService) { + super(http, authService, config); + } + + /** + * @see ILinkedAccountsService.getLinkedAccounts + */ + public getLinkedAccounts(): Promise { + console.info("[HubLinkedAccountsService] Getting all linked accounts"); + + let url: string = this.endpoint("/accounts"); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubLinkedAccountsService] Fetching linked accounts: %s", url); + return this.httpGet(url, options); + } + + /** + * @see ILinkedAccountsService.createLinkedAccount + */ + public createLinkedAccount(accountType: string, redirectUrl: string): Promise { + console.info("[HubLinkedAccountsService] Creating a linked account via the hub API. Type: %s", accountType); + let cla: CreateLinkedAccount = new CreateLinkedAccount(); + cla.type = accountType; + cla.redirectUrl = redirectUrl; + + let url: string = this.endpoint("/accounts"); + let options: any = this.options({ "Accept": "application/json", "Content-Type": "application/json" }); + + console.info("[HubLinkedAccountsService] Creating a linked account: %s", url); + return this.httpPostWithReturn(url, cla, options); + } + + /** + * @see ILinkedAccountsService.deleteLinkedAccount + */ + public deleteLinkedAccount(type: string): Promise { + console.info("[HubLinkedAccountsService] Deleting a linked account via the hub API"); + + let url: string = this.endpoint("/accounts/:accountType", { + accountType: type + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubLinkedAccountsService] Deleting a linked account: %s", url); + return this.httpDelete(url, options); + } + + /** + * @see ILinkedAccountsService.getLinkedAccount + */ + public getLinkedAccount(type: string): Promise { + let url: string = this.endpoint("/accounts/:accountType", { + accountType: type + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubLinkedAccountsService] Getting a linked account: %s", url); + return this.httpGet(url, options); + } + + /** + * @see ILinkedAccountsService.completeLinkedAccount + */ + public completeLinkedAccount(accountType: string, nonce: string): Promise { + console.info("[HubLinkedAccountsService] Completing a linked account via the hub API. Type: %s", accountType); + let cla: CompleteLinkedAccount = new CompleteLinkedAccount(); + cla.nonce = nonce; + + let url: string = this.endpoint("/accounts/:accountType", { + accountType: accountType + }); + let options: any = this.options({ "Accept": "application/json", "Content-Type": "application/json" }); + + console.info("[HubLinkedAccountsService] Finalizing/completing a linked account: %s", url); + return this.httpPut(url, cla, options); + } + + /** + * @see ILinkedAccountsService.getAccountOrganizations + */ + public getAccountOrganizations(accountType: string): Promise { + let organizationsUrl: string = this.endpoint("/accounts/:accountType/organizations", { + accountType: accountType + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubLinkedAccountsService] Getting organizations: %s", organizationsUrl); + return this.httpGet(organizationsUrl, options); + } + + /** + * @see ILinkedAccountsService.getAccountRepositories + */ + public getAccountRepositories(accountType: string, organizationOrTeam: string): Promise { + let repositoriesUrl: string = this.endpoint("/accounts/:accountType/organizations/:org/repositories", { + accountType: accountType, + org: organizationOrTeam + }); + if (accountType === "Bitbucket") { + repositoriesUrl = this.endpoint("/accounts/:accountType/teams/:team/repositories", { + accountType: accountType, + team: organizationOrTeam + }); + } + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubLinkedAccountsService] Getting repositories: %s", repositoriesUrl); + return this.httpGet(repositoriesUrl, options); + } + + /** + * @see ILinkedAccountsService.getAccountGroups + */ + public getAccountGroups(accountType: string): Promise { + let groupsUrl: string = this.endpoint("/accounts/:accountType/groups", { + accountType: accountType + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubLinkedAccountsService] Getting groups: %s", groupsUrl); + return this.httpGet(groupsUrl, options); + } + + /** + * @see ILinkedAccountsService.getAccountProjects + */ + public getAccountProjects(accountType: string, group: string): Promise { + let projectsUrl: string = this.endpoint("/accounts/:accountType/groups/:group/projects", { + accountType: accountType, + group: group + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubLinkedAccountsService] Getting projects: %s", projectsUrl); + return this.httpGet(projectsUrl, options); + } + + /** + * @see ILinkedAccountsService.getAccountTeams + */ + public getAccountTeams(accountType: string): Promise { + let teamsUrl: string = this.endpoint("/accounts/:accountType/teams", { + accountType: accountType + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubLinkedAccountsService] Getting teams: %s", teamsUrl); + return this.httpGet(teamsUrl, options); + } + +} diff --git a/front-end/studio/src/app/services/accounts.service.provider.ts b/front-end/studio/src/app/services/accounts.service.provider.ts new file mode 100644 index 000000000..218779d4c --- /dev/null +++ b/front-end/studio/src/app/services/accounts.service.provider.ts @@ -0,0 +1,42 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {IAuthenticationService} from "./auth.service"; +import {ConfigService} from "./config.service"; +import {ILinkedAccountsService} from "./accounts.service"; +import {HubLinkedAccountsService} from "./accounts-hub.service"; +import {HttpClient} from "@angular/common/http"; + + +export function LinkedAccountsServiceFactory(http: HttpClient, authService: IAuthenticationService, config: ConfigService): ILinkedAccountsService { + if (config.apisType() === "hub") { + console.info("[LinkedAccountsServiceFactory] Creating instance of HubLinkedAccountsService"); + return new HubLinkedAccountsService(http, authService, config); + } else { + console.error("[LinkedAccountsServiceFactory] Unknown type for Linked Accounts Service: %s", config.apisType()); + return null; + } +}; + + +export let LinkedAccountsServiceProvider = +{ + provide: ILinkedAccountsService, + useFactory: LinkedAccountsServiceFactory, + deps: [HttpClient, IAuthenticationService, ConfigService] +}; + diff --git a/front-end/studio/src/app/services/accounts.service.ts b/front-end/studio/src/app/services/accounts.service.ts new file mode 100644 index 000000000..c0419570c --- /dev/null +++ b/front-end/studio/src/app/services/accounts.service.ts @@ -0,0 +1,109 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {InitiatedLinkedAccount} from "../models/initiated-linked-account"; +import {LinkedAccount} from "../models/linked-account"; +import {GitHubOrganization} from "../models/github-organization"; +import {GitHubRepository} from "../models/github-repository"; +import {GitLabGroup} from "../models/gitlab-group"; +import {GitLabProject} from "../models/gitlab-project"; +import {BitbucketRepository} from "../models/bitbucket-repository"; +import {BitbucketTeam} from "../models/bitbucket-team"; +import {AbstractHubService} from "./hub"; + + +/** + * Used to access and manipulate the user's Linked Accounts. + */ +export abstract class ILinkedAccountsService extends AbstractHubService { + + /** + * Gets a promise over all of the Linked Accounts for the current user. + * @return {Promise} + */ + abstract getLinkedAccounts(): Promise; + + /** + * Initiates the process of creating a new linked account. This begins the OIDC/OAuth + * process, which will ultimately result in a new linked account. + * @param {string} accountType + * @param {string} redirectUrl + * @return {Promise} + */ + abstract createLinkedAccount(accountType: string, redirectUrl: string): Promise; + + /** + * Called to delete or cancel a Linked Account. + * @param {string} type + * @return {Promise} + */ + abstract deleteLinkedAccount(type: string): Promise; + + /** + * Gets a single Api by its ID. + * @param {string} type + * @return {Promise} + */ + abstract getLinkedAccount(type: string): Promise; + + /** + * Finalizes/completes the account linking process for a particular account type. This + * should get called once the account linking flow has been completed. + * @param {string} accountType + * @param {string} nonce + * @return {Promise} + */ + abstract completeLinkedAccount(accountType: string, nonce: string): Promise; + + /** + * Gets a list of all organizations the user belongs to. + * @param {string} accountType + * @return {Promise} + */ + abstract getAccountOrganizations(accountType: string): Promise; + + /** + * Gets all of the repositories found in a given organization. + * @param {string} accountType + * @param {string} organizationOrTeam + * @return {Promise} + */ + abstract getAccountRepositories(accountType: string, organizationOrTeam: string): Promise<(GitHubRepository[]|BitbucketRepository[])>; + + /** + * Gets a list of all groups the user belongs to. + * @param {string} accountType + * @return {Promise} + */ + abstract getAccountGroups(accountType: string): Promise; + + /** + * Gets all of the projects found in a given group. + * @param {string} accountType + * @param {string} group + * @return {Promise} + */ + abstract getAccountProjects(accountType: string, group: string): Promise; + + /** + * Gets a list of all teams the user belongs to. + * @param {string} accountType + * @return {Promise} + */ + abstract getAccountTeams(accountType: string): Promise; + +} diff --git a/front-end/studio/src/app/services/apis-hub.service.ts b/front-end/studio/src/app/services/apis-hub.service.ts new file mode 100644 index 000000000..c1a776cff --- /dev/null +++ b/front-end/studio/src/app/services/apis-hub.service.ts @@ -0,0 +1,719 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {Observable} from "rxjs/Observable"; +import 'rxjs/Rx'; +import {BehaviorSubject} from "rxjs/BehaviorSubject"; + +import { + IActivityHandler, IApiEditingSession, IApisService, ICommandHandler, + IConnectionHandler +} from "./apis.service"; +import {Api, ApiDefinition, EditableApiDefinition} from "../models/api.model"; +import {IAuthenticationService} from "./auth.service"; +import {ConfigService} from "./config.service"; +import {ApiContributor, ApiContributors} from "../models/api-contributors"; + +import {NewApi} from "../models/new-api.model"; +import {ImportApi} from "../models/import-api.model"; +import {AbstractHubService} from "./hub"; +import {User} from "../models/user.model"; +import {ICommand, MarshallUtils, OtCommand} from "oai-ts-commands"; +import {OasLibraryUtils} from "oai-ts-core"; +import {ApiDesignCommandAck} from "../models/ack.model"; +import {ApiCollaborator} from "../models/api-collaborator"; +import {Invitation} from "../models/invitation"; +import {ApiEditorUser} from "../models/editor-user.model"; +import {ApiDesignChange} from "../models/api-design-change"; +import {HttpClient, HttpHeaders, HttpResponse} from "@angular/common/http"; + + +const RECENT_APIS_LOCAL_STORAGE_KEY = "apicurio.studio.services.hub-apis.recent-apis"; + + +/** + * An implementation of an API editing session. Uses a Web Socket to communicate with + * the server. + */ +export class ApiEditingSession implements IApiEditingSession { + + private _connectionHandler: IConnectionHandler; + private _commandHandler: ICommandHandler; + private _activityHandler: IActivityHandler; + private _oasLibrary: OasLibraryUtils; + + private _connected: boolean; + private _pingIntervalId: number; + + private _users: any = {}; + + /** + * Constructor. + * @param {EditableApiDefinition} api + * @param {WebSocket} socket + */ + constructor(private api: EditableApiDefinition, private socket: WebSocket) { + this._oasLibrary = new OasLibraryUtils(); + this._connected = false; + } + + /** + * Connects the websocket to the server. + * @param {IConnectionHandler} handler + */ + connect(handler: IConnectionHandler): void { + let me: ApiEditingSession = this; + this._connectionHandler = handler; + this.socket.onopen = () => { + console.info("[ApiEditingSession] WS connection to server OPEN."); + this._connected = true; + this._connectionHandler.onConnected(); + // Start the 45s ping. + me.ping(); + }; + this.socket.onmessage = (msgEvent) => { + console.info("[ApiEditingSession] Message received from server."); + let msg: any = JSON.parse(msgEvent.data); + console.info(" Message type: %s", msg.type); + if (msg.type === "command") { + // Process a 'command' style message + console.info(" Content Version: %o", msg.contentVersion); + console.info(" Command: %o", msg.command); + if (this._commandHandler) { + let command: ICommand = MarshallUtils.unmarshallCommand(msg.command); + let otCmd: OtCommand = new OtCommand(); + otCmd.contentVersion = msg.contentVersion; + otCmd.command = command; + this._commandHandler.onCommand(otCmd); + } + } else if (msg.type === "ack") { + // Process an 'ack' style message + console.info(" Command Id: %o", msg.commandId); + if (this._commandHandler) { + let ack: ApiDesignCommandAck = new ApiDesignCommandAck(); + ack.commandId = msg.commandId; + ack.contentVersion = msg.contentVersion; + this._commandHandler.onAck(ack); + } + } else if (msg.type === "join") { + // Process a 'join' style message (user joined the session) + console.info(" User: %s", msg.user); + console.info(" ID: %s", msg.id); + let user: ApiEditorUser = new ApiEditorUser(); + user.userId = msg.id; + user.userName = msg.user; + this._users[msg.id] = user; + if (this._activityHandler) { + this._activityHandler.onJoin(user); + } + } else if (msg.type === "leave") { + // Process a 'leave' style message (user left the session) + console.info(" User: %s", msg.user); + console.info(" ID: %s", msg.id); + let user: ApiEditorUser = this._users[msg.id]; + if (user) { + delete this._users[msg.id]; + if (this._activityHandler) { + this._activityHandler.onLeave(user); + } + } + } else if (msg.type === "selection") { + // Process a 'leave' style message (user left the session) + console.info(" User: %s", msg.user); + console.info(" ID: %s", msg.id); + console.info(" Selection: %s", msg.selection); + let user: ApiEditorUser = this._users[msg.id]; + if (user) { + this._activityHandler.onSelection(user, msg.selection); + } + } else { + console.error("[ApiEditingSession] *** Invalid message type: %s", msg.type); + } + }; + this.socket.onclose = (event) => { + console.info("[ApiEditingSession] WS connection to server CLOSED: %o", event); + if (event.code === 1000) { + this._connectionHandler.onClosed(); + } else { + this._connectionHandler.onDisconnected(event.code); + } + this._connected = false; + window.clearInterval(this._pingIntervalId); + }; + } + + /** + * Called to set the command handler. + * @param {ICommandHandler} handler + */ + commandHandler(handler: ICommandHandler): void { + this._commandHandler = handler; + } + + /** + * Called to set the activity handler. + * @param {IActivityHandler} handler + */ + activityHandler(handler: IActivityHandler): void { + this._activityHandler = handler; + } + + /** + * Called to send a command to the server. + * @param {ICommand} command + */ + sendCommand(command: OtCommand): void { + let data: any = { + type: "command", + commandId: command.contentVersion, + command: MarshallUtils.marshallCommand(command.command) + }; + let dataStr: string = JSON.stringify(data); + this.socket.send(dataStr); + } + + /** + * Called to send a selection event. This is done whenever the local user + * changes her selection (e.g. in the master view). + * @param {string} selection + */ + sendSelection(selection: string): void { + let data: any = { + type: "selection", + selection: selection + }; + let dataStr: string = JSON.stringify(data); + this.socket.send(dataStr); + } + + /** + * Called to send a ping message to the server. + */ + sendPing(): void { + console.info("[ApiEditingSession] Sending PING."); + let data: any = { + type: "ping" + }; + let dataStr: string = JSON.stringify(data); + this.socket.send(dataStr); + } + + /** + * Called to close the connection with the server. Should only be called + * when the user leaves the editor. + */ + close(): void { + this.socket.close(); + } + + private ping(): void { + console.info("[ApiEditingSession] Starting the ping interval."); + this._pingIntervalId = window.setInterval(() => { + this.sendPing(); + }, 45000); + } +} + + +/** + * An implementation of the APIs service that uses the Apicurio Studio back-end (Hub API) service + * to store and retrieve relevant information for the user. + */ +export class HubApisService extends IApisService { + + private theRecentApis: Api[]; + private _recentApis: BehaviorSubject = new BehaviorSubject([]); + private recentApis: Observable = this._recentApis.asObservable(); + + private cachedApis: Api[] = null; + private _user: User; + + /** + * Constructor. + * @param {HttpClient} http + * @param {IAuthenticationService} authService + * @param {ConfigService} config + */ + constructor(http: HttpClient, authService: IAuthenticationService, config: ConfigService) { + super(http, authService, config); + this.theRecentApis = this.loadRecentApis(); + if (this.theRecentApis === null) { + this._recentApis.next([]); + } else { + this._recentApis.next(this.theRecentApis); + } + authService.getAuthenticatedUser().subscribe( user => { + this._user = user; + }); + } + + /** + * @see IApisService.getSupportedRepositoryTypes + */ + public getSupportedRepositoryTypes(): string[] { + return ["GitHub"]; + } + + /** + * @see IApisService.getApis + */ + public getApis(): Promise { + console.info("[HubApisService] Getting all APIs"); + + let listApisUrl: string = this.endpoint("/designs"); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubApisService] Fetching API list: %s", listApisUrl); + return this.httpGet(listApisUrl, options, (apis) => { + this.cachedApis = apis; + if (this.theRecentApis === null) { + this.theRecentApis = this.getRecentFromAllApis(apis); + this._recentApis.next(this.theRecentApis); + } + this.removeMissingRecentApis(apis); + return apis; + }); + } + + /** + * @see IApisService.getRecentApis + */ + public getRecentApis(): Observable { + return this.recentApis; + } + + /** + * @see IApisService.createApi + */ + public createApi(api: NewApi): Promise { + console.info("[HubApisService] Creating the API via the hub API"); + + let createApiUrl: string = this.endpoint("/designs"); + let options: any = this.options({ "Accept": "application/json", "Content-Type": "application/json" }); + + console.info("[HubApisService] Creating an API Design: %s", createApiUrl); + return this.httpPostWithReturn(createApiUrl, api, options); + } + + /** + * @see IApisService.importApi + */ + public importApi(api: ImportApi): Promise { + console.info("[HubApisService] Importing an API design via the hub API"); + + let importApiUrl: string = this.endpoint("/designs"); + let options: any = this.options({ "Accept": "application/json", "Content-Type": "application/json" }); + + console.info("[HubApisService] Importing an API Design: %s", importApiUrl); + return this.httpPutWithReturn(importApiUrl, api, options); + } + + /** + * @see IApisService.deleteApi + */ + public deleteApi(api: Api): Promise { + console.info("[HubApisService] Deleting an API design via the hub API"); + + let deleteApiUrl: string = this.endpoint("/designs/:designId", { + designId: api.id + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubApisService] Deleting an API Design: %s", deleteApiUrl); + return this.httpDelete(deleteApiUrl, options, () => { + this.cachedApis = null; + this.removeFromRecent(api); + this._recentApis.next(this.theRecentApis); + }); + } + + /** + * @see IApisService.getApi + */ + public getApi(apiId: string): Promise { + let getApiUrl: string = this.endpoint("/designs/:designId", { + designId: apiId + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubApisService] Getting an API Design: %s", getApiUrl); + return this.httpGet(getApiUrl, options, (api) => { + this.addToRecentApis(api); + this._recentApis.next(this.theRecentApis); + return api; + }); + } + + /** + * @see IApisService.editApi + */ + public editApi(apiId: string): Promise { + return this.getApi(apiId).then( api => { + let editApiUrl: string = this.endpoint("/designs/:designId/session", { + designId: apiId + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubApisService] Editing API Design: %s", editApiUrl); + options["observe"] = "response"; + return this.http.get(editApiUrl, options).map( event => { + let response: HttpResponse = event as HttpResponse; + let openApiSpec: any = response.body; + let rheaders: HttpHeaders = response.headers; + let editingSessionUuid: string = rheaders.get("X-Apicurio-EditingSessionUuid"); + let contentVersion: string = rheaders.get("X-Apicurio-ContentVersion"); + + console.info("[HubApisService] Editing Session UUID: %s", editingSessionUuid); + console.info("[HubApisService] Content Version: %s", contentVersion); + + let def: EditableApiDefinition = EditableApiDefinition.fromApi(api); + def.spec = openApiSpec; + def.editingSessionUuid = editingSessionUuid; + def.contentVersion = parseInt(contentVersion); + + return def; + }).toPromise(); + }, () => { + // TODO handle an error here! + return null; + }); + } + + /** + * @see IApisService.openEditingSession + */ + public openEditingSession(api: EditableApiDefinition): IApiEditingSession { + let designId: string = api.id; + let uuid: string = api.editingSessionUuid; + let user: string = this._user.login; + let secret: string = this.authService.getAuthenticationSecret().substr(0, 64); + let url = this.editingEndpoint("/designs/:designId?uuid=:uuid&user=:user&secret=:secret", { + designId: designId, + uuid: uuid, + user: user, + secret: secret + }); + + console.info("[HubApisService] Opening editing session on URL: %s", url); + + let websocket: WebSocket = new WebSocket(url); + + let session: ApiEditingSession = new ApiEditingSession(api, websocket); + return session; + } + + /** + * @see IApisService.getApiDefinition + */ + public getApiDefinition(apiId: string): Promise { + return this.getApi(apiId).then( api => { + let getContentUrl: string = this.endpoint("/designs/:designId/content", { + designId: apiId + }); + let options: any = this.options({ "Accept": "application/json" }); + console.info("[HubApisService] Getting API Design content: %s", getContentUrl); + options["observe"] = "response"; + return this.http.get(getContentUrl, options).map( event => { + let response: HttpResponse = event as HttpResponse; + let openApiSpec: any = response.body; + let apiDef: ApiDefinition = ApiDefinition.fromApi(api); + apiDef.spec = openApiSpec; + return apiDef; + }).toPromise(); + }); + } + + /** + * @see IApisService.getContributors + */ + public getContributors(apiId: string): Promise { + let contributorsUrl: string = this.endpoint("/designs/:designId/contributors", { + designId: apiId + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubApisService] Getting contributors: %s", contributorsUrl); + options["observe"] = "response"; + return this.http.get(contributorsUrl, options).map( event => { + let response: HttpResponse = event as HttpResponse; + let items: any[] = response.body; + let rval: ApiContributors = new ApiContributors(); + rval.contributors = []; + rval.totalEdits = 0; + items.forEach( item => { + let name: string = item["name"]; + let edits: number = item["edits"]; + let contributor: ApiContributor = new ApiContributor(); + contributor.edits = edits; + contributor.name = name; + rval.contributors.push(contributor); + rval.totalEdits += edits; + }); + return rval; + }).toPromise(); + } + + /** + * @see IApisService.getCollaborators + */ + public getCollaborators(apiId: string): Promise { + console.info("[HubApisService] Getting collaborators for API Design %s", apiId); + + let getCollaboratorsUrl: string = this.endpoint("/designs/:designId/collaborators", { + designId: apiId + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubApisService] Fetching collaborator list: %s", getCollaboratorsUrl); + return this.httpGet(getCollaboratorsUrl, options); + } + + /** + * @see IApisService.deleteCollaborator + */ + public deleteCollaborator(apiId: string, userId: string): Promise { + console.info("[HubApisService] Deleting an API collaborator for API %s", apiId); + + let deleteCollaboratorUrl: string = this.endpoint("/designs/:designId/collaborators/:userId", { + designId: apiId, + userId: userId + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubApisService] Deleting an API collaborator: %s", deleteCollaboratorUrl); + return this.httpDelete(deleteCollaboratorUrl, options); + } + + /** + * @see IApisService.getInvitations + */ + public getInvitations(apiId: string): Promise { + console.info("[HubApisService] Getting all invitations for API %s", apiId); + + let getInvitationsUrl: string = this.endpoint("/designs/:designId/invitations", { + designId: apiId + }); + + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubApisService] Fetching collaboration invitations: %s", getInvitationsUrl); + return this.httpGet(getInvitationsUrl, options); + } + + /** + * @see IApisService.getInvitation + */ + public getInvitation(apiId: string, inviteId: string): Promise { + let getInviteUrl: string = this.endpoint("/designs/:designId/invitations/:inviteId", { + designId: apiId, + inviteId: inviteId + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubApisService] Getting an Invitation: %s", getInviteUrl); + return this.httpGet(getInviteUrl, options); + } + + /** + * @see IApisService.createInvitation + */ + public createInvitation(apiId: string): Promise { + console.info("[HubApisService] Creating a collaboration invitation for API %s", apiId); + + let createInviteUrl: string = this.endpoint("/designs/:designId/invitations", { + designId: apiId + }); + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubApisService] Creating an API Design collaboration invite: %s", createInviteUrl); + return this.httpPostWithReturn(createInviteUrl, null, options); + } + + /** + * @see IApisService.rejectInvitation + */ + public rejectInvitation(apiId: string, inviteId: string): Promise { + console.info("[HubApisService] Rejecting an API invitation to collaborate for API %s", apiId); + + let deleteInviteUrl: string = this.endpoint("/designs/:designId/invitations/:inviteId", { + designId: apiId, + inviteId: inviteId + }); + let options: any = this.options({}); + + console.info("[HubApisService] Rejecting an API invitation: %s", deleteInviteUrl); + return this.httpDelete(deleteInviteUrl, options); + } + + /** + * @see IApisService.acceptInvitation + */ + public acceptInvitation(apiId: string, inviteId: string): Promise { + console.info("[HubApisService] Accepting an API invitation to collaborate for API %s", apiId); + + let acceptInviteUrl: string = this.endpoint("/designs/:designId/invitations/:inviteId", { + designId: apiId, + inviteId: inviteId + }); + let options: any = this.options({}); + + console.info("[HubApisService] Accepting an API invitation: %s", acceptInviteUrl); + return this.httpPut(acceptInviteUrl, null, options); + } + + /** + * @see IApisService.getActivity + */ + public getActivity(apiId: string, start: number, end: number): Promise { + console.info("[HubApisService] Getting all activity for API %s", apiId); + + let activityUrl: string = this.endpoint("/designs/:designId/activity", { + designId: apiId + }, { + start: start, + end: end + }); + + let options: any = this.options({ "Accept": "application/json" }); + + console.info("[HubApisService] Fetching API design activity: %s", activityUrl); + return this.httpGet(activityUrl, options); + } + + + /** + * Loads the recent APIs from browser local storage. + * @return {Api[]} + */ + private loadRecentApis(): Api[] { + let storedApis: string = localStorage.getItem(RECENT_APIS_LOCAL_STORAGE_KEY); + if (storedApis) { + let apis: any[] = JSON.parse(storedApis); + return apis; + } else { + return null; + } + } + + /** + * Returns the most recent 3 APIs from the full list of APIs provided. Uses the + * API's "last modified" time to determine the most recent 3 APIs. + * @param {Api[]} apis + * @return {Api[]} + */ + private getRecentFromAllApis(apis: Api[]): Api[] { + return apis.slice().sort( (api1, api2) => { + return -1; + }).slice(0, 3); + } + + /** + * Adds the given API to the list of "recent APIs". This may also remove an API + * from the list in addition to re-ordering the recent APIs list. + * @param {Api} api + * @return {Api[]} + */ + private addToRecentApis(api: Api): Api[] { + let recent: Api[] = []; + if (this.theRecentApis !== null) { + recent = this.theRecentApis.slice(); + } + + // Check if the api is already in the list + let oldApi: Api = null; + recent.forEach( a => { + if (api.id === a.id) { + oldApi = a; + } + }); + // If so, remove it. + if (oldApi !== null) { + recent.splice(recent.indexOf(oldApi), 1); + } + + // Now push the API onto the list (should be first) + recent.unshift(api); + + // Limit the # of APIs to 3 + if (recent.length > 3) { + recent = recent.slice(0, 3); + } + + // Finally save the recent APIs in local storage + setTimeout(() => { + let serializedApis: string = JSON.stringify(recent); + localStorage.setItem(RECENT_APIS_LOCAL_STORAGE_KEY, serializedApis); + }, 50); + + this.theRecentApis = recent; + return recent; + } + + /** + * Removes an API from the list of recent APIs. + * @param {Api} api + * @return {Api[]} + */ + private removeFromRecent(api: Api) { + let recent: Api[] = []; + if (this.theRecentApis !== null) { + recent = this.theRecentApis.slice(); + } + + // Check if the api is in the list + let oldApi: Api = null; + recent.forEach( a => { + if (api.id === a.id) { + oldApi = a; + } + }); + // If so, remove it. + if (oldApi !== null) { + recent.splice(recent.indexOf(oldApi), 1); + } + + // Save the recent APIs in local storage + setTimeout(() => { + let serializedApis: string = JSON.stringify(recent); + localStorage.setItem(RECENT_APIS_LOCAL_STORAGE_KEY, serializedApis); + }, 50); + + this.theRecentApis = recent; + return recent; + } + + /** + * Removes any API from the "recent APIs" list that is not in the + * given list of "all" Apis. + * @param {Api[]} apis + */ + private removeMissingRecentApis(apis: Api[]) { + this.theRecentApis.filter( recentApi => { + let found: boolean = false; + apis.forEach( api => { + if (api.id === recentApi.id) { + found = true; + } + }); + return !found; + }).forEach( diffApi => { + console.info("[HubApisService] Removing '%s' from the recent APIs list.", diffApi.name); + this.theRecentApis.splice(this.theRecentApis.indexOf(diffApi), 1); + }); + } +} diff --git a/front-end/studio/src/app/services/apis.service.provider.ts b/front-end/studio/src/app/services/apis.service.provider.ts new file mode 100644 index 000000000..583dc727a --- /dev/null +++ b/front-end/studio/src/app/services/apis.service.provider.ts @@ -0,0 +1,42 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {IApisService} from "./apis.service"; +import {IAuthenticationService} from "./auth.service"; +import {ConfigService} from "./config.service"; +import {HubApisService} from "./apis-hub.service"; +import {HttpClient} from "@angular/common/http"; + + +export function ApisServiceFactory(http: HttpClient, authService: IAuthenticationService, config: ConfigService): IApisService { + if (config.apisType() === "hub") { + console.info("[ApisServiceFactory] Creating instance of HubApisService"); + return new HubApisService(http, authService, config); + } else { + console.error("[ApisServiceFactory] Unknown type for APIs Service: %s", config.apisType()); + return null; + } +}; + + +export let ApisServiceProvider = +{ + provide: IApisService, + useFactory: ApisServiceFactory, + deps: [HttpClient, IAuthenticationService, ConfigService] +}; + diff --git a/front-end/studio/src/app/services/apis.service.ts b/front-end/studio/src/app/services/apis.service.ts new file mode 100644 index 000000000..90dad74dc --- /dev/null +++ b/front-end/studio/src/app/services/apis.service.ts @@ -0,0 +1,224 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Observable} from "rxjs/Observable"; + +import {Api, ApiDefinition, EditableApiDefinition} from "../models/api.model"; +import {ApiContributors} from "../models/api-contributors"; +import {NewApi} from "../models/new-api.model"; +import {ImportApi} from "../models/import-api.model"; +import {OtCommand} from "oai-ts-commands"; +import {ApiDesignCommandAck} from "../models/ack.model"; +import {ApiCollaborator} from "../models/api-collaborator"; +import {Invitation} from "../models/invitation"; +import {ApiEditorUser} from "../models/editor-user.model"; +import {ApiDesignChange} from "../models/api-design-change"; +import {AbstractHubService} from "./hub"; + + +export interface IConnectionHandler { + // Called when the connection is established. + onConnected(): void; + // Called when the connection is closed properly. + onClosed(): void; + // Called when the connection drops unexpectedly. + onDisconnected(closeCode: number): void; +} + +export interface ICommandHandler { + onCommand(command: OtCommand): void; + onAck(ack: ApiDesignCommandAck): void; +} + +export interface IActivityHandler { + onJoin(user: ApiEditorUser): void; + onLeave(user: ApiEditorUser): void; + onSelection(user: ApiEditorUser, selection: string): void; +} + + +/** + * An interface representing an API editing session. Whenever an API Design is opened + * in the designer, an editing session will be created. This is basically a connection + * to the back-end, allowing commands created locally to be sent in real-time to the + * server for sequencing and distribution to peers. + */ +export interface IApiEditingSession { + + connect(handler: IConnectionHandler): void; + + commandHandler(handler: ICommandHandler): void; + + activityHandler(handler: IActivityHandler): void; + + sendCommand(command: OtCommand): void; + + sendSelection(selection: string): void; + + close(): void; + +} + + +/** + * Service used to manage, list, get, create, edit APIs. This is the meat and potatoes + * of the Apicurio UI. + */ +export abstract class IApisService extends AbstractHubService { + + /** + * Gets an array of the repository types supported by this apis service. + * + * @return string[] + */ + abstract getSupportedRepositoryTypes(): string[]; + + /** + * Gets a promise over all of the APIs. The list of APIs is not guaranteed + * to be in any particular order. The resulting observer can be used to watch for + * changes to the list of APIs. It is assumed that calling this will fetch + * the list of APIs from the server. + */ + abstract getApis(): Promise; + + /** + * Gets an observable over the recent APIs. Callers can then subscribe to this + * observable to be notified when the value changes. + * + * @return Observable + */ + abstract getRecentApis(): Observable; + + /** + * Creates a new API with the given information. The assumption is that no OpenAPI + * document yet exists at the indicated repository location, and thus the API Service + * must create it. + * + * This will store the API in whatever storage is used by this service impl. It will + * return a Promise that the caller can use to be notified when the API has been successfully + * stored. + * @param api + * @return Promise + */ + abstract createApi(api: NewApi): Promise; + + /** + * Imports an existing API to the Studio. The assumption with this call is that the + * API's OpenAPI document already exists in the source repository (or URL). + * @param api + */ + abstract importApi(api: ImportApi): Promise; + + /** + * Called to delete an API. This is done asynchronously and thus returns a promise. + * @param api + */ + abstract deleteApi(api: Api): Promise; + + /** + * Gets a single Api by its ID. + * @param apiId the ID of the api + */ + abstract getApi(apiId: string): Promise; + + /** + * Starts a new (or connects to an existing) editing session for the given API Design (by ID). + * @param {string} apiId + * @return {Promise} + */ + abstract editApi(apiId: string): Promise; + + /** + * Opens an API editing session for the given Api. + * @param {EditableApiDefinition} api + * @return {ApiEditingSession} + */ + abstract openEditingSession(api: EditableApiDefinition): IApiEditingSession; + + /** + * Gets a single API definition by its ID. + * @param apiId + */ + abstract getApiDefinition(apiId: string): Promise; + + /** + * Gets the list of contributors for the API with the given id. + * @param apiId + */ + abstract getContributors(apiId: string): Promise; + + /** + * Gets the list of collaborators for the API with the given id. + * @param {string} apiId + * @return {Promise} + */ + abstract getCollaborators(apiId: string): Promise; + + /** + * Deletes a collaborator of an API, removing access for that user. + * @param {string} apiId + * @param {string} userId + * @return {Promise} + */ + abstract deleteCollaborator(apiId: string, userId: string): Promise; + + /** + * Gets the list of collaborator invitations for the API with the given id. + * @param {string} apiId + * @return {Promise} + */ + abstract getInvitations(apiId: string): Promise; + + /** + * Gets a single invitation by its ID (for a given API design). + * @param {string} apiId + * @param {string} inviteId + * @return {Promise} + */ + abstract getInvitation(apiId:string, inviteId: string): Promise; + + /** + * Creates a new invite-to-collaborate for the given API design. + * @param {string} apiId + * @return {Promise} + */ + abstract createInvitation(apiId: string): Promise; + + /** + * Rejects an invitation to collaborate. + * @param {string} apiId + * @param {string} inviteId + * @return {Promise} + */ + abstract rejectInvitation(apiId: string, inviteId: string): Promise; + + /** + * Accepts an invitation to collaborate. + * @param {string} apiId + * @param {string} inviteId + * @return {Promise} + */ + abstract acceptInvitation(apiId: string, inviteId: string): Promise; + + /** + * Gets the list of activity items for a given API design. + * @param {string} apiId + * @return {Promise} + */ + abstract getActivity(apiId: string, start: number, end: number): Promise; + +} diff --git a/front-end/studio/src/app/services/auth-keycloak.service.ts b/front-end/studio/src/app/services/auth-keycloak.service.ts new file mode 100644 index 000000000..e613e153a --- /dev/null +++ b/front-end/studio/src/app/services/auth-keycloak.service.ts @@ -0,0 +1,125 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {IAuthenticationService} from "./auth.service"; +import {Observable} from "rxjs/Observable"; +import {BehaviorSubject} from "rxjs/BehaviorSubject"; +import {User} from "../models/user.model"; +import {ConfigService} from "./config.service"; +import {HttpClient} from "@angular/common/http"; + +/** + * A version of the authentication service that uses keycloak.js to provide + * authentication services. + */ +export class KeycloakAuthenticationService extends IAuthenticationService { + + private _authenticated: BehaviorSubject = new BehaviorSubject(false); + public authenticated: Observable = this._authenticated.asObservable(); + + private _authenticatedUser: BehaviorSubject = new BehaviorSubject(null); + public authenticatedUser: Observable = this._authenticatedUser.asObservable(); + + private keycloak: any; + + /** + * Constructor. + * @param {Http} http + * @param {ConfigService} config + */ + constructor(private http: HttpClient, private config: ConfigService) { + super(); + let w: any = window; + this.keycloak = w["keycloak"]; + + // console.info("Token: %s", JSON.stringify(this.keycloak.tokenParsed, null, 2)); + // console.info("ID Token: %s", JSON.stringify(this.keycloak.idTokenParsed, null, 2)); + // console.info("Access Token: %s", this.keycloak.token); + + let user: User = new User(); + user.name = this.keycloak.tokenParsed.name; + user.login = this.keycloak.tokenParsed.preferred_username; + user.email = this.keycloak.tokenParsed.email; + + this._authenticated.next(true); + this._authenticatedUser.next(user); + + // Periodically refresh + // TODO run this outsize NgZone using zone.runOutsideAngular() : https://angular.io/api/core/NgZone + setInterval(() => { + this.keycloak.updateToken(30); + }, 30000); + } + + /** + * Returns the observable for is/isnot authenticated. + * @return {Observable} + */ + public isAuthenticated(): Observable { + return this.authenticated; + } + + /** + * Returns an observable over the currently authenticated User (or null if not logged in). + * @return {any} + */ + public getAuthenticatedUser(): Observable { + return this.authenticatedUser; + } + + /** + * Returns the currently authenticated user. + * @return {User} + */ + public getAuthenticatedUserNow(): User { + return this._authenticatedUser.getValue(); + } + + /** + * Not supported. + * @param user + * @param credential + */ + public login(user: string, credential: any): Promise { + throw new Error("Not supported."); + } + + /** + * Logout. + */ + public logout(): void { + this.keycloak.logout({ redirectUri: location.href }); + } + + /** + * Called to inject authentication headers into a remote API call. + * @param headers + */ + public injectAuthHeaders(headers: {[header: string]: string}): void { + let authHeader: string = "bearer " + this.keycloak.token; + headers["Authorization"] = authHeader; + } + + /** + * Called to return the keycloak access token. + * @return {string} + */ + public getAuthenticationSecret(): string { + return this.keycloak.token; + } + +} diff --git a/front-end/studio/src/app/services/auth-oidc.service.ts b/front-end/studio/src/app/services/auth-oidc.service.ts new file mode 100644 index 000000000..5fec71590 --- /dev/null +++ b/front-end/studio/src/app/services/auth-oidc.service.ts @@ -0,0 +1,175 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {IAuthenticationService} from "./auth.service"; +import {Observable} from "rxjs/Observable"; +import {BehaviorSubject} from "rxjs/BehaviorSubject"; +import {User} from "../models/user.model"; +import {ConfigService} from "./config.service"; +import {HttpClient, HttpResponse} from "@angular/common/http"; + + +export class OIDCDirectGrantAccessToken { + public access_token: string; + public expires_in: number; + public refresh_expires_in: number; + public token_type: "bearer"; + public session_state: string; +} + + +/** + * A version of the authentication service that uses OpenID Connect Direct + * Grant to obtain an access token. + */ +export class OIDCDirectGrantAuthenticationService extends IAuthenticationService { + + private _authenticated: BehaviorSubject = new BehaviorSubject(false); + public authenticated: Observable = this._authenticated.asObservable(); + + private _authenticatedUser: BehaviorSubject = new BehaviorSubject(null); + public authenticatedUser: Observable = this._authenticatedUser.asObservable(); + + private accessToken: OIDCDirectGrantAccessToken; + + /** + * Constructor. + * @param http + * @param config + */ + constructor(private http: HttpClient, private config: ConfigService) { + super(); + console.info("[OIDCDirectGrantAuthenticationService] C'tor"); + } + + /** + * Returns the observable for is/isnot authenticated. + * @return {Observable} + */ + public isAuthenticated(): Observable { + return this.authenticated; + } + + /** + * Returns an observable over the currently authenticated User (or null if not logged in). + * @return {any} + */ + public getAuthenticatedUser(): Observable { + return this.authenticatedUser; + } + + /** + * Returns the currently authenticated user. + * @return {User} + */ + public getAuthenticatedUserNow(): User { + return this._authenticatedUser.getValue(); + } + + /** + * Not supported. + * @param user + * @param credential + */ + public login(user: string, credential: any): Promise { + let authData: any = this.config.authData(); + + let authTokenUrl: string = authData.auth_url; + let headers: any = { "Accept": "application/json", "Content-Type": "application/x-www-form-urlencoded" }; + let options: any = { + observe: "response", + headers: headers + }; + let body: any = "grant_type=" + authData.grant_type + "&client_id=" + authData.client_id + "&username=" + + user + "&password=" + credential; + + console.info("[OIDCDirectGrantAuthenticationService] Getting an access token from: %s", authTokenUrl); + + return this.http.post(authTokenUrl, body, options).map( event => { + let response: HttpResponse = event as HttpResponse; + console.info("[OIDCDirectGrantAuthenticationService] Received access token."); + let token: OIDCDirectGrantAccessToken = response.body; + this.accessToken = token; + let user: User = this.toUser(token); + return user; + }).toPromise().then( user => { + this._authenticated.next(true); + this._authenticatedUser.next(user); + return Promise.resolve(user); + }).catch( reason => { + console.error("[OIDCDirectGrantAuthenticationService] Failed to obtain access token: %o", reason); + let errorMessage: string = reason.statusText; + if (reason.status === 401) { + errorMessage = reason.json().message; + } + return Promise.reject(errorMessage) + }); + } + + /** + * Logout. + */ + public logout(): void { + location.reload(true); + } + + /** + * Called to inject authentication headers into a remote API call. + * @param headers + */ + public injectAuthHeaders(headers: {[header: string]: string}): void { + let authHeader: string = "bearer " + this.accessToken.access_token; + headers["Authorization"] = authHeader; + } + + /** + * Extracts user information from the access token. + * @param {OIDCDirectGrantAccessToken} token + * @return {User} + */ + protected toUser(token: OIDCDirectGrantAccessToken): User { + let tokenData: any = this.parseToken(token.access_token); + let user: User = new User(); + user.login = tokenData["preferred_username"]; + user.email = tokenData["email"]; + user.name = tokenData["name"]; + return user; + } + + /** + * Parses the access token into a js object. The access token is a base64 encoded JWT. + * @param {string} token + * @return {any} + */ + protected parseToken(token: string): any { + token = token.split(".")[1]; + token = token.replace(/-/g, '+'); + token = token.replace(/_/g, '/'); + let raw: string = atob(token); + let obj: any = JSON.parse(raw); + return obj; + } + + /** + * Called to return the access token. + * @return {string} + */ + public getAuthenticationSecret(): string { + return this.accessToken.access_token; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/services/auth-token.service.ts b/front-end/studio/src/app/services/auth-token.service.ts new file mode 100644 index 000000000..0bf4d7ce9 --- /dev/null +++ b/front-end/studio/src/app/services/auth-token.service.ts @@ -0,0 +1,165 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {IAuthenticationService} from "./auth.service"; +import {Observable} from "rxjs/Observable"; +import {BehaviorSubject} from "rxjs/BehaviorSubject"; +import {User} from "../models/user.model"; +import {ConfigService} from "./config.service"; +import {HttpClient, HttpResponse} from "@angular/common/http"; + +/** + * A version of the authentication service that uses token information passed to it + * when the application loads. The assumption here is that authentication is handled + * on the server (for example via OAuth2 web flow). The server will then pass the + * token information down into the angular app. + */ +export class TokenAuthenticationService extends IAuthenticationService { + + private _authenticated: BehaviorSubject = new BehaviorSubject(false); + public authenticated: Observable = this._authenticated.asObservable(); + + private _authenticatedUser: BehaviorSubject = new BehaviorSubject(null); + public authenticatedUser: Observable = this._authenticatedUser.asObservable(); + + private accessToken: string; + + /** + * Constructor. + * @param http + * @param config + */ + constructor(private http: HttpClient, private config: ConfigService) { + super(); + this.accessToken = config.authToken(); + + this._authenticated.next(true); + this._authenticatedUser.next(config.user()); + + let refreshPeriod: number = config.authRefreshPeriod(); + if (refreshPeriod) { + console.info("[TokenAuthenticationService] Will refresh auth token in %d seconds.", refreshPeriod) + setTimeout(() => { + this.refreshToken(); + }, refreshPeriod * 1000); + } else { + console.info("[TokenAuthenticationService] No refresh period set. Token may expire unexpectedly!"); + } + } + + /** + * Returns the observable for is/isnot authenticated. + * @return {Observable} + */ + public isAuthenticated(): Observable { + return this.authenticated; + } + + /** + * Returns an observable over the currently authenticated User (or null if not logged in). + * @return {any} + */ + public getAuthenticatedUser(): Observable { + return this.authenticatedUser; + } + + /** + * Returns the currently authenticated user. + * @return {User} + */ + public getAuthenticatedUserNow(): User { + return this._authenticatedUser.getValue(); + } + + /** + * Not supported. + * @param user + * @param credential + */ + public login(user: string, credential: any): Promise { + throw new Error("Not supported."); + } + + /** + * Logout. + */ + public logout(): void { + window.location.href = this.config.logoutUrl(); + } + + /** + * Called to inject authentication headers into a remote API call. + * @param headers + */ + public injectAuthHeaders(headers: {[header: string]: string}): void { + let authHeader: string = "bearer " + this.accessToken; + headers["Authorization"] = authHeader; + } + + /** + * Returns the oauth access token. + * @return {string} + */ + public getAuthenticationSecret(): string { + return this.accessToken; + } + + /** + * Refreshes the authentication token. + */ + protected refreshToken(): void { + let base: string = document.getElementsByTagName("base")[0].href; + if (base.indexOf("/") == 0) { + base = location.origin + base; + } + let url: string = base + "token"; + let headers: any = { "Accept": "application/json" }; + let options: any = { + observe: "response", + headers: new Headers(headers) + }; + + console.info("[TokenAuthenticationService] Refreshing auth token: %s", url); + + this.http.get(url, options).map( event => { + let response: HttpResponse = event as HttpResponse; + let auth: any = response.body; + return auth; + }).toPromise().then( auth => { + this.accessToken = auth.token; + let refreshPeriod: number = auth.tokenRefreshPeriod; + if (refreshPeriod) { + console.info("[TokenAuthenticationService] Will refresh auth token in %d seconds.", refreshPeriod) + setTimeout(() => { + this.refreshToken(); + }, refreshPeriod * 1000); + } else { + console.info("[TokenAuthenticationService] No refresh period set. Token may expire unexpectedly!"); + } + }).catch( error => { + console.info("[TokenAuthenticationService] Error refreshing auth token. Will try again in 30s."); + console.info("[TokenAuthenticationService] %o", error); + if (error.status === 0) { + // TODO an error 0 may indicate that the user is logged out + } + setTimeout(() => { + this.refreshToken(); + }, 30 * 1000); + }); + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/services/auth.service.provider.ts b/front-end/studio/src/app/services/auth.service.provider.ts new file mode 100644 index 000000000..83c7ae550 --- /dev/null +++ b/front-end/studio/src/app/services/auth.service.provider.ts @@ -0,0 +1,48 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {IAuthenticationService} from "./auth.service"; +import {ConfigService} from "./config.service"; +import {TokenAuthenticationService} from "./auth-token.service"; +import {OIDCDirectGrantAuthenticationService} from "./auth-oidc.service"; +import {KeycloakAuthenticationService} from "./auth-keycloak.service"; +import {HttpClient} from "@angular/common/http"; + + +export function AuthenticationServiceFactory(http: HttpClient, config: ConfigService): IAuthenticationService { + if (config.authType() === "keycloakjs") { + console.info("[AuthenticationServiceFactory] Creating keycloak.js auth service."); + return new KeycloakAuthenticationService(http, config); + } else if (config.authType() === "token") { + console.info("[AuthenticationServiceFactory] Creating token auth service."); + return new TokenAuthenticationService(http, config); + } else if (config.authType() === "oidc-direct-grant") { + console.info("[AuthenticationServiceFactory] Creating OIDC Direct Grant auth service."); + return new OIDCDirectGrantAuthenticationService(http, config); + } else { + console.error("[AuthenticationServiceFactory] Unsupported auth type: %s", config.authType()); + return null; + } +}; + + +export let AuthenticationServiceProvider = +{ + provide: IAuthenticationService, + useFactory: AuthenticationServiceFactory, + deps: [HttpClient, ConfigService] +}; diff --git a/front-end/studio/src/app/services/auth.service.ts b/front-end/studio/src/app/services/auth.service.ts new file mode 100644 index 000000000..a3fd75d36 --- /dev/null +++ b/front-end/studio/src/app/services/auth.service.ts @@ -0,0 +1,67 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Observable} from "rxjs/Observable"; + +import {User} from "../models/user.model"; + + +export abstract class IAuthenticationService { + + /** + * A way for consumers to subscribe to the current authentication status of the user/app. + */ + abstract isAuthenticated(): Observable; + + /** + * Get the currently authenticated user. May be null if the user is not currently authenticated. + */ + abstract getAuthenticatedUser(): Observable; + + /** + * Immediately gets the current authenticated user (if any). Returns null if no user is + * currently authenticated. + * @return {User} + */ + abstract getAuthenticatedUserNow(): User; + + /** + * Called to authenticate a user. + * @param user + * @param credential + */ + abstract login(user:string, credential:any): Promise; + + /** + * Called to log out the current user. + */ + abstract logout(): void; + + /** + * Called to inject authentication headers into an API REST call. + * @param headers + */ + abstract injectAuthHeaders(headers: {[header: string]: string}): void; + + /** + * Called to return an authentication secret (e.g. the auth access token). + * @return {string} + */ + abstract getAuthenticationSecret(): string; +} + +//export const IAuthenticationService = new InjectionToken("IAuthenticationService"); diff --git a/front-end/studio/src/app/services/config.service.ts b/front-end/studio/src/app/services/config.service.ts new file mode 100644 index 000000000..ccd9574ca --- /dev/null +++ b/front-end/studio/src/app/services/config.service.ts @@ -0,0 +1,112 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Injectable} from "@angular/core"; +import {User} from "../models/user.model"; + +let DEFAULT_CONFIG: any = { + mode: "dev", + auth: { + type: "keycloakjs" + }, + apis: { + type: "hub", + hubUrl: "http://localhost:8080/api-hub", + editingUrl: "http://localhost:8080/api-editing", + } +}; + + +/** + * An abstract base class for services that need to make API calls to Github. + */ +@Injectable() +export class ConfigService { + + private config: any; + + constructor() { + let w: any = window; + if (w["ApicurioStudioConfig"]) { + this.config = w["ApicurioStudioConfig"]; + console.info("[ConfigService] Found app config."); + } else { + console.error("[ConfigService] App config not found!"); + this.config = DEFAULT_CONFIG; + } + } + + public authType(): string { + if (!this.config.auth) { + return null; + } + return this.config.auth.type; + } + + public authToken(): string { + if (!this.config.auth) { + return null; + } + return this.config.auth.token; + } + + public authRefreshPeriod(): number { + if (!this.config.auth) { + return null; + } + return this.config.auth.tokenRefreshPeriod; + } + + public authData(): any { + if (!this.config.auth) { + return null; + } + return this.config.auth.data; + } + + public logoutUrl(): string { + if (!this.config.auth) { + return null; + } + return this.config.auth.logoutUrl; + } + + public user(): User { + return this.config.user; + } + + public apisType(): string { + if (!this.config.apis) { + return null; + } + return this.config.apis.type; + } + + public hubUrl(): string { + if (!this.config.apis) { + return null; + } + return this.config.apis.hubUrl; + } + + public editingUrl(): string { + if (!this.config.apis) { + return null; + } + return this.config.apis.editingUrl; + } +} \ No newline at end of file diff --git a/front-end/studio/src/app/services/github.ts b/front-end/studio/src/app/services/github.ts new file mode 100644 index 000000000..c89171f60 --- /dev/null +++ b/front-end/studio/src/app/services/github.ts @@ -0,0 +1,42 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +const GITHUB_API_ENDPOINT = "https://api.github.com"; + +/** + * An abstract base class for services that need to make API calls to Github. + */ +export abstract class AbstractGithubService { + + /** + * Creates a github API endpoint from the api path and params. + * @param path + * @param params + * @return {string} + */ + protected endpoint(path: string, params?: any): string { + if (params) { + for (let key in params) { + let value: string = params[key]; + path = path.replace(":" + key, value); + } + } + return GITHUB_API_ENDPOINT + path; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/app/services/hub.ts b/front-end/studio/src/app/services/hub.ts new file mode 100644 index 000000000..e1a52e804 --- /dev/null +++ b/front-end/studio/src/app/services/hub.ts @@ -0,0 +1,219 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {IAuthenticationService} from "./auth.service"; +import {ConfigService} from "./config.service"; +import {HttpClient, HttpResponse} from "@angular/common/http"; + +/** + * Base class for all Hub-API based services. + */ +export abstract class AbstractHubService { + + private apiBaseHref: string; + private editingBaseHref: string; + + /** + * Constructor. + * @param http + * @param authService + */ + constructor(protected http: HttpClient, protected authService: IAuthenticationService, protected config: ConfigService) { + this.apiBaseHref = this.config.hubUrl(); + this.editingBaseHref = this.config.editingUrl(); + } + + /** + * Creates a hub API endpoint from the api path and params. + * @param {string} path + * @param params + * @return {string} + */ + protected endpoint(path: string, params?: any, queryParams?: any): string { + if (params) { + for (let key in params) { + let value: string = params[key]; + path = path.replace(":" + key, value); + } + } + let rval: string = this.apiBaseHref + path; + if (queryParams) { + let first: boolean = true; + for (let key in queryParams) { + let value: string = queryParams[key]; + if (first) { + rval = rval + "?" + key; + } else { + rval = rval + "&" + key; + } + if (value != null && value != undefined) { + rval = rval + "=" + value; + } + first = false; + } + } + return rval; + } + + /** + * Creates an editing endpoint from the given relative path and params. + * @param {string} path + * @param params + * @return {string} + */ + protected editingEndpoint(path: string, params?: any): string { + if (params) { + for (let key in params) { + let value: string = params[key]; + path = path.replace(":" + key, value); + } + } + return this.editingBaseHref + path; + } + + /** + * Creates the request options used by the HTTP service when making + * API calls. + * @param {{[p: string]: string}} headers + * @param {boolean} authenticated + * @return {any} + */ + protected options(headers: {[header: string]: string}, authenticated: boolean = true): any { + let options = { + headers: headers + }; + if (authenticated) { + this.authService.injectAuthHeaders(options.headers); + } + return options; + } + + /** + * Performs an HTTP GET operation to the given URL with the given options. Returns + * a Promise to the HTTP response data. + * @param {string} url + * @param options + * @return {Promise} + */ + protected httpGet(url: string, options: any, successCallback?: (apis: T) => T): Promise { + options["observe"] = "response"; + return this.http.get(url, options).map( event => { + let response: HttpResponse = event as HttpResponse; + if (successCallback) { + return successCallback(response.body); + } else { + return response.body; + } + }).toPromise(); + } + + /** + * Performs an HTTP POST operation to the given URL with the given body and options. Returns + * a Promise to null (no response data expected). + * @param {string} url + * @param {I} body + * @param options + * @return {Promise} + */ + protected httpPost(url: string, body: I, options: any, successCallback?: () => void): Promise { + options["observe"] = "response"; + return this.http.post(url, body, options).map( () => { + if (successCallback) { + successCallback(); + } + return null; + }).toPromise(); + } + + /** + * Performs an HTTP POST operation to the given URL with the given body and options. Returns + * a Promise to the HTTP response data. + * @param {string} url + * @param {I} body + * @param options + * @return {Promise} + */ + protected httpPostWithReturn(url: string, body: I, options: any, successCallback?: (data: O) => O): Promise { + options["observe"] = "response"; + return this.http.post(url, body, options).map( event => { + let response: HttpResponse = event as HttpResponse; + let data: O = response.body; + if (successCallback) { + return successCallback(data); + } else { + return response.body; + } + }).toPromise(); + } + + /** + * Performs an HTTP PUT operation to the given URL with the given body and options. Returns + * a Promise to null (no response data expected). + * @param {string} url + * @param {I} body + * @param options + * @return {Promise} + */ + protected httpPut(url: string, body: I, options: any, successCallback?: () => void): Promise { + options["observe"] = "response"; + return this.http.put(url, body, options).map( () => { + if (successCallback) { + successCallback(); + } + return null; + }).toPromise(); + } + + /** + * Performs an HTTP PUT operation to the given URL with the given body and options. Returns + * a Promise to the HTTP response data. + * @param {string} url + * @param {I} body + * @param options + * @return {Promise} + */ + protected httpPutWithReturn(url: string, body: I, options: any, successCallback?: (data: O) => O): Promise { + options["observe"] = "response"; + return this.http.put(url, body, options).map( event => { + let response: HttpResponse = event as HttpResponse; + let data: O = response.body; + if (successCallback) { + return successCallback(data); + } else { + return response.body; + } + }).toPromise(); + } + + /** + * Performs an HTTP DELETE operation to the given URL with the given body and options. + * @param {string} url + * @param options + * @return {Promise} + */ + protected httpDelete(url: string, options: any, successCallback?: () => void): Promise { + options["observe"] = "response"; + return this.http.delete(url, options).map( () => { + if (successCallback) { + successCallback(); + } + return null; + }).toPromise(); + } + +} diff --git a/front-end/studio/src/app/util/common.ts b/front-end/studio/src/app/util/common.ts new file mode 100644 index 000000000..c61262c98 --- /dev/null +++ b/front-end/studio/src/app/util/common.ts @@ -0,0 +1,111 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +export class ArrayUtils { + + /** + * Returns the intersection of two arrays. + * @param a1 + * @param a2 + */ + public static intersect(a1: any[], a2: any[]): any[] { + let rval: any[] = []; + for (let item of a1) { + if (ArrayUtils.contains(a2, item)) { + rval.push(item); + } + } + return rval; + } + + /** + * Returns true if the given item is contained in the given array. + * @param a + * @param item + * @return {boolean} + */ + public static contains(a: any[], item: any): boolean { + for (let aitem of a) { + if (aitem === item) { + return true; + } + } + return false; + } + +} + +export class ObjectUtils { + + public static isNullOrUndefined(object: any): boolean { + return object === undefined || object === null; + } + + public static objectEquals(x:any, y:any) { + if (x === null || x === undefined || y === null || y === undefined) { return x === y; } + // after this just checking type of one would be enough + if (x.constructor !== y.constructor) { return false; } + // if they are functions, they should exactly refer to same one (because of closures) + if (x instanceof Function) { return x === y; } + // if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES) + if (x instanceof RegExp) { return x === y; } + if (x === y || x.valueOf() === y.valueOf()) { return true; } + if (Array.isArray(x) && x.length !== y.length) { return false; } + + // if they are dates, they must had equal valueOf + if (x instanceof Date) { return false; } + + // if they are strictly equal, they both need to be object at least + if (!(x instanceof Object)) { return false; } + if (!(y instanceof Object)) { return false; } + + var p = Object.keys(x); + return Object.keys(y).every(function (i) { return p.indexOf(i) !== -1; }) && + p.every(function (i) { return ObjectUtils.objectEquals(x[i], y[i]); }); + } + +} + +export class HttpUtils { + + public static parseLinkHeader(header: string): any { + let links: any = new Object(); + if (ObjectUtils.isNullOrUndefined(header)) { + return links; + } + if (header.length === 0) { + return links; + } + + // Split parts by comma + let parts: string[] = header.split(','); + + // Parse each part into a named link + parts.forEach( part => { + var section = part.split(';'); + if (section.length == 2) { + var url = section[0].replace(/<(.*)>/, '$1').trim(); + var name = section[1].replace(/rel="(.*)"/, '$1').trim(); + links[name] = url; + } + }); + + return links; + } + +} \ No newline at end of file diff --git a/front-end/studio/src/assets/.gitkeep b/front-end/studio/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/front-end/studio/src/assets/apicurio_icon_darkbkg_32px.png b/front-end/studio/src/assets/apicurio_icon_darkbkg_32px.png new file mode 100644 index 000000000..8d89ee3c1 Binary files /dev/null and b/front-end/studio/src/assets/apicurio_icon_darkbkg_32px.png differ diff --git a/front-end/studio/src/assets/favicon.ico b/front-end/studio/src/assets/favicon.ico new file mode 100644 index 000000000..795c6b51d Binary files /dev/null and b/front-end/studio/src/assets/favicon.ico differ diff --git a/front-end/studio/src/assets/login-logo.png b/front-end/studio/src/assets/login-logo.png new file mode 100644 index 000000000..46235a38c Binary files /dev/null and b/front-end/studio/src/assets/login-logo.png differ diff --git a/front-end/studio/src/assets/openapi.png b/front-end/studio/src/assets/openapi.png new file mode 100644 index 000000000..d957983d7 Binary files /dev/null and b/front-end/studio/src/assets/openapi.png differ diff --git a/front-end/studio/src/assets/openapi_32.png b/front-end/studio/src/assets/openapi_32.png new file mode 100644 index 000000000..b3ccb877a Binary files /dev/null and b/front-end/studio/src/assets/openapi_32.png differ diff --git a/front-end/studio/src/assets/swagger.png b/front-end/studio/src/assets/swagger.png new file mode 100644 index 000000000..7393d426c Binary files /dev/null and b/front-end/studio/src/assets/swagger.png differ diff --git a/front-end/studio/src/assets/swagger_32.png b/front-end/studio/src/assets/swagger_32.png new file mode 100644 index 000000000..107e10a6e Binary files /dev/null and b/front-end/studio/src/assets/swagger_32.png differ diff --git a/front-end/studio/src/environments/environment.prod.ts b/front-end/studio/src/environments/environment.prod.ts new file mode 100644 index 000000000..3612073bc --- /dev/null +++ b/front-end/studio/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/front-end/studio/src/environments/environment.ts b/front-end/studio/src/environments/environment.ts new file mode 100644 index 000000000..b7f639aec --- /dev/null +++ b/front-end/studio/src/environments/environment.ts @@ -0,0 +1,8 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `.angular-cli.json`. + +export const environment = { + production: false +}; diff --git a/front-end/studio/src/index.html b/front-end/studio/src/index.html new file mode 100644 index 000000000..47cd66916 --- /dev/null +++ b/front-end/studio/src/index.html @@ -0,0 +1,28 @@ + + + + + Apicurio Studio + + + + + + + + + + + + + + + + Loading... + + + + + + + diff --git a/front-end/studio/src/main.ts b/front-end/studio/src/main.ts new file mode 100644 index 000000000..b1dab3382 --- /dev/null +++ b/front-end/studio/src/main.ts @@ -0,0 +1,40 @@ +import {enableProdMode} from '@angular/core'; +import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; + +import {AppModule} from './app/app.module'; +import {environment} from './environments/environment'; + +function printBanner(env) { + console.log(`%c ___________________________________________________ + Welcome to _ _ + /\\ (_) (_) + / \\ _ __ _ ___ _ _ _ __ _ ___ + / /\\ \\ | '_ \\| |/ __| | | | '__| |/ _ \\ + / ____ \\| |_) | | (__| |_| | | | | (_) | + /_/ \\_\\ .__/|_|\\___|\\__,_|_| |_|\\___/ + | | + |_| + + Mode: %s + ___________________________________________________ +`, "font-family:monospace", env); +} + +if (environment.production) { + enableProdMode(); + platformBrowserDynamic().bootstrapModule(AppModule).then(() => { + printBanner("Production"); + }).catch(err => console.log(err)); +} else { + var keycloak = window["Keycloak"](); + keycloak.init({onLoad: 'login-required'}).success(function (authenticated) { + if (authenticated) { + window['keycloak'] = keycloak; + platformBrowserDynamic().bootstrapModule(AppModule).then(() => { + printBanner("Development"); + }).catch(err => console.log(err)); + } + }).error(function () { + alert('Failed to initialize authentication subsystem.'); + }); +} diff --git a/front-end/studio/src/polyfills.ts b/front-end/studio/src/polyfills.ts new file mode 100644 index 000000000..d68672ffe --- /dev/null +++ b/front-end/studio/src/polyfills.ts @@ -0,0 +1,66 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +// import 'core-js/es6/function'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/weak-map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following for the Reflect API. */ +// import 'core-js/es6/reflect'; + + +/** Evergreen browsers require these. **/ +// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. +import 'core-js/es7/reflect'; + + +/** + * Required to support Web Animations `@angular/platform-browser/animations`. + * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation + **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/front-end/studio/src/styles.css b/front-end/studio/src/styles.css new file mode 100644 index 000000000..e7863ec04 --- /dev/null +++ b/front-end/studio/src/styles.css @@ -0,0 +1,133 @@ + +body { + font-size: 13px; +} + +#api-page-body { + margin-left: 120px; +} + +#api-page-body .modal-header { + background-color: rgb(57, 165, 220); + color: white; +} +#api-page-body .modal-header button.close { + color: white; +} +#api-page-body .modal-footer { + border-top: 1px solid rgb(221, 221, 221); + background-color: rgb(239, 239, 239); +} + +#login-form { + background-color: black; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.modal-dialog { + margin-top: 50px; +} + +/** + PACE Theme: minimal + */ +.pace { + -webkit-pointer-events: none; + pointer-events: none; + + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.pace-inactive { + display: none; +} + +.pace .pace-progress { + background: #ef6366; + position: fixed; + z-index: 2000; + top: 0; + right: 100%; + width: 100%; + height: 3px; +} + +form input.ng-invalid.ng-touched { + border-color: red; +} + +.center-text { + text-align: center; +} + +.dropdown-menu > li > a { + cursor: pointer; +} + +/* Loading Animations */ +#load-container { + padding-top: 60px; // Approx in content area rather than nav-bar. +margin: auto; + animation: fadein 2s; +} + +#apicurio-the-hero { + display: block; + height: auto; + margin: auto; + width: 200px; +} +#apicurio-the-hero #path3401, +#apicurio-the-hero #path3397, +#apicurio-the-hero #path3393 { + fill: white; + stroke: #bfbfbf; + stroke-width: 1; +} + +#ball-holder { + width: 200px; + margin: auto; +} +#ball-holder #ball { + display: block; + width: 30px; + height: 30px; + border-radius: 100%; + fill: white; + stroke: #bfbfbf; + stroke-width: 2; + animation: loadball 3s infinite linear; +} + +@keyframes loadball { + 50% { transform: translate(170px, 0px); } +} + +@keyframes fadein { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +#api-breadcrumb-bar { + background: white; + border-bottom: 1px solid #ccc; + height: 37px; +} +#api-breadcrumb-bar .api-breadcrumbs { + height: 37px; +} + +.dropdown-menu { + font-size: 13px; +} diff --git a/front-end/studio/src/tsconfig.app.json b/front-end/studio/src/tsconfig.app.json new file mode 100644 index 000000000..e7e30699d --- /dev/null +++ b/front-end/studio/src/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "angularCompilerOptions": { + "entryModule": "./app/app.module#AppModule" + }, + "compilerOptions": { + "outDir": "../out-tsc/app", + "baseUrl": "./", + "module": "es2015", + "types": [] + }, + "exclude": [ + ] +} diff --git a/front-end/studio/src/typings.d.ts b/front-end/studio/src/typings.d.ts new file mode 100644 index 000000000..ef5c7bd62 --- /dev/null +++ b/front-end/studio/src/typings.d.ts @@ -0,0 +1,5 @@ +/* SystemJS module definition */ +declare var module: NodeModule; +interface NodeModule { + id: string; +} diff --git a/front-end/studio/src/version.js b/front-end/studio/src/version.js new file mode 100644 index 000000000..dc9828212 --- /dev/null +++ b/front-end/studio/src/version.js @@ -0,0 +1,5 @@ +var ApicurioStudioInfo = { + version: "DEV", + builtOn: new Date(), + url: "http://www.apicur.io/" +}; diff --git a/front-end/studio/tsconfig.json b/front-end/studio/tsconfig.json new file mode 100644 index 000000000..a6c016bf3 --- /dev/null +++ b/front-end/studio/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ] + } +} diff --git a/front-end/studio/yarn.lock b/front-end/studio/yarn.lock new file mode 100644 index 000000000..ec0bdb6a2 --- /dev/null +++ b/front-end/studio/yarn.lock @@ -0,0 +1,6052 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@angular-devkit/build-optimizer@0.0.42": + version "0.0.42" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.0.42.tgz#402b0dda4883db91e2381c3ddc55888408a7894e" + dependencies: + loader-utils "^1.1.0" + source-map "^0.5.6" + typescript "~2.6.2" + webpack-sources "^1.0.1" + +"@angular-devkit/core@0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-0.0.29.tgz#6fb319b45a62eff172318cbe256fdb24ef20af2b" + dependencies: + ajv "~5.5.1" + chokidar "^1.7.0" + rxjs "^5.5.6" + source-map "^0.5.6" + +"@angular-devkit/schematics@0.0.52": + version "0.0.52" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-0.0.52.tgz#cbd2f42778b50d6422a254ffaec05ad4ef3cb6c0" + dependencies: + "@ngtools/json-schema" "^1.1.0" + rxjs "^5.5.6" + +"@angular/animations@^5.2.0": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-5.2.5.tgz#3e72184321c4979305619c74902b8be92d76db70" + dependencies: + tslib "^1.7.1" + +"@angular/cli@1.6.8": + version "1.6.8" + resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.6.8.tgz#963b48086ee3e9584a136d3c45893260f516dd75" + dependencies: + "@angular-devkit/build-optimizer" "0.0.42" + "@angular-devkit/core" "0.0.29" + "@angular-devkit/schematics" "0.0.52" + "@ngtools/json-schema" "1.1.0" + "@ngtools/webpack" "1.9.8" + "@schematics/angular" "0.1.17" + autoprefixer "^7.2.3" + chalk "~2.2.0" + circular-dependency-plugin "^4.2.1" + common-tags "^1.3.1" + copy-webpack-plugin "^4.1.1" + core-object "^3.1.0" + css-loader "^0.28.1" + cssnano "^3.10.0" + denodeify "^1.2.1" + ember-cli-string-utils "^1.0.0" + exports-loader "^0.6.3" + extract-text-webpack-plugin "^3.0.2" + file-loader "^1.1.5" + fs-extra "^4.0.0" + glob "^7.0.3" + html-webpack-plugin "^2.29.0" + istanbul-instrumenter-loader "^3.0.0" + karma-source-map-support "^1.2.0" + less "^2.7.2" + less-loader "^4.0.5" + license-webpack-plugin "^1.0.0" + loader-utils "1.1.0" + lodash "^4.11.1" + memory-fs "^0.4.1" + minimatch "^3.0.4" + node-modules-path "^1.0.0" + nopt "^4.0.1" + opn "~5.1.0" + portfinder "~1.0.12" + postcss-import "^11.0.0" + postcss-loader "^2.0.10" + postcss-url "^7.1.2" + raw-loader "^0.5.1" + resolve "^1.1.7" + rxjs "^5.5.6" + sass-loader "^6.0.6" + semver "^5.1.0" + silent-error "^1.0.0" + source-map-support "^0.4.1" + style-loader "^0.13.1" + stylus "^0.54.5" + stylus-loader "^3.0.1" + uglifyjs-webpack-plugin "^1.1.8" + url-loader "^0.6.2" + webpack "~3.10.0" + webpack-dev-middleware "~1.12.0" + webpack-dev-server "~2.11.0" + webpack-merge "^4.1.0" + webpack-sources "^1.0.0" + webpack-subresource-integrity "^1.0.1" + optionalDependencies: + node-sass "^4.7.2" + +"@angular/common@^5.2.0": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-5.2.5.tgz#08dd636fa46077d047066b13a1aae494066f6c55" + dependencies: + tslib "^1.7.1" + +"@angular/compiler-cli@^5.2.0": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-5.2.5.tgz#b1988bb2c0a956e7fc163acf8c7d794a07a88d08" + dependencies: + chokidar "^1.4.2" + minimist "^1.2.0" + reflect-metadata "^0.1.2" + tsickle "^0.26.0" + +"@angular/compiler@^5.2.0": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-5.2.5.tgz#5e3b511906048a579fcd007aba72911472e5aa28" + dependencies: + tslib "^1.7.1" + +"@angular/core@^5.2.0": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-5.2.5.tgz#24f9cd75c5b2728f2ddd1869777590ea7177bca9" + dependencies: + tslib "^1.7.1" + +"@angular/forms@^5.2.0": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-5.2.5.tgz#2ad7a420c6ef6cd87a34071c5319ec83f7ed56aa" + dependencies: + tslib "^1.7.1" + +"@angular/language-service@^5.2.0": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-5.2.5.tgz#7c3021f347dd4b28ae0a867700894f3de4930f48" + +"@angular/platform-browser-dynamic@^5.2.0": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-5.2.5.tgz#b89df410bd953e2a6843325f9ac3c09a10eadaf0" + dependencies: + tslib "^1.7.1" + +"@angular/platform-browser@^5.2.0": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-5.2.5.tgz#eae4af2b742fb901d84d6367cd99f9e88102151f" + dependencies: + tslib "^1.7.1" + +"@angular/router@^5.2.0": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-5.2.5.tgz#f8f220d5fb85fc10d60fe606b0f2a64732265142" + dependencies: + tslib "^1.7.1" + +"@ngtools/json-schema@1.1.0", "@ngtools/json-schema@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@ngtools/json-schema/-/json-schema-1.1.0.tgz#c3a0c544d62392acc2813a42c8a0dc6f58f86922" + +"@ngtools/webpack@1.9.8": + version "1.9.8" + resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.9.8.tgz#c9d1d1884b86ea9a5fbbd6f65080dc3ac37c8d97" + dependencies: + chalk "~2.2.0" + enhanced-resolve "^3.1.0" + loader-utils "^1.0.2" + magic-string "^0.22.3" + semver "^5.3.0" + source-map "^0.5.6" + tree-kill "^1.0.0" + webpack-sources "^1.1.0" + +"@schematics/angular@0.1.17": + version "0.1.17" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-0.1.17.tgz#084a7cbe2de6f94a856bd08d95c9d35ef8905e2b" + dependencies: + typescript "~2.6.2" + +"@types/node@~6.0.60": + version "6.0.101" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.101.tgz#0c5911cfb434af4a51c0a499931fe6423207d921" + +"@types/strip-bom@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" + +"@types/strip-json-comments@0.0.30": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + +accepts@~1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" + dependencies: + mime-types "~2.1.16" + negotiator "0.6.1" + +ace-builds@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.3.1.tgz#c7f9d7a657e7d9c630acd78f1dc2fa1e0e2a84f6" + +acorn-dynamic-import@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" + dependencies: + acorn "^4.0.3" + +acorn@^4.0.3: + version "4.0.13" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" + +acorn@^5.0.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102" + +ajv-keywords@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" + +ajv-keywords@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.1.0.tgz#ac2b27939c543e95d2c06e7f7f5c27be4aa543be" + +ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + +ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5, ajv@~5.5.1: + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + +ajv@^6.1.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.1.1.tgz#978d597fbc2b7d0e5a5c3ddeb149a682f2abfa0e" + dependencies: + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-html@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-styles@^3.1.0, ansi-styles@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" + dependencies: + color-convert "^1.9.0" + +anymatch@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" + dependencies: + micromatch "^2.1.5" + normalize-path "^2.0.0" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +aproba@^1.0.3, aproba@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + +are-we-there-yet@~1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + dependencies: + arr-flatten "^1.0.1" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + +arr-flatten@^1.0.1, arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + +array-flatten@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296" + +array-includes@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + +arrify@^1.0.0, arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + +asn1.js@^4.0.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + +assert@^1.1.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" + dependencies: + util "0.10.3" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + +async-foreach@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" + +async@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +async@^2.1.2, async@^2.1.5, async@^2.4.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" + dependencies: + lodash "^4.14.0" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +atob@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" + +autoprefixer@^6.3.1: + version "6.7.7" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" + dependencies: + browserslist "^1.7.6" + caniuse-db "^1.0.30000634" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^5.2.16" + postcss-value-parser "^3.2.3" + +autoprefixer@^7.2.3: + version "7.2.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.2.6.tgz#256672f86f7c735da849c4f07d008abb056067dc" + dependencies: + browserslist "^2.11.3" + caniuse-lite "^1.0.30000805" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^6.0.17" + postcss-value-parser "^3.2.3" + +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + +aws4@^1.2.1, aws4@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + +babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-generator@^6.18.0: + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.17.4" + source-map "^0.5.7" + trim-right "^1.0.1" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + dependencies: + babel-runtime "^6.22.0" + +babel-runtime@^6.22.0, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-template@^6.16.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + dependencies: + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" + +babel-traverse@^6.18.0, babel-traverse@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" + +babel-types@^6.18.0, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + +balanced-match@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +base64-js@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + +bcrypt-pbkdf@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + dependencies: + tweetnacl "^0.14.3" + +big.js@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" + +binary-extensions@^1.0.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + dependencies: + inherits "~2.0.0" + +bluebird@^3.4.7, bluebird@^3.5.0: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + +body-parser@1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.1" + http-errors "~1.6.2" + iconv-lite "0.4.19" + on-finished "~2.3.0" + qs "6.5.1" + raw-body "2.3.2" + type-is "~1.6.15" + +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + +boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + +boom@4.x.x: + version "4.3.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" + dependencies: + hoek "4.x.x" + +boom@5.x.x: + version "5.2.0" + resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" + dependencies: + hoek "4.x.x" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/brace/-/brace-0.10.0.tgz#edef4eb9b0928ba1ee5f717ffc157749a6dd5d76" + dependencies: + w3c-blob "0.0.1" + +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + +braces@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.0.tgz#a46941cb5fb492156b3d6a656e06c35364e3e66e" + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + define-property "^1.0.0" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f" + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a" + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd" + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + +browserify-rsa@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + dependencies: + bn.js "^4.1.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + dependencies: + bn.js "^4.1.1" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.2" + elliptic "^6.0.0" + inherits "^2.0.1" + parse-asn1 "^5.0.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + dependencies: + pako "~1.0.5" + +browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: + version "1.7.7" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" + dependencies: + caniuse-db "^1.0.30000639" + electron-to-chromium "^1.2.7" + +browserslist@^2.11.3: + version "2.11.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.11.3.tgz#fe36167aed1bbcde4827ebfe71347a2cc70b99b2" + dependencies: + caniuse-lite "^1.0.30000792" + electron-to-chromium "^1.3.30" + +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + +buffer@^4.3.0: + version "4.9.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + +cacache@^10.0.1: + version "10.0.2" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.2.tgz#105a93a162bbedf3a25da42e1939ed99ffb145f8" + dependencies: + bluebird "^3.5.0" + chownr "^1.0.1" + glob "^7.1.2" + graceful-fs "^4.1.11" + lru-cache "^4.1.1" + mississippi "^1.3.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.1" + ssri "^5.0.0" + unique-filename "^1.1.0" + y18n "^3.2.1" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +camel-case@3.0.x: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" + dependencies: + no-case "^2.2.0" + upper-case "^1.1.1" + +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + +caniuse-api@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" + dependencies: + browserslist "^1.3.6" + caniuse-db "^1.0.30000529" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: + version "1.0.30000808" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000808.tgz#30dfd83009d5704f02dffb37725068ed12a366bb" + +caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805: + version "1.0.30000808" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000808.tgz#7d759b5518529ea08b6705a19e70dbf401628ffc" + +caseless@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +chalk@^1.1.1, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.3.0, chalk@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796" + dependencies: + ansi-styles "^3.2.0" + escape-string-regexp "^1.0.5" + supports-color "^5.2.0" + +chalk@~2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.2.2.tgz#4403f5cf18f35c05f51fbdf152bf588f956cf7cb" + dependencies: + ansi-styles "^3.1.0" + escape-string-regexp "^1.0.5" + supports-color "^4.0.0" + +chokidar@^1.4.2, chokidar@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + +chokidar@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.2.tgz#4dc65139eeb2714977735b6a35d06e97b494dfd7" + dependencies: + anymatch "^2.0.0" + async-each "^1.0.0" + braces "^2.3.0" + glob-parent "^3.1.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^2.1.1" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + upath "^1.0.0" + optionalDependencies: + fsevents "^1.0.0" + +chownr@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +circular-dependency-plugin@^4.2.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-4.4.0.tgz#f8a1a746a3f6c8e57f4dae9b54d991cd2a582f5d" + +clap@^1.0.9: + version "1.2.3" + resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" + dependencies: + chalk "^1.1.3" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +clean-css@4.1.x: + version "4.1.9" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.9.tgz#35cee8ae7687a49b98034f70de00c4edd3826301" + dependencies: + source-map "0.5.x" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +clone-deep@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.3.0.tgz#348c61ae9cdbe0edfe053d91ff4cc521d790ede8" + dependencies: + for-own "^1.0.0" + is-plain-object "^2.0.1" + kind-of "^3.2.2" + shallow-clone "^0.1.2" + +clone@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" + +clone@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +coa@~1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd" + dependencies: + q "^1.1.2" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.3.0, color-convert@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" + dependencies: + color-name "^1.1.1" + +color-name@^1.0.0, color-name@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + +color-string@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" + dependencies: + color-name "^1.0.0" + +color@^0.11.0: + version "0.11.4" + resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" + dependencies: + clone "^1.0.2" + color-convert "^1.3.0" + color-string "^0.3.0" + +colormin@^1.0.5: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" + dependencies: + color "^0.11.0" + css-color-names "0.0.4" + has "^1.0.1" + +colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + +combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" + dependencies: + delayed-stream "~1.0.0" + +commander@2.14.x, commander@^2.9.0, commander@~2.14.1: + version "2.14.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" + +commander@~2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" + +common-tags@^1.3.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.7.2.tgz#24d9768c63d253a56ecff93845b44b4df1d52771" + dependencies: + babel-runtime "^6.26.0" + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + +component-emitter@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + +compressible@~2.0.11: + version "2.0.12" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66" + dependencies: + mime-db ">= 1.30.0 < 2" + +compression@^1.5.2: + version "1.7.1" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.1.tgz#eff2603efc2e22cf86f35d2eb93589f9875373db" + dependencies: + accepts "~1.3.4" + bytes "3.0.0" + compressible "~2.0.11" + debug "2.6.9" + on-headers "~1.0.1" + safe-buffer "5.1.1" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +connect-history-api-fallback@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a" + +console-browserify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" + dependencies: + date-now "^0.1.4" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + +convert-source-map@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + +copy-concurrently@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" + dependencies: + aproba "^1.1.1" + fs-write-stream-atomic "^1.0.8" + iferr "^0.1.5" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.0" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + +copy-webpack-plugin@^4.1.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.4.1.tgz#1e8c366211db6dc2ddee40e5a3e4fc661dd149e8" + dependencies: + cacache "^10.0.1" + find-cache-dir "^1.0.0" + globby "^7.1.1" + is-glob "^4.0.0" + loader-utils "^0.2.15" + minimatch "^3.0.4" + p-limit "^1.0.0" + serialize-javascript "^1.4.0" + +core-js@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" + +core-js@^2.4.0, core-js@^2.4.1: + version "2.5.3" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" + +core-object@^3.1.0: + version "3.1.5" + resolved "https://registry.yarnpkg.com/core-object/-/core-object-3.1.5.tgz#fa627b87502adc98045e44678e9a8ec3b9c0d2a9" + dependencies: + chalk "^2.0.0" + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.2.2.tgz#6173cebd56fac042c1f4390edf7af6c07c7cb892" + dependencies: + is-directory "^0.3.1" + js-yaml "^3.4.3" + minimist "^1.2.0" + object-assign "^4.1.0" + os-homedir "^1.0.1" + parse-json "^2.2.0" + require-from-string "^1.1.0" + +create-ecdh@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d" + dependencies: + bn.js "^4.1.0" + elliptic "^6.0.0" + +create-hash@^1.1.0, create-hash@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + ripemd160 "^2.0.0" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: + version "1.1.6" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06" + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" + +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + +cryptiles@3.x.x: + version "3.1.2" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" + dependencies: + boom "5.x.x" + +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +css-color-names@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + +css-loader@^0.28.1: + version "0.28.9" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.9.tgz#68064b85f4e271d7ce4c48a58300928e535d1c95" + dependencies: + babel-code-frame "^6.26.0" + css-selector-tokenizer "^0.7.0" + cssnano "^3.10.0" + icss-utils "^2.1.0" + loader-utils "^1.0.2" + lodash.camelcase "^4.3.0" + object-assign "^4.1.1" + postcss "^5.0.6" + postcss-modules-extract-imports "^1.2.0" + postcss-modules-local-by-default "^1.2.0" + postcss-modules-scope "^1.1.0" + postcss-modules-values "^1.3.0" + postcss-value-parser "^3.3.0" + source-list-map "^2.0.0" + +css-parse@1.7.x: + version "1.7.0" + resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-1.7.0.tgz#321f6cf73782a6ff751111390fc05e2c657d8c9b" + +css-select@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + +css-selector-tokenizer@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" + dependencies: + cssesc "^0.1.0" + fastparse "^1.1.1" + regexpu-core "^1.0.0" + +css-what@2.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" + +cssesc@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" + +cssnano@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" + dependencies: + autoprefixer "^6.3.1" + decamelize "^1.1.2" + defined "^1.0.0" + has "^1.0.1" + object-assign "^4.0.1" + postcss "^5.0.14" + postcss-calc "^5.2.0" + postcss-colormin "^2.1.8" + postcss-convert-values "^2.3.4" + postcss-discard-comments "^2.0.4" + postcss-discard-duplicates "^2.0.1" + postcss-discard-empty "^2.0.1" + postcss-discard-overridden "^0.1.1" + postcss-discard-unused "^2.2.1" + postcss-filter-plugins "^2.0.0" + postcss-merge-idents "^2.1.5" + postcss-merge-longhand "^2.0.1" + postcss-merge-rules "^2.0.3" + postcss-minify-font-values "^1.0.2" + postcss-minify-gradients "^1.0.1" + postcss-minify-params "^1.0.4" + postcss-minify-selectors "^2.0.4" + postcss-normalize-charset "^1.1.0" + postcss-normalize-url "^3.0.7" + postcss-ordered-values "^2.1.0" + postcss-reduce-idents "^2.2.2" + postcss-reduce-initial "^1.0.0" + postcss-reduce-transforms "^1.0.3" + postcss-svgo "^2.1.1" + postcss-unique-selectors "^2.0.2" + postcss-value-parser "^3.2.3" + postcss-zindex "^2.0.1" + +csso@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" + dependencies: + clap "^1.0.9" + source-map "^0.5.3" + +cuint@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" + +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + dependencies: + array-find-index "^1.0.1" + +cyclist@~0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" + +d@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" + dependencies: + es5-ext "^0.10.9" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +date-now@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" + +debug@*, debug@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + +deep-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + +deep-extend@~0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" + +define-properties@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + dependencies: + foreach "^2.0.5" + object-keys "^1.0.8" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + dependencies: + is-descriptor "^1.0.0" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + +del@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" + dependencies: + globby "^6.1.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + p-map "^1.1.1" + pify "^3.0.0" + rimraf "^2.2.8" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +denodeify@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" + +depd@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" + +depd@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + +des.js@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + +detect-node@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" + +diff@^3.1.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" + +diffie-hellman@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +dir-glob@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" + dependencies: + arrify "^1.0.1" + path-type "^3.0.0" + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + +dns-packet@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + dependencies: + buffer-indexof "^1.0.0" + +dom-converter@~0.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.1.4.tgz#a45ef5727b890c9bffe6d7c876e7b19cb0e17f3b" + dependencies: + utila "~0.3" + +dom-serializer@0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" + dependencies: + domelementtype "~1.1.1" + entities "~1.1.1" + +domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + +domelementtype@1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" + +domelementtype@~1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" + +domhandler@2.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594" + dependencies: + domelementtype "1" + +domutils@1.1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" + dependencies: + domelementtype "1" + +domutils@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + dependencies: + dom-serializer "0" + domelementtype "1" + +duplexify@^3.4.2, duplexify@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.3.tgz#8b5818800df92fd0125b27ab896491912858243e" + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +ejs@^2.5.7: + version "2.5.7" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" + +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.30: + version "1.3.33" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.33.tgz#bf00703d62a7c65238136578c352d6c5c042a545" + +elliptic@^6.0.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + +ember-cli-string-utils@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ember-cli-string-utils/-/ember-cli-string-utils-1.1.0.tgz#39b677fc2805f55173735376fcef278eaa4452a1" + +emojis-list@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + +encodeurl@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + dependencies: + once "^1.4.0" + +enhanced-resolve@^3.1.0, enhanced-resolve@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.4.0" + object-assign "^4.0.1" + tapable "^0.2.7" + +entities@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + +errno@^0.1.1, errno@^0.1.3, errno@^0.1.4: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" + dependencies: + prr "~1.0.1" + +error-ex@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.7.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.1" + has "^1.0.1" + is-callable "^1.1.3" + is-regex "^1.0.4" + +es-to-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" + dependencies: + is-callable "^1.1.1" + is-date-object "^1.0.1" + is-symbol "^1.0.1" + +es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: + version "0.10.38" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.38.tgz#fa7d40d65bbc9bb8a67e1d3f9cc656a00530eed3" + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.1" + +es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-map@^0.1.3: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-set "~0.1.5" + es6-symbol "~3.1.1" + event-emitter "~0.3.5" + +es6-set@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-symbol "3.1.1" + event-emitter "~0.3.5" + +es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + dependencies: + d "1" + es5-ext "~0.10.14" + +es6-weak-map@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" + dependencies: + d "1" + es5-ext "^0.10.14" + es6-iterator "^2.0.1" + es6-symbol "^3.1.1" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +escope@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" + dependencies: + es6-map "^0.1.3" + es6-weak-map "^2.0.1" + esrecurse "^4.1.0" + estraverse "^4.1.1" + +esprima@^2.6.0: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + +esprima@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + +esrecurse@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + dependencies: + estraverse "^4.1.0" + object-assign "^4.0.1" + +estraverse@^4.1.0, estraverse@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + +event-emitter@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + dependencies: + d "1" + es5-ext "~0.10.14" + +eventemitter3@1.x.x: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" + +events@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + +eventsource@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232" + dependencies: + original ">=0.0.5" + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + dependencies: + is-posix-bracket "^0.1.0" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" + +exports-loader@^0.6.3: + version "0.6.4" + resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-0.6.4.tgz#d70fc6121975b35fc12830cf52754be2740fc886" + dependencies: + loader-utils "^1.0.2" + source-map "0.5.x" + +express@^4.16.2: + version "4.16.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" + dependencies: + accepts "~1.3.4" + array-flatten "1.1.1" + body-parser "1.18.2" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.1" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.0" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.2" + qs "6.5.1" + range-parser "~1.2.0" + safe-buffer "5.1.1" + send "0.16.1" + serve-static "1.13.1" + setprototypeof "1.1.0" + statuses "~1.3.1" + type-is "~1.6.15" + utils-merge "1.0.1" + vary "~1.1.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.0, extend@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + dependencies: + is-extglob "^1.0.0" + +extglob@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extract-text-webpack-plugin@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz#5f043eaa02f9750a9258b78c0a6e0dc1408fb2f7" + dependencies: + async "^2.4.1" + loader-utils "^1.1.0" + schema-utils "^0.3.0" + webpack-sources "^1.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + +fast-deep-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + +fastparse@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" + +faye-websocket@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + dependencies: + websocket-driver ">=0.5.1" + +faye-websocket@~0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" + dependencies: + websocket-driver ">=0.5.1" + +file-loader@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.6.tgz#7b9a8f2c58f00a77fddf49e940f7ac978a3ea0e8" + dependencies: + loader-utils "^1.0.2" + schema-utils "^0.3.0" + +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + +fill-range@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^1.1.3" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +finalhandler@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" + dependencies: + debug "2.6.9" + encodeurl "~1.0.1" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.3.1" + unpipe "~1.0.0" + +find-cache-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" + dependencies: + commondir "^1.0.1" + make-dir "^1.0.0" + pkg-dir "^2.0.0" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + +flatten@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" + +flush-write-stream@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.4" + +for-in@^0.1.3: + version "0.1.8" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" + +for-in@^1.0.1, for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + dependencies: + for-in "^1.0.1" + +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + dependencies: + for-in "^1.0.1" + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +form-data@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + dependencies: + asynckit "^0.4.0" + combined-stream "1.0.6" + mime-types "^2.1.12" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + +from2@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + +fs-extra@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-write-stream-atomic@^1.0.8: + version "1.0.10" + resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" + dependencies: + graceful-fs "^4.1.2" + iferr "^0.1.5" + imurmurhash "^0.1.4" + readable-stream "1 || 2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +fsevents@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" + dependencies: + nan "^2.3.0" + node-pre-gyp "^0.6.39" + +fstream-ignore@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" + dependencies: + fstream "^1.0.0" + inherits "2" + minimatch "^3.0.0" + +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +function-bind@^1.0.2, function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +gaze@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.2.tgz#847224677adb8870d679257ed3388fdb61e40105" + dependencies: + globule "^1.0.0" + +generate-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + dependencies: + is-property "^1.0.0" + +get-caller-file@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob@7.0.x: + version "7.0.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.2" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2, glob@~7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +globby@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" + dependencies: + array-union "^1.0.1" + dir-glob "^2.0.0" + glob "^7.1.2" + ignore "^3.3.5" + pify "^3.0.0" + slash "^1.0.0" + +globule@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09" + dependencies: + glob "~7.1.1" + lodash "~4.17.4" + minimatch "~3.0.2" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +handle-thing@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" + +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + +har-validator@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" + dependencies: + chalk "^1.1.1" + commander "^2.9.0" + is-my-json-valid "^2.12.4" + pinkie-promise "^2.0.0" + +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + +har-validator@~5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + dependencies: + ajv "^5.1.0" + har-schema "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + +hash-base@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" + dependencies: + inherits "^2.0.1" + +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.0" + +hawk@3.1.3, hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + +hawk@~6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" + dependencies: + boom "4.x.x" + cryptiles "3.x.x" + hoek "4.x.x" + sntp "2.x.x" + +he@1.1.x: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + +hoek@4.x.x: + version "4.2.1" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" + +homedir-polyfill@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" + dependencies: + parse-passwd "^1.0.0" + +hosted-git-info@^2.1.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-comment-regex@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" + +html-entities@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" + +html-minifier@^3.2.3: + version "3.5.9" + resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.9.tgz#74424014b872598d4bb0e20ac420926ec61024b6" + dependencies: + camel-case "3.0.x" + clean-css "4.1.x" + commander "2.14.x" + he "1.1.x" + ncname "1.0.x" + param-case "2.1.x" + relateurl "0.2.x" + uglify-js "3.3.x" + +html-webpack-plugin@^2.29.0: + version "2.30.1" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-2.30.1.tgz#7f9c421b7ea91ec460f56527d78df484ee7537d5" + dependencies: + bluebird "^3.4.7" + html-minifier "^3.2.3" + loader-utils "^0.2.16" + lodash "^4.17.3" + pretty-error "^2.0.2" + toposort "^1.0.0" + +htmlparser2@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" + dependencies: + domelementtype "1" + domhandler "2.1" + domutils "1.1" + readable-stream "1.0" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + +http-errors@1.6.2, http-errors@~1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" + dependencies: + depd "1.1.1" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + +http-parser-js@>=0.4.0: + version "0.4.10" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" + +http-proxy-middleware@~0.17.4: + version "0.17.4" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833" + dependencies: + http-proxy "^1.16.2" + is-glob "^3.1.0" + lodash "^4.17.2" + micromatch "^2.3.11" + +http-proxy@^1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" + dependencies: + eventemitter3 "1.x.x" + requires-port "1.x.x" + +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + +iconv-lite@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" + +icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + +icss-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962" + dependencies: + postcss "^6.0.1" + +ieee754@^1.1.4: + version "1.1.8" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + +iferr@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" + +ignore@^3.3.5: + version "3.3.7" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" + +image-size@~0.5.0: + version "0.5.5" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" + +import-local@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc" + dependencies: + pkg-dir "^2.0.0" + resolve-cwd "^2.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +in-publish@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" + +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + dependencies: + repeating "^2.0.0" + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + +indexof@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + +internal-ip@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" + dependencies: + meow "^3.3.0" + +interpret@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" + +invariant@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" + dependencies: + loose-envify "^1.0.0" + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + +ip@^1.1.0, ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + +ipaddr.js@1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.0.2, is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-callable@^1.1.1, is-callable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + dependencies: + is-extglob "^2.1.1" + +is-my-ip-valid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" + +is-my-json-valid@^2.12.4: + version "2.17.2" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c" + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + is-my-ip-valid "^1.0.0" + jsonpointer "^4.0.0" + xtend "^4.0.0" + +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-odd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-1.0.0.tgz#3b8a932eb028b3775c39bb09e91767accdb69088" + dependencies: + is-number "^3.0.0" + +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" + dependencies: + path-is-inside "^1.0.1" + +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + dependencies: + isobject "^3.0.1" + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + +is-property@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + dependencies: + has "^1.0.1" + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +is-svg@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" + dependencies: + html-comment-regex "^1.1.0" + +is-symbol@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +istanbul-instrumenter-loader@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.0.tgz#9f553923b22360bac95e617aaba01add1f7db0b2" + dependencies: + convert-source-map "^1.5.0" + istanbul-lib-instrument "^1.7.3" + loader-utils "^1.1.0" + schema-utils "^0.3.0" + +istanbul-lib-coverage@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.2.tgz#4113c8ff6b7a40a1ef7350b01016331f63afde14" + +istanbul-lib-instrument@^1.7.3: + version "1.9.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.2.tgz#84905bf47f7e0b401d6b840da7bad67086b4aab6" + dependencies: + babel-generator "^6.18.0" + babel-template "^6.16.0" + babel-traverse "^6.18.0" + babel-types "^6.18.0" + babylon "^6.18.0" + istanbul-lib-coverage "^1.1.2" + semver "^5.3.0" + +js-base64@^2.1.8, js-base64@^2.1.9: + version "2.4.3" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582" + +js-tokens@^3.0.0, js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +js-yaml@^3.4.3: + version "3.10.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@~3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" + dependencies: + argparse "^1.0.7" + esprima "^2.6.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + +json-loader@^0.5.4: + version "0.5.7" + resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json3@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" + +json5@^0.5.0, json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsonpointer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +karma-source-map-support@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/karma-source-map-support/-/karma-source-map-support-1.2.0.tgz#1bf81e7bb4b089627ab352ec4179e117c406a540" + dependencies: + source-map-support "^0.4.1" + +killable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b" + +kind-of@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5" + dependencies: + is-buffer "^1.0.2" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0, kind-of@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0, kind-of@^5.0.2: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + +lazy-cache@^0.2.3: + version "0.2.7" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + +lazy-cache@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" + dependencies: + set-getter "^0.1.0" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + dependencies: + invert-kv "^1.0.0" + +less-loader@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-4.0.5.tgz#ae155a7406cac6acd293d785587fcff0f478c4dd" + dependencies: + clone "^2.1.1" + loader-utils "^1.1.0" + pify "^2.3.0" + +less@^2.7.2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/less/-/less-2.7.3.tgz#cc1260f51c900a9ec0d91fb6998139e02507b63b" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + mime "^1.2.11" + mkdirp "^0.5.0" + promise "^7.1.1" + request "2.81.0" + source-map "^0.5.3" + +license-webpack-plugin@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-1.1.1.tgz#76b2cedccc78f139fd7877e576f756cfc141b8c2" + dependencies: + ejs "^2.5.7" + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +loader-runner@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" + +loader-utils@1.1.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" + dependencies: + big.js "^3.1.3" + emojis-list "^2.0.0" + json5 "^0.5.0" + +loader-utils@^0.2.15, loader-utils@^0.2.16: + version "0.2.17" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" + dependencies: + big.js "^3.1.3" + emojis-list "^2.0.0" + json5 "^0.5.0" + object-assign "^4.0.1" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash.assign@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + +lodash.clonedeep@^4.3.2, lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + +lodash.endswith@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09" + +lodash.isfunction@^3.0.8: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + +lodash.mergewith@^4.6.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" + +lodash.startswith@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.startswith/-/lodash.startswith-4.2.1.tgz#c598c4adce188a27e53145731cdc6c0e7177600c" + +lodash.tail@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + +lodash@^4.0.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@~4.17.4: + version "4.17.5" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" + +loglevel@^1.4.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" + +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + +loose-envify@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +lower-case@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + +lru-cache@^4.0.1, lru-cache@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +macaddress@^0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" + +magic-string@^0.22.3: + version "0.22.4" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.4.tgz#31039b4e40366395618c1d6cf8193c53917475ff" + dependencies: + vlq "^0.2.1" + +make-dir@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" + dependencies: + pify "^3.0.0" + +make-error@^1.1.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.4.tgz#19978ed575f9e9545d2ff8c13e33b5d18a67d535" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + dependencies: + object-visit "^1.0.0" + +math-expression-evaluator@^1.2.14: + version "1.2.17" + resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" + +md5.js@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + +mem@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + dependencies: + mimic-fn "^1.0.0" + +memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +meow@^3.3.0, meow@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + +micromatch@^2.1.5, micromatch@^2.3.11: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +micromatch@^3.1.4: + version "3.1.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.5.tgz#d05e168c206472dfbca985bfef4f57797b4cd4ba" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.0" + define-property "^1.0.0" + extend-shallow "^2.0.1" + extglob "^2.0.2" + fragment-cache "^0.2.1" + kind-of "^6.0.0" + nanomatch "^1.2.5" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +"mime-db@>= 1.30.0 < 2": + version "1.32.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.32.0.tgz#485b3848b01a3cda5f968b4882c0771e58e09414" + +mime-db@~1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" + +mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: + version "2.1.17" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" + dependencies: + mime-db "~1.30.0" + +mime@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + +mime@^1.2.11, mime@^1.4.1, mime@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + +minimalistic-assert@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.1.3, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +mississippi@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-1.3.1.tgz#2a8bb465e86550ac8b36a7b6f45599171d78671e" + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^1.0.0" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + +mixin-deep@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mixin-object@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" + dependencies: + for-in "^0.1.3" + is-extendable "^0.1.1" + +mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +move-concurrently@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" + dependencies: + aproba "^1.1.1" + copy-concurrently "^1.0.0" + fs-write-stream-atomic "^1.0.8" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.3" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + +multicast-dns@^6.0.1: + version "6.2.3" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" + dependencies: + dns-packet "^1.3.1" + thunky "^1.0.2" + +nan@^2.3.0, nan@^2.3.2: + version "2.8.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" + +nanomatch@^1.2.5: + version "1.2.7" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.7.tgz#53cd4aa109ff68b7f869591fdc9d10daeeea3e79" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^1.0.0" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + is-odd "^1.0.0" + kind-of "^5.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +ncname@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ncname/-/ncname-1.0.0.tgz#5b57ad18b1ca092864ef62b0b1ed8194f383b71c" + dependencies: + xml-char-classes "^1.0.0" + +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + +ng2-ace-editor@0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/ng2-ace-editor/-/ng2-ace-editor-0.3.4.tgz#32a53e1e6e63c3cd3689858e26a0cdfcdefb41d5" + dependencies: + ace-builds "^1.2.9" + brace "^0.10.0" + +ngx-bootstrap@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ngx-bootstrap/-/ngx-bootstrap-2.0.2.tgz#6862b2ee4764bb6cfd34c66528873d0d050c2faa" + +no-case@^2.2.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + dependencies: + lower-case "^1.1.1" + +node-forge@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300" + +node-gyp@^3.3.1: + version "3.6.2" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60" + dependencies: + fstream "^1.0.0" + glob "^7.0.3" + graceful-fs "^4.1.2" + minimatch "^3.0.2" + mkdirp "^0.5.0" + nopt "2 || 3" + npmlog "0 || 1 || 2 || 3 || 4" + osenv "0" + request "2" + rimraf "2" + semver "~5.3.0" + tar "^2.0.0" + which "1" + +node-libs-browser@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df" + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^1.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.0" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.10.3" + vm-browserify "0.0.4" + +node-modules-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/node-modules-path/-/node-modules-path-1.0.1.tgz#40096b08ce7ad0ea14680863af449c7c75a5d1c8" + +node-pre-gyp@^0.6.39: + version "0.6.39" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" + dependencies: + detect-libc "^1.0.2" + hawk "3.1.3" + mkdirp "^0.5.1" + nopt "^4.0.1" + npmlog "^4.0.2" + rc "^1.1.7" + request "2.81.0" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^2.2.1" + tar-pack "^3.4.0" + +node-sass@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e" + dependencies: + async-foreach "^0.1.3" + chalk "^1.1.1" + cross-spawn "^3.0.0" + gaze "^1.0.0" + get-stdin "^4.0.1" + glob "^7.0.3" + in-publish "^2.0.0" + lodash.assign "^4.2.0" + lodash.clonedeep "^4.3.2" + lodash.mergewith "^4.6.0" + meow "^3.7.0" + mkdirp "^0.5.1" + nan "^2.3.2" + node-gyp "^3.3.1" + npmlog "^4.0.0" + request "~2.79.0" + sass-graph "^2.2.4" + stdout-stream "^1.4.0" + "true-case-path" "^1.0.2" + +"nopt@2 || 3": + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + dependencies: + abbrev "1" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + +normalize-url@^1.4.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +nth-check@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" + dependencies: + boolbase "~1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +oai-ts-commands@0.2.18: + version "0.2.18" + resolved "https://registry.yarnpkg.com/oai-ts-commands/-/oai-ts-commands-0.2.18.tgz#dd0ccc1c4312405fce063bfaaefd0398201e22fd" + dependencies: + core-js "2.4.1" + oai-ts-core "0.2.9" + +oai-ts-core@0.2.9: + version "0.2.9" + resolved "https://registry.yarnpkg.com/oai-ts-core/-/oai-ts-core-0.2.9.tgz#29a3a8645bbaadd35a714bc7f05fdfec1bce5b4c" + dependencies: + core-js "2.4.1" + +oauth-sign@~0.8.1, oauth-sign@~0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-keys@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + dependencies: + isobject "^3.0.0" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + dependencies: + isobject "^3.0.1" + +obuf@^1.0.0, obuf@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.1.tgz#104124b6c602c6796881a042541d36db43a5264e" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" + +once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +opn@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" + dependencies: + is-wsl "^1.1.0" + +opn@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.1.0.tgz#72ce2306a17dbea58ff1041853352b4a8fc77519" + dependencies: + is-wsl "^1.1.0" + +original@>=0.0.5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b" + dependencies: + url-parse "1.0.x" + +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + +os-homedir@^1.0.0, os-homedir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + dependencies: + lcid "^1.0.0" + +os-locale@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + dependencies: + execa "^0.7.0" + lcid "^1.0.0" + mem "^1.1.0" + +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +osenv@0, osenv@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-limit@^1.0.0, p-limit@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" + dependencies: + p-try "^1.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +p-map@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + +pako@~1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" + +parallel-transform@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06" + dependencies: + cyclist "~0.2.2" + inherits "^2.0.3" + readable-stream "^2.1.5" + +param-case@2.1.x: + version "2.1.1" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" + dependencies: + no-case "^2.2.0" + +parse-asn1@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712" + dependencies: + asn1.js "^4.0.0" + browserify-aes "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + +parseurl@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + +path-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-is-inside@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + dependencies: + pify "^3.0.0" + +pbkdf2@^3.0.3: + version "3.0.14" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade" + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + +pify@^2.0.0, pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + dependencies: + find-up "^2.1.0" + +portfinder@^1.0.9, portfinder@~1.0.12: + version "1.0.13" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" + dependencies: + async "^1.5.2" + debug "^2.2.0" + mkdirp "0.5.x" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + +postcss-calc@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" + dependencies: + postcss "^5.0.2" + postcss-message-helpers "^2.0.0" + reduce-css-calc "^1.2.6" + +postcss-colormin@^2.1.8: + version "2.2.2" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b" + dependencies: + colormin "^1.0.5" + postcss "^5.0.13" + postcss-value-parser "^3.2.3" + +postcss-convert-values@^2.3.4: + version "2.6.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d" + dependencies: + postcss "^5.0.11" + postcss-value-parser "^3.1.2" + +postcss-discard-comments@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d" + dependencies: + postcss "^5.0.14" + +postcss-discard-duplicates@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932" + dependencies: + postcss "^5.0.4" + +postcss-discard-empty@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5" + dependencies: + postcss "^5.0.14" + +postcss-discard-overridden@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58" + dependencies: + postcss "^5.0.16" + +postcss-discard-unused@^2.2.1: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433" + dependencies: + postcss "^5.0.14" + uniqs "^2.0.0" + +postcss-filter-plugins@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c" + dependencies: + postcss "^5.0.4" + uniqid "^4.0.0" + +postcss-import@^11.0.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-11.1.0.tgz#55c9362c9192994ec68865d224419df1db2981f0" + dependencies: + postcss "^6.0.1" + postcss-value-parser "^3.2.3" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-load-config@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-1.2.0.tgz#539e9afc9ddc8620121ebf9d8c3673e0ce50d28a" + dependencies: + cosmiconfig "^2.1.0" + object-assign "^4.1.0" + postcss-load-options "^1.2.0" + postcss-load-plugins "^2.3.0" + +postcss-load-options@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-load-options/-/postcss-load-options-1.2.0.tgz#b098b1559ddac2df04bc0bb375f99a5cfe2b6d8c" + dependencies: + cosmiconfig "^2.1.0" + object-assign "^4.1.0" + +postcss-load-plugins@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz#745768116599aca2f009fad426b00175049d8d92" + dependencies: + cosmiconfig "^2.1.1" + object-assign "^4.1.0" + +postcss-loader@^2.0.10: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.0.tgz#038c2d6d59753fef4667827fd3ae03f5dc5e6a7a" + dependencies: + loader-utils "^1.1.0" + postcss "^6.0.0" + postcss-load-config "^1.2.0" + schema-utils "^0.4.0" + +postcss-merge-idents@^2.1.5: + version "2.1.7" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" + dependencies: + has "^1.0.1" + postcss "^5.0.10" + postcss-value-parser "^3.1.1" + +postcss-merge-longhand@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658" + dependencies: + postcss "^5.0.4" + +postcss-merge-rules@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721" + dependencies: + browserslist "^1.5.2" + caniuse-api "^1.5.2" + postcss "^5.0.4" + postcss-selector-parser "^2.2.2" + vendors "^1.0.0" + +postcss-message-helpers@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e" + +postcss-minify-font-values@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69" + dependencies: + object-assign "^4.0.1" + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-minify-gradients@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1" + dependencies: + postcss "^5.0.12" + postcss-value-parser "^3.3.0" + +postcss-minify-params@^1.0.4: + version "1.2.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.2" + postcss-value-parser "^3.0.2" + uniqs "^2.0.0" + +postcss-minify-selectors@^2.0.4: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf" + dependencies: + alphanum-sort "^1.0.2" + has "^1.0.1" + postcss "^5.0.14" + postcss-selector-parser "^2.0.0" + +postcss-modules-extract-imports@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85" + dependencies: + postcss "^6.0.1" + +postcss-modules-local-by-default@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-scope@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-values@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" + dependencies: + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" + +postcss-normalize-charset@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" + dependencies: + postcss "^5.0.5" + +postcss-normalize-url@^3.0.7: + version "3.0.8" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222" + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^1.4.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + +postcss-ordered-values@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.1" + +postcss-reduce-idents@^2.2.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-reduce-initial@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea" + dependencies: + postcss "^5.0.4" + +postcss-reduce-transforms@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1" + dependencies: + has "^1.0.1" + postcss "^5.0.8" + postcss-value-parser "^3.0.1" + +postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^2.1.1: + version "2.1.6" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" + dependencies: + is-svg "^2.0.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + svgo "^0.7.0" + +postcss-unique-selectors@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss-url@^7.1.2: + version "7.3.0" + resolved "https://registry.yarnpkg.com/postcss-url/-/postcss-url-7.3.0.tgz#cf2f45e06743cf43cfea25309f81cbc003dc783f" + dependencies: + mime "^1.4.1" + minimatch "^3.0.4" + mkdirp "^0.5.0" + postcss "^6.0.1" + xxhashjs "^0.2.1" + +postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" + +postcss-zindex@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" + dependencies: + has "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16: + version "5.2.18" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" + dependencies: + chalk "^1.1.3" + js-base64 "^2.1.9" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.17: + version "6.0.18" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.18.tgz#370f5f44d47f3a205f0eb2f6262bbf202df2a80e" + dependencies: + chalk "^2.3.1" + source-map "^0.6.1" + supports-color "^5.2.0" + +prepend-http@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + +pretty-error@^2.0.2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" + dependencies: + renderkid "^2.0.1" + utila "~0.4" + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + dependencies: + asap "~2.0.3" + +proxy-addr@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.5.2" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +public-encrypt@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6" + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + +pump@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb" + dependencies: + duplexify "^3.5.3" + inherits "^2.0.3" + pump "^2.0.0" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + +punycode@^1.2.4, punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + +qs@6.5.1, qs@~6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + +qs@~6.3.0: + version "6.3.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" + +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + +querystringify@0.0.x: + version "0.0.4" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c" + +querystringify@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" + +randomatic@^1.1.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.3.tgz#b96b7df587f01dd91726c418f30553b1418e3d62" + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +range-parser@^1.0.3, range-parser@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + +raw-body@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" + dependencies: + bytes "3.0.0" + http-errors "1.6.2" + iconv-lite "0.4.19" + unpipe "1.0.0" + +raw-loader@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" + +rc@^1.1.7: + version "1.2.5" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.5.tgz#275cd687f6e3b36cc756baa26dfee80a790301fd" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + dependencies: + pify "^2.3.0" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.4.tgz#c946c3f47fa7d8eabc0b6150f4a12f69a4574071" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + +readable-stream@1.0: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readdirp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" + dependencies: + graceful-fs "^4.1.2" + minimatch "^3.0.2" + readable-stream "^2.0.2" + set-immediate-shim "^1.0.1" + +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + +reduce-css-calc@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" + dependencies: + balanced-match "^0.4.2" + math-expression-evaluator "^1.2.14" + reduce-function-call "^1.0.1" + +reduce-function-call@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99" + dependencies: + balanced-match "^0.4.2" + +reflect-metadata@^0.1.2: + version "0.1.12" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.12.tgz#311bf0c6b63cd782f228a81abe146a2bfa9c56f2" + +regenerate@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + +regex-cache@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" + dependencies: + is-equal-shallow "^0.1.3" + +regex-not@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.0.tgz#42f83e39771622df826b02af176525d6a5f157f9" + dependencies: + extend-shallow "^2.0.1" + +regexpu-core@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + dependencies: + jsesc "~0.5.0" + +relateurl@0.2.x: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + +renderkid@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.1.tgz#898cabfc8bede4b7b91135a3ffd323e58c0db319" + dependencies: + css-select "^1.1.0" + dom-converter "~0.1" + htmlparser2 "~3.3.0" + strip-ansi "^3.0.0" + utila "~0.3" + +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + +repeat-string@^1.5.2, repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +request@2: + version "2.83.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + hawk "~6.0.2" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + stringstream "~0.0.5" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + +request@2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +request@~2.79.0: + version "2.79.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.11.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~2.0.6" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + qs "~6.3.0" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "~0.4.1" + uuid "^3.0.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + +require-from-string@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + +requires-port@1.0.x, requires-port@1.x.x, requires-port@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + dependencies: + resolve-from "^3.0.0" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + +resolve@^1.1.7: + version "1.5.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" + dependencies: + path-parse "^1.0.5" + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + dependencies: + align-text "^0.1.1" + +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + dependencies: + glob "^7.0.5" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" + dependencies: + hash-base "^2.0.0" + inherits "^2.0.1" + +run-queue@^1.0.0, run-queue@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" + dependencies: + aproba "^1.1.1" + +rxjs@^5.5.6: + version "5.5.6" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.6.tgz#e31fb96d6fd2ff1fd84bcea8ae9c02d007179c02" + dependencies: + symbol-observable "1.0.1" + +safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +sass-graph@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" + dependencies: + glob "^7.0.0" + lodash "^4.0.0" + scss-tokenizer "^0.2.3" + yargs "^7.0.0" + +sass-loader@^6.0.6: + version "6.0.6" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.6.tgz#e9d5e6c1f155faa32a4b26d7a9b7107c225e40f9" + dependencies: + async "^2.1.5" + clone-deep "^0.3.0" + loader-utils "^1.0.1" + lodash.tail "^4.1.1" + pify "^3.0.0" + +sax@0.5.x: + version "0.5.8" + resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" + +sax@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + +schema-utils@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" + dependencies: + ajv "^5.0.0" + +schema-utils@^0.4.0, schema-utils@^0.4.2: + version "0.4.5" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e" + dependencies: + ajv "^6.1.0" + ajv-keywords "^3.1.0" + +scss-tokenizer@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" + dependencies: + js-base64 "^2.1.8" + source-map "^0.4.2" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + +selfsigned@^1.9.1: + version "1.10.2" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.2.tgz#b4449580d99929b65b10a48389301a6592088758" + dependencies: + node-forge "0.7.1" + +"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" + +semver@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + +send@0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" + dependencies: + debug "2.6.9" + depd "~1.1.1" + destroy "~1.0.4" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.6.2" + mime "1.4.1" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.3.1" + +serialize-javascript@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005" + +serve-index@^1.7.2: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719" + dependencies: + encodeurl "~1.0.1" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.1" + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +set-getter@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" + dependencies: + to-object-path "^0.3.0" + +set-immediate-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + +set-value@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.1" + to-object-path "^0.3.0" + +set-value@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.10" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.10.tgz#b1fde5cd7d11a5626638a07c604ab909cfa31f9b" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shallow-clone@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060" + dependencies: + is-extendable "^0.1.1" + kind-of "^2.0.1" + lazy-cache "^0.2.3" + mixin-object "^2.0.1" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +silent-error@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/silent-error/-/silent-error-1.1.0.tgz#2209706f1c850a9f1d10d0d840918b46f26e1bc9" + dependencies: + debug "^2.2.0" + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.1.tgz#e12b5487faded3e3dea0ac91e9400bf75b401370" + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^2.0.0" + +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + +sntp@2.x.x: + version "2.1.0" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" + dependencies: + hoek "4.x.x" + +sockjs-client@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" + dependencies: + debug "^2.6.6" + eventsource "0.1.6" + faye-websocket "~0.11.0" + inherits "^2.0.1" + json3 "^3.3.2" + url-parse "^1.1.8" + +sockjs@0.3.19: + version "0.3.19" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" + dependencies: + faye-websocket "^0.10.0" + uuid "^3.0.1" + +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + +source-list-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" + +source-list-map@~0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" + +source-map-resolve@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a" + dependencies: + atob "^2.0.0" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.4.1, source-map-support@^0.4.2: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + dependencies: + source-map "^0.5.6" + +source-map-support@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76" + dependencies: + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + +source-map@0.1.x: + version "0.1.43" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" + dependencies: + amdefine ">=0.0.4" + +source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +source-map@^0.4.2, source-map@~0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" + dependencies: + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" + +spdy-transport@^2.0.18: + version "2.0.20" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.20.tgz#735e72054c486b2354fe89e702256004a39ace4d" + dependencies: + debug "^2.6.8" + detect-node "^2.0.3" + hpack.js "^2.1.6" + obuf "^1.1.1" + readable-stream "^2.2.9" + safe-buffer "^5.0.1" + wbuf "^1.7.2" + +spdy@^3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc" + dependencies: + debug "^2.6.8" + handle-thing "^1.2.5" + http-deceiver "^1.2.7" + safe-buffer "^5.0.1" + select-hose "^2.0.0" + spdy-transport "^2.0.18" + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + +sshpk@^1.7.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +ssri@^5.0.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.2.2.tgz#797be390aefe03996e4d961657a946121e2feacf" + dependencies: + safe-buffer "^5.1.1" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +"statuses@>= 1.3.1 < 2": + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + +statuses@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + +stdout-stream@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b" + dependencies: + readable-stream "^2.0.1" + +stream-browserify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-each@^1.1.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.2.tgz#8e8c463f91da8991778765873fe4d960d8f616bd" + dependencies: + end-of-stream "^1.1.0" + stream-shift "^1.0.0" + +stream-http@^2.7.2: + version "2.8.0" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.0.tgz#fd86546dac9b1c91aff8fc5d287b98fafb41bc10" + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.3" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +stream-shift@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" + +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@^1.0.0, string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +stringstream@~0.0.4, stringstream@~0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + dependencies: + is-utf8 "^0.2.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + dependencies: + get-stdin "^4.0.1" + +strip-json-comments@^2.0.0, strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +style-loader@^0.13.1: + version "0.13.2" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.13.2.tgz#74533384cf698c7104c7951150b49717adc2f3bb" + dependencies: + loader-utils "^1.0.2" + +stylus-loader@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/stylus-loader/-/stylus-loader-3.0.1.tgz#77f4b34fd030d25b2617bcf5513db5b0730c4089" + dependencies: + loader-utils "^1.0.2" + lodash.clonedeep "^4.5.0" + when "~3.6.x" + +stylus@^0.54.5: + version "0.54.5" + resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.5.tgz#42b9560931ca7090ce8515a798ba9e6aa3d6dc79" + dependencies: + css-parse "1.7.x" + debug "*" + glob "7.0.x" + mkdirp "0.5.x" + sax "0.5.x" + source-map "0.1.x" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + dependencies: + has-flag "^1.0.0" + +supports-color@^4.0.0, supports-color@^4.2.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" + dependencies: + has-flag "^2.0.0" + +supports-color@^5.1.0, supports-color@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a" + dependencies: + has-flag "^3.0.0" + +svgo@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" + dependencies: + coa "~1.0.1" + colors "~1.1.2" + csso "~2.3.1" + js-yaml "~3.7.0" + mkdirp "~0.5.1" + sax "~1.2.1" + whet.extend "~0.9.9" + +symbol-observable@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" + +tapable@^0.2.7: + version "0.2.8" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" + +tar-pack@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" + dependencies: + debug "^2.2.0" + fstream "^1.0.10" + fstream-ignore "^1.0.5" + once "^1.3.3" + readable-stream "^2.1.4" + rimraf "^2.5.1" + tar "^2.2.1" + uid-number "^0.0.6" + +tar@^2.0.0, tar@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + +through2@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + dependencies: + readable-stream "^2.1.5" + xtend "~4.0.1" + +thunky@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.2.tgz#a862e018e3fb1ea2ec3fce5d55605cf57f247371" + +time-stamp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357" + +timers-browserify@^2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.6.tgz#241e76927d9ca05f4d959819022f5b3664b64bae" + dependencies: + setimmediate "^1.0.4" + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.1.tgz#15358bee4a2c83bd76377ba1dc049d0f18837aae" + dependencies: + define-property "^0.2.5" + extend-shallow "^2.0.1" + regex-not "^1.0.0" + +toposort@^1.0.0: + version "1.0.6" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.6.tgz#c31748e55d210effc00fdcdc7d6e68d7d7bb9cec" + +tough-cookie@~2.3.0, tough-cookie@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" + dependencies: + punycode "^1.4.1" + +tree-kill@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" + +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + +"true-case-path@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.2.tgz#7ec91130924766c7f573be3020c34f8fdfd00d62" + dependencies: + glob "^6.0.4" + +ts-node@~4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-4.1.0.tgz#36d9529c7b90bb993306c408cd07f7743de20712" + dependencies: + arrify "^1.0.0" + chalk "^2.3.0" + diff "^3.1.0" + make-error "^1.1.1" + minimist "^1.2.0" + mkdirp "^0.5.1" + source-map-support "^0.5.0" + tsconfig "^7.0.0" + v8flags "^3.0.0" + yn "^2.0.0" + +tsconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" + dependencies: + "@types/strip-bom" "^3.0.0" + "@types/strip-json-comments" "0.0.30" + strip-bom "^3.0.0" + strip-json-comments "^2.0.0" + +tsickle@^0.26.0: + version "0.26.0" + resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.26.0.tgz#40b30a2dd6abcb33b182e37596674bd1cfe4039c" + dependencies: + minimist "^1.2.0" + mkdirp "^0.5.1" + source-map "^0.5.6" + source-map-support "^0.4.2" + +tslib@^1.7.1: + version "1.9.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tunnel-agent@~0.4.1: + version "0.4.3" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +type-is@~1.6.15: + version "1.6.15" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" + dependencies: + media-typer "0.3.0" + mime-types "~2.1.15" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +typescript@~2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.3.tgz#df3dcdc38f3beb800d4bc322646b04a3f6ca7f0d" + +typescript@~2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" + +uglify-es@^3.3.4: + version "3.3.9" + resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" + dependencies: + commander "~2.13.0" + source-map "~0.6.1" + +uglify-js@3.3.x: + version "3.3.10" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.10.tgz#8e47821d4cf28e14c1826a0078ba0825ed094da8" + dependencies: + commander "~2.14.1" + source-map "~0.6.1" + +uglify-js@^2.8.29: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + +uglifyjs-webpack-plugin@^0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" + dependencies: + source-map "^0.5.6" + uglify-js "^2.8.29" + webpack-sources "^1.0.1" + +uglifyjs-webpack-plugin@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.1.8.tgz#1302fb9471a7daf3d0a5174da6d65f0f415e75ad" + dependencies: + cacache "^10.0.1" + find-cache-dir "^1.0.0" + schema-utils "^0.4.2" + serialize-javascript "^1.4.0" + source-map "^0.6.1" + uglify-es "^3.3.4" + webpack-sources "^1.1.0" + worker-farm "^1.5.2" + +uid-number@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" + +union-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^0.4.3" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + +uniqid@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-4.1.1.tgz#89220ddf6b751ae52b5f72484863528596bb84c1" + dependencies: + macaddress "^0.2.8" + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + +unique-filename@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3" + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.0.tgz#db6676e7c7cc0629878ff196097c78855ae9f4ab" + dependencies: + imurmurhash "^0.1.4" + +universalify@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.2.tgz#80aaae5395abc5fd402933ae2f58694f0860204c" + dependencies: + lodash.endswith "^4.2.1" + lodash.isfunction "^3.0.8" + lodash.isstring "^4.0.1" + lodash.startswith "^4.2.1" + +upper-case@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + +url-loader@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.6.2.tgz#a007a7109620e9d988d14bce677a1decb9a993f7" + dependencies: + loader-utils "^1.0.2" + mime "^1.4.1" + schema-utils "^0.3.0" + +url-parse@1.0.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b" + dependencies: + querystringify "0.0.x" + requires-port "1.0.x" + +url-parse@^1.1.8: + version "1.2.0" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986" + dependencies: + querystringify "~1.0.0" + requires-port "~1.0.0" + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8" + dependencies: + define-property "^0.2.5" + isobject "^3.0.0" + lazy-cache "^2.0.2" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +util@0.10.3, util@^0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + dependencies: + inherits "2.0.1" + +utila@~0.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.3.3.tgz#d7e8e7d7e309107092b05f8d9688824d633a4226" + +utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + +uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" + +v8flags@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.0.1.tgz#dce8fc379c17d9f2c9e9ed78d89ce00052b1b76b" + dependencies: + homedir-polyfill "^1.0.1" + +validate-npm-package-license@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + +vendors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vlq@^0.2.1: + version "0.2.3" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" + +vm-browserify@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" + dependencies: + indexof "0.0.1" + +w3c-blob@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/w3c-blob/-/w3c-blob-0.0.1.tgz#b0cd352a1a50f515563420ffd5861f950f1d85b8" + +watchpack@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" + dependencies: + async "^2.1.2" + chokidar "^1.7.0" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.2.tgz#d697b99f1f59512df2751be42769c1580b5801fe" + dependencies: + minimalistic-assert "^1.0.0" + +webpack-core@^0.6.8: + version "0.6.9" + resolved "https://registry.yarnpkg.com/webpack-core/-/webpack-core-0.6.9.tgz#fc571588c8558da77be9efb6debdc5a3b172bdc2" + dependencies: + source-list-map "~0.1.7" + source-map "~0.4.1" + +webpack-dev-middleware@1.12.2, webpack-dev-middleware@~1.12.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e" + dependencies: + memory-fs "~0.4.1" + mime "^1.5.0" + path-is-absolute "^1.0.0" + range-parser "^1.0.3" + time-stamp "^2.0.0" + +webpack-dev-server@~2.11.0: + version "2.11.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.11.1.tgz#6f9358a002db8403f016e336816f4485384e5ec0" + dependencies: + ansi-html "0.0.7" + array-includes "^3.0.3" + bonjour "^3.5.0" + chokidar "^2.0.0" + compression "^1.5.2" + connect-history-api-fallback "^1.3.0" + debug "^3.1.0" + del "^3.0.0" + express "^4.16.2" + html-entities "^1.2.0" + http-proxy-middleware "~0.17.4" + import-local "^1.0.0" + internal-ip "1.2.0" + ip "^1.1.5" + killable "^1.0.0" + loglevel "^1.4.1" + opn "^5.1.0" + portfinder "^1.0.9" + selfsigned "^1.9.1" + serve-index "^1.7.2" + sockjs "0.3.19" + sockjs-client "1.1.4" + spdy "^3.4.1" + strip-ansi "^3.0.0" + supports-color "^5.1.0" + webpack-dev-middleware "1.12.2" + yargs "6.6.0" + +webpack-merge@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.1.tgz#f1197a0a973e69c6fbeeb6d658219aa8c0c13555" + dependencies: + lodash "^4.17.4" + +webpack-sources@^1.0.0, webpack-sources@^1.0.1, webpack-sources@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54" + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack-subresource-integrity@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-1.0.4.tgz#8fac8a7e8eb59fc6a16768a85c9d94ee7f9d0edb" + dependencies: + webpack-core "^0.6.8" + +webpack@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.10.0.tgz#5291b875078cf2abf42bdd23afe3f8f96c17d725" + dependencies: + acorn "^5.0.0" + acorn-dynamic-import "^2.0.0" + ajv "^5.1.5" + ajv-keywords "^2.0.0" + async "^2.1.2" + enhanced-resolve "^3.4.0" + escope "^3.6.0" + interpret "^1.0.0" + json-loader "^0.5.4" + json5 "^0.5.1" + loader-runner "^2.3.0" + loader-utils "^1.1.0" + memory-fs "~0.4.1" + mkdirp "~0.5.0" + node-libs-browser "^2.0.0" + source-map "^0.5.3" + supports-color "^4.2.1" + tapable "^0.2.7" + uglifyjs-webpack-plugin "^0.4.6" + watchpack "^1.4.0" + webpack-sources "^1.0.1" + yargs "^8.0.2" + +websocket-driver@>=0.5.1: + version "0.7.0" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" + dependencies: + http-parser-js ">=0.4.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" + +when@~3.6.x: + version "3.6.4" + resolved "https://registry.yarnpkg.com/when/-/when-3.6.4.tgz#473b517ec159e2b85005497a13983f095412e34e" + +whet.extend@~0.9.9: + version "0.9.9" + resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" + +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + +which@1, which@^1.2.9: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + dependencies: + string-width "^1.0.2" + +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + +worker-farm@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.2.tgz#32b312e5dc3d5d45d79ef44acc2587491cd729ae" + dependencies: + errno "^0.1.4" + xtend "^4.0.1" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +xml-char-classes@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d" + +xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +xxhashjs@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8" + dependencies: + cuint "^0.2.2" + +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +yamljs@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/yamljs/-/yamljs-0.3.0.tgz#dc060bf267447b39f7304e9b2bfbe8b5a7ddb03b" + dependencies: + argparse "^1.0.7" + glob "^7.0.5" + +yargs-parser@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" + dependencies: + camelcase "^3.0.0" + +yargs-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" + dependencies: + camelcase "^3.0.0" + +yargs-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" + dependencies: + camelcase "^4.1.0" + +yargs@6.6.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^4.2.0" + +yargs@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^5.0.0" + +yargs@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" + dependencies: + camelcase "^4.1.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + read-pkg-up "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^7.0.0" + +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0" + +yn@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" + +zone.js@^0.8.19: + version "0.8.20" + resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.20.tgz#a218c48db09464b19ff6fc8f0d4bb5b1046e185d" diff --git a/platforms/swarm/ui/pom.xml b/platforms/swarm/ui/pom.xml index bb43475aa..cc1c5f935 100644 --- a/platforms/swarm/ui/pom.xml +++ b/platforms/swarm/ui/pom.xml @@ -27,7 +27,7 @@ ${project.groupId} - apicurio-studio-fe-app + apicurio-studio-fe-studio ${project.version} provided @@ -64,13 +64,13 @@ ${project.groupId} - apicurio-studio-fe-app + apicurio-studio-fe-studio ${project.version} jar true - ${project.build.directory}/fe-app + ${project.build.directory}/fe-studio false true @@ -83,7 +83,7 @@ - target/fe-app + target/fe-studio src/main/filtered-resources diff --git a/platforms/wildfly/ui/pom.xml b/platforms/wildfly/ui/pom.xml index 455ba0dea..ba199db85 100644 --- a/platforms/wildfly/ui/pom.xml +++ b/platforms/wildfly/ui/pom.xml @@ -25,7 +25,7 @@ ${project.groupId} - apicurio-studio-fe-app + apicurio-studio-fe-studio ${project.version} provided @@ -48,13 +48,13 @@ ${project.groupId} - apicurio-studio-fe-app + apicurio-studio-fe-studio ${project.version} jar true - ${project.build.directory}/fe-app + ${project.build.directory}/fe-studio false true @@ -67,7 +67,7 @@ - target/fe-app + target/fe-studio src/main/filtered-resources