-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #124 from uatisdeproblem/103-home-communications-oage
feat: Add home/communications page #103
- Loading branch information
Showing
25 changed files
with
1,174 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/// | ||
/// IMPORTS | ||
/// | ||
|
||
import { DynamoDB, HandledError, ResourceController, S3 } from 'idea-aws'; | ||
import { SignedURL } from 'idea-toolbox'; | ||
|
||
import { User } from '../models/user.model'; | ||
|
||
/// | ||
/// CONSTANTS, ENVIRONMENT VARIABLES, HANDLER | ||
/// | ||
|
||
const PROJECT = process.env.PROJECT; | ||
const ddb = new DynamoDB(); | ||
|
||
const DDB_TABLES = { users: process.env.DDB_TABLE_users }; | ||
|
||
const S3_BUCKET_MEDIA = process.env.S3_BUCKET_MEDIA; | ||
const S3_IMAGES_FOLDER = process.env.S3_IMAGES_FOLDER; | ||
const s3 = new S3(); | ||
|
||
export const handler = (ev: any, _: any, cb: any): Promise<void> => new Media(ev, cb).handleRequest(); | ||
|
||
/// | ||
/// RESOURCE CONTROLLER | ||
/// | ||
|
||
class Media extends ResourceController { | ||
user: User; | ||
|
||
constructor(event: any, callback: any) { | ||
super(event, callback); | ||
} | ||
|
||
protected async checkAuthBeforeRequest(): Promise<void> { | ||
try { | ||
this.user = new User(await ddb.get({ TableName: DDB_TABLES.users, Key: { userId: this.principalId } })); | ||
} catch (err) { | ||
throw new HandledError('User not found'); | ||
} | ||
|
||
if (!this.user.permissions.isAdmin) throw new HandledError('Unauthorized'); | ||
} | ||
|
||
protected async postResources(): Promise<SignedURL> { | ||
const imageURI = await ddb.IUNID(PROJECT.concat('-media')); | ||
|
||
const key = `${S3_IMAGES_FOLDER}/${imageURI}.png`; | ||
const signedURL = await s3.signedURLPut(S3_BUCKET_MEDIA, key); | ||
signedURL.id = imageURI; | ||
|
||
return signedURL; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { CommonModule } from '@angular/common'; | ||
import { FormsModule } from '@angular/forms'; | ||
import { | ||
Component, | ||
EventEmitter, | ||
Input, | ||
OnChanges, | ||
OnInit, | ||
Output, | ||
SimpleChanges, | ||
inject, | ||
SecurityContext | ||
} from '@angular/core'; | ||
import { DomSanitizer } from '@angular/platform-browser'; | ||
import { IonicModule } from '@ionic/angular'; | ||
import { AngularEditorConfig, AngularEditorModule } from '@kolkov/angular-editor'; | ||
import { docsSoap } from 'docs-soap'; | ||
import { HttpClientModule } from '@angular/common/http'; | ||
|
||
@Component({ | ||
standalone: true, | ||
imports: [CommonModule, FormsModule, IonicModule, HttpClientModule, AngularEditorModule], | ||
selector: 'app-html-editor', | ||
template: ` | ||
@if(editMode){ | ||
<angular-editor | ||
[(ngModel)]="text" | ||
[config]="editorConfig" | ||
(input)="contentChange.emit($event.target.innerHTML)" | ||
(paste)="cleanHTMLCode()" | ||
/> | ||
}@else { | ||
<div class="view" [innerHTML]="sanitizedHtml"></div> | ||
} | ||
`, | ||
styles: [ | ||
` | ||
div.view { | ||
margin: var(--app-html-editor-margin, 0); | ||
padding: var(--app-html-editor-padding, 20px); | ||
background-color: var(--app-html-editor-background-color, var(--ion-color-white)); | ||
color: var(--app-html-editor-color, var(--ion-color-text)); | ||
box-shadow: var(--app-html-editor-box-shadow, none); | ||
border: var(--app-html-editor-border-width, 1px) solid var(--ion-border-color); | ||
border-radius: var(--app-html-editor-border-radius, 0); | ||
} | ||
` | ||
] | ||
}) | ||
export class HTMLEditorComponent implements OnInit, OnChanges { | ||
/** | ||
* Whether the parent page is in editMode or not (simplified). | ||
*/ | ||
@Input() editMode = false; | ||
/** | ||
* The HTML content. | ||
*/ | ||
@Input() content: string; | ||
/** | ||
* Trigger when the HTML content changes. | ||
*/ | ||
@Output() contentChange = new EventEmitter<string>(); | ||
|
||
text: string; | ||
|
||
editorConfig: AngularEditorConfig = { | ||
editable: true, | ||
spellcheck: false, | ||
sanitize: true, | ||
rawPaste: false, | ||
minHeight: '300px', | ||
toolbarHiddenButtons: [ | ||
[ | ||
'subscript', | ||
'superscript', | ||
'justifyLeft', | ||
'justifyCenter', | ||
'justifyRight', | ||
'justifyFull', | ||
'heading', | ||
'fontName' | ||
], | ||
[ | ||
'fontSize', | ||
'textColor', | ||
'backgroundColor', | ||
'customClasses', | ||
'insertImage', | ||
'insertVideo', | ||
'insertHorizontalRule', | ||
'toggleEditorMode' | ||
] | ||
] | ||
}; | ||
|
||
sanitizedHtml: string; | ||
|
||
_sanitizer = inject(DomSanitizer); | ||
|
||
ngOnInit(): void { | ||
this.text = this.content; | ||
} | ||
ngOnChanges(changes: SimpleChanges): void { | ||
if (changes.editMode) this.sanitizedHtml = this._sanitizer.sanitize(SecurityContext.HTML, this.content); | ||
} | ||
|
||
cleanHTMLCode(): void { | ||
setTimeout((): void => { | ||
this.text = docsSoap(this.text); | ||
this.contentChange.emit(this.text); | ||
}, 100); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Injectable } from '@angular/core'; | ||
import { IDEAApiService } from '@idea-ionic/common'; | ||
|
||
@Injectable({ providedIn: 'root' }) | ||
export class MediaService { | ||
constructor(private api: IDEAApiService) {} | ||
|
||
/** | ||
* Upload a new image and get its URI. | ||
*/ | ||
async uploadImage(file: File): Promise<string> { | ||
const { url, id } = await this.api.postResource('media'); | ||
await fetch(url, { method: 'PUT', body: file, headers: { 'Content-Type': file.type } }); | ||
return id; | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
front-end/src/app/tabs/home/communications/communication.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { CommonModule } from '@angular/common'; | ||
import { Component, EventEmitter, Input, Output } from '@angular/core'; | ||
import { IonicModule } from '@ionic/angular'; | ||
import { IDEATranslationsModule } from '@idea-ionic/common'; | ||
|
||
import { AppService } from 'src/app/app.service'; | ||
|
||
import { Communication } from '@models/communication.model'; | ||
|
||
@Component({ | ||
standalone: true, | ||
imports: [CommonModule, IonicModule, IDEATranslationsModule], | ||
selector: 'app-communication', | ||
template: ` | ||
<ion-card [color]="color" *ngIf="!communication"> | ||
<ion-skeleton-text animated style="height: 180px"></ion-skeleton-text> | ||
<ion-card-header> | ||
<ion-card-subtitle> | ||
<ion-skeleton-text animated style="width: 30%"></ion-skeleton-text> | ||
</ion-card-subtitle> | ||
<ion-card-title><ion-skeleton-text animated style="width: 60%"></ion-skeleton-text></ion-card-title> | ||
<ion-card-subtitle><ion-skeleton-text animated style="width: 80%"></ion-skeleton-text></ion-card-subtitle> | ||
</ion-card-header> | ||
</ion-card> | ||
<ion-card *ngIf="communication" [color]="color" [button]="button" (click)="select.emit()"> | ||
<ion-img *ngIf="communication.imageURI" [src]="app.getImageURLByURI(communication.imageURI)"></ion-img> | ||
<ion-card-header> | ||
<ion-card-subtitle>{{ communication.publishedAt | dateLocale : 'longDate' }}</ion-card-subtitle> | ||
<ion-card-title> | ||
<ion-text color="medium" style="font-weight: 600"> | ||
{{ communication.title }} | ||
</ion-text> | ||
</ion-card-title> | ||
</ion-card-header> | ||
<ion-card-content><ng-content></ng-content></ion-card-content> | ||
</ion-card> | ||
`, | ||
styles: [ | ||
` | ||
ion-card { | ||
margin: 0 4px 16px 4px; | ||
max-width: 600px; | ||
} | ||
ion-img { | ||
object-fit: cover; | ||
height: 180px; | ||
border-bottom: 1px solid var(--ion-color-light); | ||
} | ||
ion-card-subtitle:first-of-type { | ||
color: var(--ion-color-step-500); | ||
} | ||
ion-card-subtitle:nth-of-type(2) { | ||
margin-top: 4px; | ||
font-weight: 400; | ||
} | ||
` | ||
] | ||
}) | ||
export class CommunicationComponent { | ||
/** | ||
* The communication to show; if not set, shows a skeleton instead. | ||
*/ | ||
@Input() communication: Communication; | ||
/** | ||
* The color for the component. | ||
*/ | ||
@Input() color = 'white'; | ||
/** | ||
* Whether the component should act like a button. | ||
*/ | ||
@Input() button = false; | ||
/** | ||
* Trigger when selected. | ||
*/ | ||
@Output() select = new EventEmitter<void>(); | ||
|
||
constructor(public app: AppService) {} | ||
} |
Oops, something went wrong.