mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Plumb layer panel (#107)
* WIP ExpandFolder handling * Implement response parsing in typescript * Update layer panel with list sent by wasm * Add events for layer interaction * Add proper default naming * Fix displaying of the eye icon * Attach path to LayerPanelEntry * Fix lint issues Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
parent
76d3e8cde4
commit
6adb984f2d
@@ -21,7 +21,7 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
indent: ["error", "tab"],
|
||||
indent: ["error", "tab", { SwitchCase: 1 }],
|
||||
quotes: ["error", "double"],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"eol-last": ["error", "always"],
|
||||
@@ -29,7 +29,7 @@ module.exports = {
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"max-len": ["error", { code: 200, tabWidth: 4 }],
|
||||
"@typescript-eslint/camelcase": "off",
|
||||
camelcase: ["error", { ignoreImports: true, ignoreDestructuring: true }],
|
||||
camelcase: ["error", { allow: ["^(?:[a-z]+_)*[a-z]+$"] }],
|
||||
"prettier-vue/prettier": [
|
||||
"error",
|
||||
{
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { ResponseType, registerResponseHandler } from "../../response-handler";
|
||||
import { ResponseType, registerResponseHandler, Response, UpdateCanvas, SetActiveTool } from "../../response-handler";
|
||||
import LayoutRow from "../layout/LayoutRow.vue";
|
||||
import LayoutCol from "../layout/LayoutCol.vue";
|
||||
import ShelfItem from "../widgets/ShelfItem.vue";
|
||||
@@ -324,13 +324,13 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
registerResponseHandler(ResponseType["Tool::UpdateCanvas"], (responseData: any) => {
|
||||
this.viewportSvg = responseData.Tool.UpdateCanvas.document;
|
||||
registerResponseHandler(ResponseType.UpdateCanvas, (responseData: Response) => {
|
||||
const updateData = responseData as UpdateCanvas;
|
||||
if (updateData) this.viewportSvg = updateData.document;
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
registerResponseHandler(ResponseType["Tool::SetActiveTool"], (responseData: any) => {
|
||||
this.activeTool = responseData.Tool.SetActiveTool.tool_name;
|
||||
registerResponseHandler(ResponseType.SetActiveTool, (responseData: Response) => {
|
||||
const toolData = responseData as SetActiveTool;
|
||||
if (toolData) this.activeTool = toolData.tool_name;
|
||||
});
|
||||
|
||||
window.addEventListener("keyup", (e: KeyboardEvent) => this.keyUp(e));
|
||||
|
||||
@@ -7,16 +7,10 @@
|
||||
</LayoutRow>
|
||||
<LayoutRow :class="'layer-tree'">
|
||||
<LayoutCol :class="'list'">
|
||||
<div
|
||||
class="layer-row"
|
||||
v-for="layerId in Array(5)
|
||||
.fill()
|
||||
.map((_, i) => i)"
|
||||
:key="layerId"
|
||||
>
|
||||
<div class="layer-row" v-for="layer in layers" :key="layer.path">
|
||||
<div class="layer-visibility">
|
||||
<IconButton v-if="layerId % 2 == 0" @click="hideLayer(layerId)" :size="24" title="Visible"><EyeVisible /></IconButton>
|
||||
<IconButton v-if="layerId % 2 == 1" @click="showLayer(layerId)" :size="24" title="Hidden"><EyeHidden /></IconButton>
|
||||
<IconButton v-if="layer.visible" @click="hideLayer(layer)" :size="24" title="Visible"><EyeVisible /></IconButton>
|
||||
<IconButton v-if="!layer.visible" @click="showLayer(layer)" :size="24" title="Hidden"><EyeHidden /></IconButton>
|
||||
</div>
|
||||
<div class="layer">
|
||||
<div class="layer-thumbnail"></div>
|
||||
@@ -24,7 +18,7 @@
|
||||
<IconContainer :size="24" title="Path"><NodeTypePath /></IconContainer>
|
||||
</div>
|
||||
<div class="layer-name">
|
||||
<span>Foo bar</span>
|
||||
<span>{{ layer.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,7 +71,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { ResponseType, registerResponseHandler } from "../../response-handler";
|
||||
import { ResponseType, registerResponseHandler, Response, ExpandFolder, LayerPanelEntry } from "../../response-handler";
|
||||
import LayoutRow from "../layout/LayoutRow.vue";
|
||||
import LayoutCol from "../layout/LayoutCol.vue";
|
||||
import NumberInput from "../widgets/NumberInput.vue";
|
||||
@@ -102,23 +96,42 @@ export default defineComponent({
|
||||
},
|
||||
props: {},
|
||||
methods: {
|
||||
hideLayer(layerId: number) {
|
||||
console.log(`Hidden layer ID: ${layerId}`);
|
||||
hideLayer(layerId: LayerPanelEntry) {
|
||||
const layer = layerId as LayerPanelEntry;
|
||||
if (layer) {
|
||||
console.log(`Hidden layer ID: ${layer.path}`);
|
||||
} else {
|
||||
console.error("hideLayer did not receive valid arguments");
|
||||
}
|
||||
},
|
||||
showLayer(layerId: number) {
|
||||
console.log(`Shown layer ID: ${layerId}`);
|
||||
showLayer(layerId: LayerPanelEntry) {
|
||||
const layer = layerId as LayerPanelEntry;
|
||||
if (layer) {
|
||||
console.log(`Shown layer: ${layer.path}`);
|
||||
} else {
|
||||
console.error("showLayer did not receive valid arguments");
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
registerResponseHandler(ResponseType["Document::ExpandFolder"], (responseData) => {
|
||||
console.log("ExpandFolder: ", responseData);
|
||||
registerResponseHandler(ResponseType.ExpandFolder, (responseData: Response) => {
|
||||
const expandData = responseData as ExpandFolder;
|
||||
if (expandData) {
|
||||
const responsePath = expandData.path;
|
||||
const responseLayers = expandData.children as Array<LayerPanelEntry>;
|
||||
if (responsePath.length > 0) console.error("Non root paths are currently not implemented");
|
||||
|
||||
this.layers = responseLayers;
|
||||
}
|
||||
});
|
||||
registerResponseHandler(ResponseType["Document::CollapseFolder"], (responseData) => {
|
||||
registerResponseHandler(ResponseType.CollapseFolder, (responseData) => {
|
||||
console.log("CollapseFolder: ", responseData);
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
return {
|
||||
layers: [] as Array<LayerPanelEntry>,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
type ResponseCallback = (responseData: string) => void;
|
||||
type ResponseCallback = (responseData: Response) => void;
|
||||
type ResponseMap = {
|
||||
[response: string]: ResponseCallback | undefined;
|
||||
};
|
||||
@@ -9,10 +9,10 @@ declare global {
|
||||
}
|
||||
|
||||
export enum ResponseType {
|
||||
"Tool::UpdateCanvas" = "Tool::UpdateCanvas",
|
||||
"Document::ExpandFolder" = "Document::ExpandFolder",
|
||||
"Document::CollapseFolder" = "Document::CollapseFolder",
|
||||
"Tool::SetActiveTool" = "Tool::SetActiveTool",
|
||||
UpdateCanvas = "UpdateCanvas",
|
||||
ExpandFolder = "ExpandFolder",
|
||||
CollapseFolder = "CollapseFolder",
|
||||
SetActiveTool = "SetActiveTool",
|
||||
}
|
||||
|
||||
export function attachResponseHandlerToPage() {
|
||||
@@ -24,12 +24,82 @@ export function registerResponseHandler(responseType: ResponseType, callback: Re
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function handleResponse(responseType: ResponseType, responseData: any) {
|
||||
const callback = window.responseMap[responseType];
|
||||
function parseResponse(origin: string, responseType: string, data: any): Response {
|
||||
type OriginNames = "Document" | "Tool";
|
||||
|
||||
if (callback) {
|
||||
callback(responseData);
|
||||
const originHandlers = {
|
||||
Document: () => {
|
||||
switch (responseType) {
|
||||
case "DocumentChanged":
|
||||
return (data.Document.DocumentChanged as DocumentChanged) as Response;
|
||||
case "CollapseFolder":
|
||||
return (data.Document.CollapseFolder as CollapseFolder) as Response;
|
||||
case "ExpandFolder":
|
||||
return (data.Document.ExpandFolder as ExpandFolder) as Response;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
Tool: () => {
|
||||
switch (responseType) {
|
||||
case "SetActiveTool":
|
||||
return (data.Tool.SetActiveTool as SetActiveTool) as Response;
|
||||
case "UpdateCanvas":
|
||||
return (data.Tool.UpdateCanvas as UpdateCanvas) as Response;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Optional chaining would be nice here when we can upgrade to Webpack 5: https://github.com/webpack/webpack/issues/10227
|
||||
// const response = originHandlers[origin as OriginNames]?.();
|
||||
const response = originHandlers[origin as OriginNames] && originHandlers[origin as OriginNames]();
|
||||
if (!response) throw new Error("ResponseType not recognized.");
|
||||
return response;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function handleResponse(responseIdentifier: string, responseData: any) {
|
||||
const [origin, responesType] = responseIdentifier.split("::", 2);
|
||||
const callback = window.responseMap[responesType];
|
||||
const data = parseResponse(origin, responesType, responseData);
|
||||
|
||||
if (callback && data) {
|
||||
callback(data);
|
||||
} else if (data) {
|
||||
console.error(`Received a Response of type "${responseIdentifier}" but no handler was registered for it from the client.`);
|
||||
} else {
|
||||
console.error(`Received a Response of type "${responseType}" but no handler was registered for it from the client.`);
|
||||
console.error(`Received a Response of type "${responseIdentifier}" but but was not able to parse the data.`);
|
||||
}
|
||||
}
|
||||
|
||||
export type Response = SetActiveTool | UpdateCanvas | DocumentChanged | CollapseFolder | ExpandFolder;
|
||||
|
||||
export interface SetActiveTool {
|
||||
tool_name: string;
|
||||
}
|
||||
export interface UpdateCanvas {
|
||||
document: string;
|
||||
}
|
||||
export type DocumentChanged = {};
|
||||
export interface CollapseFolder {
|
||||
path: Array<number>;
|
||||
}
|
||||
export interface ExpandFolder {
|
||||
path: Array<number>;
|
||||
children: Array<LayerPanelEntry>;
|
||||
}
|
||||
|
||||
export interface LayerPanelEntry {
|
||||
name: string;
|
||||
visible: boolean;
|
||||
layer_type: LayerType;
|
||||
collapsed: boolean;
|
||||
path: Array<number>;
|
||||
}
|
||||
|
||||
export enum LayerType {
|
||||
Folder,
|
||||
Shape,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::shims::Error;
|
||||
use crate::wrappers::{translate_key, translate_tool, Color};
|
||||
use crate::EDITOR_STATE;
|
||||
use editor_core::events;
|
||||
use editor_core::{events, LayerId};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
fn convert_error(err: editor_core::EditorError) -> JsValue {
|
||||
@@ -32,7 +32,14 @@ mod mouse_state {
|
||||
(false, 1) => Event::LmbUp(state),
|
||||
(false, 2) => Event::RmbUp(state),
|
||||
(false, 4) => Event::MmbUp(state),
|
||||
_ => panic!("two buttons where modified at the same time. modification: {:#010b}", diff),
|
||||
(down, _) => {
|
||||
log::warn!("two buttons where modified at the same time. Modification: {:#010b}", diff);
|
||||
if down {
|
||||
Event::AmbiguousMouseDown(state)
|
||||
} else {
|
||||
Event::AmbiguousMouseUp(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,3 +125,45 @@ pub fn swap_colors() -> Result<(), JsValue> {
|
||||
pub fn reset_colors() -> Result<(), JsValue> {
|
||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_event(events::Event::ResetColors)).map_err(convert_error)
|
||||
}
|
||||
|
||||
/// Select a layer from the layer list
|
||||
#[wasm_bindgen]
|
||||
pub fn select_layer(path: Vec<LayerId>) -> Result<(), JsValue> {
|
||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_event(events::Event::SelectLayer(path))).map_err(convert_error)
|
||||
}
|
||||
|
||||
/// Toggle visibility of a layer from the layer list
|
||||
#[wasm_bindgen]
|
||||
pub fn toggle_layer_visibility(path: Vec<LayerId>) -> Result<(), JsValue> {
|
||||
EDITOR_STATE
|
||||
.with(|editor| editor.borrow_mut().handle_event(events::Event::ToggleLayerVisibility(path)))
|
||||
.map_err(convert_error)
|
||||
}
|
||||
|
||||
/// Toggle expansions state of a layer from the layer list
|
||||
#[wasm_bindgen]
|
||||
pub fn toggle_layer_expansion(path: Vec<LayerId>) -> Result<(), JsValue> {
|
||||
EDITOR_STATE
|
||||
.with(|editor| editor.borrow_mut().handle_event(events::Event::ToggleLayerExpansion(path)))
|
||||
.map_err(convert_error)
|
||||
}
|
||||
|
||||
/// Renames a layer from the layer list
|
||||
#[wasm_bindgen]
|
||||
pub fn rename_layer(path: Vec<LayerId>, new_name: String) -> Result<(), JsValue> {
|
||||
EDITOR_STATE
|
||||
.with(|editor| editor.borrow_mut().handle_event(events::Event::RenameLayer(path, new_name)))
|
||||
.map_err(convert_error)
|
||||
}
|
||||
|
||||
/// Deletes a layer from the layer list
|
||||
#[wasm_bindgen]
|
||||
pub fn delete_layer(path: Vec<LayerId>) -> Result<(), JsValue> {
|
||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_event(events::Event::DeleteLayer(path))).map_err(convert_error)
|
||||
}
|
||||
|
||||
/// Requests the backend to add a layer to the layer list
|
||||
#[wasm_bindgen]
|
||||
pub fn add_layer(path: Vec<LayerId>) -> Result<(), JsValue> {
|
||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_event(events::Event::AddLayer(path))).map_err(convert_error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user