* feat(frontend): record frontend errors

* feat(frontend): display result

* feat(frontend): Fix name shadowing

* feat(frontend): fix typo

* fix(frontend): better display frontend errors

* fix(frontend): wip

* wip

* feat(frontend): text input

* feat(frontend): enable double click to open + disabled mode when input is computed or connected

* feat(frontend): fix monaco setCode

* feat(frontend): revert package.json changes

* feat(frontend): fix syncing issues

* feat(frontend): adapt style

* feat(frontend): fix event propagation
This commit is contained in:
Faton Ramadani
2023-04-04 16:00:50 +02:00
committed by GitHub
parent b86ca29fde
commit 831ff60bdf
9 changed files with 264 additions and 75 deletions
@@ -11,7 +11,8 @@
import libStdContent from '$lib/es5.d.ts.txt?raw'
import { editor as meditor, languages, Range, Uri as mUri } from 'monaco-editor'
import { buildWorkerDefinition } from 'monaco-editor-workers'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import { createEventDispatcher, getContext, onDestroy, onMount } from 'svelte'
import type { AppViewerContext } from './apps/types'
languages.typescript.javascriptDefaults.setCompilerOptions({
target: languages.typescript.ScriptTarget.Latest,
@@ -372,6 +373,17 @@
let editor: meditor.IStandaloneCodeEditor
let model: meditor.ITextModel
const { componentControl, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
if ($selectedComponent) {
$componentControl[$selectedComponent[0]] = {
setCode: (value: string) => {
code = value
setCode(value)
}
}
}
export let code: string = ''
export let hash: string = createHash()
export let automaticLayout = true
@@ -29,13 +29,15 @@
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'textcomponent'> | undefined = undefined
export let render: boolean
export let editorMode: boolean = false
let resolvedConfig = initConfig(
components['textcomponent'].initialData.configuration,
configuration
)
const { app, worldStore, mode } = getContext<AppViewerContext>('AppViewerContext')
const { app, worldStore, mode, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
const editorcontext = getContext<AppEditorContext>('AppEditorContext')
@@ -79,10 +81,67 @@
}
}
function getClassesByType() {
switch (resolvedConfig.style) {
case 'Title':
return 'h1-textarea'
case 'Subtitle':
return 'h3-textarea'
case 'Body':
return 'p-textarea'
case 'Caption':
return 'p-textarea'
case 'Label':
return 'label'
default:
return 'p-textarea'
}
}
let component = 'p'
let classes = ''
function getHorizontalAlignement() {
if (horizontalAlignment) {
switch (horizontalAlignment) {
case 'left':
return '!text-left'
case 'center':
return '!text-center'
case 'right':
return '!text-right'
default:
return '!text-left'
}
}
}
$: resolvedConfig.style && (component = getComponent())
$: resolvedConfig.style && (classes = getClasses())
$: initialValue = componentInput?.type == 'template' ? componentInput.eval : ''
$: editableValue = initialValue ? JSON.parse(JSON.stringify(initialValue)) : ''
let rows = 1
function onInput(e: Event) {
const target = e.target as HTMLTextAreaElement
if (target.value) {
$componentControl[id]?.setCode?.(target.value)
editableValue
autosize()
}
}
function autosize() {
const el = document.getElementById(`text-${id}`)
setTimeout(() => {
if (el !== null) {
el.style.cssText = 'height:auto; padding:0'
el.style.cssText = 'height:' + el.scrollHeight + 'px'
}
}, 0)
}
</script>
{#each Object.keys(components['textcomponent'].initialData.configuration) as key (key)}
@@ -95,58 +154,99 @@
{/each}
<RunnableWrapper {outputs} {render} {componentInput} {id} bind:initializing bind:result>
<div class="h-full w-full overflow-hidden">
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
{#if !result || result === ''}
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">
No text
</div>
{:else}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="flex flex-wrap gap-2 pb-0.5 {$mode === 'dnd' && componentInput?.type == 'template'
? 'cursor-text'
: ''}"
on:click={() => {
if ($mode === 'dnd' && componentInput?.type == 'template') {
let ontextfocus = editorcontext?.ontextfocus
if (ontextfocus) {
get(ontextfocus)?.()
}
<div
class="h-full w-full overflow-hidden"
on:dblclick={() => {
if (!editorMode) {
editorMode = true
document.getElementById(`text-${id}`)?.focus()
autosize()
}
}}
on:keydown|stopPropagation
>
{#if editorMode && componentInput?.type == 'template'}
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
<textarea
class={twMerge(
'whitespace-pre-wrap !outline-none !border-0 !bg-transparent !resize-none !overflow-hidden !ring-0 !p-0 text-center',
$app.css?.['textcomponent']?.['text']?.class,
customCss?.text?.class,
classes,
getClasses(),
getClassesByType(),
getHorizontalAlignement()
)}
on:pointerdown|stopPropagation
style={[$app.css?.['textcomponent']?.['text']?.style, customCss?.text?.style].join(';')}
id={`text-${id}`}
on:pointerenter={() => {
const elem = document.getElementById(`text-${id}`)
if (elem) {
elem.focus()
}
}}
>
<svelte:element
this={component}
class={twMerge(
'whitespace-pre-wrap',
$app.css?.['textcomponent']?.['text']?.class,
customCss?.text?.class,
classes
)}
style={[$app.css?.['textcomponent']?.['text']?.style, customCss?.text?.style].join(';')}
{rows}
on:input={onInput}
value={editableValue}
/>
</AlignWrapper>
{:else}
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
{#if !result || result === ''}
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">
No text
</div>
{:else}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="flex flex-wrap gap-2 pb-0.5 {$mode === 'dnd' &&
componentInput?.type == 'template'
? 'cursor-text'
: ''}"
on:click={() => {
if ($mode === 'dnd' && componentInput?.type == 'template') {
let ontextfocus = editorcontext?.ontextfocus
if (ontextfocus) {
get(ontextfocus)?.()
}
}
}}
>
{String(result)}
{#if resolvedConfig.tooltip != ''}
<Tooltip>{resolvedConfig.tooltip}</Tooltip>
{/if}
{#if resolvedConfig.copyButton && result}
<Popover notClickable>
<Button
variant="border"
size="xs"
color="dark"
btnClasses="!p-1"
on:click={() => copyToClipboard(result)}
>
<Clipboard size={14} strokeWidth={2} />
</Button>
<svelte:fragment slot="text">Copy to clipboard</svelte:fragment>
</Popover>
{/if}
</svelte:element>
</div>
{/if}
</AlignWrapper>
<svelte:element
this={component}
class={twMerge(
'whitespace-pre-wrap',
$app.css?.['textcomponent']?.['text']?.class,
customCss?.text?.class,
classes
)}
style={[$app.css?.['textcomponent']?.['text']?.style, customCss?.text?.style].join(
';'
)}
>
{String(result)}
{#if resolvedConfig.tooltip != ''}
<Tooltip>{resolvedConfig.tooltip}</Tooltip>
{/if}
{#if resolvedConfig.copyButton && result}
<Popover notClickable>
<Button
variant="border"
size="xs"
color="dark"
btnClasses="!p-1"
on:click={() => copyToClipboard(result)}
>
<Clipboard size={14} strokeWidth={2} />
</Button>
<svelte:fragment slot="text">Copy to clipboard</svelte:fragment>
</Popover>
{/if}
</svelte:element>
</div>
{/if}
</AlignWrapper>
{/if}
</div>
</RunnableWrapper>
@@ -1,6 +1,7 @@
<script lang="ts">
import { twMerge } from 'tailwind-merge'
import type { HorizontalAlignment, VerticalAlignment } from '../../types'
import { tailwindHorizontalAlignment, tailwindVerticalAlignment } from '../../utils'
export let horizontalAlignment: HorizontalAlignment | undefined = undefined
export let verticalAlignment: VerticalAlignment | undefined = undefined
@@ -10,26 +11,6 @@
export let style = ''
export let render: boolean = true
function tailwindHorizontalAlignment(alignment?: HorizontalAlignment) {
if (!alignment) return ''
const classes: Record<HorizontalAlignment, string> = {
left: 'justify-start',
center: 'justify-center',
right: 'justify-end'
}
return classes[alignment]
}
function tailwindVerticalAlignment(alignment?: VerticalAlignment) {
if (!alignment) return ''
const classes: Record<VerticalAlignment, string> = {
top: 'items-start',
center: 'items-center',
bottom: 'items-end'
}
return classes[alignment]
}
$: classes = twMerge(
'flex z-auto',
noWFull ? '' : 'w-full',
@@ -1,7 +1,7 @@
<script lang="ts">
import { classNames } from '$lib/utils'
import type { AppViewerContext } from '../types'
import { Anchor, Bug, Expand, Move } from 'lucide-svelte'
import { Anchor, Bug, Expand, Move, Pen } from 'lucide-svelte'
import { createEventDispatcher, getContext } from 'svelte'
import Popover from '$lib/components/Popover.svelte'
import { Alert, Button } from '$lib/components/common'
@@ -14,6 +14,8 @@
export let locked: boolean = false
export let hover: boolean = false
export let shouldHideActions: boolean = false
export let hasInlineEditor: boolean = false
export let inlineEditorOpened: boolean = false
const dispatch = createEventDispatcher()
@@ -49,6 +51,23 @@
{#if selected && !shouldHideActions}
<div class="top-[-9px] -right-[8px] flex flex-row absolute gap-1.5 z-50">
{#if hasInlineEditor}
<button
title="Expand"
class={classNames(
'px-1 text-2xs py-0.5 font-bold w-fit border cursor-pointer rounded-sm',
'bg-indigo-100 text-indigo-600 border-indigo-500 hover:bg-indigo-200 hover:text-indigo-800'
)}
on:click={() => dispatch('triggerInlineEditor')}
on:pointerdown|stopPropagation
>
{#if inlineEditorOpened}
<Pen aria-label="Unlock position" size={14} class="text-orange-500" />
{:else}
<Pen aria-label="Lock position" size={14} />
{/if}
</button>
{/if}
<button
title="Expand"
class={classNames(
@@ -55,6 +55,8 @@
let initializing: boolean | undefined = undefined
let componentContainerHeight: number = 0
let inlineEditorOpened: boolean = false
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
@@ -81,6 +83,13 @@
on:lock
on:expand
{locked}
{inlineEditorOpened}
hasInlineEditor={component.type === 'textcomponent' &&
component.componentInput &&
component.componentInput.type !== 'connected'}
on:triggerInlineEditor={() => {
inlineEditorOpened = !inlineEditorOpened
}}
/>
{/if}
@@ -207,6 +216,7 @@
configuration={component.configuration}
customCss={component.customCss}
bind:initializing
bind:editorMode={inlineEditorOpened}
componentInput={component.componentInput}
{render}
/>
@@ -158,7 +158,7 @@
noVariablePicker
/>
{:else if componentSettings.item.data.componentInput.type === 'template' && componentSettings.item.data.componentInput !== undefined}
<div class="py-1 min-h-[28px] rounded border border-1 border-gray-500">
<div class="py-1 min-h-[28px] rounded border border-1 border-gray-500">
<TemplateEditor
bind:this={editor}
fontSize={12}
@@ -159,6 +159,7 @@ export type AppViewerContext = {
right?: (skipTableActions?: boolean | undefined) => string | boolean
setTab?: (index: number) => void
agGrid?: { api: any; columnApi: any }
setCode?: (value: string) => void
}
>
>
+27 -1
View File
@@ -6,7 +6,13 @@ import { twMerge } from 'tailwind-merge'
import type { AppComponent } from './editor/component'
import type { AppInput, InputType, ResultAppInput, StaticAppInput } from './inputType'
import type { Output } from './rx'
import type { App, ComponentCssProperty, GridItem } from './types'
import type {
App,
ComponentCssProperty,
GridItem,
HorizontalAlignment,
VerticalAlignment
} from './types'
export function allItems(
grid: GridItem[],
@@ -262,3 +268,23 @@ export function concatCustomCss<T extends Record<string, ComponentCssProperty>>(
})
) as T
}
export function tailwindHorizontalAlignment(alignment?: HorizontalAlignment) {
if (!alignment) return ''
const classes: Record<HorizontalAlignment, string> = {
left: 'justify-start',
center: 'justify-center',
right: 'justify-end'
}
return classes[alignment]
}
export function tailwindVerticalAlignment(alignment?: VerticalAlignment) {
if (!alignment) return ''
const classes: Record<VerticalAlignment, string> = {
top: 'items-start',
center: 'items-center',
bottom: 'items-end'
}
return classes[alignment]
}
+40
View File
@@ -482,6 +482,46 @@ const config = {
fontFamily: theme('fontFamily.mono'),
fontSize: theme('fontSize.sm') + ' !important',
lineHeight: theme('lineHeight.4') + ' !important'
},
'.h1-textarea': {
fontSize: '24px !important',
fontWeight: `${theme('fontWeight.extrabold')} !important`,
lineHeight: '1.05 !important',
color: theme('!colors.gray.800'),
[`@media (min-width: ${theme('screens.lg')})`]: {
fontSize: '26px !important'
},
[`@media (min-width: ${theme('screens.fhd')})`]: {
fontSize: '29px !important'
},
[`@media (min-width: ${theme('screens.qhd')})`]: {
fontSize: '34px !important'
}
},
'.h3-textarea': {
fontSize: '18px !important',
fontWeight: `${theme('fontWeight.bold')} !important`,
lineHeight: '1.2 !important',
color: theme('!colors.gray.600'),
[`@media (min-width: ${theme('screens.fhd')})`]: {
fontSize: '20px !important'
},
[`@media (min-width: ${theme('screens.qhd')})`]: {
fontSize: '22px !important'
}
},
'.p-textarea': {
fontSize: '16px !important',
fontWeight: `${theme('fontWeight.normal')} !important`,
lineHeight: '1.5 !important',
color: theme('!colors.gray.600'),
[`@media (min-width: ${theme('screens.fhd')})`]: {
fontSize: '18px !important'
},
[`@media (min-width: ${theme('screens.qhd')})`]: {
fontSize: '20px !important'
}
}
})
addComponents({