Merge pull request #4817 from ethereum/gridView

fixes in Grid view and updates for remix guide
pull/4894/head
Aniket 5 months ago committed by GitHub
commit c307125d4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      apps/remix-ide/src/app.js
  2. 224
      apps/remix-ide/src/app/plugins/remixGuide.tsx
  3. 47
      apps/remix-ide/src/app/plugins/remixGuideData.json
  4. 1
      apps/remix-ide/src/remixAppManager.js
  5. 9
      libs/remix-ui/grid-view/src/lib/components/customCheckbox.tsx
  6. 4
      libs/remix-ui/grid-view/src/lib/filtersContext.tsx
  7. 6
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css
  8. 42
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx
  9. 5
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx
  10. 69
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx

@ -346,7 +346,7 @@ class AppComponent {
solidityumlgen,
compilationDetails,
vyperCompilationDetails,
// remixGuide,
remixGuide,
contractFlattener,
solidityScript,
templates,
@ -513,9 +513,6 @@ class AppComponent {
)
await this.appManager.activatePlugin(['solidity-script'])
await this.appManager.activatePlugin(['solcoder'])
await this.appManager.activatePlugin(['filePanel'])
// Set workspace after initial activation

@ -1,12 +1,12 @@
import React from 'react'
import React, {useState} from 'react' // eslint-disable-line
import { ViewPlugin } from '@remixproject/engine-web'
import { PluginViewWrapper } from '@remix-ui/helper'
import { RemixAppManager } from '../../remixAppManager'
import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view'
import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section'
import { RemixUIGridCell } from '@remix-ui/remix-ui-grid-cell'
import { ThemeKeys, ThemeObject } from '@microlink/react-json-view'
import * as Data from './remixGuideData.json'
//@ts-ignore
const _paq = (window._paq = window._paq || [])
@ -15,7 +15,6 @@ const profile = {
displayName: 'Remix Guide',
description: 'Learn remix with videos',
location: 'mainPanel',
methods: ['showDetails'],
events: []
}
@ -24,13 +23,20 @@ export class RemixGuidePlugin extends ViewPlugin {
appManager: RemixAppManager
element: HTMLDivElement
payload: any
themeStyle: any
theme: ThemeKeys | ThemeObject
showVideo: boolean
videoID: string
handleKeyDown: any
handleEscape: any
constructor(appManager: RemixAppManager) {
super(profile)
this.appManager = appManager
this.element = document.createElement('div')
this.element.setAttribute('id', 'remixGuideEl')
this.payload = {
sectionToExpandedCell: [['', '']],
data: {}
}
}
async onActivation() {
@ -38,24 +44,23 @@ export class RemixGuidePlugin extends ViewPlugin {
await this.call('tabs', 'focus', 'remixGuide')
this.renderComponent()
_paq.push(['trackEvent', 'plugin', 'activated', 'remixGuide'])
// Read the data
this.payload.data = Data
this.handleKeyDown = (event) => {
if (event.key === 'Escape') {
this.showVideo = false
this.renderComponent()
}
onDeactivation(): void {
}
document.addEventListener('keydown', this.handleKeyDown);
}
async showDetails(sentPayload: any) {
const contractName = Object.entries(sentPayload).find(([key, value]) => key)
await this.call('tabs', 'focus', 'remixGuide')
this.profile.displayName = `${contractName[0]}`
this.payload = sentPayload
const active = await this.call('theme', 'currentTheme')
this.renderComponent()
onDeactivation(): void {
document.removeEventListener('keydown', this.handleKeyDown);
}
private handleThemeChange() {
this.on('theme', 'themeChanged', (theme: any) => {
this.renderComponent()
})
}
@ -64,6 +69,7 @@ export class RemixGuidePlugin extends ViewPlugin {
this.dispatch = dispatch
this.renderComponent()
}
render() {
return (
<div className="bg-dark" id="remixGuide">
@ -76,20 +82,21 @@ export class RemixGuidePlugin extends ViewPlugin {
this.dispatch({
...this,
...this.payload,
themeStyle: this.themeStyle,
theme: this.theme
showVideo: this.showVideo,
videoID: this.videoID
})
}
updateComponent(state: any) {
return (
<div className='d-flex'>
<RemixUIGridView
plugin={this}
styleList={""}
logo='/assets/img/YouTubeLogo.webp'
enableFilter={true}
showUntagged={true}
showPin={true}
showPin={false}
tagList={[
['beginner', 'danger'],
['advanced', 'warning'],
@ -99,145 +106,64 @@ export class RemixGuidePlugin extends ViewPlugin {
['vyper', 'info'],
['L2', 'danger']
]}
title='Remix Guide'
description="Streamlined access to categorized video tutorials for mastering Remix IDE. From fundamentals to advanced techniques, level up your development skills with ease."
//themeStyle={state.themeStyle}
title={Data.title}
description={Data.description}
>
<RemixUIGridSection
{ Data.sections.map(section => {
return <RemixUIGridSection
plugin={this}
title='Basics'
title={section.title}
hScrollable= {true}
>
<RemixUIGridCell
plugin={this}
title="first item"
tagList={['L2', 'AI']}
logo='/assets/img/soliditySurvey2023.webp'
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="next"
pinned={true}
tagList={['L2', 'plugins']}
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="something"
pinned={false}
tagList={['solidity', 'plugins']}
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
tagList={['solidity']}
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="Something very very long"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
</RemixUIGridSection>
<RemixUIGridSection
plugin={this}
title='Basics not scrollable'
hScrollable= {false}
>
<RemixUIGridCell
plugin={this}
title="first item"
logo='/assets/img/soliditySurvey2023.webp'
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="next"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
{ section.cells.map(cell => {
return <RemixUIGridCell
plugin={this}
title="something"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
title={cell.title}
tagList={cell.tagList}
expandViewEl={
cell.expandViewElement
}
handleExpand={() => {
this.showVideo = true
this.videoID = cell.expandViewElement.videoID
this.renderComponent()
}}
logo={cell.expandViewElement.logo}
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
<a href={"https://www.youtube.com/@" + cell.authorURL} target="__blank">
<img src={"//img.youtube.com/vi/" + this.videoID + "/0.jpg"} style={{ height: '70px', width: '70px' }}></img>
</a>
</RemixUIGridCell>
})}
</RemixUIGridSection>
})}
</RemixUIGridView>
{ state.showVideo && <div
data-id={`EnterModalDialogContainer-react`}
data-backdrop="static"
data-keyboard="false"
className={"modal d-flex"}
role="dialog"
style={{ justifyContent: "center" }}
>
<div className="align-self-center pb-4" role="document">
<div
tabIndex={-1}
className={'modal-content remixModalContent mb-4'}
>
<div className="text-break remixModalBody d-flex flex-column p-3 justify-content-between" data-id={`EnterModalDialogModalBody-react`}>
<iframe style={{ minHeight: "500px", minWidth: "1000px" }} width="1000" height="500" src={"https://www.youtube.com/embed/" + this.videoID + "?si=ZdckOaSPR7VsLj_2"} allowFullScreen></iframe>
</div>
<div className="modal-footer d-flex flex-column">
<button onClick={() => {
this.showVideo = false
this.renderComponent()
}}>Close</button>
</div>
</div>
</div>
</div>}
</div>
)
}

@ -0,0 +1,47 @@
{
"logo": "/assets/img/YouTubeLogo.webp",
"title": "Remix Guide",
"description": "Streamlined access to categorized video tutorials for mastering Remix IDE. From fundamentals to advanced techniques, level up your development skills with ease.",
"sections": [
{
"title": "Basics",
"hScrollable": "true",
"cells": [
{
"title": "first item",
"tagList": [
"L2",
"AI"
],
"authorURL": "EatTheBlocks",
"expandViewElement": {
"videoID": "vH8T3In6ZkE",
"logo": "https://yt3.ggpht.com/9NFZbC9mkA152sSWJJgNBls6GlBdknsF-9gi6ZVk_xsHjmc82j3q1Pd5a--GCnOKUrP-YtNbHls=s48-c-k-c0x00ffffff-no-rj"
}
},
{
"title": "second item",
"tagList": [
"solidity",
"AI"
],
"expandViewElement": {
"videoID": "vH8T3In6ZkE",
"logo": "https://yt3.ggpht.com/9NFZbC9mkA152sSWJJgNBls6GlBdknsF-9gi6ZVk_xsHjmc82j3q1Pd5a--GCnOKUrP-YtNbHls=s48-c-k-c0x00ffffff-no-rj"
}
},
{
"title": "third item",
"tagList": [
"vyper",
"AI"
],
"expandViewElement": {
"videoID": "vH8T3In6ZkE",
"logo": "https://yt3.ggpht.com/9NFZbC9mkA152sSWJJgNBls6GlBdknsF-9gi6ZVk_xsHjmc82j3q1Pd5a--GCnOKUrP-YtNbHls=s48-c-k-c0x00ffffff-no-rj"
}
}
]
}
]
}

@ -159,7 +159,6 @@ export class RemixAppManager extends PluginManager {
if (Registry.getInstance().get('platform').api.isDesktop()) {
requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep']
}
}
async canActivatePlugin(from, to) {

@ -16,15 +16,16 @@ export const CustomCheckbox = (props: CustomCheckboxProps) => {
if (!textColor || textColor == '') textColor = filterCon.keyValueMap[props.label].color
return (
<div id={textColor + props.label} className="h-80 mx-1 align-items-center custom-control custom-checkbox" style={{ minWidth: '4rem' }}>
<div id={textColor + props.label}
className="h-80 mx-1 align-items-center custom-control custom-checkbox"
style={{ minWidth: '4rem' }}
>
<input
className="custom-control-input"
id={"GVCheckbox" + props.label}
defaultChecked={defChecked}
onChange={e => {
if (props.label == 'no tag')
filterCon.showUntagged = ! filterCon.showUntagged
else filterCon.updateValue(props.label, e.target.checked, textColor)}}
filterCon.updateValue(props.label, e.target.checked, textColor)}}
type="checkbox"
/>
<label

@ -3,9 +3,10 @@ import React, { createContext, useState, useContext } from 'react';
interface FilterContextType {
showUntagged: boolean
showPin: boolean
keyValueMap: Record<string, { enabled: boolean; color: string; }>;
keyValueMap: Record<string, { enabled: boolean; color: string; }>
updateValue: (key: string, enabled: boolean, color: string) => void
addValue: (key: string, enabled: boolean, color: string) => void
filter: string
}
const FiltersContext = createContext<FilterContextType>({
showUntagged: false,
@ -13,6 +14,7 @@ const FiltersContext = createContext<FilterContextType>({
keyValueMap: {},
updateValue: () => {},
addValue: () => {},
filter: ""
});
export default FiltersContext

@ -38,6 +38,12 @@
top: 0.1rem;
}
.remixui_grid_cell_tags_no_pin {
position: relative;
right: 0rem;
top: 0.1rem;
}
.remixui_grid_cell_tag {
font-size: x-small;
font-weight: bolder;

@ -1,4 +1,4 @@
import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line
import React, {useState, useEffect, useContext, useRef, ReactNode, ReactHTMLElement} from 'react' // eslint-disable-line
import './remix-ui-grid-cell.css'
import FiltersContext from "./filtersContext"
@ -16,26 +16,51 @@ interface RemixUIGridCellProps {
pinned?: boolean
pinStateCallback?: any
logo?: string
title?: string
title: string
tagList?: string[] // max 8, others will be ignored
classList?: string
styleList?: any
children?: ReactNode
expandViewEl?: any
handleExpand?: any
}
export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
const filterCon = useContext(FiltersContext)
const [anyEnabled, setAnyEnabled] = useState(false)
const [expand, setExpand] = useState(false)
const [pinned, setPinned] = useState<boolean>(props.pinned)
useEffect(() => {
if (props.tagList) setAnyEnabled(props.tagList.some((key) => filterCon.keyValueMap[key]?.enabled))
else setAnyEnabled(filterCon.showUntagged)
else setAnyEnabled(filterCon?.keyValueMap['no tag']?.enabled)
if (filterCon.filter != '') setAnyEnabled(anyEnabled && props.title.toLowerCase().includes(filterCon.filter))
console.log("pin ", pinned)
}, [filterCon, props.tagList])
/*const listenOnExpand = (key) => {
if (key === props.key) setExpand(props.toggleExpandView)
console.log('expand ----> ', key)
}
// The expanded widged should go to the grid-segment and be updated based on the expandedItem state variable of the plugin.
// The state var will work like theme dispattching is working.
useEffect(() => {
// TODO should be refactored to update based on state of plugin.
props.plugin.on(props.plugin.name, 'expandGridCell', listenOnExpand)
}, [])
*/
return (
<div className='mr-2 mt-3'>
{ anyEnabled && <div className='d-flex flex-grid'>
<div className='mr-2 mt-3' onClick={() => {
if (props.expandViewEl)
props.handleExpand(!expand)
else return
}}>
{ anyEnabled && <div className='d-flex flex-column'>
<div className='d-flex flex-grid'>
<div className={"d-flex mx-0 p-2 bg-light border border-secondary remixui_grid_cell_container " + props.classList || ''} data-id={"remixUIGS" + props.title}>
<div className="d-flex remixui_grid_cell flex-column">
<div className='d-flex flex-row pb-1 align-items-end' style={{ width: '8rem', height: '1rem' }}>
@ -57,7 +82,7 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
props.pinStateCallback()
}}
></button>}
{ props.tagList && <div className='d-flex flex-column align-items-begin remixui_grid_cell_tags'>
{ props.tagList && <div className={`d-flex flex-column align-items-begin ` +`${filterCon.showPin ? 'remixui_grid_cell_tags' : 'remixui_grid_cell_tags_no_pin'}`}>
{ Object.keys(props.tagList).map((key) => (
filterCon.keyValueMap[props.tagList[key]].enabled && (
<CustomTooltip
@ -77,6 +102,11 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
{ !props.tagList && <span
className={'remixui_grid_cell_tags'}>
</span> }
</div>
{ expand && <div>
{ props.expandViewEl }
</div>
}
</div> }
</div>
)

@ -16,6 +16,7 @@ interface RemixUIGridSectionProps {
classList?: string
styleList?: any
children?: ReactNode
expandedCell?: any
}
export const RemixUIGridSection = (props: RemixUIGridSectionProps) => {
@ -30,6 +31,10 @@ export const RemixUIGridSection = (props: RemixUIGridSectionProps) => {
<div className={(props.hScrollable) ? `d-flex flex-row pb-2 overflow-auto` : `d-flex flex-wrap`}>
{ props.children }
</div>
{ props.expandedCell && <div>
{ props.expandedCell }
</div>
}
</div>
</div>
)

@ -1,7 +1,6 @@
import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line
import './remix-ui-grid-view.css'
import { ThemeContext, themes } from './themeContext'
import CustomCheckbox from './components/customCheckbox'
import FiltersContext from "./filtersContext"
@ -28,7 +27,7 @@ interface RemixUIGridViewProps {
export const RemixUIGridView = (props: RemixUIGridViewProps) => {
const [keyValueMap, setKeyValueMap] = useState<Record<string, { enabled: boolean; color: string; }>>({});
const [filter, setFilter] = useState("")
const showUntagged = props.showUntagged || false
const showPin = props.showPin || false
const updateValue = (key: string, enabled: boolean, color?: string) => {
@ -39,6 +38,30 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
}))
}
const [state, setState] = useState<{
searchDisable: boolean
}>({
searchDisable: true
})
const searchInputRef = useRef(null)
const handleSearchKeyDown = (e: KeyboardEvent) => {
if (e.target !== searchInputRef.current) return
if (e.key === 'Enter') {
searchInputRef.current.value = ''
} else {
setState((prevState) => {
console.log("update filter", searchInputRef.current.value)
return {
...prevState,
searchDisable: searchInputRef.current.value === '',
filter: searchInputRef.current.value
}
})
setFilter(searchInputRef.current.value)
}
}
const addValue = (key: string, enabled: boolean, color: string) => {
// Check if the key already exists, if so, do not add
if (key in keyValueMap) {
@ -52,17 +75,10 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
}))
}
const { plugin } = props.plugin
const searchInputRef = useRef(null)
const [state, setState] = useState<{
themeQuality: {filter: string; name: string}
}>({
themeQuality: themes.light
})
// Initialize filters context with data from props
useEffect(() => {
document.addEventListener('keyup', (e) => handleSearchKeyDown(e))
if (props.tagList && Array.isArray(props.tagList)) {
const initialKeyValueMap: Record<string, { enabled: boolean; color: string; }> = {};
@ -74,33 +90,14 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
if (showUntagged) initialKeyValueMap['no tag'] = { enabled: true, color: 'primary' }
setKeyValueMap(initialKeyValueMap)
}
}, [])
useEffect(() => {
plugin?.call('theme', 'currentTheme').then((theme) => {
// update theme quality. To be used for for images
setState((prevState) => {
return {
...prevState,
themeQuality: theme.quality === 'dark' ? themes.dark : themes.light
}
})
})
plugin?.on('theme', 'themeChanged', (theme) => {
// update theme quality. To be used for for images
setState((prevState) => {
return {
...prevState,
themeQuality: theme.quality === 'dark' ? themes.dark : themes.light
return () => {
document.removeEventListener('keyup', handleSearchKeyDown)
}
})
})
}, [plugin])
}, [])
return (
<FiltersContext.Provider value={{ showUntagged, showPin, keyValueMap, updateValue, addValue }}>
<FiltersContext.Provider value={{ showUntagged, showPin, keyValueMap, updateValue, addValue, filter }}>
<div className={"d-flex flex-column bg-dark w-100 h-100 remixui_grid_view_container " + props.classList || ''} data-id="remixUIGV">
<ThemeContext.Provider value={state.themeQuality}>
<div className="d-flex flex-column w-100 remixui_grid_view">
<div className='d-flex p-4 bg-light flex-column remixui_grid_view_titlebar'>
<div className='d-flex flex-row align-items-center mb-2'>
@ -112,10 +109,11 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
<div className="d-flex flex-row pr-2 pb-1 align-items-center justify-content-between">
<div className='d-flex' id="GVFilter">
<button
disabled={state.searchDisable}
className="remixui_grid_view_btn text-secondary form-control bg-light border d-flex align-items-center p-2 justify-content-center fas fa-filter bg-light"
onClick={(e) => {
setFilter(searchInputRef.current.value)
_paq.push(['trackEvent', 'GridView' + props.title ? props.title : '', 'filter', searchInputRef.current.value])
//setstate
}}
></button>
<input
@ -138,7 +136,6 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
</div>
{ props.children }
</div>
</ThemeContext.Provider>
</div>
</FiltersContext.Provider>
)

Loading…
Cancel
Save