mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-07-15 16:34:49 +00:00
improve mission selector
This commit is contained in:
parent
af17b43584
commit
10b4a65a87
18 changed files with 594 additions and 183 deletions
28
app/page.tsx
28
app/page.tsx
|
|
@ -37,19 +37,21 @@ function MapInspector() {
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<main>
|
<main>
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<Canvas shadows frameloop="always">
|
<div id="canvasContainer">
|
||||||
<CamerasProvider>
|
<Canvas shadows frameloop="always">
|
||||||
<AudioProvider>
|
<CamerasProvider>
|
||||||
<Mission key={missionName} name={missionName} />
|
<AudioProvider>
|
||||||
<ObserverCamera />
|
<Mission key={missionName} name={missionName} />
|
||||||
<DebugElements />
|
<ObserverCamera />
|
||||||
<ObserverControls />
|
<DebugElements />
|
||||||
</AudioProvider>
|
<ObserverControls />
|
||||||
</CamerasProvider>
|
</AudioProvider>
|
||||||
<EffectComposer>
|
</CamerasProvider>
|
||||||
<N8AO intensity={3} aoRadius={3} quality="performance" />
|
<EffectComposer>
|
||||||
</EffectComposer>
|
<N8AO intensity={3} aoRadius={3} quality="performance" />
|
||||||
</Canvas>
|
</EffectComposer>
|
||||||
|
</Canvas>
|
||||||
|
</div>
|
||||||
<InspectorControls
|
<InspectorControls
|
||||||
missionName={missionName}
|
missionName={missionName}
|
||||||
onChangeMission={setMissionName}
|
onChangeMission={setMissionName}
|
||||||
|
|
|
||||||
176
app/style.css
176
app/style.css
|
|
@ -1,10 +1,16 @@
|
||||||
html,
|
html {
|
||||||
body {
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: black;
|
background: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-family:
|
font-family:
|
||||||
system-ui,
|
system-ui,
|
||||||
|
|
@ -21,11 +27,25 @@ html {
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#canvasContainer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
#controls {
|
#controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -35,9 +55,10 @@ main {
|
||||||
left: 0;
|
left: 0;
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 10px 12px 10px 8px;
|
padding: 8px 12px 8px 8px;
|
||||||
border-radius: 0 0 4px 0;
|
border-radius: 0 0 4px 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CheckboxField {
|
.CheckboxField {
|
||||||
|
|
@ -87,3 +108,152 @@ main {
|
||||||
.AxisLabel[data-axis="z"] {
|
.AxisLabel[data-axis="z"] {
|
||||||
color: rgb(0, 153, 255);
|
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,[],""]
|
2:I[9766,[],""]
|
||||||
3:I[8924,[],""]
|
3:I[8924,[],""]
|
||||||
4:I[1959,[],"ClientPageRoot"]
|
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"]
|
8:I[4431,[],"OutletBoundary"]
|
||||||
a:I[5278,[],"AsyncMetadataOutlet"]
|
a:I[5278,[],"AsyncMetadataOutlet"]
|
||||||
c:I[4431,[],"ViewportBoundary"]
|
c:I[4431,[],"ViewportBoundary"]
|
||||||
e:I[4431,[],"MetadataBoundary"]
|
e:I[4431,[],"MetadataBoundary"]
|
||||||
f:"$Sreact.suspense"
|
f:"$Sreact.suspense"
|
||||||
11:I[7150,[],""]
|
11:I[7150,[],""]
|
||||||
:HL["/t2-mapper/_next/static/css/534ae28a03b00388.css","style"]
|
:HL["/t2-mapper/_next/static/css/9e91738631ff0ad7.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}
|
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:{}
|
6:{}
|
||||||
7:"$0:f:0:1:2:children:1:props:children:0:props:params"
|
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"}]]
|
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",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ariakit/react": "^0.4.20",
|
||||||
"@react-three/drei": "^10.7.6",
|
"@react-three/drei": "^10.7.6",
|
||||||
"@react-three/fiber": "^9.3.0",
|
"@react-three/fiber": "^9.3.0",
|
||||||
"@react-three/postprocessing": "^3.0.4",
|
"@react-three/postprocessing": "^3.0.4",
|
||||||
"@tanstack/react-query": "^5.90.8",
|
"@tanstack/react-query": "^5.90.8",
|
||||||
"ignore": "^7.0.5",
|
"ignore": "^7.0.5",
|
||||||
"lodash.orderby": "^4.6.0",
|
"lodash.orderby": "^4.6.0",
|
||||||
|
"match-sorter": "^8.2.0",
|
||||||
"next": "^15.5.2",
|
"next": "^15.5.2",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
|
@ -38,6 +40,44 @@
|
||||||
"vitest": "^4.0.14"
|
"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": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.28.4",
|
"version": "7.28.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||||
|
|
@ -505,6 +545,31 @@
|
||||||
"node": ">=18"
|
"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": {
|
"node_modules/@img/sharp-darwin-arm64": {
|
||||||
"version": "0.34.3",
|
"version": "0.34.3",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz",
|
"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"
|
"@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": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
|
@ -3657,6 +3732,12 @@
|
||||||
"util-deprecate": "~1.0.1"
|
"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": {
|
"node_modules/require-from-string": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,14 @@
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ariakit/react": "^0.4.20",
|
||||||
"@react-three/drei": "^10.7.6",
|
"@react-three/drei": "^10.7.6",
|
||||||
"@react-three/fiber": "^9.3.0",
|
"@react-three/fiber": "^9.3.0",
|
||||||
"@react-three/postprocessing": "^3.0.4",
|
"@react-three/postprocessing": "^3.0.4",
|
||||||
"@tanstack/react-query": "^5.90.8",
|
"@tanstack/react-query": "^5.90.8",
|
||||||
"ignore": "^7.0.5",
|
"ignore": "^7.0.5",
|
||||||
"lodash.orderby": "^4.6.0",
|
"lodash.orderby": "^4.6.0",
|
||||||
|
"match-sorter": "^8.2.0",
|
||||||
"next": "^15.5.2",
|
"next": "^15.5.2",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^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 { useControls, useDebug, useSettings } from "./SettingsProvider";
|
||||||
import orderBy from "lodash.orderby";
|
import { MissionSelect } from "./MissionSelect";
|
||||||
|
|
||||||
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"],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export function InspectorControls({
|
export function InspectorControls({
|
||||||
missionName,
|
missionName,
|
||||||
|
|
@ -102,19 +21,6 @@ export function InspectorControls({
|
||||||
const { speedMultiplier, setSpeedMultiplier } = useControls();
|
const { speedMultiplier, setSpeedMultiplier } = useControls();
|
||||||
const { debugMode, setDebugMode } = useDebug();
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
id="controls"
|
id="controls"
|
||||||
|
|
@ -122,32 +28,7 @@ export function InspectorControls({
|
||||||
onPointerDown={(e) => e.stopPropagation()}
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<select
|
<MissionSelect value={missionName} onChange={onChangeMission} />
|
||||||
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>
|
|
||||||
<div className="CheckboxField">
|
<div className="CheckboxField">
|
||||||
<input
|
<input
|
||||||
id="fogInput"
|
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.
|
// Don't let KeyboardControls handle stuff when metaKey is held.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKey = (e: KeyboardEvent) => {
|
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) {
|
if (e.metaKey) {
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue