Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8e174ff
Merge pull request #451 from intersective/feature/CORE-6655/typing-ev…
sasangachathumal Jul 22, 2024
98ab31f
Merge branch 'prerelease' into golive/2.4
shawnm0705 Jul 25, 2024
c0faefa
Merge pull request #436 from intersective/golive/2.4
shawnm0705 Jul 25, 2024
ede8744
Merge pull request #456 from intersective/golive/2.3.2.7
shawnm0705 Jul 29, 2024
89fd8ad
[CORE-6710]
sasangachathumal Jul 29, 2024
1d47d31
[CORE-6710]
sasangachathumal Jul 30, 2024
fa3dc4f
[CORE-6718]
sasangachathumal Aug 1, 2024
0df2522
[CORE-6719]
sasangachathumal Aug 1, 2024
f301573
Merge pull request #457 from intersective/bugfix/CORE-6710/Invalid-va…
sasangachathumal Aug 1, 2024
cebd414
Merge pull request #460 from intersective/bugfix/CORE-6718/Announceme…
sasangachathumal Aug 1, 2024
b2afbbe
Merge pull request #462 from intersective/bugfix/CORE-6719/typo-issue…
sasangachathumal Aug 1, 2024
575499e
[CORE-6710]
sasangachathumal Aug 5, 2024
8c66998
Merge pull request #464 from intersective/bugfix/CORE-6710/Invalid-va…
jazzmind Aug 8, 2024
13f0324
Merge pull request #453 from intersective/feature/CORE-6606/use-graph…
sasangachathumal Aug 15, 2024
d9ee196
fix: prerelease update ci workflow versions actions
rodentskie Sep 5, 2024
cb1c72f
Merge pull request #474 from intersective/fix/upgrade-ci-workflow-ver…
shawnm0705 Sep 5, 2024
3f8ce11
[CORE-7750] FileResponse and fileObject input
trtshen Feb 17, 2025
3401ca7
[CORE-7750] mimetype to type
trtshen Feb 17, 2025
305de85
Merge remote-tracking branch 'origin/trunk' into 2.4.4/CORE-7750/file…
trtshen Feb 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
indent_size = 2
indent_style = space
trim_trailing_whitespace = true
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,30 @@
1. Ignore the `.localsegment` error during compilation (it's for devops purpose)
1. Start coding!

### Routes

1. Open the `app-routing.module.ts` file.
2. Find the available routing URLs.
3. Manually update the URL in your browser to access the desired page.

### Sandbox API environment (option 1)

1. Copy the "me" object from the localStorage from our app or core-admin
1. Paste it in your running local cutie-app's localStorage with the same key ("me")
1. There is no content in the root "/"
1. relative-url must be added after the `https://localhost:4202/` in order to see the content that you expect to see
1. For example, `https://localhost:4202/overview-only` will redirect you to *overview-only* route.
1. You would not redirected and will get stuck in `https://localhost:4202` when there is no relative URL appended.
1. Copy the "me" object from the localStorage of our app or core-admin.
2. Paste it into the localStorage of your running local cutie-app with the same key ("me").
3. Ensure there is no content in the root "/".
4. Add a relative URL after `https://localhost:4202/` to see the expected content.
- For example, `https://localhost:4202/overview-only` will redirect you to the *overview-only* route.
- Without a relative URL, you will get stuck at `https://localhost:4202`.

> Note: Use port 4202 if running `npm start`, or change it according to how you serve the app locally.

> Note: If you prefer to access a specific page, please refer to the [Routes](#routes) section above.

### Sandbox API environment (option 2)

1. Follow this URL format: https://localhost:4202/?redirect=overview-only&jwt={OUR-APP-APIKEY}
1. Get the APIKEY from the already authorised app user localStorage or HTTP header and replace {OUR-APP-APIKEY} with the obtained APIKEY
1. You'll be redirect to the specified "redirect" destination.
1. Use the following URL format: `https://localhost:4202/?redirect=overview-only&jwt={OUR-APP-APIKEY}`
2. Retrieve the APIKEY from the localStorage or HTTP header of an already authorised app user and replace `{OUR-APP-APIKEY}` with the obtained APIKEY.
3. You will be redirected to the specified "redirect" destination.

## Installation

Expand Down
4 changes: 3 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ import { urlFormatter } from 'helper';
NgbModule
],
providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
{
provide: RouteReuseStrategy, useClass: IonicRouteStrategy
},
UtilsService
],
bootstrap: [AppComponent]
Expand Down
84 changes: 43 additions & 41 deletions src/app/chat/chat-room/chat-room.component.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<ion-header mode="ios">
<ion-header mode="ios" aria-label="chat room">
<ion-toolbar class="ion-toolbar-absolute">
<ion-title class="ion-text-center subtitle-2">{{ chatChannel.name }}</ion-title>
<ion-button slot="end" fill="clear" size="small" (click)="openChatInfo()">
<ion-icon color="primary" slot="icon-only" name="information-circle"></ion-icon>
<ion-button slot="end" fill="clear" size="small" (click)="openChatInfo()" aria-label="chatroom info">
<ion-icon color="primary" slot="icon-only" name="information-circle" aria-hidden="true"></ion-icon>
</ion-button>
</ion-toolbar>
<ion-toolbar *ngIf="isScheduleListOpen || chatChannel.scheduledMessageCount > 0" class="schedule-message-banner">
Expand All @@ -23,25 +23,25 @@
class="ion-content-absolute-with-footer" color="light" [scrollEvents]="true"
(ionScroll)="loadMoreMessages($event)">

