Rename project structure with /core and /client

This commit is contained in:
Keavon Chambers
2021-03-22 11:21:15 -07:00
parent 18a8442b0e
commit 7bfb04a99a
64 changed files with 63 additions and 43 deletions

9
client/cli/Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name = "graphite-cli"
version = "0.1.0"
authors = ["Graphite Authors <graphite@keavon.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

3
client/cli/src/main.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
println!("Hello, Graphite!");
}

3
client/native/README.md Normal file
View File

@@ -0,0 +1,3 @@
# Native editor client
This will be the official desktop and web client. While the Rust GUI ecosystem is relatively immature and the Graphite project does not have the resources to make a custom GUI system that nicely supports extensions, the web frontend is the temporary official editor client. The HTML-based frontend client will be replaced by the native client compiled to WASM and running in an HTML Canvas with the WebGPU browser graphics API.

7
client/web/.editorconfig Normal file
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
client/web/.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"],
},
};

3
client/web/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules/
dist/
pkg/

1
client/web/main.js Normal file
View File

@@ -0,0 +1 @@
import("./pkg");

13047
client/web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

57
client/web/package.json Normal file
View File

@@ -0,0 +1,57 @@
{
"name": "graphite-web-frontend",
"version": "0.1.0",
"description": "Graphite's web app frontend. Planned to be replaced by a Rust native GUI framework in the future.",
"main": "main.js",
"scripts": {
"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",
"url": "git+https://github.com/keavon/graphite.git"
},
"author": "Keavon Chambers <graphite@keavon.com>",
"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.12",
"@vue/cli-plugin-typescript": "^4.5.12",
"@vue/cli-service": "^4.5.12",
"@vue/compiler-sfc": "^3.0.7",
"@vue/eslint-config-airbnb": "^5.0.2",
"@vue/eslint-config-typescript": "^5.0.2",
"@wasm-tool/wasm-pack-plugin": "^1.3.3",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-vue": "^7.8.0",
"html-webpack-plugin": "^4.5.2",
"lint-staged": "^9.5.0",
"sass": "^1.32.8",
"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.7",
"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">
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700;1,900&family=Inconsolata:wght@400;700">
<title>Graphite</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"
}

25
client/web/src/App.vue Normal file
View File

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

View File

@@ -0,0 +1,19 @@
<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 { defineComponent } from "vue";
export default defineComponent({});
</script>

View File

@@ -0,0 +1,19 @@
<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 { defineComponent } from "vue";
export default defineComponent({});
</script>

View File

@@ -0,0 +1,195 @@
<template>
<div class="panel">
<div class="tab-bar" :class="{ 'min-widths': tabMinWidths }">
<div class="tab-group">
<div class="tab" :class="{ active: tabIndex === tabActiveIndex }" v-for="(tabLabel, tabIndex) in tabLabels" :key="tabLabel">
<span>{{tabLabel}}</span>
<button v-if="tabCloseButtons">
<svg width="16" height="16" viewBox="0 0 16 16">
<polygon points="12,5 11,4 8,7 5,4 4,5 7,8 4,11 5,12 8,9 11,12 12,11 9,8" />
</svg>
</button>
</div>
</div>
<div class="panel-options-ellipsis">
<svg viewBox="0 0 16 24">
<circle cx="8" cy="8" r="1.5" />
<circle cx="8" cy="12" r="1.5" />
<circle cx="8" cy="16" r="1.5" />
</svg>
</div>
</div>
<div class="panel-content">
<component :is="panelType" />
</div>
</div>
</template>
<style lang="scss">
.panel {
background: #111;
border-radius: 8px;
flex-grow: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.tab-bar {
height: 28px;
display: flex;
flex-direction: row;
&.min-widths .tab-group .tab {
min-width: 120px;
max-width: 360px;
}
.tab-group {
flex: 1 1 100%;
display: flex;
flex-direction: row;
overflow: hidden;
.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;
// Height and line-height 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;
fill: #ddd;
&:hover {
background: #555;
color: white;
fill: 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-options-ellipsis {
width: 16px;
height: 24px;
margin: 2px 4px;
svg {
width: 16px;
height: 24px;
border-radius: 2px;
fill: #ddd;
}
&:hover svg {
background: #555;
fill: #fff;
}
}
}
.panel-content {
background: #333;
flex-grow: 1;
}
}
</style>
<script lang="ts">
import { defineComponent } from "vue";
import Viewport from "../panels/ViewportPanel.vue";
import Properties from "../panels/PropertiesPanel.vue";
import Layers from "../panels/LayersPanel.vue";
import Minimap from "../panels/MinimapPanel.vue";
export default defineComponent({
components: {
Viewport,
Properties,
Layers,
Minimap,
},
props: {
tabMinWidths: { type: Boolean, default: false },
tabCloseButtons: { type: Boolean, default: false },
tabLabels: { type: Array, required: true },
tabActiveIndex: { type: Number, required: true },
panelType: { type: String, required: true },
},
});
</script>

View File

@@ -0,0 +1,54 @@
<template>
<LayoutRow class="dockable-grid-subdivision">
<LayoutCol class="dockable-grid-subdivision" style="flex-grow: 1597;">
<DockablePanel :panelType="'Viewport'" :tabCloseButtons="true" :tabMinWidths="true" :tabLabels="['X-35B Over Death Valley*', '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 :panelType="'Properties'" :tabLabels="['Properties', 'Typography', 'Colors']" :tabActiveIndex="0" />
</LayoutRow>
<LayoutRow class="dockable-grid-resize-gutter"></LayoutRow>
<LayoutRow class="dockable-grid-subdivision">
<DockablePanel :panelType="'Layers'" :tabLabels="['Layers']" :tabActiveIndex="0" />
</LayoutRow>
<LayoutRow class="dockable-grid-resize-gutter"></LayoutRow>
<LayoutRow class="dockable-grid-subdivision" style="flex-grow: 0; height: 0;">
<DockablePanel :panelType="'Minimap'" :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 { defineComponent } from "vue";
import LayoutRow from "../layout/LayoutRow.vue";
import LayoutCol from "../layout/LayoutCol.vue";
import DockablePanel from "./DockablePanel.vue";
export default defineComponent({
components: {
LayoutRow,
LayoutCol,
DockablePanel,
},
});
</script>

View File

@@ -0,0 +1,21 @@
<template>
<div>
</div>
</template>
<style lang="scss">
</style>
<script lang="ts">
import { defineComponent } from "vue";
// import LayoutRow from "../layout/LayoutRow.vue";
// import LayoutCol from "../layout/LayoutCol.vue";
export default defineComponent({
components: {
},
props: {
},
});
</script>

View File

@@ -0,0 +1,21 @@
<template>
<div>
</div>
</template>
<style lang="scss">
</style>
<script lang="ts">
import { defineComponent } from "vue";
// import LayoutRow from "../layout/LayoutRow.vue";
// import LayoutCol from "../layout/LayoutCol.vue";
export default defineComponent({
components: {
},
props: {
},
});
</script>

View File

@@ -0,0 +1,21 @@
<template>
<div>
</div>
</template>
<style lang="scss">
</style>
<script lang="ts">
import { defineComponent } from "vue";
// import LayoutRow from "../layout/LayoutRow.vue";
// import LayoutCol from "../layout/LayoutCol.vue";
export default defineComponent({
components: {
},
props: {
},
});
</script>

View File

@@ -0,0 +1,81 @@
<template>
<LayoutCol :class="'viewport'">
<LayoutRow :class="'options-bar'">
<div class="left side">
<!-- <span class="label">Select</span>
<div class="divider"></div> -->
</div>
<div class="spacer"></div>
<div class="right side">
<!-- <span class="label">Layer 1</span> -->
</div>
</LayoutRow>
<LayoutRow :class="'tools-and-viewport'">
<LayoutCol :class="'tools'"></LayoutCol>
<LayoutCol :class="'viewport-container'"></LayoutCol>
</LayoutRow>
</LayoutCol>
</template>
<style lang="scss">
.viewport {
height: 100%;
.options-bar {
flex: 0 0 32px;
.side {
height: 100%;
flex: 0 1 auto;
display: flex;
align-items: center;
margin: 0 4px;
> * {
margin: 0 4px;
}
.label {
white-space: nowrap;
font-weight: bold;
}
.divider {
width: 1px;
height: 24px;
background: #888;
}
}
.spacer {
flex: 1 1 100%;
}
}
.tools-and-viewport {
.tools {
flex: 0 0 32px;
}
.viewport-container {
background: #111;
flex: 1 1 100%;
}
}
}
</style>
<script lang="ts">
import { defineComponent } from "vue";
import LayoutRow from "../layout/LayoutRow.vue";
import LayoutCol from "../layout/LayoutCol.vue";
export default defineComponent({
components: {
LayoutRow,
LayoutCol,
},
props: {
},
});
</script>

View File

@@ -0,0 +1,66 @@
<template>
<LayoutCol class="main-window">
<LayoutRow :class="'title-bar'">
<TitleBar :platform="platform" :maximized="maximized" />
</LayoutRow>
<LayoutRow :class="'panel-container'">
<PanelArea />
</LayoutRow>
<LayoutRow :class="'footer-bar'">
<FooterBar />
</LayoutRow>
</LayoutCol>
</template>
<style lang="scss">
.main-window {
height: 100%;
}
.title-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 { defineComponent } from "vue";
import LayoutRow from "../layout/LayoutRow.vue";
import LayoutCol from "../layout/LayoutCol.vue";
import PanelArea from "../panel-system/PanelArea.vue";
import TitleBar from "./title-bar/TitleBar.vue";
import FooterBar from "./footer-bar/FooterBar.vue";
export enum ApplicationPlatform {
"Windows" = "Windows",
"Mac" = "Mac",
"Linux" = "Linux",
"Web" = "Web",
}
export default defineComponent({
components: {
LayoutRow,
LayoutCol,
TitleBar,
PanelArea,
FooterBar,
},
data() {
return {
platform: ApplicationPlatform.Windows,
maximized: true,
};
},
});
</script>

View File

@@ -0,0 +1,73 @@
<template>
<div class="hint-area footer-area">
<InputHint :inputMouse="'LMB'">Select Object</InputHint>
<span class="plus">+</span>
<InputHint :inputKeys="['⇧']">Grow/Shrink Selection</InputHint>
<div class="divider"></div>
<InputHint :inputMouse="'LMBDrag'">Select Area</InputHint>
<span class="plus">+</span>
<InputHint :inputKeys="['⇧']">Grow/Shrink Selection</InputHint>
<div class="divider"></div>
<InputHint :inputKeys="['G']">Grab Selected</InputHint>
<InputHint :inputKeys="['R']">Rotate Selected</InputHint>
<InputHint :inputKeys="['S']">Scale Selected</InputHint>
<div class="divider"></div>
<InputHint :inputKeys="['↑', '→', '↓', '←']">Nudge Selected</InputHint>
<span class="plus">+</span>
<InputHint :inputKeys="['⇧']">Big Increment Nudge</InputHint>
<div class="divider"></div>
<InputHint :inputKeys="['Alt']" :inputMouse="'LMBDrag'">Move Duplicate</InputHint>
<InputHint :inputKeys="['Ctrl', 'D']">Duplicate</InputHint>
</div>
<div class="status-area footer-area">
<div>
<span>Graphite 0.0.1</span>
</div>
</div>
</template>
<style lang="scss">
.footer-area {
display: flex;
&.hint-area {
flex: 1 1 100%;
}
&.status-area div {
margin: 0 8px;
display: flex;
align-items: center;
white-space: nowrap;
}
}
.plus {
display: flex;
align-items: center;
font-weight: bold;
}
.divider {
width: 1px;
height: 16px;
margin: 4px 0;
background: #888;
}
.input-hint + .input-hint {
margin-left: 0;
}
</style>
<script lang="ts">
import { defineComponent } from "vue";
import InputHint from "./InputHint.vue";
export default defineComponent({
components: {
InputHint,
},
});
</script>

View File

@@ -0,0 +1,168 @@
<template>
<div class="input-hint">
<span class="input-key" v-for="inputKey in inputKeys" :key="inputKey" :class="keyCapWidth(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;
white-space: nowrap;
.input-key, .input-mouse {
margin-right: 4px;
}
.input-key {
font-family: "Inconsolata", monospace;
font-weight: bold;
text-align: center;
color: #111;
background: #ddd;
border: 1px;
box-sizing: border-box;
border-style: solid;
border-color: #888;
border-radius: 4px;
height: 16px;
line-height: 16px;
}
.input-key.width-16 {
width: 16px;
}
.input-key.width-24 {
width: 24px;
}
.input-key.width-32 {
width: 32px;
}
.input-key.width-40 {
width: 40px;
}
.input-key.width-48 {
width: 48px;
}
.input-mouse {
font-size: 0;
.bright {
fill: #ddd;
}
.dim {
fill: #888;
}
}
}
</style>
<script lang="ts">
import { defineComponent } from "vue";
export enum MouseInputInteraction {
"None" = "None",
"LMB" = "LMB",
"RMB" = "RMB",
"MMB" = "MMB",
"ScrollUp" = "ScrollUp",
"ScrollDown" = "ScrollDown",
"Drag" = "Drag",
"LMBDrag" = "LMBDrag",
"RMBDrag" = "RMBDrag",
"MMBDrag" = "MMBDrag",
}
export default defineComponent({
props: {
inputKeys: { type: Array, default: () => [] },
inputMouse: { type: String },
},
methods: {
keyCapWidth(keyText: string) {
return `width-${keyText.length * 8 + 8}`;
},
},
computed: {
getMouseIconInnerSVG(): string {
switch (this.inputMouse) {
case MouseInputInteraction.None: return `
<path class="dim" 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 class="dim" 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 class="bright" d="M8,1H6C4.35,1,3,2.35,3,4v4h5V1z" />
<path class="dim" 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="dim" d="M8,1h2c1.65,0,3,1.35,3,3v4H8V1z" />
<path class="bright" 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 class="bright" 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 class="dim" 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 class="bright" points="10.5,4 8,2 5.5,4 5.5,2 8,0 10.5,2" />
<polygon class="bright" points="10.5,8 8,6 5.5,8 5.5,6 8,4 10.5,6" />
<path class="dim" 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 class="bright" points="5.5,4 8,6 10.5,4 10.5,6 8,8 5.5,6" />
<polygon class="bright" points="5.5,0 8,2 10.5,0 10.5,2 8,4 5.5,2" />
<path class="dim" 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 class="dim" 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 class="bright" 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 class="bright" 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 class="dim" 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 class="bright" 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 class="bright" 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 class="bright" d="M7,1H5C3.35,1,2,2.35,2,4v4h5V1z" />
<path class="dim" 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 class="bright" 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 class="bright" 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 class="bright" d="M7,1h2c1.65,0,3,1.35,3,3v4H7V1z" />
<path class="dim" 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 class="bright" 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 class="bright" 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 class="bright" 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 class="dim" 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 "";
}
},
},
});
</script>

View File

@@ -0,0 +1,59 @@
<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>Document</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;
white-space: nowrap;
padding: 0 8px;
svg {
fill: #ddd;
}
&:hover {
background: #555;
svg {
fill: #fff;
}
span {
color: #fff;
}
}
}
</style>
<script lang="ts">
import { defineComponent } from "vue";
import { ApplicationPlatform } from "../MainWindow.vue";
export default defineComponent({
data() {
return {
ApplicationPlatform,
};
},
});
</script>

View File

@@ -0,0 +1,58 @@
<template>
<div class="header-third">
<WindowButtonsMac :maximized="maximized" v-if="platform === ApplicationPlatform.Mac" />
<FileMenu v-if="platform !== ApplicationPlatform.Mac" />
</div>
<div class="header-third">
<WindowTitle :title="'X-35B Over Death Valley.gdd* - Graphite'" />
</div>
<div class="header-third">
<WindowButtonsWindows :maximized="maximized" v-if="platform === ApplicationPlatform.Windows || platform === ApplicationPlatform.Linux" />
</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 { defineComponent } from "vue";
import FileMenu from "./FileMenu.vue";
import WindowTitle from "./WindowTitle.vue";
import WindowButtonsWindows from "./WindowButtonsWindows.vue";
import WindowButtonsMac from "./WindowButtonsMac.vue";
import { ApplicationPlatform } from "../MainWindow.vue";
export default defineComponent({
components: {
FileMenu,
WindowTitle,
WindowButtonsWindows,
WindowButtonsMac,
},
props: {
platform: { type: String, required: true },
maximized: { type: Boolean, required: true },
},
data() {
return {
ApplicationPlatform,
};
},
});
</script>

View File

@@ -0,0 +1,45 @@
<template>
<div class="mac window-buttons">
<div class="close" title="Close"></div>
<div class="minimize" title="Minimize"></div>
<div class="zoom" title="Zoom"></div>
</div>
</template>
<style lang="scss">
.mac.window-buttons {
display: flex;
align-items: center;
div {
display: flex;
align-items: center;
margin-left: 8px;
width: 11px;
height: 11px;
border-radius: 50%;
&.close {
background: #ff5f57;
}
&.minimize {
background: #ffbd2f;
}
&.zoom {
background: #29c93f;
}
}
}
</style>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
props: {
maximized: { type: Boolean, default: false },
},
});
</script>

View File

@@ -0,0 +1,56 @@
<template>
<div class="windows window-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="windows window-button maximize" title="Maximize" v-if="!maximized">
<svg width="10" height="10" viewBox="0 0 10 10">
<path d="M10,0v10H0V0H10z M9,1H1v8h8V1z" />
</svg>
</div>
<div class="windows window-button restore-down" title="Restore Down" v-if="maximized">
<svg width="10" height="10" viewBox="0 0 10 10">
<path d="M10,8H8v2H0V2h2V0h8V8z M7,3H1v6h6V3z M9,1H3v1h5v5h1V1z" />
</svg>
</div>
<div class="windows window-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">
.windows.window-button {
display: flex;
align-items: center;
padding: 0 20px;
svg {
fill: #ddd;
}
&:hover {
background: #555;
svg {
fill: #fff;
}
}
&.close:hover {
background: #e81123;
}
}
</style>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
props: {
maximized: { type: Boolean, default: false },
},
});
</script>

View File

@@ -0,0 +1,24 @@
<template>
<div class="window-title">
<span>{{title}}</span>
</div>
</template>
<style lang="scss">
.window-title {
display: flex;
align-items: center;
white-space: nowrap;
padding: 0 8px;
}
</style>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
props: {
title: { type: String, required: true },
},
});
</script>

4
client/web/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
client/web/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
}

40
client/web/tsconfig.json Normal file
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"
]
}

1
client/web/wasm/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
pkg/

View File

@@ -0,0 +1,24 @@
[package]
name = "wasm-wrapper"
version = "0.1.0"
authors = ["Keavon Chambers <graphite@keavon.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.72"
graphite-editor = { path = "../../packages/graphite-editor" }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.6", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.3.22"

View File

@@ -0,0 +1,18 @@
pub mod utils;
pub mod viewport;
pub mod window;
pub mod wrappers;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn init() {
utils::set_panic_hook();
}
/// Send events
#[wasm_bindgen]
pub fn handle_event(event_name: String) {
// TODO: add payload
todo!()
}

View File

@@ -0,0 +1,10 @@
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}

View File

@@ -0,0 +1,20 @@
use crate::wrappers::Color;
use wasm_bindgen::prelude::*;
/// Modify the currently selected tool in the document state store
#[wasm_bindgen]
pub fn select_tool(tool: String) {
todo!()
}
/// Mouse movement with the bounds of the canvas
#[wasm_bindgen]
pub fn on_mouse_move(x: u32, y: u32) {
todo!()
}
/// Update working colors
#[wasm_bindgen]
pub fn update_colors(primary_color: Color, secondary_color: Color) {
todo!()
}

View File

@@ -0,0 +1,43 @@
use wasm_bindgen::prelude::*;
type DocumentId = u32;
/// Modify the active Document in the editor state store
#[wasm_bindgen]
pub fn set_active_document(document_id: DocumentId) {
todo!()
}
/// Query the name of a specific document
#[wasm_bindgen]
pub fn get_document_name(document_id: DocumentId) -> String {
todo!()
}
/// Query the id of the most recently interacted with document
#[wasm_bindgen]
pub fn get_active_document() -> DocumentId {
todo!()
}
use graphite_editor::workspace::PanelId;
/// Notify the editor that the mouse hovers above a panel
#[wasm_bindgen]
pub fn panel_hover_enter(panel_id: PanelId) {
todo!()
}
/// Query a list of currently available operations
#[wasm_bindgen]
pub fn get_available_operations() -> Vec<JsValue> {
todo!();
// vec!["example1", "example2"].into_iter().map(JsValue::from).collect()
}
/*
/// Load a new .gdd file into the editor
/// Returns a unique document identifier
#[wasm_bindgen]
pub fn load_document(raw_data: &[u8]) -> DocumentId {
todo!()
}*/

View File

@@ -0,0 +1,13 @@
use graphite_editor::Color as InnerColor;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct Color(InnerColor);
#[wasm_bindgen]
impl Color {
#[wasm_bindgen(constructor)]
pub fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
Self(InnerColor::from_rgbaf32(red, green, blue, alpha).unwrap_throw())
}
}

View File

@@ -0,0 +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,
// },
};