11import { ChevronLeft , ChevronRight , Home , Lock , Maximize2 , Menu , MoreVertical , Plus , RotateCw , Star , X } from "lucide-react" ;
22import { useEffect , useMemo , useRef , useState } from "react" ;
3- import { actionBarClass , addressInputClass , classNames , closeButtonClass , encodeProxyUrl , formatUrl , getActualUrl , getDefaultUrl , iconButtonClass , type Tab , tabButtonClass } from "@/lib/tabs" ;
3+ import { actionBarClass , addressInputClass , classNames , closeButtonClass , encodeProxyUrl , formatUrl , getActualUrl , getDefaultUrl , type Tab , tabButtonClass } from "@/lib/tabs" ;
4+
5+ const IconButton = ( { onClick, icon : Icon , className = "" , disabled = false , title = "" } : { onClick ?: ( ) => void ; icon : React . ComponentType < { className ?: string } > ; className ?: string ; disabled ?: boolean ; title ?: string } ) => (
6+ < button
7+ type = "button"
8+ onClick = { onClick }
9+ disabled = { disabled }
10+ title = { title }
11+ className = { `group p-2 rounded-full text-text-secondary transition-all duration-200
12+ hover:bg-interactive hover:text-text disabled:opacity-50 disabled:cursor-not-allowed active:scale-95 ${ className } ` }
13+ >
14+ < Icon className = "h-4 w-4 stroke-[2.5px] group-hover:stroke-text" />
15+ </ button >
16+ ) ;
417
518export default function Browser ( ) {
619 const [ tabs , setTabs ] = useState < Tab [ ] > ( [ { id : 1 , title : "Tab 1" , url : "about:blank" , active : true , reloadKey : 0 } ] ) ;
@@ -14,7 +27,7 @@ export default function Browser() {
1427 let firstTabUrl = getDefaultUrl ( ) ;
1528 try {
1629 const goUrl = sessionStorage . getItem ( "goUrl" ) ;
17- if ( goUrl && goUrl . trim ( ) ) {
30+ if ( goUrl ? .trim ( ) ) {
1831 firstTabUrl = goUrl ;
1932 }
2033 } catch ( error ) {
@@ -46,7 +59,7 @@ export default function Browser() {
4659 const iframe = iframeRefs . current [ activeTab . id ] ;
4760 if ( ! iframe ) return ;
4861
49- const updateUrlBar = ( ) => {
62+ const updateState = ( ) => {
5063 const actualUrl = getActualUrl ( iframe ) ;
5164 if ( actualUrl && actualUrl !== url ) {
5265 setUrl ( actualUrl ) ;
@@ -69,74 +82,56 @@ export default function Browser() {
6982 const urlObj = new URL ( actualUrl ) ;
7083 const defaultFavicon = `${ urlObj . origin } /favicon.ico` ;
7184 setFavicons ( ( prev ) => ( { ...prev , [ activeTab . id ] : defaultFavicon } ) ) ;
72- } catch ( e ) { }
85+ } catch ( _e ) { }
7386 }
7487 }
75- } catch ( e ) { }
88+ } catch ( _e ) { }
7689 } ;
7790
78- iframe . addEventListener ( "load" , updateUrlBar ) ;
79-
80- const interval = setInterval ( updateUrlBar , 1000 ) ;
91+ iframe . addEventListener ( "load" , updateState ) ;
92+ const interval = setInterval ( updateState , 1000 ) ;
8193
8294 return ( ) => {
83- iframe . removeEventListener ( "load" , updateUrlBar ) ;
95+ iframe . removeEventListener ( "load" , updateState ) ;
8496 clearInterval ( interval ) ;
8597 } ;
8698 } , [ activeTab , url ] ) ;
8799
88100 const setActiveTab = ( id : number ) => {
89- const target = tabs . find ( ( tab ) => tab . id === id ) ;
90- if ( ! target ) return ;
91- setTabs ( ( prev ) =>
92- prev . map ( ( tab ) => ( {
93- ...tab ,
94- active : tab . id === id ,
95- } ) ) ,
96- ) ;
97-
98- const iframe = iframeRefs . current [ id ] ;
99- const actualUrl = getActualUrl ( iframe ) ;
100- setUrl ( actualUrl || target . url ) ;
101+ setTabs ( ( prev ) => prev . map ( ( tab ) => ( { ...tab , active : tab . id === id } ) ) ) ;
101102 } ;
102103
103104 const addNewTab = ( ) => {
104- setTabs ( ( prev ) => {
105- const nextId = prev . length ? Math . max ( ...prev . map ( ( tab ) => tab . id ) ) + 1 : 1 ;
106- const newTabs = prev . map ( ( tab ) => ( { ...tab , active : false } ) ) ;
107- return [ ...newTabs , { id : nextId , title : `Tab ${ nextId } ` , url : getDefaultUrl ( ) , active : true , reloadKey : 0 } ] ;
108- } ) ;
109- setUrl ( getDefaultUrl ( ) ) ;
105+ const newId = tabs . length ? Math . max ( ...tabs . map ( ( tab ) => tab . id ) ) + 1 : 1 ;
106+ const defaultUrl = getDefaultUrl ( ) ;
107+
108+ setTabs ( ( prev ) => [ ...prev . map ( ( tab ) => ( { ...tab , active : false } ) ) , { id : newId , title : `Tab ${ newId } ` , url : defaultUrl , active : true , reloadKey : 0 } ] ) ;
109+ setUrl ( defaultUrl ) ;
110110 } ;
111111
112112 const closeTab = ( id : number ) => {
113113 setTabs ( ( prev ) => {
114- let nextUrl = url ;
115- const filtered = prev . filter ( ( tab ) => tab . id !== id ) ;
114+ const remaining = prev . filter ( ( tab ) => tab . id !== id ) ;
116115
117- if ( filtered . length === 0 ) {
116+ if ( remaining . length === 0 ) {
118117 let firstTabUrl = getDefaultUrl ( ) ;
119118 try {
120119 const goUrl = sessionStorage . getItem ( "goUrl" ) ;
121- if ( goUrl && goUrl . trim ( ) ) {
120+ if ( goUrl ? .trim ( ) ) {
122121 firstTabUrl = goUrl ;
123122 }
124123 } catch ( error ) {
125124 console . warn ( "Session storage access failed:" , error ) ;
126125 }
127126 return [ { id : Date . now ( ) , title : "Tab 1" , url : firstTabUrl , active : true , reloadKey : 0 } ] ;
128- } else if ( ! filtered . some ( ( tab ) => tab . active ) ) {
129- filtered [ 0 ] = { ...filtered [ 0 ] , active : true } ;
130- nextUrl = filtered [ 0 ] . url ;
131- } else {
132- const currentActive = filtered . find ( ( tab ) => tab . active ) ;
133- if ( currentActive ) {
134- nextUrl = currentActive . url ;
135- }
136127 }
137128
138- setUrl ( nextUrl ) ;
139- return filtered ;
129+ if ( prev . find ( ( tab ) => tab . id === id ) ?. active ) {
130+ remaining [ remaining . length - 1 ] . active = true ;
131+ setUrl ( remaining [ remaining . length - 1 ] . url ) ;
132+ }
133+
134+ return remaining ;
140135 } ) ;
141136 } ;
142137
@@ -158,36 +153,38 @@ export default function Browser() {
158153 setUrl ( formattedUrl ) ;
159154 } ;
160155
161- const goHome = ( ) => {
162- window . location . href = "/" ;
163- } ;
164-
165- const removeBookmark = ( index : number ) => {
156+ const removeBookmark = ( bookmarkUrl : string , bookmarkTitle : string ) => {
166157 try {
167- const updatedBookmarks = bookmarks . filter ( ( _ , i ) => i !== index ) ;
158+ const updatedBookmarks = bookmarks . filter ( ( b ) => ! ( b . url === bookmarkUrl && b . Title === bookmarkTitle ) ) ;
168159 setBookmarks ( updatedBookmarks ) ;
169160 localStorage . setItem ( "bookmarks" , JSON . stringify ( updatedBookmarks ) ) ;
170161 } catch ( e ) {
171162 console . error ( "Failed to remove bookmark:" , e ) ;
172163 }
173164 } ;
174165
175- const goBack = ( ) => {
166+ const Action = ( action : "back" | "forward" | "reload" | "home" ) => {
176167 if ( ! activeTab ) return ;
177168 const iframe = iframeRefs . current [ activeTab . id ] ;
178- iframe ?. contentWindow ?. history . back ( ) ;
179- } ;
180169
181- const goForward = ( ) => {
182- if ( ! activeTab ) return ;
183- const iframe = iframeRefs . current [ activeTab . id ] ;
184- iframe ?. contentWindow ?. history . forward ( ) ;
185- } ;
170+ if ( action === "home" ) {
171+ window . location . href = "/" ;
172+ return ;
173+ }
186174
187- const reloadTab = ( ) => {
188- if ( ! activeTab ) return ;
189- const iframe = iframeRefs . current [ activeTab . id ] ;
190- iframe ?. contentWindow ?. location . reload ( ) ;
175+ if ( ! iframe ?. contentWindow ) return ;
176+
177+ switch ( action ) {
178+ case "back" :
179+ iframe . contentWindow . history . back ( ) ;
180+ break ;
181+ case "forward" :
182+ iframe . contentWindow . history . forward ( ) ;
183+ break ;
184+ case "reload" :
185+ iframe . contentWindow . location . reload ( ) ;
186+ break ;
187+ }
191188 } ;
192189
193190 const toggleFullscreen = ( ) => {
@@ -215,7 +212,7 @@ export default function Browser() {
215212 try {
216213 const urlObj = new URL ( actualUrl ) ;
217214 faviconUrl = `${ urlObj . origin } /favicon.ico` ;
218- } catch ( e ) {
215+ } catch ( _e ) {
219216 faviconUrl = "" ;
220217 }
221218 }
@@ -237,19 +234,7 @@ export default function Browser() {
237234 < div className = "flex h-screen flex-col bg-background" >
238235 < div className = "flex items-center gap-1 bg-background px-3 pt-1.5 pb-0" >
239236 { tabs . map ( ( tab ) => (
240- < div
241- key = { tab . id }
242- role = "button"
243- tabIndex = { 0 }
244- onClick = { ( ) => setActiveTab ( tab . id ) }
245- onKeyDown = { ( event ) => {
246- if ( event . key === "Enter" || event . key === " " ) {
247- event . preventDefault ( ) ;
248- setActiveTab ( tab . id ) ;
249- }
250- } }
251- className = { classNames ( tabButtonClass , tab . active ? "bg-background-secondary text-text shadow-sm" : "bg-background text-text-secondary hover:bg-interactive" ) }
252- >
237+ < button key = { tab . id } type = "button" onClick = { ( ) => setActiveTab ( tab . id ) } className = { classNames ( tabButtonClass , tab . active ? "bg-background-secondary text-text shadow-sm" : "bg-background text-text-secondary hover:bg-interactive" ) } >
253238 < div className = "flex min-w-0 flex-1 items-center gap-2" >
254239 { favicons [ tab . id ] ? (
255240 < img
@@ -276,7 +261,7 @@ export default function Browser() {
276261 >
277262 < X className = "h-3 w-3" />
278263 </ button >
279- </ div >
264+ </ button >
280265 ) ) }
281266 < button type = "button" className = "inline-flex h-8 w-8 items-center justify-center rounded-md text-sm font-medium text-text-secondary transition-colors hover:bg-background-secondary/50 hover:text-text" onClick = { addNewTab } aria-label = "Add tab" >
282267 < Plus className = "h-4 w-4" />
@@ -285,18 +270,10 @@ export default function Browser() {
285270
286271 < div className = "flex items-center justify-between gap-3 bg-background-secondary px-3 py-2 backdrop-blur-xl" >
287272 < div className = "flex items-center gap-1" >
288- < button type = "button" className = { iconButtonClass } onClick = { goHome } aria-label = "Home" >
289- < Home className = "h-4 w-4" />
290- </ button >
291- < button type = "button" className = { iconButtonClass } onClick = { goBack } aria-label = "Back" >
292- < ChevronLeft className = "h-4 w-4" />
293- </ button >
294- < button type = "button" className = { iconButtonClass } onClick = { goForward } aria-label = "Forward" >
295- < ChevronRight className = "h-4 w-4" />
296- </ button >
297- < button type = "button" className = { iconButtonClass } onClick = { reloadTab } aria-label = "Reload" >
298- < RotateCw className = "h-4 w-4" />
299- </ button >
273+ < IconButton icon = { Home } onClick = { ( ) => Action ( "home" ) } title = "Home" />
274+ < IconButton icon = { ChevronLeft } onClick = { ( ) => Action ( "back" ) } title = "Back" />
275+ < IconButton icon = { ChevronRight } onClick = { ( ) => Action ( "forward" ) } title = "Forward" />
276+ < IconButton icon = { RotateCw } onClick = { ( ) => Action ( "reload" ) } title = "Reload" />
300277 </ div >
301278
302279 < div className = "flex-1" >
@@ -317,34 +294,26 @@ export default function Browser() {
317294 </ div >
318295
319296 < div className = "flex items-center gap-1" >
320- < button type = "button" className = { iconButtonClass } onClick = { toggleFullscreen } aria-label = "Fullscreen" >
321- < Maximize2 className = "h-4 w-4" />
322- </ button >
323- < button type = "button" className = { iconButtonClass } onClick = { addBookmark } aria-label = "Bookmark" >
324- < Star className = "h-4 w-4" />
325- </ button >
326- < button type = "button" className = { iconButtonClass } aria-label = "Menu" >
327- < Menu className = "h-4 w-4" />
328- </ button >
329- < button type = "button" className = { iconButtonClass } aria-label = "More" >
330- < MoreVertical className = "h-4 w-4" />
331- </ button >
297+ < IconButton icon = { Maximize2 } onClick = { toggleFullscreen } title = "Fullscreen" />
298+ < IconButton icon = { Star } onClick = { addBookmark } title = "Bookmark" />
299+ < IconButton icon = { Menu } title = "Menu" />
300+ < IconButton icon = { MoreVertical } title = "More" />
332301 </ div >
333302 </ div >
334303
335304 { bookmarks . length > 0 && (
336305 < div className = "flex items-center gap-0.5 bg-background-secondary px-3 py-1.5 overflow-x-auto border-b border-border/50" >
337- { bookmarks . map ( ( bookmark , index ) => (
306+ { bookmarks . map ( ( bookmark ) => (
338307 < button
339- key = { index }
308+ key = { ` ${ bookmark . url } - ${ bookmark . Title } ` }
340309 type = "button"
341310 className = "inline-flex items-center gap-2 rounded-lg px-3 py-1.5 text-sm text-text-secondary hover:bg-interactive hover:scale-105 transition-all shrink-0"
342311 style = { { maxWidth : "195px" } }
343312 onClick = { ( ) => handleNavigate ( bookmark . url ) }
344313 onContextMenu = { ( e ) => {
345314 e . preventDefault ( ) ;
346315 if ( confirm ( `Remove bookmark "${ bookmark . Title } "?` ) ) {
347- removeBookmark ( index ) ;
316+ removeBookmark ( bookmark . url , bookmark . Title ) ;
348317 }
349318 } }
350319 >
0 commit comments