Skip to content

Instantly share code, notes, and snippets.

@nightire
Created September 5, 2021 03:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nightire/b109ad2ee1173c18d5522e6a2f7d875c to your computer and use it in GitHub Desktop.
Save nightire/b109ad2ee1173c18d5522e6a2f7d875c to your computer and use it in GitHub Desktop.
Example
{{#if @searchKeyword}}
<div class="p-4 text-gray-600">
{{~t "tasks.searching-on" keyword=@searchKeyword~}}
</div>
{{/if}}
<div
class="divide-gray-200 divide-y"
role="list"
{{navigatable "[role=listitem]"}}
>
{{#each @tasks key="id" as |task index|}}
<div
class="group flex items-center p-4 space-x-2 hover:bg-gray-100"
role="listitem"
tabindex="0"
>
<span
class="{{if
task.isPending
"bg-blue-400 border-blue-600 "
"bg-green-400 border-green-600 "
~}}border h-3 rounded-full w-3"
role="button"
{{on "click" task.toggleCompleteness}}
></span>
{{#if task.editable}}
<label class="hidden" for="task{{index}}-title"></label>
<input
id="task{{index}}-title"
class="flex-1 px-2 rounded-sm text-gray-800 focus:outline-none focus:ring-2 group-hover:font-bold"
minlength="3"
required
type="text"
value={{task.title}}
{{did-insert @getFocusIn}}
{{validity}}
{{on "validate" task.setValidations}}
{{on "change" (pick "currentTarget.value" task.setTitle)}}
/>
{{else}}
<span
class="flex-1 px-2 text-gray-800 group-focus:font-bold group-hover:font-bold"
role="button"
{{on "click" (toggle "editable" task)}}
>
{{~task.title~}}
</span>
{{/if}}
<span class="text-gray-400 text-sm">
{{~format-time task.dateCreated format="hhmmss"~}}
</span>
</div>
{{else}}
<div class="p-4 text-gray-600">Loading...</div>
{{/each}}
</div>
import { registerDestructor } from '@ember/destroyable';
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { task, timeout } from 'ember-concurrency';
import { taskFor } from 'ember-concurrency-ts';
import type { Named, Positional } from 'ember-resources';
import { Resource, useResource } from 'ember-resources';
import { api } from 'os-dashboard';
type TaskStatus = 'canceled' | 'completed' | 'pending';
interface TaskSchema {
title: string;
status: TaskStatus;
dateCreated: string;
}
class Task {
constructor(params: TaskSchema) {
this.dateCreated = params.dateCreated;
this.status = params.status;
this.title = params.title;
}
dateCreated: string;
@tracked
status: TaskStatus;
@tracked
title: string;
@tracked
editable = false;
get isPending(): boolean {
return this.status === 'pending';
}
@action
toggleCompleteness() {
this.status = this.status === 'pending' ? 'completed' : 'pending';
}
@action
setTitle(title: string) {
this.title = title;
this.editable = false;
}
validateTitle({ value }: { value: string }) {
return /^aaa/.test(value) ? ['title is required'] : [];
}
@action
setValidations(event: InputEvent) {
const input = event.target as HTMLInputElement;
const isValid = input.validity.valid;
if (isValid) {
if (document.activeElement !== input) {
this.editable = false;
}
if (input.classList.contains('focus:ring-red-200')) {
input.classList.remove('focus:ring-red-200');
}
} else {
input.classList.add('focus:ring-red-200');
input.focus();
}
}
}
type Args = Positional<[string]> & Named;
class TaskResource extends Resource<Args> {
keyword: string;
constructor(owner: unknown, args: Args, state?: TaskResource) {
super(owner, args, state);
this.keyword = args.positional[0];
if (typeof state === 'undefined') {
taskFor(this.fetchTasks).perform();
} else {
this._tasks = state._tasks;
}
registerDestructor(this, () => {
taskFor(this.fetchTasks).cancelAll();
});
}
@tracked
_tasks: Task[] = [];
@task({ drop: true, maxConcurrency: 1 })
async fetchTasks(): Promise<void> {
const { data } = await api.get('/tasks', {
params: { sort: 'date_created' },
});
await timeout(1000);
this._tasks = data.map((task: TaskSchema) => new Task(task));
}
get value(): unknown[] {
return this._tasks.filter((task) => {
return task.title.toLowerCase().includes(this.keyword.toLowerCase());
});
}
}
export default class TasksContainer extends Component {
tasks = useResource(this, TaskResource, () => [this.searchKeyword]);
@tracked
searchKeyword = '';
@action
setSearchKeyword(keyword: string): void {
this.searchKeyword = keyword;
}
focusIn(element: HTMLElement): void {
element.focus();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment