Skip to content

Commit

Permalink
Sidebar layout (#92)
Browse files Browse the repository at this point in the history
* Change "dashboard" to "view" so pages can share sidebar layout

* Rename nav to sidebar, apply only to `/view/*`

* Group layouts without changing URLs:
Discovered this https://kit.svelte.dev/docs/advanced-routing#advanced-layouts-breaking-out-of-layouts

* Move generations to sidebar

* Merge branch 'main' into sidebar-layout

* New GenerationList, simple sorted list of links

* Some polish on the sidebar

Signed-off-by: Frank Noirot <[email protected]>

* Add icons, start polishing view page

* Make error version of view page

* Persist generations to localStorage

* Change "This Week" to "Past 7 Days"

* Invalidate threlte renderer on navigation or resize

* Add links to billing and github issues

* Fix broken conditional for failed submissions

* UX polish

* Add mobile styling, sidebar

* Make prompt text box auto-resize, other polish

* preserve query params through homepage redirect

* UX polish, colors and submit on enter for textarea

* Polish error styling on view page

* Polish dashboard, separate out components

* Make some 3D lights move with camera

* Add screen read labels to feedback buttons

* Polish model color and resizing
thank you to the @threlte team for setting me straight here: threlte/threlte#817 (comment)

* Tablet polish and copy tweak

* Add storage version key, remove @square/svelte-store

* Remove logs, misc UX polish

* Fix new prompt button dark mode hover

* Trim prompt text

* Add first integration test

* Fix unit tests for time buckets

* Try adding GitHub CI action

* Switch to only do unit tests in CI for now

* Remove Playwright steps

* Fixes found after user testing

* Make polling work better

* Clean up home page responsive styles

* @jessfraz feedback

* Fix endless refresh after model completion

---------

Signed-off-by: Frank Noirot <[email protected]>
  • Loading branch information
franknoirot authored Jan 10, 2024
1 parent eea13bf commit 3d02177
Show file tree
Hide file tree
Showing 48 changed files with 5,725 additions and 678 deletions.
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
VITE_API_BASE_URL=https://api.dev.zoo.dev
VITE_SITE_BASE_URL=https://dev.zoo.dev
PLAYWRIGHT_SESSION_COOKIE=''
22 changes: 22 additions & 0 deletions .github/workflows/unitTests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Unit Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
main:
timeout-minutes: 10
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn
- name: Run integration tests
run: yarn test:unit run
env:
CI: true
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ node_modules
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*


# playwright artifacts
test-results
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v20.5.0
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,13 @@ CookieDate.setFullYear(CookieDate.getFullYear() + 10)
document.cookie =
'__Secure-next-auth.session-token=YOUR_TOKEN;Secure;expires=' + CookieDate.toUTCString() + ';'
```

### Running Playwright E2E tests locally

In order to run our Playwright testing suite locally, please set the `PLAYWRIGHT_SESSION_COOKIE` variable within `.env.development` to a token from a logged in local development session. You can retrieve it by:

1. logging in to the project locally using the method outlined above
2. opening the Application tab in your browser developer tools
3. copying out the value of the cookie titled `__Secure-next-auth.session-token` with the domain of `localhost`

Now you should be able to run the `yarn test:integration` and `yarn test` commands successfully.
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"test": "npm run test:integration && npm run test:unit",
"test": "npm run test:integration && npm run test:unit run",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
Expand All @@ -20,12 +20,14 @@
"@sveltejs/kit": "^1.20.4",
"@testing-library/jest-dom": "^6.2.0",
"@testing-library/svelte": "^4.0.5",
"@types/object.groupby": "^1.0.3",
"@types/testing-library__jest-dom": "^6.0.0",
"@types/three": "^0.157.2",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitest/ui": "^1.1.2",
"autoprefixer": "^10.4.16",
"dotenv": "^16.3.1",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0",
Expand All @@ -49,6 +51,10 @@
"@kittycad/lib": "^0.0.48",
"@threlte/core": "^6.1.0",
"@threlte/extras": "^7.3.0",
"@types/core-js": "^2.5.8",
"core-js-pure": "^3.35.0",
"object.groupby": "^1.0.1",
"svelte-autosize": "^1.1.0",
"three": "^0.157.0"
}
}
36 changes: 33 additions & 3 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,42 @@
import type { PlaywrightTestConfig } from '@playwright/test'
import { AUTH_COOKIE_NAME } from './src/lib/cookies'
import dotenv from 'dotenv'
import path from 'path'

dotenv.config({ path: path.resolve(path.dirname('.'), '.env.development') })
const expiration = new Date()
expiration.setFullYear(expiration.getFullYear() + 1)

const config: PlaywrightTestConfig = {
use: {
baseURL: 'https://localhost:3000',
storageState: {
cookies: [
{
name: AUTH_COOKIE_NAME,
value: process.env.PLAYWRIGHT_SESSION_COOKIE ?? '',
domain: 'localhost',
path: '/',
expires: expiration.getTime() / 1000,
httpOnly: true,
secure: true,
sameSite: 'None'
}
],
origins: [
{
origin: 'https://localhost:3000',
localStorage: []
}
]
}
},
webServer: {
command: 'npm run build && npm run preview',
port: 4173
command: 'yarn dev',
port: 3000
},
testDir: 'tests',
testMatch: /(.+\.)?(test|spec)\.[jt]s/
testMatch: /(.+\.)?(playwright)\.[jt]s/
}

export default config
103 changes: 70 additions & 33 deletions src/components/AccountMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { Models } from '@kittycad/lib'
import { paths } from '$lib/paths'
import Person from './Icons/Person.svelte'
import ArrowRight from './Icons/ArrowRight.svelte'
export let user: Models['User_type']
let open = false
Expand All @@ -11,6 +12,14 @@
((user?.name && user.name.length > 0) ||
(user?.first_name && user.first_name.length > 0) ||
(user?.email && user.email.length > 0))
let displayName =
user?.first_name && user.first_name.length > 0
? (user.first_name + (user.last_name ? ' ' + user.last_name : '')).trim()
: user?.name && user.name.length > 0
? user.name
: user?.email && user.email.length > 0
? user.email
: 'Unnamed User'
function dismiss(e: KeyboardEvent) {
if (e.key === 'Escape') {
Expand All @@ -19,55 +28,74 @@
}
</script>

<div class={'relative flex justify-center items-center ' + (open ? 'open' : '')}>
<div class={'relative flex ' + (open ? 'open' : '')}>
<button
class={'toggle grid place-content-center border border-solid hover:border-green overflow-hidden bg-currentColor w-8 h-8 md:w-12 md:h-12 ' +
(shouldDisplayInitial || shouldDisplayImage ? 'rounded-full' : 'rounded')}
class="flex flex-auto items-center gap-2"
on:click={() => {
open = !open
}}
on:keydown={dismiss}
>
<img
src={user.image}
alt="Avatar"
class="object-fill"
style={`display: ${shouldDisplayImage ? 'block' : 'none'}`}
referrerpolicy="no-referrer"
/>
{#if shouldDisplayInitial}
<span
class="w-5 h-5 font-bold text-xl leading-[1] pt-0.5 text-center text-chalkboard-10 dark:text-chalkboard-120"
data-testid="initial"
>
{user.name?.[0] || user.first_name?.[0] || user.email?.[0]}
</span>
{:else if !shouldDisplayImage}
<Person
data-testid="person-icon"
class="w-full text-chalkboard-10 dark:text-chalkboard-120"
<div
class={'toggle grid place-content-center border border-solid hover:border-green overflow-hidden bg-currentColor w-8 h-8 md:w-12 md:h-12 ' +
(shouldDisplayInitial || shouldDisplayImage ? 'rounded-full' : 'rounded')}
>
<img
src={user?.image}
alt="Avatar"
class="object-fill"
style={`display: ${shouldDisplayImage ? 'block' : 'none'}`}
referrerpolicy="no-referrer"
/>
{/if}
<span class="sr-only">Open menu</span>
{#if shouldDisplayInitial}
<span
class="uppercase w-5 h-5 font-bold text-xl leading-[1] pt-0.5 text-center text-chalkboard-10 dark:text-chalkboard-120"
data-testid="initial"
>
{user.name?.[0] || user.first_name?.[0] || user.email?.[0]}
</span>
{:else if !shouldDisplayImage}
<Person
data-testid="person-icon"
class="w-full text-chalkboard-10 dark:text-chalkboard-120"
/>
{/if}
</div>
<span class="mt-0.5 font-mono">{displayName}</span>
</button>
<dialog class="menu">
<menu class="contents">
<div class="p-4 pb-2">
<p class="font-mono">
{user?.first_name
? user.first_name + (user.last_name ? user.last_name : '')
? user.first_name + (user.last_name ? ' ' + user.last_name : '')
: user?.name || 'Unnamed User'}
</p>
<p class="font-mono text-sm text-chalkboard-70 dark:text-chalkboard-40">
{user?.email || '[email protected]'}
</p>
</div>
<a
data-sveltekit-reload
href={paths.SIGN_OUT}
class="text-sm font-mono uppercase tracking-[1px] hover:bg-green hover:text-chalkboard-120 text-center px-4 py-2 border-t"
href={paths.ZOO_BILLING}
class="menu-button"
on:keydown={dismiss}
target="_blank"
rel="noopener noreferrer"
>
<span>Billing Info</span>
<ArrowRight class="w-5 h-5 inline-block origin-center -rotate-45 ml-1" />
</a>
<a
href={paths.GITHUB_NEW_ISSUE}
class="menu-button"
on:keydown={dismiss}
target="_blank"
rel="noopener noreferrer"
>
<span>Report UI Issue</span>
<ArrowRight class="w-5 h-5 inline-block origin-center -rotate-45 ml-1" />
</a>
<a data-sveltekit-reload href={paths.SIGN_OUT} class="menu-button" on:keydown={dismiss}>
Sign Out
</a>
</menu>
Expand All @@ -76,33 +104,42 @@

<style lang="postcss">
.menu {
@apply absolute top-full -right-4;
@apply z-10 mt-1 mr-0;
@apply absolute bottom-full left-0;
@apply z-10 mb-2;
@apply text-chalkboard-120 dark:text-chalkboard-10;
@apply bg-white dark:bg-chalkboard-90;
@apply border-solid border-2 border-chalkboard-100;
@apply border border-chalkboard-100 dark:border-chalkboard-20;
@apply flex flex-col gap-5;
@apply w-screen text-right;
@apply flex flex-col;
@apply w-screen;
max-inline-size: min(90vw, 250px);
min-inline-size: 150px;
@apply shadow-md;
/* These will transition in */
pointer-events: none;
opacity: 0;
translate: 1px 10px;
translate: -1px 10px;
transition: transform 0.2s ease-out, opacity 0.1s ease-out;
}
.open .menu {
pointer-events: auto;
opacity: 1;
translate: 1px 0px;
translate: -1px 0px;
}
.open .toggle::after {
content: '';
@apply fixed inset-0 z-0 bg-chalkboard-110/20;
@apply pointer-events-auto;
}
.menu-button {
@apply uppercase tracking-[1px] hover:bg-green hover:text-chalkboard-120;
@apply flex gap-2 justify-center items-center text-sm font-mono text-center px-4 py-2 border-t;
}
.menu-button span {
@apply pt-0.5;
}
</style>
22 changes: 16 additions & 6 deletions src/components/DownloadButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@
import type { ConvertResponse } from '../routes/api/convert/[output_format]/+server'
import { toKebabCase } from '$lib/toKebabCase'
import LoadingIndicator from './LoadingIndicator.svelte'
import { onNavigate } from '$app/navigation'
import { tick } from 'svelte'
export let prompt: string = ''
export let outputs: PromptResponse['outputs']
export let className: string = ''
let link: HTMLAnchorElement
let currentOutput: CADFormat = 'gltf'
let outputData = outputs ? outputs[`source.${currentOutput}`] : ''
let status: 'loading' | 'ready' | 'failed' = 'ready'
onNavigate(() => {
currentOutput = 'gltf'
})
$: currentMimeType = CADMIMETypes[currentOutput]
$: dataUrl = `data:${currentMimeType};base64,${outputData}`
$: fileName = `${toKebabCase(prompt)}.${currentOutput}`
Expand All @@ -36,20 +43,23 @@
return
}
// TODO: handle asynchronous case where the conversion is not yet complete
outputs[`source.${currentOutput}`] = responseData.outputs[`source.${currentOutput}`]
outputData = outputs[`source.${currentOutput}`]
}
status = outputData ? 'ready' : 'failed'
if (outputData) {
await tick()
link.click()
}
}
</script>

<div class={`split-button ${status}${status === 'loading' ? ' shimmer ' : ' '}${className}`}>
{#if status == 'ready'}
<a href={dataUrl} download={fileName} class="mt-1">Download</a>
<a href={dataUrl} download={fileName} class="mt-1" bind:this={link}>Download</a>
{:else if status == 'loading'}
<button disabled class="mt-1">Loading&nbsp;</button>
<button disabled class="mt-1">Converting&nbsp;</button>
{:else}
<button disabled class="mt-1">Failed</button>
{/if}
Expand Down Expand Up @@ -77,8 +87,8 @@

<style lang="postcss">
.split-button {
@apply inline-flex justify-center items-center px-2 py-1 gap-4 relative;
@apply font-mono uppercase text-sm tracking-[1px] text-chalkboard-120 bg-green hover:hue-rotate-15;
@apply inline-flex md:flex-col lg:flex-row justify-center items-center px-2 py-4 md:py-1 gap-4 relative;
@apply font-mono uppercase md:text-sm tracking-[1px] text-chalkboard-120 bg-green hover:hue-rotate-15;
}
.split-button:global(.loading),
Expand Down
Loading

0 comments on commit 3d02177

Please sign in to comment.