Skip to content

Commit

Permalink
Page can be previewed, but no styles yet.
Browse files Browse the repository at this point in the history
  • Loading branch information
cibernox committed Oct 20, 2023
1 parent 0c05dc7 commit 7e5c525
Show file tree
Hide file tree
Showing 17 changed files with 7,489 additions and 30 deletions.
6 changes: 3 additions & 3 deletions assets/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions assets/svelte/components/BrowserFrame.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script lang="ts">
// import type { Page } from '../../routes/api/[site]/components/lib';
// import { page } from '$lib/stores/page';
// import DropdownMenu from '$lib/components/DropdownMenu.svelte';
// import NewPageDialog from '$lib/components/NewPageDialog.svelte';
// import { goto } from '$app/navigation';
import type { Page } from "$lib/types";
export let page: Page;
// function handlePageSelect(e: CustomEvent<Page>) {
// goto(e.detail.id);
// }
function getPageName(page: Page): string {
return (!page.path || page.path === '') ? 'index' : page.path;
}
</script>
<div
class="flex-1 flex flex-col"
data-test-id="fake-browser">
<div
class="bg-gray-50 border-b border-gray-200 border-solid rounded-t-xl h-12 px-3.5 flex"
data-test-id="address-bar">
<div class="ml-4 py-2">
<span class="inline-block h-2 w-2 ml-2 rounded-full bg-red-900"></span>
<span class="inline-block h-2 w-2 ml-2 rounded-full bg-amber-400"></span>
<span class="inline-block h-2 w-2 ml-2 rounded-full bg-lime-700"></span>
</div>
<div class="flex-1 py-2.5 overflow-visible">
<div class="rounded bg-white bg-gray-50 border-b border-gray-200 shadow max-w-xs mx-auto text-center py-0.5 relative">
<span data-test-id="url-box">{getPageName(page)}</span>

<!-- <NewPageDialog let:show>
<DropdownMenu items={pages} on:select={handlePageSelect} let:item >
{getPageName(item)}
<button slot="extra" let:close type="button" on:click={() => { close(); show() }}>Add new page</button>
</DropdownMenu>
</NewPageDialog> -->
</div>
</div>
<div class="py-3">
D | T | P
</div>
</div>
<slot/>
</div>
8 changes: 4 additions & 4 deletions assets/svelte/components/ComponentsSidebar.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { translate } from '../lib/utils/animations';
import { currentComponentCategory } from '../lib/stores/currentComponentCategory';
import { draggedObject } from '../lib/stores/dragAndDrop';
import { translate } from '$lib/utils/animations';
import { currentComponentCategory } from '$lib/stores/currentComponentCategory';
import { draggedObject } from '$lib/stores/dragAndDrop';
import type { ComponentCategory, ComponentDefinition } from '$lib/types';
export let components;
$: menuCategories = [{
Expand Down
27 changes: 27 additions & 0 deletions assets/svelte/components/LayoutAstNode.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script lang="ts">
import { isAstElement } from '$lib/stores/page';
import type { AstNode } from '$lib/types';
export let node: AstNode;
</script>

{#if isAstElement(node)}
{#if node.tag === 'html_comment'}
{@html "<!--" + node.content + "-->"}
{:else if node.tag === 'eex_comment'}
{@html "<!--" + node.content + "-->"}
{:else if node.tag === 'eex' && node.content[0] === '@inner_content'}
<slot/>
{:else if node.rendered_html}
{@html node.rendered_html}
{:else if node.attrs.selfClose}
<svelte:element this={node.tag} {...node.attrs}/>
{:else}
<svelte:element this={node.tag} {...node.attrs}>
{#each node.content as subnode, index}
<svelte:self node={subnode}/>
{/each}
</svelte:element>
{/if}
{:else}
{node}
{/if}
109 changes: 109 additions & 0 deletions assets/svelte/components/PageAstNode.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<script lang="ts">
import { selectedAstElement, slotTargetElement, selectedAstElementId, highlightedAstElement, isAstElement } from '$lib/stores/page';
import type { AstNode } from '$lib/types';
export let node: AstNode;
export let nodeId: string;
import { draggedObject } from '$lib/stores/dragAndDrop';
function handleDragEnter() {
if (isAstElement(node) && $draggedObject?.category === 'basic') {
$slotTargetElement = node;
}
}
function handleDragLeave() {
if (isAstElement(node) && $draggedObject?.category === 'basic' && $slotTargetElement === node) {
$slotTargetElement = undefined;
}
}
function handleMouseOver() {
isAstElement(node) && ($highlightedAstElement = node);
}
function handleMouseOut() {
$highlightedAstElement = undefined
}
function handleClick() {
$selectedAstElementId = nodeId
}
// When rendering raw html, we can't add the usual classes to the wrapper.
function highlightContent(wrapperDiv: HTMLElement, { selected, highlighted }: { selected: boolean, highlighted: boolean }) {
let startsWithOneChildren = wrapperDiv.children.length === 1;
if (startsWithOneChildren) {
let child = wrapperDiv.children[0];
child.setAttribute('data-selected', String(selected));
child.setAttribute('data-highlighted', String(highlighted));
}
return {
update({ selected, highlighted }: { selected: boolean, highlighted: boolean }) {
if (wrapperDiv.children.length === 1) {
let child = wrapperDiv.children[0];
child.setAttribute('data-selected', String(selected));
child.setAttribute('data-highlighted', String(highlighted));
} else if (wrapperDiv.children.length === 0 && wrapperDiv.childNodes.length === 1) {
wrapperDiv.setAttribute('data-nochildren', "true");
wrapperDiv.setAttribute('data-selected', String(selected));
wrapperDiv.setAttribute('data-highlighted', String(highlighted));
} else if (startsWithOneChildren) {
Array.from(wrapperDiv.children).forEach(child => {
child.removeAttribute('data-selected');
child.removeAttribute('data-highlighted');
});
}
},
destroy() {
// noop
}
}
}
</script>

{#if isAstElement(node)}
{#if node.tag === 'html_comment'}
{@html "<!--" + node.content + "-->"}
{:else if node.tag === 'eex_comment'}
{@html "<!--" + node.content + "-->"}
{:else if node.tag === 'eex' && node.content[0] === '@inner_content'}
<slot/>
{:else if node.rendered_html}
<div
class="contents"
on:mouseover|stopPropagation={handleMouseOver}
on:mouseout|stopPropagation={handleMouseOut}
on:click|preventDefault|stopPropagation={() => $selectedAstElementId = nodeId}
use:highlightContent={{selected: $selectedAstElement === node, highlighted: $highlightedAstElement === node}}
>{@html node.rendered_html}</div>
{:else if node.attrs.selfClose}
<svelte:element
this={node.tag}
{...node.attrs}
data-selected={$selectedAstElement === node}
data-highlighted={$highlightedAstElement === node}
data-slot-target={$slotTargetElement === node && !$slotTargetElement.attrs.selfClose}
on:dragenter|stopPropagation={handleDragEnter}
on:dragleave|stopPropagation={handleDragLeave}
on:mouseover|stopPropagation={handleMouseOver}
on:mouseout|stopPropagation={handleMouseOut}
on:click|preventDefault|stopPropagation={handleClick} />
{:else}
<svelte:element
this={node.tag}
{...node.attrs}
data-selected={$selectedAstElement === node}
data-highlighted={$highlightedAstElement === node}
data-slot-target={$slotTargetElement === node}
on:dragenter|stopPropagation={handleDragEnter}
on:dragleave|stopPropagation={handleDragLeave}
on:mouseover|stopPropagation={handleMouseOver}
on:mouseout|stopPropagation={handleMouseOut}
on:click|preventDefault|stopPropagation={() => $selectedAstElementId = nodeId}>
{#each node.content as subnode, index}
<svelte:self node={subnode} nodeId="{nodeId}.{index}"/>
{/each}
</svelte:element>
{/if}
{:else}
{node}
{/if}
67 changes: 67 additions & 0 deletions assets/svelte/components/PagePreview.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script lang="ts">
import { Page, AstElement } from "$lib/types"
import LayoutAstNode from './LayoutAstNode.svelte';
import PageAstNode from './PageAstNode.svelte';
import BrowserFrame from './BrowserFrame.svelte';
import { selectedAstElementId } from "$lib/stores/page";
export let page: Page;
let isDraggingOver = false;
async function handleDragDrop(e: DragEvent) {
// let { target } = e;
// $currentComponentCategory = null;
// if (!$draggedObject) return;
// if ($draggedObject.category === 'basic') {
// if (!(target instanceof HTMLElement)) return;
// if (target.id === 'fake-browser-content') return;
// if (!$slotTargetElement) return;
// if ($slotTargetElement.attrs.selfClose) return;
// addBasicComponentToTarget($slotTargetElement);
// } else {
// let { ast }: { ast: AstNode[] } = await renderComponentInPage($draggedObject.id, $page);
// $page.ast = [...$page.ast, ...ast]
// let { data: newPage} = await updatePage();
// $page = newPage;
// }
// isDraggingOver = false;
}
async function addBasicComponentToTarget(astElement: AstElement) {
// if (!$draggedObject) return;
// let componentDefinition = $draggedObject;
// $draggedObject = null;
// let targetNode = astElement;
// let { ast }: { ast: AstNode[] } = await renderComponentInPage(componentDefinition.id, $page);
// targetNode?.content.push(...ast);
// $slotTargetElement = undefined;
// let { data: newPage} = await updatePage();
// $page = newPage;
}
function dragOver() {
isDraggingOver = true;
}
</script>

<div class="flex-1 px-8 py-4 flex max-h-full" data-test-id="main">
<BrowserFrame page={page}>
<div
on:drop|preventDefault={handleDragDrop}
on:dragover|preventDefault={dragOver}
style="--outlined-id: title-1"
id="fake-browser-content"
class="bg-white rounded-b-xl relative overflow-hidden flex-1 {isDraggingOver && 'border-dashed border-blue-500 border-2'}"
data-test-id="browser-content">
<div id="page-wrapper" class="p-1 m-1" data-selected={$selectedAstElementId === 'root'}>
{#each page.layout.ast as layoutAstNode}
<LayoutAstNode node={layoutAstNode}>
{#each page.ast as astNode, index}
<PageAstNode node={astNode} nodeId="{index}"/>
{/each}
</LayoutAstNode>
{/each}
</div>
</div>
</BrowserFrame>
</div>
8 changes: 6 additions & 2 deletions assets/svelte/components/UiBuilder.svelte
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
<script lang="ts">
import ComponentsSidebar from "./ComponentsSidebar.svelte";
import Backdrop from "./Backdrop.svelte";
import type { ComponentDefinition, Page } from "../types";
import PagePreview from "./PagePreview.svelte";
import { page as pageStore } from "$lib/stores/page";
import type { ComponentDefinition, Page } from "$lib/types";
export let components: ComponentDefinition;
export let page: Page;
$: $pageStore = page;
</script>
<Backdrop/>
<div class="flex min-h-screen bg-gray-100" data-test-id="app-container">
<!-- Left sidebar -->
<ComponentsSidebar {components}/>

<!-- Main -->
<PagePreview {page} />

<!-- <slot/> -->
</div>
File renamed without changes.
53 changes: 53 additions & 0 deletions assets/svelte/stores/page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { writable, derived, get } from 'svelte/store';
import type { Writable, Readable } from 'svelte/store';
import type { AstElement, AstNode, Page } from '$lib/types';

export const page: Writable<Page> = writable();
export const selectedAstElementId: Writable<string | undefined> = writable();
// export const highlightedAstElementId: Writable<string | undefined> = writable();
export const highlightedAstElement: Writable<AstElement | undefined> = writable();
export const slotTargetElement: Writable<AstElement | undefined> = writable();

export const rootAstElement: Readable<AstElement | undefined> = derived([page], ([$page]) => {
// This is a virtual AstElement intended to simulate the page itself to reorder the components at the first level.
return { tag: 'root', attrs: {}, content: $page.ast };
});
export const selectedAstElement: Readable<AstElement | undefined> = derived([page, selectedAstElementId], ([$page, $selectedAstElementId]) => {
if ($selectedAstElementId) {
if ($selectedAstElementId === 'root') return get(rootAstElement);
return findAstElement($page.ast, $selectedAstElementId);
}
});

export function isAstElement(maybeNode: AstNode): maybeNode is AstElement {
return typeof maybeNode !== 'string';
}

export function findAstElement(ast: AstNode[], id: string): AstElement {
let indexes = id.split(".").map(s => parseInt(s, 10));
let node: AstNode = ast[indexes[0]] as AstElement
ast = node.content;
for(let i = 1; i < indexes.length; i++) {
node = ast[indexes[i]] as AstElement;
ast = node.content;
}
return node;
}
export function findAstElementId(astNode: AstNode): string | undefined {
let $page = get(page);
return _findAstElementId($page.ast, astNode, "");
}

export function _findAstElementId(ast: AstNode[], astNode: AstNode, id: string): string | undefined {
for(let i = 0; i < ast.length; i++) {
let currentNode = ast[i];
if (currentNode === astNode) {
return id + i;
} else if (isAstElement(currentNode)) {
let result = _findAstElementId(currentNode.content, astNode, id + i + ".");
if (result) {
return result;
}
}
}
}
4 changes: 2 additions & 2 deletions assets/svelte/lib/types.ts → assets/svelte/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export interface AstElement {
tag: string;
attrs: Record<string, string>,
content: AstNode[],
renderedHtml?: string
rendered_html?: string
}

export enum ComponentCategoryId {
Expand Down Expand Up @@ -71,7 +71,7 @@ export interface Component {
}
export interface RootComponent extends Component {
pageId: string;
renderedHtml: string | null
rendered_html: string | null
}

export interface Layout {
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ Application.put_env(:beacon_live_admin, DemoWeb.Endpoint,
pubsub_server: Demo.PubSub,
watchers: [
# esbuild: {Esbuild, :install_and_run, [:cdn_min, ~w(--watch)]},
tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
# node: ["build.js", "--watch", cd: Path.expand("../assets", __DIR__)]
tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]},
node: ["build.js", "--watch", cd: Path.expand("./assets", __DIR__)]
],
live_reload: [
patterns: [
Expand Down
Loading

0 comments on commit 7e5c525

Please sign in to comment.