Skip to content

Commit

Permalink
✨ Role icons & editing
Browse files Browse the repository at this point in the history
  • Loading branch information
homostellaris committed Aug 9, 2024
1 parent 87eca0a commit 6e77523
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 85 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000",
"url": "http://localhost:6603",
"webRoot": "${workspaceFolder}"
}
]
Expand Down
8 changes: 7 additions & 1 deletion components/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface Todo {
completedAt?: Date
id: string
note?: Note
starRole?: StarRole['id']
starRole?: StarRole['id'] // TODO: How do we delete this when the star role is deleted? Need to add associative table and compute ID of association from todo ID and starRoleID, then delete entries when star roles gets deleted.
starPoints?: string
title: string
}
Expand All @@ -17,9 +17,15 @@ export interface Note {

export interface StarRole {
id: string
icon?: Icon
title: string
}

export interface Icon {
type: 'ionicon'
name: string
}

export interface List {
order: string[] // Todo IDs
type: '#important'
Expand Down
39 changes: 26 additions & 13 deletions components/pages/Constellation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { add, starOutline } from 'ionicons/icons'
import { RefObject, useCallback, useEffect, useRef } from 'react'
import { db } from '../db'
import { useCreateStarRoleModal } from '../starRoles/create/useCreateStarRoleModal'
import { getIonIcon } from '../starRoles/icons'
import { useStarRoleActionSheet } from '../starRoles/StarRoleActionSheet'

export default function Constellation() {
const starRoles = useLiveQuery(() => db.starRoles.toArray())
Expand All @@ -34,6 +36,8 @@ export default function Constellation() {
})
}, [fab, presentCreateStarRoleModal])

const [present] = useStarRoleActionSheet()

useGlobalKeyboardShortcuts(fab, openCreateStarRoleModal)

return (
Expand Down Expand Up @@ -64,20 +68,29 @@ export default function Constellation() {
</div>
) : (
<IonList inset>
<IonReorderGroup
{/* <IonReorderGroup
disabled={false}
// onIonItemReorder={handleReorder}
>
{starRoles.map(role => (
<IonItem
button
key={role.id}
>
<IonLabel>{role?.title}</IonLabel>
<IonReorder slot="end"></IonReorder>
</IonItem>
))}
</IonReorderGroup>
onIonItemReorder={handleReorder}
> */}
{starRoles.map(starRole => (
<IonItem
button
key={starRole.id}
onClick={() => {
present(starRole)
}}
>
<IonLabel>{starRole?.title}</IonLabel>
{starRole?.icon && (
<IonIcon
icon={getIonIcon(starRole.icon.name)}
slot="end"
/>
)}
<IonReorder slot="end"></IonReorder>
</IonItem>
))}
{/* </IonReorderGroup> */}
</IonList>
)}
<IonFab
Expand Down
12 changes: 11 additions & 1 deletion components/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import {
cloudUploadSharp,
documentText,
filterSharp,
logOutSharp,
rocketSharp,
thunderstormSharp,
} from 'ionicons/icons'
Expand All @@ -66,6 +65,7 @@ import { SelectedTodoProvider } from '../todos/SelectedTodo'
import { useTodoActionSheet } from '../todos/TodoActionSheet'
import { useCreateTodoModal } from '../todos/create/useCreateTodoModal'
import useView, { ViewProvider } from '../view'
import { getIonIcon } from '../starRoles/icons'

const Home = () => {
const [logLimit, setLogLimit] = useState(7)
Expand Down Expand Up @@ -630,6 +630,7 @@ export const Important = () => {
todo => matchesQuery(query, todo!) && inActiveStarRoles(todo!),
) as Todo[]
}, [importantList, inActiveStarRoles, query])
const starRoles = useLiveQuery(() => db.starRoles.toArray())

const [present] = useTodoActionSheet()

