Skip to content

Commit

Permalink
Cards (#152)
Browse files Browse the repository at this point in the history
* layout groups

* hover fic projects

* fetch from supabase and basic layout

* why now?

* maybe ?

* another one

* aaaaaah

* finally

* get address

* i18n

* i18n fixes

* more

* one more

* move i18n to the server

* more finetuning, image generation

* card flipping, save image in sb

* more translations

* fine tuning

* fix background

* more fixes

* scrollable card content
  • Loading branch information
LukaHarambasic authored Dec 27, 2023
1 parent 3c36d49 commit ed56353
Show file tree
Hide file tree
Showing 36 changed files with 1,726 additions and 999 deletions.
2,179 changes: 1,227 additions & 952 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write .",
"generate-favicons": "node scripts/fetch-favicon/index.js",
"generateFavicons": "node scripts/fetch-favicon/index.js",
"socialMedia:auto": "node scripts/generate-social-media-preview/generateAutomatic.js",
"socialMedia:manual": "node scripts/generate-social-media-preview/generateManual.js",
"newPost": "node scripts/generate-post/index.js"
"newPost": "node scripts/generate-post/index.js",
"generateCardImages": "node --env-file=.env scripts/generate-card-images/index.js"
},
"devDependencies": {
"@fontsource/fira-mono": "^5.0.8",
Expand All @@ -38,13 +39,16 @@
"eslint-config-prettier": "^9.0.0",
"front-matter": "^4.0.2",
"gray-matter": "^4.0.3",
"highlight.js": "^11.9.0",
"js-yaml": "^4.1.0",
"openai": "^4.24.1",
"playwright": "^1.37.1",
"postcss-nested": "^6.0.1",
"postcss-size": "^4.0.1",
"postcss-sorting": "^8.0.2",
"prettier": "^3.0.3",
"prettier-plugin-svelte": "^3.0.3",
"rehype-highlight": "^7.0.0",
"rehype-stringify": "^10.0.0",
"remark": "^15.0.1",
"remark-frontmatter": "^5.0.0",
Expand All @@ -57,10 +61,7 @@
"typescript": "^5.2.2",
"unist-util-visit": "^5.0.0",
"vite": "^4.4.9",
"vitest": "^0.34.4",
"highlight.js": "^11.9.0",
"rehype-highlight": "^7.0.0"
"vitest": "^0.34.4"
},
"type": "module",
"dependencies": {}
"type": "module"
}
55 changes: 55 additions & 0 deletions scripts/generate-card-images/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import fetch from 'node-fetch';
import OpenAI from "openai"
import { createClient } from '@supabase/supabase-js'

const openai = new OpenAI({apiKey: process.env.OPENAI_API_KEY})
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_API_KEY)

async function _fetchCards() {
const { data, error } = await supabase.from('cards').select('*').eq('image_url', null);
console.log('Cards fetched: ', data.length)
if (error) throw new Error(`error`)
if (!data) throw new Error('No data: ', data)
return data.map(({uuid, name, content, language}) => {
return {
uuid,
name,
content,
language,
}
})
}

async function _generateImage(name) {
const image = await openai.images.generate({
model: "dall-e-3",
prompt: `abstract layered christmas themed with light refracting , very cool subtle minimal christmas illustration, christmas red with subtle highlights. each image is unique for a user, use their name in some way to make it more personal, it doesnt has to be integrated directly: ${name}`,
n: 1,
size: "1024x1024",
})
return image.data[0].url
}

async function _fetchImage(imageUrl) {
const response = await fetch(imageUrl);
if (!response.ok) throw new Error('Failed to fetch image');
return await response.buffer();
}

async function _saveImage(blob, uuid) {
const { data, error } = await supabase.storage.from('cards').upload(`${uuid}.jpg`, blob);
if (error) throw new Error('Failed to upload image: ', error);
const imageUrl = `${process.env.SUPABASE_URL}/storage/v1/object/public/${data.fullPath}`
await supabase.from('cards').update({image_url: imageUrl}).eq('uuid', uuid);
}

