Add web frontend panel skeleton & Vue environment

This commit is contained in:
Keavon Chambers
2021-02-16 03:21:15 -08:00
parent cd6843a63f
commit 076491c869
33 changed files with 13929 additions and 63 deletions

View File

@@ -0,0 +1,7 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100

23
web-frontend/.eslintrc.js Normal file
View File

@@ -0,0 +1,23 @@
module.exports = {
root: true,
env: {
node: true,
},
extends: [
"plugin:vue/vue3-essential",
"@vue/airbnb",
"@vue/typescript/recommended",
],
parserOptions: {
ecmaVersion: 2020,
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-tabs": 0,
"max-len": 0,
"linebreak-style": ["error", "unix"],
indent: ["error", "tab"],
quotes: ["error", "double"],
},
};

12919
web-frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,14 @@
{
"name": "graphite-web-frontend",
"version": "0.1.0",
"description": "Graphite's web app frontend pathfinder. Planned to be replaced by a Rust native GUI framework in the future.",
"description": "Graphite's web app frontend. Planned to be replaced by a Rust native GUI framework in the future.",
"main": "main.js",
"scripts": {
"build": "webpack",
"start": "webpack serve"
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"webpack-build": "webpack",
"webpack-start": "webpack serve"
},
"repository": {
"type": "git",
@@ -15,10 +18,40 @@
"license": "Apache-2.0",
"homepage": "https://www.graphite.design",
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-airbnb": "^5.0.2",
"@vue/eslint-config-typescript": "^5.0.2",
"@wasm-tool/wasm-pack-plugin": "^1.3.3",
"html-webpack-plugin": "^5.1.0",
"webpack": "^5.21.2",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-vue": "^7.0.0-0",
"html-webpack-plugin": "^4.5.1",
"lint-staged": "^9.5.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"typescript": "~3.9.3",
"vue-svg-loader": "^0.16.0",
"vue-template-compiler": "^2.6.12",
"webpack": "^4.45.0",
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.{js,jsx,vue,ts,tsx}": [
"vue-cli-service lint",
"git add"
]
},
"dependencies": {
"vue": "^3.0.0",
"vue-class-component": "^8.0.0-0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<noscript>JavaScript is required</noscript>
<div id="app"></div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

28
web-frontend/src/App.vue Normal file
View File

@@ -0,0 +1,28 @@
<template>
<MainWindow />
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import MainWindow from "./components/window/MainWindow.vue";
@Options({
components: {
MainWindow,
},
})
export default class App extends Vue {}
</script>
<style lang="scss">
html, body, #app {
margin: 0;
height: 100%;
font-family: "Segoe UI", Arial, sans-serif;
font-size: 14px;
line-height: 1;
color: #ddd;
background: #222;
user-select: none;
}
</style>

View File

@@ -0,0 +1,60 @@
<template>
<div class="status-area footer-third">
<div>
<span>File: 1.8 MB | Memory: 137 MB | Scratch: 0.7/12.3 GB</span>
</div>
</div>
<div class="hint-area footer-third">
<InputHint :inputMouse="'LMBDrag'">Box Select</InputHint>
<InputHint :inputKeys="['G']">Grab Selection</InputHint>
<InputHint :inputKeys="['R']">Rotate Selection</InputHint>
<InputHint :inputKeys="['S']">Scale Selection</InputHint>
</div>
<div class="version-area footer-third">
<div>
<span>Graphite 0.1.0</span>
</div>
</div>
</template>
<style lang="scss">
.footer-third {
display: flex;
flex: 1 1 100%;
&:nth-child(1) {
justify-content: flex-start;
}
&:nth-child(2) {
justify-content: center;
}
&:nth-child(3) {
justify-content: flex-end;
}
&.status-area div, &.version-area {
margin: 0 8px;
display: flex;
align-items: center;
}
}
</style>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import LayoutRow from "../layout/LayoutRow.vue";
import LayoutCol from "../layout/LayoutCol.vue";
import InputHint from "./InputHint.vue";
@Options({
components: {
LayoutRow,
LayoutCol,
InputHint,
},
props: {},
})
export default class FooterBar extends Vue {}
</script>

View File

