Core welcome tour functionality

This commit is contained in:
Michael Shamoon 2022-09-21 21:44:15 -07:00
parent e7ebc33090
commit 4e56fe339e
16 changed files with 169 additions and 35 deletions

View File

@ -26,6 +26,7 @@
"ngx-color": "^8.0.3",
"ngx-cookie-service": "^14.0.1",
"ngx-file-drop": "^14.0.1",
"ngx-ui-tour-ng-bootstrap": "^11.0.0",
"rxjs": "~7.5.7",
"tslib": "^2.3.1",
"uuid": "^9.0.0",
@ -12936,6 +12937,36 @@
"@angular/core": ">=14.0.0"
}
},
"node_modules/ngx-ui-tour-core": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ngx-ui-tour-core/-/ngx-ui-tour-core-9.0.0.tgz",
"integrity": "sha512-3HvrprosDaZm9jHi/w+oA8N9bPeaV9k0Y70nsEkRPRQ1jM302JyjGYFuM6/FzbXU5FITGLChtrFJpBn/Q4yJIA==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": "^14.0.0",
"@angular/core": "^14.0.0",
"@angular/router": "^14.0.0",
"rxjs": ">=6.0.0",
"typescript": ">=3.8.0"
}
},
"node_modules/ngx-ui-tour-ng-bootstrap": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/ngx-ui-tour-ng-bootstrap/-/ngx-ui-tour-ng-bootstrap-11.0.0.tgz",
"integrity": "sha512-oF5ySZEiO4ib/RfYno81V2UBaX7EMzxSPwfCdedDr38e5BEPTOrcCufOaiOfuQzQftg+7CC/BbFY8Df9kmeL1A==",
"dependencies": {
"ngx-ui-tour-core": "9.0.0",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": "^14.0.0",
"@angular/core": "^14.0.0",
"@ng-bootstrap/ng-bootstrap": "^13.0.0",
"typescript": ">=3.8.0"
}
},
"node_modules/nice-napi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
@ -27156,6 +27187,23 @@
"tslib": "^2.3.0"
}
},
"ngx-ui-tour-core": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ngx-ui-tour-core/-/ngx-ui-tour-core-9.0.0.tgz",
"integrity": "sha512-3HvrprosDaZm9jHi/w+oA8N9bPeaV9k0Y70nsEkRPRQ1jM302JyjGYFuM6/FzbXU5FITGLChtrFJpBn/Q4yJIA==",
"requires": {
"tslib": "^2.0.0"
}
},
"ngx-ui-tour-ng-bootstrap": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/ngx-ui-tour-ng-bootstrap/-/ngx-ui-tour-ng-bootstrap-11.0.0.tgz",
"integrity": "sha512-oF5ySZEiO4ib/RfYno81V2UBaX7EMzxSPwfCdedDr38e5BEPTOrcCufOaiOfuQzQftg+7CC/BbFY8Df9kmeL1A==",
"requires": {
"ngx-ui-tour-core": "9.0.0",
"tslib": "^2.0.0"
}
},
"nice-napi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",

View File

@ -31,6 +31,7 @@
"ngx-color": "^8.0.3",
"ngx-cookie-service": "^14.0.1",
"ngx-file-drop": "^14.0.1",
"ngx-ui-tour-ng-bootstrap": "^11.0.0",
"rxjs": "~7.5.7",
"tslib": "^2.3.1",
"uuid": "^9.0.0",

View File

