-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* basic shapes - rectangle - ellipse - polygon * some other tools - undo & redo - group & ungroup - zoom tools - copy
- Loading branch information
Showing
18 changed files
with
1,364 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
<!-- | ||
Copyright: Ankitects Pty Ltd and contributors | ||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html | ||
--> | ||
<script lang="ts"> | ||
import { fabric } from "fabric"; | ||
import panzoom from "panzoom"; | ||
import protobuf from "protobufjs"; | ||
import { getImageClozeMetadata } from "./lib"; | ||
import SideToolbar from "./SideToolbar.svelte"; | ||
import { zoomResetValue } from "./store"; | ||
import { undoRedoInit } from "./tools/tool-undo-redo"; | ||
export let path: string; | ||
let instance; | ||
let innerWidth = 0; | ||
let canvas: fabric.Canvas; | ||
getImageClozeMetadata(path).then((metadata) => { | ||
const b64encoded = protobuf.util.base64.encode( | ||
metadata.data, | ||
0, | ||
metadata.data.length, | ||
); | ||
const data = "data:image/png;base64," + b64encoded; | ||
canvas = new fabric.Canvas("canvas", { | ||
hoverCursor: "pointer", | ||
selectionBorderColor: "green", | ||
}); | ||
// for debug in devtools | ||
globalThis.canvas = canvas; | ||
// get image width and height | ||
const image = new Image(); | ||
image.onload = function () { | ||
canvas.setWidth(image.width); | ||
canvas.setHeight(image.height); | ||
fabric.Image.fromURL(image.src, function (image) { | ||
canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas), { | ||
scaleX: canvas.width! / image.width!, | ||
scaleY: canvas.height! / image.height!, | ||
}); | ||
const zoomRatio = innerWidth / canvas.width!; | ||
zoomResetValue.set(zoomRatio); | ||
instance!.smoothZoom(0, 0, zoomRatio); | ||
}); | ||
}; | ||
undoRedoInit(canvas); | ||
image.src = data; | ||
image.remove(); | ||
}); | ||
function initPanzoom(node) { | ||
instance = panzoom(node, { | ||
bounds: true, | ||
maxZoom: 3, | ||
minZoom: 0.1, | ||
zoomDoubleClickSpeed: 1, | ||
}); | ||
instance.pause(); | ||
} | ||
</script> | ||
|
||
<div><SideToolbar {instance} {canvas} /></div> | ||
<div class="editor-main" bind:clientWidth={innerWidth}> | ||
<div class="editor-container" use:initPanzoom> | ||
<canvas id="canvas" /> | ||
</div> | ||
</div> | ||
|
||
<style lang="scss"> | ||
.editor-main { | ||
position: absolute; | ||
top: 80px; | ||
left: 36px; | ||
bottom: 2px; | ||
right: 2px; | ||
border: 1px solid rgb(96, 141, 225); | ||
overflow: auto; | ||
padding-bottom: 100px; | ||
} | ||
.editor-container { | ||
width: 100%; | ||
height: 100%; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<!-- | ||
Copyright: Ankitects Pty Ltd and contributors | ||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html | ||
--> | ||
<script lang="ts"> | ||
import IconButton from "../components/IconButton.svelte"; | ||
import { drawRectangle, drawEllipse, drawPolygon } from "./tools/index"; | ||
import { enableSelectable, fillShapeColor, stopDraw } from "./tools/lib"; | ||
import { tools } from "./tools/tool-buttons"; | ||
import TopToolbar from "./TopToolbar.svelte"; | ||
export let instance; | ||
export let canvas; | ||
const iconSize = 80; | ||
let activeTool = "cursor"; | ||
let toolTopPos = 0; | ||
function setActive(toolId) { | ||
activeTool = toolId; | ||
disableFunctions(); | ||
switch (toolId) { | ||
case "cursor": | ||
enableSelectable(canvas, true); | ||
break; | ||
case "magnify": | ||
instance.resume(); | ||
break; | ||
case "draw-rectangle": | ||
drawRectangle(canvas); | ||
break; | ||
case "draw-ellipse": | ||
drawEllipse(canvas); | ||
break; | ||
case "draw-polygon": | ||
drawPolygon(canvas, instance); | ||
break; | ||
case "shape-fill-color": | ||
enableSelectable(canvas, true); | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
const disableFunctions = () => { | ||
instance.pause(); | ||
stopDraw(canvas); | ||
enableSelectable(canvas, false); | ||
}; | ||
const changeShapeFillColor = (node: any) => { | ||
if (node) { | ||
fillShapeColor(canvas, node.target.value); | ||
} | ||
}; | ||
</script> | ||
|
||
<TopToolbar {canvas} {activeTool} {instance} {iconSize} /> | ||
|
||
<div class="tool-bar-container"> | ||
{#each tools as tool} | ||
<IconButton | ||
class="tool-icon-button {activeTool == tool.id ? 'active-tool' : ''}" | ||
{iconSize} | ||
active={activeTool === tool.id} | ||
on:click={(e) => { | ||
setActive(tool.id); | ||
// popup position for color dialog | ||
toolTopPos = e.pageY - 60; | ||
}}>{@html tool.icon}</IconButton | ||
> | ||
{/each} | ||
</div> | ||
|
||
<style> | ||
.tool-bar-container { | ||
position: fixed; | ||
top: 42px; | ||
left: 0; | ||
height: 100%; | ||
border-right: 1px solid #e3e3e3; | ||
overflow-y: auto; | ||
width: 32px; | ||
z-index: 99; | ||
background: white; | ||
padding-bottom: 100px; | ||
} | ||
:global(.tool-icon-button) { | ||
border: unset; | ||
display: block; | ||
width: 32px; | ||
height: 32px; | ||
margin: unset; | ||
padding: 6px !important; | ||
} | ||
:global(.active-tool) { | ||
color: red !important; | ||
background: unset !important; | ||
} | ||
::-webkit-scrollbar { | ||
width: 0.2em !important; | ||
height: 0.2em !important; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<!-- | ||
Copyright: Ankitects Pty Ltd and contributors | ||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html | ||
--> | ||
<script> | ||
import IconButton from "../components/IconButton.svelte"; | ||
import { cursorTools, zoomTools } from "./tools/more-tools"; | ||
import { undoRedoTools } from "./tools/tool-undo-redo"; | ||
export let activeTool = "cursor"; | ||
export let canvas; | ||
export let instance; | ||
export let iconSize; | ||
</script> | ||
|
||
<div class="top-tool-bar-container"> | ||
{#each undoRedoTools as undoRedoTool} | ||
<IconButton | ||
class="top-tool-icon-button" | ||
{iconSize} | ||
on:click={() => { | ||
undoRedoTool.action(canvas); | ||
}} | ||
> | ||
{@html undoRedoTool.icon} | ||
</IconButton> | ||
{/each} | ||
|
||
{#each zoomTools as zoomBottomTool} | ||
<IconButton | ||
class="top-tool-icon-button" | ||
{iconSize} | ||
on:click={() => { | ||
zoomBottomTool.action(instance); | ||
}} | ||
> | ||
{@html zoomBottomTool.icon} | ||
</IconButton> | ||
{/each} | ||
|
||
{#if activeTool === "cursor"} | ||
{#each cursorTools as cursorBottomTool} | ||
<IconButton | ||
class="top-tool-icon-button" | ||
{iconSize} | ||
on:click={() => { | ||
cursorBottomTool.action(canvas); | ||
}} | ||
> | ||
{@html cursorBottomTool.icon} | ||
</IconButton> | ||
{/each} | ||
{/if} | ||
</div> | ||
|
||
<style> | ||
.top-tool-bar-container { | ||
position: fixed; | ||
top: 42px; | ||
left: 36px; | ||
width: 100%; | ||
border-right: 1px solid #e3e3e3; | ||
overflow-y: auto; | ||
z-index: 99; | ||
background: white; | ||
} | ||
:global(.top-tool-icon-button) { | ||
border: unset; | ||
display: inline; | ||
width: 32px; | ||
height: 32px; | ||
margin: unset; | ||
padding: 6px !important; | ||
} | ||
</style> |
Oops, something went wrong.