feat(ui): use LCE textinput

This commit is contained in:
neoapps-dev
2026-09-18 14:27:00 +03:00
parent 31639366d4
commit a243b8362c
21 changed files with 278 additions and 175 deletions
+19 -15
View File
@@ -433,7 +433,9 @@ const SkinViewer = memo(function SkinViewer({
renderer.domElement.addEventListener("mousedown", onMouseDown);
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
renderer.domElement.addEventListener("touchstart", onTouchStart, { passive: true });
renderer.domElement.addEventListener("touchstart", onTouchStart, {
passive: true,
});
window.addEventListener("touchmove", onTouchMove, { passive: false });
window.addEventListener("touchend", onTouchEnd);
return () => {
@@ -692,20 +694,22 @@ const SkinViewer = memo(function SkinViewer({
data-focus="0"
tabIndex={0}
>
<input
type="text"
value={username}
maxLength={16}
style={{ width: `${Math.max(username.length, 3) + 2}ch` }}
onChange={(e) => setUsername(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.currentTarget.blur();
e.stopPropagation();
}
}}
className="bg-transparent text-white focus:text-[#FFFF55] outline-none border-none text-center font-[var(--font-base)] mc-text-shadow tracking-widest text-xl cursor-text"
/>
<div>
<input
type="text"
value={username}
maxLength={16}
style={{ width: `${Math.max(username.length, 3) + 2}ch` }}
onChange={(e) => setUsername(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.currentTarget.blur();
e.stopPropagation();
}
}}
className="text-white focus:text-[#FFFF55] outline-none text-center font-[var(--font-base)] tracking-widest text-xl cursor-text"
/>
</div>
</div>
)}
{!legacyMode && (
+12 -4
View File
@@ -144,6 +144,7 @@ export default function CustomTUModal({
<label className="text-gray text-sm mc-text-shadow uppercase tracking-widest">
{t("modals.customTu.tuName")}
</label>
<div className="mc-textinput-outer">
<input
type="text"
autoFocus
@@ -151,39 +152,44 @@ export default function CustomTUModal({
onChange={(e) => setName(e.target.value)}
onFocus={() => setFocusIndex(0)}
placeholder={t("modals.customTu.namePlaceholder")}
className="w-full h-10 px-3 bg-black/40 border-2 border-[#373737] text-white text-base outline-none font-[var(--font-base)]"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
style={{ imageRendering: "pixelated" }}
/>
</div>
</div>
<div className="flex flex-col gap-1">
<label className="text-gray text-sm mc-text-shadow uppercase tracking-widest">
{t("modals.customTu.description")}
</label>
<div className="mc-textinput-outer">
<input
type="text"
value={desc}
onChange={(e) => setDesc(e.target.value)}
onFocus={() => setFocusIndex(1)}
placeholder={t("modals.customTu.descPlaceholder")}
className="w-full h-10 px-3 bg-black/40 border-2 border-[#373737] text-white text-base outline-none font-[var(--font-base)]"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
style={{ imageRendering: "pixelated" }}
/>
</div>
</div>
<div className="flex flex-col gap-1">
<label className="text-gray text-sm mc-text-shadow uppercase tracking-widest">
{t("modals.customTu.downloadUrl")}
</label>
<div className="mc-textinput-outer">
<input
type="text"
value={url}
onChange={(e) => setUrl(e.target.value)}
onFocus={() => setFocusIndex(2)}
placeholder={t("modals.customTu.urlPlaceholder")}
className="w-full h-10 px-3 bg-black/40 border-2 border-[#373737] text-white text-base outline-none font-[var(--font-base)]"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
style={{ imageRendering: "pixelated" }}
/>
</div>
</div>
{path && (
@@ -191,13 +197,15 @@ export default function CustomTUModal({
<label className="text-gray text-sm mc-text-shadow uppercase tracking-widest">
{t("modals.customTu.localPath")}
</label>
<div className="mc-textinput-outer">
<input
type="text"
readOnly
value={path}
className="w-full h-10 px-3 bg-black/20 border-2 border-[#222] text-black text-xs outline-none font-[var(--font-base)] cursor-not-allowed"
className="mc-textinput w-full h-10 px-3 text-black text-xs outline-none font-[var(--font-base)] cursor-not-allowed"
style={{ imageRendering: "pixelated" }}
/>
</div>
</div>
)}
+6 -2
View File
@@ -255,6 +255,7 @@ export default function OptionsModal({
case "int":
case "number":
return (
<div className="mc-textinput-outer w-24">
<input
ref={(el) => {
inputRefs.current[index] = el;
@@ -273,14 +274,16 @@ export default function OptionsModal({
}));
}}
onFocus={() => setFocusIndex(index)}
className={`w-24 h-8 bg-black/40 border-2 border-[#373737] text-white text-sm px-2 outline-none text-center font-[var(--font-base)] focus:border-[#FFFF55] [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none ${
className={`mc-textinput w-full h-10 px-3 text-white text-sm outline-none text-center font-[var(--font-base)] [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none ${
disabled ? "opacity-40 cursor-not-allowed" : ""
}`}
style={{ imageRendering: "pixelated" }}
/>
</div>
);
case "string":
return (
<div className="mc-textinput-outer w-44">
<input
ref={(el) => {
inputRefs.current[index] = el;
@@ -293,11 +296,12 @@ export default function OptionsModal({
setValues((prev) => ({ ...prev, [option.id]: e.target.value }));
}}
onFocus={() => setFocusIndex(index)}
className={`w-44 h-8 bg-black/40 border-2 border-[#373737] text-white text-sm px-2 outline-none font-[var(--font-base)] focus:border-[#FFFF55] ${
className={`mc-textinput w-full h-10 px-3 text-white text-sm outline-none font-[var(--font-base)] ${
disabled ? "opacity-40 cursor-not-allowed" : ""
}`}
style={{ imageRendering: "pixelated" }}
/>
</div>
);
case "choice":
return (
+3 -1
View File
@@ -183,6 +183,7 @@ export default function SetUidModal({
<label className="text-gray text-xs mc-text-shadow uppercase tracking-widest text-[#AAAAAA]">
{t("modals.setUid.enterUid")}
</label>
<div className="mc-textinput-outer w-full">
<input
type="text"
autoFocus
@@ -190,9 +191,10 @@ export default function SetUidModal({
onChange={(e) => setUid(e.target.value)}
onFocus={() => setFocusIndex(2)}
placeholder="0xFF02F0C87E8AC1F2"
className={`w-full h-10 px-3 bg-black/40 border-2 ${focusIndex === 2 ? 'border-white' : 'border-[#373737]'} text-white text-base outline-none font-[var(--font-base)] text-center`}
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)] text-center"
style={{ imageRendering: "pixelated", filter: focusIndex === 2 ? 'brightness(1.2)' : 'none' }}
/>
</div>
</div>
) : (
<div className="flex flex-col gap-1 items-center w-full relative">
+12 -4
View File
@@ -387,13 +387,15 @@ export const ArcEditorView: React.FC = () => {
<div className="flex h-full overflow-hidden">
<div className="w-2/3 flex flex-col p-4 border-r-2 border-black/20">
<div className="mb-4 flex gap-4">
<div className="mc-textinput-outer flex-1">
<input
type="text"
placeholder={t("arcEditor.searchEntries")}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="flex-1 bg-black/40 border-2 border-[#373737] text-white px-4 py-2 outline-none focus:border-[#FFFF55] transition-colors"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
/>
</div>
<button
onClick={() => setIsAddModalOpen(true)}
className="px-6 py-2 text-white mc-text-shadow text-sm"
@@ -506,13 +508,15 @@ export const ArcEditorView: React.FC = () => {
<option key={idx} value={idx}>{lang.id} {lang.isStatic ? `[${t("arcEditor.static")}]` : `[${t("arcEditor.keyed")}]`}</option>
))}
</select>
<div className="mc-textinput-outer flex-1">
<input
type="text"
placeholder={t("arcEditor.searchStrings")}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="flex-1 bg-black/40 border-2 border-[#373737] text-white px-4 py-2 outline-none focus:border-[#FFFF55] transition-colors"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
/>
</div>
<button
onClick={() => setIsLocEditModalOpen({ langIdx: selectedLocLangIdx, strIdx: -1, isNew: true })}
className="px-6 py-2 text-white mc-text-shadow text-sm"
@@ -665,12 +669,14 @@ function RenameModal({ initialName, initialCompressed, onClose, onConfirm }: { i
<div className="flex flex-col gap-4">
<div>
<label className="text-white/40 text-xs uppercase mb-2 block">{t("arcEditor.newArchivePath")}</label>
<div className="mc-textinput-outer">
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full bg-black/40 border-2 border-[#373737] text-white px-4 py-3 outline-none focus:border-[#FFFF55] transition-colors"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
/>
</div>
</div>
<label className="flex items-center gap-3 cursor-pointer group">
<input type="checkbox" checked={comp} onChange={(e) => setComp(e.target.checked)} className="w-5 h-5 accent-[#FFFF55]" />
@@ -704,13 +710,15 @@ function LocEditModal({ data, lang, onClose, onConfirm }: { data: { langIdx: num
{!lang.isStatic ? (
<div>
<label className="text-white/40 text-xs uppercase mb-2 block">{t("arcEditor.stringKey")}</label>
<div className="mc-textinput-outer">
<input
type="text"
value={key}
onChange={(e) => setKey(e.target.value)}
className="w-full bg-black/40 border-2 border-[#373737] text-white px-4 py-3 outline-none focus:border-[#FFFF55] transition-colors font-mono"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)] font-mono"
/>
</div>
</div>
) : (
<div className="text-white/40 italic mb-2">{t("arcEditor.staticEntry", { index: data.isNew ? lang.strings.length : data.strIdx })}</div>
)}
+33 -19
View File
@@ -183,13 +183,15 @@ export default function ColEditorView() {
<div className="flex-1 flex flex-col p-4 overflow-hidden">
<div className="mb-4 flex gap-4">
<div className="mc-textinput-outer flex-1">
<input
type="text"
placeholder={t("colEditor.searchColors")}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="flex-1 bg-black/40 border-2 border-[#373737] text-white px-4 py-2 outline-none focus:border-[#FFFF55] transition-colors"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
/>
</div>
<button
onClick={activeTab === "colors" ? handleAddColor : handleAddWorldColor}
className="px-6 py-2 text-white mc-text-shadow text-sm"
@@ -219,12 +221,14 @@ export default function ColEditorView() {
{activeTab === "colors" && currentColors.map((c) => (
<tr key={c.originalIdx} className="border-b border-[#373737]/30 group">
<td className="p-2">
<div className="mc-textinput-outer">
<input
type="text"
value={c.name}
onChange={(e) => handleUpdateColor(c.originalIdx, "name", e.target.value)}
className="w-full bg-black/40 border border-[#373737] px-2 py-1 outline-none focus:border-[#FFFF55] text-white text-sm"
className="mc-textinput w-full h-10 px-3 text-white text-sm outline-none font-[var(--font-base)]"
/>
</div>
</td>
<td className="p-2">
<div className="flex items-center gap-3">
@@ -239,19 +243,21 @@ export default function ColEditorView() {
}}
className="w-8 h-8 p-0 cursor-pointer shrink-0 border border-[#373737] rounded-sm bg-transparent"
/>
<input
type="text"
value={argbToHex(c.color)}
onChange={(e) => {
const s = e.target.value.replace(/[^0-9A-Fa-f]/g, '');
if (s.length <= 8) {
const parsed = parseInt(s, 16);
if (!isNaN(parsed)) handleUpdateColor(c.originalIdx, "color", parsed >>> 0);
}
}}
className="bg-black/40 border border-[#373737] w-24 px-2 py-1 outline-none focus:border-[#FFFF55] text-white font-mono text-sm uppercase"
maxLength={8}
/>
<div className="mc-textinput-outer w-24">
<input
type="text"
value={argbToHex(c.color)}
onChange={(e) => {
const s = e.target.value.replace(/[^0-9A-Fa-f]/g, '');
if (s.length <= 8) {
const parsed = parseInt(s, 16);
if (!isNaN(parsed)) handleUpdateColor(c.originalIdx, "color", parsed >>> 0);
}
}}
className="mc-textinput w-full h-10 px-3 text-white font-mono text-sm uppercase outline-none font-[var(--font-base)]"
maxLength={8}
/>
</div>
</div>
</td>
<td className="p-2 text-right">
@@ -265,12 +271,14 @@ export default function ColEditorView() {
{activeTab === "worldColors" && currentWorldColors.map((w) => (
<tr key={w.originalIdx} className="border-b border-[#373737]/30 group">
<td className="p-2">
<div className="mc-textinput-outer">
<input
type="text"
value={w.name}
onChange={(e) => handleUpdateWorldColor(w.originalIdx, "name", e.target.value)}
className="w-full bg-black/40 border border-[#373737] px-2 py-1 outline-none focus:border-[#FFFF55] text-white text-sm"
className="mc-textinput w-full h-10 px-3 text-white text-sm outline-none font-[var(--font-base)]"
/>
</div>
</td>
<td className="p-2">
<div className="flex items-center gap-2">
@@ -284,6 +292,7 @@ export default function ColEditorView() {
}}
className="w-6 h-6 p-0 cursor-pointer shrink-0 border border-[#373737] rounded-sm bg-transparent"
/>
<div className="mc-textinput-outer w-20">
<input
type="text"
value={argbToHex(w.waterColor)}
@@ -294,9 +303,10 @@ export default function ColEditorView() {
if (!isNaN(parsed)) handleUpdateWorldColor(w.originalIdx, "waterColor", parsed >>> 0);
}
}}
className="bg-black/40 border border-[#373737] w-20 px-2 py-1 outline-none focus:border-[#FFFF55] text-white font-mono text-xs uppercase"
className="mc-textinput w-full h-10 px-3 text-white font-mono text-xs uppercase outline-none font-[var(--font-base)]"
maxLength={8}
/>
</div>
</div>
</td>
<td className="p-2">
@@ -311,6 +321,7 @@ export default function ColEditorView() {
}}
className="w-6 h-6 p-0 cursor-pointer shrink-0 border border-[#373737] rounded-sm bg-transparent"
/>
<div className="mc-textinput-outer w-20">
<input
type="text"
value={argbToHex(w.underwaterColor)}
@@ -321,9 +332,10 @@ export default function ColEditorView() {
if (!isNaN(parsed)) handleUpdateWorldColor(w.originalIdx, "underwaterColor", parsed >>> 0);
}
}}
className="bg-black/40 border border-[#373737] w-20 px-2 py-1 outline-none focus:border-[#FFFF55] text-white font-mono text-xs uppercase"
className="mc-textinput w-full h-10 px-3 text-white font-mono text-xs uppercase outline-none font-[var(--font-base)]"
maxLength={8}
/>
</div>
</div>
</td>
<td className="p-2">
@@ -338,6 +350,7 @@ export default function ColEditorView() {
}}
className="w-6 h-6 p-0 cursor-pointer shrink-0 border border-[#373737] rounded-sm bg-transparent"
/>
<div className="mc-textinput-outer w-20">
<input
type="text"
value={argbToHex(w.fogColor)}
@@ -348,9 +361,10 @@ export default function ColEditorView() {
if (!isNaN(parsed)) handleUpdateWorldColor(w.originalIdx, "fogColor", parsed >>> 0);
}
}}
className="bg-black/40 border border-[#373737] w-20 px-2 py-1 outline-none focus:border-[#FFFF55] text-white font-mono text-xs uppercase"
className="mc-textinput w-full h-10 px-3 text-white font-mono text-xs uppercase outline-none font-[var(--font-base)]"
maxLength={8}
/>
</div>
</div>
</td>
<td className="p-2 text-right">
+13 -13
View File
@@ -354,7 +354,7 @@ const GoldMapperView = memo(function GoldMapperView() {
? handleToggleEnabled
: () => openBind(row.id)
}
className={`w-full h-10 flex items-center pl-6 pr-4 outline-none border-none shrink-0 transition-colors ${
className={`w-full h-10 flex items-center ${!isEnable ? "pl-6" : "pl-2"} pr-4 outline-none border-none shrink-0 transition-colors ${
focused
? "text-[#ffff00]"
: isEnable
@@ -386,7 +386,7 @@ const GoldMapperView = memo(function GoldMapperView() {
</div>
)}
<span
className={`tracking-widest text-lg mc-text-shadow truncate ${
className={`tracking-widest text-lg truncate ${
isReset ? "" : "flex-1 text-left"
}`}
>
@@ -425,9 +425,9 @@ const GoldMapperView = memo(function GoldMapperView() {
<button
onMouseEnter={() => setFocusIndex(null)}
onClick={handleBack}
className="w-40 h-10 flex items-center justify-center transition-colors text-xl mc-text-shadow outline-none border-none hover:text-[#ffff00] mt-4 text-white"
className="w-40 h-10 flex items-center justify-center transition-colors text-xl outline-none border-none hover:text-[#ffff00] mt-4 text-white"
style={{
backgroundImage: "url('/images/Layout_Button_Bmp.png')",
backgroundImage: "url('/images/Button_Background.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
@@ -443,13 +443,13 @@ const GoldMapperView = memo(function GoldMapperView() {
}}
>
<div className="relative w-[620px] max-w-[95vw] h-[580px] max-h-[88vh] p-5 flex flex-col font-[var(--font-base)] mc-options-bg">
<h2 className="text-xl text-black mc-text-shadow mb-4 text-center">
<h2 className="text-xl text-black mb-4 text-center">
{t("goldMapper.assign", { name: displayName(editing) })}
</h2>
<div className="w-full flex-1 min-h-0 overflow-y-auto custom-scrollbar mb-4">
<div className="mb-3">
<h3 className="text-[#333333] mc-text-shadow uppercase tracking-widest text-sm px-3 pt-2 pb-1">
<h3 className="text-[#333333] tracking-widest text-sm px-3 pt-2 pb-1">
{t("goldMapper.mouse")}
</h3>
<div className="grid grid-cols-4 gap-2 p-1 content-start">
@@ -472,7 +472,7 @@ const GoldMapperView = memo(function GoldMapperView() {
</div>
<div className="mb-3">
<h3 className="text-[#333333] mc-text-shadow uppercase tracking-widest text-sm px-3 pt-2 pb-1">
<h3 className="text-[#333333] tracking-widest text-sm px-3 pt-2 pb-1">
{t("goldMapper.controller")}
</h3>
<div className="grid grid-cols-4 gap-2 p-1 content-start">
@@ -500,9 +500,10 @@ const GoldMapperView = memo(function GoldMapperView() {
</div>
<div className="mb-3">
<h3 className="text-[#333333] mc-text-shadow uppercase tracking-widest text-sm px-3 pt-2 pb-1">
<h3 className="text-[#333333] tracking-widest text-sm px-3 pt-2 pb-1">
{t("goldMapper.keyboard")}
</h3>
<div className="mc-textinput-outer">
<input
data-modal-index={MOUSE_IDS.length + controllerIds.length}
value={keyInput}
@@ -520,13 +521,12 @@ const GoldMapperView = memo(function GoldMapperView() {
if (e.key === "Enter") submitKeyInput();
}}
placeholder={t("goldMapper.typeKeyName")}
className={`w-full h-10 px-3 bg-black/40 border-2 text-white text-base outline-none text-center ${
keyInputError
? "border-red-600"
: "border-[#373737] focus:border-[#FFFF55]"
className={`mc-textinput w-full h-10 px-3 text-white text-base outline-none text-center font-[var(--font-base)] ${
keyInputError ? "" : ""
}`}
style={{ imageRendering: "pixelated" }}
/>
</div>
{keyInputError && (
<p className="text-red-600 text-xs mt-1 px-3">
{keyInputError}
@@ -544,7 +544,7 @@ const GoldMapperView = memo(function GoldMapperView() {
setModalFocusIndex(MOUSE_IDS.length + controllerIds.length + 1)
}
onClick={closeModal}
className={`w-full h-12 flex items-center justify-center text-xl mc-text-shadow transition-colors outline-none border-none cursor-pointer ${
className={`w-full h-12 flex items-center justify-center text-xl transition-colors outline-none border-none cursor-pointer ${
modalFocusIndex === MOUSE_IDS.length + controllerIds.length + 1
? "text-[#ffff00]"
: "text-white"
+3 -1
View File
@@ -291,12 +291,14 @@ function GrfNodeView({ node, level, path, onUpdate }: { node: GrfNode, level: nu
{node.parameters.map((p, i) => (
<div key={i} className="flex gap-4 border-b border-[#373737]/30 py-2 text-sm items-center">
<span className="text-[#AAAAAA] w-1/3 truncate">{p.name}</span>
<div className="mc-textinput-outer flex-1">
<input
type="text"
value={p.value}
onChange={(e) => onUpdate(currentPath, i, e.target.value)}
className="flex-1 bg-white/5 border border-white/10 px-2 py-1 text-white outline-none focus:border-[#FFFF55]/50 font-mono transition-colors"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)] font-mono"
/>
</div>
</div>
))}
</div>
+3 -1
View File
@@ -821,14 +821,16 @@ const LceOnlineView = memo(function LceOnlineView({
<h2 className="text-[#FFFF55] text-3xl mc-text-shadow mb-6 border-b-2 border-[#373737] pb-2 w-full text-center uppercase tracking-widest">
{t("lceOnline.addFriend")}
</h2>
<div className="mc-textinput-outer w-full mb-6">
<input
ref={addFriendInputRef}
type="text"
className="bg-black/20 border-4 border-[#555] text-white p-4 w-full text-2xl font-bold outline-none focus:border-[#FFFF55] transition-colors placeholder:text-[#888] mb-6 mc-text-shadow"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)] text-2xl font-bold placeholder:text-[#888] mc-text-shadow"
placeholder={t("lceOnline.username")}
value={addFriendUsername}
onChange={(e) => setAddFriendUsername(e.target.value)}
/>
</div>
<div className="flex gap-4 w-full">
<button
className="h-12 flex-1 flex items-center justify-center text-white mc-text-shadow text-xl font-bold uppercase tracking-widest hover:text-[#FFFF55] outline-none border-none"
+6 -2
View File
@@ -150,13 +150,15 @@ export default function LocEditorView() {
<option key={idx} value={idx}>{lang.id} {lang.isStatic ? `[${t("locEditor.static")}]` : `[${t("locEditor.keyed")}]`}</option>
))}
</select>
<div className="mc-textinput-outer flex-1">
<input
type="text"
placeholder={t("locEditor.searchStrings")}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="flex-1 bg-black/40 border-2 border-[#373737] text-white px-4 py-2 outline-none focus:border-[#FFFF55] transition-colors"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
/>
</div>
<button
onClick={() => setIsLocEditModalOpen({ langIdx: selectedLocLangIdx, strIdx: -1, isNew: true })}
className="px-6 py-2 text-white mc-text-shadow text-sm"
@@ -250,12 +252,14 @@ function LocEditModal({ data, lang, onClose, onConfirm }: { data: { langIdx: num
{!lang.isStatic ? (
<div>
<label className="text-white/40 text-xs uppercase mb-2 block">{t("locEditor.stringKey")}</label>
<div className="mc-textinput-outer">
<input
type="text"
value={key}
onChange={(e) => setKey(e.target.value)}
className="w-full bg-black/40 border-2 border-[#373737] text-white px-4 py-3 outline-none focus:border-[#FFFF55] transition-colors font-mono"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)] font-mono"
/>
</div>
</div>
) : (
<div className="text-white/40 italic mb-2">{t("locEditor.staticEntry", { index: data.isNew ? lang.strings.length : data.strIdx })}</div>
+18 -6
View File
@@ -865,13 +865,15 @@ function PartEditModal({
<label className="text-white/40 text-[10px] uppercase tracking-widest mb-1 block">
{t("modelEditor.name")}
</label>
<div className="mc-textinput-outer">
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full bg-black/40 border-2 border-[#373737] text-white px-4 py-2 outline-none focus:border-[#FFFF55] transition-colors"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
autoFocus
/>
</div>
</div>
<div>
<label className="text-white/40 text-[10px] uppercase tracking-widest mb-1 block">
@@ -883,6 +885,7 @@ function PartEditModal({
<span className="text-white/40 text-xs font-mono">
{axis}
</span>
<div className="mc-textinput-outer flex-1">
<input
type="number"
step={0.5}
@@ -893,8 +896,9 @@ function PartEditModal({
else if (axis === "Y") setTy(v);
else setTz(v);
}}
className="w-full bg-black/40 border border-[#373737] text-white px-2 py-1 outline-none focus:border-[#FFFF55] text-sm"
className="mc-textinput w-full h-10 px-3 text-white text-sm outline-none font-[var(--font-base)]"
/>
</div>
</div>
))}
</div>
@@ -985,6 +989,7 @@ function BoxEditModal({
<span className="text-white/40 text-xs font-mono">
{axis}
</span>
<div className="mc-textinput-outer flex-1">
<input
type="number"
step={0.5}
@@ -995,8 +1000,9 @@ function BoxEditModal({
else if (axis === "Y") setPosY(v);
else setPosZ(v);
}}
className="w-full bg-black/40 border border-[#373737] text-white px-2 py-1 outline-none focus:border-[#FFFF55] text-sm"
className="mc-textinput w-full h-10 px-3 text-white text-sm outline-none font-[var(--font-base)]"
/>
</div>
</div>
))}
</div>
@@ -1011,6 +1017,7 @@ function BoxEditModal({
<span className="text-white/40 text-xs font-mono">
{axis}
</span>
<div className="mc-textinput-outer flex-1">
<input
type="number"
step={0.5}
@@ -1022,8 +1029,9 @@ function BoxEditModal({
else if (axis === "Y") setSizeY(v);
else setSizeZ(v);
}}
className="w-full bg-black/40 border border-[#373737] text-white px-2 py-1 outline-none focus:border-[#FFFF55] text-sm"
className="mc-textinput w-full h-10 px-3 text-white text-sm outline-none font-[var(--font-base)]"
/>
</div>
</div>
))}
</div>
@@ -1038,6 +1046,7 @@ function BoxEditModal({
<span className="text-white/40 text-xs font-mono">
{axis}
</span>
<div className="mc-textinput-outer flex-1">
<input
type="number"
min={0}
@@ -1047,8 +1056,9 @@ function BoxEditModal({
if (axis === "X") setUvX(v);
else setUvY(v);
}}
className="w-full bg-black/40 border border-[#373737] text-white px-2 py-1 outline-none focus:border-[#FFFF55] text-sm"
className="mc-textinput w-full h-10 px-3 text-white text-sm outline-none font-[var(--font-base)]"
/>
</div>
</div>
))}
</div>
@@ -1057,13 +1067,15 @@ function BoxEditModal({
<label className="text-white/40 text-[10px] uppercase tracking-widest">
{t("modelEditor.inflate")}
</label>
<div className="mc-textinput-outer w-20">
<input
type="number"
step={0.1}
value={inflate}
onChange={(e) => setInflate(parseFloat(e.target.value) || 0)}
className="w-20 bg-black/40 border border-[#373737] text-white px-2 py-1 outline-none focus:border-[#FFFF55] text-sm"
className="mc-textinput w-full h-10 px-3 text-white text-sm outline-none font-[var(--font-base)]"
/>
</div>
</div>
<div className="flex items-center gap-3 justify-end">
<label className="text-white/40 text-[10px] uppercase tracking-widest">
+9 -3
View File
@@ -161,27 +161,33 @@ export default function OptionsEditorView() {
<div className="flex flex-col gap-8 max-w-xl mx-auto">
<div className="flex flex-col gap-2">
<span className="text-white/80 uppercase tracking-widest">{t("optionsEditor.chosenSkinId")}</span>
<div className="mc-textinput-outer">
<input
type="number" value={opt.chosenSkin} onChange={(e) => updateSetting("chosenSkin", parseInt(e.target.value) || 0)}
className="bg-black/40 border border-[#373737] p-2 text-white outline-none focus:border-[#FFFF55] font-mono text-lg"
className="mc-textinput w-full h-10 px-3 text-white text-lg outline-none font-[var(--font-base)] font-mono"
/>
</div>
</div>
<div className="flex flex-col gap-2">
<span className="text-white/80 uppercase tracking-widest">{t("optionsEditor.playerCapeId")}</span>
<div className="mc-textinput-outer">
<input
type="number" value={opt.playerCape} onChange={(e) => updateSetting("playerCape", parseInt(e.target.value) || 0)}
className="bg-black/40 border border-[#373737] p-2 text-white outline-none focus:border-[#FFFF55] font-mono text-lg"
className="mc-textinput w-full h-10 px-3 text-white text-lg outline-none font-[var(--font-base)] font-mono"
/>
</div>
</div>
<div className="flex flex-col gap-2">
<span className="text-white/80 uppercase tracking-widest">{t("optionsEditor.favoriteSkins")}</span>
{opt.favoriteSkins.map((s, i) => (
<div key={i} className="flex items-center gap-4">
<span className="text-white/40 w-8">{i + 1}.</span>
<div className="mc-textinput-outer flex-1">
<input
type="number" value={s} onChange={(e) => updateFavSkin(i, parseInt(e.target.value) || 0)}
className="bg-black/40 flex-1 border border-[#373737] p-2 text-white outline-none focus:border-[#FFFF55] font-mono"
className="mc-textinput w-full h-10 px-3 text-white outline-none font-[var(--font-base)] font-mono"
/>
</div>
</div>
))}
</div>
+12 -4
View File
@@ -646,13 +646,15 @@ export default function PckEditorView() {
}}
>
<div className="mb-4 flex gap-4">
<div className="mc-textinput-outer flex-1">
<input
type="text"
placeholder={t("pckEditor.searchAssets")}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="flex-1 bg-black/40 border-2 border-[#373737] text-white px-4 py-2 outline-none focus:border-[#FFFF55] transition-colors"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
/>
</div>
<button
onClick={() => addAssetInputRef.current?.click()}
className="px-4 py-2 text-white mc-text-shadow text-sm shrink-0"
@@ -848,14 +850,16 @@ export default function PckEditorView() {
className="flex flex-col gap-1 group/prop"
>
<div className="flex justify-between items-center px-1">
<div className="mc-textinput-outer w-2/3">
<input
type="text"
value={prop.key}
onChange={(e) =>
handlePropertyEdit(idx, e.target.value, true)
}
className="bg-transparent text-white/40 text-[10px] outline-none hover:text-white/60 focus:text-[#FFFF55] w-2/3"
className="mc-textinput w-full h-8 px-2 text-white/60 text-[10px] outline-none font-[var(--font-base)]"
/>
</div>
<button
onClick={() => handleRemoveProperty(idx)}
className="text-red-500/0 group-hover/prop:text-red-500/40 hover:text-red-500 transition-colors text-[10px] uppercase"
@@ -864,14 +868,16 @@ export default function PckEditorView() {
</button>
</div>
<div className="relative">
<div className="mc-textinput-outer">
<input
type="text"
value={prop.value}
onChange={(e) =>
handlePropertyEdit(idx, e.target.value)
}
className="w-full bg-black/40 p-2 text-white border border-[#373737] text-sm focus:border-[#FFFF55] outline-none transition-colors"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)] text-sm"
/>
</div>
</div>
{prop.key === "ANIM" && (
@@ -1175,13 +1181,15 @@ function RenameAssetModal({
<label className="text-white/40 text-[10px] uppercase tracking-widest mb-1 block">
{t("pckEditor.newAssetPath")}
</label>
<div className="mc-textinput-outer">
<input
type="text"
value={path}
onChange={(e) => setPath(e.target.value)}
className="w-full bg-black/40 border-2 border-[#373737] text-white px-4 py-3 outline-none focus:border-[#FFFF55] transition-colors"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
autoFocus
/>
</div>
</div>
<div className="flex justify-end gap-4 mt-4">
<button
+34 -16
View File
@@ -798,6 +798,13 @@ const SettingsView = memo(function SettingsView() {
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
if (
document.activeElement instanceof HTMLInputElement &&
document.activeElement.type === "text"
) {
document.activeElement.blur();
return;
}
if (showModal) {
playBackSound();
setShowModal(null);
@@ -848,6 +855,11 @@ const SettingsView = memo(function SettingsView() {
const item = settingsItems[focusIndex];
if (item.type === "button") {
item.onClick();
} else if (item.type === "textinput") {
const el = containerRef.current?.querySelector(
`[data-index="${focusIndex}"] input`,
) as HTMLInputElement | null;
el?.focus();
}
}
};
@@ -955,6 +967,7 @@ const SettingsView = memo(function SettingsView() {
<div
key={item.id}
data-index={index}
tabIndex={0}
onMouseEnter={() => setFocusIndex(index)}
className="relative w-[480px] flex flex-col cursor-pointer transition-all outline-none shrink-0"
>
@@ -1116,6 +1129,7 @@ const SettingsView = memo(function SettingsView() {
<div
key={item.id}
data-index={index}
tabIndex={0}
onMouseEnter={() => setFocusIndex(index)}
className="relative w-[600px] flex flex-col cursor-pointer transition-all outline-none shrink-0"
>
@@ -1238,14 +1252,16 @@ const SettingsView = memo(function SettingsView() {
<p className="text-[#AAAAAA] text-xs mb-4 text-center mc-text-shadow">
{t("settings.extraLaunchArgsDesc")}
</p>
<input
autoFocus
value={argsInput}
onChange={(e) => setArgsInput(e.target.value)}
placeholder="e.g. -quitondisconnect -ip 127.0.0.1"
className="w-full h-10 px-3 bg-black/40 border-2 border-[#373737] text-white text-base outline-none font-[var(--font-base)] text-center"
style={{ imageRendering: "pixelated" }}
/>
<div className="mc-textinput-outer">
<input
autoFocus
value={argsInput}
onChange={(e) => setArgsInput(e.target.value)}
placeholder="e.g. -quitondisconnect -ip 127.0.0.1"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)] text-center"
style={{ imageRendering: "pixelated" }}
/>
</div>
<div className="flex gap-4 mt-6 w-full justify-center">
<button
onClick={() => {
@@ -1318,14 +1334,16 @@ const SettingsView = memo(function SettingsView() {
<p className="text-[#AAAAAA] text-xs mb-4 text-center mc-text-shadow">
{t("settings.launchPrefixDesc")}
</p>
<input
autoFocus
value={prefixInput}
onChange={(e) => setPrefixInput(e.target.value)}
placeholder="e.g. gamemoderun"
className="w-full h-10 px-3 bg-black/40 border-2 border-[#373737] text-white text-base outline-none font-[var(--font-base)] text-center"
style={{ imageRendering: "pixelated" }}
/>
<div className="mc-textinput-outer">
<input
autoFocus
value={prefixInput}
onChange={(e) => setPrefixInput(e.target.value)}
placeholder="e.g. gamemoderun"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)] text-center"
style={{ imageRendering: "pixelated" }}
/>
</div>
<div className="flex gap-4 mt-6 w-full justify-center">
<button
onClick={() => {
+6 -6
View File
@@ -273,6 +273,7 @@ const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
{t("setup.username")}
</span>
<div className="relative">
<div className="mc-textinput-outer">
<input
type="text"
ref={(el) => {
@@ -293,7 +294,7 @@ const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
const caret =
document.getElementById("custom-caret");
if (caret) {
caret.style.left = `${cursorPosition + 16}px`;
caret.style.left = `${cursorPosition + 14}px`;
}
}
}}
@@ -321,29 +322,28 @@ const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
const caret =
document.getElementById("custom-caret");
if (caret) {
caret.style.left = `${cursorPosition + 16}px`;
caret.style.left = `${cursorPosition + 14}px`;
}
}
}}
className={`w-full px-4 py-2 focus:outline-none transition-colors text-white tracking-widest
${focusIndex === 0 ? "border-4 border-[#FFFF55] text-[#FFFF55]" : "border-4 border-[#323232]"}`}
className="mc-textinput w-full h-10 px-3 text-white text-base tracking-widest outline-none font-[var(--font-base)]"
style={{
imageRendering: "pixelated",
fontFamily: "'Mojangles', monospace",
backgroundColor: "#646464",
caretColor: "transparent",
}}
placeholder={t("setup.enterUsername")}
maxLength={16}
autoFocus
/>
</div>
{focusIndex === 0 && (
<span
id="custom-caret"
className="absolute top-1/2 -translate-y-1/2 text-white blink-caret"
style={{
fontFamily: "'Mojangles', monospace",
left: "16px",
left: "14px",
}}
>
_
+3 -1
View File
@@ -641,6 +641,7 @@ const SkinEditorView = memo(function SkinEditorView() {
style={{ boxShadow: "inset 2px 2px 0 #000, inset -1px -1px 0 #555" }}
>
<span className="pl-2 text-white/50 text-sm mc-text-shadow">#</span>
<div className="mc-textinput-outer w-full h-8">
<input
type="text"
value={hexValue}
@@ -651,8 +652,9 @@ const SkinEditorView = memo(function SkinEditorView() {
setHexValue(hex.toUpperCase());
if (hex.length === 6) setColorFromHex(`#${hex}`);
}}
className="w-full h-full px-1 text-sm text-white tracking-widest uppercase mc-text-shadow bg-transparent"
className="mc-textinput w-full h-full px-1 text-sm text-white tracking-widest uppercase outline-none"
/>
</div>
</div>
<div className="relative w-full h-10 flex items-center justify-center" style={sliderBg}>
+46 -34
View File
@@ -561,7 +561,9 @@ const SkinsView = memo(function SkinsView() {
imageRendering: "pixelated",
}}
>
{viewMode === "skin" ? t("skins.importSkin") : t("skins.importCape")}
{viewMode === "skin"
? t("skins.importSkin")
: t("skins.importCape")}
</button>
<button
@@ -596,7 +598,9 @@ const SkinsView = memo(function SkinsView() {
imageRendering: "pixelated",
}}
>
{viewMode === "skin" ? t("skins.deleteSkin") : t("skins.deleteCape")}
{viewMode === "skin"
? t("skins.deleteSkin")
: t("skins.deleteCape")}
</button>
{viewMode === "skin" && (
@@ -708,16 +712,20 @@ const SkinsView = memo(function SkinsView() {
>
<HeadPreview src={skin.url} />
</div>
<input
type="text"
value={skin.name}
maxLength={16}
onChange={(e) => handleNameChange(skin.id, e.target.value)}
className={`bg-transparent text-center outline-none border-none text-base mc-text-shadow w-full truncate transition-colors relative z-10 ${isActive || isFocused ? "text-[#FFFF55]" : "text-white"} ${isDefaultSkin(skin.id) ? "pointer-events-none" : ""}`}
onClick={(e) => e.stopPropagation()}
spellCheck={false}
readOnly={isDefaultSkin(skin.id)}
/>
<div className="w-full">
<input
type="text"
value={skin.name}
maxLength={16}
onChange={(e) =>
handleNameChange(skin.id, e.target.value)
}
className={`w-full h-10 px-3 text-base text-center truncate outline-none relative z-10 font-[var(--font-base)] ${isActive || isFocused ? "text-[#FFFF55]" : "text-white"} ${isDefaultSkin(skin.id) ? "pointer-events-none" : ""}`}
onClick={(e) => e.stopPropagation()}
spellCheck={false}
readOnly={isDefaultSkin(skin.id)}
/>
</div>
</div>
);
})
@@ -779,18 +787,20 @@ const SkinsView = memo(function SkinsView() {
>
<CapePreview src={cape.url} />
</div>
<input
type="text"
value={cape.name}
maxLength={16}
onChange={(e) =>
handleCapeNameChange(cape.id, e.target.value)
}
className={`bg-transparent text-center outline-none border-none text-base mc-text-shadow w-full truncate transition-colors relative z-10 ${isActive || isFocused ? "text-[#FFFF55]" : "text-white"} ${isDefaultCape(cape.id) ? "pointer-events-none" : ""}`}
onClick={(e) => e.stopPropagation()}
spellCheck={false}
readOnly={isDefaultCape(cape.id)}
/>
<div className="mc-textinput-outer w-full">
<input
type="text"
value={cape.name}
maxLength={16}
onChange={(e) =>
handleCapeNameChange(cape.id, e.target.value)
}
className={`mc-textinput w-full h-10 px-3 text-base text-center truncate outline-none relative z-10 font-[var(--font-base)] ${isActive || isFocused ? "text-[#FFFF55]" : "text-white"} ${isDefaultCape(cape.id) ? "pointer-events-none" : ""}`}
onClick={(e) => e.stopPropagation()}
spellCheck={false}
readOnly={isDefaultCape(cape.id)}
/>
</div>
</div>
);
})}
@@ -896,16 +906,18 @@ const SkinsView = memo(function SkinsView() {
</div>
) : importMode === "username" ? (
<div className="flex flex-col gap-4 w-full px-4 mb-2">
<input
type="text"
placeholder={t("skins.minecraftUsername")}
value={importUsername}
onChange={(e) => setImportUsername(e.target.value)}
onFocus={() => setModalFocusIndex(0)}
autoFocus
spellCheck={false}
className={`w-full h-12 bg-black/50 border-2 text-white px-4 text-xl outline-none transition-colors relative z-10 ${modalFocusIndex === 0 ? "border-[#FFFF55]" : "border-[#373737]"}`}
/>
<div className="mc-textinput-outer w-full">
<input
type="text"
placeholder={t("skins.minecraftUsername")}
value={importUsername}
onChange={(e) => setImportUsername(e.target.value)}
onFocus={() => setModalFocusIndex(0)}
autoFocus
spellCheck={false}
className="mc-textinput w-full h-10 px-3 text-white text-xl outline-none font-[var(--font-base)]"
/>
</div>
{importError && (
<span className="text-red-400 text-sm text-center mc-text-shadow">
+3 -1
View File
@@ -218,13 +218,15 @@ export default function SwfView() {
<div className="flex-1 w-full flex gap-4 overflow-hidden">
<div className="w-1/3 flex flex-col p-4" style={{ backgroundImage: "url('/images/frame_background.png')", backgroundSize: "100% 100%", imageRendering: "pixelated" }}>
<div className="mb-4">
<div className="mc-textinput-outer">
<input
type="text"
placeholder={t("swfEditor.searchAssets")}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full bg-black/40 border-2 border-[#373737] text-white px-4 py-2 outline-none focus:border-[#FFFF55] transition-colors"
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
/>
</div>
</div>
<div className="flex-1 overflow-y-auto pr-2 custom-scrollbar space-y-1">
{filteredImages.map(img => (
+34 -35
View File
@@ -843,46 +843,41 @@ const WorkshopView = memo(function WorkshopView({
{showSearch ? (
<div className="absolute inset-0 flex flex-col pt-2">
<div className="flex items-center gap-3 px-6 pb-4">
<div
className="flex items-center flex-1 h-12 px-4 border-2 border-[#444] bg-black/40 rounded shadow-inner"
style={{
backgroundImage: "url('/images/Button_Background2.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
<input
ref={searchRef}
type="text"
value={search}
onChange={(e) => {
setSearch(e.target.value);
setFocusedIdx(null);
}}
placeholder={
isInstalledTab
? t("workshop.filterInstalled")
: isVersionTab
? t("workshop.filterVersions")
: isPluginTab
? t("workshop.filterPlugins")
: activeTab === "Server Plugins"
<div className="flex items-center flex-1 h-12 px-4 rounded">
<div className="mc-textinput-outer flex-1">
<input
ref={searchRef}
type="text"
value={search}
onChange={(e) => {
setSearch(e.target.value);
setFocusedIdx(null);
}}
placeholder={
isInstalledTab
? t("workshop.filterInstalled")
: isVersionTab
? t("workshop.filterVersions")
: isPluginTab
? t("workshop.filterPlugins")
: activeTab === "Server"
? t("workshop.filterServers")
: t("workshop.enterKeywords")
}
spellCheck={false}
autoFocus={isSearchTab}
className="bg-transparent border-none outline-none text-white text-lg mc-text-shadow w-full placeholder-white/40 font-[var(--font-base)] tracking-widest"
/>
: activeTab === "Server Plugins"
? t("workshop.filterPlugins")
: activeTab === "Server"
? t("workshop.filterServers")
: t("workshop.enterKeywords")
}
spellCheck={false}
autoFocus={isSearchTab}
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)] placeholder-white/40 tracking-widest"
/>
</div>
{search && (
<button
onClick={() => {
setSearch("");
searchRef.current?.focus();
}}
className="text-white/60 hover:text-white text-lg ml-2 bg-transparent border-none outline-none cursor-pointer mc-text-shadow"
className="text-white/60 hover:text-white text-lg mx-2 border-none outline-none cursor-pointer"
>
</button>
@@ -901,7 +896,9 @@ const WorkshopView = memo(function WorkshopView({
: "text-[#A0A0A0] bg-black/30 border-[#444] hover:text-white hover:border-[#888]"
}`}
>
{cat === "all" ? t("common.all").toUpperCase() : cat.toUpperCase()}
{cat === "all"
? t("common.all").toUpperCase()
: cat.toUpperCase()}
</button>
))}
</div>
@@ -918,7 +915,9 @@ const WorkshopView = memo(function WorkshopView({
: "text-[#A0A0A0] bg-black/30 border-[#444] hover:text-white hover:border-[#888]"
}`}
>
{cat === "all" ? t("common.all").toUpperCase() : cat.toUpperCase()}
{cat === "all"
? t("common.all").toUpperCase()
: cat.toUpperCase()}
</button>
))}
</div>
+2 -1
View File
@@ -453,7 +453,8 @@ body {
image-rendering: pixelated;
}
.mc-textinput-outer:focus-within {
.mc-textinput-outer:focus-within,
:focus > .mc-textinput-outer {
border: 2px solid #ffff00;
}
+1 -6
View File
@@ -1,11 +1,6 @@
import { useEffect, useState, useMemo, useCallback, useRef } from "react";
import { useTranslation } from "react-i18next";
import {
motion,
AnimatePresence,
MotionConfig,
getDefaultTransition,
} from "framer-motion";
import { motion, AnimatePresence, MotionConfig } from "framer-motion";
import "../css/App.css";
import HomeView from "../components/views/HomeView";
import SettingsView from "../components/views/SettingsView";