commit
e2f1158739
@ -0,0 +1,106 @@ |
||||
import React from 'react' |
||||
import { EventEmitter } from 'events' |
||||
import { Plugin } from '@remixproject/engine' |
||||
import packageJson from '../../../../../package.json' |
||||
import { PluginViewWrapper } from '@remix-ui/helper' |
||||
import { PluginProfile, StatusBarInterface } from '../../types' |
||||
import { RemixUIStatusBar } from '@remix-ui/statusbar' |
||||
import { FilePanelType } from '@remix-ui/workspace' |
||||
import { VerticalIcons } from './vertical-icons' |
||||
|
||||
const statusBarProfile: PluginProfile = { |
||||
name: 'statusBar', |
||||
displayName: 'Status Bar', |
||||
description: 'Remix IDE status bar panel', |
||||
methods: ['isAIActive'], |
||||
version: packageJson.version, |
||||
} |
||||
|
||||
export class StatusBar extends Plugin implements StatusBarInterface { |
||||
htmlElement: HTMLDivElement |
||||
events: EventEmitter |
||||
filePanelPlugin: FilePanelType |
||||
verticalIcons: VerticalIcons |
||||
dispatch: React.Dispatch<any> = () => {} |
||||
currentWorkspaceName: string = '' |
||||
isGitRepo: boolean = false |
||||
isAiActive: boolean = false |
||||
constructor(filePanel: FilePanelType, veritcalIcons: VerticalIcons) { |
||||
super(statusBarProfile) |
||||
this.filePanelPlugin = filePanel |
||||
this.verticalIcons = veritcalIcons |
||||
this.events = new EventEmitter() |
||||
this.htmlElement = document.createElement('div') |
||||
this.htmlElement.setAttribute('id', 'status-bar') |
||||
this.filePanelPlugin |
||||
} |
||||
|
||||
async isWorkspaceAGitRepo() { |
||||
const isGit = await this.call('fileManager', 'isGitRepo') |
||||
if (!isGit) return |
||||
this.isGitRepo = true |
||||
this.renderComponent() |
||||
} |
||||
|
||||
async setCurrentGitWorkspaceName() { |
||||
if (!this.isGitRepo) return |
||||
const workspaceName = localStorage.getItem('currentWorkspace') |
||||
workspaceName && workspaceName.length > 0 ? this.currentWorkspaceName = workspaceName : this.currentWorkspaceName = 'unknown' |
||||
this.renderComponent() |
||||
} |
||||
|
||||
async isAIActive() { |
||||
let aiActive |
||||
this.on('settings', 'copilotChoiceUpdated', async (isChecked) => { |
||||
aiActive = isChecked |
||||
this.isAiActive = isChecked |
||||
}) |
||||
this.renderComponent() |
||||
return aiActive |
||||
} |
||||
|
||||
onActivation(): void { |
||||
this.on('filePanel', 'workspaceInitializationCompleted', async () => { |
||||
const isGit = await this.call('fileManager', 'isGitRepo') |
||||
if (!isGit) return |
||||
const workspaceName = localStorage.getItem('currentWorkspace') |
||||
workspaceName && workspaceName.length > 0 ? this.currentWorkspaceName = workspaceName : this.currentWorkspaceName = '' |
||||
}) |
||||
this.on('filePanel', 'switchToWorkspace', async (workspace: string) => { |
||||
console.log('from status bar switchToWorkspace') |
||||
await this.isWorkspaceAGitRepo() |
||||
if (!this.isGitRepo) { |
||||
this.currentWorkspaceName = 'Not a git repo' |
||||
return |
||||
} |
||||
const workspaceName = localStorage.getItem('currentWorkspace') |
||||
workspaceName && workspaceName.length > 0 ? this.currentWorkspaceName = workspaceName : this.currentWorkspaceName = 'error' |
||||
}) |
||||
this.on('settings', 'copilotChoiceChanged', (isAiActive) => { |
||||
this.isAiActive = isAiActive |
||||
}) |
||||
this.renderComponent() |
||||
} |
||||
|
||||
setDispatch(dispatch: React.Dispatch<any>) { |
||||
this.dispatch = dispatch |
||||
} |
||||
|
||||
renderComponent() { |
||||
this.dispatch({ |
||||
plugins: this, |
||||
}) |
||||
} |
||||
|
||||
updateComponent(state: any) { |
||||
return <RemixUIStatusBar statusBarPlugin={state.plugins} /> |
||||
} |
||||
|
||||
render() { |
||||
return ( |
||||
<div data-id="status-bar-container"> |
||||
<PluginViewWrapper plugin={this} /> |
||||
</div> |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
export interface PluginProfile { |
||||
name: string |
||||
displayName: string |
||||
description: string |
||||
keywords?: string[] |
||||
icon?: string |
||||
url?: string |
||||
methods?: string[] |
||||
events?: string[] |
||||
version?: string |
||||
} |
||||
|
||||
export interface StatusBarInterface { |
||||
htmlElement: HTMLDivElement |
||||
events: EventEmitter |
||||
filePanelPlugin: FilePanelType |
||||
dispatch: React.Dispatch<any> |
||||
setDispatch(dispatch: React.Dispatch<any>): void |
||||
} |
@ -0,0 +1,23 @@ |
||||
|
||||
remixui_statusbar:hover { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.remixui_statusbar_gitstatus |
||||
.remixui_statusbar_gitstatus:hover { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
/** |
||||
* approximately same height with vscode statusbar |
||||
**/ |
||||
.remixui_statusbar_height { |
||||
height: 21px; |
||||
} |
||||
|
||||
.remixui_statusbar_activelink { |
||||
text-decoration: none; |
||||
} |
||||
.remixui_statusbar_activelink:active { |
||||
color: var(--danger); |
||||
} |
@ -0,0 +1,2 @@ |
||||
export * from './lib/remixui-statusbar-panel' |
||||
export { StatusBarInterface } from './lib/types' |
@ -0,0 +1,44 @@ |
||||
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
|
||||
import { StatusBar } from 'apps/remix-ide/src/app/components/status-bar' |
||||
import { CustomTooltip } from '@remix-ui/helper' |
||||
import React, { useEffect, useState } from 'react' |
||||
|
||||
interface AIStatusProps { |
||||
plugin: StatusBar |
||||
isAiActive: boolean |
||||
setIsAiActive: (isAiActive: boolean) => void |
||||
aiActive: () => Promise<any> |
||||
} |
||||
|
||||
export default function AIStatus(props: AIStatusProps) { |
||||
const [copilotActive, setCopilotActive] = useState(false) |
||||
useEffect(() => { |
||||
const run = async () => { |
||||
props.plugin.on('fileManager', 'currentFileChanged', async (isAiActive) => { |
||||
const aiActivate = await props.plugin.call('settings', 'get', 'settings/copilot/suggest/activate') |
||||
setCopilotActive(aiActivate) |
||||
}) |
||||
} |
||||
run() |
||||
}, [props.plugin.isAiActive, props.isAiActive]) |
||||
|
||||
useEffect(() => { |
||||
const run = async () => { |
||||
props.plugin.on('settings', 'copilotChoiceUpdated', async (isChecked) => { |
||||
await props.plugin.isAIActive() |
||||
setCopilotActive(isChecked) |
||||
}) |
||||
} |
||||
run() |
||||
}, [props.plugin.isAiActive]) |
||||
return ( |
||||
<CustomTooltip |
||||
tooltipText={copilotActive ? "Remix Copilot activated" : "Remix Copilot disabled."} |
||||
> |
||||
<div className="d-flex flex-row pr-2 text-white justify-content-center align-items-center"> |
||||
<span className={copilotActive === false ? "fa-regular fa-microchip-ai ml-1 text-danger" : "fa-regular fa-microchip-ai ml-1"}></span> |
||||
<span className={copilotActive === false ? "small mx-1 text-danger semi-bold" : "small mx-1 semi-bold" }>Remix Copilot</span> |
||||
</div> |
||||
</CustomTooltip> |
||||
) |
||||
} |
@ -0,0 +1,88 @@ |
||||
import React, { useEffect, Dispatch } from 'react' |
||||
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
|
||||
import { StatusBar } from 'apps/remix-ide/src/app/components/status-bar' |
||||
import '../../css/statusbar.css' |
||||
import { CustomTooltip } from '@remix-ui/helper' |
||||
|
||||
export interface GitStatusProps { |
||||
plugin: StatusBar |
||||
gitBranchName: string |
||||
setGitBranchName: Dispatch<React.SetStateAction<string>> |
||||
} |
||||
|
||||
export default function GitStatus({ plugin, gitBranchName, setGitBranchName }: GitStatusProps) { |
||||
|
||||
useEffect(() => { |
||||
const run = async () => { |
||||
plugin.on('filePanel', 'setWorkspace', async (workspace) => { |
||||
const isGit = await plugin.call('fileManager', 'isGitRepo') |
||||
if (isGit) { |
||||
setGitBranchName(workspace.name) |
||||
} else { |
||||
setGitBranchName('Not a git repo') |
||||
} |
||||
}) |
||||
} |
||||
run() |
||||
}, [gitBranchName, plugin.isGitRepo]) |
||||
|
||||
useEffect(() => { |
||||
const run = async () => { |
||||
plugin.on('filePanel', 'workspaceInitializationCompleted', async () => { |
||||
const isGit = await plugin.call('fileManager', 'isGitRepo') |
||||
if (isGit) { |
||||
const workspace = localStorage.getItem('currentWorkspace') |
||||
setGitBranchName(workspace) |
||||
} else { |
||||
setGitBranchName('Not a git repo') |
||||
} |
||||
}) |
||||
} |
||||
run() |
||||
}, [gitBranchName, plugin.isGitRepo]) |
||||
|
||||
useEffect(() => { |
||||
const run = async () => { |
||||
plugin.on('dGitProvider', 'init', async () => { |
||||
const isGit = await plugin.call('fileManager', 'isGitRepo') |
||||
if (isGit) { |
||||
const workspace = localStorage.getItem('currentWorkspace') |
||||
setGitBranchName(workspace) |
||||
} |
||||
}) |
||||
} |
||||
run() |
||||
}, [gitBranchName, plugin.isGitRepo]) |
||||
|
||||
const lightDgitUp = async () => { |
||||
const isActive = await plugin.call('manager', 'isActive', 'dgit') |
||||
const isGit = await plugin.call('fileManager', 'isGitRepo') |
||||
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit') |
||||
if (gitBranchName.length > 0 && isGit) { |
||||
plugin.verticalIcons.select('dgit') |
||||
} |
||||
} |
||||
|
||||
const initializeNewGitRepo = async () => { |
||||
await plugin.call('dGitProvider', 'init') |
||||
const isActive = await plugin.call('manager', 'isActive', 'dgit') |
||||
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit') |
||||
// plugin.verticalIcons.select('dgit')
|
||||
} |
||||
|
||||
return ( |
||||
<CustomTooltip |
||||
tooltipText={`${gitBranchName === 'Not a git repo' ? 'Initialize as a git repo' : gitBranchName} (Git)`} |
||||
> |
||||
<div |
||||
className="d-flex flex-row pl-3 text-white justify-content-center align-items-center remixui_statusbar_gitstatus" |
||||
onClick={async () => await lightDgitUp()} |
||||
> |
||||
{gitBranchName.length > 0 && gitBranchName !== 'Not a git repo' ? <span className="fa-regular fa-code-branch ml-1"></span> |
||||
: <span className=" ml-1" onClick={initializeNewGitRepo}> Initialize as git repo</span>} |
||||
{gitBranchName.length > 0 && gitBranchName !== 'Not a git repo' && <span className="ml-1">{gitBranchName}</span>} |
||||
{gitBranchName.length > 0 && gitBranchName !== 'Not a git repo' && <span className="fa-solid fa-arrows-rotate fa-1 ml-1"></span>} |
||||
</div> |
||||
</CustomTooltip> |
||||
) |
||||
} |
@ -0,0 +1,27 @@ |
||||
import React from 'react' |
||||
import { FormattedMessage } from 'react-intl' |
||||
import { ExtendedRefs, ReferenceType } from '@floating-ui/react' |
||||
import { CustomTooltip } from '@remix-ui/helper' |
||||
|
||||
export interface ScamAlertStatusProps { |
||||
refs: ExtendedRefs<ReferenceType> |
||||
getReferenceProps: (userProps?: React.HTMLProps<HTMLElement> | undefined) => Record<string, unknown> |
||||
} |
||||
|
||||
export default function ScamAlertStatus ({ refs, getReferenceProps }: ScamAlertStatusProps) { |
||||
|
||||
return ( |
||||
<> |
||||
<CustomTooltip |
||||
tooltipText={"Scam Alerts"} |
||||
> |
||||
<div className="mr-2 d-flex align-items-center justify-content-center" id="hTScamAlertSection" ref={refs.setReference} {...getReferenceProps()}> |
||||
<span className="pr-2 far fa-exclamation-triangle text-white"></span> |
||||
<span className="text-white font-semibold small"> |
||||
<FormattedMessage id="home.scamAlert" /> |
||||
</span> |
||||
</div> |
||||
</CustomTooltip> |
||||
</> |
||||
) |
||||
} |
@ -0,0 +1,48 @@ |
||||
import { ExtendedRefs, ReferenceType } from '@floating-ui/react' |
||||
import React, { CSSProperties } from 'react' |
||||
import { FormattedMessage } from 'react-intl' |
||||
import { ScamAlert } from '../remixui-statusbar-panel' |
||||
import '../../css/statusbar.css' |
||||
|
||||
const _paq = (window._paq = window._paq || []) // eslint-disable-line
|
||||
|
||||
export interface ScamDetailsProps { |
||||
refs: ExtendedRefs<ReferenceType> |
||||
floatStyle: CSSProperties |
||||
getFloatingProps: (userProps?: React.HTMLProps<HTMLElement> | undefined) => Record<string, unknown> |
||||
scamAlerts: ScamAlert[] |
||||
} |
||||
|
||||
export default function ScamDetails ({ refs, floatStyle, scamAlerts }: ScamDetailsProps) { |
||||
|
||||
return ( |
||||
<div |
||||
ref={refs.setFloating} |
||||
style={ floatStyle } |
||||
className="px-1 ml-1 mb-1 d-flex w-25 alert alert-danger border border-danger" |
||||
> |
||||
<span className="align-self-center pl-4 mt-1"> |
||||
<i style={{ fontSize: 'xxx-large', fontWeight: 'lighter' }} className="pr-2 far text-danger fa-exclamation-triangle"></i> |
||||
</span> |
||||
<div className="d-flex flex-column text-danger"> |
||||
{scamAlerts && scamAlerts.map((alert, index) => ( |
||||
<span className="pl-4 mt-1" key={`${alert.url}${index}`}> |
||||
{alert.url.length < 1 ? <FormattedMessage id={`home.scamAlertText${index + 1}`} defaultMessage={alert.message} /> |
||||
: (<><FormattedMessage id={`home.scamAlertText${index + 1}`} defaultMessage={alert.message} /> : |
||||
<a |
||||
className={`remixui_home_text text-decoration-none ${index === 1 ? 'pl-2' : ''}`} |
||||
onClick={() => { |
||||
index === 1 && _paq.push(['trackEvent', 'hometab', 'scamAlert', 'learnMore']) |
||||
index === 2 && _paq.push(['trackEvent', 'hometab', 'scamAlert', 'safetyTips']) |
||||
}} |
||||
target="__blank" |
||||
href={scamAlerts[index].url} |
||||
> |
||||
<FormattedMessage id="home.here" defaultMessage={scamAlerts[index].message} /> |
||||
</a></>)} |
||||
</span> |
||||
))} |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
@ -0,0 +1,86 @@ |
||||
import React, { useEffect, useState } from 'react' |
||||
import GitStatus from './components/gitStatus' |
||||
import AIStatus from './components/aiStatus' |
||||
import ScamAlertStatus from './components/scamAlertStatus' |
||||
import ScamDetails from './components/scamDetails' |
||||
import { FloatingFocusManager, autoUpdate, flip, offset, shift, size, useClick, useDismiss, useFloating, useInteractions, useRole } from '@floating-ui/react' |
||||
import axios from 'axios' |
||||
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
|
||||
import { StatusBar } from 'apps/remix-ide/src/app/components/status-bar' |
||||
|
||||
export interface RemixUIStatusBarProps { |
||||
statusBarPlugin: StatusBar |
||||
} |
||||
|
||||
export type ScamAlert = { |
||||
message: string |
||||
url: string |
||||
} |
||||
|
||||
export function RemixUIStatusBar({ statusBarPlugin }: RemixUIStatusBarProps) { |
||||
const [showScamDetails, setShowScamDetails] = useState(false) |
||||
const [scamAlerts, setScamAlerts] = useState<ScamAlert[]>([]) |
||||
const [gitBranchName, setGitBranchName] = useState('') |
||||
const [isAiActive, setIsAiActive] = useState(false) |
||||
const { refs, context, floatingStyles } = useFloating({ |
||||
open: showScamDetails, |
||||
onOpenChange: setShowScamDetails, |
||||
middleware: [offset(10), flip({ fallbackAxisSideDirection: 'end' }), shift({ |
||||
mainAxis: true, padding: 10 |
||||
}), size({ |
||||
apply({ availableWidth, availableHeight, elements, ...state }) { |
||||
console.log(state) |
||||
Object.assign(elements.floating.style, { |
||||
maxWidth: `${availableWidth}`, |
||||
maxHeight: `auto` |
||||
}) |
||||
} |
||||
})], |
||||
whileElementsMounted: autoUpdate, |
||||
}) |
||||
const click = useClick(context) |
||||
const dismiss = useDismiss(context) |
||||
const role = useRole(context) |
||||
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss, role]) |
||||
|
||||
useEffect(() => { |
||||
const abortController = new AbortController() |
||||
const signal = abortController.signal |
||||
async function getScamAlerts() { |
||||
const response = await axios.get('https://raw.githubusercontent.com/remix-project-org/remix-dynamics/main/ide/scam-alerts.json', { signal }) |
||||
if (signal.aborted) return |
||||
setScamAlerts(response.data.alerts) |
||||
} |
||||
getScamAlerts() |
||||
return () => { |
||||
abortController.abort() |
||||
} |
||||
}, []) |
||||
|
||||
const lightAiUp = async () => { |
||||
const aiActive = await statusBarPlugin.call('settings', 'get', 'settings/copilot/suggest/activate') |
||||
if (!aiActive) return |
||||
setIsAiActive(aiActive) |
||||
return aiActive |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
{showScamDetails && ( |
||||
<FloatingFocusManager context={context} modal={false}> |
||||
<ScamDetails refs={refs} floatStyle={{ ...floatingStyles, minHeight: 'auto', alignContent: 'center', paddingRight: '0.5rem' }} getFloatingProps={getFloatingProps} scamAlerts={scamAlerts} /> |
||||
</FloatingFocusManager> |
||||
)} |
||||
<div className="d-flex remixui_statusbar_height flex-row bg-primary justify-content-between align-items-center"> |
||||
<div className="remixui_statusbar remixui_statusbar_gitstatus"> |
||||
<GitStatus plugin={statusBarPlugin} gitBranchName={gitBranchName} setGitBranchName={setGitBranchName} /> |
||||
</div> |
||||
<div className="remixui_statusbar"></div> |
||||
<div className="remixui_statusbar d-flex flex-row"> |
||||
<ScamAlertStatus refs={refs} getReferenceProps={getReferenceProps} /> |
||||
<AIStatus plugin={statusBarPlugin} aiActive={lightAiUp} isAiActive={isAiActive} setIsAiActive={setIsAiActive} /> |
||||
</div> |
||||
</div> |
||||
</> |
||||
) |
||||
} |
@ -0,0 +1,27 @@ |
||||
import EventEmitter from 'events' |
||||
import { Plugin } from '@remixproject/engine' |
||||
import { FilePanelType } from '@remix-ui/workspace' |
||||
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
|
||||
import { VerticalIcons } from 'apps/remix-ide/src/app/components/vertical-icons' |
||||
export interface PluginProfile { |
||||
name: string |
||||
displayName: string |
||||
description: string |
||||
keywords?: string[] |
||||
icon?: string |
||||
url?: string |
||||
methods?: string[] |
||||
events?: string[] |
||||
version?: string |
||||
} |
||||
|
||||
export interface StatusBarInterface extends Plugin { |
||||
htmlElement: HTMLDivElement |
||||
events: EventEmitter |
||||
dispatch: React.Dispatch<any> |
||||
filePanel: FilePanelType |
||||
verticalIcons: VerticalIcons |
||||
setDispatch(dispatch: React.Dispatch<any>): void |
||||
getGitBranchName: () => Promise<any> |
||||
currentWorkspaceName: string |
||||
} |
@ -1,4 +1,4 @@ |
||||
export * from './lib/providers/FileSystemProvider' |
||||
export * from './lib/contexts' |
||||
export * from './lib/utils/constants' |
||||
export { FileType } from './lib/types/index' |
||||
export { FileType, FilePanelType } from './lib/types/index' |
||||
|
Loading…
Reference in new issue