<div *ngIf="loadingChatMessages" class="ion-text-center">
<div *ngIf="loadingChatMessages" class="ion-text-center" aria-label="loading message">
<ion-spinner></ion-spinner>
</div>

<div *ngIf="!messageList.length && !loadingChatMessages" class="ion-text-center not-started-empty-status">
<img class="image desktop" src="/assets/icon-epmty-chat.svg">
<p class="head-text headline-5 gray-3">Type your first message!</p>
<p class="sub-text subtitle-1 gray-1">It's time to start a chat</p>
<div *ngIf="!messageList.length && !loadingChatMessages" class="ion-text-center not-started-empty-status" aria-live="polite">
<img class="image desktop" src="/assets/icon-epmty-chat.svg" alt="empty chat" i18n-alt aria-hidden="true">
<p class="head-text headline-5 gray-3" i18n>Type your first message!</p>
<p class="sub-text subtitle-1 gray-1" i18n>It's time to start a chat</p>
</div>

<ion-list lines="none" color="light" class="chat-list desktop-view ion-no-padding">
<ion-item *ngFor="let message of messageList; let index=index" color="light">
<ng-container *ngIf="isLastMessage(message)">
<ion-avatar [ngClass]="getAvatarClass(message)" slot="start">
<img [src]="message.senderAvatar">
<img [src]="message.senderAvatar" alt="sender avatar">
</ion-avatar>
</ng-container>

<ion-label [ngClass]="getClassForMessageBubble(message)">
<ion-label [ngClass]="getClassForMessageBubble(message)" text-wrap>

<div *ngIf="checkToShowMessageTime(message)" class="time caption gray-2">
<p>{{getMessageDate(message.sentAt)}}</p>
Expand All @@ -53,53 +53,55 @@
<ion-button class="delete-btn" fill="clear" size="small" title="Delete Message" (click)="deleteMessage(message.uuid)">
<ion-icon name="trash" slot="icon-only"></ion-icon>
</ion-button>
<ion-button fill="clear" size="small" title="Edit Message" *ngIf="!message.fileObject" (click)="openEditMessagePopup(index)">
<ion-button fill="clear" size="small" title="Edit Message" *ngIf="!message.file" (click)="openEditMessagePopup(index)">
<ion-icon name="create" slot="icon-only"></ion-icon>
</ion-button>
</div>

