From 0824cf9f4fe6c6a13ff8312d2f69a2f358de54fa Mon Sep 17 00:00:00 2001 From: yushiang Date: Sun, 18 Feb 2024 21:42:17 +0800 Subject: [PATCH 1/5] feature: dula-net console page and editro behavior. --- .env | 2 + apps/admin.js | 108 ++++++++++++++++++++++++++++++++ components/GridLayout/index.js | 7 +++ components/GridLayout/styled.js | 12 ++++ components/Icon/index.js | 3 + components/Image/index.js | 13 ++++ components/Image/styled.js | 48 ++++++++++++++ components/RouteSwitch/index.js | 8 +++ components/Upload/index.js | 31 +++++++++ components/Upload/styled.js | 41 ++++++++++++ hooks/useClick2AddWalls.js | 90 ++++++++++++++++++++------ pages/_app.js | 2 +- pages/admin/index.js | 3 + public/icons/add.svg | 4 ++ public/icons/github-mark.svg | 1 + public/icons/user.svg | 4 ++ 16 files changed, 357 insertions(+), 20 deletions(-) create mode 100644 .env create mode 100644 apps/admin.js create mode 100644 components/GridLayout/index.js create mode 100644 components/GridLayout/styled.js create mode 100644 components/Image/index.js create mode 100644 components/Image/styled.js create mode 100644 components/Upload/index.js create mode 100644 components/Upload/styled.js create mode 100644 pages/admin/index.js create mode 100644 public/icons/add.svg create mode 100644 public/icons/github-mark.svg create mode 100644 public/icons/user.svg diff --git a/.env b/.env new file mode 100644 index 0000000..17e0677 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +NEXT_PUBLIC_DULA_NET_ADMIN_TASKS=/api/admin/tasks +NEXT_PUBLIC_DULA_NET_TASK=/api/task/ \ No newline at end of file diff --git a/apps/admin.js b/apps/admin.js new file mode 100644 index 0000000..17a2e32 --- /dev/null +++ b/apps/admin.js @@ -0,0 +1,108 @@ +import Upload from "../components/Upload"; +import GridLayout from "../components/GridLayout"; +import PageContainer from "../components/PageContainer"; +import Image from "../components/Image"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import { Core } from "@pano-to-mesh/three"; +import { encodeString } from "@pano-to-mesh/base64"; + +const fetchAll = (callback) => { + fetch(process.env.NEXT_PUBLIC_DULA_NET_ADMIN_TASKS) + .then((data) => data.json()) + .then((data) => callback(data.tasks)) + .catch((e) => console.error(e)); +}; + +const postImage = (formData, callback) => { + const api = process.env.NEXT_PUBLIC_DULA_NET_TASK; + const method = "POST"; + const xhr = new XMLHttpRequest(); + xhr.open(method, api, true); + xhr.onload = callback; + xhr.send(formData); +}; + +const getTask = (uuid, callback) => { + fetch(`${process.env.NEXT_PUBLIC_DULA_NET_TASK}?id=${uuid}`) + .then((data) => data.json()) + .then((data) => { + if (data.output) { + const { + layout, + images: { aligned }, + } = data.output; + + const { cameraHeight, layoutHeight } = layout; + + const point3dToCoords = (point) => { + const { longitude, latitude } = + Core.Math.coordinates.cartesian2Spherical( + point[0] - 0, + point[1] - cameraHeight, + point[2] - 0 + ); + const { x, y } = Core.Math.coordinates.spherical2NormalizedXY( + longitude, + latitude + ); + return [1 - x, y]; + }; + + const coords = layout.layoutPoints.points.map(({ xyz }) => + point3dToCoords(xyz) + ); + + const viewer = { + ceilingY: layoutHeight, + floorY: 0, + layout2D: coords, + panorama: aligned, + panoramaOrigin: [0, cameraHeight, 0], + }; + + callback(viewer); + } + }) + .catch((e) => console.error(e)); +}; + +const Admin = () => { + const router = useRouter(); + const [tasks, setTasks] = useState([]); + useEffect(() => { + fetchAll(setTasks); + }, []); + + const onFileChange = (formData) => { + postImage(formData, () => { + fetchAll(setTasks); + }); + }; + + const onClickFactory = (uuid) => { + return () => { + getTask(uuid, (data) => { + const hash = encodeString(JSON.stringify(data)); + router.push(`/editors/layout2d#${hash}`); + }); + }; + }; + + return ( + + + + {tasks.map((data) => ( + + ))} + + + ); +}; + +export default Admin; diff --git a/components/GridLayout/index.js b/components/GridLayout/index.js new file mode 100644 index 0000000..1188b69 --- /dev/null +++ b/components/GridLayout/index.js @@ -0,0 +1,7 @@ +import { Wrapper } from "./styled"; + +const GridLayout = ({ children }) => { + return {children}; +}; + +export default GridLayout; diff --git a/components/GridLayout/styled.js b/components/GridLayout/styled.js new file mode 100644 index 0000000..bf91f76 --- /dev/null +++ b/components/GridLayout/styled.js @@ -0,0 +1,12 @@ +import styled from "styled-components"; + +export const Wrapper = styled.div` + display: inline-flex; + flex-wrap: wrap; + justify-content: center; + gap: 10px; + padding: 10px; + width: 50%; + max-height: 80dvh; + overflow: auto; +`; diff --git a/components/Icon/index.js b/components/Icon/index.js index 15909ac..15b78c9 100644 --- a/components/Icon/index.js +++ b/components/Icon/index.js @@ -38,6 +38,7 @@ const Icon = ({ src, onClick, ...props }) => { // all svg resources is download from https://www.svgrepo.com/vectors/cursor/ const IconFolder = `/icons`; const files = { + add: `${IconFolder}/add.svg`, arrowToDown: `${IconFolder}/arrowToDown.svg`, arrowToTop: `${IconFolder}/arrowToTop.svg`, download: `${IconFolder}/download.svg`, @@ -56,6 +57,8 @@ const files = { trash: `${IconFolder}/trash.svg`, arrange: `${IconFolder}/arrange.svg`, preview: `${IconFolder}/preview.svg`, + github: `${IconFolder}/github-mark.svg`, + user: `${IconFolder}/user.svg`, }; const Icons = Object.keys(files).reduce((acc, key) => { diff --git a/components/Image/index.js b/components/Image/index.js new file mode 100644 index 0000000..a1ca229 --- /dev/null +++ b/components/Image/index.js @@ -0,0 +1,13 @@ +import { Wrapper, Content, Container } from "./styled"; + +const Image = ({ url, onClick }) => { + return ( + + + + + + ); +}; + +export default Image; diff --git a/components/Image/styled.js b/components/Image/styled.js new file mode 100644 index 0000000..5a92fee --- /dev/null +++ b/components/Image/styled.js @@ -0,0 +1,48 @@ +import styled from "styled-components"; + +export const Wrapper = styled.div` + width: 95%; + + // Small devices (landscape phones, 576px and up) + @media (min-width: 576px) { + width: 48%; + } + + // Medium devices (tablets, 768px and up) + @media (min-width: 768px) { + width: 31%; + } + + // Large devices (desktops, 992px and up) + @media (min-width: 992px) { + width: 23%; + } + + // Extra large devices (large desktops, 1200px and up) + @media (min-width: 1200px) { + width: 18%; + } + + &:hover { + cursor: pointer; + transform: scale(1.05); + } +`; + +export const Container = styled.div` + position: relative; + width: 100%; + padding-top: 56%; +`; + +export const Content = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: 100% 100%; + border-radius: 5px; + + background-image: url("${(props) => props.src}"); +`; diff --git a/components/RouteSwitch/index.js b/components/RouteSwitch/index.js index 3ef7212..abdc27a 100644 --- a/components/RouteSwitch/index.js +++ b/components/RouteSwitch/index.js @@ -38,6 +38,14 @@ const DataWrapper = () => { useEffect(() => { setData([ + { + link: "https://github.com/yushiang-demo/pano-to-mesh", + Icon: Icons.github, + }, + { + link: "/admin", + Icon: Icons.user, + }, { link: "/editors/layout2d", Icon: Icons.panorama, diff --git a/components/Upload/index.js b/components/Upload/index.js new file mode 100644 index 0000000..0b7270e --- /dev/null +++ b/components/Upload/index.js @@ -0,0 +1,31 @@ +import Icons from "../Icon"; +import { Input, Wrapper, Label } from "./styled"; +const Upload = ({ onFileChange }) => { + const onChange = (e) => { + const { files } = e.target; + const length = files.length; + Array.from({ length }).forEach((_, index) => { + const file = files[index]; + const formData = new FormData(); + formData.append("file", file); + onFileChange(formData); + }); + }; + + return ( + + + + + ); +}; + +export default Upload; diff --git a/components/Upload/styled.js b/components/Upload/styled.js new file mode 100644 index 0000000..5147058 --- /dev/null +++ b/components/Upload/styled.js @@ -0,0 +1,41 @@ +import styled from "styled-components"; + +export const Input = styled.input` + display: none; +`; + +export const Label = styled.label` + width: 100%; + height: 100%; + display: flex; + cursor: pointer; + justify-content: center; + align-items: center; +`; + +export const Wrapper = styled.div` + border: 1px solid; + border-radius: 5px; + + width: 95%; + + // Small devices (landscape phones, 576px and up) + @media (min-width: 576px) { + width: 48%; + } + + // Medium devices (tablets, 768px and up) + @media (min-width: 768px) { + width: 31%; + } + + // Large devices (desktops, 992px and up) + @media (min-width: 992px) { + width: 23%; + } + + // Extra large devices (large desktops, 1200px and up) + @media (min-width: 1200px) { + width: 18%; + } +`; diff --git a/hooks/useClick2AddWalls.js b/hooks/useClick2AddWalls.js index 3558854..3502a22 100644 --- a/hooks/useClick2AddWalls.js +++ b/hooks/useClick2AddWalls.js @@ -26,6 +26,7 @@ const useClick2AddWalls = ({ }) => { const [dragging, setDragging] = useState(false); const [previewImageCoord, setPreviewImageCoord] = useState(null); + const [previewCoordIndex, setPreviewCoordIndex] = useState(null); const [imageCoord, setImageCoord] = useState(defaultData || []); const [layout2D, setLayout2D] = useState([]); @@ -67,48 +68,99 @@ const useClick2AddWalls = ({ setPreviewImageCoord([normalizedX, normalizedY]); } else { setPreviewImageCoord(null); + setPreviewCoordIndex(null); } }; const onMouseUp = ({ normalizedX, normalizedY }) => { if (dragging) { if (parseMousePointTo3D([normalizedX, normalizedY])) { - setImageCoord([ - ...imageCoord, - parser2DCeilingCoordToFloorCoord([normalizedX, normalizedY]), - ]); + const newCoord = [...imageCoord]; + newCoord.splice( + previewCoordIndex, + 0, + parser2DCeilingCoordToFloorCoord([normalizedX, normalizedY]) + ); + + setImageCoord(newCoord); } setPreviewImageCoord(null); + setPreviewCoordIndex(null); setDragging(false); } }; const onMouseDown = ({ width, height, normalizedX, normalizedY }) => { - const points = imageCoord.filter( - ([x, y]) => - !pointSelector( - normalizedX, - normalizedY, - ({ x, y }) => ({ - x: x * width, - y: y * height, - }), - selectThresholdPixel - )([x, y]) + const selectedIndex = imageCoord.findIndex(([x, y]) => + pointSelector( + normalizedX, + normalizedY, + ({ x, y }) => ({ + x: x * width, + y: y * height, + }), + selectThresholdPixel + )([x, y]) ); + const points = imageCoord.filter((_, index) => index !== selectedIndex); if (points.length) setImageCoord(points); else setImageCoord([ parser2DCeilingCoordToFloorCoord([normalizedX, normalizedY]), ]); setDragging(true); - setPreviewImageCoord([normalizedX, normalizedY]); + const targetCoord = [normalizedX, normalizedY]; + + const index = (() => { + if (selectedIndex > -1) return selectedIndex; + let index = null; + let boundaryIndex = null; + let targetWidth = 1; // max coord distance is 1; + + const isEqual = (x, y) => x - y < 1e-3; + + const xArray = imageCoord.map(([x]) => x); + const minX = Math.min(...xArray); + const maxX = Math.max(...xArray); + + for (let i = 0; i < imageCoord.length; i++) { + const start = imageCoord[i][0]; + const end = imageCoord[(i + 1) % imageCoord.length][0]; + + if ( + ((isEqual(start, minX) && isEqual(end, maxX)) || + (isEqual(end, minX) && isEqual(start, maxX))) && + !boundaryIndex + ) { + boundaryIndex = i; + } + + const newX = targetCoord[0]; + + const isInRange = isEqual( + Math.abs(newX - start) + Math.abs(newX - end) - Math.abs(start - end), + 0 + ); + + const wallWidth = Math.abs(start - end); + + if (isInRange && wallWidth < targetWidth) { + index = i; + targetWidth = wallWidth; + } + } + + return (index !== null ? index : boundaryIndex) + 1; + })(); + setPreviewImageCoord(targetCoord); + setPreviewCoordIndex(index); }; useEffect(() => { const coord2d = [...imageCoord]; - if (previewImageCoord) coord2d.push(previewImageCoord); - coord2d.sort(([x1], [x2]) => x1 - x2); + if (previewImageCoord) { + coord2d.splice(previewCoordIndex, 0, previewImageCoord); + } const pointsXZ = coord2d .map((coord) => { @@ -117,7 +169,7 @@ const useClick2AddWalls = ({ }) .filter((value) => value); setLayout2D(pointsXZ); - }, [imageCoord, previewImageCoord, parseMousePointTo3D]); + }, [imageCoord, previewImageCoord, previewCoordIndex, parseMousePointTo3D]); return { imageCoord, diff --git a/pages/_app.js b/pages/_app.js index d2bb9e8..a03d9d4 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -3,7 +3,7 @@ export default function App({ Component, pageProps }) { return ( <> - ; + ); } diff --git a/pages/admin/index.js b/pages/admin/index.js new file mode 100644 index 0000000..ca90fa1 --- /dev/null +++ b/pages/admin/index.js @@ -0,0 +1,3 @@ +import Admin from "../../apps/admin"; + +export default Admin; diff --git a/public/icons/add.svg b/public/icons/add.svg new file mode 100644 index 0000000..3f32f53 --- /dev/null +++ b/public/icons/add.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/icons/github-mark.svg b/public/icons/github-mark.svg new file mode 100644 index 0000000..37fa923 --- /dev/null +++ b/public/icons/github-mark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/user.svg b/public/icons/user.svg new file mode 100644 index 0000000..4b30c94 --- /dev/null +++ b/public/icons/user.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 8d3c32dc1e875d9e7cd6d37a680936eb20be6af4 Mon Sep 17 00:00:00 2001 From: yushiang Date: Sun, 18 Feb 2024 21:46:54 +0800 Subject: [PATCH 2/5] doc: changelog #73 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bc8e2e..38c74a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,12 @@ The format is based on [Keep a Changelog](https://github.com/olivierlacan/keep-a ### Added +- Dula-net demo page. (https://github.com/yushiang-demo/pano-to-mesh/pull/73) + ### Changed +- Remove 2d layout coords sorting to support occluded wall. (https://github.com/yushiang-demo/pano-to-mesh/pull/73) + ### Fixed ### Removed From fd4cbab00451ee494d52b49aaeebfd33c29e85fb Mon Sep 17 00:00:00 2001 From: yushiang Date: Sun, 18 Feb 2024 21:56:32 +0800 Subject: [PATCH 3/5] refactor: remove license. --- LICENSE | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d2a31db..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 yushiang-demo - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. From 6fdba1f0a7692d77be719a6ce9d3dbebb1bf4d4d Mon Sep 17 00:00:00 2001 From: yushiang Date: Sun, 18 Feb 2024 21:58:26 +0800 Subject: [PATCH 4/5] doc: changelog pr#74 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bc8e2e..12fa3e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ The format is based on [Keep a Changelog](https://github.com/olivierlacan/keep-a ### Removed +- Remove MIT license. (https://github.com/yushiang-demo/pano-to-mesh/pull/74) + ## [2.0.1] - 2023-12-17 ### Added From 3c36411288def6b8fc7809898675f9626631e2d7 Mon Sep 17 00:00:00 2001 From: yushiang Date: Sun, 18 Feb 2024 22:19:09 +0800 Subject: [PATCH 5/5] changelog v2.1.0 --- CHANGELOG.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8665c4f..cb90a36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ The format is based on [Keep a Changelog](https://github.com/olivierlacan/keep-a ### Added +### Changed + +### Fixed + +### Removed + +## [2.1.0] - 2024-02-18 + +### Added + - Dula-net demo page. (https://github.com/yushiang-demo/pano-to-mesh/pull/73) ### Changed @@ -203,7 +213,8 @@ Codes without pull requests won't be recorded. ### Removed -[unreleased]: https://github.com/yushiang-demo/PanoToMesh/compare/v2.0.1...HEAD +[unreleased]: https://github.com/yushiang-demo/PanoToMesh/compare/v2.1.0...HEAD +[2.1.0]: https://github.com/yushiang-demo/PanoToMesh/compare/v2.0.1...v2.1.0 [2.0.1]: https://github.com/yushiang-demo/PanoToMesh/compare/v2.0.0...v2.0.1 [2.0.0]: https://github.com/yushiang-demo/PanoToMesh/compare/v1.4.1...v2.0.0 [1.4.1]: https://github.com/yushiang-demo/PanoToMesh/compare/v1.4.0...v1.4.1