@@ -0,0 +1,144 @@
<template>
<div class="input-hint">
<span class="input-key" v-for="inputKey in inputKeys" :key="inputKey">
{{inputKey}}
</span>
<span class="input-mouse" v-if="inputMouse">
<svg width="16" height="16" viewBox="0 0 16 16" v-html="getMouseIconInnerSVG"></svg>
</span>
<span class="hint-text">
<slot></slot>
</span>
</div>
</template>
<style lang="scss">
.input-hint {
height: 100%;
margin: 0 8px;
display: flex;
align-items: center;
.input-key, .input-mouse {
margin-right: 4px;
}
.input-key {
font-family: "Consolas", monospace;
font-weight: bold;
text-align: center;
color: #000;
background: #fff;
border: 2px;
border-style: solid;
border-color: #999;
border-radius: 4px;
width: 14px;
height: 14px;
line-height: 14px;
}
.input-mouse {
font-size: 0;
.primary {
fill: #fff;
}
.secondary {
fill: #888;
}
}
}
</style>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
export enum MouseInputInteraction {
"None" = "None",
"LMB" = "LMB",
"RMB" = "RMB",
"MMB" = "MMB",
"ScrollUp" = "ScrollUp",
"ScrollDown" = "ScrollDown",
"Drag" = "Drag",
"LMBDrag" = "LMBDrag",
"RMBDrag" = "RMBDrag",
"MMBDrag" = "MMBDrag",
}
@Options({
components: {},
props: {
inputKeys: { type: Array, default: [] },
inputMouse: { type: String },
},
computed: {
getMouseIconInnerSVG() {
switch (this.inputMouse) {
case MouseInputInteraction.None: return `
<path style="fill:#888888;" d="M9,7c0,0.55-0.45,1-1,1l0,0C7.45,8,7,7.55,7,7V4.5c0-0.55,0.45-1,1-1l0,0c0.55,0,1,0.45,1,1V7z" />
<path style="fill:#888888;" d="M10,2c1.1,0,2,0.9,2,2v6c0,2.21-1.79,4-4,4s-4-1.79-4-4V4c0-1.1,0.9-2,2-2H10 M10,1H6
C4.35,1,3,2.35,3,4v6c0,2.76,2.24,5,5,5s5-2.24,5-5V4C13,2.35,11.65,1,10,1L10,1z" />`;
case MouseInputInteraction.LMB: return `
<path style="fill:#FFFFFF;" d="M8,1H6C4.35,1,3,2.35,3,4v4h5V1z" />
<path style="fill:#888888;" d="M10,1H9v1h1c1.1,0,2,0.9,2,2v6c0,2.21-1.79,4-4,4s-4-1.79-4-4V9H3v1c0,2.76,2.24,5,5,5s5-2.24,5-5V4
C13,2.35,11.65,1,10,1z" />`;
case MouseInputInteraction.RMB: return `
<path class="secondary" d="M8,1h2c1.65,0,3,1.35,3,3v4H8V1z" />
<path class="primary" d="M6,1h1v1H6C4.9,2,4,2.9,4,4v6c0,2.21,1.79,4,4,4s4-1.79,4-4V9h1v1c0,2.76-2.24,5-5,5s-5-2.24-5-5V4
C3,2.35,4.35,1,6,1z" />`;
case MouseInputInteraction.MMB: return `
<path style="fill:#FFFFFF;" d="M9,7c0,0.55-0.45,1-1,1l0,0C7.45,8,7,7.55,7,7V4.5c0-0.55,0.45-1,1-1l0,0c0.55,0,1,0.45,1,1V7z" />
<path style="fill:#888888;" d="M10,2c1.1,0,2,0.9,2,2v6c0,2.21-1.79,4-4,4s-4-1.79-4-4V4c0-1.1,0.9-2,2-2H10 M10,1H6
C4.35,1,3,2.35,3,4v6c0,2.76,2.24,5,5,5s5-2.24,5-5V4C13,2.35,11.65,1,10,1L10,1z" />`;
case MouseInputInteraction.ScrollUp: return `
<polygon style="fill:#FFFFFF;" points="10.5,4 8,2 5.5,4 5.5,2 8,0 10.5,2 " />
<polygon style="fill:#FFFFFF;" points="10.5,8 8,6 5.5,8 5.5,6 8,4 10.5,6 " />
<path style="fill:#888888;" d="M11.5,1.42v1.28C11.8,3.06,12,3.5,12,4v6c0,2.21-1.79,4-4,4s-4-1.79-4-4V4c0-0.5,0.2-0.94,0.5-1.29
V1.42C3.61,1.94,3,2.9,3,4v6c0,2.76,2.24,5,5,5s5-2.24,5-5V4C13,2.9,12.39,1.94,11.5,1.42z" />`;
case MouseInputInteraction.ScrollDown: return `
<polygon style="fill:#FFFFFF;" points="5.5,4 8,6 10.5,4 10.5,6 8,8 5.5,6 " />
<polygon style="fill:#FFFFFF;" points="5.5,0 8,2 10.5,0 10.5,2 8,4 5.5,2 " />
<path style="fill:#888888;" d="M11.5,1.42v1.28C11.8,3.06,12,3.5,12,4v6c0,2.21-1.79,4-4,4s-4-1.79-4-4V4c0-0.5,0.2-0.94,0.5-1.29
V1.42C3.61,1.94,3,2.9,3,4v6c0,2.76,2.24,5,5,5s5-2.24,5-5V4C13,2.9,12.39,1.94,11.5,1.42z" />`;
case MouseInputInteraction.Drag: return `
<path style="fill:#888888;" d="M8,7c0,0.55-0.45,1-1,1l0,0C6.45,8,6,7.55,6,7V4.5c0-0.55,0.45-1,1-1l0,0c0.55,0,1,0.45,1,1V7z" />
<path style="fill:#FFFFFF;" d="M11,16c-0.18,0-0.36-0.1-0.45-0.28c-0.12-0.25-0.02-0.55,0.22-0.67C10.87,15.01,13,13.88,13,11V6
c0-0.28,0.22-0.5,0.5-0.5S14,5.72,14,6v5c0,3.52-2.66,4.89-2.78,4.95C11.15,15.98,11.08,16,11,16z" />
<path style="fill:#FFFFFF;" d="M14.5,15c-0.13,0-0.26-0.05-0.35-0.15c-0.19-0.19-0.2-0.51,0-0.7C14.17,14.12,15,13.2,15,11V8
c0-0.28,0.22-0.5,0.5-0.5S16,7.72,16,8v3c0,2.68-1.1,3.81-1.15,3.85C14.76,14.95,14.63,15,14.5,15z" />
<path style="fill:#888888;" d="M9,2c1.1,0,2,0.9,2,2v6c0,2.21-1.79,4-4,4s-4-1.79-4-4V4c0-1.1,0.9-2,2-2H9 M9,1H5C3.35,1,2,2.35,2,4
v6c0,2.76,2.24,5,5,5s5-2.24,5-5V4C12,2.35,10.65,1,9,1L9,1z" />`;
case MouseInputInteraction.LMBDrag: return `
<path style="fill:#FFFFFF;" d="M11,16c-0.18,0-0.36-0.1-0.45-0.28c-0.12-0.25-0.02-0.55,0.22-0.67C10.87,15.01,13,13.88,13,11V6
c0-0.28,0.22-0.5,0.5-0.5S14,5.72,14,6v5c0,3.52-2.66,4.89-2.78,4.95C11.15,15.98,11.08,16,11,16z" />
<path style="fill:#FFFFFF;" d="M14.5,15c-0.13,0-0.26-0.05-0.35-0.15c-0.19-0.19-0.2-0.51,0-0.7C14.17,14.12,15,13.2,15,11V8
c0-0.28,0.22-0.5,0.5-0.5S16,7.72,16,8v3c0,2.68-1.1,3.81-1.15,3.85C14.76,14.95,14.63,15,14.5,15z" />
<path style="fill:#FFFFFF;" d="M7,1H5C3.35,1,2,2.35,2,4v4h5V1z" />
<path style="fill:#888888;" d="M9,1H8v1h1c1.1,0,2,0.9,2,2v6c0,2.21-1.79,4-4,4s-4-1.79-4-4V9H2v1c0,2.76,2.24,5,5,5s5-2.24,5-5V4
C12,2.35,10.65,1,9,1z" />`;
case MouseInputInteraction.RMBDrag: return `
<path style="fill:#FFFFFF;" d="M11,16c-0.18,0-0.36-0.1-0.45-0.28c-0.12-0.25-0.02-0.55,0.22-0.67C10.87,15.01,13,13.88,13,11V6
c0-0.28,0.22-0.5,0.5-0.5S14,5.72,14,6v5c0,3.52-2.66,4.89-2.78,4.95C11.15,15.98,11.08,16,11,16z" />
<path style="fill:#FFFFFF;" d="M14.5,15c-0.13,0-0.26-0.05-0.35-0.15c-0.19-0.19-0.2-0.51,0-0.7C14.17,14.12,15,13.2,15,11V8
c0-0.28,0.22-0.5,0.5-0.5S16,7.72,16,8v3c0,2.68-1.1,3.81-1.15,3.85C14.76,14.95,14.63,15,14.5,15z" />
<path style="fill:#FFFFFF;" d="M7,1h2c1.65,0,3,1.35,3,3v4H7V1z" />
<path style="fill:#888888;" d="M5,1h1v1H5C3.9,2,3,2.9,3,4v6c0,2.21,1.79,4,4,4s4-1.79,4-4V9h1v1c0,2.76-2.24,5-5,5s-5-2.24-5-5V4
C2,2.35,3.35,1,5,1z" />`;
case MouseInputInteraction.MMBDrag: return `
<path style="fill:#FFFFFF;" d="M8,7c0,0.55-0.45,1-1,1l0,0C6.45,8,6,7.55,6,7V4.5c0-0.55,0.45-1,1-1l0,0c0.55,0,1,0.45,1,1V7z" />
<path style="fill:#FFFFFF;" d="M11,16c-0.18,0-0.36-0.1-0.45-0.28c-0.12-0.25-0.02-0.55,0.22-0.67C10.87,15.01,13,13.88,13,11V6
c0-0.28,0.22-0.5,0.5-0.5S14,5.72,14,6v5c0,3.52-2.66,4.89-2.78,4.95C11.15,15.98,11.08,16,11,16z" />
<path style="fill:#FFFFFF;" d="M14.5,15c-0.13,0-0.26-0.05-0.35-0.15c-0.19-0.19-0.2-0.51,0-0.7C14.17,14.12,15,13.2,15,11V8
c0-0.28,0.22-0.5,0.5-0.5S16,7.72,16,8v3c0,2.68-1.1,3.81-1.15,3.85C14.76,14.95,14.63,15,14.5,15z" />
<path style="fill:#888888;" d="M9,2c1.1,0,2,0.9,2,2v6c0,2.21-1.79,4-4,4s-4-1.79-4-4V4c0-1.1,0.9-2,2-2H9 M9,1H5C3.35,1,2,2.35,2,4
v6c0,2.76,2.24,5,5,5s5-2.24,5-5V4C12,2.35,10.65,1,9,1L9,1z" />`;
default: return "";
}
},
},
})
export default class InputHint extends Vue {}
</script>

View File

@@ -0,0 +1,55 @@
<template>
<div class="entry">
<svg width="16" height="16" viewBox="0 0 16 16">
<path d="M5.5,10.2c0.1,0.2,0,0.3-0.1,0.4c-0.2,0.1-0.3,0-0.4-0.1c0,0,0,0,0,0L2.7,6.7c-0.1-0.2,0-0.3,0.1-0.4c0.2-0.1,0.3,0,0.4,0.1
L5.5,10.2z M14.9,14.9L14.9,14.9C14.9,14.9,14.9,15,14.9,14.9C14.8,15,14.8,15,14.9,14.9L14.9,14.9c-0.5,0.2-1,0.4-1.5,0.5
c-0.5,0.1-1.1,0.2-1.6,0.2c-0.5,0.1-1.1,0.1-1.6,0c-0.5,0-1.1-0.1-1.6-0.1l-1.6-0.1c-1.7,0-3.9-0.1-4.4-0.1s-1.1-0.2-1.1-0.3
c0-0.1,0.3-0.2,0.6-0.2c0.5-0.1,1.1-0.2,1.7-0.3c0.6-0.1,2-0.2,4.1-0.2c0.8,0,1.8,0,1.8,0c0.6,0,1,0,1.9,0c0.7,0,1.1,0,1.2,0
c0.3,0,0.6,0.1,0.9,0.2l-2.6-1.9c-0.2,0.1-0.4,0.2-0.7,0.2H4.5c-0.5,0-0.9-0.2-1.1-0.6l-2.9-5c-0.2-0.4-0.2-0.9,0-1.3l2.9-5
C3.6,0.5,4,0.3,4.5,0.3h5.8c0.5,0,0.9,0.2,1.1,0.6l2.9,5l0,0l0,0c0,0.1,0.1,0.2,0.1,0.3c0,0,0,0.1,0,0.1c0,0.1,0,0.1,0,0.2
c0,0,0.7,7.2,0.7,7.8v0C15.2,14.6,15.1,14.8,14.9,14.9L14.9,14.9z M4.3,1.9L4.2,2l3.6,6.2c0.4-0.1,0.9-0.2,1.4-0.2l0.9-1.3l1.6-0.2
c0.2-0.4,0.5-0.8,0.8-1.1l-2.1-3.7c-0.1-0.2-0.3-0.3-0.5-0.3H4.8C4.6,1.6,4.4,1.7,4.3,1.9z M9.4,11.6l-2-1.4C7.4,10.1,7.3,10,7.3,10
l0,0L3.5,3.3l-1.7,3c-0.1,0.2-0.1,0.4,0,0.6l2.6,4.5c0.1,0.2,0.3,0.3,0.5,0.3L9.4,11.6z M13.5,11.3l-0.5-4.6c-0.3,0.3-0.5,0.7-0.7,1
l-1.6,0.2L9.8,9.2c-0.4,0-0.8,0-1.3,0.1l3.7,2.7C12.5,11.6,13,11.3,13.5,11.3L13.5,11.3z" />
</svg>
</div>
<div class="entry"><span>File</span></div>
<div class="entry"><span>Edit</span></div>
<div class="entry"><span>Comp</span></div>
<div class="entry"><span>View</span></div>
<div class="entry"><span>Help</span></div>
</template>
<style lang="scss">
.entry {
display: flex;
align-items: center;
padding: 0 8px;
svg {
fill: #ddd;
}
&:hover {
background: #555;
svg {
fill: #fff;
}
span {
color: #fff;
}
}
}
</style>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({
components: {},
props: {},
})
export default class FileMenu extends Vue {}
</script>

View File

@@ -0,0 +1,51 @@
<template>
<div class="header-third">
<FileMenu />
</div>
<div class="header-third">
<WindowTitle />
</div>
<div class="header-third">
<WindowButtons :maximized="true" />
</div>
</template>
<style lang="scss">
.header-third {
display: flex;
flex: 1 1 100%;
&:nth-child(1) {
justify-content: flex-start;
}
&:nth-child(2) {
justify-content: center;
}
&:nth-child(3) {
justify-content: flex-end;
}
}
</style>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import LayoutRow from "../layout/LayoutRow.vue";
import LayoutCol from "../layout/LayoutCol.vue";
import FileMenu from "./FileMenu.vue";
import WindowTitle from "./WindowTitle.vue";
import WindowButtons from "./WindowButtons.vue";
@Options({
components: {
LayoutRow,
LayoutCol,
FileMenu,
WindowTitle,
WindowButtons,
},
props: {},
})
export default class HeaderBar extends Vue {}
</script>

View File

@@ -0,0 +1,54 @@
<template>
<div class="button minimize" title="Minimize">
<svg width="10" height="10" viewBox="0 0 10 10">
<rect y="4" width="10" height="1" />
</svg>
</div>
<div class="button maximize" title="Maximize" v-if="maximized === false">
<svg width="10" height="10" viewBox="0 0 10 10">
<path d="M10,0v10H0V0H10z M9,1H1v8h8V1z" />
</svg>
</div>
<div class="button restore-down" title="Restore Down" v-if="maximized === true">
<svg width="10" height="10" viewBox="0 0 10 10">
<path d="M10,8H8v2H0V2h2V0h8V8z M7,3H1v6h6V3z M9,1H3v1h5v5h1V1z" />
</svg>
</div>
<div class="button close" title="Close">
<svg width="10" height="10" viewBox="0 0 10 10">
<polygon points="10,0.7 9.3,0 5,4.3 0.7,0 0,0.7 4.3,5 0,9.3 0.7,10 5,5.7 9.3,10 10,9.3 5.7,5" />
</svg>
</div>
</template>
<style lang="scss">
.button {
display: flex;
align-items: center;
padding: 0 20px;
svg {
fill: #ddd;
}
&:hover {
background: #555;
svg {
fill: #fff;
}
}
}
</style>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({
components: {},
props: {
maximized: { type: Boolean, default: false },
},
})
export default class WindowButtons extends Vue {}
</script>

View File

@@ -0,0 +1,23 @@
<template>
<div class="window-title">
<span>X-35B.gdd* - Graphite</span>
</div>
</template>
<style lang="scss">
.window-title {
display: flex;
align-items: center;
padding: 0 8px;
}
</style>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({
components: {},
props: {},
})
export default class WindowTitle extends Vue {}
</script>

View File

@@ -0,0 +1,23 @@
<template>
<div :class="['layout-col']">
<slot></slot>
</div>
</template>
<style lang="scss">
.layout-col {
display: flex;
flex-direction: column;
flex-grow: 1;
}
</style>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({
components: {},
props: {},
})
export default class LayoutCol extends Vue {}
</script>

View File

@@ -0,0 +1,23 @@
<template>
<div :class="['layout-row']">
<slot></slot>
</div>
</template>
<style lang="scss">
.layout-row {
display: flex;
flex-direction: row;
flex-grow: 1;
}
</style>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({
components: {},
props: {},
})
export default class LayoutRow extends Vue {}
</script>

View File

@@ -0,0 +1,146 @@
<template>
<div class="panel">
<div class="tab-bar" :class="{ 'constant-widths': tabConstantWidths }">
<div class="tab" :class="{ active: tabIndex === tabActiveIndex }" v-for="(tabLabel, tabIndex) in tabLabels" :key="tabLabel">
<span>{{tabLabel}}</span>
<button v-if="tabCloseButtons"></button>
</div>
</div>
<div class="panel-content">
</div>
</div>
</template>
<style lang="scss">
.panel {
background: #111;
border-radius: 8px;
flex-grow: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.tab-bar {
flex-direction: row;
height: 28px;
display: flex;
overflow: hidden;
&.constant-widths .tab {
width: 120px;
}
.tab {
height: 100%;
padding: 0 10px;
display: flex;
align-items: center;
position: relative;
&.active {
background: #333;
border-radius: 8px 8px 0 0;
position: relative;
&::before, &::after {
content: "";
width: 16px;
height: 8px;
position: absolute;
bottom: 0;
box-shadow: #333;
}
&::before {
left: -16px;
border-bottom-right-radius: 8px;
box-shadow: 8px 0 0 0 #333;
}
&::after {
right: -16px;
border-bottom-left-radius: 8px;
box-shadow: -8px 0 0 0 #333;
}
}
span {
flex: 1 1 100%;
overflow-x: hidden;
white-space: nowrap;
text-overflow: ellipsis;
// Required because https://stackoverflow.com/a/21611191/775283
height: 100%;
line-height: 28px;
}
button {
flex: 0 0 auto;
outline: none;
border: none;
padding: 0;
width: 16px;
height: 16px;
background: none;
color: #ddd;
font-weight: bold;
font-size: 10px;
border-radius: 2px;
margin-left: 8px;
&:hover {
background: #555;
color: white;
}
}
&:not(.active) + .tab:not(.active) {
margin-left: 1px;
&::before {
content: "";
position: absolute;
left: -1px;
width: 1px;
height: 16px;
background: #444;
}
}
&:last-of-type:not(.active) {
margin-right: 1px;
&::after {
content: "";
position: absolute;
right: -1px;
width: 1px;
height: 16px;
background: #444;
}
}
}
}
.panel-content {
background: #333;
flex-grow: 1;
}
}
</style>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({
components: {},
props: {
tabConstantWidths: { type: Boolean, default: false },
tabCloseButtons: { type: Boolean, default: false },
tabLabels: { type: Array, required: true },
tabActiveIndex: { type: Number, required: true },
},
})
export default class DockablePanel extends Vue {}
</script>

View File

@@ -0,0 +1,56 @@
<template>
<LayoutRow class="dockable-grid-subdivision">
<LayoutCol class="dockable-grid-subdivision" style="flex-grow: 1597;">
<DockablePanel :tabCloseButtons="true" :tabConstantWidths="true" :tabLabels="['X-35B*', 'Document 2', 'Document 3', 'Document 4', 'Document 5']" :tabActiveIndex="0" />
</LayoutCol>
<LayoutCol class="dockable-grid-resize-gutter"></LayoutCol>
<LayoutCol class="dockable-grid-subdivision" style="flex-grow: 319;">
<LayoutRow class="dockable-grid-subdivision">
<DockablePanel :tabLabels="['Properties', 'Typography', 'Colors']" :tabActiveIndex="0" />
</LayoutRow>
<LayoutRow class="dockable-grid-resize-gutter"></LayoutRow>
<LayoutRow class="dockable-grid-subdivision">
<DockablePanel :tabLabels="['Layers']" :tabActiveIndex="0" />
</LayoutRow>
<LayoutRow class="dockable-grid-resize-gutter"></LayoutRow>
<LayoutRow class="dockable-grid-subdivision" style="flex-grow: 0; height: 0;">
<DockablePanel :tabLabels="['Minimap', 'Brushes', 'Links']" :tabActiveIndex="0" />
</LayoutRow>
</LayoutCol>
</LayoutRow>
</template>
<style lang="scss">
.dockable-grid-subdivision {
min-height: 28px;
}
.dockable-grid-resize-gutter {
flex: 0 0 4px;
&.layout-row {
cursor: ns-resize;
}
&.layout-col {
cursor: ew-resize;
}
}
</style>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import LayoutRow from "../layout/LayoutRow.vue";
import LayoutCol from "../layout/LayoutCol.vue";
import DockablePanel from "./DockablePanel.vue";
@Options({
components: {
LayoutRow,
LayoutCol,
DockablePanel,
},
props: {},
})
export default class PanelArea extends Vue {}
</script>

View File

@@ -0,0 +1,55 @@
<template>
<LayoutCol class="main-window">
<LayoutRow :class="['header-bar']">
<HeaderBar />
</LayoutRow>
<LayoutRow :class="['panel-container']">
<PanelArea />
</LayoutRow>
<LayoutRow :class="['footer-bar']">
<FooterBar />
</LayoutRow>
</LayoutCol>
</template>
<style lang="scss">
.main-window {
height: 100%;
}
.header-bar {
height: 28px;
flex: 0 0 auto;
}
.panel-container {
flex: 1 1 100%;
height: 100%;
}
.footer-bar {
height: 24px;
flex: 0 0 auto;
}
</style>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import LayoutRow from "../layout/LayoutRow.vue";
import LayoutCol from "../layout/LayoutCol.vue";
import HeaderBar from "../header/HeaderBar.vue";
import PanelArea from "../panel-system/PanelArea.vue";
import FooterBar from "../footer/FooterBar.vue";
@Options({
components: {
LayoutRow,
LayoutCol,
HeaderBar,
PanelArea,
FooterBar,
},
props: {},
})
export default class MainWindow extends Vue {}
</script>

4
web-frontend/src/main.ts Normal file
View File

@@ -0,0 +1,4 @@
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");

6
web-frontend/src/shims-vue.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@@ -0,0 +1,40 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

View File

@@ -1,23 +1,23 @@
const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
module.exports = {
entry: "./main.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "main.js",
},
plugins: [
new HtmlWebpackPlugin({ title: 'Graphite' }),
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, "..", "packages", "wasm-bindings"),
outDir: path.resolve(__dirname, "pkg"),
}),
],
mode: "development",
devtool: 'source-map',
experiments: {
syncWebAssembly: true,
},
};
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
module.exports = {
entry: "./main.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "main.js",
},
plugins: [
new HtmlWebpackPlugin({ title: "Graphite" }),
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, "..", "packages", "wasm-bindings"),
outDir: path.resolve(__dirname, "pkg"),
}),
],
mode: "development",
devtool: "source-map",
// experiments: {
// syncWebAssembly: true,
// },
};