Skip to content

Commit

Permalink
✨ Add post meatballs
Browse files Browse the repository at this point in the history
  • Loading branch information
NatoBoram committed Aug 3, 2023
1 parent adcb808 commit ec6708f
Show file tree
Hide file tree
Showing 19 changed files with 876 additions and 45 deletions.
23 changes: 23 additions & 0 deletions src/lib/Modal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts">
import { fly } from 'svelte/transition'
</script>

<!-- @component Unused -->

<div class="relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<!-- Backdrop -->
<div class="fixed inset-0 bg-base bg-opacity-50 transition-opacity" />

<div class="fixed inset-0 z-10 overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<!-- Content -->
<div
in:fly
class="base-container relative transform overflow-hidden rounded-lg px-4 pb-4 pt-5 text-left
shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6"
>
<slot />
</div>
</div>
</div>
</div>
13 changes: 13 additions & 0 deletions src/lib/Spinner.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script lang="ts">
let className: string | undefined = 'w-5 h-5'
export { className as class }
</script>

<!-- @component
Taken from <https://tailwind-elements.com/docs/standard/components/spinners/#basic> -->

<div
class="inline-block animate-spin rounded-full border-4 border-solid border-current
border-r-transparent {className}"
role="status"
/>
66 changes: 66 additions & 0 deletions src/lib/buttons/DeletePostButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script lang="ts">
import { Trash as TrashOutline } from '@natoboram/heroicons.svelte/24/outline'
import { Trash as TrashSolid } from '@natoboram/heroicons.svelte/24/solid'
import type { Post, PostResponse } from 'lemmy-js-client'
import { createEventDispatcher } from 'svelte'
import { getClientContext } from '$lib/contexts/client'
import Spinner from '$lib/Spinner.svelte'
import MeatballButton from './MeatballButton.svelte'
let className: string | undefined = undefined
export { className as class }
export let jwt: string
export let post: Post
const client = getClientContext()
const dispatch = createEventDispatcher<{
delete: PostResponse
error: Error
response: Response
}>()
let deletePending = false
async function deletePost() {
if (!jwt) return dispatch('error', new Error('You must be logged in to delete posts.'))
if (deletePending) return
deletePending = true
const deleted = await client
.deletePost({
auth: jwt,
post_id: post.id,
deleted: !post.deleted,
})
.catch((e: Response) => void dispatch('response', e))
if (deleted) {
post = deleted.post_view.post
dispatch('delete', deleted)
}
deletePending = false
return deleted
}
</script>

