mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-01-19 20:25:01 +00:00
improve mission selector
This commit is contained in:
parent
af17b43584
commit
10b4a65a87
28
app/page.tsx
28
app/page.tsx
|
|
@ -37,19 +37,21 @@ function MapInspector() {
|
|||
<QueryClientProvider client={queryClient}>
|
||||
<main>
|
||||
<SettingsProvider>
|
||||
<Canvas shadows frameloop="always">
|
||||
<CamerasProvider>
|
||||
<AudioProvider>
|
||||
<Mission key={missionName} name={missionName} />
|
||||
<ObserverCamera />
|
||||
<DebugElements />
|
||||
<ObserverControls />
|
||||
</AudioProvider>
|
||||
</CamerasProvider>
|
||||
<EffectComposer>
|
||||
<N8AO intensity={3} aoRadius={3} quality="performance" />
|
||||
</EffectComposer>
|
||||
</Canvas>
|
||||
<div id="canvasContainer">
|
||||
<Canvas shadows frameloop="always">
|
||||
<CamerasProvider>
|
||||
<AudioProvider>
|
||||
<Mission key={missionName} name={missionName} />
|
||||
<ObserverCamera />
|
||||
<DebugElements />
|
||||
<ObserverControls />
|
||||
</AudioProvider>
|
||||
</CamerasProvider>
|
||||
<EffectComposer>
|
||||
<N8AO intensity={3} aoRadius={3} quality="performance" />
|
||||
</EffectComposer>
|
||||
</Canvas>
|
||||
</div>
|
||||
<InspectorControls
|
||||
missionName={missionName}
|
||||
onChangeMission={setMissionName}
|
||||
|
|
|
|||
176
app/style.css
176
app/style.css
|
|
@ -1,10 +1,16 @@
|
|||
html,
|
||||
body {
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: black;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family:
|
||||
system-ui,
|
||||
|
|
@ -21,11 +27,25 @@ html {
|
|||
font-size: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#canvasContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -35,9 +55,10 @@ main {
|
|||
left: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: #fff;
|
||||
padding: 10px 12px 10px 8px;
|
||||
padding: 8px 12px 8px 8px;
|
||||
border-radius: 0 0 4px 0;
|
||||
font-size: 13px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.CheckboxField {
|
||||
|
|
@ -87,3 +108,152 @@ main {
|
|||
.AxisLabel[data-axis="z"] {
|
||||
color: rgb(0, 153, 255);
|
||||
}
|
||||
|
||||
/* MissionSelect combobox styles */
|
||||
.MissionSelect-inputWrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.MissionSelect-shortcut {
|
||||
position: absolute;
|
||||
right: 7px;
|
||||
font-family: system-ui, sans-serif;
|
||||
font-size: 11px;
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.MissionSelect-input[aria-expanded="true"] ~ .MissionSelect-shortcut {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.MissionSelect-input {
|
||||
width: 240px;
|
||||
padding: 6px 36px 6px 8px;
|
||||
font-size: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 3px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: #fff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.MissionSelect-input[aria-expanded="true"] {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.MissionSelect-input:focus {
|
||||
border-color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.MissionSelect-input::placeholder {
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.MissionSelect-popover {
|
||||
z-index: 100;
|
||||
min-width: 320px;
|
||||
max-height: var(--popover-available-height, 90vh);
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
background: rgba(20, 20, 20, 0.95);
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.MissionSelect-list {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.MissionSelect-list:has(> .MissionSelect-group:first-child) {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.MissionSelect-group {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.MissionSelect-groupLabel {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding: 6px 8px 6px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: rgb(198, 202, 202);
|
||||
background: rgba(58, 69, 72, 0.95);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.MissionSelect-group:not(:last-child) {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.MissionSelect-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
margin: 4px 4px 0;
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
scroll-margin-top: 32px;
|
||||
}
|
||||
|
||||
.MissionSelect-list > .MissionSelect-item:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.MissionSelect-item[data-active-item] {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.MissionSelect-item[aria-selected="true"] {
|
||||
background: rgba(100, 150, 255, 0.3);
|
||||
}
|
||||
|
||||
.MissionSelect-itemHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.MissionSelect-itemName {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.MissionSelect-itemTypes {
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.MissionSelect-itemType {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
background: rgba(255, 157, 0, 0.4);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.MissionSelect-itemMissionName {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.MissionSelect-noResults {
|
||||
padding: 12px 8px;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
38
docs/_next/static/chunks/627-9fd7bd53939c6ff2.js
Normal file
38
docs/_next/static/chunks/627-9fd7bd53939c6ff2.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
body,html{margin:0;padding:0;background:black}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;font-size:100%}main{width:100vw;height:100vh}#controls{display:flex;align-items:center;gap:20px;position:fixed;top:0;left:0;background:rgba(0,0,0,.5);color:#fff;padding:10px 12px 10px 8px;border-radius:0 0 4px 0;font-size:13px}.CheckboxField,.Field{display:flex;align-items:center;gap:6px}#fovInput,#speedInput{max-width:80px}.StaticShapeLabel{background:rgba(0,0,0,.5);color:#fff;font-size:11px;white-space:nowrap;padding:1px 3px;border-radius:1px}.StatsPanel{left:auto!important;right:0}.AxisLabel{font-size:12px;pointer-events:none}.AxisLabel[data-axis=x]{color:rgb(255,153,0)}.AxisLabel[data-axis=y]{color:rgb(153,255,0)}.AxisLabel[data-axis=z]{color:rgb(0,153,255)}
|
||||
1
docs/_next/static/css/9e91738631ff0ad7.css
Normal file
1
docs/_next/static/css/9e91738631ff0ad7.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
html{box-sizing:border-box;margin:0;padding:0;background:black}*,:after,:before{box-sizing:inherit}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;font-size:100%}body{margin:0;padding:0}main{width:100vw;height:100vh}#canvasContainer{position:absolute;top:0;left:0;right:0;bottom:0;z-index:0}#controls{display:flex;align-items:center;gap:20px;position:fixed;top:0;left:0;background:rgba(0,0,0,.5);color:#fff;padding:8px 12px 8px 8px;border-radius:0 0 4px 0;font-size:13px;z-index:1}.CheckboxField,.Field{display:flex;align-items:center;gap:6px}#fovInput,#speedInput{max-width:80px}.StaticShapeLabel{background:rgba(0,0,0,.5);color:#fff;font-size:11px;white-space:nowrap;padding:1px 3px;border-radius:1px}.StatsPanel{left:auto!important;right:0}.AxisLabel{font-size:12px;pointer-events:none}.AxisLabel[data-axis=x]{color:rgb(255,153,0)}.AxisLabel[data-axis=y]{color:rgb(153,255,0)}.AxisLabel[data-axis=z]{color:rgb(0,153,255)}.MissionSelect-inputWrapper{position:relative;display:flex;align-items:center}.MissionSelect-shortcut{position:absolute;right:7px;font-family:system-ui,sans-serif;font-size:11px;padding:1px 4px;border-radius:3px;background:rgba(255,255,255,.15);color:rgba(255,255,255,.6);pointer-events:none}.MissionSelect-input[aria-expanded=true]~.MissionSelect-shortcut{display:none}.MissionSelect-input{width:240px;padding:6px 36px 6px 8px;font-size:14px;border:1px solid rgba(255,255,255,.3);border-radius:3px;background:rgba(0,0,0,.6);color:#fff;outline:none}.MissionSelect-input[aria-expanded=true]{padding-right:8px}.MissionSelect-input:focus{border-color:rgba(255,255,255,.6)}.MissionSelect-input::placeholder{color:#fff;font-weight:600}.MissionSelect-popover{z-index:100;min-width:320px;max-height:var(--popover-available-height,90vh);overflow-y:auto;overscroll-behavior:contain;background:rgba(20,20,20,.95);border:1px solid rgba(255,255,255,.5);border-radius:3px;box-shadow:0 8px 24px rgba(0,0,0,.6)}.MissionSelect-list{padding:4px 0}.MissionSelect-list:has(>.MissionSelect-group:first-child){padding-top:0}.MissionSelect-group{padding-bottom:4px}.MissionSelect-groupLabel{position:-webkit-sticky;position:sticky;top:0;padding:6px 8px 6px 12px;font-size:13px;font-weight:600;color:rgb(198,202,202);background:rgba(58,69,72,.95);z-index:1}.MissionSelect-group:not(:last-child),.MissionSelect-groupLabel{border-bottom:1px solid rgba(255,255,255,.3)}.MissionSelect-item{display:flex;flex-direction:column;gap:1px;margin:4px 4px 0;padding:6px 8px;border-radius:4px;cursor:pointer;outline:none;scroll-margin-top:32px}.MissionSelect-list>.MissionSelect-item:first-child{margin-top:0}.MissionSelect-item[data-active-item]{background:rgba(255,255,255,.15)}.MissionSelect-item[aria-selected=true]{background:rgba(100,150,255,.3)}.MissionSelect-itemHeader{display:flex;align-items:center;gap:6px}.MissionSelect-itemName{font-size:14px;font-weight:600;color:#fff}.MissionSelect-itemTypes{display:flex;gap:3px}.MissionSelect-itemType{font-size:10px;font-weight:600;padding:2px 5px;border-radius:3px;background:rgba(255,157,0,.4);color:#fff}.MissionSelect-itemMissionName{font-size:12px;color:rgba(255,255,255,.5)}.MissionSelect-noResults{padding:12px 8px;font-size:13px;color:rgba(255,255,255,.5);text-align:center}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -2,15 +2,15 @@
|
|||
2:I[9766,[],""]
|
||||
3:I[8924,[],""]
|
||||
4:I[1959,[],"ClientPageRoot"]
|
||||
5:I[8517,["367","static/chunks/b536a0f1-05ee2c75df4a3b9d.js","831","static/chunks/bd904a5c-3aea2adebde6f067.js","664","static/chunks/a3cd4a83-5c5b758da206345b.js","794","static/chunks/f6211eb1-4f3105d2434536dc.js","413","static/chunks/1329d575-16915d95397758f8.js","504","static/chunks/504-86077f19f8e7280c.js","974","static/chunks/app/page-c6fae8e66dd2a27e.js"],"default"]
|
||||
5:I[8283,["367","static/chunks/b536a0f1-05ee2c75df4a3b9d.js","831","static/chunks/bd904a5c-3aea2adebde6f067.js","664","static/chunks/a3cd4a83-5c5b758da206345b.js","794","static/chunks/f6211eb1-4f3105d2434536dc.js","413","static/chunks/1329d575-16915d95397758f8.js","627","static/chunks/627-9fd7bd53939c6ff2.js","974","static/chunks/app/page-fa222674b6748878.js"],"default"]
|
||||
8:I[4431,[],"OutletBoundary"]
|
||||
a:I[5278,[],"AsyncMetadataOutlet"]
|
||||
c:I[4431,[],"ViewportBoundary"]
|
||||
e:I[4431,[],"MetadataBoundary"]
|
||||
f:"$Sreact.suspense"
|
||||
11:I[7150,[],""]
|
||||
:HL["/t2-mapper/_next/static/css/534ae28a03b00388.css","style"]
|
||||
0:{"P":null,"b":"K9x0gdhs26x3eBYhFHgIi","p":"/t2-mapper","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/css/534ae28a03b00388.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","searchParams":{},"params":{},"promises":["$@6","$@7"]}],null,["$","$L8",null,{"children":["$L9",["$","$La",null,{"promise":"$@b"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lc",null,{"children":"$Ld"}],null],["$","$Le",null,{"children":["$","div",null,{"hidden":true,"children":["$","$f",null,{"fallback":null,"children":"$L10"}]}]}]]}],false]],"m":"$undefined","G":["$11",[]],"s":false,"S":true}
|
||||
:HL["/t2-mapper/_next/static/css/9e91738631ff0ad7.css","style"]
|
||||
0:{"P":null,"b":"mHNhcJMqff39xhZuHO3JA","p":"/t2-mapper","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/css/9e91738631ff0ad7.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","searchParams":{},"params":{},"promises":["$@6","$@7"]}],null,["$","$L8",null,{"children":["$L9",["$","$La",null,{"promise":"$@b"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lc",null,{"children":"$Ld"}],null],["$","$Le",null,{"children":["$","div",null,{"hidden":true,"children":["$","$f",null,{"fallback":null,"children":"$L10"}]}]}]]}],false]],"m":"$undefined","G":["$11",[]],"s":false,"S":true}
|
||||
6:{}
|
||||
7:"$0:f:0:1:2:children:1:props:children:0:props:params"
|
||||
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
||||
|
|
|
|||
81
package-lock.json
generated
81
package-lock.json
generated
|
|
@ -9,12 +9,14 @@
|
|||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ariakit/react": "^0.4.20",
|
||||
"@react-three/drei": "^10.7.6",
|
||||
"@react-three/fiber": "^9.3.0",
|
||||
"@react-three/postprocessing": "^3.0.4",
|
||||
"@tanstack/react-query": "^5.90.8",
|
||||
"ignore": "^7.0.5",
|
||||
"lodash.orderby": "^4.6.0",
|
||||
"match-sorter": "^8.2.0",
|
||||
"next": "^15.5.2",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
|
|
@ -38,6 +40,44 @@
|
|||
"vitest": "^4.0.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@ariakit/core": {
|
||||
"version": "0.4.17",
|
||||
"resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.17.tgz",
|
||||
"integrity": "sha512-OmbUcVZgmQw0AvpX5urCAi3KtEuD30DG8W8gpQVzFpCUWUtJ21bmc6a4s2rm2g1oKPIVShy61FGLtKdKLaTG6g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ariakit/react": {
|
||||
"version": "0.4.20",
|
||||
"resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.20.tgz",
|
||||
"integrity": "sha512-1X44x3co7MInk5SV4lSvRdy8Nwrt56YNBreKPkcZ/LlwdmY2/2r4A26I7Kzhv+VYIxDTavZYrqOlWnix5ojceg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ariakit/react-core": "0.4.20"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ariakit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ariakit/react-core": {
|
||||
"version": "0.4.20",
|
||||
"resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.20.tgz",
|
||||
"integrity": "sha512-4rfmaKgSIctHRDrA4wt8MLSI4rNA6wyD7XSTShghHrJfDEXujuOoZqvmPjtMr///kWfW9OV9USOOn8ie/7H/nw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ariakit/core": "0.4.17",
|
||||
"@floating-ui/dom": "^1.0.0",
|
||||
"use-sync-external-store": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
|
|
@ -505,6 +545,31 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
|
||||
"integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
|
||||
"integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.7.3",
|
||||
"@floating-ui/utils": "^0.2.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
|
||||
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz",
|
||||
|
|
@ -3089,6 +3154,16 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/match-sorter": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-8.2.0.tgz",
|
||||
"integrity": "sha512-qRVB7wYMJXizAWR4TKo5UYwgW7oAVzA8V9jve0wGzRvV91ou9dcqL+/2gJtD0PZ/Pm2Fq6cVT4VHXHmDFVMGRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.8",
|
||||
"remove-accents": "0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
|
|
@ -3657,6 +3732,12 @@
|
|||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/remove-accents": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",
|
||||
"integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -22,12 +22,14 @@
|
|||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ariakit/react": "^0.4.20",
|
||||
"@react-three/drei": "^10.7.6",
|
||||
"@react-three/fiber": "^9.3.0",
|
||||
"@react-three/postprocessing": "^3.0.4",
|
||||
"@tanstack/react-query": "^5.90.8",
|
||||
"ignore": "^7.0.5",
|
||||
"lodash.orderby": "^4.6.0",
|
||||
"match-sorter": "^8.2.0",
|
||||
"next": "^15.5.2",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
|
|
|
|||
|
|
@ -1,86 +1,5 @@
|
|||
import { Fragment, useMemo } from "react";
|
||||
import { getMissionInfo, getMissionList, getSourceAndPath } from "../manifest";
|
||||
import { useControls, useDebug, useSettings } from "./SettingsProvider";
|
||||
import orderBy from "lodash.orderby";
|
||||
|
||||
const excludeMissions = new Set([
|
||||
"SkiFree",
|
||||
"SkiFree_Daily",
|
||||
"SkiFree_Randomizer",
|
||||
]);
|
||||
|
||||
const sourceGroupNames = {
|
||||
"missions.vl2": "Official",
|
||||
"TR2final105-client.vl2": "Team Rabbit 2",
|
||||
"z_mappacks/CTF/Classic_maps_v1.vl2": "Classic",
|
||||
"z_mappacks/CTF/DynamixFinalPack.vl2": "Official",
|
||||
"z_mappacks/CTF/KryMapPack_b3EDIT.vl2": "KryMapPack",
|
||||
"z_mappacks/CTF/S5maps.vl2": "S5",
|
||||
"z_mappacks/CTF/S8maps.vl2": "S8",
|
||||
"z_mappacks/CTF/TWL-MapPack.vl2": "TWL",
|
||||
"z_mappacks/CTF/TWL-MapPackEDIT.vl2": "TWL",
|
||||
"z_mappacks/CTF/TWL2-MapPack.vl2": "TWL2",
|
||||
"z_mappacks/CTF/TWL2-MapPackEDIT.vl2": "TWL2",
|
||||
"z_mappacks/TWL_T2arenaOfficialMaps.vl2": "Arena",
|
||||
"z_mappacks/z_DMP2-V0.6.vl2": "DMP2 (Discord Map Pack)",
|
||||
"z_mappacks/zDMP-4.7.3DX.vl2": "DMP (Discord Map Pack)",
|
||||
// "SkiFreeGameType.vl2": "SkiFree",
|
||||
};
|
||||
|
||||
const dirGroupNames = {
|
||||
"z_mappacks/DM": "DM",
|
||||
"z_mappacks/LCTF": "LCTF",
|
||||
"z_mappacks/Lak": "LakRabbit",
|
||||
};
|
||||
|
||||
const getDirName = (sourcePath: string) => {
|
||||
const match = sourcePath.match(/^(.*)(\/[^/]+)$/);
|
||||
return match ? match[1] : "";
|
||||
};
|
||||
|
||||
const groupedMissions = getMissionList().reduce(
|
||||
(groupMap, missionName) => {
|
||||
const missionInfo = getMissionInfo(missionName);
|
||||
const [sourcePath] = getSourceAndPath(missionInfo.resourcePath);
|
||||
const sourceDir = getDirName(sourcePath);
|
||||
const groupName =
|
||||
sourceGroupNames[sourcePath] ?? dirGroupNames[sourceDir] ?? null;
|
||||
const groupMissions = groupMap.get(groupName) ?? [];
|
||||
if (!excludeMissions.has(missionName)) {
|
||||
groupMissions.push({
|
||||
resourcePath: missionInfo.resourcePath,
|
||||
missionName,
|
||||
displayName: missionInfo.displayName,
|
||||
sourcePath,
|
||||
});
|
||||
groupMap.set(groupName, groupMissions);
|
||||
}
|
||||
return groupMap;
|
||||
},
|
||||
new Map<
|
||||
string | null,
|
||||
Array<{
|
||||
resourcePath: string;
|
||||
missionName: string;
|
||||
displayName: string;
|
||||
sourcePath: string;
|
||||
}>
|
||||
>(),
|
||||
);
|
||||
|
||||
groupedMissions.forEach((groupMissions, groupName) => {
|
||||
groupedMissions.set(
|
||||
groupName,
|
||||
orderBy(
|
||||
groupMissions,
|
||||
[
|
||||
(missionInfo) =>
|
||||
(missionInfo.displayName || missionInfo.missionName).toLowerCase(),
|
||||
],
|
||||
["asc"],
|
||||
),
|
||||
);
|
||||
});
|
||||
import { MissionSelect } from "./MissionSelect";
|
||||
|
||||
export function InspectorControls({
|
||||
missionName,
|
||||
|
|
@ -102,19 +21,6 @@ export function InspectorControls({
|
|||
const { speedMultiplier, setSpeedMultiplier } = useControls();
|
||||
const { debugMode, setDebugMode } = useDebug();
|
||||
|
||||
const groupedMissionOptions = useMemo(() => {
|
||||
const groups = orderBy(
|
||||
Array.from(groupedMissions.entries()),
|
||||
[
|
||||
([groupName]) =>
|
||||
groupName === "Official" ? 0 : groupName == null ? 2 : 1,
|
||||
([groupName]) => (groupName ? groupName.toLowerCase() : ""),
|
||||
],
|
||||
["asc", "asc"],
|
||||
);
|
||||
return groups;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
id="controls"
|
||||
|
|
@ -122,32 +28,7 @@ export function InspectorControls({
|
|||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<select
|
||||
id="missionList"
|
||||
value={missionName}
|
||||
onChange={(event) => onChangeMission(event.target.value)}
|
||||
>
|
||||
{groupedMissionOptions.map(([groupName, groupMissions]) =>
|
||||
groupName ? (
|
||||
<optgroup key={groupName} label={groupName}>
|
||||
{groupMissions.map((mission) => (
|
||||
<option key={mission.missionName} value={mission.missionName}>
|
||||
{mission.displayName || mission.missionName}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
) : (
|
||||
<Fragment key="null">
|
||||
<hr />
|
||||
{groupMissions.map((mission) => (
|
||||
<option key={mission.missionName} value={mission.missionName}>
|
||||
{mission.displayName || mission.missionName}
|
||||
</option>
|
||||
))}
|
||||
</Fragment>
|
||||
),
|
||||
)}
|
||||
</select>
|
||||
<MissionSelect value={missionName} onChange={onChangeMission} />
|
||||
<div className="CheckboxField">
|
||||
<input
|
||||
id="fogInput"
|
||||
|
|
|
|||
271
src/components/MissionSelect.tsx
Normal file
271
src/components/MissionSelect.tsx
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
import {
|
||||
Fragment,
|
||||
startTransition,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
ComboboxPopover,
|
||||
ComboboxProvider,
|
||||
ComboboxGroup,
|
||||
ComboboxGroupLabel,
|
||||
useComboboxStore,
|
||||
} from "@ariakit/react";
|
||||
import { matchSorter } from "match-sorter";
|
||||
import { getMissionInfo, getMissionList, getSourceAndPath } from "../manifest";
|
||||
import orderBy from "lodash.orderby";
|
||||
|
||||
const excludeMissions = new Set([
|
||||
"SkiFree",
|
||||
"SkiFree_Daily",
|
||||
"SkiFree_Randomizer",
|
||||
]);
|
||||
|
||||
const sourceGroupNames: Record<string, string> = {
|
||||
"missions.vl2": "Official",
|
||||
"TR2final105-client.vl2": "Team Rabbit 2",
|
||||
"z_mappacks/CTF/Classic_maps_v1.vl2": "Classic",
|
||||
"z_mappacks/CTF/DynamixFinalPack.vl2": "Official",
|
||||
"z_mappacks/CTF/KryMapPack_b3EDIT.vl2": "KryMapPack",
|
||||
"z_mappacks/CTF/S5maps.vl2": "S5",
|
||||
"z_mappacks/CTF/S8maps.vl2": "S8",
|
||||
"z_mappacks/CTF/TWL-MapPack.vl2": "TWL",
|
||||
"z_mappacks/CTF/TWL-MapPackEDIT.vl2": "TWL",
|
||||
"z_mappacks/CTF/TWL2-MapPack.vl2": "TWL2",
|
||||
"z_mappacks/CTF/TWL2-MapPackEDIT.vl2": "TWL2",
|
||||
"z_mappacks/TWL_T2arenaOfficialMaps.vl2": "Arena",
|
||||
"z_mappacks/z_DMP2-V0.6.vl2": "DMP2 (Discord Map Pack)",
|
||||
"z_mappacks/zDMP-4.7.3DX.vl2": "DMP (Discord Map Pack)",
|
||||
};
|
||||
|
||||
const dirGroupNames: Record<string, string> = {
|
||||
"z_mappacks/DM": "DM",
|
||||
"z_mappacks/LCTF": "LCTF",
|
||||
"z_mappacks/Lak": "LakRabbit",
|
||||
};
|
||||
|
||||
interface MissionItem {
|
||||
resourcePath: string;
|
||||
missionName: string;
|
||||
displayName: string;
|
||||
sourcePath: string;
|
||||
groupName: string | null;
|
||||
missionTypes: string[];
|
||||
}
|
||||
|
||||
const getDirName = (sourcePath: string) => {
|
||||
const match = sourcePath.match(/^(.*)(\/[^/]+)$/);
|
||||
return match ? match[1] : "";
|
||||
};
|
||||
|
||||
const allMissions: MissionItem[] = getMissionList()
|
||||
.filter((name) => !excludeMissions.has(name))
|
||||
.map((missionName) => {
|
||||
const missionInfo = getMissionInfo(missionName);
|
||||
const [sourcePath] = getSourceAndPath(missionInfo.resourcePath);
|
||||
const sourceDir = getDirName(sourcePath);
|
||||
const groupName =
|
||||
sourceGroupNames[sourcePath] ?? dirGroupNames[sourceDir] ?? null;
|
||||
return {
|
||||
resourcePath: missionInfo.resourcePath,
|
||||
missionName,
|
||||
displayName: missionInfo.displayName,
|
||||
sourcePath,
|
||||
groupName,
|
||||
missionTypes: missionInfo.missionTypes,
|
||||
};
|
||||
});
|
||||
|
||||
const missionsByName = new Map(allMissions.map((m) => [m.missionName, m]));
|
||||
|
||||
function groupMissions(missions: MissionItem[]) {
|
||||
const groupMap = new Map<string | null, MissionItem[]>();
|
||||
|
||||
for (const mission of missions) {
|
||||
const group = groupMap.get(mission.groupName) ?? [];
|
||||
group.push(mission);
|
||||
groupMap.set(mission.groupName, group);
|
||||
}
|
||||
|
||||
groupMap.forEach((groupMissions, groupName) => {
|
||||
groupMap.set(
|
||||
groupName,
|
||||
orderBy(
|
||||
groupMissions,
|
||||
[(m) => (m.displayName || m.missionName).toLowerCase()],
|
||||
["asc"],
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
return orderBy(
|
||||
Array.from(groupMap.entries()),
|
||||
[
|
||||
([groupName]) =>
|
||||
groupName === "Official" ? 0 : groupName == null ? 2 : 1,
|
||||
([groupName]) => (groupName ? groupName.toLowerCase() : ""),
|
||||
],
|
||||
["asc", "asc"],
|
||||
);
|
||||
}
|
||||
|
||||
const defaultGroups = groupMissions(allMissions);
|
||||
|
||||
const isMac =
|
||||
typeof navigator !== "undefined" &&
|
||||
/Mac|iPhone|iPad|iPod/.test(navigator.platform);
|
||||
|
||||
function MissionItemContent({ mission }: { mission: MissionItem }) {
|
||||
return (
|
||||
<>
|
||||
<span className="MissionSelect-itemHeader">
|
||||
<span className="MissionSelect-itemName">
|
||||
{mission.displayName || mission.missionName}
|
||||
</span>
|
||||
{mission.missionTypes.length > 0 && (
|
||||
<span className="MissionSelect-itemTypes">
|
||||
{mission.missionTypes.map((type) => (
|
||||
<span key={type} className="MissionSelect-itemType">
|
||||
{type}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="MissionSelect-itemMissionName">
|
||||
{mission.missionName}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function MissionSelect({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: string;
|
||||
onChange: (missionName: string) => void;
|
||||
}) {
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const combobox = useComboboxStore({
|
||||
resetValueOnHide: true,
|
||||
selectedValue: value,
|
||||
setSelectedValue: (newValue) => {
|
||||
if (newValue) onChange(newValue);
|
||||
},
|
||||
setValue: (value) => {
|
||||
startTransition(() => setSearchValue(value));
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
inputRef.current?.focus();
|
||||
combobox.show();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, [combobox]);
|
||||
|
||||
const selectedMission = missionsByName.get(value);
|
||||
|
||||
// When searching, return flat list sorted by relevance; otherwise return grouped
|
||||
const filteredResults = useMemo(() => {
|
||||
if (!searchValue)
|
||||
return { type: "grouped" as const, groups: defaultGroups };
|
||||
const matches = matchSorter(allMissions, searchValue, {
|
||||
keys: ["displayName", "missionName"],
|
||||
});
|
||||
return { type: "flat" as const, missions: matches };
|
||||
}, [searchValue]);
|
||||
|
||||
const displayValue = selectedMission
|
||||
? selectedMission.displayName || selectedMission.missionName
|
||||
: value;
|
||||
|
||||
const noResults =
|
||||
filteredResults.type === "flat"
|
||||
? filteredResults.missions.length === 0
|
||||
: filteredResults.groups.length === 0;
|
||||
|
||||
return (
|
||||
<ComboboxProvider store={combobox}>
|
||||
<div className="MissionSelect-inputWrapper">
|
||||
<Combobox
|
||||
ref={inputRef}
|
||||
autoSelect
|
||||
placeholder={displayValue}
|
||||
className="MissionSelect-input"
|
||||
onFocus={() => {
|
||||
document.exitPointerLock();
|
||||
combobox.show();
|
||||
}}
|
||||
/>
|
||||
<kbd className="MissionSelect-shortcut">{isMac ? "⌘K" : "^K"}</kbd>
|
||||
</div>
|
||||
<ComboboxPopover gutter={4} fitViewport className="MissionSelect-popover">
|
||||
<ComboboxList className="MissionSelect-list">
|
||||
{filteredResults.type === "flat"
|
||||
? filteredResults.missions.map((mission) => (
|
||||
<ComboboxItem
|
||||
key={mission.missionName}
|
||||
value={mission.missionName}
|
||||
className="MissionSelect-item"
|
||||
focusOnHover
|
||||
>
|
||||
<MissionItemContent mission={mission} />
|
||||
</ComboboxItem>
|
||||
))
|
||||
: filteredResults.groups.map(([groupName, missions]) =>
|
||||
groupName ? (
|
||||
<ComboboxGroup
|
||||
key={groupName}
|
||||
className="MissionSelect-group"
|
||||
>
|
||||
<ComboboxGroupLabel className="MissionSelect-groupLabel">
|
||||
{groupName}
|
||||
</ComboboxGroupLabel>
|
||||
{missions.map((mission) => (
|
||||
<ComboboxItem
|
||||
key={mission.missionName}
|
||||
value={mission.missionName}
|
||||
className="MissionSelect-item"
|
||||
focusOnHover
|
||||
>
|
||||
<MissionItemContent mission={mission} />
|
||||
</ComboboxItem>
|
||||
))}
|
||||
</ComboboxGroup>
|
||||
) : (
|
||||
<Fragment key="ungrouped">
|
||||
{missions.map((mission) => (
|
||||
<ComboboxItem
|
||||
key={mission.missionName}
|
||||
value={mission.missionName}
|
||||
className="MissionSelect-item"
|
||||
focusOnHover
|
||||
>
|
||||
<MissionItemContent mission={mission} />
|
||||
</ComboboxItem>
|
||||
))}
|
||||
</Fragment>
|
||||
),
|
||||
)}
|
||||
{noResults && (
|
||||
<div className="MissionSelect-noResults">No missions found</div>
|
||||
)}
|
||||
</ComboboxList>
|
||||
</ComboboxPopover>
|
||||
</ComboboxProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -183,6 +183,10 @@ export function ObserverControls() {
|
|||
// Don't let KeyboardControls handle stuff when metaKey is held.
|
||||
useEffect(() => {
|
||||
const handleKey = (e: KeyboardEvent) => {
|
||||
// Let Cmd/Ctrl+K pass through for search focus.
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
||||
return;
|
||||
}
|
||||
if (e.metaKey) {
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue