finishing touches to editor and started homepage
This commit is contained in:
@@ -8,6 +8,10 @@ export default defineConfig({
|
||||
integrations: [preact()],
|
||||
output: 'server',
|
||||
|
||||
devToolbar: {
|
||||
enabled: false,
|
||||
},
|
||||
|
||||
adapter: node({
|
||||
mode: 'standalone',
|
||||
}),
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 243 KiB |
@@ -3,10 +3,10 @@ import clsx from 'clsx'
|
||||
|
||||
import { TreeView } from '@/client/components/TreeView.jsx'
|
||||
import { Editor } from '@/client/components/Editor.jsx'
|
||||
import { useEffect } from 'preact/hooks'
|
||||
import { useEffect, useRef } from 'preact/hooks'
|
||||
import { Terminal } from './Terminal.jsx'
|
||||
|
||||
import { clamp, fetchJson, stripPrefix } from '@/client/utils.js'
|
||||
import { clamp, fetchJson, stripPrefix, useClickOutside } from '@/client/utils.js'
|
||||
|
||||
export const Ide = ({}) => {
|
||||
const ws = useSignal(null)
|
||||
@@ -42,10 +42,20 @@ export const Ide = ({}) => {
|
||||
return tabs.value[activeTab.value].content
|
||||
})
|
||||
|
||||
const runCompoundButtonOpen = useSignal(false)
|
||||
|
||||
const runCompoundPopupRef = useRef(null)
|
||||
|
||||
useClickOutside(runCompoundPopupRef, () => {
|
||||
runCompoundButtonOpen.value = false
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="ide">
|
||||
<div class="sidebar">
|
||||
<div class="logo">PHC / Run</div>
|
||||
<div class="logo">
|
||||
<a href="/">PHC / Run</a>
|
||||
</div>
|
||||
|
||||
<div class="flex-row">
|
||||
<button class="icon">
|
||||
@@ -82,6 +92,12 @@ export const Ide = ({}) => {
|
||||
console.log('content', content)
|
||||
|
||||
batch(() => {
|
||||
const existingIndex = tabs.value.findIndex(tab => tab.id === path)
|
||||
if (existingIndex !== -1) {
|
||||
activeTab.value = existingIndex
|
||||
return
|
||||
}
|
||||
|
||||
tabs.value = [
|
||||
...tabs.value,
|
||||
{
|
||||
@@ -120,8 +136,8 @@ export const Ide = ({}) => {
|
||||
alt="profile picture"
|
||||
/>
|
||||
</div>
|
||||
<div class="username">{'aziis98'}</div>
|
||||
<div class="email">{'aziis98'}@exmaple.com</div>
|
||||
<div class="username">{'username'}</div>
|
||||
<div class="email">{'username'}@exmaple.com</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header">
|
||||
@@ -156,20 +172,28 @@ export const Ide = ({}) => {
|
||||
>
|
||||
Run
|
||||
</span>
|
||||
<span class="part">
|
||||
<span
|
||||
class="part"
|
||||
onMouseDown={e => {
|
||||
e.stopPropagation()
|
||||
runCompoundButtonOpen.value = !runCompoundButtonOpen.value
|
||||
}}
|
||||
>
|
||||
<span class="material-symbols-outlined">keyboard_arrow_down</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="popup anchor-mode-1">
|
||||
<div class="menu">
|
||||
<div class="item">Run</div>
|
||||
<div class="item">Debug</div>
|
||||
<div class="item">Test</div>
|
||||
<div class="item">Profile</div>
|
||||
<div class="item">Bench</div>
|
||||
{runCompoundButtonOpen.value && (
|
||||
<div class="popup anchor-mode-1" ref={runCompoundPopupRef}>
|
||||
<div class="menu">
|
||||
<div class="item">Run</div>
|
||||
<div class="item">Debug</div>
|
||||
<div class="item">Test</div>
|
||||
<div class="item">Profile</div>
|
||||
<div class="item">Bench</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -184,7 +208,7 @@ export const Ide = ({}) => {
|
||||
<div class="buttons">
|
||||
<button
|
||||
class="flat icon"
|
||||
onClick={e => {
|
||||
onMouseDown={e => {
|
||||
e.stopPropagation()
|
||||
|
||||
batch(() => {
|
||||
|
||||
@@ -62,7 +62,7 @@ const TreeViewNode = ({ listDir, actionOpenFile, node, depth, path }) => {
|
||||
class="entry folder"
|
||||
style={{ '--depth': depth }}
|
||||
title={path}
|
||||
onClick={() => setCollapsed(c => !c)}
|
||||
onMouseDown={() => setCollapsed(c => !c)}
|
||||
>
|
||||
<div class="material-symbols-outlined">folder</div>
|
||||
<div class="name">{node.name}/</div>
|
||||
@@ -88,7 +88,7 @@ const TreeViewNode = ({ listDir, actionOpenFile, node, depth, path }) => {
|
||||
class="entry file"
|
||||
style={{ '--depth': depth }}
|
||||
title={path}
|
||||
onClick={() => {
|
||||
onMouseDown={() => {
|
||||
console.log('open', path)
|
||||
actionOpenFile?.(path)
|
||||
}}
|
||||
|
||||
+142
-1
@@ -196,6 +196,18 @@ input[type='submit'],
|
||||
}
|
||||
}
|
||||
|
||||
a,
|
||||
a:visited {
|
||||
color: #8000ff;
|
||||
text-decoration: none;
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline solid 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.run-style {
|
||||
--fg: #fff;
|
||||
|
||||
@@ -400,6 +412,15 @@ input[type='submit'],
|
||||
.logo {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
|
||||
a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .spacer {
|
||||
@@ -545,7 +566,7 @@ input[type='submit'],
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
|
||||
&:hover {
|
||||
background: #f8f8f8;
|
||||
background: #f4f4f4;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -625,6 +646,126 @@ input[type='submit'],
|
||||
}
|
||||
}
|
||||
|
||||
.homepage {
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
gap: 1rem;
|
||||
|
||||
padding: 1rem;
|
||||
|
||||
place-content: center;
|
||||
|
||||
background: linear-gradient(180deg, #f5efff 0%, #fafcfe 100%);
|
||||
|
||||
> section {
|
||||
display: grid;
|
||||
|
||||
grid-auto-flow: row;
|
||||
grid-auto-rows: auto;
|
||||
|
||||
align-content: start;
|
||||
|
||||
padding: 3rem 0;
|
||||
gap: 3rem;
|
||||
|
||||
min-height: 100vh;
|
||||
|
||||
max-width: 65rem;
|
||||
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&.hero-section {
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
|
||||
min-height: calc(100vh + 20rem);
|
||||
|
||||
padding-top: 20vh;
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-top: 3rem;
|
||||
|
||||
width: 100%;
|
||||
max-width: 65rem;
|
||||
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
box-shadow: 0 0 0.25rem 0 #680bff08, 0 0.5rem 1rem 0 #680bff10;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
border-top: 2px solid #d9d9d9;
|
||||
}
|
||||
|
||||
> .features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
||||
|
||||
gap: 1rem;
|
||||
|
||||
width: 100%;
|
||||
|
||||
> .feature-item {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
|
||||
gap: 0.5rem;
|
||||
|
||||
/* width: 100%; */
|
||||
|
||||
padding: 1rem;
|
||||
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
box-shadow: 0 0.125rem 0.25rem 0 #00000010;
|
||||
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
|
||||
padding: 1rem;
|
||||
|
||||
place-content: center;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
|
||||
border-top: 1px solid #d9d9d9;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
|
||||
body {
|
||||
|
||||
@@ -27,3 +27,18 @@ export function stripPrefix(s, prefix) {
|
||||
export function clamp(min, value, max) {
|
||||
return Math.max(min, Math.min(value, max))
|
||||
}
|
||||
|
||||
export function useClickOutside(ref, handler) {
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event) {
|
||||
if (ref.current && !ref.current.contains(event.target)) {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
}, [ref, handler])
|
||||
}
|
||||
|
||||
+45
-7
@@ -4,11 +4,49 @@ import '../client/style.css'
|
||||
import Base from '../layouts/Base.astro'
|
||||
---
|
||||
|
||||
<Base>
|
||||
<h1>docker+xterm.js</h1>
|
||||
<div id="terminal"></div>
|
||||
|
||||
<script>
|
||||
import '../client/term.js'
|
||||
</script>
|
||||
<Base title="PHC / Run">
|
||||
<div class="homepage">
|
||||
<section class="hero-section">
|
||||
<h1>PHC / Run</h1>
|
||||
<h2>A platform to easily create, run and share code projects and prototypes</h2>
|
||||
<img src="/public/screenshot-1.png" alt="phc run screenshot" />
|
||||
</section>
|
||||
<section>
|
||||
<h2>Features</h2>
|
||||
<div class="features">
|
||||
<div class="feature-item">
|
||||
<h3>Multiple Language Support</h3>
|
||||
<p>
|
||||
PHC / Run supports multiple languages, including Python, JavaScript, Ruby, Java, C, C++, and
|
||||
many more.
|
||||
</p>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<h3>Share Code</h3>
|
||||
<p>Share a link to your code with anyone, and they can run it in their browser.</p>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<h3>Collaborate</h3>
|
||||
<p>Invite others to collaborate on your code projects.</p>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<h3>Run Code</h3>
|
||||
<p>Run your code in the browser and see the output instantly without having to install anything.</p>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<h3>Docker Support</h3>
|
||||
<p>All code runs in docker containers, so you can run any code without worrying about security.</p>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<h3>Open Source</h3>
|
||||
<p>PHC / Run is licensed under the AGPL license, so you can self-host it if you want.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<footer>
|
||||
<p>
|
||||
<a href="/login">Login</a> to get started.
|
||||
</p>
|
||||
</footer>
|
||||
</Base>
|
||||
|
||||
Reference in New Issue
Block a user