@edadma/logo 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +176 -0
- package/dist/logo.js +44428 -0
- package/dist/react/LogoCanvas.js +74 -0
- package/dist/react/index.js +2 -0
- package/dist/types/LogoCanvas.d.ts +67 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +59 -0
- package/react/LogoCanvas.tsx +174 -0
- package/react/index.ts +2 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useEffect, useImperativeHandle, forwardRef } from 'react';
|
|
3
|
+
export const LogoCanvas = forwardRef(({ width = 600, height = 400, program, pathRendering = true, onError, onComplete, className, style, }, ref) => {
|
|
4
|
+
const canvasRef = useRef(null);
|
|
5
|
+
const logoRef = useRef(null);
|
|
6
|
+
// Initialize Logo instance
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (canvasRef.current && typeof Logo !== 'undefined') {
|
|
9
|
+
logoRef.current = new Logo(canvasRef.current);
|
|
10
|
+
logoRef.current.setPathRendering(pathRendering);
|
|
11
|
+
}
|
|
12
|
+
}, []);
|
|
13
|
+
// Update path rendering setting
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (logoRef.current) {
|
|
16
|
+
logoRef.current.setPathRendering(pathRendering);
|
|
17
|
+
}
|
|
18
|
+
}, [pathRendering]);
|
|
19
|
+
// Run program when it changes
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (logoRef.current && program !== undefined) {
|
|
22
|
+
try {
|
|
23
|
+
logoRef.current.clear();
|
|
24
|
+
logoRef.current.run(program);
|
|
25
|
+
onComplete?.();
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}, [program, onError, onComplete]);
|
|
32
|
+
// Expose methods via ref
|
|
33
|
+
useImperativeHandle(ref, () => ({
|
|
34
|
+
execute: (command) => {
|
|
35
|
+
if (logoRef.current) {
|
|
36
|
+
try {
|
|
37
|
+
logoRef.current.execute(command);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
run: (prog) => {
|
|
45
|
+
if (logoRef.current) {
|
|
46
|
+
try {
|
|
47
|
+
logoRef.current.run(prog);
|
|
48
|
+
onComplete?.();
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
clear: () => {
|
|
56
|
+
logoRef.current?.clear();
|
|
57
|
+
},
|
|
58
|
+
getDrawing: () => {
|
|
59
|
+
return logoRef.current?.getDrawing() ?? { lines: [], labels: [] };
|
|
60
|
+
},
|
|
61
|
+
getTurtle: () => {
|
|
62
|
+
return (logoRef.current?.getTurtle() ?? {
|
|
63
|
+
x: 0,
|
|
64
|
+
y: 0,
|
|
65
|
+
heading: Math.PI / 2,
|
|
66
|
+
visible: true,
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
getLogo: () => logoRef.current,
|
|
70
|
+
}));
|
|
71
|
+
return (_jsx("canvas", { ref: canvasRef, width: width, height: height, className: className, style: { backgroundColor: 'white', ...style } }));
|
|
72
|
+
});
|
|
73
|
+
LogoCanvas.displayName = 'LogoCanvas';
|
|
74
|
+
export default LogoCanvas;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface LogoDrawing {
|
|
3
|
+
lines: Array<{
|
|
4
|
+
x1: number;
|
|
5
|
+
y1: number;
|
|
6
|
+
x2: number;
|
|
7
|
+
y2: number;
|
|
8
|
+
color: string;
|
|
9
|
+
width: number;
|
|
10
|
+
}>;
|
|
11
|
+
labels: Array<{
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
heading: number;
|
|
15
|
+
text: string;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
interface TurtleState {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
heading: number;
|
|
22
|
+
visible: boolean;
|
|
23
|
+
}
|
|
24
|
+
interface LogoInstance {
|
|
25
|
+
run(program: string): void;
|
|
26
|
+
execute(command: string): void;
|
|
27
|
+
clear(): void;
|
|
28
|
+
render(): void;
|
|
29
|
+
setPathRendering(enabled: boolean): void;
|
|
30
|
+
setAutoRender(enabled: boolean): void;
|
|
31
|
+
getDrawing(): LogoDrawing;
|
|
32
|
+
getTurtle(): TurtleState;
|
|
33
|
+
}
|
|
34
|
+
export interface LogoCanvasProps {
|
|
35
|
+
/** Width of the canvas in pixels */
|
|
36
|
+
width?: number;
|
|
37
|
+
/** Height of the canvas in pixels */
|
|
38
|
+
height?: number;
|
|
39
|
+
/** Logo program to run */
|
|
40
|
+
program?: string;
|
|
41
|
+
/** Whether to use path-based rendering (smoother curves) */
|
|
42
|
+
pathRendering?: boolean;
|
|
43
|
+
/** Callback when an error occurs */
|
|
44
|
+
onError?: (error: Error) => void;
|
|
45
|
+
/** Callback when program execution completes */
|
|
46
|
+
onComplete?: () => void;
|
|
47
|
+
/** Additional CSS class for the canvas */
|
|
48
|
+
className?: string;
|
|
49
|
+
/** Additional inline styles for the canvas */
|
|
50
|
+
style?: React.CSSProperties;
|
|
51
|
+
}
|
|
52
|
+
export interface LogoCanvasRef {
|
|
53
|
+
/** Execute a Logo command */
|
|
54
|
+
execute: (command: string) => void;
|
|
55
|
+
/** Run a full Logo program */
|
|
56
|
+
run: (program: string) => void;
|
|
57
|
+
/** Clear the canvas and reset turtle */
|
|
58
|
+
clear: () => void;
|
|
59
|
+
/** Get the current drawing data */
|
|
60
|
+
getDrawing: () => LogoDrawing;
|
|
61
|
+
/** Get the current turtle state */
|
|
62
|
+
getTurtle: () => TurtleState;
|
|
63
|
+
/** Get the underlying Logo instance */
|
|
64
|
+
getLogo: () => LogoInstance | null;
|
|
65
|
+
}
|
|
66
|
+
export declare const LogoCanvas: React.ForwardRefExoticComponent<LogoCanvasProps & React.RefAttributes<LogoCanvasRef>>;
|
|
67
|
+
export default LogoCanvas;
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@edadma/logo",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Logo programming language interpreter with React component",
|
|
5
|
+
"main": "dist/logo.js",
|
|
6
|
+
"module": "dist/logo.js",
|
|
7
|
+
"types": "dist/types/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"react"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/logo.js",
|
|
15
|
+
"types": "./dist/types/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./react": {
|
|
18
|
+
"import": "./react/index.ts",
|
|
19
|
+
"types": "./react/index.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "npm run build:scala && npm run build:react",
|
|
24
|
+
"build:scala": "cd .. && sbt logoJS/fullLinkJS && cp js/target/scala-3.7.4/logo-opt/main.js js/dist/logo.js",
|
|
25
|
+
"build:react": "tsc --project tsconfig.react.json",
|
|
26
|
+
"prepublishOnly": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"logo",
|
|
30
|
+
"programming",
|
|
31
|
+
"turtle",
|
|
32
|
+
"graphics",
|
|
33
|
+
"education",
|
|
34
|
+
"react",
|
|
35
|
+
"canvas"
|
|
36
|
+
],
|
|
37
|
+
"author": "Edward A. Maxedon, Sr.",
|
|
38
|
+
"license": "ISC",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/edadma/logo.git"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/edadma/logo/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/edadma/logo#readme",
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": ">=17.0.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"react": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/react": "^18.2.0",
|
|
57
|
+
"typescript": "^5.0.0"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import React, { useRef, useEffect, useImperativeHandle, forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
// Type definitions for the Logo class from Scala.js
|
|
4
|
+
interface LogoDrawing {
|
|
5
|
+
lines: Array<{
|
|
6
|
+
x1: number;
|
|
7
|
+
y1: number;
|
|
8
|
+
x2: number;
|
|
9
|
+
y2: number;
|
|
10
|
+
color: string;
|
|
11
|
+
width: number;
|
|
12
|
+
}>;
|
|
13
|
+
labels: Array<{
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
heading: number;
|
|
17
|
+
text: string;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface TurtleState {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
heading: number;
|
|
25
|
+
visible: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface LogoInstance {
|
|
29
|
+
run(program: string): void;
|
|
30
|
+
execute(command: string): void;
|
|
31
|
+
clear(): void;
|
|
32
|
+
render(): void;
|
|
33
|
+
setPathRendering(enabled: boolean): void;
|
|
34
|
+
setAutoRender(enabled: boolean): void;
|
|
35
|
+
getDrawing(): LogoDrawing;
|
|
36
|
+
getTurtle(): TurtleState;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare const Logo: new (canvas: HTMLCanvasElement) => LogoInstance;
|
|
40
|
+
|
|
41
|
+
export interface LogoCanvasProps {
|
|
42
|
+
/** Width of the canvas in pixels */
|
|
43
|
+
width?: number;
|
|
44
|
+
/** Height of the canvas in pixels */
|
|
45
|
+
height?: number;
|
|
46
|
+
/** Logo program to run */
|
|
47
|
+
program?: string;
|
|
48
|
+
/** Whether to use path-based rendering (smoother curves) */
|
|
49
|
+
pathRendering?: boolean;
|
|
50
|
+
/** Callback when an error occurs */
|
|
51
|
+
onError?: (error: Error) => void;
|
|
52
|
+
/** Callback when program execution completes */
|
|
53
|
+
onComplete?: () => void;
|
|
54
|
+
/** Additional CSS class for the canvas */
|
|
55
|
+
className?: string;
|
|
56
|
+
/** Additional inline styles for the canvas */
|
|
57
|
+
style?: React.CSSProperties;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface LogoCanvasRef {
|
|
61
|
+
/** Execute a Logo command */
|
|
62
|
+
execute: (command: string) => void;
|
|
63
|
+
/** Run a full Logo program */
|
|
64
|
+
run: (program: string) => void;
|
|
65
|
+
/** Clear the canvas and reset turtle */
|
|
66
|
+
clear: () => void;
|
|
67
|
+
/** Get the current drawing data */
|
|
68
|
+
getDrawing: () => LogoDrawing;
|
|
69
|
+
/** Get the current turtle state */
|
|
70
|
+
getTurtle: () => TurtleState;
|
|
71
|
+
/** Get the underlying Logo instance */
|
|
72
|
+
getLogo: () => LogoInstance | null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const LogoCanvas = forwardRef<LogoCanvasRef, LogoCanvasProps>(
|
|
76
|
+
(
|
|
77
|
+
{
|
|
78
|
+
width = 600,
|
|
79
|
+
height = 400,
|
|
80
|
+
program,
|
|
81
|
+
pathRendering = true,
|
|
82
|
+
onError,
|
|
83
|
+
onComplete,
|
|
84
|
+
className,
|
|
85
|
+
style,
|
|
86
|
+
},
|
|
87
|
+
ref
|
|
88
|
+
) => {
|
|
89
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
90
|
+
const logoRef = useRef<LogoInstance | null>(null);
|
|
91
|
+
|
|
92
|
+
// Initialize Logo instance
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (canvasRef.current && typeof Logo !== 'undefined') {
|
|
95
|
+
logoRef.current = new Logo(canvasRef.current);
|
|
96
|
+
logoRef.current.setPathRendering(pathRendering);
|
|
97
|
+
}
|
|
98
|
+
}, []);
|
|
99
|
+
|
|
100
|
+
// Update path rendering setting
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (logoRef.current) {
|
|
103
|
+
logoRef.current.setPathRendering(pathRendering);
|
|
104
|
+
}
|
|
105
|
+
}, [pathRendering]);
|
|
106
|
+
|
|
107
|
+
// Run program when it changes
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (logoRef.current && program !== undefined) {
|
|
110
|
+
try {
|
|
111
|
+
logoRef.current.clear();
|
|
112
|
+
logoRef.current.run(program);
|
|
113
|
+
onComplete?.();
|
|
114
|
+
} catch (e) {
|
|
115
|
+
onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}, [program, onError, onComplete]);
|
|
119
|
+
|
|
120
|
+
// Expose methods via ref
|
|
121
|
+
useImperativeHandle(ref, () => ({
|
|
122
|
+
execute: (command: string) => {
|
|
123
|
+
if (logoRef.current) {
|
|
124
|
+
try {
|
|
125
|
+
logoRef.current.execute(command);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
run: (prog: string) => {
|
|
132
|
+
if (logoRef.current) {
|
|
133
|
+
try {
|
|
134
|
+
logoRef.current.run(prog);
|
|
135
|
+
onComplete?.();
|
|
136
|
+
} catch (e) {
|
|
137
|
+
onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
clear: () => {
|
|
142
|
+
logoRef.current?.clear();
|
|
143
|
+
},
|
|
144
|
+
getDrawing: () => {
|
|
145
|
+
return logoRef.current?.getDrawing() ?? { lines: [], labels: [] };
|
|
146
|
+
},
|
|
147
|
+
getTurtle: () => {
|
|
148
|
+
return (
|
|
149
|
+
logoRef.current?.getTurtle() ?? {
|
|
150
|
+
x: 0,
|
|
151
|
+
y: 0,
|
|
152
|
+
heading: Math.PI / 2,
|
|
153
|
+
visible: true,
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
},
|
|
157
|
+
getLogo: () => logoRef.current,
|
|
158
|
+
}));
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<canvas
|
|
162
|
+
ref={canvasRef}
|
|
163
|
+
width={width}
|
|
164
|
+
height={height}
|
|
165
|
+
className={className}
|
|
166
|
+
style={{ backgroundColor: 'white', ...style }}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
LogoCanvas.displayName = 'LogoCanvas';
|
|
173
|
+
|
|
174
|
+
export default LogoCanvas;
|
package/react/index.ts
ADDED