Skip to content

Commit

Permalink
added comment thread component for subjects
Browse files Browse the repository at this point in the history
  • Loading branch information
roncodes committed Feb 1, 2024
1 parent 8ed7284 commit 1c29773
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 0 deletions.
15 changes: 15 additions & 0 deletions addon/components/comment-thread.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="flex flex-col mb-4" ...attributes>
<Textarea @value={{this.input}} class="form-input w-full" placeholder={{t "component.comment-thread.comment-input-placeholder"}} rows={{3}} disabled={{not this.publishComment.isIdle}} />
<div class="flex flex-row items-center justify-end mt-2">
<Button @type="primary" @buttonType="button" @icon="paper-plane" @text={{t "component.comment-thread.publish-comment-button-text"}} @onClick={{perform this.publishComment}} @disabled={{or (not this.publishComment.isIdle) (not this.input)}} />
</div>
</div>
<div>
{{#each this.comments as |comment|}}
{{#if (has-block)}}
{{yield (component "comment-thread/comment" comment=comment) comment}}
{{else}}
<CommentThread::Comment @comment={{comment}} />
{{/if}}
{{/each}}
</div>
60 changes: 60 additions & 0 deletions addon/components/comment-thread.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { isArray } from '@ember/array';
import { task } from 'ember-concurrency-decorators';
import getWithDefault from '@fleetbase/ember-core/utils/get-with-default';
import getModelName from '@fleetbase/ember-core/utils/get-model-name';

export default class CommentThreadComponent extends Component {
@service store;
@service notifications;
@tracked subject;
@tracked comments = [];
@tracked input = '';

constructor(owner, { subject, subjectType }) {
super(...arguments);

this.subject = subject;
this.comments = subject.comments;
this.subjectType = subjectType ? subjectType : getModelName(subject);
}

@task *publishComment() {
if (this.isCommentInvalid(this.input)) {
return;
}

let comment = this.store.createRecord('comment', {
content: this.input,
subject_uuid: this.subject.id,
subject_type: this.subjectType,
});

yield comment.save();
yield this.reloadComments.perform();

this.input = '';
}

@task *reloadComments() {
this.comments = yield this.store.query('comment', { subject_uuid: this.subject.id, sort: '-created_at' });
}

isCommentInvalid(comment) {
if (!comment) {
this.notification.warning(this.intl.t('fleet-ops.operations.orders.index.view.comment-input-empty-notification'));
return true;
}

// make sure comment is atleast 12 characters
if (typeof comment === 'string' && comment.length < 2) {
this.notification.warning(this.intl.t('fleet-ops.operations.orders.index.view.comment-min-length-notification'));
return true;
}

return false;
}
}
46 changes: 46 additions & 0 deletions addon/components/comment-thread/comment.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<div class="thread-comment flex flex-row p-1 space-x-3" ...attributes>
<div class="thread-comment-avatar-wrapper w-18 flex flex-col items-center">
<Image src={{this.comment.author.avatar_url}} @fallbackSrc={{config "defaultValues.userImage"}} alt={{this.comment.author.name}} class="w-8 h-8 rounded-full" />
</div>
<div class="thread-comment-content-wrapper flex-1">
<div class="thread-comment-author flex flex-row items-center">
<div class="thread-comment-author-name text-sm dark:text-white text-black font-bold mr-1.5">{{this.comment.author.name}}</div>
<div class="thread-comment-created-at dark:text-gray-300 text-gray-600 text-xs">{{t "component.comment-thread.comment-published-ago" createdAgo=this.comment.createdAgo}}</div>
</div>
<div class="thread-comment-conent-paragraph-wrapper mt-2">
{{#if this.editing}}
<Textarea @value={{this.comment.content}} class="form-input w-full" placeholder={{t "component.comment-thread.comment-reply-placeholder"}} rows={{2}} disabled={{not this.updateComment.isIdle}} />
<div class="mt-2 flex flex-row items-center justify-end">
<Button @type="primary" @buttonType="button" @icon="save" @size="xs" @iconSize="xs" @iconClass="text-xs" @text={{t "common.save"}} @onClick={{perform this.updateComment}} @disabled={{or (not this.updateComment.isIdle) (not this.comment.content)}} />
</div>
{{else}}
<p class="thread-comment-conent-paragraph text-xs text-gray-900 dark:text-gray-100">{{this.comment.content}}</p>
{{/if}}
</div>
<div class="thread-comment-conent-actions-wrapper flex flex-row items-center mt-2 space-x-4">
<Button @wrapperClass="thread-comment-conent-actions-reply" @type="link" @buttonType="button" @size="xs" @iconSize="xs" @textClass="text-xs" @icon="reply" @text={{t "component.comment-thread.reply-comment-button-text"}} @onClick={{this.reply}} />
{{#if this.comment.editable}}
<Button @wrapperClass="thread-comment-conent-actions-edit" @type="link" @buttonType="button" @size="xs" @iconSize="xs" @textClass="text-xs" @icon="edit" @text={{t "component.comment-thread.edit-comment-button-text"}} @onClick={{this.edit}} />
<Button @wrapperClass="thread-comment-conent-actions-delete" @type="link" @buttonType="button" @size="xs" @iconSize="xs" @iconClass="text-xs text-danger" @textClass="text-xs text-danger" @icon="trash" @text={{t "component.comment-thread.delete-comment-button-text"}} @onClick={{this.delete}} />
{{/if}}
</div>
{{#if this.replying}}
<div class="flex flex-col mt-3">
<Textarea @value={{this.input}} class="form-input w-full" placeholder={{t "component.comment-thread.comment-reply-placeholder"}} rows={{2}} disabled={{not this.publishReply.isIdle}} />
<div class="flex flex-row items-center justify-end mt-2 space-x-4">
<Button @type="link" @buttonType="button" @size="xs" @text={{t "common.cancel"}} @onClick={{this.cancelReply}} @disabled={{not this.publishReply.isIdle}} />
<Button @type="primary" @buttonType="button" @icon="reply" @size="xs" @iconSize="xs" @iconClass="text-xs" @text={{t "component.comment-thread.publish-reply-button-text"}} @onClick={{perform this.publishReply}} @disabled={{or (not this.publishReply.isIdle) (not this.input)}} />
</div>
</div>
{{/if}}
<div class="thread-comment-replies mt-3">
{{#each this.comment.replies as |reply|}}
{{#if (has-block)}}
{{yield (component "comment-thread/comment" comment=reply) reply}}
{{else}}
<CommentThread::Comment @comment={{reply}} />
{{/if}}
{{/each}}
</div>
</div>
</div>
62 changes: 62 additions & 0 deletions addon/components/comment-thread/comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency-decorators';

export default class CommentThreadCommentComponent extends Component {
@service store;
@tracked input = '';
@tracked replying = false;
@tracked editing = false;

constructor(owner, { comment }) {
super(...arguments);

this.comment = comment;
}

@action reply() {
this.replying = true;
}

@action cancelReply() {
this.replying = false;
}

@action edit() {
this.editing = true;
}

@action cancelEdit() {
this.editing = false;
}

@action delete() {
this.comment.destroyRecord();
}

@task *updateComment() {
yield this.comment.save();
this.editing = false;
}

@task *publishReply() {
let comment = this.store.createRecord('comment', {
content: this.input,
parent_comment_uuid: this.comment.id,
subject_uuid: this.comment.subject_uuid,
subject_type: this.comment.subject_type,
});

yield comment.save();
yield this.reloadReplies.perform();

this.replying = false;
this.input = '';
}

@task *reloadReplies() {
this.comment = yield this.comment.reload();
}
}
10 changes: 10 additions & 0 deletions addon/styles/layout/next.css
Original file line number Diff line number Diff line change
Expand Up @@ -1724,10 +1724,16 @@ body[data-theme='light'] .next-dd-menu-seperator {
height: 57px;
}

.text-danger,
span.text-danger,
a.text-danger {
@apply text-red-500;
}

.text-danger:active,
.text-danger:hover,
span.text-danger:active,
span.text-danger:hover,
a.text-danger:active,
a.text-danger:hover {
@apply text-red-400;
Expand Down Expand Up @@ -1855,4 +1861,8 @@ input.order-list-overlay-search:focus {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
max-width: 1200px;
}

.thread-comment-conent-paragraph-wrapper {
min-height: 1.75rem;
}
1 change: 1 addition & 0 deletions app/components/comment-thread.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/ember-ui/components/comment-thread';
1 change: 1 addition & 0 deletions app/components/comment-thread/comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/ember-ui/components/comment-thread/comment';
26 changes: 26 additions & 0 deletions tests/integration/components/comment-thread-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Component | comment-thread', function (hooks) {
setupRenderingTest(hooks);

test('it renders', async function (assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });

await render(hbs`<CommentThread />`);

assert.dom().hasText('');

// Template block usage:
await render(hbs`
<CommentThread>
template block text
</CommentThread>
`);

assert.dom().hasText('template block text');
});
});
26 changes: 26 additions & 0 deletions tests/integration/components/comment-thread/comment-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Component | comment-thread/comment', function (hooks) {
setupRenderingTest(hooks);

test('it renders', async function (assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });

await render(hbs`<CommentThread::Comment />`);

assert.dom().hasText('');

// Template block usage:
await render(hbs`
<CommentThread::Comment>
template block text
</CommentThread::Comment>
`);

assert.dom().hasText('template block text');
});
});

0 comments on commit 1c29773

Please sign in to comment.