Spaces:
Running
Running
analyze fix upgrade, prioritize mvp top 3 e2e tested n functional simple but valuable human struggle time minimizers
3e519ec verified | class ToolTasks extends HTMLElement { | |
| connectedCallback() { | |
| this.attachShadow({ mode: 'open' }); | |
| // Load tasks from localStorage if available | |
| const savedTasks = JSON.parse(localStorage.getItem('flowstate_tasks')) || []; | |
| this.tasks = savedTasks; | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| .wrapper { | |
| background: #1e293b; | |
| border-radius: 1rem; | |
| padding: 2rem; | |
| border: 1px solid #334155; | |
| } | |
| .input-group { | |
| display: flex; | |
| gap: 0.5rem; | |
| margin-bottom: 2rem; | |
| } | |
| input[type="text"] { | |
| flex-grow: 1; | |
| background: #0f172a; | |
| border: 1px solid #334155; | |
| padding: 0.75rem 1rem; | |
| border-radius: 0.5rem; | |
| color: white; | |
| font-size: 1rem; | |
| outline: none; | |
| transition: border-color 0.2s; | |
| } | |
| input[type="text"]:focus { | |
| border-color: #10b981; | |
| } | |
| .add-btn { | |
| background: #10b981; | |
| color: white; | |
| border: none; | |
| padding: 0 1.5rem; | |
| border-radius: 0.5rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: background 0.2s; | |
| } | |
| .add-btn:hover { | |
| background: #059669; | |
| } | |
| .task-list { | |
| list-style: none; | |
| padding: 0; | |
| margin: 0; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.75rem; | |
| max-height: 60vh; | |
| overflow-y: auto; | |
| } | |
| .task-item { | |
| display: flex; | |
| align-items: center; | |
| background: #0f172a; | |
| padding: 0.75rem 1rem; | |
| border-radius: 0.5rem; | |
| border: 1px solid #334155; | |
| transition: all 0.2s; | |
| animation: slideIn 0.3s ease; | |
| } | |
| @keyframes slideIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .task-item:hover { | |
| border-color: #475569; | |
| transform: translateX(4px); | |
| } | |
| .task-item.completed { | |
| opacity: 0.5; | |
| background: #020617; | |
| } | |
| .task-item.completed span { | |
| text-decoration: line-through; | |
| color: #64748b; | |
| } | |
| .checkbox { | |
| width: 20px; | |
| height: 20px; | |
| border: 2px solid #475569; | |
| border-radius: 4px; | |
| margin-right: 1rem; | |
| cursor: pointer; | |
| display: grid; | |
| place-items: center; | |
| flex-shrink: 0; | |
| } | |
| .task-item.completed .checkbox { | |
| background: #10b981; | |
| border-color: #10b981; | |
| } | |
| .task-text { | |
| flex-grow: 1; | |
| font-size: 1rem; | |
| } | |
| .delete-btn { | |
| background: transparent; | |
| border: none; | |
| color: #ef4444; | |
| opacity: 0; | |
| cursor: pointer; | |
| transition: opacity 0.2s; | |
| } | |
| .task-item:hover .delete-btn { | |
| opacity: 1; | |
| } | |
| .empty-state { | |
| text-align: center; | |
| color: #64748b; | |
| padding: 2rem; | |
| font-style: italic; | |
| } | |
| /* Custom Scrollbar for list */ | |
| .task-list::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .task-list::-webkit-scrollbar-thumb { | |
| background: #334155; | |
| border-radius: 3px; | |
| } | |
| </style> | |
| <div class="wrapper"> | |
| <div class="input-group"> | |
| <input type="text" id="taskInput" placeholder="What needs doing?"> | |
| <button class="add-btn" id="addBtn"> | |
| <i data-feather="plus"></i> | |
| </button> | |
| </div> | |
| <ul class="task-list" id="taskList"> | |
| <!-- Tasks will be injected here --> | |
| </ul> | |
| </div> | |
| `; | |
| this.inputEl = this.shadowRoot.getElementById('taskInput'); | |
| this.listEl = this.shadowRoot.getElementById('taskList'); | |
| this.addBtn = this.shadowRoot.getElementById('addBtn'); | |
| this.addBtn.addEventListener('click', () => this.addTask()); | |
| this.inputEl.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') this.addTask(); | |
| }); | |
| this.renderTasks(); | |
| feather.replace(); | |
| } | |
| renderTasks() { | |
| this.listEl.innerHTML = ''; | |
| if (this.tasks.length === 0) { | |
| this.listEl.innerHTML = '<li class="empty-state">All clear. Flow unblocked.</li>'; | |
| return; | |
| } | |
| this.tasks.forEach((task, index) => { | |
| const li = document.createElement('li'); | |
| li.className = `task-item ${task.completed ? 'completed' : ''}`; | |
| li.innerHTML = ` | |
| <div class="checkbox" onclick="this.getRootNode().host.toggleTask(${index})"> | |
| ${task.completed ? '<i data-feather="check" color="white" width="14" height="14"></i>' : ''} | |
| </div> | |
| <span class="task-text">${this.escapeHtml(task.text)}</span> | |
| <button class="delete-btn" onclick="this.getRootNode().host.deleteTask(${index})"> | |
| <i data-feather="trash-2" width="18" height="18"></i> | |
| </button> | |
| `; | |
| this.listEl.appendChild(li); | |
| }); | |
| feather.replace(); | |
| this.save(); | |
| } | |
| addTask() { | |
| const text = this.inputEl.value.trim(); | |
| if (text) { | |
| this.tasks.unshift({ text, completed: false }); | |
| this.inputEl.value = ''; | |
| this.renderTasks(); | |
| } | |
| } | |
| toggleTask(index) { | |
| this.tasks[index].completed = !this.tasks[index].completed; | |
| this.renderTasks(); | |
| } | |
| deleteTask(index) { | |
| this.tasks.splice(index, 1); | |
| this.renderTasks(); | |
| } | |
| save() { | |
| localStorage.setItem('flowstate_tasks', JSON.stringify(this.tasks)); | |
| } | |
| escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| } | |
| customElements.define('tool-tasks', ToolTasks); |