Skip to content

Commit

Permalink
Merge pull request #40 from cmgriffing/feat/saved-snippets
Browse files Browse the repository at this point in the history
feat: get saved snippets working
  • Loading branch information
cmgriffing authored Oct 26, 2024
2 parents d583322 + ffc93e9 commit 58f8a40
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 48 deletions.
1 change: 1 addition & 0 deletions playgrounds/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"solid-color": "^0.0.4",
"solid-icons": "^1.1.0",
"solid-js": "^1.9.1",
"solid-sonner": "^0.2.8",
"tailwind-merge": "^2.5.3",
"tailwindcss": "^3.4.3",
"tailwindcss-animate": "^1.0.7",
Expand Down
4 changes: 3 additions & 1 deletion playgrounds/app/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Suspense } from 'solid-js'
import { Router } from '@solidjs/router'
import { FileRoutes } from '@solidjs/start/router'
import { Suspense } from 'solid-js'
import { Toaster } from 'solid-sonner'
import Header from '~/components/Header'
import Footer from './components/Footer'
import '@fontsource/bungee-inline'
Expand All @@ -15,6 +16,7 @@ export default function App() {
<Header />
<Suspense>{props.children}</Suspense>
<Footer />
<Toaster />
</>
)}
>
Expand Down
88 changes: 86 additions & 2 deletions playgrounds/app/src/components/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import {
} from '~/components/ui/combobox'
import { Button } from '~/components/ui/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '~/components/ui/tabs'
import { TextField, TextFieldLabel, TextFieldTextArea } from '~/components/ui/text-field'
import {
TextField,
TextFieldInput,
TextFieldLabel,
TextFieldTextArea,
} from '~/components/ui/text-field'
import { MagicMoveElement } from 'shiki-magic-move/types'
import {
DropdownMenu,
Expand Down Expand Up @@ -50,12 +55,17 @@ import { createMemo, createResource, createSignal, onCleanup, Setter, Show } fro
import { createHighlighter, bundledThemes, bundledLanguages } from 'shiki'
import { ShikiMagicMove } from 'shiki-magic-move/solid'
import { AnimationFrameConfig } from '~/types'
import { authFetch } from '~/lib/utils'
import { useNavigate } from '@solidjs/router'
import { authToken } from '~/lib/store'
import { toast } from 'solid-sonner'

const animationSeconds = 1
const animationFPS = 10
const animationFrames = animationSeconds * animationFPS

interface EditorProps {
snippetId?: string
startCode: string
setStartCode: Setter<string>
endCode: string
Expand All @@ -82,9 +92,12 @@ interface EditorProps {
setLanguage: Setter<string>
theme: string
setTheme: Setter<string>
// TODO: If the app grows, this logic should be surfaced to the top level route
title: string
}

export default function Editor(props: EditorProps) {
const navigate = useNavigate()
const [selectedTab, setSelectedTab] = createSignal<'snippets' | 'output'>('snippets')
const [toggled, setToggled] = createSignal(false)

Expand All @@ -100,6 +113,8 @@ export default function Editor(props: EditorProps) {
const [isGenerating, setIsGenerating] = createSignal(false)
const [gifDataUrl, setGifDataUrl] = createSignal('')
const [isShowingGifDialog, setIsShowingGifDialog] = createSignal(false)
const [title, setTitle] = createSignal(props.title)
const [isSaving, setIsSaving] = createSignal(false)

const [highlighter] = createResource(async () => {
const newHighlighter = await createHighlighter({
Expand Down Expand Up @@ -492,6 +507,7 @@ export default function Editor(props: EditorProps) {
</div>
<div class="flex flex-row gap-2" id="toolbar-right">
<Button
disabled={isGenerating()}
onClick={async () => {
setIsGenerating(true)
setHiddenCode(props.endCode)
Expand All @@ -517,8 +533,9 @@ export default function Editor(props: EditorProps) {
}}
>
<p class="text-center">Preview</p>
<div class="flex flex-row items-center justify-center">
<div id="snippet-wrapper" class="flex flex-row items-center justify-center">
<div
id="styled-snippet"
class="flex flex-row items-center justify-center overflow-hidden"
style={{
background: props.bgColor,
Expand Down Expand Up @@ -606,6 +623,73 @@ export default function Editor(props: EditorProps) {
</div>
</TabsContent>
</Tabs>

{/* TODO: If the app grows, this logic should be surfaced to the top level route */}
<Show when={Boolean(authToken())}>
<div class="flex flex-row items-end justify-between dark:bg-[#27272a] bg-gray-100 rounded p-2 mt-2">
<TextField>
<TextFieldInput
type="text"
class="bg-white text-black"
value={title()}
placeholder={'Snippet Title'}
aria-label="Snippet Title"
onInput={e => setTitle(e.currentTarget.value)}
/>
</TextField>
<Button
disabled={isSaving() || props.startCode === '' || props.endCode === '' || !title()}
onClick={async () => {
setIsSaving(true)
const body = JSON.stringify({
title: title(),
codeLeft: props.startCode,
codeRight: props.endCode,
snippetWidth: props.snippetWidth,
yPadding: props.yPadding,
xPadding: props.xPadding,
shadowEnabled: props.shadowEnabled,
shadowOffsetY: props.shadowOffsetY,
shadowBlur: props.shadowBlur,
shadowColor: props.shadowColor,
shadowOpacity: props.shadowOpacity,
bgColor: props.bgColor,
language: props.language,
theme: props.theme,
})

let url = '/api/snippets'
let method = 'POST'

if (props.snippetId) {
url = `/api/snippets/${props.snippetId}`
method = 'PUT'
}

const result = await authFetch(url, {
method,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body,
})

if (result.ok) {
const newSnippet = await result.json()
navigate(`/snippets/${newSnippet.id}`)
} else {
// notify with a toast
toast.error('Error creating Snippet')
}

setIsSaving(false)
}}
>
{isSaving() ? 'Saving...' : 'Save'}
</Button>
</div>
</Show>
<Dialog open={isShowingGifDialog()} onOpenChange={setIsShowingGifDialog} modal>
<DialogContent>
<DialogHeader>
Expand Down
33 changes: 33 additions & 0 deletions playgrounds/app/src/components/SnippetPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Highlighter } from 'shiki'
import { ShikiMagicMove } from 'shiki-magic-move/solid'
import { Snippet } from '~/types'

interface SnippetPreviewProps {
highlighter: Highlighter
snippet: Snippet
}

export function SnippetPreview(props: SnippetPreviewProps) {
return (
<div
class="w-32 h-32 flex items-center justify-center"
style={{
background: props.snippet.bgColor,
// padding: `${props.snippet.yPadding}px ${props.snippet.xPadding}px`,
}}
>
<ShikiMagicMove
class="p-4 shadow-xl rounded select-none overflow-hidden h-24 w-24"
highlighter={props.highlighter}
lang={props.snippet.language}
code={props.snippet.codeLeft}
theme={props.snippet.theme}
options={{
duration: 800,
stagger: 0,
lineNumbers: false,
}}
/>
</div>
)
}
21 changes: 11 additions & 10 deletions playgrounds/app/src/lib/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,21 @@ import type { APIEvent } from '@solidjs/start/server'
export async function getUser({ request }: APIEvent) {
const token = request.headers.get('Authorization')?.replace('Bearer ', '')

console.log({ token })

if (!token) {
return null
}

const decoded = await decodeToken(token)

console.log({ decoded })
try {
const decoded = await decodeToken(token)

const user = await db.query.users.findFirst({
// @ts-ignore
where: eq(usersTable.id, decoded?.sub?.userId),
})
const user = await db.query.users.findFirst({
// @ts-ignore
where: eq(usersTable.id, decoded?.sub?.id),
})

return user
return user
} catch (error) {
console.error('Error getting user:', error) // Handle the error
return null
}
}
4 changes: 1 addition & 3 deletions playgrounds/app/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ export async function getGithubContributors(owner: string, repo: string): Promis
}

export async function authFetch(url: string, options?: RequestInit) {
const navigate = useNavigate()

const response = await fetch(url, {
...options,
headers: {
Expand All @@ -67,7 +65,7 @@ export async function authFetch(url: string, options?: RequestInit) {
})

if (!response.ok && response.status === 401) {
navigate('/logged-out')
location.href = '/logged-out'
}

return response
Expand Down
4 changes: 0 additions & 4 deletions playgrounds/app/src/routes/api/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import { encodeAccessToken } from '~/lib/jwt'
export async function POST(event: APIEvent) {
const { code } = await event.request.json()

console.log('client id', process.env.VITE_GITHUB_CLIENT_ID)

const githubResponse = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
Expand All @@ -37,8 +35,6 @@ export async function POST(event: APIEvent) {

const githubUser = await githubUserResponse.json()

console.log({ githubUser })

let user = await db.query.users.findFirst({
where: eq(usersTable.githubId, githubUser.id),
})
Expand Down
28 changes: 16 additions & 12 deletions playgrounds/app/src/routes/api/snippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ export async function POST(event: APIEvent) {
})
}

const requestBody = await event.request.json()

const isValid = snippetValidator.safeParse(requestBody)

if (!isValid.success) {
return new Response(JSON.stringify(isValid.error.flatten()), {
status: 400,
headers: {
'Content-Type': 'application/json',
},
})
}

const {
title,
codeLeft,
Expand All @@ -49,18 +62,7 @@ export async function POST(event: APIEvent) {
bgColor,
language,
theme,
} = await event.request.json()

const isValid = snippetValidator.safeParse({ title, codeLeft, codeRight })

if (!isValid.success) {
return new Response(JSON.stringify(isValid.error.flatten()), {
status: 400,
headers: {
'Content-Type': 'application/json',
},
})
}
} = requestBody

const newSnippet = {
id: customNanoid(),
Expand All @@ -83,6 +85,8 @@ export async function POST(event: APIEvent) {
theme,
}

console.log({ newSnippet })

await db.insert(snippetsTable).values(newSnippet)

return new Response(JSON.stringify(newSnippet), {
Expand Down
46 changes: 38 additions & 8 deletions playgrounds/app/src/routes/api/snippets/[snippetId].ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,12 @@ export async function GET(event: APIEvent) {
})
}

return new Response(
JSON.stringify(snippet),
{
status: 200,
headers: {
'Content-Type': 'application/json',
},
return new Response(JSON.stringify(snippet), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
)
})
}

export async function PUT(event: APIEvent) {
Expand Down Expand Up @@ -130,3 +127,36 @@ export async function PUT(event: APIEvent) {
},
})
}

export async function DELETE(event: APIEvent) {
const user = await getUser(event)
if (!user) {
return new Response(null, {
status: 401,
})
}

const snippetId = event.params.snippetId

if (!snippetId) {
return new Response(null, {
status: 404,
})
}

const snippet = await db.query.snippets.findFirst({
where: eq(snippetsTable.id, snippetId),
})

if (!snippet || snippet?.userId !== user.id) {
return new Response(null, {
status: 404,
})
}

await db.delete(snippetsTable).where(eq(snippetsTable.id, snippetId))

return new Response(null, {
status: 204,
})
}
2 changes: 0 additions & 2 deletions playgrounds/app/src/routes/oauth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ export default function OAuth() {

const data = await response.json()

console.log(data)

setAuthToken(data.jwt)
setUser(data.user)

Expand Down
Loading

0 comments on commit 58f8a40

Please sign in to comment.