<MeatballButton
on:click={deletePost}
class="{deletePending ? 'cursor-progress' : ''} hover:surface surface-container {className}"
disabled={deletePending}
>
{#if deletePending && post.deleted}
<Spinner class="h-5 w-5" />
Restoring...
{:else if deletePending && !post.deleted}
<Spinner class="h-5 w-5" />
Deleting...
{:else if post.deleted}
<TrashSolid class="h-5 w-5" />
Deleted
{:else}
<TrashOutline class="h-5 w-5" />
Delete
{/if}
</MeatballButton>
86 changes: 86 additions & 0 deletions src/lib/buttons/FeatureButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<script lang="ts">
import { Sparkles as SparklesOutline } from '@natoboram/heroicons.svelte/24/outline'
import { Sparkles as SparklesSolid } from '@natoboram/heroicons.svelte/24/solid'
import type { PostFeatureType, PostResponse, PostView } from 'lemmy-js-client'
import { createEventDispatcher } from 'svelte'
import { getClientContext } from '$lib/contexts/client'
import Spinner from '$lib/Spinner.svelte'
import MeatballButton from './MeatballButton.svelte'
let className: string | undefined = undefined
export { className as class }
export let jwt: string
export let postView: PostView
export let type: PostFeatureType
const client = getClientContext()
const dispatch = createEventDispatcher<{
feature: PostResponse
error: Error
response: Response
}>()
let featurePending = false
function featuredValue(postView: PostView, type: PostFeatureType) {
switch (type) {
case 'Community':
return postView.post.featured_community
case 'Local':
return postView.post.featured_local
default: {
const error = new Error('Invalid feature type.')
dispatch('error', error)
throw error
}
}
}
async function featurePost() {
if (!jwt) return dispatch('error', new Error('You must be logged in to feature posts.'))
if (featurePending) return
featurePending = true
const featured = await client
.featurePost({
auth: jwt,
feature_type: type,
featured: !featuredValue(postView, type),
post_id: postView.post.id,
})
.catch((e: Response) => void dispatch('response', e))
if (featured) {
postView = featured.post_view
dispatch('feature', featured)
}
featurePending = false
return featured
}
</script>

<MeatballButton
on:click={featurePost}
class="{featurePending ? 'cursor-progress' : ''} hover:surface surface-container {className}"
disabled={featurePending}
>
{#if featurePending && featuredValue(postView, type)}
<Spinner class="h-5 w-5" />
Unfeaturing from {type}...
{:else if featurePending && !featuredValue(postView, type)}
<Spinner class="h-5 w-5" />
Featuring to {type}...
{:else if featuredValue(postView, type)}
<div class:text-success={type === 'Community'} class:text-danger={type === 'Local'}>
<SparklesSolid class="h-5 w-5" />
</div>
Featured ({type})
{:else}
<SparklesOutline class="h-5 w-5" />
Feature ({type})
{/if}
</MeatballButton>
22 changes: 20 additions & 2 deletions src/lib/buttons/FlatButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,30 @@
let className: string | undefined = undefined
export { className as class }
export let disabled = false
export let disabled: boolean | null | undefined = undefined
export let form: string | null | undefined = undefined
export let formaction: string | null | undefined = undefined
export let formenctype: string | null | undefined = undefined
export let formmethod: string | null | undefined = undefined
export let formnovalidate: boolean | null | undefined = undefined
export let formtarget: string | null | undefined = undefined
export let name: string | null | undefined = undefined
export let type: 'button' | 'reset' | 'submit' | null | undefined = undefined
export let value: string[] | number | string | null | undefined = undefined
</script>

<button
{disabled}
class="flex flex-row items-center justify-center gap-2 px-2 py-4 {className}"
{form}
{formaction}
{formenctype}
{formmethod}
{formnovalidate}
{formtarget}
{name}
{type}
{value}
class="flex flex-row items-center justify-center gap-2 px-4 py-2 {className}"
on:click
>
<slot />
Expand Down
66 changes: 66 additions & 0 deletions src/lib/buttons/LockPostButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script lang="ts">
import { LockClosed as LockClosedOutline } from '@natoboram/heroicons.svelte/24/outline'
import { LockClosed as LockClosedSolid } from '@natoboram/heroicons.svelte/24/solid'
import type { Post, PostResponse } from 'lemmy-js-client'
import { createEventDispatcher } from 'svelte'
import { getClientContext } from '$lib/contexts/client'
import Spinner from '$lib/Spinner.svelte'
import MeatballButton from './MeatballButton.svelte'
let className: string | undefined = undefined
export { className as class }
export let jwt: string
export let post: Post
const client = getClientContext()
const dispatch = createEventDispatcher<{
lock: PostResponse
error: Error
response: Response
}>()
let lockPending = false
async function lockPost() {
if (!jwt) return dispatch('error', new Error('You must be logged in to lock posts.'))
if (lockPending) return
lockPending = true
const locked = await client
.lockPost({
auth: jwt,
locked: !post.locked,
post_id: post.id,
})
.catch((e: Response) => void dispatch('response', e))
if (locked) {
post = locked.post_view.post
dispatch('lock', locked)
}
lockPending = false
return locked
}
</script>

<MeatballButton
on:click={lockPost}
class="{lockPending ? 'cursor-progress' : ''} hover:surface surface-container {className}"
disabled={lockPending}
>
{#if lockPending && post.locked}
<Spinner class="h-5 w-5" />
Unsaving...
{:else if lockPending && !post.locked}
<Spinner class="h-5 w-5" />
Saving...
{:else if post.locked}
<LockClosedSolid class="h-5 w-5 text-warning" />
Locked
{:else}
<LockClosedOutline class="h-5 w-5" />
Lock
{/if}
</MeatballButton>
65 changes: 65 additions & 0 deletions src/lib/buttons/MarkPostAsReadButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<script lang="ts">
import { Check } from '@natoboram/heroicons.svelte/20/solid'
import type { PostResponse, PostView } from 'lemmy-js-client'
import { createEventDispatcher } from 'svelte'
import { getClientContext } from '$lib/contexts/client'
import Spinner from '$lib/Spinner.svelte'
import MeatballButton from './MeatballButton.svelte'
let className: string | undefined = undefined
export { className as class }
export let jwt: string
export let postView: PostView
const client = getClientContext()
const dispatch = createEventDispatcher<{
read: PostResponse
error: Error
response: Response
}>()
let markPending = false
async function markPostAsRead() {
if (!jwt) return dispatch('error', new Error('You must be logged in to mark posts as read.'))
if (markPending) return
markPending = true
const marked = await client
.markPostAsRead({
auth: jwt,
post_id: postView.post.id,
read: !postView.read,
})
.catch((e: Response) => void dispatch('response', e))
if (marked) {
postView = marked.post_view
dispatch('read', marked)
}
markPending = false
return marked
}
</script>

<MeatballButton
on:click={markPostAsRead}
class="{markPending ? 'cursor-progress' : ''} hover:surface surface-container {className}"
disabled={markPending}
>
{#if markPending && postView.read}
<Spinner class="h-5 w-5" />
Unmarking as read...
{:else if markPending && !postView.read}
<Spinner class="h-5 w-5" />
Marking as read...
{:else if postView.read}
<Check class="h-5 w-5 text-success" />
Read
{:else}
<Check class="h-5 w-5" />
Mark as read
{/if}
</MeatballButton>
33 changes: 33 additions & 0 deletions src/lib/buttons/MeatballButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script lang="ts">
let className: string | undefined = undefined
export { className as class }
export let disabled: boolean | null | undefined = undefined
export let form: string | null | undefined = undefined
export let formaction: string | null | undefined = undefined
export let formenctype: string | null | undefined = undefined
export let formmethod: string | null | undefined = undefined
export let formnovalidate: boolean | null | undefined = undefined
export let formtarget: string | null | undefined = undefined
export let name: string | null | undefined = undefined
export let type: 'button' | 'reset' | 'submit' | null | undefined = undefined
export let value: string[] | number | string | null | undefined = undefined
</script>

<button
{disabled}
{form}
{formaction}
{formenctype}
{formmethod}
{formnovalidate}
{formtarget}
{name}
{type}
{value}
class:muted={disabled}
class="flex flex-row items-center justify-start gap-2 whitespace-nowrap px-4 py-2 {className}"
on:click
>
<slot />
</button>
Loading

0 comments on commit ec6708f

Please sign in to comment.