Rename project structure with /core and /client
9
client/cli/Cargo.toml
Normal 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
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, Graphite!");
|
||||
}
|
||||
3
client/native/README.md
Normal 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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
pkg/
|
||||
1
client/web/main.js
Normal file
@@ -0,0 +1 @@
|
||||
import("./pkg");
|
||||
13047
client/web/package-lock.json
generated
Normal file
57
client/web/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
client/web/public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
client/web/public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
client/web/public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
9
client/web/public/browserconfig.xml
Normal 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>
|
||||
BIN
client/web/public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
client/web/public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
client/web/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
24
client/web/public/index.html
Normal 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>
|
||||
BIN
client/web/public/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
19
client/web/public/site.webmanifest
Normal 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
@@ -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>
|
||||
19
client/web/src/components/layout/LayoutCol.vue
Normal 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>
|
||||
19
client/web/src/components/layout/LayoutRow.vue
Normal 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>
|
||||
195
client/web/src/components/panel-system/DockablePanel.vue
Normal 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>
|
||||
54
client/web/src/components/panel-system/PanelArea.vue
Normal 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>
|
||||
21
client/web/src/components/panels/LayersPanel.vue
Normal 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>
|
||||
21
client/web/src/components/panels/MinimapPanel.vue
Normal 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>
|
||||
21
client/web/src/components/panels/PropertiesPanel.vue
Normal 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>
|
||||
81
client/web/src/components/panels/ViewportPanel.vue
Normal 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>
|
||||
66
client/web/src/components/window/MainWindow.vue
Normal 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>
|
||||
73
client/web/src/components/window/footer-bar/FooterBar.vue
Normal 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>
|
||||
168
client/web/src/components/window/footer-bar/InputHint.vue
Normal 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>
|
||||
59
client/web/src/components/window/title-bar/FileMenu.vue
Normal 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>
|
||||
58
client/web/src/components/window/title-bar/TitleBar.vue
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
24
client/web/src/components/window/title-bar/WindowTitle.vue
Normal 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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
|
||||
pkg/
|
||||
24
client/web/wasm/Cargo.toml
Normal 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"
|
||||
18
client/web/wasm/src/lib.rs
Normal 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!()
|
||||
}
|
||||
10
client/web/wasm/src/utils.rs
Normal 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();
|
||||
}
|
||||
20
client/web/wasm/src/viewport.rs
Normal 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!()
|
||||
}
|
||||
43
client/web/wasm/src/window.rs
Normal 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!()
|
||||
}*/
|
||||
13
client/web/wasm/src/wrappers.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
23
client/web/webpack.config.js
Normal 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,
|
||||
// },
|
||||
};
|
||||