diff --git a/bot/admin/web/.npmrc b/bot/admin/web/.npmrc new file mode 100644 index 0000000000..5660f81af2 --- /dev/null +++ b/bot/admin/web/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org/ \ No newline at end of file diff --git a/nlp/admin/web/.npmrc b/nlp/admin/web/.npmrc new file mode 100644 index 0000000000..5660f81af2 --- /dev/null +++ b/nlp/admin/web/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org/ \ No newline at end of file diff --git a/nlp/admin/web/package-lock.json b/nlp/admin/web/package-lock.json index 6005eb8cbc..a9266da84b 100644 --- a/nlp/admin/web/package-lock.json +++ b/nlp/admin/web/package-lock.json @@ -24,7 +24,7 @@ "@nebular/theme": "10.0.0", "@ng-bootstrap/ng-bootstrap": "^13.1.1", "ang-jsoneditor": "^1.10.5", - "bootstrap": "^5.2.0", + "bootstrap": "^4.4.1", "echarts": "^5.3.3", "file-saver-es": "^2.0.5", "jsoneditor": "^9.5.6", @@ -4677,9 +4677,9 @@ "dev": true }, "node_modules/bootstrap": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", - "integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", + "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", "funding": [ { "type": "github", @@ -4691,7 +4691,8 @@ } ], "peerDependencies": { - "@popperjs/core": "^2.11.6" + "jquery": "1.9.1 - 3", + "popper.js": "^1.16.1" } }, "node_modules/brace-expansion": { @@ -8756,6 +8757,12 @@ "node": ">= 0.6.0" } }, + "node_modules/jquery": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz", + "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ==", + "peer": true + }, "node_modules/js-sdsl": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", @@ -10899,6 +10906,17 @@ "node": ">=8" } }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/postcss": { "version": "8.4.16", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", diff --git a/nlp/admin/web/package.json b/nlp/admin/web/package.json index 32637802e7..e664759bcd 100644 --- a/nlp/admin/web/package.json +++ b/nlp/admin/web/package.json @@ -27,7 +27,7 @@ "@nebular/theme": "10.0.0", "@ng-bootstrap/ng-bootstrap": "^13.1.1", "ang-jsoneditor": "^1.10.5", - "bootstrap": "^5.2.0", + "bootstrap": "^4.4.1", "echarts": "^5.3.3", "file-saver-es": "^2.0.5", "jsoneditor": "^9.5.6", diff --git a/nlp/admin/web/src/app/intents/intents-filters/intents-filters.component.html b/nlp/admin/web/src/app/intents/intents-filters/intents-filters.component.html new file mode 100644 index 0000000000..02e1678ca5 --- /dev/null +++ b/nlp/admin/web/src/app/intents/intents-filters/intents-filters.component.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + diff --git a/nlp/admin/web/src/app/intents/intents-filters/intents-filters.component.scss b/nlp/admin/web/src/app/intents/intents-filters/intents-filters.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/nlp/admin/web/src/app/intents/intents-filters/intents-filters.component.spec.ts b/nlp/admin/web/src/app/intents/intents-filters/intents-filters.component.spec.ts new file mode 100644 index 0000000000..94e72b66e7 --- /dev/null +++ b/nlp/admin/web/src/app/intents/intents-filters/intents-filters.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IntentsFiltersComponent } from './intents-filters.component'; + +describe('IntentsFiltersComponent', () => { + let component: IntentsFiltersComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ IntentsFiltersComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(IntentsFiltersComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nlp/admin/web/src/app/intents/intents-filters/intents-filters.component.ts b/nlp/admin/web/src/app/intents/intents-filters/intents-filters.component.ts new file mode 100644 index 0000000000..0ea5da7188 --- /dev/null +++ b/nlp/admin/web/src/app/intents/intents-filters/intents-filters.component.ts @@ -0,0 +1,45 @@ +import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { debounceTime, Subject, takeUntil } from 'rxjs'; + +interface IntentsFilterForm { + search: FormControl; +} + +export interface IntentsFilter { + search: string; +} + +@Component({ + selector: 'tock-intents-filters', + templateUrl: './intents-filters.component.html', + styleUrls: ['./intents-filters.component.scss'] +}) +export class IntentsFiltersComponent implements OnInit, OnDestroy { + private readonly destroy$: Subject = new Subject(); + + @Output() onFilter = new EventEmitter(); + + form = new FormGroup({ + search: new FormControl() + }); + + get search(): FormControl { + return this.form.get('search') as FormControl; + } + + ngOnInit(): void { + this.form.valueChanges.pipe(takeUntil(this.destroy$), debounceTime(300)).subscribe(() => { + this.onFilter.emit(this.form.value as IntentsFilter); + }); + } + + clearSearch(): void { + this.search.reset(); + } + + ngOnDestroy(): void { + this.destroy$.next(true); + this.destroy$.complete(); + } +} diff --git a/nlp/admin/web/src/app/intents/intents-list/intents-list.component.html b/nlp/admin/web/src/app/intents/intents-list/intents-list.component.html new file mode 100644 index 0000000000..1bac6bf44e --- /dev/null +++ b/nlp/admin/web/src/app/intents/intents-list/intents-list.component.html @@ -0,0 +1,173 @@ + + Intent + Entities + Shared Intents + Mandatory States + Actions + + + + + + {{ state.isOtherNamespaceIntent(intent) ? intent.qualifiedName() : intent.intentLabel() }} + + + + + + {{ intent.description }} + + + + + + {{ e.qualifiedName(state.user) }} + + + + + + + + + + + + {{ state.findIntentById(intentId)?.name }} + + + + + + + + + + + + + + + + + {{ s }} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nlp/admin/web/src/app/intents/intents-list/intents-list.component.scss b/nlp/admin/web/src/app/intents/intents-list/intents-list.component.scss new file mode 100644 index 0000000000..9f3c485a6b --- /dev/null +++ b/nlp/admin/web/src/app/intents/intents-list/intents-list.component.scss @@ -0,0 +1,39 @@ +.table-list { + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr 8rem; + column-gap: 0.5rem; + min-width: 50rem; + + &.table-list-header { + font-size: smaller; + font-weight: bold; + opacity: 0.6; + } + &:not(:last-child) { + .table-list-entry { + border-bottom: 1px solid var(--border-basic-color-3); + } + } + + .table-list-entry { + min-width: 8rem; + padding: 1rem 0; + &.bordered { + border-right: 1px solid var(--border-basic-color-3); + } + } +} + +.tag { + display: inline-flex; + width: fit-content; + max-width: 8rem; + padding: 0rem 0.3rem 0rem 0.9rem; + margin-right: 2px; + margin-bottom: 2px; + border-radius: 10rem; + background-color: var(--border-basic-color-3); + font-size: 0.7rem; + line-height: 1.25rem; + white-space: nowrap; +} diff --git a/nlp/admin/web/src/app/intents/intents-list/intents-list.component.ts b/nlp/admin/web/src/app/intents/intents-list/intents-list.component.ts new file mode 100644 index 0000000000..6aa6e7034a --- /dev/null +++ b/nlp/admin/web/src/app/intents/intents-list/intents-list.component.ts @@ -0,0 +1,79 @@ +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { Subject } from 'rxjs'; +import { StateService } from '../../core-nlp/state.service'; +import { EntityDefinition, Intent } from '../../model/nlp'; +import { UserRole } from '../../model/auth'; + +@Component({ + selector: 'tock-intents-list', + templateUrl: './intents-list.component.html', + styleUrls: ['./intents-list.component.scss'] +}) +export class IntentsListComponent implements OnInit, OnDestroy { + private readonly destroy$: Subject = new Subject(); + + UserRole = UserRole; + + @Input() intents: Intent[]; + + @Output() onRemoveEntity = new EventEmitter(); + @Output() onRemoveSharedIntent = new EventEmitter(); + @Output() onDisplayAddSharedIntentDialog = new EventEmitter(); + @Output() onRemoveState = new EventEmitter(); + @Output() onAddState = new EventEmitter(); + @Output() onUpdateIntent = new EventEmitter(); + @Output() onDownloadSentencesDump = new EventEmitter(); + @Output() onDeleteIntent = new EventEmitter(); + + constructor(public state: StateService) {} + + ngOnInit(): void {} + + ngOnDestroy(): void { + this.destroy$.next(true); + this.destroy$.complete(); + } + + removeEntity(intent: Intent, entity: EntityDefinition): void { + this.onRemoveEntity.emit({ intent, entity }); + } + + removeSharedIntent(intent: Intent, intentId: string): void { + this.onRemoveSharedIntent.emit({ intent, intentId }); + } + + displayAddSharedIntentDialog(intent: Intent): void { + this.onDisplayAddSharedIntentDialog.emit(intent); + } + + removeState(intent: Intent, state: string): void { + this.onRemoveState.emit({ intent, state }); + } + + addState(intent: Intent): void { + this.onAddState.emit(intent); + } + + updateIntent(intent: Intent): void { + this.onUpdateIntent.emit(intent); + } + + downloadSentencesDump(intent: Intent): void { + this.onDownloadSentencesDump.emit(intent); + } + + deleteIntent(intent: Intent): void { + this.onDeleteIntent.emit(intent); + } + + // To share with Scenario's version after merge + getContrastYIQ(hexcolor: string): '' | 'black' | 'white' { + if (!hexcolor) return ''; + hexcolor = hexcolor.replace('#', ''); + let r = parseInt(hexcolor.substring(0, 2), 16); + let g = parseInt(hexcolor.substring(2, 4), 16); + let b = parseInt(hexcolor.substring(4, 6), 16); + let yiq = (r * 299 + g * 587 + b * 114) / 1000; + return yiq >= 128 ? 'black' : 'white'; + } +} diff --git a/nlp/admin/web/src/app/intents/intents.component.html b/nlp/admin/web/src/app/intents/intents.component.html index b306adcdf3..d48f634b0d 100644 --- a/nlp/admin/web/src/app/intents/intents.component.html +++ b/nlp/admin/web/src/app/intents/intents.component.html @@ -14,188 +14,60 @@ ~ limitations under the License. --> - + + + + No intent found + + + + + + + + + - + {{ category.category }} - - Intent - Entities - Shared Intents - Mandatory States - Actions - - - - - - {{ state.isOtherNamespaceIntent(intent) ? intent.qualifiedName() : intent.intentLabel() }} - - - - - - {{ intent.description }} - - - - - - {{ e.qualifiedName(state.user) }} - - - - - - - - - - - - {{ state.findIntentById(intentId)?.name }} - - - - - - - - - - - - - - - - - {{ s }} - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/nlp/admin/web/src/app/intents/intents.component.scss b/nlp/admin/web/src/app/intents/intents.component.scss index 81c9910c10..a2ee1cb68a 100644 --- a/nlp/admin/web/src/app/intents/intents.component.scss +++ b/nlp/admin/web/src/app/intents/intents.component.scss @@ -22,43 +22,3 @@ margin-top: -1.5rem; margin-bottom: -1rem; } - -.table-list { - display: grid; - grid-template-columns: 2fr 1fr 1fr 1fr 8rem; - column-gap: 0.5rem; - min-width: 50rem; - - &.table-list-header { - font-size: smaller; - font-weight: bold; - opacity: 0.6; - } - &:not(:last-child) { - .table-list-entry { - border-bottom: 1px solid var(--border-basic-color-3); - } - } - - .table-list-entry { - min-width: 8rem; - padding: 1rem 0; - &.bordered { - border-right: 1px solid var(--border-basic-color-3); - } - } -} - -.tag { - display: inline-flex; - width: fit-content; - max-width: 8rem; - padding: 0rem 0.3rem 0rem 0.9rem; - margin-right: 2px; - margin-bottom: 2px; - border-radius: 10rem; - background-color: var(--border-basic-color-3); - font-size: 0.7rem; - line-height: 1.25rem; - white-space: nowrap; -} diff --git a/nlp/admin/web/src/app/intents/intents.component.ts b/nlp/admin/web/src/app/intents/intents.component.ts index 57b41cd7c6..7397f2be9a 100644 --- a/nlp/admin/web/src/app/intents/intents.component.ts +++ b/nlp/admin/web/src/app/intents/intents.component.ts @@ -26,6 +26,7 @@ import { UserRole } from '../model/auth'; import { IntentDialogComponent } from '../sentence-analysis/intent-dialog/intent-dialog.component'; import { DialogService } from '../core-nlp/dialog.service'; import { AddSharedIntentDialogComponent } from './add-shared-intent/add-shared-intent-dialog.component'; +import { IntentsFilter } from './intents-filters/intents-filters.component'; @Component({ selector: 'tock-intents', @@ -51,6 +52,30 @@ export class IntentsComponent implements OnInit { }); } + filters: IntentsFilter; + + filteredIntents: Intent[]; + + filterIntents(filters: IntentsFilter) { + this.filters = filters; + this.updateFilteredIntents(); + } + + updateFilteredIntents(): void { + if (this.filters.search?.trim().length) { + let allIntents = []; + this.intentsCategories.forEach((cat) => { + allIntents = [...allIntents, ...cat.intents]; + }); + const searchStr = this.filters.search.toLowerCase(); + this.filteredIntents = allIntents.filter((intent) => { + return intent.label?.toLowerCase().search(searchStr) > -1 || intent.name?.toLowerCase().search(searchStr) > -1; + }); + } else { + this.filteredIntents = undefined; + } + } + updateIntent(intent: Intent): void { const dialogRef = this.dialog.openDialog(IntentDialogComponent, { context: { @@ -99,6 +124,7 @@ export class IntentsComponent implements OnInit { (_) => { this.state.removeIntent(intent); this.dialog.notify(`Intent ${intent.name} removed`, 'Remove Intent'); + this.updateFilteredIntents(); }, (_) => this.dialog.notify(`Delete Intent ${intent.name} failed`) ); @@ -106,11 +132,11 @@ export class IntentsComponent implements OnInit { }); } - removeState(intent: Intent, state: string): void { - this.nlp.removeState(this.state.currentApplication, intent, state).subscribe( + removeState(event: { intent: Intent; state: string }): void { + this.nlp.removeState(this.state.currentApplication, event.intent, event.state).subscribe( (_) => { - intent.mandatoryStates.splice(intent.mandatoryStates.indexOf(state), 1); - this.dialog.notify(`State ${state} removed from Intent ${intent.name}`, 'Remove State'); + event.intent.mandatoryStates.splice(event.intent.mandatoryStates.indexOf(event.state), 1); + this.dialog.notify(`State ${event.state} removed from Intent ${event.intent.name}`, 'Remove State'); }, (_) => { this.dialog.notify(`Remove State failed`); @@ -140,8 +166,8 @@ export class IntentsComponent implements OnInit { }); } - removeEntity(intent: Intent, entity: EntityDefinition): void { - const entityName = entity.qualifiedName(this.state.user); + removeEntity(event: { intent: Intent; entity: EntityDefinition }): void { + const entityName = event.entity.qualifiedName(this.state.user); const dialogRef = this.dialog.openDialog(ConfirmDialogComponent, { context: { title: `Remove the Entity ${entityName}`, @@ -151,10 +177,10 @@ export class IntentsComponent implements OnInit { }); dialogRef.onClose.subscribe((result) => { if (result === 'remove') { - this.nlp.removeEntity(this.state.currentApplication, intent, entity).subscribe((deleted) => { - this.state.currentApplication.intentById(intent._id).removeEntity(entity); + this.nlp.removeEntity(this.state.currentApplication, event.intent, event.entity).subscribe((deleted) => { + this.state.currentApplication.intentById(event.intent._id).removeEntity(event.entity); if (deleted) { - this.state.removeEntityTypeByName(entity.entityTypeName); + this.state.removeEntityTypeByName(event.entity.entityTypeName); } this.dialog.notify(`Entity ${entityName} removed from intent`, 'Remove Entity'); }); @@ -162,12 +188,12 @@ export class IntentsComponent implements OnInit { }); } - removeSharedIntent(intent: Intent, intentId: string): void { + removeSharedIntent(event: { intent: Intent; intentId: string }): void { this.selectedIntent = null; - this.nlp.removeSharedIntent(this.state.currentApplication, intent, intentId).subscribe( + this.nlp.removeSharedIntent(this.state.currentApplication, event.intent, event.intentId).subscribe( (_) => { - intent.sharedIntents.splice(intent.sharedIntents.indexOf(intentId), 1); - this.dialog.notify(`Shared Intent removed from Intent ${intent.name}`, 'Remove Intent'); + event.intent.sharedIntents.splice(event.intent.sharedIntents.indexOf(event.intentId), 1); + this.dialog.notify(`Shared Intent removed from Intent ${event.intent.name}`, 'Remove Intent'); }, (_) => { this.dialog.notify(`Remove Shared Intent failed`); @@ -231,15 +257,4 @@ export class IntentsComponent implements OnInit { collapsedChange(category: IntentsCategory): void { this.expandedCategory = category.category; } - - // To share with Scenario's version after merge - getContrastYIQ(hexcolor: string): '' | 'black' | 'white' { - if (!hexcolor) return ''; - hexcolor = hexcolor.replace('#', ''); - let r = parseInt(hexcolor.substring(0, 2), 16); - let g = parseInt(hexcolor.substring(2, 4), 16); - let b = parseInt(hexcolor.substring(4, 6), 16); - let yiq = (r * 299 + g * 587 + b * 114) / 1000; - return yiq >= 128 ? 'black' : 'white'; - } } diff --git a/nlp/admin/web/src/app/nlp-tabs/nlp.module.ts b/nlp/admin/web/src/app/nlp-tabs/nlp.module.ts index ad9bc07696..adcf13a0a5 100644 --- a/nlp/admin/web/src/app/nlp-tabs/nlp.module.ts +++ b/nlp/admin/web/src/app/nlp-tabs/nlp.module.ts @@ -22,6 +22,8 @@ import { NlpTabsComponent } from './nlp-tabs.component'; import { InboxComponent } from '../inbox/inbox.component'; import { ArchiveComponent } from '../archive/archive.component'; import { IntentsComponent } from '../intents/intents.component'; +import { IntentsFiltersComponent } from '../intents/intents-filters/intents-filters.component'; +import { IntentsListComponent } from '../intents/intents-list/intents-list.component'; import { SearchComponent } from '../search/search.component'; import { SearchFilterComponent } from '../search/filter/search-filter.component'; import { DisplayFullLogComponent, LogsComponent } from '../logs/logs.component'; @@ -120,58 +122,60 @@ const routes: Routes = [ export class NlpRoutingModule {} @NgModule({ - imports: [ - CommonModule, - SharedModule, - NlpRoutingModule, - ApplicationsModule, - InfiniteScrollModule, - MomentModule, - MatDatepickerModule, - MatNativeDateModule, - FileUploadModule, - MatNativeDateModule, - ThemeModule, - NbTabsetModule, - NbRouteTabsetModule, - NbAccordionModule, - NbCardModule, - NbCheckboxModule, - NbSpinnerModule, - NbActionsModule, - NbSelectModule, - NbButtonModule, - NbTooltipModule, - NbInputModule, - NgJsonEditorModule, - NbTreeGridModule, - NbAutocompleteModule, - ReactiveFormsModule, - NbFormFieldModule - ], - declarations: [ - NlpTabsComponent, - TryComponent, - InboxComponent, - ArchiveComponent, - IntentsComponent, - SearchComponent, - SearchFilterComponent, - LogsComponent, - HighlightComponent, - EditEntitiesComponent, - SentenceAnalysisComponent, - CreateEntityDialogComponent, - IntentDialogComponent, - SentencesScrollComponent, - DisplayFullLogComponent, - AddStateDialogComponent, - AddSharedIntentDialogComponent, - EntitiesComponent, - EntityDetailsComponent, - ReviewRequestDialogComponent - ], - exports: [SentenceAnalysisComponent, HighlightComponent], - providers: [NlpService] + imports: [ + CommonModule, + SharedModule, + NlpRoutingModule, + ApplicationsModule, + InfiniteScrollModule, + MomentModule, + MatDatepickerModule, + MatNativeDateModule, + FileUploadModule, + MatNativeDateModule, + ThemeModule, + NbTabsetModule, + NbRouteTabsetModule, + NbAccordionModule, + NbCardModule, + NbCheckboxModule, + NbSpinnerModule, + NbActionsModule, + NbSelectModule, + NbButtonModule, + NbTooltipModule, + NbInputModule, + NgJsonEditorModule, + NbTreeGridModule, + NbAutocompleteModule, + ReactiveFormsModule, + NbFormFieldModule + ], + declarations: [ + NlpTabsComponent, + TryComponent, + InboxComponent, + ArchiveComponent, + IntentsComponent, + SearchComponent, + SearchFilterComponent, + LogsComponent, + HighlightComponent, + EditEntitiesComponent, + SentenceAnalysisComponent, + CreateEntityDialogComponent, + IntentDialogComponent, + SentencesScrollComponent, + DisplayFullLogComponent, + AddStateDialogComponent, + AddSharedIntentDialogComponent, + EntitiesComponent, + EntityDetailsComponent, + ReviewRequestDialogComponent, + IntentsFiltersComponent, + IntentsListComponent + ], + exports: [SentenceAnalysisComponent, HighlightComponent], + providers: [NlpService] }) export class NlpModule {} diff --git a/nlp/admin/web/src/app/theme/styles/styles.scss b/nlp/admin/web/src/app/theme/styles/styles.scss index c0d66b8c20..f41e245ff9 100644 --- a/nlp/admin/web/src/app/theme/styles/styles.scss +++ b/nlp/admin/web/src/app/theme/styles/styles.scss @@ -23,12 +23,27 @@ @import '@nebular/theme/styles/globals'; @import 'bootstrap/scss/functions'; + +$blue: #3366ff; +$green: #00d68f; +$cyan: #0095ff; +$yellow: #ffaa00; +$red: #ff3d71; + @import 'bootstrap/scss/variables'; -@import 'bootstrap/scss/maps'; @import 'bootstrap/scss/mixins'; +@import 'bootstrap/scss/root'; +@import 'bootstrap/scss/reboot'; +@import 'bootstrap/scss/type'; +@import 'bootstrap/scss/images'; @import 'bootstrap/scss/grid'; - +@import 'bootstrap/scss/tables'; +@import 'bootstrap/scss/forms'; +@import 'bootstrap/scss/transitions'; @import 'bootstrap/scss/pagination'; +@import 'bootstrap/scss/close'; +@import 'bootstrap/scss/utilities'; +@import 'bootstrap/scss/print'; @import './layout'; @import './overrides';