Expand Down Expand Up @@ -727,6 +728,15 @@ export const Important = () => {
}}
/>
<IonLabel>{todo?.title}</IonLabel>
{todo.starRole && (
<IonIcon
icon={getIonIcon(
starRoles?.find(starRole => starRole.id === todo.starRole)
?.icon?.name,
)}
slot="end"
/>
)}
{todo.note && (
<a
href={todo.note.uri}
Expand Down
51 changes: 51 additions & 0 deletions components/starRoles/StarRoleActionSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ActionSheetOptions, useIonActionSheet } from '@ionic/react'
import { HookOverlayOptions } from '@ionic/react/dist/types/hooks/HookOverlayOptions'
import { StarRole, db } from '../db'
import { useEditStarRoleModal } from './edit/useEditStarRoleModal'

// TODO: Make this so that todo is never null, action sheet doesn't make sense to be open if its null
export function useStarRoleActionSheet() {
// Using controller action sheet rather than inline because I was re-inventing what it was doing allowing dynamic options to be passed easily
const [presentActionSheet, dismissActionSheet] = useIonActionSheet()
// Using controller modal than inline because the trigger prop doesn't work with an ID on a controller-based action sheet button
const [presentEditStarRoleModal] = useEditStarRoleModal()

return [
(starRole: StarRole, options?: ActionSheetOptions & HookOverlayOptions) => {
presentActionSheet({
buttons: [
...(options?.buttons || []),
{
text: 'Edit',
data: {
action: 'edit',
},
handler: () => {
presentEditStarRoleModal(starRole)
},
},
{
text: 'Delete',
role: 'destructive',
data: {
action: 'delete',
},
handler: async () => {
db.transaction('rw', db.lists, db.starRoles, async () => {
await db.starRoles.delete(starRole.id)
const starRoles = await db.lists.get('#starRoles')
if (starRoles!.order.includes(starRole.id)) {
await db.lists.update('#starRoles', list => {
list.order = list.order.filter(id => id !== starRole.id)
})
}
})
},
},
],
header: starRole.title,
})
},
dismissActionSheet,
]
}
92 changes: 74 additions & 18 deletions components/starRoles/StarRoleModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,75 @@ import {
IonContent,
IonFooter,
IonHeader,
IonIcon,
IonInput,
IonPage,
IonTitle,
IonToolbar,
} from '@ionic/react'
import { ComponentProps, MutableRefObject, ReactNode } from 'react'
import {
ComponentProps,
MutableRefObject,
ReactNode,
useCallback,
useState,
} from 'react'
import { StarRole } from '../db'
import Icons, { getIonIcon } from './icons'

export default function StarRoleModal({
dismiss,
title,
titleInput,
starRole = {},
starRole,
toolbarSlot,
...props
}: {
dismiss: (data?: any, role?: string) => void
title: string
starRole?: Partial<StarRole>
title: string
titleInput: MutableRefObject<HTMLIonInputElement | null>
toolbarSlot?: ReactNode
} & ComponentProps<typeof IonPage>) {
const [starRoleTitleInput, setStarRoleTitleInput] = useState<string>(
starRole?.title || '',
) // TODO: Figure out why this becomes necessary due to other states resetting the title input value when its uncontrolled.
const [iconInput, setIconInput] = useState<{
name: string
value: string
} | null>()
const [iconQuery, setIconQuery] = useState<string>('')

const initialIcon = starRole?.icon
? { name: starRole.icon.name, value: getIonIcon(starRole?.icon.name) }
: null
const icon = iconInput || initialIcon
const starRoleTitle = starRoleTitleInput || starRole?.title

const emitStarRole = useCallback(() => {
dismiss(
{
...(icon
? {
icon: {
type: 'ionicon',
name: icon.name,
},
}
: {}),
title: starRoleTitle,
},
'confirm',
)
}, [dismiss, icon, starRoleTitle])

return (
<IonPage
{...props}
onKeyDown={event => {
if (event.key === 'Enter') {
event.preventDefault()
dismiss(
{
...starRole,
title: titleInput.current?.value,
},
'confirm',
)
emitStarRole()
}
props.onKeyDown?.(event)
}}
Expand All @@ -55,7 +89,35 @@ export default function StarRoleModal({
type="text"
label="Title"
labelPlacement="floating"
value={starRole?.title}
onIonInput={event => {
setStarRoleTitleInput(event.detail.value || '')
}}
value={starRoleTitleInput || starRole?.title}
/>
<IonInput
debounce={200}
fill="outline"
helperText="Type a search query and select an icon from the list that appears below"
label="Icon"
labelPlacement="floating"
onIonInput={event => {
setIconQuery(event.detail.value || '')
}}
placeholder="Rocket"
type="text"
value={iconQuery}
>
<IonIcon
icon={icon?.value}
slot="end"
/>
</IonInput>
{/* TODO: Refactor to radio group or select to avoid too many event listeners */}
<Icons
query={iconQuery}
onClick={icon => {
setIconInput(icon)
}}
/>
</IonContent>
<IonFooter>
Expand All @@ -71,13 +133,7 @@ export default function StarRoleModal({
<IonButtons slot="primary">
<IonButton
onClick={() => {
dismiss(
{
...starRole,
title: titleInput.current?.value,
},
'confirm',
)
emitStarRole()
}}
strong={true}
>
Expand Down
19 changes: 2 additions & 17 deletions components/starRoles/create/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,7 @@ import { ComponentProps } from 'react'
import StarRoleModal from '../StarRoleModal'

export function CreateStarRoleModal({
dismiss,
...props
}: {
dismiss: (data?: any, role?: string) => void
} & ComponentProps<typeof StarRoleModal>) {
return (
<StarRoleModal
dismiss={(data?: any, role?: string) => {
dismiss(
{
starRole: data,
},
role,
)
}}
{...props}
/>
)
}: ComponentProps<typeof StarRoleModal>) {
return <StarRoleModal {...props} />
}
9 changes: 4 additions & 5 deletions components/starRoles/create/useCreateStarRoleModal.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useIonModal } from '@ionic/react'
import { HookOverlayOptions } from '@ionic/react/dist/types/hooks/HookOverlayOptions'
import { useCallback, useRef } from 'react'
import { db } from '../../db'
import { db, StarRole } from '../../db'
import { CreateStarRoleModal } from './modal'

export function useCreateStarRoleModal(): [
Expand All @@ -18,8 +18,8 @@ export function useCreateStarRoleModal(): [
title: 'Create star role',
titleInput,
})
const createStarRole = useCallback(async ({ title }: { title: any }) => {
db.starRoles.add({ title })
const createStarRole = useCallback(async (properties: StarRole) => {
db.starRoles.add(properties)
}, [])

return [
Expand All @@ -30,8 +30,7 @@ export function useCreateStarRoleModal(): [
},
onWillDismiss: event => {
if (event.detail.role === 'confirm') {
const { starRole } = event.detail.data
createStarRole(starRole)
createStarRole(event.detail.data)
}
onWillDismiss?.(event)
},
Expand Down
Loading

0 comments on commit 6e77523

Please sign in to comment.