Show read-only editor in versions (not actually versioning yet)
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Garrett Mills 2020-11-02 22:33:13 -06:00
parent 26e8d6ecd6
commit ab811bb54c
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246
12 changed files with 56 additions and 147 deletions

View File

@ -17,7 +17,7 @@ const routes: Routes = [
}, },
{ {
path: 'editor', path: 'editor',
loadChildren: () => import('./pages/editor/editor.module').then( m => m.EditorPageModule) loadChildren: () => import('./components/components.module').then( m => m.ComponentsModule)
} }
]; ];

View File

@ -31,6 +31,8 @@ import {MarkdownComponent as MarkdownEditorComponent} from './nodes/markdown/mar
import {DirectivesModule} from '../directives/directives.module'; import {DirectivesModule} from '../directives/directives.module';
import {MarkdownModule} from 'ngx-markdown'; import {MarkdownModule} from 'ngx-markdown';
import {VersionModalComponent} from './version-modal/version-modal.component'; import {VersionModalComponent} from './version-modal/version-modal.component';
import {EditorPageRoutingModule} from '../pages/editor/editor-routing.module';
import {EditorPage} from '../pages/editor/editor.page';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -58,6 +60,7 @@ import {VersionModalComponent} from './version-modal/version-modal.component';
NormComponent, NormComponent,
MarkdownEditorComponent, MarkdownEditorComponent,
VersionModalComponent, VersionModalComponent,
EditorPage,
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -69,6 +72,7 @@ import {VersionModalComponent} from './version-modal/version-modal.component';
MonacoEditorModule, MonacoEditorModule,
DirectivesModule, DirectivesModule,
MarkdownModule, MarkdownModule,
EditorPageRoutingModule,
], ],
entryComponents: [ entryComponents: [
NodePickerComponent, NodePickerComponent,
@ -95,6 +99,7 @@ import {VersionModalComponent} from './version-modal/version-modal.component';
NormComponent, NormComponent,
MarkdownEditorComponent, MarkdownEditorComponent,
VersionModalComponent, VersionModalComponent,
EditorPage,
], ],
exports: [ exports: [
NodePickerComponent, NodePickerComponent,
@ -121,6 +126,7 @@ import {VersionModalComponent} from './version-modal/version-modal.component';
NormComponent, NormComponent,
MarkdownEditorComponent, MarkdownEditorComponent,
VersionModalComponent, VersionModalComponent,
EditorPage,
] ]
}) })
export class ComponentsModule {} export class ComponentsModule {}

View File

@ -19,7 +19,7 @@
</ion-item> </ion-item>
</ion-list> </ion-list>
<div class="preview"> <div class="preview">
Page preview will be here. <app-editor [pageId]="pageId" *ngIf="selectedVersion" [hosted]="true" [readonly]="true" #editor></app-editor>
</div> </div>
</div> </div>
</div> </div>

View File

