remix-project mirror
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
remix-project/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx

236 lines
8.4 KiB

3 years ago
import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators'
import { CustomTooltip } from '@remix-ui/helper'
3 years ago
import { Plugin } from '@remixproject/engine'
3 years ago
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
3 years ago
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'
3 years ago
import './remix-ui-tabs.css'
2 years ago
const _paq = window._paq = window._paq || []
3 years ago
2 years ago
3 years ago
/* eslint-disable-next-line */
export interface TabsUIProps {
3 years ago
tabs: Array<any>
plugin: Plugin,
onSelect: (index: number) => void
onClose: (index: number) => void
onZoomOut: () => void
onZoomIn: () => void
onReady: (api: any) => void
themeQuality: string
3 years ago
}
export interface TabsUIApi {
activateTab: (name: string) => void
3 years ago
active: () => string
}
interface ITabsState {
selectedIndex: number,
fileDecorations: fileDecoration[],
currentExt: string
3 years ago
}
interface ITabsAction {
type: string,
payload: any,
ext?: string,
3 years ago
}
const initialTabsState: ITabsState = {
selectedIndex: -1,
fileDecorations: [],
currentExt: ''
3 years ago
}
const tabsReducer = (state: ITabsState, action: ITabsAction) => {
switch (action.type) {
case 'SELECT_INDEX':
return {
...state,
currentExt: action.ext,
3 years ago
selectedIndex: action.payload,
}
case 'SET_FILE_DECORATIONS':
return {
...state,
fileDecorations: action.payload as fileDecoration[],
}
default:
return state
}
3 years ago
}
export const TabsUI = (props: TabsUIProps) => {
3 years ago
const [tabsState, dispatch] = useReducer(tabsReducer, initialTabsState);
3 years ago
const currentIndexRef = useRef(-1)
const tabsRef = useRef({})
3 years ago
const tabsElement = useRef(null)
3 years ago
3 years ago
const tabs = useRef(props.tabs)
tabs.current = props.tabs // we do this to pass the tabs list to the onReady callbacks
useEffect(() => {
3 years ago
if (props.tabs[tabsState.selectedIndex]) {
tabsRef.current[tabsState.selectedIndex].scrollIntoView({ behavior: 'smooth', block: 'center' })
}
3 years ago
}, [tabsState.selectedIndex])
const getFileDecorationClasses = (tab: any) => {
const fileDecoration = tabsState.fileDecorations.find((fileDecoration: fileDecoration) => {
if(`${fileDecoration.workspace.name}/${fileDecoration.path}` === tab.name) return true
})
return fileDecoration && fileDecoration.fileStateLabelClass
}
const getFileDecorationIcons = (tab: any) => {
return <FileDecorationIcons file={{path: tab.name}} fileDecorations={tabsState.fileDecorations} />
}
const renderTab = (tab, index) => {
3 years ago
const classNameImg = 'my-1 mr-1 text-dark ' + tab.iconClass
const classNameTab = 'nav-item nav-link d-flex justify-content-center align-items-center px-2 py-1 tab' + (index === currentIndexRef.current ? ' active' : '')
const invert = props.themeQuality === 'dark' ? 'invert(1)' : 'invert(0)'
3 years ago
return (
2 years ago
<CustomTooltip
tooltipId="tabsActive"
tooltipText={tab.tooltip}
placement="bottom"
>
<div
ref={el => { tabsRef.current[index] = el }}
className={classNameTab}
data-id={index === currentIndexRef.current ? 'tab-active' : ''}
data-path={tab.name}
2 years ago
>
{tab.icon ? (<img className="my-1 mr-1 iconImage" style={{ filter: invert }} src={tab.icon} />) : (<i className={classNameImg}></i>)}
2 years ago
<span className={`title-tabs ${getFileDecorationClasses(tab)}`}>{tab.title}</span>
2 years ago
{getFileDecorationIcons(tab)}
<span className="close-tabs" onClick={(event) => { props.onClose(index); event.stopPropagation() }}>
<i className="text-dark fas fa-times"></i>
</span>
</div>
</CustomTooltip>
3 years ago
)
}
const active = () => {
3 years ago
if (currentIndexRef.current < 0) return ''
3 years ago
return tabs.current[currentIndexRef.current].name
3 years ago
}
3 years ago
const activateTab = (name: string) => {
3 years ago
const index = tabs.current.findIndex((tab) => tab.name === name)
3 years ago
currentIndexRef.current = index
dispatch({ type: 'SELECT_INDEX', payload: index, ext: getExt(name)})
3 years ago
}
const setFileDecorations = (fileStates: fileDecoration[]) => {
dispatch({ type: 'SET_FILE_DECORATIONS', payload: fileStates })
3 years ago
}
3 years ago
3 years ago
const transformScroll = (event) => {
3 years ago
if (!event.deltaY) {
3 years ago
return
}
3 years ago
event.currentTarget.scrollLeft += event.deltaY + event.deltaX
event.preventDefault()
}
3 years ago
useEffect(() => {
props.onReady({
activateTab,
3 years ago
active,
setFileDecorations
3 years ago
})
3 years ago
3 years ago
return () => { tabsElement.current.removeEventListener('wheel', transformScroll) }
3 years ago
}, [])
const getExt = (path) => {
const root = path.split('#')[0].split('?')[0]
const ext = root.indexOf('.') !== -1 ? /[^.]+$/.exec(root) : null
2 years ago
if (ext) return ext[0].toLowerCase()
else return ''
}
3 years ago
return (
<div className='d-block'>
2 years ago
<CustomTooltip
placement="bottom"
tooltipId="overlay-tooltip-all-tabs"
tooltipText="Scroll to see all tabs"
>
<div className='remix-ui-tabs_end position-absolute position-fixed'></div>
2 years ago
</CustomTooltip>
<div className="remix-ui-tabs d-flex justify-content-between border-0 header nav-tabs" data-id="tabs-component">
<div className="d-flex flex-row" style={{ maxWidth: 'fit-content', width: '97%' }}>
<div className="d-flex flex-row justify-content-center align-items-center m-1 mt-1">
<button
data-id='play-editor'
className="btn text-success py-0"
disabled={!(tabsState.currentExt === 'js' || tabsState.currentExt === 'ts' || tabsState.currentExt === 'sol')}
onClick={async () => {
const path = active().substr(active().indexOf('/') + 1, active().length)
const content = await props.plugin.call('fileManager', "readFile", path)
if (tabsState.currentExt === 'js' || tabsState.currentExt === 'ts') {
await props.plugin.call('scriptRunner', 'execute', content, path)
_paq.push(['trackEvent', 'editor', 'clickRunFromEditor', tabsState.currentExt])
} else if (tabsState.currentExt === 'sol' || tabsState.currentExt === 'yul') {
await props.plugin.call('solidity', 'compile', path)
_paq.push(['trackEvent', 'editor', 'clickRunFromEditor', tabsState.currentExt])
}
}}
>
<CustomTooltip
placement="bottom"
tooltipId="overlay-tooltip-run-script"
tooltipText={<span>
{(tabsState.currentExt === 'js' || tabsState.currentExt === 'ts') ? "Run script (CTRL + SHIFT + S)" :
tabsState.currentExt === 'sol' || tabsState.currentExt === 'yul'? "Compile CTRL + S" : "Select .sol or .yul file to compile or a .ts or .js file and run it"}
</span>}
>
<i className="fad fa-play"></i>
</CustomTooltip>
</button>
<CustomTooltip
placement="bottom"
2 years ago
tooltipId="overlay-tooltip-zoom-out"
tooltipText="Zoom out"
>
2 years ago
<span data-id="tabProxyZoomOut" className="btn btn-sm px-2 fas fa-search-minus text-dark" onClick={() => props.onZoomOut()}></span>
</CustomTooltip>
2 years ago
<CustomTooltip
placement="bottom"
tooltipId="overlay-tooltip-run-zoom-in"
tooltipText="Zoom in"
>
<span data-id="tabProxyZoomIn" className="btn btn-sm px-2 fas fa-search-plus text-dark" onClick={() => props.onZoomIn()}></span>
</CustomTooltip>
</div>
<Tabs
className="tab-scroll"
selectedIndex={tabsState.selectedIndex}
domRef={(domEl) => {
if (tabsElement.current) return
tabsElement.current = domEl
tabsElement.current.addEventListener('wheel', transformScroll)
}}
onSelect={(index) => {
props.onSelect(index)
currentIndexRef.current = index
dispatch({ type: 'SELECT_INDEX', payload: index, ext: getExt(props.tabs[currentIndexRef.current].name)})
}}
>
<TabList className="d-flex flex-row align-items-center">
{props.tabs.map((tab, i) => <Tab className="" key={tab.name}>{renderTab(tab, i)}</Tab>)}
</TabList>
{props.tabs.map((tab) => <TabPanel key={tab.name} ></TabPanel>)}
</Tabs>
</div>
3 years ago
</div>
</div>
)
}
export default TabsUI