vines
This commit is contained in:
+88
-10
@@ -10,6 +10,9 @@ const copied = ref(false)
|
|||||||
const items = ref([])
|
const items = ref([])
|
||||||
const newName = ref('')
|
const newName = ref('')
|
||||||
|
|
||||||
|
const editingId = ref(null)
|
||||||
|
const editName = ref('')
|
||||||
|
|
||||||
const sublists = ref([])
|
const sublists = ref([])
|
||||||
const newSublistName = ref('')
|
const newSublistName = ref('')
|
||||||
const newSublistColor = ref('#16a34a')
|
const newSublistColor = ref('#16a34a')
|
||||||
@@ -180,24 +183,62 @@ async function removeItem(item) {
|
|||||||
items.value = items.value.filter((i) => i.id !== item.id)
|
items.value = items.value.filter((i) => i.id !== item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startEdit(item) {
|
||||||
|
editingId.value = item.id
|
||||||
|
editName.value = item.name
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelEdit() {
|
||||||
|
editingId.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveEdit(item) {
|
||||||
|
if (editingId.value !== item.id) return
|
||||||
|
editingId.value = null
|
||||||
|
const name = editName.value.trim()
|
||||||
|
if (!name || name === item.name) return
|
||||||
|
fetch(`/api/lists/${listId.value}/items/${item.id}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ name }),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((updated) => Object.assign(item, updated))
|
||||||
|
}
|
||||||
|
|
||||||
const SWIPE_THRESHOLD = 70
|
const SWIPE_THRESHOLD = 70
|
||||||
const SWIPE_MAX = 140
|
const SWIPE_MAX = 140
|
||||||
|
const LONG_PRESS_MS = 500
|
||||||
|
const LONG_PRESS_TOLERANCE = 10
|
||||||
let drag = null
|
let drag = null
|
||||||
|
let longPressTimer = null
|
||||||
|
|
||||||
function onSwipeStart(e) {
|
function onSwipeStart(e, item) {
|
||||||
drag = { startX: e.clientX, dx: 0, el: e.currentTarget }
|
drag = { startX: e.clientX, startY: e.clientY, dx: 0, el: e.currentTarget }
|
||||||
drag.el.setPointerCapture(e.pointerId)
|
drag.el.setPointerCapture(e.pointerId)
|
||||||
drag.el.style.transition = 'none'
|
drag.el.style.transition = 'none'
|
||||||
|
|
||||||
|
clearTimeout(longPressTimer)
|
||||||
|
longPressTimer = setTimeout(() => {
|
||||||
|
if (!drag) return
|
||||||
|
startEdit(item)
|
||||||
|
drag.el.style.transform = ''
|
||||||
|
drag = null
|
||||||
|
}, LONG_PRESS_MS)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSwipeMove(e) {
|
function onSwipeMove(e) {
|
||||||
if (!drag) return
|
if (!drag) return
|
||||||
|
if (Math.abs(e.clientX - drag.startX) > LONG_PRESS_TOLERANCE || Math.abs(e.clientY - drag.startY) > LONG_PRESS_TOLERANCE) {
|
||||||
|
clearTimeout(longPressTimer)
|
||||||
|
}
|
||||||
const dx = e.clientX - drag.startX
|
const dx = e.clientX - drag.startX
|
||||||
drag.dx = Math.max(-SWIPE_MAX, Math.min(SWIPE_MAX, dx))
|
drag.dx = Math.max(-SWIPE_MAX, Math.min(SWIPE_MAX, dx))
|
||||||
drag.el.style.transform = `translateX(${drag.dx}px)`
|
drag.el.style.transform = `translateX(${drag.dx}px)`
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSwipeEnd(e, item) {
|
function onSwipeEnd(e, item) {
|
||||||
|
clearTimeout(longPressTimer)
|
||||||
if (!drag) return
|
if (!drag) return
|
||||||
const { dx, el } = drag
|
const { dx, el } = drag
|
||||||
el.style.transition = 'transform 0.2s ease'
|
el.style.transition = 'transform 0.2s ease'
|
||||||
@@ -371,6 +412,19 @@ function onHandleEnd() {
|
|||||||
item.position = position
|
item.position = position
|
||||||
item.sublist_id = targetSublistId
|
item.sublist_id = targetSublistId
|
||||||
|
|
||||||
|
// Reposition within the underlying array too — groups only filter it, they
|
||||||
|
// don't sort by position, so without this the item stays wherever it
|
||||||
|
// physically sat in the array until the next full refetch reorders it.
|
||||||
|
const globalIdx = items.value.findIndex((i) => i.id === item.id)
|
||||||
|
items.value.splice(globalIdx, 1)
|
||||||
|
const anchor = next ?? prev
|
||||||
|
if (anchor) {
|
||||||
|
const anchorIdx = items.value.findIndex((i) => i.id === anchor.id)
|
||||||
|
items.value.splice(next ? anchorIdx : anchorIdx + 1, 0, item)
|
||||||
|
} else {
|
||||||
|
items.value.push(item)
|
||||||
|
}
|
||||||
|
|
||||||
fetch(`/api/lists/${listId.value}/items/${item.id}`, {
|
fetch(`/api/lists/${listId.value}/items/${item.id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@@ -412,7 +466,7 @@ onMounted(init)
|
|||||||
<main v-else>
|
<main v-else>
|
||||||
<h1>skeps</h1>
|
<h1>skeps</h1>
|
||||||
|
|
||||||
<form @submit.prevent="addItem">
|
<form class="add-item-bar" @submit.prevent="addItem">
|
||||||
<input v-model="newName" type="text" placeholder="Add an item" />
|
<input v-model="newName" type="text" placeholder="Add an item" />
|
||||||
<span class="dot" :style="{ background: newItemSublistColor }"></span>
|
<span class="dot" :style="{ background: newItemSublistColor }"></span>
|
||||||
<select class="item-sublist" v-model="newItemSublistId">
|
<select class="item-sublist" v-model="newItemSublistId">
|
||||||
@@ -444,7 +498,7 @@ onMounted(init)
|
|||||||
<div
|
<div
|
||||||
class="swipe-content"
|
class="swipe-content"
|
||||||
:class="{ done: item.done }"
|
:class="{ done: item.done }"
|
||||||
@pointerdown="onSwipeStart"
|
@pointerdown="onSwipeStart($event, item)"
|
||||||
@pointermove="onSwipeMove"
|
@pointermove="onSwipeMove"
|
||||||
@pointerup="onSwipeEnd($event, item)"
|
@pointerup="onSwipeEnd($event, item)"
|
||||||
@pointercancel="onSwipeEnd($event, item)"
|
@pointercancel="onSwipeEnd($event, item)"
|
||||||
@@ -457,10 +511,21 @@ onMounted(init)
|
|||||||
@pointercancel.stop="onHandleEnd"
|
@pointercancel.stop="onHandleEnd"
|
||||||
>⠿</span
|
>⠿</span
|
||||||
>
|
>
|
||||||
<label>
|
<label v-if="editingId !== item.id">
|
||||||
<input type="checkbox" :checked="item.done" @change="toggleDone(item)" />
|
<input type="checkbox" :checked="item.done" @change="toggleDone(item)" />
|
||||||
{{ item.name }} <span class="qty">x{{ item.quantity }}</span>
|
{{ item.name }}
|
||||||
</label>
|
</label>
|
||||||
|
<input
|
||||||
|
v-else
|
||||||
|
class="edit-name"
|
||||||
|
v-model="editName"
|
||||||
|
type="text"
|
||||||
|
autofocus
|
||||||
|
@pointerdown.stop
|
||||||
|
@keydown.enter="saveEdit(item)"
|
||||||
|
@keydown.esc="cancelEdit"
|
||||||
|
@blur="saveEdit(item)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -635,9 +700,21 @@ body {
|
|||||||
main {
|
main {
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
margin: 2rem auto;
|
margin: 2rem auto;
|
||||||
padding: env(safe-area-inset-top) 1rem env(safe-area-inset-bottom);
|
padding: calc(env(safe-area-inset-top) + 4.25rem) 1rem env(safe-area-inset-bottom);
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
.add-item-bar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 20;
|
||||||
|
max-width: 480px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: calc(env(safe-area-inset-top) + 0.75rem) 1rem 0.75rem;
|
||||||
|
background: var(--bg);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
.theme-picker {
|
.theme-picker {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@@ -754,6 +831,10 @@ ul {
|
|||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
.edit-name {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
.handle {
|
.handle {
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
@@ -791,9 +872,6 @@ ul {
|
|||||||
.add-sublist input[type='color']::-moz-color-swatch {
|
.add-sublist input[type='color']::-moz-color-swatch {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
.qty {
|
|
||||||
color: var(--muted);
|
|
||||||
}
|
|
||||||
.settings {
|
.settings {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
|
|||||||
Reference in New Issue
Block a user