@ -11,3 +11,27 @@
</div>
</ng-template>
</ngx-file-drop>
<tour-step-template>
<ng-template #tourStep let-step="step">
<p class="tour-step-content" [innerHTML]="step?.content"></p>
<div class="d-flex justify-content-between align-items-end">
<span class="badge bg-secondary">{{ tourService.steps?.indexOf(step) + 1 }}/{{ tourService.steps?.length }}</span>
<div class="tour-step-navigation btn-toolbar" role="toolbar" aria-label="Controls">
<div class="btn-group me-2" role="group" aria-label="Dismiss">
<button class="btn btn-outline-danger btn-sm" (click)="tourService.end()">
{{ step?.endBtnTitle }}
</button>
</div>
<div class="btn-group align-self-end" role="group" aria-label="Previous / Next">
<button *ngIf="tourService.hasPrev(step)" class="btn btn-outline-primary btn-sm" (click)="tourService.prev()">
« {{ step?.prevBtnTitle }}
</button>
<button *ngIf="tourService.hasNext(step)" class="btn btn-outline-primary btn-sm" (click)="tourService.next()">
{{ step?.nextBtnTitle }} »
</button>
</div>
</div>
</div>
</ng-template>
</tour-step-template>

View File

@ -1,6 +1,6 @@
import { SettingsService } from './services/settings.service'
import { SETTINGS_KEYS } from './data/paperless-uisettings'
import { Component, OnDestroy, OnInit } from '@angular/core'
import { Component, OnDestroy, OnInit, Renderer2 } from '@angular/core'
import { Router } from '@angular/router'
import { Subscription } from 'rxjs'
import { ConsumerStatusService } from './services/consumer-status.service'
@ -8,6 +8,7 @@ import { ToastService } from './services/toast.service'
import { NgxFileDropEntry } from 'ngx-file-drop'
import { UploadDocumentsService } from './services/upload-documents.service'
import { TasksService } from './services/tasks.service'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
@Component({
selector: 'app-root',
@ -29,7 +30,9 @@ export class AppComponent implements OnInit, OnDestroy {
private toastService: ToastService,
private router: Router,
private uploadDocumentsService: UploadDocumentsService,
private tasksService: TasksService
private tasksService: TasksService,
public tourService: TourService,
private renderer: Renderer2
) {
let anyWindow = window as any
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'
@ -112,6 +115,40 @@ export class AppComponent implements OnInit, OnDestroy {
})
}
})
this.tourService.initialize([
{
anchorId: 'tour.intro',
title: `Hello ${this.settings.displayName}, welcome to Paperless-ngx!`,
content:
"Here's a tutorial to guide you around some of Paperless-ngx's most useful features.",
route: '/dashboard',
},
{
anchorId: 'tour.dashboard',
title: 'The Dashboard',
content: "Here's some dashboard info",
route: '/dashboard',
},
{
anchorId: 'tour.documents',
title: 'Documents List',
content: "Here's some dashboard info",
route: '/documents',
delayAfterNavigation: 500,
},
])
this.tourService.start$.subscribe(() => {
this.renderer.addClass(document.body, 'tour-active')
})
this.tourService.end$.subscribe(() => {
// animation time
setTimeout(() => {
this.renderer.removeClass(document.body, 'tour-active')
}, 500)
})
}
public get dragDropEnabled(): boolean {

View File

@ -69,6 +69,11 @@ import { ColorComponent } from './components/common/input/color/color.component'
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
import { DocumentCommentsComponent } from './components/document-comments/document-comments.component'
import { DirtyDocGuard } from './guards/dirty-doc.guard'
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
import { StoragePathEditDialogComponent } from './components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
import { SettingsService } from './services/settings.service'
import { TasksComponent } from './components/manage/tasks/tasks.component'
import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
import localeBe from '@angular/common/locales/be'
import localeCs from '@angular/common/locales/cs'
@ -89,10 +94,6 @@ import localeSr from '@angular/common/locales/sr'
import localeSv from '@angular/common/locales/sv'
import localeTr from '@angular/common/locales/tr'
import localeZh from '@angular/common/locales/zh'
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
import { StoragePathEditDialogComponent } from './components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
import { SettingsService } from './services/settings.service'
import { TasksComponent } from './components/manage/tasks/tasks.component'
registerLocaleData(localeBe)
registerLocaleData(localeCs)
@ -188,6 +189,7 @@ function initializeApp(settings: SettingsService) {
PdfViewerModule,
NgSelectModule,
ColorSliderModule,
TourNgBootstrapModule.forRoot(),
],
providers: [
{

View File

@ -4,8 +4,8 @@
(click)="isMenuCollapsed = !isMenuCollapsed">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand me-0 px-3 py-3 order-sm-0" [ngClass]="slimSidebarEnabled ? 'slim' : 'col-auto col-md-3 col-lg-2'" routerLink="/dashboard">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1em" fill="currentColor">
<a class="navbar-brand col-auto col-md-3 col-lg-2 me-0 px-3 py-3 order-sm-0" [ngClass]="slimSidebarEnabled ? 'slim' : 'col-auto col-md-3 col-lg-2'" routerLink="/dashboard" tourAnchor="tour.intro">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1em" class="me-2" fill="currentColor">
<path d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z" transform="translate(0 0)"/>
</svg>
<span class="ms-2" [class.visually-hidden]="slimSidebarEnabled" i18n="app title">Paperless-ngx</span>

View File

@ -52,7 +52,7 @@ export class AppFrameComponent implements OnInit, ComponentCanDeactivate {
}
this.tasksService.reload()
}
versionString = `${environment.appTitle} ${environment.version}`
appRemoteVersion

View File

@ -19,14 +19,14 @@
</svg>
</app-page-header>
<div class='row'>
<div class='row' tourAnchor="tour.dashboard">
<div class="col-lg-8">
<ng-container *ngIf="savedViewService.loading">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container i18n>Loading...</ng-container>
</ng-container>
<app-welcome-widget *ngIf="!savedViewService.loading && savedViewService.dashboardViews.length == 0"></app-welcome-widget>
<app-welcome-widget *ngIf="settingsService.offerTour()"></app-welcome-widget>
<ng-container *ngFor="let v of savedViewService.dashboardViews">
<app-saved-view-widget [savedView]="v"></app-saved-view-widget>

View File

@ -1,5 +1,4 @@
import { Component, OnInit } from '@angular/core'
import { Meta } from '@angular/platform-browser'
import { Component } from '@angular/core'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service'
@ -16,9 +15,9 @@ export class DashboardComponent {
get subtitle() {
if (this.settingsService.displayName) {
return $localize`Hello ${this.settingsService.displayName}, welcome to Paperless-ngx!`
return $localize`Hello ${this.settingsService.displayName}, welcome to Paperless-ngx`
} else {
return $localize`Welcome to Paperless-ngx!`
return $localize`Welcome to Paperless-ngx`
}
}
}

View File

@ -1,16 +1,10 @@
<app-widget-frame title="First steps" i18n-title>
<ng-container content>
<img src="assets/save-filter.png" class="float-right">
<p i18n>Paperless is running! :)</p>
<p i18n>You can start uploading documents by dropping them in the file upload box to the right or by dropping them in the configured consumption folder and they'll start showing up in the documents list.
After you've added some metadata to your documents, use the filtering mechanisms of paperless to create custom views (such as 'Recently added', 'Tagged TODO') and they will appear on the dashboard instead of this message.</p>
<p i18n>Paperless offers some more features that try to make your life easier:</p>
<ul>
<li i18n>Once you've got a couple documents in paperless and added metadata to them, paperless can assign that metadata to new documents automatically.</li>
<li i18n>You can configure paperless to read your mails and add documents from attached files.</li>
</ul>
<p i18n>Consult the documentation on how to use these features. The section on basic usage also has some information on how to use paperless in general.</p>
</ng-container>
</app-widget-frame>
<ngb-alert type="primary" [dismissible]="false">
<!-- [dismissible]="isFinished(status)" (closed)="dismiss(status)" -->
<h4 class="alert-heading" i18n>Paperless-ngx is running!</h4>
<p i18n>You're ready to start uploading documents! Feel free to explore the various features or start a quick tour using the button below.</p>
<p i18n>More detail on how to use and configure paperless is always available in the <a href="https://paperless-ngx.readthedocs.io" target="_blank">documentation</a>.</p>
<hr>
<div class="btn-toolbar" role="toolbar" aria-label="Controls">
<button class="btn btn-primary ms-auto" (click)="tourService.start()"><ng-container i18n>Start Tour</ng-container> &rarr;</button>
</div>
</ngb-alert>

View File

@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
@Component({
selector: 'app-welcome-widget',
@ -6,7 +7,7 @@ import { Component, OnInit } from '@angular/core'
styleUrls: ['./welcome-widget.component.scss'],
})
export class WelcomeWidgetComponent implements OnInit {
constructor() {}
constructor(public readonly tourService: TourService) {}
ngOnInit(): void {}
}

View File

@ -75,10 +75,12 @@
</app-page-header>
<div class="row sticky-top pt-3 pt-sm-4 pb-2 pb-lg-4 bg-body">
<div tourAnchor="tour.documents"></div>
<app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" #filterEditor></app-filter-editor>
<app-bulk-editor [hidden]="!isBulkEditing"></app-bulk-editor>
</div>
<ng-template #pagination>
<div class="d-flex justify-content-between align-items-center">
<p>
@ -103,7 +105,6 @@
</ng-container>
<ng-template #documentListNoError>
<div *ngIf="displayMode == 'largeCards'">
<app-document-card-large [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" *ngFor="let d of list.documents; trackBy: trackByDocumentId" [document]="d" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)" (clickStoragePath)="clickStoragePath($event)" (clickMoreLike)="clickMoreLike(d.id)">
</app-document-card-large>

View File

@ -446,4 +446,9 @@ export class SettingsService {
get updateCheckingIsSet(): boolean {
return this.settingIsSet(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED)
}
offerTour(): boolean {
return true
// !savedViewService.loading && savedViewService.dashboardViews.length == 0
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -479,6 +479,12 @@ table.table {
user-select: none !important;
}
.alert-primary {
--bs-alert-color: var(--bs-primary);
--bs-alert-bg: var(--pngx-primary-faded);
--bs-alert-border-color: var(--bs-primary);
}
.alert-danger {
color: var(--bs-body-color);
background-color: var(--bs-danger);
@ -496,14 +502,23 @@ table.table {
}
.popover {
background-color: var(--pngx-bg-alt);
.popover-header {
background-color: var(--pngx-bg-alt);
}
.popover-header,
.popover-body {
background-color: var(--pngx-bg-alt);
border-color: var(--bs-border-color);
color: var(--bs-body-color);
}
}
// Tour popovers
.tour-active .popover {
min-width: 400px;
margin: 0 .25rem .25rem !important;
}
// fix popover carat colors
.bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="left"] {
border-left-color: var(--pngx-bg-alt);

View File

@ -10,11 +10,12 @@ body {
--bs-primary: hsl(var(--pngx-primary), var(--pngx-primary-lightness));
--bs-border-color: var(--bs-gray-400);
--pngx-primary-faded: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 72%));
--pngx-primary-faded: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 76%));
--pngx-primary-lighten-30: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 30%));
--pngx-primary-darken-5: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 5%));
--pngx-primary-darken-15: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 15%));
--pngx-primary-darken-18: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 18%));
--pngx-primary-darken-27: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 27%));
--pngx-bg-alt: #fff;
--pngx-bg-darker: var(--bs-gray-100);
--pngx-focus-alpha: 0.3;
@ -177,6 +178,12 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt
color: var(--bs-body-color);
}
.alert-primary {
--bs-alert-color: var(--pngx-primary-text-contrast);
--bs-alert-bg: var(--pngx-primary-darken-27);
--bs-alert-border-color: var(--pngx-bg-darker);
}
.table-striped > tbody > tr:nth-of-type(odd) > * {
color: var(--pngx-body-color-accent);
}