<div class="message-body" [ngClass]="getClassForMessageBody(message)">
<div class="message-body" [ngClass]="getClassForMessageBody(message)" aria-describedby="message-content">
<ng-container *ngIf="!message.isSender">
<p class="seen-text subtitle-1 black">{{message.senderName}}</p>
<p class="seen-text caption gray-2">{{ utils.getUserRolesForUI(message.senderRole) }}</p>
</ng-container>

<ng-container *ngIf="message.message || !message.fileObject;">
<ng-container *ngIf="message.message || !message.file;">
<!-- Quill read only view/ use to render html content -->
<quill-view [content]="message.message"></quill-view>
<quill-view id="message-content" [content]="message?.message" [sanitize]="true" tabindex="0"></quill-view>
</ng-container>

<ng-container *ngIf="message.fileObject">
<ng-container *ngIf="message.fileObject && message.fileObject.mimetype && message.fileObject.mimetype.includes('video')">
<ng-container *ngIf="message?.fileObject?.mimetype === 'video/mp4'; else nonMp4Format">
<p class="label">
{{ message.fileObject.filename }}
</p>
<div id="inner-box" (click)="preview(message.fileObject)">
<p>
<ion-icon name="play-circle"></ion-icon>
<ng-container *ngIf="message?.file?.type">
<ng-container *ngIf="message?.file?.type?.includes('video')">
<ng-container *ngIf="message?.file?.type === 'video/mp4'; else nonMp4Format">
<div id="message-content" class="label" (click)="preview(message.file)" (keydown)="preview(message.file)" tabindex="0" role="button">
<p class="label">
{{ message.file.name }}
</p>
<div id="inner-box">
<p>
<ion-icon name="play-circle"></ion-icon>
</p>
</div>
</div>
</ng-container>
<ng-template #nonMp4Format>
<app-video-conversion [video]="message" (preview)="preview($event)"></app-video-conversion>
</ng-template>
</ng-container>

<ng-container *ngIf="message.fileObject && message.fileObject.mimetype && message.fileObject.mimetype.includes('image')">
<div [innerHTML]="message.preview" (click)="preview(message.fileObject)"></div>
<ng-container *ngIf="message?.file?.type?.includes('image')">
<div [innerHTML]="message.preview" (click)="preview(message.file)" (keydown)="preview(message.file)" tabindex="0" role="button"></div>
</ng-container>

<ng-container *ngIf="(message.fileObject && !message.fileObject.mimetype) || (message.fileObject && (
!message.fileObject.mimetype.includes('image') && !message.fileObject.mimetype.includes('video')))">
<ion-item class="general-attachment" (click)="previewFile(message.fileObject)">
<ng-container *ngIf="(message.file && !message.file.type) || (message.file && (
!message.file.type.includes('image') && !message.file.type.includes('video')))">
<ion-item class="general-attachment" (click)="previewFile(message.file)">
<ion-ripple-effect></ion-ripple-effect>
<ion-icon name="document" slot="start"></ion-icon>
<ion-icon name="document-outline" slot="start"></ion-icon>
<ion-label color="primary">
{{ message.fileObject.filename }}
{{ message.file.name }}
</ion-label>
<ion-note *ngIf="getTypeByMime(message.fileObject.mimetype)">
{{ getTypeByMime(message.fileObject.mimetype) }}
<ion-note *ngIf="getTypeByMime(message.file.type)">
{{ getTypeByMime(message.file.type) }}
</ion-note>
</ion-item>
</ng-container>
Expand All @@ -111,10 +113,10 @@
</ion-label>
</ion-item>

