Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
HFT-informatics-weekend
AR - Urban Planner
Commits
4c6160de
Commit
4c6160de
authored
Oct 12, 2025
by
Stoll
Browse files
Fix: Emojis loaded correctly
parent
98fab318
Changes
2
Hide whitespace changes
Inline
Side-by-side
webxr-cube/src/components/ScreenshotButton.tsx
0 → 100644
View file @
4c6160de
import
React
from
"
react
"
;
interface
ScreenshotButtonProps
{
mountRef
:
React
.
RefObject
<
HTMLDivElement
|
null
>
;
overlayRef
:
React
.
RefObject
<
HTMLDivElement
|
null
>
;
reticleRef
?:
React
.
RefObject
<
any
>
;
// Optional if you have a reticle
}
const
ScreenshotButton
:
React
.
FC
<
ScreenshotButtonProps
>
=
({
mountRef
,
overlayRef
,
reticleRef
,
})
=>
{
const
captureAndDownload
=
()
=>
{
const
canvas
=
(
mountRef
.
current
?.
querySelector
(
"
canvas
"
)
||
document
.
querySelector
(
"
canvas
"
))
as
HTMLCanvasElement
|
null
;
if
(
!
canvas
||
!
overlayRef
.
current
)
return
;
const
overlayEl
=
overlayRef
.
current
;
const
prevPointer
=
overlayEl
.
style
.
pointerEvents
;
const
prevOpacity
=
overlayEl
.
style
.
opacity
;
overlayEl
.
style
.
pointerEvents
=
"
none
"
;
overlayEl
.
style
.
opacity
=
"
0
"
;
const
reticle
=
reticleRef
?.
current
;
const
prevReticleVisible
=
reticle
?.
visible
??
false
;
if
(
reticle
)
reticle
.
visible
=
false
;
requestAnimationFrame
(()
=>
{
canvas
.
toBlob
((
blob
)
=>
{
overlayEl
.
style
.
pointerEvents
=
prevPointer
;
overlayEl
.
style
.
opacity
=
prevOpacity
;
if
(
reticle
)
reticle
.
visible
=
prevReticleVisible
;
if
(
!
blob
)
return
;
const
url
=
URL
.
createObjectURL
(
blob
);
const
a
=
document
.
createElement
(
"
a
"
);
const
ts
=
new
Date
().
toISOString
().
replace
(
/
[
:.
]
/g
,
"
-
"
);
a
.
href
=
url
;
a
.
download
=
`ar-snapshot-
${
ts
}
.png`
;
document
.
body
.
appendChild
(
a
);
a
.
click
();
a
.
remove
();
URL
.
revokeObjectURL
(
url
);
},
"
image/png
"
);
});
};
return
(
<
button
onClick
=
{
captureAndDownload
}
aria-label
=
"Save snapshot"
style
=
{
{
position
:
"
absolute
"
,
top
:
"
4rem
"
,
right
:
"
1.5rem
"
,
zIndex
:
1000
,
padding
:
"
10px 12px
"
,
borderRadius
:
12
,
border
:
"
none
"
,
background
:
"
white
"
,
fontSize
:
18
,
boxShadow
:
"
0 2px 8px rgba(0,0,0,0.25)
"
,
cursor
:
"
pointer
"
,
}
}
>
📷
</
button
>
);
};
export
default
ScreenshotButton
;
webxr-cube/src/components/XRRenderer.tsx
View file @
4c6160de
...
@@ -10,6 +10,7 @@ import {ARBottomBar} from "./ARBottomBar";
...
@@ -10,6 +10,7 @@ import {ARBottomBar} from "./ARBottomBar";
import
{
RoomEnvironment
}
from
"
three/examples/jsm/environments/RoomEnvironment
"
;
import
{
RoomEnvironment
}
from
"
three/examples/jsm/environments/RoomEnvironment
"
;
import
{
addLightToScene
,
createCamera
,
createPlacementRing
,
createRenderer
,
setSceneBackground
}
from
"
./Setup.ts
"
;
import
{
addLightToScene
,
createCamera
,
createPlacementRing
,
createRenderer
,
setSceneBackground
}
from
"
./Setup.ts
"
;
import
ScreenshotButton
from
"
./ScreenshotButton.tsx
"
;
// bottom UI for selecting objects
import
{
ModelEmojiMap
,
ModelName
,
ModelTypeMap
}
from
"
../model/ModelType.ts
"
;
// bottom UI for selecting objects
import
{
ModelEmojiMap
,
ModelName
,
ModelTypeMap
}
from
"
../model/ModelType.ts
"
;
// bottom UI for selecting objects
...
@@ -139,7 +140,7 @@ export const XRRenderer: React.FC = () => {
...
@@ -139,7 +140,7 @@ export const XRRenderer: React.FC = () => {
if
(
name
===
ModelName
.
CUBE
)
{
if
(
name
===
ModelName
.
CUBE
)
{
objectToAdd
=
new
Cube
(
position
);
objectToAdd
=
new
Cube
(
position
);
}
else
{
}
else
{
objectToAdd
=
new
GLTFModel
(
`/
${
ModelTypeMap
[
name
as
ModelName
]}
/
${
name
}
.glb`
,
position
,
`/
${
ModelTypeMap
[
name
as
ModelName
]}
/colormap.png`
)
objectToAdd
=
new
GLTFModel
(
`/
${
ModelTypeMap
[
name
as
ModelName
]}
/
${
name
.
toLowerCase
().
replace
(
/
\s
/g
,
""
)
}
.glb`
,
position
,
`/
${
ModelTypeMap
[
name
as
ModelName
]}
/colormap.png`
)
}
}
const
audio
=
new
Audio
(
'
/sounds/cash.mp3
'
);
const
audio
=
new
Audio
(
'
/sounds/cash.mp3
'
);
...
@@ -164,62 +165,23 @@ export const XRRenderer: React.FC = () => {
...
@@ -164,62 +165,23 @@ export const XRRenderer: React.FC = () => {
};
};
},
[]);
// empty dependency array → runs only once
},
[]);
// empty dependency array → runs only once
// --- 🔸 Screenshot function ---
const
captureAndDownload
=
()
=>
{
// Get the <canvas> element from the renderer
const
canvas
=
(
mountRef
.
current
?.
querySelector
(
"
canvas
"
)
||
document
.
querySelector
(
"
canvas
"
))
as
HTMLCanvasElement
|
null
;
if
(
!
canvas
||
!
overlayRef
.
current
)
return
;
// Hide UI overlay for a clean capture
const
overlayEl
=
overlayRef
.
current
;
const
prevPointer
=
overlayEl
.
style
.
pointerEvents
;
const
prevOpacity
=
overlayEl
.
style
.
opacity
;
overlayEl
.
style
.
pointerEvents
=
"
none
"
;
overlayEl
.
style
.
opacity
=
"
0
"
;
// Hide reticle (optional)
const
reticle
=
reticleRef
.
current
;
const
prevReticleVisible
=
reticle
?.
visible
??
false
;
if
(
reticle
)
reticle
.
visible
=
false
;
// Wait one frame → take screenshot
requestAnimationFrame
(()
=>
{
canvas
.
toBlob
((
blob
)
=>
{
// Restore UI and reticle visibility
overlayEl
.
style
.
pointerEvents
=
prevPointer
;
overlayEl
.
style
.
opacity
=
prevOpacity
;
if
(
reticle
)
reticle
.
visible
=
prevReticleVisible
;
if
(
!
blob
)
return
;
// Create download link
const
url
=
URL
.
createObjectURL
(
blob
);
const
a
=
document
.
createElement
(
"
a
"
);
const
ts
=
new
Date
().
toISOString
().
replace
(
/
[
:.
]
/g
,
"
-
"
);
a
.
href
=
url
;
a
.
download
=
`ar-snapshot-
${
ts
}
.png`
;
document
.
body
.
appendChild
(
a
);
a
.
click
();
// trigger download
a
.
remove
();
URL
.
revokeObjectURL
(
url
);
},
"
image/png
"
);
});
};
// --- 🔸 Handle user selection from bottom bar ---
// --- 🔸 Handle user selection from bottom bar ---
const
handleSelect
=
(
name
:
string
)
=>
{
const
handleSelect
=
(
name
:
string
)
=>
{
setSelectedObject
(
name
);
// update state
setSelectedObject
(
name
);
// update state
(
window
as
any
).
addSelectedObject
?.(
name
);
// add object immediately
(
window
as
any
).
addSelectedObject
?.(
name
);
// add object immediately
};
};
const
items
=
[];
const
items
:
{
name
:
string
;
emoji
:
string
;
}[]
|
{
name
:
ModelName
;
emoji
:
string
;
}[]
=
[];
for
(
const
key
in
ModelName
)
{
(
Object
.
keys
(
ModelName
)
as
Array
<
keyof
typeof
ModelName
>
).
forEach
((
key
)
=>
{
const
model
=
ModelName
[
key
];
items
.
push
({
items
.
push
({
name
:
ModelName
[
key
as
keyof
typeof
ModelName
]
,
name
:
model
,
emoji
:
ModelEmojiMap
[
key
as
keyof
typeof
ModelEmojiMap
],
emoji
:
ModelEmojiMap
[
model
],
});
});
}
});
console
.
log
(
items
);
// --- 🔸 Render layout ---
// --- 🔸 Render layout ---
return
(
return
(
...
@@ -232,26 +194,9 @@ for (const key in ModelName) {
...
@@ -232,26 +194,9 @@ for (const key in ModelName) {
ref
=
{
overlayRef
}
ref
=
{
overlayRef
}
style
=
{
{
position
:
"
absolute
"
,
width
:
"
100%
"
,
height
:
"
100%
"
}
}
style
=
{
{
position
:
"
absolute
"
,
width
:
"
100%
"
,
height
:
"
100%
"
}
}
>
>
{
/* 📷 Screenshot button (top-right corner) */
}
{
/* Screenshot button moved to its own component */
}
<
button
<
ScreenshotButton
mountRef
=
{
mountRef
}
overlayRef
=
{
overlayRef
}
reticleRef
=
{
reticleRef
}
/>
onClick
=
{
captureAndDownload
}
aria-label
=
"Save snapshot"
style
=
{
{
position
:
"
absolute
"
,
top
:
"
4rem
"
,
right
:
"
1.5rem
"
,
zIndex
:
1000
,
padding
:
"
10px 12px
"
,
borderRadius
:
12
,
border
:
"
none
"
,
background
:
"
white
"
,
fontSize
:
18
,
boxShadow
:
"
0 2px 8px rgba(0,0,0,0.25)
"
,
cursor
:
"
pointer
"
,
}
}
>
📷
</
button
>
{
/* Bottom bar appears only during active AR session */
}
{
/* Bottom bar appears only during active AR session */
}
{
/*{isSessionActive && (*/
}
{
/*{isSessionActive && (*/
}
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment