Browse Source

Update editor.

pull/1045/head
Sebastian 3 years ago
parent
commit
6c56c62075
  1. 511
      backend/src/Squidex/wwwroot/editor/squidex-editor.js
  2. 854
      frontend/package-lock.json
  3. 57
      frontend/package.json
  4. 41
      frontend/src/app/declarations.d.ts
  5. 48
      frontend/src/app/features/content/pages/calendar/calendar-page.component.ts
  6. 100
      frontend/src/app/features/settings/pages/workflows/workflow-diagram.component.ts
  7. 144
      frontend/src/app/framework/angular/forms/editors/code-editor.component.ts
  8. 16
      frontend/src/app/framework/angular/video-player.component.ts
  9. 165
      frontend/src/app/shared/components/forms/geolocation-editor.component.ts
  10. 118
      frontend/src/app/shared/components/forms/rich-editor.component.ts
  11. 10
      frontend/src/app/shared/services/auth.service.ts
  12. 1
      frontend/tsconfig.app.json

511
backend/src/Squidex/wwwroot/editor/squidex-editor.js

File diff suppressed because one or more lines are too long

854
frontend/package-lock.json

File diff suppressed because it is too large

57
frontend/package.json

@ -15,26 +15,27 @@
},
"private": true,
"dependencies": {
"@angular-devkit/architect": "^0.1700.0",
"@angular/animations": "17.0.2",
"@angular/cdk": "17.0.0",
"@angular/cdk-experimental": "17.0.0",
"@angular/common": "17.0.2",
"@angular/core": "17.0.2",
"@angular/forms": "17.0.2",
"@angular/localize": "17.0.2",
"@angular/platform-browser": "17.0.2",
"@angular/platform-browser-dynamic": "17.0.2",
"@angular/platform-server": "17.0.2",
"@angular/router": "17.0.2",
"@angular-devkit/architect": "^0.1700.1",
"@angular/animations": "17.0.3",
"@angular/cdk": "17.0.1",
"@angular/cdk-experimental": "17.0.1",
"@angular/common": "17.0.3",
"@angular/core": "17.0.3",
"@angular/forms": "17.0.3",
"@angular/localize": "17.0.3",
"@angular/platform-browser": "17.0.3",
"@angular/platform-browser-dynamic": "17.0.3",
"@angular/platform-server": "17.0.3",
"@angular/router": "17.0.3",
"@egjs/hammerjs": "2.0.17",
"@floating-ui/dom": "^1.5.3",
"@graphiql/toolkit": "^0.9.1",
"@iharbeck/ngx-virtual-scroller": "^16.0.0",
"@iharbeck/ngx-virtual-scroller": "^17.0.0",
"@lithiumjs/angular": "^7.3.0",
"@lithiumjs/ngx-virtual-scroll": "^0.3.0",
"@marker.io/browser": "^0.19.0",
"ace-builds": "1.31.1",
"@types/ace": "^0.0.51",
"ace-builds": "^1.31.2",
"angular-gridster2": "16.0.0",
"angular-mentions": "1.5.0",
"bootstrap": "5.2.3",
@ -77,16 +78,16 @@
"zone.js": "0.14.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.0.0",
"@angular-eslint/builder": "17.0.1",
"@angular-eslint/eslint-plugin": "17.0.1",
"@angular-eslint/eslint-plugin-template": "17.0.1",
"@angular-eslint/schematics": "17.0.1",
"@angular-eslint/template-parser": "17.0.1",
"@angular/cli": "^17.0.0",
"@angular/compiler": "^17.0.2",
"@angular/compiler-cli": "^17.0.2",
"@angular/elements": "^17.0.2",
"@angular-devkit/build-angular": "^17.0.1",
"@angular-eslint/builder": "17.1.0",
"@angular-eslint/eslint-plugin": "17.1.0",
"@angular-eslint/eslint-plugin-template": "17.1.0",
"@angular-eslint/schematics": "17.1.0",
"@angular-eslint/template-parser": "17.1.0",
"@angular/cli": "^17.0.1",
"@angular/compiler": "^17.0.3",
"@angular/compiler-cli": "^17.0.3",
"@angular/elements": "^17.0.3",
"@compodoc/compodoc": "^1.1.22",
"@storybook/addon-actions": "^7.5.3",
"@storybook/addon-essentials": "^7.5.3",
@ -100,16 +101,16 @@
"@types/marked": "5.0.1",
"@types/mersenne-twister": "1.1.7",
"@types/mousetrap": "1.6.14",
"@types/node": "20.9.0",
"@types/node": "20.9.1",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@types/simplemde": "1.11.11",
"@types/tapable": "2.2.7",
"@types/ws": "8.5.9",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@webcomponents/custom-elements": "^1.6.0",
"eslint": "^8.53.0",
"eslint": "^8.54.0",
"eslint-config-airbnb-typescript": "17.1.0",
"eslint-plugin-deprecation": "^2.0.0",
"eslint-plugin-import": "2.29.0",

41
frontend/src/app/declarations.d.ts

@ -45,12 +45,17 @@ type Content = {
title: string;
};
type OnAnnotationCreate = (annotation: AnnotationSelection) => void;
type OnAnnotationUpdate = (annotation: ReadonlyArray<Annotation>) => void;
type OnAnnotationFocus = (annotation: ReadonlyArray<string>) => void;
type OnAssetEdit = (id: string) => void;
type OnAssetUpload = (images: UploadRequest[]) => DelayedPromiseCreator<Asset>[];
type OnChange = (value: string | undefined) => void;
type OnContentEdit = (schemaName: string, contentId: string) => void;
type OnSelectAIText = () => Promise<string | undefined | null>;
type OnSelectAssets = () => Promise<Asset[]>;
type OnSelectContents = () => Promise<Content[]>;
type OnChange = (value: string | undefined) => void;
type SquidexEditorMode = 'Html' | 'Markdown';
interface UploadRequest {
@ -90,14 +95,22 @@ interface EditorProps {
onSelectContents?: OnSelectContents;
// Called when an asset is to be edited.
onEditAsset: (assetId: string) => void;
onEditAsset: OnAssetEdit;
// Called when a content is to be edited.
onEditContent: (schemaName: string, contentId: string) => void;
onEditContent: OnContentEdit;
// Called when a file needs to be uploaded.
onUpload?: (images: UploadRequest[]) => DelayedPromiseCreator<Asset>[];
onUpload?: OnAssetUpload;
// Triggered, when an annotation is clicked.
onAnnotationsFocus?: OnAnnotationFocus;
// Triggered, when an annotation are updated.
onAnnotationsUpdate?: OnAnnotationUpdate;
// Triggered, when an annotation is created.
onAnnotationCreate?: OnAnnotationCreate;
// True, if disabled.
isDisabled?: boolean;
@ -110,6 +123,22 @@ interface EditorProps {
// Indicates whether content items can be selected.
canSelectContents?: boolean;
// The annotations.
annotations?: ReadonlyArray<Annotation>;
}
interface AnnotationSelection {
// The start of the annotation selection.
from: number;
// The end of the annotation selection.
to: number;
}
interface Annotation extends AnnotationSelection {
// The ID of the annotation.
id: string;
}
type DelayedPromiseCreator<T> = (context: unknown) => Promise<T>;

48
frontend/src/app/features/content/pages/calendar/calendar-page.component.ts

@ -72,39 +72,39 @@ export class CalendarPageComponent implements AfterViewInit, OnDestroy, OnInit {
this.language = this.languagesState.snapshot.languages.find(x => x.language.isMaster)!.language;
}
public ngAfterViewInit() {
Promise.all([
public async ngAfterViewInit() {
await Promise.all([
this.resourceLoader.loadLocalStyle('dependencies/tui-calendar/tui-calendar.min.css'),
this.resourceLoader.loadLocalScript('dependencies/tui-calendar/tui-code-snippet.min.js'),
this.resourceLoader.loadLocalScript('dependencies/tui-calendar/tui-calendar.min.js'),
]).then(() => {
const Calendar = tui.Calendar;
this.calendar = new Calendar(this.calendarContainer.nativeElement, {
defaultView: 'month',
isReadOnly: true,
scheduleView: ['time'],
taskView: false,
...getLocalizationSettings(),
});
]);
this.calendar.on('clickSchedule', (event: any) => {
this.contentSelected = event.schedule.raw;
this.contentDialog.show();
this.calendar?.destroy();
this.calendar = new tui.Calendar(this.calendarContainer.nativeElement, {
defaultView: 'month',
isReadOnly: true,
isLoading: false,
scheduleView: ['time'],
taskView: false,
...getLocalizationSettings(),
});
this.changeDetector.detectChanges();
});
this.calendar.on('clickSchedule', (event: any) => {
this.contentSelected = event.schedule.raw;
this.contentDialog.show();
this.calendar.on('clickDayname', (event: any) => {
if (this.calendar.getViewName() === 'week') {
this.calendar.setDate(new Date(event.date));
this.changeDetector.detectChanges();
});
this.changeView('day');
}
});
this.calendar.on('clickDayname', (event: any) => {
if (this.calendar.getViewName() === 'week') {
this.calendar.setDate(new Date(event.date));
this.load();
this.changeView('day');
}
});
this.load();
}
@HostListener('click', ['$event'])

100
frontend/src/app/features/settings/pages/workflows/workflow-diagram.component.ts

@ -47,72 +47,78 @@ export class WorkflowDiagramComponent implements AfterViewInit, OnDestroy {
this.updateNetwork();
}
private updateNetwork() {
if (this.chartContainer?.nativeElement && this.workflow) {
this.resourceLoader.loadLocalScript('dependencies/vis-network/vis-network.min.js')
.then(() => {
this.network?.destroy();
const nodes = new vis.DataSet();
private async updateNetwork() {
if (!this.chartContainer?.nativeElement || !this.workflow) {
return;
}
for (const step of this.workflow.steps) {
let label = `<b>${step.name}</b>`;
await this.resourceLoader.loadLocalScript('dependencies/vis-network/vis-network.min.js');
if (step.noUpdate) {
label += '\nPrevent updates';
const { edges, nodes } = buildGraph(this.workflow);
if (step.noUpdateExpression) {
label += `\nwhen (${step.noUpdateExpression})`;
}
this.network?.destroy();
this.network = new vis.Network(this.chartContainer.nativeElement, { edges, nodes }, GRAPH_OPTIONS);
this.network.stabilize();
this.network.fit();
if (step.noUpdateRoles && step.noUpdateRoles.length > 0) {
label += `\nfor ${step.noUpdateRoles.join(', ')}`;
}
}
this.isLoaded = true;
}
}
if (step.name === 'Published') {
label += '\nAvailable in the API';
}
function buildGraph(workflow: WorkflowDto) {
const nodes = new vis.DataSet();
const node: any = { id: step.name, label, color: step.color };
for (const step of workflow.steps) {
let label = `<b>${step.name}</b>`;
nodes.add(node);
}
if (step.noUpdate) {
label += '\nPrevent updates';
if (this.workflow.initial) {
nodes.add({ id: 0, color: '#000', label: 'Start', shape: 'dot', size: 3 });
}
if (step.noUpdateExpression) {
label += `\nwhen (${step.noUpdateExpression})`;
}
const edges = new vis.DataSet();
if (step.noUpdateRoles && step.noUpdateRoles.length > 0) {
label += `\nfor ${step.noUpdateRoles.join(', ')}`;
}
}
for (const transition of this.workflow.transitions) {
let label = '';
if (step.name === 'Published') {
label += '\nAvailable in the API';
}
if (transition.expression) {
label += `\nwhen (${transition.expression})`;
}
const node: any = { id: step.name, label, color: step.color };
if (transition.roles && transition.roles.length > 0) {
label += `\nfor ${transition.roles.join(', ')}`;
}
nodes.add(node);
}
const edge: any = { ...transition, label };
if (workflow.initial) {
nodes.add({ id: 0, color: '#000', label: 'Start', shape: 'dot', size: 3 });
}
edges.add(edge);
}
const edges = new vis.DataSet();
if (this.workflow.initial) {
edges.add({ from: 0, to: this.workflow.initial });
}
for (const transition of workflow.transitions) {
let label = '';
this.network = new vis.Network(this.chartContainer.nativeElement, { edges, nodes }, GRAPH_OPTIONS);
this.network.stabilize();
this.network.fit();
if (transition.expression) {
label += `\nwhen (${transition.expression})`;
}
this.isLoaded = true;
});
if (transition.roles && transition.roles.length > 0) {
label += `\nfor ${transition.roles.join(', ')}`;
}
const edge: any = { ...transition, label };
edges.add(edge);
}
if (workflow.initial) {
edges.add({ from: 0, to: workflow.initial });
}
return { edges, nodes };
}
const GRAPH_OPTIONS = {

144
frontend/src/app/framework/angular/forms/editors/code-editor.component.ts

@ -8,11 +8,9 @@
import { AfterViewInit, booleanAttribute, ChangeDetectionStrategy, Component, ElementRef, forwardRef, Input, numberAttribute, ViewChild } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { debounceTime, Subject } from 'rxjs';
import { ResourceLoaderService, ScriptCompletions, StatefulControlComponent, TypedSimpleChanges, Types } from '@app/framework/internal';
import { ResourceLoaderService, ScriptCompletion, ScriptCompletions, StatefulControlComponent, TypedSimpleChanges, Types } from '@app/framework/internal';
import { FocusComponent } from '../forms-helper';
declare const ace: any;
export const SQX_CODE_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CodeEditorComponent), multi: true,
};
@ -28,11 +26,11 @@ export const SQX_CODE_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CodeEditorComponent extends StatefulControlComponent<{}, any> implements AfterViewInit, FocusComponent {
private aceEditor: any;
private aceTools: any;
private aceModes?: ModeList;
private aceEditor?: AceAjax.Editor;
private aceTools = false;
private valueChanged = new Subject();
private value = '';
private modelist: any;
private completions: ReadonlyArray<{ name: string; value: string }> = [];
@ViewChild('editor', { static: false })
@ -137,84 +135,88 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, any> imple
this.aceEditor.focus();
}
public ngAfterViewInit() {
public async ngAfterViewInit() {
this.valueChanged.pipe(debounceTime(500))
.subscribe(() => {
this.changeValue();
});
Promise.all([
await Promise.all([
this.resourceLoader.loadLocalScript('dependencies/ace/ace.js'),
this.resourceLoader.loadLocalScript('dependencies/ace/ext-modelist.js'),
this.resourceLoader.loadLocalScript('dependencies/ace/ext-language_tools.js'),
]).then(() => {
this.modelist = ace.require('ace/ext-modelist');
this.aceEditor = ace.edit(this.editor.nativeElement);
this.aceEditor.setFontSize(14);
this.aceTools = ace.require('ace/ext-language_tools');
this.setValue(this.value);
this.setMode();
this.setOptions();
this.setWordWrap();
this.onDisabled(this.snapshot.isDisabled);
if (this.aceTools) {
const previous = this.aceEditor.completers;
this.aceEditor.completers = [
previous?.[0], {
getCompletions: (editor: any, session: any, pos: any, prefix: any, callback: any) => {
callback(null, this.completions);
},
getDocTooltip: (item: any) => {
if (item.path && item.description) {
item.docHTML = `<b>${item.value}</b><hr></hr>${item.description}`;
]);
this.aceEditor = ace.edit(this.editor.nativeElement);
this.aceEditor.setFontSize('14px');
this.aceModes = ace.require('ace/ext-modelist');
this.aceTools = !!ace.require('ace/ext-language_tools');
this.setValue(this.value);
this.setMode();
this.setOptions();
this.setWordWrap();
this.onDisabled(this.snapshot.isDisabled);
if (this.aceTools) {
const previous = (this.aceEditor as any)['completers'] as AceAjax.Completer[];
(this.aceEditor as any)['completers'] = [
previous?.[0], {
getCompletions: (editor: any, session: any, pos: any, prefix: any, callback: any) => {
callback(null, this.completions);
},
getDocTooltip: (item: AceAjax.Completion) => {
const source = item as unknown as ScriptCompletion;
if (item.allowedValues) {
item.docHTML += '<div class="mt-2 mb-2">Allowed Values:<ul>';
if (source.path && source.description) {
item.docHTML = `<b>${item.value}</b><hr></hr>${source.description}`;
for (const value of item.allowedValues) {
item.docHTML += `<li><code>${value}</code></li>`;
}
if (source.allowedValues) {
item.docHTML += '<div class="mt-2 mb-2">Allowed Values:<ul>';
item.docHTML += '</ul></div>';
for (const value of source.allowedValues) {
item.docHTML += `<li><code>${value}</code></li>`;
}
if (item.deprecationReason) {
item.docHTML += `<div class="mt-2 mb-2"><strong>Deprecated</strong>: ${item.deprecationReason}</div>`;
}
item.docHTML += '</ul></div>';
}
},
// eslint-disable-next-line no-useless-escape
identifierRegexps: [/[a-zA-Z_0-9\$\-\.\u00A2-\u2000\u2070-\uFFFF]/],
},
];
}
this.aceEditor.on('blur', () => {
this.changeValue();
this.callTouched();
});
if (source.deprecationReason) {
item.docHTML += `<div class="mt-2 mb-2"><strong>Deprecated</strong>: ${source.deprecationReason}</div>`;
}
}
},
identifierRegexps: [/[a-zA-Z_0-9\$\-\.\u00A2-\u2000\u2070-\uFFFF]/],
} as AceAjax.Completer,
];
}
this.aceEditor.on('blur', () => {
this.changeValue();
this.aceEditor.on('change', () => {
this.valueChanged.next(true);
});
this.callTouched();
});
this.aceEditor.on('paste', (event: any) => {
if (this.singleLine) {
event.text = event.text.replace(/[\r\n]+/g, ' ');
}
});
this.aceEditor.on('change', () => {
this.valueChanged.next(true);
});
this.detach();
this.aceEditor.on('paste', (event: any) => {
if (this.singleLine) {
event.text = event.text.replace(/[\r\n]+/g, ' ');
}
});
this.detach();
}
private changeValue() {
if (!this.aceEditor) {
return;
}
let newValueText = this.aceEditor.getValue();
let newValueOut = newValueText;
let newValueOut: string | null = newValueText;
if (this.valueMode === 'Json') {
const isValid = this.aceEditor.getSession().getAnnotations().length === 0;
@ -252,8 +254,8 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, any> imple
return;
}
if (this.valueFile && this.modelist) {
const mode = this.modelist.getModeForPath(this.valueFile).mode;
if (this.valueFile && this.aceModes) {
const mode = this.aceModes.getModeForPath(this.valueFile).mode;
this.aceEditor.getSession().setMode(mode);
} else {
@ -286,9 +288,9 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, any> imple
this.aceEditor.setOptions({
autoScrollEditorIntoView: this.singleLine,
enableBasicAutocompletion: !!this.aceTools,
enableLiveAutocompletion: !!this.aceTools,
enableSnippets: !!this.aceTools && !this.singleLine && this.snippets,
enableBasicAutocompletion: this.aceTools,
enableLiveAutocompletion: this.aceTools,
enableSnippets: this.aceTools && !this.singleLine && this.snippets,
highlightActiveLine: !this.singleLine,
maxLines,
minLines,
@ -296,11 +298,15 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, any> imple
showGutter: !this.singleLine,
});
this.aceEditor.commands.bindKey('Enter|Shift-Enter', this.singleLine ? 'null' : undefined);
this.aceEditor.commands.bindKey('Enter|Shift-Enter', this.singleLine ? 'null' : undefined as any);
}
private setValue(value: string) {
this.aceEditor.setValue(value);
this.aceEditor.clearSelection();
this.aceEditor?.setValue(value);
this.aceEditor?.clearSelection();
}
}
interface ModeList {
getModeForPath(path: string): { mode: string };
}

16
frontend/src/app/framework/angular/video-player.component.ts

@ -49,18 +49,18 @@ export class VideoPlayerComponent implements AfterViewInit, OnDestroy {
}
}
public ngAfterViewInit() {
Promise.all([
public async ngAfterViewInit() {
await Promise.all([
this.resourceLoader.loadLocalScript('dependencies/videojs/video.min.js'),
this.resourceLoader.loadLocalStyle('dependencies/videojs/videojs.min.css'),
]).then(() => {
this.renderer.removeClass(this.video.nativeElement, 'hidden');
]);
this.player = videojs(this.video.nativeElement, {
fluid: true,
});
this.renderer.removeClass(this.video.nativeElement, 'hidden');
this.ngOnChanges();
this.player = videojs(this.video.nativeElement, {
fluid: true,
});
this.ngOnChanges();
}
}

165
frontend/src/app/shared/components/forms/geolocation-editor.component.ts

@ -175,112 +175,111 @@ export class GeolocationEditorComponent extends StatefulControlComponent<State,
}
}
private ngAfterViewInitOSM() {
private async ngAfterViewInitOSM() {
this.searchBoxInput.nativeElement.remove();
Promise.all([
this.resourceLoader.loadLocalStyle('dependencies/leaflet/Control.Geocoder.css'),
await Promise.all([
this.resourceLoader.loadLocalStyle('dependencies/leaflet/leaflet.css'),
this.resourceLoader.loadLocalScript('dependencies/leaflet/leaflet.js'),
]).then(() => {
this.map = L.map(this.editor.nativeElement).fitWorld();
L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png',
{
attribution: '&copy; <a href="https://osm.org/copyright">OpenStreetMap</a> contributors',
}).addTo(this.map);
this.resourceLoader.loadLocalScript('dependencies/leaflet/Control.Geocoder.min.js')
.then(() => {
L.Control.geocoder({
defaultMarkGeocode: false,
})
.on('markgeocode', (event: any) => {
const center = event.geocode.center;
if (!this.snapshot.isDisabled) {
this.updateValue(center.lat, center.lng);
this.updateMarker({ reset: true, fire: true });
}
})
.addTo(this.map);
});
]);
this.map.on('click', (event: any) => {
if (!this.snapshot.isDisabled) {
const latlng = event.latlng.wrap();
await Promise.all([
this.resourceLoader.loadLocalStyle('dependencies/leaflet/Control.Geocoder.css'),
this.resourceLoader.loadLocalScript('dependencies/leaflet/Control.Geocoder.min.js'),
]);
this.updateValue(latlng.lat, latlng.lng);
this.updateMarker({ fire: true });
}
});
this.map = L.map(this.editor.nativeElement).fitWorld();
this.updateMarker({ reset: true });
L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png',
{
attribution: '&copy; <a href="https://osm.org/copyright">OpenStreetMap</a> contributors',
}).addTo(this.map);
if (this.snapshot.isDisabled) {
this.map.zoomControl.disable();
L.Control.geocoder({
defaultMarkGeocode: false,
})
.on('markgeocode', (event: any) => {
const center = event.geocode.center;
this.map._handlers.forEach((handler: any) => {
handler.disable();
});
if (!this.snapshot.isDisabled) {
this.updateValue(center.lat, center.lng);
this.updateMarker({ reset: true, fire: true });
}
})
.addTo(this.map);
this.map.on('click', (event: any) => {
if (!this.snapshot.isDisabled) {
const latlng = event.latlng.wrap();
this.updateValue(latlng.lat, latlng.lng);
this.updateMarker({ fire: true });
}
});
this.updateMarker({ reset: true });
if (this.snapshot.isDisabled) {
this.map.zoomControl.disable();
this.map._handlers.forEach((handler: any) => {
handler.disable();
});
}
}
private ngAfterViewInitGoogle(apiKey: string) {
this.resourceLoader.loadScript(`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`)
.then(() => {
this.map = new google.maps.Map(this.editor.nativeElement,
{
zoom: 1,
fullscreenControl: false,
mapTypeControl: false,
mapTypeControlOptions: {},
streetViewControl: false,
center: { lat: 0, lng: 0 },
});
const searchBox = new google.maps.places.SearchBox(this.searchBoxInput.nativeElement);
this.map.addListener('click', (event: any) => {
if (!this.snapshot.isDisabled) {
const latlng = event.latLng;
private async ngAfterViewInitGoogle(apiKey: string) {
await this.resourceLoader.loadScript(`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`);
this.updateValue(latlng.lat(), latlng.lng());
this.updateMarker({ fire: true });
}
});
this.map = new google.maps.Map(this.editor.nativeElement, {
zoom: 1,
fullscreenControl: false,
mapTypeControl: false,
mapTypeControlOptions: {},
streetViewControl: false,
center: { lat: 0, lng: 0 },
});
this.map.addListener('bounds_changed', () => {
searchBox.setBounds(this.map.getBounds());
});
const searchBox = new google.maps.places.SearchBox(this.searchBoxInput.nativeElement);
searchBox.addListener('places_changed', () => {
const places = searchBox.getPlaces();
this.map.addListener('click', (event: any) => {
if (!this.snapshot.isDisabled) {
const latlng = event.latLng;
if (places.length === 1) {
const place = places[0];
this.updateValue(latlng.lat(), latlng.lng());
this.updateMarker({ fire: true });
}
});
if (!place.geometry) {
return;
}
this.map.addListener('bounds_changed', () => {
searchBox.setBounds(this.map.getBounds());
});
if (!this.snapshot.isDisabled) {
const lat = place.geometry.location.lat();
const lng = place.geometry.location.lng();
searchBox.addListener('places_changed', () => {
const places = searchBox.getPlaces();
this.updateValue(lat, lng);
this.updateMarker({ pan: true, fire: true });
}
}
});
if (places.length === 1) {
const place = places[0];
this.updateMarker({ reset: true });
if (!place.geometry) {
return;
}
if (this.snapshot.isDisabled) {
this.map.setOptions({ draggable: false, zoomControl: false });
if (!this.snapshot.isDisabled) {
const lat = place.geometry.location.lat();
const lng = place.geometry.location.lng();
this.updateValue(lat, lng);
this.updateMarker({ pan: true, fire: true });
}
});
}
});
this.updateMarker({ reset: true });
if (this.snapshot.isDisabled) {
this.map.setOptions({ draggable: false, zoomControl: false });
}
}
public clearValue() {

118
frontend/src/app/shared/components/forms/rich-editor.component.ts

@ -110,64 +110,66 @@ export class RichEditorComponent extends StatefulControlComponent<{}, string> im
}
}
public ngAfterViewInit() {
this.resourceLoader.loadLocalStyle('editor/squidex-editor.css');
this.resourceLoader.loadLocalScript('editor/squidex-editor.js').then(() => {
this.editorWrapper = new SquidexEditorWrapper(this.editor.nativeElement, {
value: this.value || '',
isDisabled: this.snapshot.isDisabled,
onSelectAIText: async () => {
if (this.snapshot.isDisabled) {
return;
}
this.currentChat = new ResolvablePromise<string | undefined | null>();
this.chatDialog.show();
return await this.currentChat.promise;
},
onSelectAssets: async () => {
if (this.snapshot.isDisabled) {
return;
}
this.currentAssets = new ResolvablePromise<any>();
this.assetsDialog.show();
return await this.currentAssets.promise;
},
onSelectContents: async () => {
if (this.snapshot.isDisabled) {
return;
}
this.currentContents = new ResolvablePromise<any>();
this.contentsDialog.show();
return await this.currentContents.promise;
},
onUpload: (requests: UploadRequest[]) => {
return this.uploadFiles(requests);
},
onChange: (value: string | undefined) => {
this.callChange(value);
},
onEditContent: (schemaName, id) => {
const url = this.apiUrl.buildUrl(`/app/${this.appsState.appName}/content/${schemaName}/${id}`);
window.open(url, '_blank');
},
onEditAsset: id => {
this.assetId.next(id);
},
appName: this.appsState.appName,
baseUrl: this.apiUrl.buildUrl(''),
canSelectAIText: this.hasChatBot,
canSelectAssets: true,
canSelectContents: !!this.schemaIds,
classNames: this.classNames,
mode: this.mode,
});
public async ngAfterViewInit() {
await Promise.all([
this.resourceLoader.loadLocalStyle('editor/squidex-editor.css'),
this.resourceLoader.loadLocalScript('editor/squidex-editor.js'),
]);
this.editorWrapper = new SquidexEditorWrapper(this.editor.nativeElement, {
onSelectAIText: async () => {
if (this.snapshot.isDisabled) {
return;
}
this.currentChat = new ResolvablePromise<string | undefined | null>();
this.chatDialog.show();
return await this.currentChat.promise;
},
onSelectAssets: async () => {
if (this.snapshot.isDisabled) {
return;
}
this.currentAssets = new ResolvablePromise<any>();
this.assetsDialog.show();
return await this.currentAssets.promise;
},
onSelectContents: async () => {
if (this.snapshot.isDisabled) {
return;
}
this.currentContents = new ResolvablePromise<any>();
this.contentsDialog.show();
return await this.currentContents.promise;
},
onUpload: (requests: UploadRequest[]) => {
return this.uploadFiles(requests);
},
onChange: (value: string | undefined) => {
this.callChange(value);
},
onEditContent: (schemaName, id) => {
const url = this.apiUrl.buildUrl(`/app/${this.appsState.appName}/content/${schemaName}/${id}`);
window.open(url, '_blank');
},
onEditAsset: id => {
this.assetId.next(id);
},
mode: this.mode,
appName: this.appsState.appName,
baseUrl: this.apiUrl.buildUrl(''),
canSelectAIText: this.hasChatBot,
canSelectAssets: true,
canSelectContents: !!this.schemaIds,
classNames: this.classNames,
isDisabled: this.snapshot.isDisabled,
value: this.value || '',
});
}

10
frontend/src/app/shared/services/auth.service.ts

@ -170,8 +170,10 @@ export class AuthService {
throw new Error('Retry limit exceeded.');
}
private checkState(promise: Promise<User | null>) {
promise.then(user => {
private async checkState(promise: Promise<User | null>) {
try {
const user = await promise;
if (user) {
this.user$.next(getProfile(user));
} else {
@ -179,11 +181,11 @@ export class AuthService {
}
return true;
}, _ => {
} catch {
this.user$.next(null);
return false;
});
}
}
}

1
frontend/tsconfig.app.json

@ -2,7 +2,6 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts"

Loading…
Cancel
Save