<ng-container *ngIf="whoIsTyping">
<ion-item color="light">
<ion-label class="received-messages no-avatar" color="medium">
<p class="message-typing">
<ng-container *ngIf="whoIsTyping">
<ion-item color="light">
<ion-label class="received-messages no-avatar" color="medium">
<p class="message-typing">
<i>{{ whoIsTyping }}</i>
<ion-spinner name="dots" class="vertical-middle"></ion-spinner>
</p>
Expand Down Expand Up @@ -162,7 +164,7 @@
<ion-row nowrap class="ion-no-padding ion-align-items-left ion-justify-content-start attach-preview">
<ng-container *ngFor="let attachment of selectedAttachments; let i = index">
<!-- Image preview -->
<ion-col size="4" size-lg="2" class="ion-no-padding" *ngIf="attachment.mimetype && attachment.mimetype.includes('image')">
<ion-col size="4" size-lg="2" class="ion-no-padding" *ngIf="attachment.type && attachment.type.includes('image')">
<div class="image">
<img [src]="getResizedImageUrl(attachment, 80)">
<div class="overlay" (click)="preview(attachment)"></div>
Expand All @@ -172,7 +174,7 @@
</div>
</ion-col>
<!-- video preview -->
<ion-col size="4" size-lg="2" class="ion-no-padding" *ngIf="attachment.mimetype && attachment.mimetype.includes('video')">
<ion-col size="4" size-lg="2" class="ion-no-padding" *ngIf="attachment.type && attachment.type.includes('video')">
<div class="video">
<div class="play-icon">
<ion-icon name="play" color="ocean"></ion-icon>
Expand All @@ -184,14 +186,14 @@
</div>
</ion-col>
<!-- File preview -->
<ion-col size="10" size-lg="6" class="ion-no-padding ion-text-center" *ngIf="!attachment.mimetype || !attachment.mimetype.includes('image') && !attachment.mimetype.includes('video')">
<ion-col size="10" size-lg="6" class="ion-no-padding ion-text-center" *ngIf="!attachment.type || !attachment.type.includes('image') && !attachment.type.includes('video')">
<div class="file-preview">
<ion-item lines="none" class="general-attachment" (click)="previewFile(attachment)">
<ion-ripple-effect></ion-ripple-effect>
<ion-icon name="document" slot="start"></ion-icon>
<ion-label color="black">
<p>{{ attachment.filename }}</p>
<p class="mimetype" *ngIf="getTypeByMime(attachment.mimetype)">{{ getTypeByMime(attachment.mimetype) }}</p>
<p>{{ attachment.name }}</p>
<p class="mimetype" *ngIf="getTypeByMime(attachment.type)">{{ getTypeByMime(attachment.type) }}</p>
</ion-label>
</ion-item>
<div class="overlay" (click)="preview(attachment)"></div>
Expand Down
59 changes: 20 additions & 39 deletions src/app/chat/chat-room/chat-room.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { PusherService, SendMessageParam } from '@shared/pusher/pusher.service';
import { FilestackService } from '@shared/filestack/filestack.service';
import { PopupService } from '@shared/popup/popup.service';