async function main() {
const cards = await _fetchCards()
cards.forEach(async (card) => {
const imageUrl = await _generateImage(card.name)
const blob = await _fetchImage(imageUrl)
await _saveImage(blob, card.uuid)
})
}

main()
10 changes: 7 additions & 3 deletions src/lib/components/Base/BaseModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import Icon from '@iconify/svelte'
export let showModal: boolean
export let isClosable: boolean = true
let dialog: HTMLDialogElement
$: if (dialog && showModal) dialog.showModal()
function closeModal() {
if (!isClosable) return
dialog.close()
showModal = false
resetParams()
Expand All @@ -23,9 +25,11 @@
<div class="content" on:click|stopPropagation>
<slot />
<!-- svelte-ignore a11y-autofocus -->
<button class="close" autofocus on:click={() => dialog.close()}>
<Icon icon="ph:x-circle-bold" />
</button>
{#if isClosable}
<button class="close" autofocus on:click={() => dialog.close()}>
<Icon icon="ph:x-circle-bold" />
</button>
{/if}
</div>
</dialog>

Expand Down
27 changes: 27 additions & 0 deletions src/lib/styles/base.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* * {
outline: 1px solid rgba(255, 0, 0, 0.25);
} */

html {
scroll-behavior: smooth;
}

body {
-webkit-font-smoothing: antialiased;
background: var(--c-light);
color: var(--c-font);
font-size: 125%;
font-family: var(--font-family);
}

:any-link {
text-decoration-thickness: var(--underline-thickness);
}

* {
scroll-margin: var(--xl);
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
hyphens: auto;
}
24 changes: 0 additions & 24 deletions src/lib/styles/global.css
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@
/* * {
outline: 1px solid rgba(255, 0, 0, 0.25);
} */

html {
scroll-behavior: smooth;
}

body {
-webkit-font-smoothing: antialiased;
background: var(--c-light);
color: var(--c-font);
font-size: 125%;
font-family: var(--font-family);
}

/* TODO: all global styles indicate the need for an component, only super basic styles are allwed in here */

.select-button {
Expand Down Expand Up @@ -80,14 +64,6 @@ body {
}
}

:any-link {
text-decoration-thickness: var(--underline-thickness);
}

* {
scroll-margin: var(--xl);
}

.rich-text {
line-height: 1.75;
* {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import '$lib/styles/reset.css'
import '$lib/styles/fonts.css'
import '$lib/styles/variables.css'
import '$lib/styles/base.css'
import '$lib/styles/global.css'
import '$lib/styles/highlight.css'
import LayoutFooter from '$lib/components/Layout/LayoutFooter.svelte'
Expand Down
File renamed without changes.
6 changes: 3 additions & 3 deletions src/routes/+page.svelte → src/routes/(main)/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// import type { Shareable } from '$lib/types/shareable'
// TODO: remove eager and only load images that got randomly selected
const pictures = import.meta.glob('../assets/img/projects/*.{avif,gif,heif,jpeg,jpg,png,tiff,webp}', {
const pictures = import.meta.glob('../../assets/img/projects/*.{avif,gif,heif,jpeg,jpg,png,tiff,webp}', {
eager: true,
query: {
enhanced: true,
Expand All @@ -18,7 +18,7 @@
})
const getImage = (name: string) => {
const image = pictures[`../assets/img/projects/${name}`]
const image = pictures[`../../assets/img/projects/${name}`]
if (!image) {
return {}
}
Expand All @@ -40,7 +40,7 @@
<section class="heyho">
<div class="inner">
<enhanced:img
src="../assets/img/profile.jpeg?w=1280;640;400"
src="../../assets/img/profile.jpeg?w=1280;640;400"
sizes="(min-width:1920px) 1280px, (min-width:1080px) 640px, (min-width:768px) 400px"
alt="Profile of Luka Harambasic"
class="profile"
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<ul>
<li>Name: Luka Harambasic</li>
<li>
E-Mail: <a href="[email protected]">[email protected]</a>
E-Mail: <a href="mailto:[email protected]">[email protected]</a>
</li>
</ul>
<h3>Graphics and Image Sources</h3>
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import BaseModal from '$lib/components/Base/BaseModal.svelte'
// TODO: remove eager and only load images that got randomly selected
const pictures = import.meta.glob('../../assets/img/projects/*.{avif,gif,heif,jpeg,jpg,png,tiff,webp}', {
const pictures = import.meta.glob('../../../assets/img/projects/*.{avif,gif,heif,jpeg,jpg,png,tiff,webp}', {
eager: true,
query: {
enhanced: true,
Expand All @@ -24,7 +24,7 @@
})
const getImage = (name: string) => {
const image = pictures[`../../assets/img/projects/${name}`]
const image = pictures[`../../../assets/img/projects/${name}`]
if (!image) {
return {}
}
Expand Down Expand Up @@ -71,7 +71,6 @@
let showModal = false
function openModal(project?: Project) {
console.log('openModal', project)
if (project) {
setParam('slug', project.slug)
projectSlug = project.slug
Expand Down Expand Up @@ -184,8 +183,9 @@
&:hover {
transform: scale(0.97);
cursor: pointer;
> img {
img {
filter: grayscale(0);
opacity: 1;
}
}
> picture {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import { onMount } from 'svelte'
// TODO: remove eager and only load images that got randomly selected
const pictures = import.meta.glob('../../assets/img/stack/*.{avif,gif,heif,jpeg,jpg,png,tiff,webp}', {
const pictures = import.meta.glob('../../../assets/img/stack/*.{avif,gif,heif,jpeg,jpg,png,tiff,webp}', {
eager: true,
query: {
enhanced: true,
Expand All @@ -21,7 +21,7 @@
})
const getImage = (name: string) => {
const image = pictures[`../../assets/img/stack/${name}`]
const image = pictures[`../../../assets/img/stack/${name}`]
if (!image) {
return {}
}
Expand Down
72 changes: 72 additions & 0 deletions src/routes/(playground)/cards/[slug]/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { createClient } from '@supabase/supabase-js'
import type { EntryGenerator, RouteParams } from './$types';
import { t } from './i18n';

const supabase = createClient(
'https://xqlghnitokncvzvxoiyq.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhxbGdobml0b2tuY3Z6dnhvaXlxIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTY4MzQyMzYwNSwiZXhwIjoxOTk4OTk5NjA1fQ.QzoaOpgNPQF32zJl5c-Olx6ZEhw03M5mkbAA_zkBrT8'
)

interface Card {
slug: string,
name: string,
content: string,
language: string,
imageUrl: string,
}

async function fetchCards(): Promise<Card[]> {
const { data, error } = await supabase.from('cards').select('*')
if (error) {
throw new Error(`error`)
}
if (!data) {
throw new Error('No data: data')
}
return data.map(({uuid, name, content, language, image_url}) => {
return {
slug: uuid,
name,
content,
language,
imageUrl: image_url,
}
})
}

function getCardBySlug(slug: string, cards: Card[]): Card | undefined {
return cards.find((card) => card!.slug === slug)
}

function getEntries(cards: Card[]): RouteParams[] {
return cards.map(({slug}) => ({ slug }))
}

export const load = async ({params}) => {
const cards = await fetchCards()
const card = getCardBySlug(params.slug, cards)
return {
name: card!.name,
language: card!.language,
slug: card!.slug,
content: card!.content,
imageUrl: card!.imageUrl,
greeting: t('greeting', card!.language, card!.name),
farewell: t('farewell', card!.language),
fullTitle: t('title', card!.language, card!.name),
description: t('description', card!.language),
socialImg: t('socialImg', card!.language),
socialImgAlt: t('socialImgAlt', card!.language),
frontTitle: t('frontTitle', card!.language, card!.name),
frontGenerated: t('frontGenerated', card!.language, card!.name),
footerGenerated: t('footerGenerated', card!.language, card!.name),
footerBy: t('footerBy', card!.language),
}
}

export const entries: EntryGenerator = async () => {
const cards = await fetchCards()
return getEntries(cards)
}

export const prerender = true
Loading

0 comments on commit ed56353

Please sign in to comment.