@ -23,7 +23,7 @@
.contents { .contents {
height: 100%; height: 100%;
overflow-y: scroll; overflow-y: hidden;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -35,6 +35,8 @@
.preview { .preview {
flex: 1; flex: 1;
height: 100%;
overflow-y: scroll;
} }
.version-date { .version-date {

View File

@ -1,7 +1,8 @@
import {Component, Input, OnInit} from '@angular/core'; import {Component, Input, OnInit, ViewChild} from '@angular/core';
import {AlertController, ModalController} from '@ionic/angular'; import {AlertController, ModalController} from '@ionic/angular';
import {EditorService} from '../../service/editor.service'; import {EditorService} from '../../service/editor.service';
import {PageVersionRecord} from '../../structures/PageRecord'; import {PageVersionRecord} from '../../structures/PageRecord';
import {EditorPage} from '../../pages/editor/editor.page';
@Component({ @Component({
selector: 'app-version-modal', selector: 'app-version-modal',
@ -10,6 +11,8 @@ import {PageVersionRecord} from '../../structures/PageRecord';
}) })
export class VersionModalComponent implements OnInit { export class VersionModalComponent implements OnInit {
@Input() pageId: string; @Input() pageId: string;
@ViewChild('editor') editor: EditorPage;
public pageVersions: PageVersionRecord[] = []; public pageVersions: PageVersionRecord[] = [];
public selectedVersion?: PageVersionRecord; public selectedVersion?: PageVersionRecord;
@ -23,7 +26,10 @@ export class VersionModalComponent implements OnInit {
async ngOnInit() { async ngOnInit() {
this.pageVersions = await this.editorService.loadPageVersions(this.pageId); this.pageVersions = await this.editorService.loadPageVersions(this.pageId);
await this.onVersionClick(this.pageVersions[0]); this.selectedVersion = this.pageVersions[0];
setTimeout(() => {
this.onVersionClick(this.pageVersions[0]);
}, 100);
} }
dismiss() { dismiss() {
@ -32,5 +38,8 @@ export class VersionModalComponent implements OnInit {
async onVersionClick(version: PageVersionRecord) { async onVersionClick(version: PageVersionRecord) {
this.selectedVersion = version; this.selectedVersion = version;
if ( this.editor ) {
this.editor.ionViewDidEnter();
}
} }
} }

View File

@ -1,22 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { EditorPageRoutingModule } from './editor-routing.module';
import { EditorPage } from './editor.page';
import {ComponentsModule} from '../../components/components.module';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
EditorPageRoutingModule,
ComponentsModule
],
declarations: [EditorPage]
})
export class EditorPageModule {}

View File

@ -7,6 +7,7 @@
<ion-title #titleBar> <ion-title #titleBar>
<ion-input <ion-input
[(ngModel)]="editorService.mutablePageName" [(ngModel)]="editorService.mutablePageName"
[readonly]="!editorService.canEdit()"
placeholder="Click to edit page name..." placeholder="Click to edit page name..."
class="title-input" class="title-input"
></ion-input> ></ion-input>
@ -43,7 +44,7 @@
> >
<div class="host-icons"> <div class="host-icons">
<i class="type-icon fa" [ngClass]="typeIcons[(node.isNorm() ? 'node' : node.type)] + ' ' + (node.isNorm() ? 'node' : node.type)"></i> <i class="type-icon fa" [ngClass]="typeIcons[(node.isNorm() ? 'node' : node.type)] + ' ' + (node.isNorm() ? 'node' : node.type)"></i>
<button (click)="onOptionsClick($event, node)"> <button (click)="onOptionsClick($event, node)" *ngIf="editorService.canEdit()">
<i class="fa fa-ellipsis-v" title="Node options"></i> <i class="fa fa-ellipsis-v" title="Node options"></i>
</button> </button>
</div> </div>
@ -63,7 +64,7 @@
<editor-files style="flex: 1;" [nodeId]="node.UUID" [editorUUID]="editorService.instanceUUID"></editor-files> <editor-files style="flex: 1;" [nodeId]="node.UUID" [editorUUID]="editorService.instanceUUID"></editor-files>
</ng-container> </ng-container>
</div> </div>
<button class="host-add-button" (click)="onAddClick($event)"> <button *ngIf="editorService.canEdit()" class="host-add-button" (click)="onAddClick($event)">
<i class="fa fa-plus"></i> Add Node <i class="fa fa-plus"></i> Add Node
</button> </button>
</div> </div>

View File

@ -17,6 +17,17 @@ import {VersionModalComponent} from '../../components/version-modal/version-moda
export class EditorPage implements OnInit { export class EditorPage implements OnInit {
public typeIcons = NodeTypeIcons; public typeIcons = NodeTypeIcons;
@Input() pageId: string; @Input() pageId: string;
@Input() hosted = false;
@Input() version?: number;
@Input()
set readonly(val: boolean) {
this.editorService.forceReadonly = val;
}
get readonly() {
return this.editorService.forceReadonly;
}
constructor( constructor(
protected route: ActivatedRoute, protected route: ActivatedRoute,
@ -39,7 +50,7 @@ export class EditorPage implements OnInit {
ionViewDidEnter() { ionViewDidEnter() {
if ( this.pageId ) { if ( this.pageId ) {
this.editorService.startEditing(this.pageId); this.editorService.startEditing(this.pageId);
} else { } else if ( !this.hosted ) {
this.router.navigate(['/home']); this.router.navigate(['/home']);
} }
} }

View File

@ -1,10 +0,0 @@
export interface RedirectAction {
type: 'redirect';
args: {
destination: string
blank?: boolean
appUrl?: boolean
};
}
export type Action = RedirectAction;

View File

@ -1,27 +0,0 @@
import { Injectable } from '@angular/core';
import { Action } from './action.types';
import {SessionService} from './session.service';
@Injectable({
providedIn: 'root'
})
export class ActionsService {
constructor(
protected readonly session: SessionService,
) { }
async perform(action: Action) {
if ( action.type === 'redirect' ) {
let destination = action.args.destination;
if ( action.args.appUrl ) {
destination = this.session.buildAppUrl(destination);
}
if ( action.args.blank ) {
window.open(destination, '_blank');
} else {
window.location.href = destination;
}
}
}
}

View File

@ -31,6 +31,7 @@ export class EditorService {
protected ready$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); protected ready$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
protected subs: Subscription[] = []; protected subs: Subscription[] = [];
protected saving = false; protected saving = false;
public forceReadonly = false;
protected saveTriggered = false; protected saveTriggered = false;
public notAvailable = false; public notAvailable = false;
public readonly instanceUUID: string; public readonly instanceUUID: string;
@ -139,7 +140,7 @@ export class EditorService {
} }
async save() { async save() {
if ( !(await this.needsSave()) || this.saving ) { if ( !(await this.needsSave()) || this.saving || this.forceReadonly ) {
return; return;
} }
@ -170,6 +171,10 @@ export class EditorService {
throw new NoPageLoadedError(); throw new NoPageLoadedError();
} }
if ( this.forceReadonly ) {
return;
}
const nodeIndex = this.currentNodes.findIndex(maybeNode => maybeNode.UUID === node.UUID); const nodeIndex = this.currentNodes.findIndex(maybeNode => maybeNode.UUID === node.UUID);
if ( nodeIndex < 0 ) { if ( nodeIndex < 0 ) {
return; return;
@ -417,6 +422,10 @@ export class EditorService {
} }
async needsSave() { async needsSave() {
if ( this.forceReadonly ) {
return false;
}
if ( this.dirtyOverride ) { if ( this.dirtyOverride ) {
return true; return true;
} }
@ -432,6 +441,10 @@ export class EditorService {
throw new NoPageLoadedError(); throw new NoPageLoadedError();
} }
if ( this.forceReadonly ) {
return;
}
const node = this.currentNodes.find(maybeNode => maybeNode.UUID === nodeId); const node = this.currentNodes.find(maybeNode => maybeNode.UUID === nodeId);
if ( !node ) { if ( !node ) {
throw new Error('Invalid node ID.'); throw new Error('Invalid node ID.');
@ -463,6 +476,10 @@ export class EditorService {
throw new NoPageLoadedError(); throw new NoPageLoadedError();
} }
if ( this.forceReadonly ) {
return;
}
const baseHost = new HostRecord(); const baseHost = new HostRecord();
baseHost.type = type; baseHost.type = type;
baseHost.PageId = this.currentPage.UUID; baseHost.PageId = this.currentPage.UUID;
@ -495,11 +512,7 @@ export class EditorService {
} }
canEdit() { canEdit() {
if ( !this.currentPage ) { return this.currentPage && !this.currentPage.isViewOnly() && !this.forceReadonly;
throw new NoPageLoadedError();
}
return !this.currentPage.isViewOnly();
} }
async registerNodeEditor(nodeId: string, editor: EditorNodeContract) { async registerNodeEditor(nodeId: string, editor: EditorNodeContract) {

View File

@ -1,74 +0,0 @@
import {Host, Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import PageRecord from '../structures/PageRecord';
import {ApiService} from './api.service';
import HostRecord from '../structures/HostRecord';
@Injectable({
providedIn: 'root'
})
export class PageService {
constructor(
protected api: ApiService,
) { }
load(id: string): Observable<PageRecord> {
return new Observable<PageRecord>(sub => {
this.api.get(`/page/${id}`).subscribe(response => {
if ( response.status === 200 ) {
sub.next(new PageRecord(response.data));
sub.complete();
} else {
throw new Error(response.message);
}
});
});
}
save(page: PageRecord): Observable<PageRecord> {
return new Observable<PageRecord>(sub => {
this.api.post(`/page/${page.UUID}/save`, page.toSave()).subscribe(res => {
sub.next(new PageRecord(res.data));
sub.complete();
});
});
}
get_nodes(page: PageRecord): Observable<Array<HostRecord>> {
return new Observable<Array<HostRecord>>(sub => {
this.api.get(`/page/${page.UUID}/nodes`).subscribe(res => {
const returns = [];
res.data.forEach(rec => {
const host = new HostRecord(rec.Value.Value);
host.load(rec);
returns.push(host);
});
sub.next(returns);
sub.complete();
});
});
}
save_nodes(page: PageRecord, nodes: Array<HostRecord>): Observable<Array<HostRecord>> {
return new Observable<Array<HostRecord>>(sub => {
nodes = nodes.map(x => {
x.PageId = page.UUID;
return x.toSave();
});
this.api.post(`/page/${page.UUID}/nodes/save`, nodes).subscribe(res => {
const returns = [];
res.data.forEach(rec => {
const host = new HostRecord(rec.Value.Value);
host.load(rec);
returns.push(host);
});
sub.next(returns);
sub.complete();
});
});
}
}