import { ChatService, ChatChannel, Message, MessageListResult } from '../chat.service';
import { ChatService, ChatChannel, Message, MessageListResult, FileResponse } from '../chat.service';
import { ChatPreviewComponent } from '../chat-preview/chat-preview.component';
import { ChatInfoComponent } from '../chat-info/chat-info.component';
import { ScheduleMessagePopupComponent } from '../schedule-message-popup/schedule-message-popup.component';
Expand Down Expand Up @@ -53,13 +53,12 @@ export class ChatRoomComponent {
whoIsTyping: string;
isScheduleListOpen: boolean;

selectedAttachments: any[] = [];
selectedAttachments: FileResponse[] = [];

constructor(
private chatService: ChatService,
public router: Router,
public storage: StorageService,
private route: ActivatedRoute,
public utils: UtilsService,
public pusherService: PusherService,
private filestackService: FilestackService,
Expand Down Expand Up @@ -177,13 +176,7 @@ export class ChatRoomComponent {
sentAt: data.sentAt
};
if (receivedMessage && receivedMessage.file) {
let fileObject = null;
fileObject = typeof receivedMessage.file === 'string' ? JSON.parse(receivedMessage.file) : receivedMessage.file;
if (this.utils.isEmpty(fileObject)) {
fileObject = null;
}
receivedMessage.fileObject = fileObject;
receivedMessage.preview = this.attachmentPreview(receivedMessage.fileObject);
receivedMessage.preview = this.attachmentPreview(receivedMessage.file);
}
if (receivedMessage.senderUuid &&
this.storage.getUser().uuid &&
Expand Down Expand Up @@ -222,8 +215,8 @@ export class ChatRoomComponent {
this.messagePageCursor = messageListResult.cursor;
this.loadingChatMessages = false;
messages = messages.map(msg => {
if (msg.file && msg.fileObject) {
msg.preview = this.attachmentPreview(msg.fileObject);
if (msg.file) {
msg.preview = this.attachmentPreview(msg.file);
}
return msg;
});
Expand Down Expand Up @@ -264,30 +257,24 @@ export class ChatRoomComponent {
}
}

private getPostMessageParams(type, file?: any) {
private getPostMessageParams(type: 'text' | 'file', file?: FileResponse) {
if (type === 'text') {
if (!this.message || this.utils.isQuillContentEmpty(this.message)) {
return;
}
const message = this.message;
return {
channelUuid: this.channelUuid,
message: message
message: this.message
};
}
if (type === 'file' && file) {
if (!file.mimetype) {
file.mimetype = '';
}
const message = this.message;
return {
channelUuid: this.channelUuid,
message: message,
file: JSON.stringify(file)
message: this.message,
file: file
};
} else {
return;
}
return;
}

postTextOnlyMessage() {
Expand Down Expand Up @@ -329,23 +316,20 @@ export class ChatRoomComponent {
});
}

triggerPusherEvent(response, file?: any) {
triggerPusherEvent(response, file?: FileResponse) {
const pusherData: SendMessageParam = {
channelUuid: this.channelUuid,
uuid: response.uuid,
isSender: response.isSender,
message: response.message,
file: response.file,
file: file || response.file,
created: response.created,
senderUuid: response.senderUuid,
senderName: response.senderName,
senderRole: response.senderRole,
senderAvatar: response.senderAvatar,
sentAt: response.sentAt
};
if (file) {
pusherData.file = JSON.stringify(file);
}
this.pusherService.triggerSendMessage(this.chatChannel.pusherChannel, pusherData);
}

Expand Down Expand Up @@ -597,24 +581,21 @@ export class ChatRoomComponent {
);
}

private attachmentPreview(filestackRes) {
if (!filestackRes) {
private attachmentPreview(fileResponse: FileResponse) {
if (!fileResponse) {
return;
}
let preview = `Uploaded ${filestackRes.filename}`;
let preview = `Uploaded ${fileResponse.name}`;
const dimension = 224;
if (!filestackRes.mimetype) {
if (!fileResponse.type) {
return preview;
}
if (filestackRes.mimetype.includes('image')) {
const attachmentURL = `https://cdn.filestackcontent.com/quality=value:70/resize=w:${dimension},h:${dimension},fit:crop/${filestackRes.handle}`;
// preview = `<p>Uploaded ${filestackRes.filename}</p><img src=${attachmentURL}>`;
preview = `<img src=${attachmentURL}>`;
} else if (filestackRes.mimetype.includes('video')) {
// we'll need to identify filetype for 'any' type fileupload
if (fileResponse.type.includes('image')) {
const truncatedName = fileResponse.name.length > 20 ? fileResponse.name.substring(0, 20) + '...' : fileResponse.name;
preview = `<img src="${fileResponse.url}" alt="${truncatedName}">`;
} else if (fileResponse.type.includes('video')) {
preview = `<app-file-display [file]="submission.answer" [fileType]="question.fileType"></app-file-display>`;
}

return preview;
}

Expand Down
Loading