Move JS script to the top of each Vue file

This commit is contained in:
Keavon Chambers
2023-03-10 03:44:49 -08:00
parent 625ddc23df
commit baf6dfbd32
50 changed files with 3482 additions and 3517 deletions
+226 -226
View File
@@ -1,229 +1,3 @@
<template>
<LayoutCol class="document">
<LayoutRow class="options-bar" :scrollableX="true">
<WidgetLayout :layout="document.state.documentModeLayout" />
<WidgetLayout :layout="document.state.toolOptionsLayout" />
<LayoutRow class="spacer"></LayoutRow>
<WidgetLayout :layout="document.state.documentBarLayout" />
</LayoutRow>
<LayoutRow class="shelf-and-viewport">
<LayoutCol class="shelf">
<LayoutCol class="tools" :scrollableY="true">
<WidgetLayout :layout="document.state.toolShelfLayout" />
</LayoutCol>
<LayoutCol class="spacer"></LayoutCol>
<LayoutCol class="working-colors">
<WidgetLayout :layout="document.state.workingColorsLayout" />
</LayoutCol>
</LayoutCol>
<LayoutCol class="viewport">
<LayoutRow class="bar-area">
<CanvasRuler :origin="rulerOrigin.x" :majorMarkSpacing="rulerSpacing" :numberInterval="rulerInterval" :direction="'Horizontal'" class="top-ruler" ref="rulerHorizontal" />
</LayoutRow>
<LayoutRow class="canvas-area">
<LayoutCol class="bar-area">
<CanvasRuler :origin="rulerOrigin.y" :majorMarkSpacing="rulerSpacing" :numberInterval="rulerInterval" :direction="'Vertical'" ref="rulerVertical" />
</LayoutCol>
<LayoutCol class="canvas-area" :style="{ cursor: canvasCursor }">
<EyedropperPreview
v-if="cursorEyedropper"
:colorChoice="cursorEyedropperPreviewColorChoice"
:primaryColor="cursorEyedropperPreviewColorPrimary"
:secondaryColor="cursorEyedropperPreviewColorSecondary"
:imageData="cursorEyedropperPreviewImageData"
:style="{ left: cursorLeft + 'px', top: cursorTop + 'px' }"
/>
<div class="canvas" @pointerdown="(e: PointerEvent) => canvasPointerDown(e)" @dragover="(e) => e.preventDefault()" @drop="(e) => pasteFile(e)" ref="canvasDiv" data-canvas>
<svg class="artboards" v-html="artboardSvg" :style="{ width: canvasWidthCSS, height: canvasHeightCSS }"></svg>
<svg
class="artwork"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
v-html="artworkSvg"
:style="{ width: canvasWidthCSS, height: canvasHeightCSS }"
></svg>
<svg class="overlays" v-html="overlaysSvg" :style="{ width: canvasWidthCSS, height: canvasHeightCSS }"></svg>
</div>
</LayoutCol>
<LayoutCol class="bar-area">
<PersistentScrollbar
:direction="'Vertical'"
:handlePosition="scrollbarPos.y"
@update:handlePosition="(newValue: number) => translateCanvasY(newValue)"
v-model:handleLength="scrollbarSize.y"
@pressTrack="(delta: number) => pageY(delta)"
class="right-scrollbar"
/>
</LayoutCol>
</LayoutRow>
<LayoutRow class="bar-area">
<PersistentScrollbar
:direction="'Horizontal'"
:handlePosition="scrollbarPos.x"
@update:handlePosition="(newValue: number) => translateCanvasX(newValue)"
v-model:handleLength="scrollbarSize.x"
@pressTrack="(delta: number) => pageX(delta)"
class="bottom-scrollbar"
/>
</LayoutRow>
</LayoutCol>
</LayoutRow>
</LayoutCol>
</template>
<style lang="scss">
.document {
height: 100%;
.options-bar {
height: 32px;
flex: 0 0 auto;
margin: 0 4px;
.spacer {
min-width: 40px;
}
}
.shelf-and-viewport {
.shelf {
flex: 0 0 auto;
.tools {
flex: 0 1 auto;
.icon-button[title^="Coming Soon"] {
opacity: 0.25;
transition: opacity 0.25s;
&:hover {
opacity: 1;
}
}
.icon-button:not(.active) {
.color-solid {
fill: var(--color-f-white);
}
.color-general {
fill: var(--color-data-general);
}
.color-vector {
fill: var(--color-data-vector);
}
.color-raster {
fill: var(--color-data-raster);
}
}
}
.spacer {
flex: 1 0 auto;
min-height: 8px;
}
.working-colors {
flex: 0 0 auto;
.widget-row {
min-height: 0;
.swatch-pair {
margin: 0;
}
.icon-button {
--widget-height: 0;
}
}
}
}
.viewport {
flex: 1 1 100%;
.canvas-area {
flex: 1 1 100%;
position: relative;
}
.bar-area {
flex: 0 0 auto;
}
.top-ruler {
padding-left: 16px;
margin-right: 16px;
}
.right-scrollbar {
margin-top: -16px;
}
.bottom-scrollbar {
margin-right: 16px;
}
.canvas {
background: var(--color-2-mildblack);
width: 100%;
height: 100%;
// Allows the SVG to be placed at explicit integer values of width and height to prevent non-pixel-perfect SVG scaling
position: relative;
overflow: hidden;
svg {
position: absolute;
// Fallback values if JS hasn't set these to integers yet
width: 100%;
height: 100%;
// Allows dev tools to select the artwork without being blocked by the SVG containers
pointer-events: none;
// Prevent inheritance from reaching the child elements
> * {
pointer-events: auto;
}
}
foreignObject {
width: 10000px;
height: 10000px;
overflow: visible;
div {
cursor: text;
background: none;
border: none;
margin: 0;
padding: 0;
overflow: visible;
white-space: pre-wrap;
display: inline-block;
// Workaround to force Chrome to display the flashing text entry cursor when text is empty
padding-left: 1px;
margin-left: -1px;
&:focus {
border: none;
outline: none; // Ok for contenteditable element
margin: -1px;
}
}
}
}
}
}
}
</style>
<script lang="ts">
import { defineComponent, nextTick } from "vue";
@@ -525,3 +299,229 @@ export default defineComponent({
},
});
</script>
<template>
<LayoutCol class="document">
<LayoutRow class="options-bar" :scrollableX="true">
<WidgetLayout :layout="document.state.documentModeLayout" />
<WidgetLayout :layout="document.state.toolOptionsLayout" />
<LayoutRow class="spacer"></LayoutRow>
<WidgetLayout :layout="document.state.documentBarLayout" />
</LayoutRow>
<LayoutRow class="shelf-and-viewport">
<LayoutCol class="shelf">
<LayoutCol class="tools" :scrollableY="true">
<WidgetLayout :layout="document.state.toolShelfLayout" />
</LayoutCol>
<LayoutCol class="spacer"></LayoutCol>
<LayoutCol class="working-colors">
<WidgetLayout :layout="document.state.workingColorsLayout" />
</LayoutCol>
</LayoutCol>
<LayoutCol class="viewport">
<LayoutRow class="bar-area">
<CanvasRuler :origin="rulerOrigin.x" :majorMarkSpacing="rulerSpacing" :numberInterval="rulerInterval" :direction="'Horizontal'" class="top-ruler" ref="rulerHorizontal" />
</LayoutRow>
<LayoutRow class="canvas-area">
<LayoutCol class="bar-area">
<CanvasRuler :origin="rulerOrigin.y" :majorMarkSpacing="rulerSpacing" :numberInterval="rulerInterval" :direction="'Vertical'" ref="rulerVertical" />
</LayoutCol>
<LayoutCol class="canvas-area" :style="{ cursor: canvasCursor }">
<EyedropperPreview
v-if="cursorEyedropper"
:colorChoice="cursorEyedropperPreviewColorChoice"
:primaryColor="cursorEyedropperPreviewColorPrimary"
:secondaryColor="cursorEyedropperPreviewColorSecondary"
:imageData="cursorEyedropperPreviewImageData"
:style="{ left: cursorLeft + 'px', top: cursorTop + 'px' }"
/>
<div class="canvas" @pointerdown="(e: PointerEvent) => canvasPointerDown(e)" @dragover="(e) => e.preventDefault()" @drop="(e) => pasteFile(e)" ref="canvasDiv" data-canvas>
<svg class="artboards" v-html="artboardSvg" :style="{ width: canvasWidthCSS, height: canvasHeightCSS }"></svg>
<svg
class="artwork"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
v-html="artworkSvg"
:style="{ width: canvasWidthCSS, height: canvasHeightCSS }"
></svg>
<svg class="overlays" v-html="overlaysSvg" :style="{ width: canvasWidthCSS, height: canvasHeightCSS }"></svg>
</div>
</LayoutCol>
<LayoutCol class="bar-area">
<PersistentScrollbar
:direction="'Vertical'"
:handlePosition="scrollbarPos.y"
@update:handlePosition="(newValue: number) => translateCanvasY(newValue)"
v-model:handleLength="scrollbarSize.y"
@pressTrack="(delta: number) => pageY(delta)"
class="right-scrollbar"
/>
</LayoutCol>
</LayoutRow>
<LayoutRow class="bar-area">
<PersistentScrollbar
:direction="'Horizontal'"
:handlePosition="scrollbarPos.x"
@update:handlePosition="(newValue: number) => translateCanvasX(newValue)"
v-model:handleLength="scrollbarSize.x"
@pressTrack="(delta: number) => pageX(delta)"
class="bottom-scrollbar"
/>
</LayoutRow>
</LayoutCol>
</LayoutRow>
</LayoutCol>
</template>
<style lang="scss">
.document {
height: 100%;
.options-bar {
height: 32px;
flex: 0 0 auto;
margin: 0 4px;
.spacer {
min-width: 40px;
}
}
.shelf-and-viewport {
.shelf {
flex: 0 0 auto;
.tools {
flex: 0 1 auto;
.icon-button[title^="Coming Soon"] {
opacity: 0.25;
transition: opacity 0.25s;
&:hover {
opacity: 1;
}
}
.icon-button:not(.active) {
.color-solid {
fill: var(--color-f-white);
}
.color-general {
fill: var(--color-data-general);
}
.color-vector {
fill: var(--color-data-vector);
}
.color-raster {
fill: var(--color-data-raster);
}
}
}
.spacer {
flex: 1 0 auto;
min-height: 8px;
}
.working-colors {
flex: 0 0 auto;
.widget-row {
min-height: 0;
.swatch-pair {
margin: 0;
}
.icon-button {
--widget-height: 0;
}
}
}
}
.viewport {
flex: 1 1 100%;
.canvas-area {
flex: 1 1 100%;
position: relative;
}
.bar-area {
flex: 0 0 auto;
}
.top-ruler {
padding-left: 16px;
margin-right: 16px;
}
.right-scrollbar {
margin-top: -16px;
}
.bottom-scrollbar {
margin-right: 16px;
}
.canvas {
background: var(--color-2-mildblack);
width: 100%;
height: 100%;
// Allows the SVG to be placed at explicit integer values of width and height to prevent non-pixel-perfect SVG scaling
position: relative;
overflow: hidden;
svg {
position: absolute;
// Fallback values if JS hasn't set these to integers yet
width: 100%;
height: 100%;
// Allows dev tools to select the artwork without being blocked by the SVG containers
pointer-events: none;
// Prevent inheritance from reaching the child elements
> * {
pointer-events: auto;
}
}
foreignObject {
width: 10000px;
height: 10000px;
overflow: visible;
div {
cursor: text;
background: none;
border: none;
margin: 0;
padding: 0;
overflow: visible;
white-space: pre-wrap;
display: inline-block;
// Workaround to force Chrome to display the flashing text entry cursor when text is empty
padding-left: 1px;
margin-left: -1px;
&:focus {
border: none;
outline: none; // Ok for contenteditable element
margin: -1px;
}
}
}
}
}
}
}
</style>
+269 -269
View File
@@ -1,272 +1,3 @@
<template>
<LayoutCol class="layer-tree" @dragleave="dragInPanel = false">
<LayoutRow class="options-bar" :scrollableX="true">
<WidgetLayout :layout="layerTreeOptionsLayout" />
</LayoutRow>
<LayoutRow class="layer-tree-rows" :scrollableY="true">
<LayoutCol class="list" ref="list" @click="() => deselectAllLayers()" @dragover="(e: DragEvent) => draggable && updateInsertLine(e)" @dragend="() => draggable && drop()">
<LayoutRow
class="layer-row"
v-for="(listing, index) in layers"
:key="String(listing.entry.path.slice(-1))"
:class="{ 'insert-folder': draggingData?.highlightFolder && draggingData?.insertFolder === listing.entry.path }"
>
<LayoutRow class="visibility">
<IconButton
:action="(e?: MouseEvent) => (toggleLayerVisibility(listing.entry.path), e?.stopPropagation())"
:size="24"
:icon="listing.entry.visible ? 'EyeVisible' : 'EyeHidden'"
:title="listing.entry.visible ? 'Visible' : 'Hidden'"
/>
</LayoutRow>
<div class="indent" :style="{ marginLeft: layerIndent(listing.entry) }"></div>
<button
v-if="listing.entry.layerType === 'Folder'"
class="expand-arrow"
:class="{ expanded: listing.entry.layerMetadata.expanded }"
@click.stop="handleExpandArrowClick(listing.entry.path)"
tabindex="0"
></button>
<LayoutRow
class="layer"
:class="{ selected: fakeHighlight ? fakeHighlight.includes(listing.entry.path) : listing.entry.layerMetadata.selected }"
:data-layer="String(listing.entry.path)"
:data-index="index"
:title="listing.entry.tooltip"
:draggable="draggable"
@dragstart="(e: DragEvent) => draggable && dragStart(e, listing)"
@click.exact="(e: MouseEvent) => selectLayer(false, false, false, listing, e)"
@click.shift.exact="(e: MouseEvent) => selectLayer(false, false, true, listing, e)"
@click.ctrl.exact="(e: MouseEvent) => selectLayer(true, false, false, listing, e)"
@click.ctrl.shift.exact="(e: MouseEvent) => selectLayer(true, false, true, listing, e)"
@click.meta.exact="(e: MouseEvent) => selectLayer(false, true, false, listing, e)"
@click.meta.shift.exact="(e: MouseEvent) => selectLayer(false, true, true, listing, e)"
@click.ctrl.meta="(e: MouseEvent) => e.stopPropagation()"
@click.alt="(e: MouseEvent) => e.stopPropagation()"
>
<LayoutRow class="layer-type-icon">
<IconLabel :icon="layerTypeData(listing.entry.layerType).icon" :title="layerTypeData(listing.entry.layerType).name" />
</LayoutRow>
<LayoutRow class="layer-name" @dblclick="() => onEditLayerName(listing)">
<input
data-text-input
type="text"
:value="listing.entry.name"
:placeholder="layerTypeData(listing.entry.layerType).name"
:disabled="!listing.editingName"
@blur="() => onEditLayerNameDeselect(listing)"
@keydown.esc="onEditLayerNameDeselect(listing)"
@keydown.enter="(e) => onEditLayerNameChange(listing, e)"
@change="(e) => onEditLayerNameChange(listing, e)"
/>
</LayoutRow>
<div class="thumbnail" v-html="listing.entry.thumbnail"></div>
</LayoutRow>
</LayoutRow>
</LayoutCol>
<div
class="insert-mark"
v-if="draggingData && !draggingData.highlightFolder && dragInPanel"
:style="{ left: markIndent(draggingData.insertFolder), top: markTopOffset(draggingData.markerHeight) }"
></div>
</LayoutRow>
</LayoutCol>
</template>
<style lang="scss">
.layer-tree {
// Options bar
.options-bar {
height: 32px;
flex: 0 0 auto;
margin: 0 4px;
align-items: center;
.widget-layout {
width: 100%;
min-width: 300px;
}
// Blend mode selector
.dropdown-input {
max-width: 120px;
}
// Blend mode selector and opacity slider
.dropdown-input,
.number-input {
flex: 1 1 auto;
}
}
// Layer tree
.layer-tree-rows {
margin-top: 4px;
// Crop away the 1px border below the bottom layer entry when it uses the full space of this panel
margin-bottom: -1px;
position: relative;
.layer-row {
flex: 0 0 auto;
align-items: center;
position: relative;
height: 32px;
margin: 0 4px;
border-bottom: 1px solid var(--color-4-dimgray);
.visibility {
flex: 0 0 auto;
height: 100%;
align-items: center;
.icon-button {
height: 100%;
width: calc(24px + 2 * 4px);
}
}
.expand-arrow {
padding: 0;
margin: 0;
margin-left: -16px;
width: 16px;
height: 100%;
border: none;
position: relative;
background: none;
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
border-radius: 2px;
&:hover {
background: var(--color-6-lowergray);
}
&::after {
content: "";
position: absolute;
width: 0;
height: 0;
border-style: solid;
border-width: 3px 0 3px 6px;
border-color: transparent transparent transparent var(--color-e-nearwhite);
&:hover {
color: var(--color-f-white);
}
}
&.expanded::after {
border-width: 6px 3px 0 3px;
border-color: var(--color-e-nearwhite) transparent transparent transparent;
&:hover {
color: var(--color-f-white);
}
}
}
.layer {
align-items: center;
z-index: 1;
width: 100%;
height: 100%;
padding: 0 4px;
border-radius: 2px;
margin-right: 8px;
&.selected {
background: var(--color-5-dullgray);
color: var(--color-f-white);
}
.layer-type-icon {
flex: 0 0 auto;
margin: 0 4px;
}
.layer-name {
flex: 1 1 100%;
margin: 0 4px;
input {
color: inherit;
background: none;
border: none;
outline: none; // Ok for input element
margin: 0;
padding: 0;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
border-radius: 2px;
height: 24px;
width: 100%;
&:disabled {
-webkit-user-select: none; // Required as of Safari 15.0 (Graphite's minimum version) through the latest release
user-select: none;
// Workaround for `user-select: none` not working on <input> elements
pointer-events: none;
}
&::placeholder {
color: inherit;
font-style: italic;
}
&:focus {
background: var(--color-1-nearblack);
padding: 0 4px;
&::placeholder {
opacity: 0.5;
}
}
}
}
.thumbnail {
width: 36px;
height: 24px;
margin: 2px 0;
margin-left: 4px;
background: white;
border-radius: 2px;
flex: 0 0 auto;
svg {
width: calc(100% - 4px);
height: calc(100% - 4px);
margin: 2px;
}
}
}
&.insert-folder .layer {
outline: 3px solid var(--color-e-nearwhite);
outline-offset: -3px;
}
}
.insert-mark {
position: absolute;
// `left` is applied dynamically
right: 0;
background: var(--color-e-nearwhite);
margin-top: -2px;
height: 5px;
z-index: 1;
pointer-events: none;
}
}
}
</style>
<script lang="ts">
import { defineComponent, nextTick } from "vue";
@@ -548,3 +279,272 @@ export default defineComponent({
},
});
</script>
<template>
<LayoutCol class="layer-tree" @dragleave="dragInPanel = false">
<LayoutRow class="options-bar" :scrollableX="true">
<WidgetLayout :layout="layerTreeOptionsLayout" />
</LayoutRow>
<LayoutRow class="layer-tree-rows" :scrollableY="true">
<LayoutCol class="list" ref="list" @click="() => deselectAllLayers()" @dragover="(e: DragEvent) => draggable && updateInsertLine(e)" @dragend="() => draggable && drop()">
<LayoutRow
class="layer-row"
v-for="(listing, index) in layers"
:key="String(listing.entry.path.slice(-1))"
:class="{ 'insert-folder': draggingData?.highlightFolder && draggingData?.insertFolder === listing.entry.path }"
>
<LayoutRow class="visibility">
<IconButton
:action="(e?: MouseEvent) => (toggleLayerVisibility(listing.entry.path), e?.stopPropagation())"
:size="24"
:icon="listing.entry.visible ? 'EyeVisible' : 'EyeHidden'"
:title="listing.entry.visible ? 'Visible' : 'Hidden'"
/>
</LayoutRow>
<div class="indent" :style="{ marginLeft: layerIndent(listing.entry) }"></div>
<button
v-if="listing.entry.layerType === 'Folder'"
class="expand-arrow"
:class="{ expanded: listing.entry.layerMetadata.expanded }"
@click.stop="handleExpandArrowClick(listing.entry.path)"
tabindex="0"
></button>
<LayoutRow
class="layer"
:class="{ selected: fakeHighlight ? fakeHighlight.includes(listing.entry.path) : listing.entry.layerMetadata.selected }"
:data-layer="String(listing.entry.path)"
:data-index="index"
:title="listing.entry.tooltip"
:draggable="draggable"
@dragstart="(e: DragEvent) => draggable && dragStart(e, listing)"
@click.exact="(e: MouseEvent) => selectLayer(false, false, false, listing, e)"
@click.shift.exact="(e: MouseEvent) => selectLayer(false, false, true, listing, e)"
@click.ctrl.exact="(e: MouseEvent) => selectLayer(true, false, false, listing, e)"
@click.ctrl.shift.exact="(e: MouseEvent) => selectLayer(true, false, true, listing, e)"
@click.meta.exact="(e: MouseEvent) => selectLayer(false, true, false, listing, e)"
@click.meta.shift.exact="(e: MouseEvent) => selectLayer(false, true, true, listing, e)"
@click.ctrl.meta="(e: MouseEvent) => e.stopPropagation()"
@click.alt="(e: MouseEvent) => e.stopPropagation()"
>
<LayoutRow class="layer-type-icon">
<IconLabel :icon="layerTypeData(listing.entry.layerType).icon" :title="layerTypeData(listing.entry.layerType).name" />
</LayoutRow>
<LayoutRow class="layer-name" @dblclick="() => onEditLayerName(listing)">
<input
data-text-input
type="text"
:value="listing.entry.name"
:placeholder="layerTypeData(listing.entry.layerType).name"
:disabled="!listing.editingName"
@blur="() => onEditLayerNameDeselect(listing)"
@keydown.esc="onEditLayerNameDeselect(listing)"
@keydown.enter="(e) => onEditLayerNameChange(listing, e)"
@change="(e) => onEditLayerNameChange(listing, e)"
/>
</LayoutRow>
<div class="thumbnail" v-html="listing.entry.thumbnail"></div>
</LayoutRow>
</LayoutRow>
</LayoutCol>
<div
class="insert-mark"
v-if="draggingData && !draggingData.highlightFolder && dragInPanel"
:style="{ left: markIndent(draggingData.insertFolder), top: markTopOffset(draggingData.markerHeight) }"
></div>
</LayoutRow>
</LayoutCol>
</template>
<style lang="scss">
.layer-tree {
// Options bar
.options-bar {
height: 32px;
flex: 0 0 auto;
margin: 0 4px;
align-items: center;
.widget-layout {
width: 100%;
min-width: 300px;
}
// Blend mode selector
.dropdown-input {
max-width: 120px;
}
// Blend mode selector and opacity slider
.dropdown-input,
.number-input {
flex: 1 1 auto;
}
}
// Layer tree
.layer-tree-rows {
margin-top: 4px;
// Crop away the 1px border below the bottom layer entry when it uses the full space of this panel
margin-bottom: -1px;
position: relative;
.layer-row {
flex: 0 0 auto;
align-items: center;
position: relative;
height: 32px;
margin: 0 4px;
border-bottom: 1px solid var(--color-4-dimgray);
.visibility {
flex: 0 0 auto;
height: 100%;
align-items: center;
.icon-button {
height: 100%;
width: calc(24px + 2 * 4px);
}
}
.expand-arrow {
padding: 0;
margin: 0;
margin-left: -16px;
width: 16px;
height: 100%;
border: none;
position: relative;
background: none;
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
border-radius: 2px;
&:hover {
background: var(--color-6-lowergray);
}
&::after {
content: "";
position: absolute;
width: 0;
height: 0;
border-style: solid;
border-width: 3px 0 3px 6px;
border-color: transparent transparent transparent var(--color-e-nearwhite);
&:hover {
color: var(--color-f-white);
}
}
&.expanded::after {
border-width: 6px 3px 0 3px;
border-color: var(--color-e-nearwhite) transparent transparent transparent;
&:hover {
color: var(--color-f-white);
}
}
}
.layer {
align-items: center;
z-index: 1;
width: 100%;
height: 100%;
padding: 0 4px;
border-radius: 2px;
margin-right: 8px;
&.selected {
background: var(--color-5-dullgray);
color: var(--color-f-white);
}
.layer-type-icon {
flex: 0 0 auto;
margin: 0 4px;
}
.layer-name {
flex: 1 1 100%;
margin: 0 4px;
input {
color: inherit;
background: none;
border: none;
outline: none; // Ok for input element
margin: 0;
padding: 0;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
border-radius: 2px;
height: 24px;
width: 100%;
&:disabled {
-webkit-user-select: none; // Required as of Safari 15.0 (Graphite's minimum version) through the latest release
user-select: none;
// Workaround for `user-select: none` not working on <input> elements
pointer-events: none;
}
&::placeholder {
color: inherit;
font-style: italic;
}
&:focus {
background: var(--color-1-nearblack);
padding: 0 4px;
&::placeholder {
opacity: 0.5;
}
}
}
}
.thumbnail {
width: 36px;
height: 24px;
margin: 2px 0;
margin-left: 4px;
background: white;
border-radius: 2px;
flex: 0 0 auto;
svg {
width: calc(100% - 4px);
height: calc(100% - 4px);
margin: 2px;
}
}
}
&.insert-folder .layer {
outline: 3px solid var(--color-e-nearwhite);
outline-offset: -3px;
}
}
.insert-mark {
position: absolute;
// `left` is applied dynamically
right: 0;
background: var(--color-e-nearwhite);
margin-top: -2px;
height: 5px;
z-index: 1;
pointer-events: none;
}
}
}
</style>
+335 -335
View File
@@ -1,338 +1,3 @@
<template>
<LayoutCol class="node-graph">
<LayoutRow class="options-bar"><WidgetLayout :layout="nodeGraphBarLayout" /></LayoutRow>
<LayoutRow
class="graph"
ref="graph"
@wheel="(e: WheelEvent) => scroll(e)"
@pointerdown="(e: PointerEvent) => pointerDown(e)"
@pointermove="(e: PointerEvent) => pointerMove(e)"
@pointerup="(e: PointerEvent) => pointerUp(e)"
@dblclick="(e: MouseEvent) => doubleClick(e)"
:style="{
'--grid-spacing': `${gridSpacing}px`,
'--grid-offset-x': `${transform.x * transform.scale}px`,
'--grid-offset-y': `${transform.y * transform.scale}px`,
'--dot-radius': `${dotRadius}px`,
}"
>
<LayoutCol class="node-list" data-node-list v-if="nodeListLocation" :style="{ marginLeft: `${nodeListX}px`, marginTop: `${nodeListY}px` }">
<TextInput placeholder="Search Nodes..." :value="searchTerm" @update:value="(val) => (searchTerm = val)" v-focus />
<LayoutCol v-for="nodeCategory in nodeCategories" :key="nodeCategory[0]">
<TextLabel>{{ nodeCategory[0] }}</TextLabel>
<TextButton v-for="nodeType in nodeCategory[1]" v-bind:key="String(nodeType)" :label="nodeType.name" :action="() => createNode(nodeType.name)" />
</LayoutCol>
<TextLabel v-if="nodeCategories.length === 0">No search results :(</TextLabel>
</LayoutCol>
<div
class="nodes"
ref="nodesContainer"
:style="{
transform: `scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`,
transformOrigin: `0 0`,
}"
>
<div
v-for="node in nodes"
:key="String(node.id)"
class="node"
:class="{ selected: selected.includes(node.id), previewed: node.previewed, disabled: node.disabled }"
:style="{
'--offset-left': (node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0),
'--offset-top': (node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0),
}"
:data-node="node.id"
>
<div class="primary">
<div class="ports">
<div
v-if="node.primaryInput"
class="input port"
data-port="input"
:data-datatype="node.primaryInput"
:style="{ '--data-color': `var(--color-data-${node.primaryInput})`, '--data-color-dim': `var(--color-data-${node.primaryInput}-dim)` }"
>
<div></div>
</div>
<div
v-if="node.outputs.length > 0"
class="output port"
data-port="output"
:data-datatype="node.outputs[0].dataType"
:style="{ '--data-color': `var(--color-data-${node.outputs[0].dataType})`, '--data-color-dim': `var(--color-data-${node.outputs[0].dataType}-dim)` }"
>
<div></div>
</div>
</div>
<IconLabel :icon="nodeIcon(node.displayName)" />
<TextLabel>{{ node.displayName }}</TextLabel>
</div>
<div v-if="[...node.exposedInputs, ...node.outputs.slice(1)].length > 0" class="parameters">
<div v-for="(parameter, index) in [...node.exposedInputs, ...node.outputs.slice(1)]" :key="index" class="parameter">
<div class="ports">
<div
v-if="index < node.exposedInputs.length"
class="input port"
data-port="input"
:data-datatype="parameter.dataType"
:style="{
'--data-color': `var(--color-data-${parameter.dataType})`,
'--data-color-dim': `var(--color-data-${parameter.dataType}-dim)`,
}"
>
<div></div>
</div>
<div
v-else
class="output port"
data-port="output"
:data-datatype="parameter.dataType"
:style="{ '--data-color': `var(--color-data-${parameter.dataType})`, '--data-color-dim': `var(--color-data-${parameter.dataType}-dim)` }"
>
<div></div>
</div>
</div>
<TextLabel :class="index < node.exposedInputs.length ? 'name' : 'output'">{{ parameter.name }}</TextLabel>
</div>
</div>
</div>
</div>
<div
class="wires"
:style="{
transform: `scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`,
transformOrigin: `0 0`,
}"
>
<svg>
<path
v-for="([pathString, dataType], index) in linkPaths"
:key="index"
:d="pathString"
:style="{ '--data-color': `var(--color-data-${dataType})`, '--data-color-dim': `var(--color-data-${dataType}-dim)` }"
/>
</svg>
</div>
</LayoutRow>
</LayoutCol>
</template>
<style lang="scss">
.node-graph {
height: 100%;
position: relative;
.node-list {
width: max-content;
position: fixed;
padding: 5px;
z-index: 3;
background-color: var(--color-3-darkgray);
.text-button + .text-button {
margin-left: 0;
margin-top: 4px;
}
}
.options-bar {
height: 32px;
margin: 0 4px;
flex: 0 0 auto;
align-items: center;
.widget-layout {
flex-direction: row;
flex-grow: 1;
justify-content: space-between;
}
}
.graph {
position: relative;
background: var(--color-2-mildblack);
width: calc(100% - 8px);
margin-left: 4px;
margin-bottom: 4px;
border-radius: 2px;
overflow: hidden;
// We're displaying the dotted grid in a pseudo-element because `image-rendering` is an inherited property and we don't want it to apply to child elements
&::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
background-size: var(--grid-spacing) var(--grid-spacing);
background-position: calc(var(--grid-offset-x) - var(--dot-radius)) calc(var(--grid-offset-y) - var(--dot-radius));
background-image: radial-gradient(circle at var(--dot-radius) var(--dot-radius), var(--color-3-darkgray) var(--dot-radius), transparent 0);
image-rendering: pixelated;
mix-blend-mode: screen;
}
}
.nodes,
.wires {
position: absolute;
width: 100%;
height: 100%;
&.wires {
width: 100%;
height: 100%;
pointer-events: none;
svg {
width: 100%;
height: 100%;
overflow: visible;
path {
fill: none;
// stroke: var(--color-data-raster-dim);
stroke: var(--data-color-dim);
stroke-width: 2px;
}
}
}
&.nodes {
.node {
position: absolute;
display: flex;
flex-direction: column;
min-width: 120px;
border-radius: 4px;
background: var(--color-4-dimgray);
left: calc((var(--offset-left) + 0.5) * 24px);
top: calc((var(--offset-top) - 0.5) * 24px);
&.selected {
border: 1px solid var(--color-e-nearwhite);
margin: -1px;
}
&.disabled {
background: var(--color-3-darkgray);
color: var(--color-a-softgray);
.icon-label {
fill: var(--color-a-softgray);
}
}
&.previewed {
outline: 3px solid var(--color-data-vector);
}
.primary {
display: flex;
align-items: center;
position: relative;
gap: 4px;
width: 100%;
height: 24px;
background: var(--color-5-dullgray);
border-radius: 4px;
.icon-label {
margin-left: 4px;
}
.text-label {
margin-right: 4px;
}
}
.parameters {
display: flex;
flex-direction: column;
width: 100%;
position: relative;
.parameter {
position: relative;
display: flex;
align-items: center;
height: 24px;
width: calc(100% - 24px * 2);
margin-left: 24px;
margin-right: 24px;
.text-label {
width: 100%;
&.output {
text-align: right;
}
}
}
// Squares to cover up the rounded corners of the primary area and make them have a straight edge
&::before,
&::after {
content: "";
position: absolute;
background: var(--color-5-dullgray);
width: 4px;
height: 4px;
top: -4px;
}
&::before {
left: 0;
}
&::after {
right: 0;
}
}
.ports {
position: absolute;
width: 100%;
height: 100%;
.port {
position: absolute;
margin: auto 0;
top: 0;
bottom: 0;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--data-color-dim);
// background: var(--color-data-raster-dim);
div {
background: var(--data-color);
// background: var(--color-data-raster);
width: 8px;
height: 8px;
border-radius: 50%;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
&.input {
left: calc(-12px - 6px);
}
&.output {
right: calc(-12px - 6px);
}
}
}
}
}
}
}
</style>
<script lang="ts">
import { defineComponent, nextTick } from "vue";
@@ -812,3 +477,338 @@ export default defineComponent({
},
});
</script>
<template>
<LayoutCol class="node-graph">
<LayoutRow class="options-bar"><WidgetLayout :layout="nodeGraphBarLayout" /></LayoutRow>
<LayoutRow
class="graph"
ref="graph"
@wheel="(e: WheelEvent) => scroll(e)"
@pointerdown="(e: PointerEvent) => pointerDown(e)"
@pointermove="(e: PointerEvent) => pointerMove(e)"
@pointerup="(e: PointerEvent) => pointerUp(e)"
@dblclick="(e: MouseEvent) => doubleClick(e)"
:style="{
'--grid-spacing': `${gridSpacing}px`,
'--grid-offset-x': `${transform.x * transform.scale}px`,
'--grid-offset-y': `${transform.y * transform.scale}px`,
'--dot-radius': `${dotRadius}px`,
}"
>
<LayoutCol class="node-list" data-node-list v-if="nodeListLocation" :style="{ marginLeft: `${nodeListX}px`, marginTop: `${nodeListY}px` }">
<TextInput placeholder="Search Nodes..." :value="searchTerm" @update:value="(val) => (searchTerm = val)" v-focus />
<LayoutCol v-for="nodeCategory in nodeCategories" :key="nodeCategory[0]">
<TextLabel>{{ nodeCategory[0] }}</TextLabel>
<TextButton v-for="nodeType in nodeCategory[1]" v-bind:key="String(nodeType)" :label="nodeType.name" :action="() => createNode(nodeType.name)" />
</LayoutCol>
<TextLabel v-if="nodeCategories.length === 0">No search results :(</TextLabel>
</LayoutCol>
<div
class="nodes"
ref="nodesContainer"
:style="{
transform: `scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`,
transformOrigin: `0 0`,
}"
>
<div
v-for="node in nodes"
:key="String(node.id)"
class="node"
:class="{ selected: selected.includes(node.id), previewed: node.previewed, disabled: node.disabled }"
:style="{
'--offset-left': (node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0),
'--offset-top': (node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0),
}"
:data-node="node.id"
>
<div class="primary">
<div class="ports">
<div
v-if="node.primaryInput"
class="input port"
data-port="input"
:data-datatype="node.primaryInput"
:style="{ '--data-color': `var(--color-data-${node.primaryInput})`, '--data-color-dim': `var(--color-data-${node.primaryInput}-dim)` }"
>
<div></div>
</div>
<div
v-if="node.outputs.length > 0"
class="output port"
data-port="output"
:data-datatype="node.outputs[0].dataType"
:style="{ '--data-color': `var(--color-data-${node.outputs[0].dataType})`, '--data-color-dim': `var(--color-data-${node.outputs[0].dataType}-dim)` }"
>
<div></div>
</div>
</div>
<IconLabel :icon="nodeIcon(node.displayName)" />
<TextLabel>{{ node.displayName }}</TextLabel>
</div>
<div v-if="[...node.exposedInputs, ...node.outputs.slice(1)].length > 0" class="parameters">
<div v-for="(parameter, index) in [...node.exposedInputs, ...node.outputs.slice(1)]" :key="index" class="parameter">
<div class="ports">
<div
v-if="index < node.exposedInputs.length"
class="input port"
data-port="input"
:data-datatype="parameter.dataType"
:style="{
'--data-color': `var(--color-data-${parameter.dataType})`,
'--data-color-dim': `var(--color-data-${parameter.dataType}-dim)`,
}"
>
<div></div>
</div>
<div
v-else
class="output port"
data-port="output"
:data-datatype="parameter.dataType"
:style="{ '--data-color': `var(--color-data-${parameter.dataType})`, '--data-color-dim': `var(--color-data-${parameter.dataType}-dim)` }"
>
<div></div>
</div>
</div>
<TextLabel :class="index < node.exposedInputs.length ? 'name' : 'output'">{{ parameter.name }}</TextLabel>
</div>
</div>
</div>
</div>
<div
class="wires"
:style="{
transform: `scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`,
transformOrigin: `0 0`,
}"
>
<svg>
<path
v-for="([pathString, dataType], index) in linkPaths"
:key="index"
:d="pathString"
:style="{ '--data-color': `var(--color-data-${dataType})`, '--data-color-dim': `var(--color-data-${dataType}-dim)` }"
/>
</svg>
</div>
</LayoutRow>
</LayoutCol>
</template>
<style lang="scss">
.node-graph {
height: 100%;
position: relative;
.node-list {
width: max-content;
position: fixed;
padding: 5px;
z-index: 3;
background-color: var(--color-3-darkgray);
.text-button + .text-button {
margin-left: 0;
margin-top: 4px;
}
}
.options-bar {
height: 32px;
margin: 0 4px;
flex: 0 0 auto;
align-items: center;
.widget-layout {
flex-direction: row;
flex-grow: 1;
justify-content: space-between;
}
}
.graph {
position: relative;
background: var(--color-2-mildblack);
width: calc(100% - 8px);
margin-left: 4px;
margin-bottom: 4px;
border-radius: 2px;
overflow: hidden;
// We're displaying the dotted grid in a pseudo-element because `image-rendering` is an inherited property and we don't want it to apply to child elements
&::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
background-size: var(--grid-spacing) var(--grid-spacing);
background-position: calc(var(--grid-offset-x) - var(--dot-radius)) calc(var(--grid-offset-y) - var(--dot-radius));
background-image: radial-gradient(circle at var(--dot-radius) var(--dot-radius), var(--color-3-darkgray) var(--dot-radius), transparent 0);
image-rendering: pixelated;
mix-blend-mode: screen;
}
}
.nodes,
.wires {
position: absolute;
width: 100%;
height: 100%;
&.wires {
width: 100%;
height: 100%;
pointer-events: none;
svg {
width: 100%;
height: 100%;
overflow: visible;
path {
fill: none;
// stroke: var(--color-data-raster-dim);
stroke: var(--data-color-dim);
stroke-width: 2px;
}
}
}
&.nodes {
.node {
position: absolute;
display: flex;
flex-direction: column;
min-width: 120px;
border-radius: 4px;
background: var(--color-4-dimgray);
left: calc((var(--offset-left) + 0.5) * 24px);
top: calc((var(--offset-top) - 0.5) * 24px);
&.selected {
border: 1px solid var(--color-e-nearwhite);
margin: -1px;
}
&.disabled {
background: var(--color-3-darkgray);
color: var(--color-a-softgray);
.icon-label {
fill: var(--color-a-softgray);
}
}
&.previewed {
outline: 3px solid var(--color-data-vector);
}
.primary {
display: flex;
align-items: center;
position: relative;
gap: 4px;
width: 100%;
height: 24px;
background: var(--color-5-dullgray);
border-radius: 4px;
.icon-label {
margin-left: 4px;
}
.text-label {
margin-right: 4px;
}
}
.parameters {
display: flex;
flex-direction: column;
width: 100%;
position: relative;
.parameter {
position: relative;
display: flex;
align-items: center;
height: 24px;
width: calc(100% - 24px * 2);
margin-left: 24px;
margin-right: 24px;
.text-label {
width: 100%;
&.output {
text-align: right;
}
}
}
// Squares to cover up the rounded corners of the primary area and make them have a straight edge
&::before,
&::after {
content: "";
position: absolute;
background: var(--color-5-dullgray);
width: 4px;
height: 4px;
top: -4px;
}
&::before {
left: 0;
}
&::after {
right: 0;
}
}
.ports {
position: absolute;
width: 100%;
height: 100%;
.port {
position: absolute;
margin: auto 0;
top: 0;
bottom: 0;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--data-color-dim);
// background: var(--color-data-raster-dim);
div {
background: var(--data-color);
// background: var(--color-data-raster);
width: 8px;
height: 8px;
border-radius: 50%;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
&.input {
left: calc(-12px - 6px);
}
&.output {
right: calc(-12px - 6px);
}
}
}
}
}
}
}
</style>
+34 -34
View File
@@ -1,3 +1,37 @@
<script lang="ts">
import { defineComponent } from "vue";
import { defaultWidgetLayout, patchWidgetLayout, UpdatePropertyPanelOptionsLayout, UpdatePropertyPanelSectionsLayout } from "@/wasm-communication/messages";
import LayoutCol from "@/components/layout/LayoutCol.vue";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import WidgetLayout from "@/components/widgets/WidgetLayout.vue";
export default defineComponent({
inject: ["editor", "dialog"],
data() {
return {
propertiesOptionsLayout: defaultWidgetLayout(),
propertiesSectionsLayout: defaultWidgetLayout(),
};
},
mounted() {
this.editor.subscriptions.subscribeJsMessage(UpdatePropertyPanelOptionsLayout, (updatePropertyPanelOptionsLayout) => {
patchWidgetLayout(this.propertiesOptionsLayout, updatePropertyPanelOptionsLayout);
});
this.editor.subscriptions.subscribeJsMessage(UpdatePropertyPanelSectionsLayout, (updatePropertyPanelSectionsLayout) => {
patchWidgetLayout(this.propertiesSectionsLayout, updatePropertyPanelSectionsLayout);
});
},
components: {
LayoutCol,
LayoutRow,
WidgetLayout,
},
});
</script>
<template>
<LayoutCol class="properties">
<LayoutRow class="options-bar">
@@ -32,37 +66,3 @@
}
}
</style>
<script lang="ts">
import { defineComponent } from "vue";
import { defaultWidgetLayout, patchWidgetLayout, UpdatePropertyPanelOptionsLayout, UpdatePropertyPanelSectionsLayout } from "@/wasm-communication/messages";
import LayoutCol from "@/components/layout/LayoutCol.vue";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import WidgetLayout from "@/components/widgets/WidgetLayout.vue";
export default defineComponent({
inject: ["editor", "dialog"],
data() {
return {
propertiesOptionsLayout: defaultWidgetLayout(),
propertiesSectionsLayout: defaultWidgetLayout(),
};
},
mounted() {
this.editor.subscriptions.subscribeJsMessage(UpdatePropertyPanelOptionsLayout, (updatePropertyPanelOptionsLayout) => {
patchWidgetLayout(this.propertiesOptionsLayout, updatePropertyPanelOptionsLayout);
});
this.editor.subscriptions.subscribeJsMessage(UpdatePropertyPanelSectionsLayout, (updatePropertyPanelSectionsLayout) => {
patchWidgetLayout(this.propertiesSectionsLayout, updatePropertyPanelSectionsLayout);
});
},
components: {
LayoutCol,
LayoutRow,
WidgetLayout,
},
});
</script>