Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
07eaf93e78 | |||
6a5ca3088a | |||
6501072d80 | |||
726fd02bad | |||
d0f9ae475f | |||
da323e22d4 | |||
8dc3628e9b | |||
253744867b | |||
c45eb1e3e3 | |||
758aac9328 | |||
309943cf2c | |||
b3d4ab91fc | |||
5e73fa45f0 | |||
17d23a17db |
@ -31,7 +31,6 @@ layout: manual
|
|||||||
* [`fillet`](kcl/fillet)
|
* [`fillet`](kcl/fillet)
|
||||||
* [`floor`](kcl/floor)
|
* [`floor`](kcl/floor)
|
||||||
* [`getEdge`](kcl/getEdge)
|
* [`getEdge`](kcl/getEdge)
|
||||||
* [`getExtrudeWallTransform`](kcl/getExtrudeWallTransform)
|
|
||||||
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
||||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||||
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
||||||
|
@ -28338,839 +28338,6 @@
|
|||||||
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> line([10, 0], %)\n |> line([0, -10], %, 'revolveAxis')\n |> close(%)\n |> extrude(10, %)\n\nconst sketch001 = startSketchOn('XY')\n |> startProfileAt([0, -10], %)\n |> line([0, -10], %)\n |> line([2, 0], %)\n |> line([0, 10], %)\n |> close(%)\n |> revolve({\n axis: getEdge('revolveAxis', box),\n angle: 90\n }, %)"
|
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> line([10, 0], %)\n |> line([0, -10], %, 'revolveAxis')\n |> close(%)\n |> extrude(10, %)\n\nconst sketch001 = startSketchOn('XY')\n |> startProfileAt([0, -10], %)\n |> line([0, -10], %)\n |> line([2, 0], %)\n |> line([0, 10], %)\n |> close(%)\n |> revolve({\n axis: getEdge('revolveAxis', box),\n angle: 90\n }, %)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "getExtrudeWallTransform",
|
|
||||||
"summary": "Returns the extrude wall transform.",
|
|
||||||
"description": "",
|
|
||||||
"tags": [],
|
|
||||||
"args": [
|
|
||||||
{
|
|
||||||
"name": "surface_name",
|
|
||||||
"type": "string",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "extrude_group",
|
|
||||||
"type": "ExtrudeGroup",
|
|
||||||
"schema": {
|
|
||||||
"description": "An extrude group is a collection of extrude surfaces.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"__meta",
|
|
||||||
"height",
|
|
||||||
"id",
|
|
||||||
"position",
|
|
||||||
"rotation",
|
|
||||||
"sketchGroupValues",
|
|
||||||
"value",
|
|
||||||
"xAxis",
|
|
||||||
"yAxis",
|
|
||||||
"zAxis"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"__meta": {
|
|
||||||
"description": "Metadata.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"description": "Metadata.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"sourceRange"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"sourceRange": {
|
|
||||||
"description": "The source range.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"endCapId": {
|
|
||||||
"description": "The id of the extrusion end cap",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"height": {
|
|
||||||
"description": "The height of the extrude group.",
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"description": "The id of the extrude group.",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid"
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"description": "The position of the extrude group.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 3,
|
|
||||||
"minItems": 3
|
|
||||||
},
|
|
||||||
"rotation": {
|
|
||||||
"description": "The rotation of the extrude group.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 4,
|
|
||||||
"minItems": 4
|
|
||||||
},
|
|
||||||
"sketchGroupValues": {
|
|
||||||
"description": "The sketch group paths.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"description": "A path.",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"description": "A path that goes to a point.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"__geoMeta",
|
|
||||||
"from",
|
|
||||||
"name",
|
|
||||||
"to",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"__geoMeta": {
|
|
||||||
"description": "Metadata.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"sourceRange"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"description": "The id of the geometry.",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid"
|
|
||||||
},
|
|
||||||
"sourceRange": {
|
|
||||||
"description": "The source range.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"from": {
|
|
||||||
"description": "The from point.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"description": "The name of the path.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"to": {
|
|
||||||
"description": "The to point.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ToPoint"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A arc that is tangential to the last path segment that goes to a point",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"__geoMeta",
|
|
||||||
"ccw",
|
|
||||||
"center",
|
|
||||||
"from",
|
|
||||||
"name",
|
|
||||||
"to",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"__geoMeta": {
|
|
||||||
"description": "Metadata.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"sourceRange"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"description": "The id of the geometry.",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid"
|
|
||||||
},
|
|
||||||
"sourceRange": {
|
|
||||||
"description": "The source range.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ccw": {
|
|
||||||
"description": "arc's direction",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"center": {
|
|
||||||
"description": "the arc's center",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"from": {
|
|
||||||
"description": "The from point.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"description": "The name of the path.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"to": {
|
|
||||||
"description": "The to point.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"TangentialArcTo"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A arc that is tangential to the last path segment",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"__geoMeta",
|
|
||||||
"from",
|
|
||||||
"name",
|
|
||||||
"to",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"__geoMeta": {
|
|
||||||
"description": "Metadata.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"sourceRange"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"description": "The id of the geometry.",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid"
|
|
||||||
},
|
|
||||||
"sourceRange": {
|
|
||||||
"description": "The source range.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"from": {
|
|
||||||
"description": "The from point.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"description": "The name of the path.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"to": {
|
|
||||||
"description": "The to point.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"TangentialArc"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A path that is horizontal.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"__geoMeta",
|
|
||||||
"from",
|
|
||||||
"name",
|
|
||||||
"to",
|
|
||||||
"type",
|
|
||||||
"x"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"__geoMeta": {
|
|
||||||
"description": "Metadata.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"sourceRange"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"description": "The id of the geometry.",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid"
|
|
||||||
},
|
|
||||||
"sourceRange": {
|
|
||||||
"description": "The source range.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"from": {
|
|
||||||
"description": "The from point.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"description": "The name of the path.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"to": {
|
|
||||||
"description": "The to point.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Horizontal"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"x": {
|
|
||||||
"description": "The x coordinate.",
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "An angled line to.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"__geoMeta",
|
|
||||||
"from",
|
|
||||||
"name",
|
|
||||||
"to",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"__geoMeta": {
|
|
||||||
"description": "Metadata.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"sourceRange"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"description": "The id of the geometry.",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid"
|
|
||||||
},
|
|
||||||
"sourceRange": {
|
|
||||||
"description": "The source range.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"from": {
|
|
||||||
"description": "The from point.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"description": "The name of the path.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"to": {
|
|
||||||
"description": "The to point.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"AngledLineTo"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"x": {
|
|
||||||
"description": "The x coordinate.",
|
|
||||||
"type": "number",
|
|
||||||
"format": "double",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"y": {
|
|
||||||
"description": "The y coordinate.",
|
|
||||||
"type": "number",
|
|
||||||
"format": "double",
|
|
||||||
"nullable": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A base path.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"__geoMeta",
|
|
||||||
"from",
|
|
||||||
"name",
|
|
||||||
"to",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"__geoMeta": {
|
|
||||||
"description": "Metadata.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"sourceRange"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"description": "The id of the geometry.",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid"
|
|
||||||
},
|
|
||||||
"sourceRange": {
|
|
||||||
"description": "The source range.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"from": {
|
|
||||||
"description": "The from point.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"description": "The name of the path.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"to": {
|
|
||||||
"description": "The to point.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"startCapId": {
|
|
||||||
"description": "The id of the extrusion start cap",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"description": "The extrude surfaces.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"description": "An extrude surface.",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"description": "An extrude plane.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"faceId",
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"position",
|
|
||||||
"rotation",
|
|
||||||
"sourceRange",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"faceId": {
|
|
||||||
"description": "The face id for the extrude plane.",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"description": "The id of the geometry.",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"description": "The name.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"description": "The position.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 3,
|
|
||||||
"minItems": 3
|
|
||||||
},
|
|
||||||
"rotation": {
|
|
||||||
"description": "The rotation.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 4,
|
|
||||||
"minItems": 4
|
|
||||||
},
|
|
||||||
"sourceRange": {
|
|
||||||
"description": "The source range.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"extrudePlane"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "An extruded arc.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"faceId",
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"position",
|
|
||||||
"rotation",
|
|
||||||
"sourceRange",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"faceId": {
|
|
||||||
"description": "The face id for the extrude plane.",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"description": "The id of the geometry.",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uuid"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"description": "The name.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"description": "The position.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 3,
|
|
||||||
"minItems": 3
|
|
||||||
},
|
|
||||||
"rotation": {
|
|
||||||
"description": "The rotation.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 4,
|
|
||||||
"minItems": 4
|
|
||||||
},
|
|
||||||
"sourceRange": {
|
|
||||||
"description": "The source range.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"extrudeArc"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"xAxis": {
|
|
||||||
"description": "The x-axis of the extrude group base plane in the 3D space",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"x",
|
|
||||||
"y",
|
|
||||||
"z"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"x": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"y": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"z": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"yAxis": {
|
|
||||||
"description": "The y-axis of the extrude group base plane in the 3D space",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"x",
|
|
||||||
"y",
|
|
||||||
"z"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"x": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"y": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"z": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zAxis": {
|
|
||||||
"description": "The z-axis of the extrude group base plane in the 3D space",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"x",
|
|
||||||
"y",
|
|
||||||
"z"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"x": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"y": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"z": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"returnValue": {
|
|
||||||
"name": "",
|
|
||||||
"type": "ExtrudeTransform",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"__meta",
|
|
||||||
"position",
|
|
||||||
"rotation"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"__meta": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"description": "Metadata.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"sourceRange"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"sourceRange": {
|
|
||||||
"description": "The source range.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 3,
|
|
||||||
"minItems": 3
|
|
||||||
},
|
|
||||||
"rotation": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 4,
|
|
||||||
"minItems": 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"unpublished": false,
|
|
||||||
"deprecated": false,
|
|
||||||
"examples": [
|
|
||||||
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> line([10, 0], %)\n |> line([0, -10], %, \"surface\")\n |> close(%)\n |> extrude(5, %)\n\nconst transform = getExtrudeWallTransform('surface', box)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "getNextAdjacentEdge",
|
"name": "getNextAdjacentEdge",
|
||||||
"summary": "Get the next adjacent edge to the edge given.",
|
"summary": "Get the next adjacent edge to the edge given.",
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
TEST_SETTINGS_ONBOARDING_START,
|
TEST_SETTINGS_ONBOARDING_START,
|
||||||
} from './storageStates'
|
} from './storageStates'
|
||||||
import * as TOML from '@iarna/toml'
|
import * as TOML from '@iarna/toml'
|
||||||
|
import { Coords2d } from 'lang/std/sketch'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
|
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
|
||||||
@ -130,6 +131,7 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
// selected two lines therefore there should be two cursors
|
// selected two lines therefore there should be two cursors
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Constrain' }).click()
|
||||||
await page.getByRole('button', { name: 'Equal Length' }).click()
|
await page.getByRole('button', { name: 'Equal Length' }).click()
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
@ -263,6 +265,88 @@ test('Can moving camera', async ({ page, context }) => {
|
|||||||
}, [1, -94, -94])
|
}, [1, -94, -94])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('if you click the format button it formats your code', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.click('.cm-content')
|
||||||
|
await page.keyboard.type(`const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
await page.click('#code-pane button:first-child')
|
||||||
|
await page.click('button:has-text("Format code")')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('if you use the format keyboard binding it formats your code', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
const lspStartPromise = page.waitForEvent('console', async (message) => {
|
||||||
|
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
||||||
|
// but that doesn't seem to make it to the console for macos/safari :(
|
||||||
|
if (message.text().includes('start kcl lsp')) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await lspStartPromise
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// focus the editor
|
||||||
|
await page.click('.cm-line')
|
||||||
|
|
||||||
|
// Hit alt+shift+f to format the code
|
||||||
|
await page.keyboard.press('Alt+Shift+KeyF')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
})
|
||||||
|
|
||||||
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
@ -528,10 +612,6 @@ test.describe('Can create sketches on all planes and their back sides', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Auto complete works', async ({ page }) => {
|
test('Auto complete works', async ({ page }) => {
|
||||||
test.skip(
|
|
||||||
true,
|
|
||||||
'CORS issue stopping the kcl lsp from working, enable again later'
|
|
||||||
)
|
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
@ -861,11 +941,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
||||||
await topHorzSegmentClick()
|
await topHorzSegmentClick()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
|
const constrainButton = page.getByRole('button', { name: 'Constrain' })
|
||||||
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
@ -875,12 +958,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
// same selection but click the axis first
|
// same selection but click the axis first
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await topHorzSegmentClick()
|
await topHorzSegmentClick()
|
||||||
|
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
@ -889,10 +974,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
// check the same selection again by putting cursor in code first then selecting axis
|
// check the same selection again by putting cursor in code first then selecting axis
|
||||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
@ -954,9 +1041,8 @@ test.describe('Command bar tests', () => {
|
|||||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
|
|
||||||
// First try opening the command bar and closing it
|
// First try opening the command bar and closing it
|
||||||
// It has a different label on mac and windows/linux, "Meta+K" and "Ctrl+/" respectively
|
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'Ctrl+/' })
|
.getByRole('button', { name: 'Commands', exact: false })
|
||||||
.or(page.getByRole('button', { name: '⌘K' }))
|
.or(page.getByRole('button', { name: '⌘K' }))
|
||||||
.click()
|
.click()
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
@ -1265,6 +1351,72 @@ test('ProgramMemory can be serialised', async ({ page }) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Hovering over 3d features highlights code', async ({ page }) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([20, 0], %)
|
||||||
|
|> line([7.13, 4 + 0], %)
|
||||||
|
|> angledLine({ angle: 3 + 0, length: 3.14 + 0 }, %)
|
||||||
|
|> lineTo([20.14 + 0, -0.14 + 0], %)
|
||||||
|
|> xLineTo(29 + 0, %)
|
||||||
|
|> yLine(-3.14 + 0, %, 'a')
|
||||||
|
|> xLine(1.63, %)
|
||||||
|
|> angledLineOfXLength({ angle: 3 + 0, length: 3.14 }, %)
|
||||||
|
|> angledLineOfYLength({ angle: 30, length: 3 + 0 }, %)
|
||||||
|
|> angledLineToX({ angle: 22.14 + 0, to: 12 }, %)
|
||||||
|
|> angledLineToY({ angle: 30, to: 11.14 }, %)
|
||||||
|
|> angledLineThatIntersects({
|
||||||
|
angle: 3.14,
|
||||||
|
intersectTag: 'a',
|
||||||
|
offset: 0
|
||||||
|
}, %)
|
||||||
|
|> tangentialArcTo([13.14 + 0, 13.14], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
const extrusionTop: Coords2d = [800, 240]
|
||||||
|
const flatExtrusionFace: Coords2d = [960, 160]
|
||||||
|
const arc: Coords2d = [840, 160]
|
||||||
|
const close: Coords2d = [720, 200]
|
||||||
|
const nothing: Coords2d = [600, 200]
|
||||||
|
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await page.mouse.click(nothing[0], nothing[1])
|
||||||
|
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
await page.mouse.move(extrusionTop[0], extrusionTop[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.mouse.move(arc[0], arc[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.mouse.move(close[0], close[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.mouse.move(flatExtrusionFace[0], flatExtrusionFace[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).toHaveCount(5) // multiple lines
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
@ -1761,6 +1913,7 @@ test('Can code mod a line length', async ({ page }) => {
|
|||||||
const startXPx = 500
|
const startXPx = 500
|
||||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||||
await page.mouse.click(615, 102)
|
await page.mouse.click(615, 102)
|
||||||
|
await page.getByRole('button', { name: 'Constrain', exact: true }).click()
|
||||||
await page.getByRole('button', { name: 'length', exact: true }).click()
|
await page.getByRole('button', { name: 'length', exact: true }).click()
|
||||||
await page.getByText('Add constraining value').click()
|
await page.getByText('Add constraining value').click()
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 45 KiB |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.16.0",
|
"@codemirror/autocomplete": "^6.16.0",
|
||||||
|
@ -74,5 +74,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"version": "0.20.2"
|
"version": "0.21.1"
|
||||||
}
|
}
|
||||||
|
133
src/Toolbar.tsx
@ -4,7 +4,6 @@ import { engineCommandManager, kclManager } from 'lib/singletons'
|
|||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { ActionButton } from 'components/ActionButton'
|
import { ActionButton } from 'components/ActionButton'
|
||||||
import usePlatform from 'hooks/usePlatform'
|
|
||||||
import { isSingleCursorInPipe } from 'lang/queryAst'
|
import { isSingleCursorInPipe } from 'lang/queryAst'
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
import {
|
import {
|
||||||
@ -12,18 +11,18 @@ import {
|
|||||||
useNetworkStatus,
|
useNetworkStatus,
|
||||||
} from 'components/NetworkHealthIndicator'
|
} from 'components/NetworkHealthIndicator'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
|
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const platform = usePlatform()
|
|
||||||
const { commandBarSend } = useCommandsContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
const { state, send, context } = useModelingContext()
|
const { state, send, context } = useModelingContext()
|
||||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||||
const iconClassName =
|
const iconClassName =
|
||||||
'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-chalkboard-10 group-pressed:!text-chalkboard-10'
|
'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-primary dark:group-enabled:group-hover:!text-inherit group-pressed:!text-chalkboard-10 group-ui-open:!text-chalkboard-10 dark:group-ui-open:!text-chalkboard-10'
|
||||||
const bgClassName =
|
const bgClassName =
|
||||||
'group-disabled:!bg-transparent group-enabled:group-hover:bg-primary group-pressed:bg-primary'
|
'group-disabled:!bg-transparent group-enabled:group-hover:bg-primary/10 dark:group-enabled:group-hover:bg-primary group-pressed:bg-primary group-ui-open:bg-primary'
|
||||||
const buttonClassName =
|
const buttonClassName =
|
||||||
'bg-chalkboard-10 dark:bg-chalkboard-100 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100'
|
'bg-chalkboard-10 dark:bg-chalkboard-100 enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!border-primary ui-open:!border-primary'
|
||||||
const pathId = useMemo(() => {
|
const pathId = useMemo(() => {
|
||||||
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
|
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
|
||||||
return false
|
return false
|
||||||
@ -59,10 +58,7 @@ export const Toolbar = () => {
|
|||||||
{...props}
|
{...props}
|
||||||
ref={toolbarButtonsRef}
|
ref={toolbarButtonsRef}
|
||||||
onWheel={handleToolbarButtonsWheelEvent}
|
onWheel={handleToolbarButtonsWheelEvent}
|
||||||
className={
|
className={'m-0 py-1 rounded-l-sm flex gap-2 items-center ' + className}
|
||||||
'm-0 py-1 rounded-l-sm flex gap-2 items-center overflow-x-auto ' +
|
|
||||||
className
|
|
||||||
}
|
|
||||||
style={{ scrollbarWidth: 'thin' }}
|
style={{ scrollbarWidth: 'thin' }}
|
||||||
>
|
>
|
||||||
{state.nextEvents.includes('Enter sketch') && (
|
{state.nextEvents.includes('Enter sketch') && (
|
||||||
@ -73,7 +69,7 @@ export const Toolbar = () => {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
send({ type: 'Enter sketch', data: { forceNewSketch: true } })
|
send({ type: 'Enter sketch', data: { forceNewSketch: true } })
|
||||||
}
|
}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'sketch',
|
icon: 'sketch',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -90,7 +86,7 @@ export const Toolbar = () => {
|
|||||||
className={buttonClassName}
|
className={buttonClassName}
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => send({ type: 'Enter sketch' })}
|
onClick={() => send({ type: 'Enter sketch' })}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'sketch',
|
icon: 'sketch',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -107,7 +103,7 @@ export const Toolbar = () => {
|
|||||||
className={buttonClassName}
|
className={buttonClassName}
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => send({ type: 'Cancel' })}
|
onClick={() => send({ type: 'Cancel' })}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'arrowLeft',
|
icon: 'arrowLeft',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -130,7 +126,7 @@ export const Toolbar = () => {
|
|||||||
: send('Equip Line tool')
|
: send('Equip Line tool')
|
||||||
}
|
}
|
||||||
aria-pressed={state?.matches('Sketch.Line tool')}
|
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'line',
|
icon: 'line',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -150,7 +146,7 @@ export const Toolbar = () => {
|
|||||||
: send('Equip tangential arc to')
|
: send('Equip tangential arc to')
|
||||||
}
|
}
|
||||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'arc',
|
icon: 'arc',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -174,7 +170,7 @@ export const Toolbar = () => {
|
|||||||
: send('Equip rectangle tool')
|
: send('Equip rectangle tool')
|
||||||
}
|
}
|
||||||
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'rectangle',
|
icon: 'rectangle',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -196,52 +192,54 @@ export const Toolbar = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{state.matches('Sketch.SketchIdle') &&
|
{state.matches('Sketch.SketchIdle') &&
|
||||||
state.nextEvents
|
state.nextEvents.filter(
|
||||||
.filter(
|
(eventName) =>
|
||||||
(eventName) =>
|
eventName.includes('Make segment') ||
|
||||||
eventName.includes('Make segment') ||
|
eventName.includes('Constrain')
|
||||||
eventName.includes('Constrain')
|
).length > 0 && (
|
||||||
)
|
<ActionButtonDropdown
|
||||||
.sort((a, b) => {
|
splitMenuItems={state.nextEvents
|
||||||
const aisEnabled = state.nextEvents
|
.filter(
|
||||||
.filter((event) => state.can(event as any))
|
(eventName) =>
|
||||||
.includes(a)
|
eventName.includes('Make segment') ||
|
||||||
const bIsEnabled = state.nextEvents
|
eventName.includes('Constrain')
|
||||||
.filter((event) => state.can(event as any))
|
)
|
||||||
.includes(b)
|
.sort((a, b) => {
|
||||||
if (aisEnabled && !bIsEnabled) {
|
const aisEnabled = state.nextEvents
|
||||||
return -1
|
.filter((event) => state.can(event as any))
|
||||||
}
|
.includes(a)
|
||||||
if (!aisEnabled && bIsEnabled) {
|
const bIsEnabled = state.nextEvents
|
||||||
return 1
|
.filter((event) => state.can(event as any))
|
||||||
}
|
.includes(b)
|
||||||
return 0
|
if (aisEnabled && !bIsEnabled) {
|
||||||
})
|
return -1
|
||||||
.map((eventName) => (
|
}
|
||||||
<li className="contents" key={eventName}>
|
if (!aisEnabled && bIsEnabled) {
|
||||||
<ActionButton
|
return 1
|
||||||
className={buttonClassName}
|
}
|
||||||
Element="button"
|
return 0
|
||||||
key={eventName}
|
})
|
||||||
onClick={() => send(eventName)}
|
.map((eventName) => ({
|
||||||
disabled={
|
label: eventName
|
||||||
|
.replace('Make segment ', '')
|
||||||
|
.replace('Constrain ', ''),
|
||||||
|
onClick: () => send(eventName),
|
||||||
|
disabled:
|
||||||
!state.nextEvents
|
!state.nextEvents
|
||||||
.filter((event) => state.can(event as any))
|
.filter((event) => state.can(event as any))
|
||||||
.includes(eventName) || disableAllButtons
|
.includes(eventName) || disableAllButtons,
|
||||||
}
|
}))}
|
||||||
title={eventName}
|
className={buttonClassName}
|
||||||
icon={{
|
Element="button"
|
||||||
icon: 'line',
|
iconStart={{
|
||||||
iconClassName,
|
icon: 'dimension',
|
||||||
bgClassName,
|
iconClassName,
|
||||||
}}
|
bgClassName,
|
||||||
>
|
}}
|
||||||
{eventName
|
>
|
||||||
.replace('Make segment ', '')
|
Constrain
|
||||||
.replace('Constrain ', '')}
|
</ActionButtonDropdown>
|
||||||
</ActionButton>
|
)}
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
{state.matches('idle') && (
|
{state.matches('idle') && (
|
||||||
<li className="contents">
|
<li className="contents">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
@ -259,7 +257,7 @@ export const Toolbar = () => {
|
|||||||
? 'extrude'
|
? 'extrude'
|
||||||
: 'sketches need to be closed, or not already extruded'
|
: 'sketches need to be closed, or not already extruded'
|
||||||
}
|
}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'extrude',
|
icon: 'extrude',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -274,17 +272,8 @@ export const Toolbar = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-full flex items-stretch rounded-l-sm rounded-r-full bg-chalkboard-10/80 dark:bg-chalkboard-110/70 relative">
|
<menu className="max-w-full whitespace-nowrap rounded px-1.5 py-0.5 backdrop-blur-sm bg-chalkboard-10/80 dark:bg-chalkboard-110/70 relative">
|
||||||
<menu className="flex-1 pl-1 pr-2 py-0 overflow-hidden rounded-l-sm whitespace-nowrap border-solid border border-primary/30 dark:border-chalkboard-90 border-r-0">
|
<ToolbarButtons />
|
||||||
<ToolbarButtons />
|
</menu>
|
||||||
</menu>
|
|
||||||
<ActionButton
|
|
||||||
Element="button"
|
|
||||||
onClick={() => commandBarSend({ type: 'Open' })}
|
|
||||||
className="rounded-r-full pr-4 self-stretch border-primary/30 hover:border-primary dark:border-chalkboard-80 dark:bg-chalkboard-80 text-primary"
|
|
||||||
>
|
|
||||||
{platform === 'macos' ? '⌘K' : 'Ctrl+/'}
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,8 @@ export const ClientSideScene = ({
|
|||||||
cursor = 'grabbing'
|
cursor = 'grabbing'
|
||||||
} else if (
|
} else if (
|
||||||
state.matches('Sketch.Line tool') ||
|
state.matches('Sketch.Line tool') ||
|
||||||
state.matches('Sketch.Tangential arc to')
|
state.matches('Sketch.Tangential arc to') ||
|
||||||
|
state.matches('Sketch.Rectangle tool')
|
||||||
) {
|
) {
|
||||||
cursor = 'crosshair'
|
cursor = 'crosshair'
|
||||||
} else {
|
} else {
|
||||||
@ -104,9 +105,9 @@ export const ClientSideScene = ({
|
|||||||
style={{ cursor: cursor }}
|
style={{ cursor: cursor }}
|
||||||
className={`absolute inset-0 h-full w-full transition-all duration-300 ${
|
className={`absolute inset-0 h-full w-full transition-all duration-300 ${
|
||||||
hideClient ? 'opacity-0' : 'opacity-100'
|
hideClient ? 'opacity-0' : 'opacity-100'
|
||||||
} ${hideServer ? 'bg-black' : ''} ${
|
} ${hideServer ? 'bg-chalkboard-10 dark:bg-chalkboard-100' : ''} ${
|
||||||
!hideClient && !hideServer && state.matches('Sketch')
|
!hideClient && !hideServer && state.matches('Sketch')
|
||||||
? 'bg-black/80'
|
? 'bg-chalkboard-10/80 dark:bg-chalkboard-100/80'
|
||||||
: ''
|
: ''
|
||||||
}`}
|
}`}
|
||||||
></div>
|
></div>
|
||||||
|
@ -97,6 +97,7 @@ import {
|
|||||||
getRectangleCallExpressions,
|
getRectangleCallExpressions,
|
||||||
updateRectangleSketch,
|
updateRectangleSketch,
|
||||||
} from 'lib/rectangleTool'
|
} from 'lib/rectangleTool'
|
||||||
|
import { getThemeColorForThreeJs } from 'lib/theme'
|
||||||
|
|
||||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||||
|
|
||||||
@ -356,6 +357,7 @@ export class SceneEntities {
|
|||||||
id: sketchGroup.start.__geoMeta.id,
|
id: sketchGroup.start.__geoMeta.id,
|
||||||
pathToNode: segPathToNode,
|
pathToNode: segPathToNode,
|
||||||
scale: factor,
|
scale: factor,
|
||||||
|
theme: sceneInfra._theme,
|
||||||
})
|
})
|
||||||
_profileStart.layers.set(SKETCH_LAYER)
|
_profileStart.layers.set(SKETCH_LAYER)
|
||||||
_profileStart.traverse((child) => {
|
_profileStart.traverse((child) => {
|
||||||
@ -406,6 +408,7 @@ export class SceneEntities {
|
|||||||
isDraftSegment,
|
isDraftSegment,
|
||||||
scale: factor,
|
scale: factor,
|
||||||
texture: sceneInfra.extraSegmentTexture,
|
texture: sceneInfra.extraSegmentTexture,
|
||||||
|
theme: sceneInfra._theme,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
seg = straightSegment({
|
seg = straightSegment({
|
||||||
@ -417,6 +420,7 @@ export class SceneEntities {
|
|||||||
scale: factor,
|
scale: factor,
|
||||||
callExpName,
|
callExpName,
|
||||||
texture: sceneInfra.extraSegmentTexture,
|
texture: sceneInfra.extraSegmentTexture,
|
||||||
|
theme: sceneInfra._theme,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
seg.layers.set(SKETCH_LAYER)
|
seg.layers.set(SKETCH_LAYER)
|
||||||
@ -1503,7 +1507,10 @@ export class SceneEntities {
|
|||||||
const isSelected = parent?.userData?.isSelected
|
const isSelected = parent?.userData?.isSelected
|
||||||
colorSegment(
|
colorSegment(
|
||||||
selected,
|
selected,
|
||||||
isSelected ? 0x0000ff : parent?.userData?.baseColor || 0xffffff
|
isSelected
|
||||||
|
? 0x0000ff
|
||||||
|
: parent?.userData?.baseColor ||
|
||||||
|
getThemeColorForThreeJs(sceneInfra._theme)
|
||||||
)
|
)
|
||||||
const extraSegmentGroup = parent?.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
const extraSegmentGroup = parent?.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||||
if (extraSegmentGroup) {
|
if (extraSegmentGroup) {
|
||||||
|
@ -30,6 +30,7 @@ import { CameraControls } from './CameraControls'
|
|||||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||||
import { settings } from 'lib/settings/initialSettings'
|
import { settings } from 'lib/settings/initialSettings'
|
||||||
import { MouseState } from 'machines/modelingMachine'
|
import { MouseState } from 'machines/modelingMachine'
|
||||||
|
import { Themes } from 'lib/theme'
|
||||||
|
|
||||||
type SendType = ReturnType<typeof useModelingContext>['send']
|
type SendType = ReturnType<typeof useModelingContext>['send']
|
||||||
|
|
||||||
@ -101,6 +102,7 @@ export class SceneInfra {
|
|||||||
isFovAnimationInProgress = false
|
isFovAnimationInProgress = false
|
||||||
_baseUnit: BaseUnit = 'mm'
|
_baseUnit: BaseUnit = 'mm'
|
||||||
_baseUnitMultiplier = 1
|
_baseUnitMultiplier = 1
|
||||||
|
_theme: Themes = Themes.System
|
||||||
extraSegmentTexture: Texture
|
extraSegmentTexture: Texture
|
||||||
lastMouseState: MouseState = { type: 'idle' }
|
lastMouseState: MouseState = { type: 'idle' }
|
||||||
onDragStartCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
onDragStartCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||||
@ -137,6 +139,9 @@ export class SceneInfra {
|
|||||||
this._baseUnitMultiplier
|
this._baseUnitMultiplier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
set theme(theme: Themes) {
|
||||||
|
this._theme = theme
|
||||||
|
}
|
||||||
resetMouseListeners = () => {
|
resetMouseListeners = () => {
|
||||||
this.setCallbacks({
|
this.setCallbacks({
|
||||||
onDragStart: () => {},
|
onDragStart: () => {},
|
||||||
|
@ -37,22 +37,26 @@ import {
|
|||||||
} from './sceneEntities'
|
} from './sceneEntities'
|
||||||
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
||||||
import { ARROWHEAD } from './sceneInfra'
|
import { ARROWHEAD } from './sceneInfra'
|
||||||
|
import { Themes, getThemeColorForThreeJs } from 'lib/theme'
|
||||||
|
|
||||||
export function profileStart({
|
export function profileStart({
|
||||||
from,
|
from,
|
||||||
id,
|
id,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
|
theme,
|
||||||
}: {
|
}: {
|
||||||
from: Coords2d
|
from: Coords2d
|
||||||
id: string
|
id: string
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
scale?: number
|
scale?: number
|
||||||
|
theme: Themes
|
||||||
}) {
|
}) {
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
|
|
||||||
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
|
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
|
||||||
const body = new MeshBasicMaterial({ color: 0xffffff })
|
const baseColor = getThemeColorForThreeJs(theme)
|
||||||
|
const body = new MeshBasicMaterial({ color: baseColor })
|
||||||
const mesh = new Mesh(geometry, body)
|
const mesh = new Mesh(geometry, body)
|
||||||
|
|
||||||
group.add(mesh)
|
group.add(mesh)
|
||||||
@ -79,6 +83,7 @@ export function straightSegment({
|
|||||||
scale = 1,
|
scale = 1,
|
||||||
callExpName,
|
callExpName,
|
||||||
texture,
|
texture,
|
||||||
|
theme,
|
||||||
}: {
|
}: {
|
||||||
from: Coords2d
|
from: Coords2d
|
||||||
to: Coords2d
|
to: Coords2d
|
||||||
@ -88,6 +93,7 @@ export function straightSegment({
|
|||||||
scale?: number
|
scale?: number
|
||||||
callExpName: string
|
callExpName: string
|
||||||
texture: Texture
|
texture: Texture
|
||||||
|
theme: Themes
|
||||||
}): Group {
|
}): Group {
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
|
|
||||||
@ -111,7 +117,8 @@ export function straightSegment({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseColor = callExpName === 'close' ? 0x444444 : 0xffffff
|
const baseColor =
|
||||||
|
callExpName === 'close' ? 0x444444 : getThemeColorForThreeJs(theme)
|
||||||
const body = new MeshBasicMaterial({ color: baseColor })
|
const body = new MeshBasicMaterial({ color: baseColor })
|
||||||
const mesh = new Mesh(geometry, body)
|
const mesh = new Mesh(geometry, body)
|
||||||
mesh.userData.type = isDraftSegment
|
mesh.userData.type = isDraftSegment
|
||||||
@ -134,7 +141,7 @@ export function straightSegment({
|
|||||||
const length = Math.sqrt(
|
const length = Math.sqrt(
|
||||||
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
|
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
|
||||||
)
|
)
|
||||||
const arrowGroup = createArrowhead(scale)
|
const arrowGroup = createArrowhead(scale, theme)
|
||||||
arrowGroup.position.set(to[0], to[1], 0)
|
arrowGroup.position.set(to[0], to[1], 0)
|
||||||
const dir = new Vector3()
|
const dir = new Vector3()
|
||||||
.subVectors(new Vector3(to[0], to[1], 0), new Vector3(from[0], from[1], 0))
|
.subVectors(new Vector3(to[0], to[1], 0), new Vector3(from[0], from[1], 0))
|
||||||
@ -147,7 +154,7 @@ export function straightSegment({
|
|||||||
group.add(mesh)
|
group.add(mesh)
|
||||||
if (callExpName !== 'close') group.add(arrowGroup)
|
if (callExpName !== 'close') group.add(arrowGroup)
|
||||||
|
|
||||||
const extraSegmentGroup = createExtraSegmentHandle(scale, texture)
|
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
|
||||||
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
|
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
|
||||||
.normalize()
|
.normalize()
|
||||||
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
|
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
|
||||||
@ -162,8 +169,10 @@ export function straightSegment({
|
|||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
|
|
||||||
function createArrowhead(scale = 1): Group {
|
function createArrowhead(scale = 1, theme: Themes): Group {
|
||||||
const arrowMaterial = new MeshBasicMaterial({ color: 0xffffff })
|
const arrowMaterial = new MeshBasicMaterial({
|
||||||
|
color: getThemeColorForThreeJs(theme),
|
||||||
|
})
|
||||||
// specify the size of the geometry in pixels (i.e. cone height = 20px, cone radius = 4.5px)
|
// specify the size of the geometry in pixels (i.e. cone height = 20px, cone radius = 4.5px)
|
||||||
// we'll scale the group to the correct size later to match these sizes in screen space
|
// we'll scale the group to the correct size later to match these sizes in screen space
|
||||||
const arrowheadMesh = new Mesh(new ConeGeometry(4.5, 20, 12), arrowMaterial)
|
const arrowheadMesh = new Mesh(new ConeGeometry(4.5, 20, 12), arrowMaterial)
|
||||||
@ -179,7 +188,11 @@ function createArrowhead(scale = 1): Group {
|
|||||||
return arrowGroup
|
return arrowGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
function createExtraSegmentHandle(scale: number, texture: Texture): Group {
|
function createExtraSegmentHandle(
|
||||||
|
scale: number,
|
||||||
|
texture: Texture,
|
||||||
|
theme: Themes
|
||||||
|
): Group {
|
||||||
const particleMaterial = new PointsMaterial({
|
const particleMaterial = new PointsMaterial({
|
||||||
size: 12, // in pixels
|
size: 12, // in pixels
|
||||||
map: texture,
|
map: texture,
|
||||||
@ -189,7 +202,7 @@ function createExtraSegmentHandle(scale: number, texture: Texture): Group {
|
|||||||
})
|
})
|
||||||
const mat = new MeshBasicMaterial({
|
const mat = new MeshBasicMaterial({
|
||||||
transparent: true,
|
transparent: true,
|
||||||
color: 0xffffff,
|
color: getThemeColorForThreeJs(theme),
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
})
|
})
|
||||||
const particleGeometry = new BufferGeometry().setFromPoints([
|
const particleGeometry = new BufferGeometry().setFromPoints([
|
||||||
@ -218,6 +231,7 @@ export function tangentialArcToSegment({
|
|||||||
isDraftSegment,
|
isDraftSegment,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
texture,
|
texture,
|
||||||
|
theme,
|
||||||
}: {
|
}: {
|
||||||
prevSegment: SketchGroup['value'][number]
|
prevSegment: SketchGroup['value'][number]
|
||||||
from: Coords2d
|
from: Coords2d
|
||||||
@ -227,6 +241,7 @@ export function tangentialArcToSegment({
|
|||||||
isDraftSegment?: boolean
|
isDraftSegment?: boolean
|
||||||
scale?: number
|
scale?: number
|
||||||
texture: Texture
|
texture: Texture
|
||||||
|
theme: Themes
|
||||||
}): Group {
|
}): Group {
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
|
|
||||||
@ -257,7 +272,8 @@ export function tangentialArcToSegment({
|
|||||||
scale,
|
scale,
|
||||||
})
|
})
|
||||||
|
|
||||||
const body = new MeshBasicMaterial({ color: 0xffffff })
|
const baseColor = getThemeColorForThreeJs(theme)
|
||||||
|
const body = new MeshBasicMaterial({ color: baseColor })
|
||||||
const mesh = new Mesh(geometry, body)
|
const mesh = new Mesh(geometry, body)
|
||||||
mesh.userData.type = isDraftSegment
|
mesh.userData.type = isDraftSegment
|
||||||
? TANGENTIAL_ARC_TO__SEGMENT_DASH
|
? TANGENTIAL_ARC_TO__SEGMENT_DASH
|
||||||
@ -271,10 +287,11 @@ export function tangentialArcToSegment({
|
|||||||
prevSegment,
|
prevSegment,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
|
baseColor,
|
||||||
}
|
}
|
||||||
group.name = TANGENTIAL_ARC_TO_SEGMENT
|
group.name = TANGENTIAL_ARC_TO_SEGMENT
|
||||||
|
|
||||||
const arrowGroup = createArrowhead(scale)
|
const arrowGroup = createArrowhead(scale, theme)
|
||||||
arrowGroup.position.set(to[0], to[1], 0)
|
arrowGroup.position.set(to[0], to[1], 0)
|
||||||
const arrowheadAngle = endAngle + (Math.PI / 2) * (ccw ? 1 : -1)
|
const arrowheadAngle = endAngle + (Math.PI / 2) * (ccw ? 1 : -1)
|
||||||
arrowGroup.quaternion.setFromUnitVectors(
|
arrowGroup.quaternion.setFromUnitVectors(
|
||||||
@ -285,7 +302,7 @@ export function tangentialArcToSegment({
|
|||||||
const shouldHide = pxLength < HIDE_SEGMENT_LENGTH
|
const shouldHide = pxLength < HIDE_SEGMENT_LENGTH
|
||||||
arrowGroup.visible = !shouldHide
|
arrowGroup.visible = !shouldHide
|
||||||
|
|
||||||
const extraSegmentGroup = createExtraSegmentHandle(scale, texture)
|
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
|
||||||
const circumferenceInPx = (2 * Math.PI * radius) / scale
|
const circumferenceInPx = (2 * Math.PI * radius) / scale
|
||||||
const extraSegmentAngleDelta =
|
const extraSegmentAngleDelta =
|
||||||
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2
|
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
||||||
import React from 'react'
|
import React, { ForwardedRef, forwardRef } from 'react'
|
||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import type { LinkProps } from 'react-router-dom'
|
import type { LinkProps } from 'react-router-dom'
|
||||||
|
|
||||||
interface BaseActionButtonProps {
|
interface BaseActionButtonProps {
|
||||||
icon?: ActionIconProps
|
iconStart?: ActionIconProps
|
||||||
|
iconEnd?: ActionIconProps
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,15 +33,15 @@ type ActionButtonAsElement = BaseActionButtonProps &
|
|||||||
Element: React.ComponentType<React.HTMLAttributes<HTMLButtonElement>>
|
Element: React.ComponentType<React.HTMLAttributes<HTMLButtonElement>>
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionButtonProps =
|
export type ActionButtonProps =
|
||||||
| ActionButtonAsButton
|
| ActionButtonAsButton
|
||||||
| ActionButtonAsLink
|
| ActionButtonAsLink
|
||||||
| ActionButtonAsExternal
|
| ActionButtonAsExternal
|
||||||
| ActionButtonAsElement
|
| ActionButtonAsElement
|
||||||
|
|
||||||
export const ActionButton = (props: ActionButtonProps) => {
|
export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
|
||||||
const classNames = `action-button m-0 group mono text-sm flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 p-[3px] text-chalkboard-100 dark:text-chalkboard-10 ${
|
const classNames = `action-button p-0 m-0 group mono text-xs leading-none flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 enabled:dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 text-chalkboard-100 dark:text-chalkboard-10 ${
|
||||||
props.icon ? 'pr-2' : 'px-2'
|
props.iconStart ? (props.iconEnd ? 'px-0' : 'pr-2') : 'px-2'
|
||||||
} ${props.className ? props.className : ''}`
|
} ${props.className ? props.className : ''}`
|
||||||
|
|
||||||
switch (props.Element) {
|
switch (props.Element) {
|
||||||
@ -48,11 +49,23 @@ export const ActionButton = (props: ActionButtonProps) => {
|
|||||||
// Note we have to destructure 'className' and 'Element' out of props
|
// Note we have to destructure 'className' and 'Element' out of props
|
||||||
// because we don't want to pass them to the button element;
|
// because we don't want to pass them to the button element;
|
||||||
// the same is true for the other cases below.
|
// the same is true for the other cases below.
|
||||||
const { Element, icon, children, className: _className, ...rest } = props
|
const {
|
||||||
|
Element,
|
||||||
|
iconStart,
|
||||||
|
iconEnd,
|
||||||
|
children,
|
||||||
|
className: _className,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
return (
|
return (
|
||||||
<button className={classNames} {...rest}>
|
<button
|
||||||
{props.icon && <ActionIcon {...icon} />}
|
ref={ref as ForwardedRef<HTMLButtonElement>}
|
||||||
|
className={classNames}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{iconStart && <ActionIcon {...iconStart} />}
|
||||||
{children}
|
{children}
|
||||||
|
{iconEnd && <ActionIcon {...iconEnd} />}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -60,15 +73,22 @@ export const ActionButton = (props: ActionButtonProps) => {
|
|||||||
const {
|
const {
|
||||||
Element,
|
Element,
|
||||||
to,
|
to,
|
||||||
icon,
|
iconStart,
|
||||||
|
iconEnd,
|
||||||
children,
|
children,
|
||||||
className: _className,
|
className: _className,
|
||||||
...rest
|
...rest
|
||||||
} = props
|
} = props
|
||||||
return (
|
return (
|
||||||
<Link to={to || paths.INDEX} className={classNames} {...rest}>
|
<Link
|
||||||
{icon && <ActionIcon {...icon} />}
|
ref={ref as ForwardedRef<HTMLAnchorElement>}
|
||||||
|
to={to || paths.INDEX}
|
||||||
|
className={classNames}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{iconStart && <ActionIcon {...iconStart} />}
|
||||||
{children}
|
{children}
|
||||||
|
{iconEnd && <ActionIcon {...iconEnd} />}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -76,33 +96,42 @@ export const ActionButton = (props: ActionButtonProps) => {
|
|||||||
const {
|
const {
|
||||||
Element,
|
Element,
|
||||||
to,
|
to,
|
||||||
icon,
|
iconStart,
|
||||||
|
iconEnd,
|
||||||
children,
|
children,
|
||||||
className: _className,
|
className: _className,
|
||||||
...rest
|
...rest
|
||||||
} = props
|
} = props
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
ref={ref as ForwardedRef<HTMLAnchorElement>}
|
||||||
to={to || paths.INDEX}
|
to={to || paths.INDEX}
|
||||||
className={classNames}
|
className={classNames}
|
||||||
{...rest}
|
{...rest}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{icon && <ActionIcon {...icon} />}
|
{iconStart && <ActionIcon {...iconStart} />}
|
||||||
{children}
|
{children}
|
||||||
|
{iconEnd && <ActionIcon {...iconEnd} />}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
const { Element, icon, children, className: _className, ...rest } = props
|
const {
|
||||||
if (!Element) throw new Error('Element is required')
|
Element,
|
||||||
|
iconStart,
|
||||||
|
children,
|
||||||
|
className: _className,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Element className={classNames} {...rest}>
|
<Element className={classNames} {...rest}>
|
||||||
{props.icon && <ActionIcon {...props.icon} />}
|
{props.iconStart && <ActionIcon {...props.iconStart} />}
|
||||||
{children}
|
{children}
|
||||||
|
{props.iconEnd && <ActionIcon {...props.iconEnd} />}
|
||||||
</Element>
|
</Element>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
55
src/components/ActionButtonDropdown.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { Popover } from '@headlessui/react'
|
||||||
|
import { ActionButton, ActionButtonProps } from './ActionButton'
|
||||||
|
|
||||||
|
type ActionButtonSplitProps = Omit<ActionButtonProps, 'iconEnd'> & {
|
||||||
|
splitMenuItems: {
|
||||||
|
label: string
|
||||||
|
shortcut?: string
|
||||||
|
onClick: () => void
|
||||||
|
disabled?: boolean
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ActionButtonDropdown({
|
||||||
|
splitMenuItems,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: ActionButtonSplitProps) {
|
||||||
|
return (
|
||||||
|
<Popover className="relative">
|
||||||
|
<Popover.Button
|
||||||
|
as={ActionButton}
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
Element="button"
|
||||||
|
iconEnd={{
|
||||||
|
icon: 'caretDown',
|
||||||
|
className: 'ui-open:rotate-180',
|
||||||
|
bgClassName:
|
||||||
|
'bg-chalkboard-20 dark:bg-chalkboard-80 ui-open:bg-primary ui-open:text-chalkboard-10',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Popover.Panel
|
||||||
|
as="ul"
|
||||||
|
className="absolute z-20 left-1/2 -translate-x-1/2 top-full mt-1 w-fit max-h-[80vh] overflow-y-auto py-2 flex flex-col gap-1 align-stretch text-inherit dark:text-chalkboard-10 bg-chalkboard-10 dark:bg-chalkboard-100 rounded shadow-lg border border-solid border-chalkboard-30 dark:border-chalkboard-80 text-sm m-0 p-0"
|
||||||
|
>
|
||||||
|
{splitMenuItems.map((item) => (
|
||||||
|
<li className="contents" key={item.label}>
|
||||||
|
<button
|
||||||
|
onClick={item.onClick}
|
||||||
|
className="block px-3 py-1 hover:bg-primary/10 dark:hover:bg-chalkboard-80 border-0 m-0 text-sm w-full rounded-none text-left disabled:!bg-transparent dark:disabled:text-chalkboard-60"
|
||||||
|
disabled={item.disabled}
|
||||||
|
>
|
||||||
|
<span className="capitalize">{item.label}</span>
|
||||||
|
{item.shortcut && (
|
||||||
|
<kbd className="bg-primary/10 dark:bg-chalkboard-80 dark:group-hover:bg-primary font-mono rounded-sm dark:text-inherit inline-block px-1 border-primary dark:border-chalkboard-90">
|
||||||
|
{item.shortcut}
|
||||||
|
</kbd>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</Popover.Panel>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
@ -29,10 +29,8 @@ export const ActionIcon = ({
|
|||||||
size = 'md',
|
size = 'md',
|
||||||
children,
|
children,
|
||||||
}: ActionIconProps) => {
|
}: ActionIconProps) => {
|
||||||
// By default, we reverse the icon color and background color in dark mode
|
const computedIconClassName = `h-auto text-inherit dark:text-current !group-disabled:text-chalkboard-60 !group-disabled:text-chalkboard-60 ${iconClassName}`
|
||||||
const computedIconClassName = `h-auto text-primary dark:text-current !group-disabled:text-chalkboard-60 !group-disabled:text-chalkboard-60 ${iconClassName}`
|
const computedBgClassName = `bg-chalkboard-20 dark:bg-chalkboard-80 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
|
||||||
|
|
||||||
const computedBgClassName = `bg-primary/10 dark:bg-chalkboard-90 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -4,10 +4,8 @@ import { type IndexLoaderData } from 'lib/types'
|
|||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import styles from './AppHeader.module.css'
|
import styles from './AppHeader.module.css'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { ActionButton } from 'components/ActionButton'
|
|
||||||
import usePlatform from 'hooks/usePlatform'
|
|
||||||
import { RefreshButton } from 'components/RefreshButton'
|
import { RefreshButton } from 'components/RefreshButton'
|
||||||
|
import { CommandBarOpenButton } from './CommandBarOpenButton'
|
||||||
|
|
||||||
interface AppHeaderProps extends React.PropsWithChildren {
|
interface AppHeaderProps extends React.PropsWithChildren {
|
||||||
showToolbar?: boolean
|
showToolbar?: boolean
|
||||||
@ -23,8 +21,6 @@ export const AppHeader = ({
|
|||||||
className = '',
|
className = '',
|
||||||
enableMenu = false,
|
enableMenu = false,
|
||||||
}: AppHeaderProps) => {
|
}: AppHeaderProps) => {
|
||||||
const platform = usePlatform()
|
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
const { auth } = useSettingsAuthContext()
|
const { auth } = useSettingsAuthContext()
|
||||||
const user = auth?.context?.user
|
const user = auth?.context?.user
|
||||||
|
|
||||||
@ -44,25 +40,13 @@ export const AppHeader = ({
|
|||||||
/>
|
/>
|
||||||
{/* Toolbar if the context deems it */}
|
{/* Toolbar if the context deems it */}
|
||||||
<div className="flex-grow flex justify-center max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
|
<div className="flex-grow flex justify-center max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
|
||||||
{showToolbar ? (
|
{showToolbar && <Toolbar />}
|
||||||
<Toolbar />
|
|
||||||
) : (
|
|
||||||
<ActionButton
|
|
||||||
Element="button"
|
|
||||||
onClick={() => commandBarSend({ type: 'Open' })}
|
|
||||||
className="text-sm self-center flex items-center w-fit gap-3"
|
|
||||||
>
|
|
||||||
Command Palette{' '}
|
|
||||||
<kbd className="bg-primary/10 dark:bg-chalkboard-100 dark:text-primary inline-block px-1 py-0.5 border-primary dark:border-chalkboard-90">
|
|
||||||
{platform === 'macos' ? '⌘K' : 'Ctrl+/'}
|
|
||||||
</kbd>
|
|
||||||
</ActionButton>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 py-1 ml-auto">
|
<div className="flex items-center gap-1 py-1 ml-auto">
|
||||||
{/* If there are children, show them, otherwise show User menu */}
|
{/* If there are children, show them, otherwise show User menu */}
|
||||||
{children || (
|
{children || (
|
||||||
<>
|
<>
|
||||||
|
<CommandBarOpenButton />
|
||||||
<RefreshButton />
|
<RefreshButton />
|
||||||
<UserSidebarMenu user={user} />
|
<UserSidebarMenu user={user} />
|
||||||
</>
|
</>
|
||||||
|
@ -175,7 +175,7 @@ function ReviewingButton() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
form="review-form"
|
form="review-form"
|
||||||
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
|
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'checkmark',
|
icon: 'checkmark',
|
||||||
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
||||||
iconClassName: '!text-chalkboard-10',
|
iconClassName: '!text-chalkboard-10',
|
||||||
@ -193,7 +193,7 @@ function GatheringArgsButton() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
form="arg-form"
|
form="arg-form"
|
||||||
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
|
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'arrowRight',
|
icon: 'arrowRight',
|
||||||
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
||||||
iconClassName: '!text-chalkboard-10',
|
iconClassName: '!text-chalkboard-10',
|
||||||
|
19
src/components/CommandBarOpenButton.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
|
import usePlatform from 'hooks/usePlatform'
|
||||||
|
|
||||||
|
export function CommandBarOpenButton() {
|
||||||
|
const { commandBarSend } = useCommandsContext()
|
||||||
|
const platform = usePlatform()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="group rounded-full flex items-center justify-center gap-2 px-2 py-1 bg-primary/10 dark:bg-chalkboard-90 dark:backdrop-blur-sm border-primary hover:border-primary dark:border-chalkboard-50 dark:hover:border-inherit text-primary dark:text-inherit"
|
||||||
|
onClick={() => commandBarSend({ type: 'Open' })}
|
||||||
|
>
|
||||||
|
<span>Commands</span>
|
||||||
|
<kbd className="bg-primary/10 dark:bg-chalkboard-80 dark:group-hover:bg-primary font-mono rounded-sm dark:text-inherit inline-block px-1 border-primary dark:border-chalkboard-90">
|
||||||
|
{platform === 'macos' ? '⌘K' : '^/'}
|
||||||
|
</kbd>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
@ -38,7 +38,7 @@ function CommandComboBox({
|
|||||||
<div className="flex items-center gap-2 px-4 pb-2 border-solid border-0 border-b border-b-chalkboard-20 dark:border-b-chalkboard-80">
|
<div className="flex items-center gap-2 px-4 pb-2 border-solid border-0 border-b border-b-chalkboard-20 dark:border-b-chalkboard-80">
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
name="search"
|
name="search"
|
||||||
className="w-5 h-5 bg-primary/10 text-primary"
|
className="w-5 h-5 bg-primary/10 dark:bg-primary text-primary dark:text-inherit"
|
||||||
/>
|
/>
|
||||||
<Combobox.Input
|
<Combobox.Input
|
||||||
onChange={(event) => setQuery(event.target.value)}
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
|
@ -71,6 +71,16 @@ const CustomIconMap = {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
|
caretDown: (
|
||||||
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M10 11.2929L6.35346 7.64642L5.64636 8.35354L9.64648 12.3536L10.3536 12.3536L14.3535 8.35353L13.6464 7.64643L10 11.2929Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
clipboardCheckmark: (
|
clipboardCheckmark: (
|
||||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
@ -101,6 +111,16 @@ const CustomIconMap = {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
|
dimension: (
|
||||||
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M15.6455 3.6455L14 2V5.291L5.291 14H2L6 18V14.7052L14.7052 6H18L16.3526 4.35261L16.3546 4.35065L15.6475 3.64354L15.6455 3.6455Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
equal: (
|
equal: (
|
||||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
|
@ -25,7 +25,7 @@ const DownloadAppBanner = () => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => setIsBannerDismissed(true)}
|
onClick={() => setIsBannerDismissed(true)}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'close',
|
icon: 'close',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
bgClassName:
|
bgClassName:
|
||||||
|
@ -29,7 +29,7 @@ export const ErrorPage = () => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="link"
|
Element="link"
|
||||||
to={'/'}
|
to={'/'}
|
||||||
icon={{ icon: faHome }}
|
iconStart={{ icon: faHome }}
|
||||||
data-testid="unexpected-error-home"
|
data-testid="unexpected-error-home"
|
||||||
>
|
>
|
||||||
Go Home
|
Go Home
|
||||||
@ -37,14 +37,14 @@ export const ErrorPage = () => {
|
|||||||
)}
|
)}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{ icon: faRefresh }}
|
iconStart={{ icon: faRefresh }}
|
||||||
onClick={() => window.location.reload()}
|
onClick={() => window.location.reload()}
|
||||||
>
|
>
|
||||||
Reload
|
Reload
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{ icon: faTrash }}
|
iconStart={{ icon: faTrash }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.localStorage.clear()
|
window.localStorage.clear()
|
||||||
}}
|
}}
|
||||||
@ -53,7 +53,7 @@ export const ErrorPage = () => {
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="externalLink"
|
Element="externalLink"
|
||||||
icon={{ icon: faBug }}
|
iconStart={{ icon: faBug }}
|
||||||
to="https://github.com/KittyCAD/modeling-app/issues/new"
|
to="https://github.com/KittyCAD/modeling-app/issues/new"
|
||||||
>
|
>
|
||||||
Report Bug
|
Report Bug
|
||||||
|
@ -109,7 +109,7 @@ function DeleteConfirmationDialog({
|
|||||||
send({ type: 'Delete file', data: fileOrDir })
|
send({ type: 'Delete file', data: fileOrDir })
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
}}
|
}}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faTrashAlt,
|
icon: faTrashAlt,
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
iconClassName:
|
iconClassName:
|
||||||
@ -340,7 +340,7 @@ export const FileTreeMenu = () => {
|
|||||||
<>
|
<>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'filePlus',
|
icon: 'filePlus',
|
||||||
iconClassName: '!text-current',
|
iconClassName: '!text-current',
|
||||||
bgClassName: 'bg-transparent',
|
bgClassName: 'bg-transparent',
|
||||||
@ -355,7 +355,7 @@ export const FileTreeMenu = () => {
|
|||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'folderPlus',
|
icon: 'folderPlus',
|
||||||
iconClassName: '!text-current',
|
iconClassName: '!text-current',
|
||||||
bgClassName: 'bg-transparent',
|
bgClassName: 'bg-transparent',
|
||||||
|
@ -85,7 +85,7 @@ function ProjectCard({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
type="submit"
|
type="submit"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faCheck,
|
icon: faCheck,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
@ -99,7 +99,7 @@ function ProjectCard({
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faX,
|
icon: faX,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
iconClassName: 'dark:!text-chalkboard-20',
|
iconClassName: 'dark:!text-chalkboard-20',
|
||||||
@ -141,7 +141,7 @@ function ProjectCard({
|
|||||||
<div className="absolute z-10 bottom-2 right-2 flex gap-1 items-center opacity-0 group-hover:opacity-100 group-focus-within:opacity-100">
|
<div className="absolute z-10 bottom-2 right-2 flex gap-1 items-center opacity-0 group-hover:opacity-100 group-focus-within:opacity-100">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faPenAlt,
|
icon: faPenAlt,
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
iconClassName: 'dark:!text-chalkboard-20',
|
iconClassName: 'dark:!text-chalkboard-20',
|
||||||
@ -161,7 +161,7 @@ function ProjectCard({
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faTrashAlt,
|
icon: faTrashAlt,
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
size: 'xs',
|
size: 'xs',
|
||||||
@ -207,7 +207,7 @@ function ProjectCard({
|
|||||||
await handleDeleteProject(project)
|
await handleDeleteProject(project)
|
||||||
setIsConfirmingDelete(false)
|
setIsConfirmingDelete(false)
|
||||||
}}
|
}}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faTrashAlt,
|
icon: faTrashAlt,
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
|
@ -165,7 +165,7 @@ function ProjectMenuPopover({
|
|||||||
<div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90">
|
<div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{ icon: 'exportFile', className: 'p-1' }}
|
iconStart={{ icon: 'exportFile', className: 'p-1' }}
|
||||||
className="border-transparent dark:border-transparent"
|
className="border-transparent dark:border-transparent"
|
||||||
disabled={!findCommand(exportCommandInfo)}
|
disabled={!findCommand(exportCommandInfo)}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -185,7 +185,7 @@ function ProjectMenuPopover({
|
|||||||
// Clear the scene and end the session.
|
// Clear the scene and end the session.
|
||||||
engineCommandManager.endSession()
|
engineCommandManager.endSession()
|
||||||
}}
|
}}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'arrowLeft',
|
icon: 'arrowLeft',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
}}
|
}}
|
||||||
|
@ -78,7 +78,7 @@ export const SetVarNameModal = ({
|
|||||||
Element="button"
|
Element="button"
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!isNewVariableNameUnique}
|
disabled={!isNewVariableNameUnique}
|
||||||
icon={{ icon: faPlus }}
|
iconStart={{ icon: faPlus }}
|
||||||
>
|
>
|
||||||
Add variable
|
Add variable
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
@ -134,6 +134,10 @@ export const SettingsAuthProviderBase = ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
setClientTheme: (context) => {
|
||||||
|
const opposingTheme = getOppositeTheme(context.app.theme.current)
|
||||||
|
sceneInfra.theme = opposingTheme
|
||||||
|
},
|
||||||
setEngineEdges: (context) => {
|
setEngineEdges: (context) => {
|
||||||
engineCommandManager.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
|
@ -59,7 +59,7 @@ export const UpdaterModal = ({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => onResolve({ wantUpdate: false })}
|
onClick={() => onResolve({ wantUpdate: false })}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'close',
|
icon: 'close',
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
||||||
@ -72,7 +72,10 @@ export const UpdaterModal = ({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => onResolve({ wantUpdate: true })}
|
onClick={() => onResolve({ wantUpdate: true })}
|
||||||
icon={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
|
iconStart={{
|
||||||
|
icon: 'arrowRight',
|
||||||
|
bgClassName: 'dark:bg-chalkboard-80',
|
||||||
|
}}
|
||||||
className="dark:hover:bg-chalkboard-80/50"
|
className="dark:hover:bg-chalkboard-80/50"
|
||||||
data-testid="update-button-update"
|
data-testid="update-button-update"
|
||||||
>
|
>
|
||||||
|
@ -31,7 +31,7 @@ export const UpdaterRestartModal = ({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => onResolve({ wantRestart: false })}
|
onClick={() => onResolve({ wantRestart: false })}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'close',
|
icon: 'close',
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
||||||
@ -44,7 +44,10 @@ export const UpdaterRestartModal = ({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => onResolve({ wantRestart: true })}
|
onClick={() => onResolve({ wantRestart: true })}
|
||||||
icon={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
|
iconStart={{
|
||||||
|
icon: 'arrowRight',
|
||||||
|
bgClassName: 'dark:bg-chalkboard-80',
|
||||||
|
}}
|
||||||
className="dark:hover:bg-chalkboard-80/50"
|
className="dark:hover:bg-chalkboard-80/50"
|
||||||
data-testid="update-restrart-button-update"
|
data-testid="update-restrart-button-update"
|
||||||
>
|
>
|
||||||
|
@ -55,7 +55,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
) : (
|
) : (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element={Popover.Button}
|
Element={Popover.Button}
|
||||||
icon={{ icon: 'menu' }}
|
iconStart={{ icon: 'menu' }}
|
||||||
className="border-transparent !px-0"
|
className="border-transparent !px-0"
|
||||||
data-testid="user-sidebar-toggle"
|
data-testid="user-sidebar-toggle"
|
||||||
>
|
>
|
||||||
@ -120,7 +120,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
<div className="p-4 flex flex-col gap-2">
|
<div className="p-4 flex flex-col gap-2">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{ icon: 'settings' }}
|
iconStart={{ icon: 'settings' }}
|
||||||
className="border-transparent dark:border-transparent hover:bg-transparent"
|
className="border-transparent dark:border-transparent hover:bg-transparent"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// since /settings is a nested route the sidebar doesn't close
|
// since /settings is a nested route the sidebar doesn't close
|
||||||
@ -138,7 +138,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="externalLink"
|
Element="externalLink"
|
||||||
to="https://github.com/KittyCAD/modeling-app/discussions"
|
to="https://github.com/KittyCAD/modeling-app/discussions"
|
||||||
icon={{ icon: faGithub, className: 'p-1', size: 'sm' }}
|
iconStart={{ icon: faGithub, className: 'p-1', size: 'sm' }}
|
||||||
className="border-transparent dark:border-transparent"
|
className="border-transparent dark:border-transparent"
|
||||||
>
|
>
|
||||||
Request a feature
|
Request a feature
|
||||||
@ -146,7 +146,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="externalLink"
|
Element="externalLink"
|
||||||
to="https://github.com/KittyCAD/modeling-app/issues/new/choose"
|
to="https://github.com/KittyCAD/modeling-app/issues/new/choose"
|
||||||
icon={{ icon: faBug, className: 'p-1', size: 'sm' }}
|
iconStart={{ icon: faBug, className: 'p-1', size: 'sm' }}
|
||||||
className="border-transparent dark:border-transparent"
|
className="border-transparent dark:border-transparent"
|
||||||
>
|
>
|
||||||
Report a bug
|
Report a bug
|
||||||
@ -154,7 +154,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => send('Log out')}
|
onClick={() => send('Log out')}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faSignOutAlt,
|
icon: faSignOutAlt,
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
bgClassName: '!bg-transparent',
|
bgClassName: '!bg-transparent',
|
||||||
|
@ -24,7 +24,7 @@ export function WasmErrBanner() {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => setBannerDismissed(true)}
|
onClick={() => setBannerDismissed(true)}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'close',
|
icon: 'close',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
bgClassName:
|
bgClassName:
|
||||||
|
@ -264,8 +264,12 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
this.sendChange({
|
this.client.textDocumentDidChange({
|
||||||
documentText: this.view.state.doc.toString(),
|
textDocument: {
|
||||||
|
uri: this.documentUri,
|
||||||
|
version: this.documentVersion++,
|
||||||
|
},
|
||||||
|
contentChanges: [{ text: this.view.state.doc.toString() }],
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await this.client.textDocumentFormatting({
|
const result = await this.client.textDocumentFormatting({
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
import useResizeObserver from '@react-hook/resize-observer'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
interface Rect {
|
|
||||||
top: number
|
|
||||||
left: number
|
|
||||||
height: number
|
|
||||||
width: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes an element id and uses React refs to create a CSS clip-path rule to apply to a backdrop element
|
|
||||||
* which excludes the element with the given id, creating a "highlight" effect.
|
|
||||||
* @param highlightId
|
|
||||||
*/
|
|
||||||
export function useBackdropHighlight(target: string): string {
|
|
||||||
const [clipPath, setClipPath] = useState('')
|
|
||||||
const [elem, setElem] = useState(document.getElementById(target))
|
|
||||||
|
|
||||||
// Build the actual clip path string, cutting out the target element
|
|
||||||
function buildClipPath({ top, left, height, width }: Rect) {
|
|
||||||
const windowWidth = window.innerWidth
|
|
||||||
const windowHeight = window.innerHeight
|
|
||||||
|
|
||||||
return `
|
|
||||||
path(evenodd, "M0 0 l${windowWidth} 0 l0 ${windowHeight} l-${windowWidth} 0 Z \
|
|
||||||
M${left} ${top} l${width} 0 l0 ${height} l-${width} 0 Z")
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
// initial setup of clip path
|
|
||||||
useEffect(() => {
|
|
||||||
if (!elem) {
|
|
||||||
const newElem = document.getElementById(target)
|
|
||||||
if (newElem === null) {
|
|
||||||
throw new Error(
|
|
||||||
`Could not find element with id "${target}" to highlight`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
setElem(document.getElementById(target))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { top, left, height, width } = elem.getBoundingClientRect()
|
|
||||||
setClipPath(buildClipPath({ top, left, height, width }))
|
|
||||||
}, [elem, target])
|
|
||||||
|
|
||||||
// update clip path on resize
|
|
||||||
useResizeObserver(elem, (entry) => {
|
|
||||||
const { height, width } = entry.contentRect
|
|
||||||
// the top and left are relative to the viewport, so we need to get the target's position
|
|
||||||
const { top, left } = entry.target.getBoundingClientRect()
|
|
||||||
setClipPath(buildClipPath({ top, left, height, width }))
|
|
||||||
})
|
|
||||||
|
|
||||||
return clipPath
|
|
||||||
}
|
|
@ -14,8 +14,8 @@ export function useEngineConnectionSubscriptions() {
|
|||||||
event: 'highlight_set_entity',
|
event: 'highlight_set_entity',
|
||||||
callback: ({ data }) => {
|
callback: ({ data }) => {
|
||||||
if (data?.entity_id) {
|
if (data?.entity_id) {
|
||||||
const sourceRange =
|
const sourceRange = engineCommandManager.artifactMap?.[data.entity_id]
|
||||||
engineCommandManager.artifactMap?.[data.entity_id]?.range
|
?.range || [0, 0]
|
||||||
editorManager.setHighlightRange(sourceRange)
|
editorManager.setHighlightRange(sourceRange)
|
||||||
} else if (
|
} else if (
|
||||||
!editorManager.highlightRange ||
|
!editorManager.highlightRange ||
|
||||||
|
@ -79,6 +79,7 @@ export default class CodeManager {
|
|||||||
if (this._code !== code) {
|
if (this._code !== code) {
|
||||||
this.code = code
|
this.code = code
|
||||||
this.#updateState(code)
|
this.#updateState(code)
|
||||||
|
this.updateCodeEditor(code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -542,6 +542,27 @@ class EngineConnection {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
this.unreliableDataChannel.addEventListener('message', (event) => {
|
||||||
|
const result: UnreliableResponses = JSON.parse(event.data)
|
||||||
|
Object.values(
|
||||||
|
this.engineCommandManager.unreliableSubscriptions[result.type] || {}
|
||||||
|
).forEach(
|
||||||
|
// TODO: There is only one response that uses the unreliable channel atm,
|
||||||
|
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||||
|
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
||||||
|
// per unreliable subscription.
|
||||||
|
(callback) => {
|
||||||
|
if (
|
||||||
|
result.type === 'highlight_set_entity' &&
|
||||||
|
result?.data?.sequence &&
|
||||||
|
result?.data.sequence > this.engineCommandManager.inSequence
|
||||||
|
) {
|
||||||
|
this.engineCommandManager.inSequence = result.data.sequence
|
||||||
|
callback(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -853,7 +874,7 @@ type CommandTypes = Models['ModelingCmd_type']['type'] | 'batch'
|
|||||||
|
|
||||||
type UnreliableResponses = Extract<
|
type UnreliableResponses = Extract<
|
||||||
Models['OkModelingCmdResponse_type'],
|
Models['OkModelingCmdResponse_type'],
|
||||||
{ type: 'highlight_set_entity' }
|
{ type: 'highlight_set_entity' | 'camera_drag_move' }
|
||||||
>
|
>
|
||||||
interface UnreliableSubscription<T extends UnreliableResponses['type']> {
|
interface UnreliableSubscription<T extends UnreliableResponses['type']> {
|
||||||
event: T
|
event: T
|
||||||
@ -1061,32 +1082,6 @@ export class EngineCommandManager {
|
|||||||
setIsStreamReady(false)
|
setIsStreamReady(false)
|
||||||
},
|
},
|
||||||
onConnectionStarted: (engineConnection) => {
|
onConnectionStarted: (engineConnection) => {
|
||||||
engineConnection?.pc?.addEventListener('datachannel', (event) => {
|
|
||||||
let unreliableDataChannel = event.channel
|
|
||||||
|
|
||||||
unreliableDataChannel.addEventListener('message', (event) => {
|
|
||||||
const result: UnreliableResponses = JSON.parse(event.data)
|
|
||||||
Object.values(
|
|
||||||
this.unreliableSubscriptions[result.type] || {}
|
|
||||||
).forEach(
|
|
||||||
// TODO: There is only one response that uses the unreliable channel atm,
|
|
||||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
|
||||||
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
|
||||||
// per unreliable subscription.
|
|
||||||
(callback) => {
|
|
||||||
if (
|
|
||||||
result?.data?.sequence &&
|
|
||||||
result?.data.sequence > this.inSequence &&
|
|
||||||
result.type === 'highlight_set_entity'
|
|
||||||
) {
|
|
||||||
this.inSequence = result.data.sequence
|
|
||||||
callback(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// When the EngineConnection starts a connection, we want to register
|
// When the EngineConnection starts a connection, we want to register
|
||||||
// callbacks into the WebSocket/PeerConnection.
|
// callbacks into the WebSocket/PeerConnection.
|
||||||
engineConnection.websocket?.addEventListener('message', (event) => {
|
engineConnection.websocket?.addEventListener('message', (event) => {
|
||||||
@ -1366,14 +1361,11 @@ export class EngineCommandManager {
|
|||||||
callback,
|
callback,
|
||||||
}: Subscription<T>): () => void {
|
}: Subscription<T>): () => void {
|
||||||
const localUnsubscribeId = uuidv4()
|
const localUnsubscribeId = uuidv4()
|
||||||
const otherEventCallbacks = this.subscriptions[event]
|
if (!this.subscriptions[event]) {
|
||||||
if (otherEventCallbacks) {
|
this.subscriptions[event] = {}
|
||||||
otherEventCallbacks[localUnsubscribeId] = callback
|
|
||||||
} else {
|
|
||||||
this.subscriptions[event] = {
|
|
||||||
[localUnsubscribeId]: callback,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this.subscriptions[event][localUnsubscribeId] = callback
|
||||||
|
|
||||||
return () => this.unSubscribeTo(event, localUnsubscribeId)
|
return () => this.unSubscribeTo(event, localUnsubscribeId)
|
||||||
}
|
}
|
||||||
private unSubscribeTo(event: ModelTypes, id: string) {
|
private unSubscribeTo(event: ModelTypes, id: string) {
|
||||||
@ -1384,14 +1376,10 @@ export class EngineCommandManager {
|
|||||||
callback,
|
callback,
|
||||||
}: UnreliableSubscription<T>): () => void {
|
}: UnreliableSubscription<T>): () => void {
|
||||||
const localUnsubscribeId = uuidv4()
|
const localUnsubscribeId = uuidv4()
|
||||||
const otherEventCallbacks = this.unreliableSubscriptions[event]
|
if (!this.unreliableSubscriptions[event]) {
|
||||||
if (otherEventCallbacks) {
|
this.unreliableSubscriptions[event] = {}
|
||||||
otherEventCallbacks[localUnsubscribeId] = callback
|
|
||||||
} else {
|
|
||||||
this.unreliableSubscriptions[event] = {
|
|
||||||
[localUnsubscribeId]: callback,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this.unreliableSubscriptions[event][localUnsubscribeId] = callback
|
||||||
return () => this.unSubscribeToUnreliable(event, localUnsubscribeId)
|
return () => this.unSubscribeToUnreliable(event, localUnsubscribeId)
|
||||||
}
|
}
|
||||||
private unSubscribeToUnreliable(
|
private unSubscribeToUnreliable(
|
||||||
|
@ -65,3 +65,15 @@ export function getThemeColorForEngine(theme: Themes) {
|
|||||||
? { r: dark, g: dark, b: dark, a: 1 }
|
? { r: dark, g: dark, b: dark, a: 1 }
|
||||||
: { r: light, g: light, b: light, a: 1 }
|
: { r: light, g: light, b: light, a: 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ThreeJS uses hex values for colors
|
||||||
|
* @param theme
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getThemeColorForThreeJs(theme: Themes) {
|
||||||
|
const resolvedTheme = getResolvedTheme(theme)
|
||||||
|
const dark = 0x1c1c1c
|
||||||
|
const light = 0xf9f9f9
|
||||||
|
return resolvedTheme === Themes.Dark ? dark : light
|
||||||
|
}
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
|
|
||||||
export const settingsMachine = createMachine(
|
export const settingsMachine = createMachine(
|
||||||
{
|
{
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QGUwBc0EsB2VYDpMIAbMAYlnXwFsB7CMYnKfAV20zVgG0AGAXUSgADrVidMtbEJAAPRABYAHAFZ8vAEwA2FVq0aNAZgCcARl0B2ADQgAnolO8LvfBYUXT+48YW+tCgF8Am1QMZgIiUgoqAENhYXw0AAswajA+QSQQUXEsKRl5BCM1DQUDMo0VQwVjJxt7BFMDJXwNWo0LFSU3c0DgkFCsXAiScgAlOHQAAkow4YyZHIl8rMKAWn18Q39q3ScVFQ1NFXqHXiVTfGNqhSdeXi1DJQ1TIJD0IbxCUbIAKgWsks8tJVopjFp8KZjEpqi9lKZDE0TnYzgZXO5PG0fH4+u85l9IuRQlMYsRiFNsGAAO4zD7hAEiMTLEGgda6fAKJoIiyIkwWYwWawoxpGFxaHkKFS1a7woL9bD0OAyQbhRZM4EFRBrUyOLY7SVafaHTSnBDGDqQiz6DRKbwqTxvAZ04bfUhq3KSFlyBxHdQIhR6HTQmoC02OUwKPW7GrPdy8QxygJAA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QGUwBc0EsB2VYDpMIAbMAYlnXwEMAHW-Ae2wCNHqAnCHKZNatAFdYAbQAMAXUShajWJizNpIAB6IAbAFZN+AOwAWAIwAOYwE4AzGYBM+-ZosAaEAE9Eh62LP51ls+v0LMWt1awMAX3DnVAweAiJSCio6BjQACzAAWzAAYUZiRg5xKSQQWXlFbGU1BA9vQ0N1CwCxdVbdY1DnNwQzPp8zTTFje1D1QwtjSOj0LFx4knJKNHxMxggwYh58DYAzakFiNABVbAVi5XKFTCVSmotNY3w7YysHRuNDTXV1bvdG7w-IKTcbaazWCzTEAxOZ4QiLJIrFL4dJZMAXUpXSrVDTGazPMQPR4GXRBAx-XoGfDWIadMx4n6EqEwuLwxLLVbrTbbNKYKBpLb8tAAUWgcAxMjk11uoHumn0+DEw2sJkMulCgWsFL6YnwfX0ELsYg61jMumZs1ZCXIACU4OgAATLWGiSSXKXYu4aLz4UwWBr6DqBYYUzSePXqUlBLxmyZmC2xeZs8gxB3UYjEJ2W+YSsoem5VL0IKy6z6EsJifQ2Czq0MTHzq8Yjfz6MQmBMu5NkABUuaxBZxCDD+DD5lJgUjxssFP0xl0I+0pm06uMmg8kSiIGwXPgpRZ83dFQHRYAtA0LCO2tZjGIHJNdB5fq5EK3dc1OsNGkrA+oO1bFoe0qFrKiAnlol7BDed5zo+FK+Doc7qhCNaVv4UwbkAA */
|
||||||
id: 'Settings',
|
id: 'Settings',
|
||||||
predictableActionArguments: true,
|
predictableActionArguments: true,
|
||||||
context: {} as ReturnType<typeof createSettings>,
|
context: {} as ReturnType<typeof createSettings>,
|
||||||
@ -59,6 +59,7 @@ export const settingsMachine = createMachine(
|
|||||||
'setThemeClass',
|
'setThemeClass',
|
||||||
'setEngineTheme',
|
'setEngineTheme',
|
||||||
'persistSettings',
|
'persistSettings',
|
||||||
|
'setClientTheme',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -83,6 +84,7 @@ export const settingsMachine = createMachine(
|
|||||||
'setClientSideSceneUnits',
|
'setClientSideSceneUnits',
|
||||||
'Execute AST',
|
'Execute AST',
|
||||||
'persistSettings',
|
'persistSettings',
|
||||||
|
'setClientTheme',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -96,6 +98,7 @@ export const settingsMachine = createMachine(
|
|||||||
'setClientSideSceneUnits',
|
'setClientSideSceneUnits',
|
||||||
'Execute AST',
|
'Execute AST',
|
||||||
'persistSettings',
|
'persistSettings',
|
||||||
|
'setClientTheme',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -210,7 +210,7 @@ const Home = () => {
|
|||||||
: '')
|
: '')
|
||||||
}
|
}
|
||||||
onClick={() => setSearchParams(getNextSearchParams(sort, 'name'))}
|
onClick={() => setSearchParams(getNextSearchParams(sort, 'name'))}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: getSortIcon(sort, 'name'),
|
icon: getSortIcon(sort, 'name'),
|
||||||
className: 'p-1.5',
|
className: 'p-1.5',
|
||||||
iconClassName: !sort.includes('name')
|
iconClassName: !sort.includes('name')
|
||||||
@ -232,7 +232,7 @@ const Home = () => {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
setSearchParams(getNextSearchParams(sort, 'modified'))
|
setSearchParams(getNextSearchParams(sort, 'modified'))
|
||||||
}
|
}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: sort ? getSortIcon(sort, 'modified') : faArrowDown,
|
icon: sort ? getSortIcon(sort, 'modified') : faArrowDown,
|
||||||
className: 'p-1.5',
|
className: 'p-1.5',
|
||||||
iconClassName: !isSortByModified ? '!text-chalkboard-40' : '',
|
iconClassName: !isSortByModified ? '!text-chalkboard-40' : '',
|
||||||
@ -278,7 +278,7 @@ const Home = () => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => send('Create project')}
|
onClick={() => send('Create project')}
|
||||||
icon={{ icon: faPlus, iconClassName: 'p-1 w-4' }}
|
iconStart={{ icon: faPlus, iconClassName: 'p-1 w-4' }}
|
||||||
data-testid="home-new-file"
|
data-testid="home-new-file"
|
||||||
>
|
>
|
||||||
New project
|
New project
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useStore } from '../../useStore'
|
import { useStore } from '../../useStore'
|
||||||
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
|
||||||
|
|
||||||
export default function CodeEditor() {
|
export default function CodeEditor() {
|
||||||
const { buttonDownInStream } = useStore((s) => ({
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
@ -12,14 +11,6 @@ export default function CodeEditor() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||||
<div
|
|
||||||
className="fixed inset-0 bg-black opacity-50 dark:opacity-80 pointer-events-none"
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
/*clipPath: useBackdropHighlight('code-pane')*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
></div>
|
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { OnboardingButtons, kbdClasses, useDismiss, useNextClick } from '.'
|
import { OnboardingButtons, kbdClasses, useDismiss, useNextClick } from '.'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useStore } from '../../useStore'
|
import { useStore } from '../../useStore'
|
||||||
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
|
||||||
import { bracketWidthConstantLine } from 'lib/exampleKcl'
|
import { bracketWidthConstantLine } from 'lib/exampleKcl'
|
||||||
|
|
||||||
export default function InteractiveNumbers() {
|
export default function InteractiveNumbers() {
|
||||||
@ -13,14 +12,6 @@ export default function InteractiveNumbers() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||||
<div
|
|
||||||
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
/*clipPath: useBackdropHighlight('code-pane')*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
></div>
|
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useStore } from '../../useStore'
|
import { useStore } from '../../useStore'
|
||||||
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
|
||||||
import { Themes, getSystemTheme } from 'lib/theme'
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { bracketThicknessCalculationLine } from 'lib/exampleKcl'
|
import { bracketThicknessCalculationLine } from 'lib/exampleKcl'
|
||||||
@ -29,14 +28,6 @@ export default function ParametricModeling() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||||
<div
|
|
||||||
className="fixed inset-0 bg-black dark:bg-black-80 opacity-50 pointer-events-none"
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
/*clipPath: useBackdropHighlight('code-pane')*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
></div>
|
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
@ -51,7 +51,7 @@ export default function Units() {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={dismiss}
|
onClick={dismiss}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faXmark,
|
icon: faXmark,
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
iconClassName:
|
iconClassName:
|
||||||
@ -64,7 +64,7 @@ export default function Units() {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={next}
|
onClick={next}
|
||||||
icon={{ icon: faArrowRight }}
|
iconStart={{ icon: faArrowRight }}
|
||||||
>
|
>
|
||||||
Next: Camera
|
Next: Camera
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
@ -144,7 +144,7 @@ export function OnboardingButtons({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={dismiss}
|
onClick={dismiss}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'close',
|
icon: 'close',
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
||||||
@ -161,7 +161,7 @@ export function OnboardingButtons({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={next}
|
onClick={next}
|
||||||
icon={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
|
iconStart={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
|
||||||
className="dark:hover:bg-chalkboard-80/50"
|
className="dark:hover:bg-chalkboard-80/50"
|
||||||
data-testid="onboarding-next"
|
data-testid="onboarding-next"
|
||||||
>
|
>
|
||||||
|
@ -269,7 +269,7 @@ export const Settings = () => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={restartOnboarding}
|
onClick={restartOnboarding}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'refresh',
|
icon: 'refresh',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
@ -300,7 +300,7 @@ export const Settings = () => {
|
|||||||
)
|
)
|
||||||
showInFolder(paths[searchParamTab])
|
showInFolder(paths[searchParamTab])
|
||||||
}}
|
}}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'folder',
|
icon: 'folder',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
@ -319,7 +319,7 @@ export const Settings = () => {
|
|||||||
})
|
})
|
||||||
toast.success('Settings restored to default')
|
toast.success('Settings restored to default')
|
||||||
}}
|
}}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'refresh',
|
icon: 'refresh',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
className: 'p-1 text-chalkboard-10',
|
className: 'p-1 text-chalkboard-10',
|
||||||
|
@ -65,7 +65,7 @@ const SignIn = () => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={signInTauri}
|
onClick={signInTauri}
|
||||||
icon={{ icon: 'arrowRight' }}
|
iconStart={{ icon: 'arrowRight' }}
|
||||||
className="w-fit mt-4"
|
className="w-fit mt-4"
|
||||||
data-testid="sign-in-button"
|
data-testid="sign-in-button"
|
||||||
>
|
>
|
||||||
@ -80,7 +80,7 @@ const SignIn = () => {
|
|||||||
typeof window !== 'undefined' &&
|
typeof window !== 'undefined' &&
|
||||||
window.location.href.replace('signin', '')
|
window.location.href.replace('signin', '')
|
||||||
)}`}
|
)}`}
|
||||||
icon={{ icon: 'arrowRight' }}
|
iconStart={{ icon: 'arrowRight' }}
|
||||||
className="w-fit mt-4"
|
className="w-fit mt-4"
|
||||||
>
|
>
|
||||||
Sign in
|
Sign in
|
||||||
|