diff --git a/src/pages/deviations-profile-generator/index.css b/src/pages/deviations-profile-generator/index.css new file mode 100644 index 0000000..f3f20cd --- /dev/null +++ b/src/pages/deviations-profile-generator/index.css @@ -0,0 +1,73 @@ +.deviations-root { + padding: 25px; +} + +.deviations-root .main-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 30px; + position: relative; +} +.deviations-root .main-container .codeblock button[aria-label="Copy code to clipboard"] { + opacity: 1 !important; + position: fixed; + right: 45px; +} +.deviations-root .main-container>button.button-logout { + position: absolute; + right: 0; +} + +.deviations-root .main-container>div { + border: 1px solid; + border-radius: 10px; + margin: 5px; + padding: 10px; +} + +.deviations-root .main-container>div.deviations-container { + display: flex; + justify-content: center; + align-items: center; + flex-direction: row; + width: 100%; + gap: 15px; +} + +.deviations-root .main-container>div.deviations-container>div { + flex: 1 1 0px; +} + +/* .deviations-root .main-container>div.deviations-container .dev-header { + display: flex; + justify-content: space-between; + align-items: flex-start; +} + +*/ +.deviations-root .main-container .scene-details-container details { + min-width: 250px; +} + +.deviations-root input:not([type=submit]), +.deviations-root textarea { + width: 100%; + padding: 12px 20px; + margin: 8px 0; + display: inline-block; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; +} + +.deviations-root input[type=submit], +.deviations-root button[type=submit] { + width: 100%; + color: white; + padding: 14px 20px; + margin: 8px 0; + border: none; + border-radius: 4px; + cursor: pointer; +} \ No newline at end of file diff --git a/src/pages/deviations-profile-generator/index.tsx b/src/pages/deviations-profile-generator/index.tsx new file mode 100644 index 0000000..255d3f6 --- /dev/null +++ b/src/pages/deviations-profile-generator/index.tsx @@ -0,0 +1,352 @@ +import React, { useState } from 'react'; +import Layout from "@theme/Layout"; +import CodeBlock from "@theme/CodeBlock"; +import { createAPI, type API, type ObjectGroup, type SceneData } from "@novorender/data-js-api"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCheck, faArrowRightFromBracket } from "@fortawesome/free-solid-svg-icons"; + +import("./index.css"); + +const JWT_KEY = "LOGIN_JWT"; + +export default function DeviationsProfileGenerator() { + + const [loginDetails, setLoginDetails] = useState<{ username: string; password: string; }>({ username: "", password: "" }); + const [sceneDetails, setSceneDetails] = useState<{ dataServerUrl: string; sceneId: string; }>({ dataServerUrl: "https://data.novorender.com/api", sceneId: "" }); + const [pointsVsTriangles, setPointsVsTriangles] = useState<{ name: string; groups: ObjectGroup[]; }>({ name: "", groups: [] }); + const [pointsVsPoints, setPointsVsPoints] = useState<{ name: string; fromGroups: ObjectGroup[]; toGroups: ObjectGroup[]; }>({ name: "", fromGroups: [], toGroups: [] }); + const [token, setToken] = useState(); + const [dataApi, setDataApi] = useState(); + const [sceneData, setSceneData] = useState(); + const [pointsVsTrianglesData, setPointsVsTrianglesData] = useState<{ pointToTriangle: { groups: Array<{ name: string; groupIds: Array; objectIds: Array; }>; }; }>({ pointToTriangle: { groups: [] } }); + const [pointsVsPointsData, setPointsVsPointsData] = useState<{ + pointToPoint: { + groups: Array<{ name: string; to: { groupIds: Array; objectIds: Array; }; from: { groupIds: Array; objectIds: Array; }; }>; + }; + }>({ pointToPoint: { groups: [] } }); + const [isDetailsOpen, setIsDetailsOpen] = useState(true); + + React.useEffect(() => { + setToken(localStorage.getItem(JWT_KEY) as string); + }, []); + + const handleInputChange = (event: React.ChangeEvent, stateKey: string) => { + const name = event.target.name; + const value = event.target.value; + switch (stateKey) { + case "login": + setLoginDetails(values => ({ ...values, [name]: value })); + break; + case "sceneDetails": + setSceneDetails(values => ({ ...values, [name]: value })); + break; + case "pointsVsTriangles": + setPointsVsTriangles(values => ({ ...values, [name]: value })); + break; + case "pointsVsPoints": + setPointsVsPoints(values => ({ ...values, [name]: value })); + break; + } + + }; + + const handlePointsVsTrianglesSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + await loadIds(); + + const groupIds: string[] = []; + const objectIds: number[] = []; + for (const g of pointsVsTriangles.groups) { + groupIds.push(g.id); + if (g.ids) { + for (const o of g.ids) { + objectIds.push(o); + } + } + } + + console.log(JSON.stringify({ groupIds, objectIds })); + setPointsVsTrianglesData(values => ({ pointToTriangle: { groups: [...values.pointToTriangle.groups, { name: pointsVsTriangles.name, groupIds, objectIds }] } })); + setPointsVsTriangles({ name: "", groups: [] }); + + }; + + + const handlePointsVsPointsSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + await loadIds(); + + const fromGroupIds: string[] = []; + const fromObjectIds: number[] = []; + const toGroupIds: string[] = []; + const toObjectIds: number[] = []; + for (const g of pointsVsPoints.fromGroups) { + fromGroupIds.push(g.id); + if (g.ids) { + for (const o of g.ids) { + fromObjectIds.push(o); + } + } + } + for (const g of pointsVsPoints.toGroups) { + toGroupIds.push(g.id); + if (g.ids) { + for (const o of g.ids) { + toObjectIds.push(o); + } + } + } + + setPointsVsPointsData(values => ({ + pointToPoint: { + groups: [...values.pointToPoint.groups, + { name: pointsVsPoints.name, to: { groupIds: toGroupIds, objectIds: toObjectIds }, from: { groupIds: fromGroupIds, objectIds: fromObjectIds } }] + } + })); + setPointsVsPoints({ name: "", fromGroups: [], toGroups: [] }); + // setSceneData(values => ({ ...values, objectGroups: values?.objectGroups.map(g => { g["picked"] = false; return g; }) } as SceneData)); + }; + + async function handleLoginSubmit(event: React.FormEvent): Promise { + event.preventDefault(); + const res: { token: string; } = await fetch("https://data.novorender.com/api" + "/user/login", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: `username=${loginDetails.username}&password=${loginDetails.password}`, + }) + .then((res) => res.json()) + .catch(() => { + return { token: "" }; + }); + + if (res?.token) { + localStorage.setItem(JWT_KEY, res.token); + setToken(res.token); + } else { + alert("Login Failed"); + } + } + + async function loadScene(event: React.FormEvent) { + event.preventDefault(); + + const dataApi = createAPI({ + serviceUrl: sceneDetails.dataServerUrl, + authHeader: async () => ({ + header: "Authorization", + value: `Bearer ${token}`, + }), + }); + + setDataApi(dataApi); + const sceneData = await dataApi.loadScene(sceneDetails.sceneId); + setSceneData(sceneData as SceneData); + setIsDetailsOpen(false); + } + + + async function loadIds(): Promise { + const local_scene_data = Object.assign({}, sceneData); + + const groupIdRequests: Promise[] | undefined = local_scene_data?.objectGroups.map(async (group) => { + if (!group.ids) { + group.ids = await dataApi?.getGroupIds(sceneDetails.sceneId, group.id).catch(() => { + console.warn("failed to load ids for group - ", group.id); + return []; + }); + } + }); + + if (groupIdRequests) { + await Promise.all(groupIdRequests); + } + + setSceneData(local_scene_data); + } + + function selectGrp(target: string, group: ObjectGroup, groups: ObjectGroup[]) { + + const _groups = [...groups]; + + const isPicked = _groups.findIndex(({ id }) => id === group.id); + if (isPicked !== -1) { + _groups.splice(isPicked, 1); + } else { + _groups.push(group); + } + + switch (target) { + case 'pointsVsTriangles': + setPointsVsTriangles({ name: pointsVsTriangles.name, groups: _groups }); + break; + case 'pointsVsPointsFrom': + setPointsVsPoints({ name: pointsVsPoints.name, fromGroups: _groups, toGroups: pointsVsPoints.toGroups }); + break; + case 'pointsVsPointsTo': + setPointsVsPoints({ name: pointsVsPoints.name, toGroups: _groups, fromGroups: pointsVsPoints.fromGroups }); + break; + } + + } + + function logout() { + localStorage.removeItem(JWT_KEY); + setToken(null); + } + + return ( + +
+ { + !token + ?
+
+

Login

+
+
+ + { handleInputChange(e, 'login'); }} + /> +
+ + { handleInputChange(e, 'login'); }} + /> +
+ +
+
+
+
+ :
+ + + +
+
+ Scene Details +
+ + { handleInputChange(e, 'sceneDetails'); }} + /> + + { handleInputChange(e, 'sceneDetails'); }} + /> + +
+
+
+ + {!sceneData + ?

Load Scene First

+ :
+
+
+

{sceneData.title}

+
+
+
+

Point vs Triangles

+
+ + { handleInputChange(e, 'pointsVsTriangles'); }} + /> +
+ +
+
    + {sceneData.objectGroups.filter(g => g.id).map(g => +
  • selectGrp('pointsVsTriangles', g, pointsVsTriangles.groups)} style={{ cursor: "pointer" }} className="dropdown__link" key={g.name}> + {(pointsVsTriangles?.groups?.findIndex(v => v.id === g.id) !== -1) && } + {" " + g.name}
  • + )} +
+