Merge pull request #4817 from ethereum/gridView

fixes in Grid view and updates for remix guide
pull/5370/head
Aniket 5 months ago committed by GitHub
commit b4b96c0a58
  1. 5
      apps/remix-ide/src/app.js
  2. 266
      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. 116
      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. 137
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx

@ -346,7 +346,7 @@ class AppComponent {
solidityumlgen, solidityumlgen,
compilationDetails, compilationDetails,
vyperCompilationDetails, vyperCompilationDetails,
// remixGuide, remixGuide,
contractFlattener, contractFlattener,
solidityScript, solidityScript,
templates, templates,
@ -513,9 +513,6 @@ class AppComponent {
) )
await this.appManager.activatePlugin(['solidity-script']) await this.appManager.activatePlugin(['solidity-script'])
await this.appManager.activatePlugin(['solcoder']) await this.appManager.activatePlugin(['solcoder'])
await this.appManager.activatePlugin(['filePanel']) await this.appManager.activatePlugin(['filePanel'])
// Set workspace after initial activation // 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 { ViewPlugin } from '@remixproject/engine-web'
import { PluginViewWrapper } from '@remix-ui/helper' import { PluginViewWrapper } from '@remix-ui/helper'
import { RemixAppManager } from '../../remixAppManager' import { RemixAppManager } from '../../remixAppManager'
import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view' import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view'
import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section' import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section'
import { RemixUIGridCell } from '@remix-ui/remix-ui-grid-cell' 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 //@ts-ignore
const _paq = (window._paq = window._paq || []) const _paq = (window._paq = window._paq || [])
@ -15,7 +15,6 @@ const profile = {
displayName: 'Remix Guide', displayName: 'Remix Guide',
description: 'Learn remix with videos', description: 'Learn remix with videos',
location: 'mainPanel', location: 'mainPanel',
methods: ['showDetails'],
events: [] events: []
} }
@ -24,13 +23,20 @@ export class RemixGuidePlugin extends ViewPlugin {
appManager: RemixAppManager appManager: RemixAppManager
element: HTMLDivElement element: HTMLDivElement
payload: any payload: any
themeStyle: any showVideo: boolean
theme: ThemeKeys | ThemeObject videoID: string
handleKeyDown: any
handleEscape: any
constructor(appManager: RemixAppManager) { constructor(appManager: RemixAppManager) {
super(profile) super(profile)
this.appManager = appManager this.appManager = appManager
this.element = document.createElement('div') this.element = document.createElement('div')
this.element.setAttribute('id', 'remixGuideEl') this.element.setAttribute('id', 'remixGuideEl')
this.payload = {
sectionToExpandedCell: [['', '']],
data: {}
}
} }
async onActivation() { async onActivation() {
@ -38,24 +44,23 @@ export class RemixGuidePlugin extends ViewPlugin {
await this.call('tabs', 'focus', 'remixGuide') await this.call('tabs', 'focus', 'remixGuide')
this.renderComponent() this.renderComponent()
_paq.push(['trackEvent', 'plugin', 'activated', 'remixGuide']) _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()
}
}
document.addEventListener('keydown', this.handleKeyDown);
} }
onDeactivation(): void { onDeactivation(): void {
} document.removeEventListener('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()
} }
private handleThemeChange() { private handleThemeChange() {
this.on('theme', 'themeChanged', (theme: any) => { this.on('theme', 'themeChanged', (theme: any) => {
this.renderComponent() this.renderComponent()
}) })
} }
@ -64,6 +69,7 @@ export class RemixGuidePlugin extends ViewPlugin {
this.dispatch = dispatch this.dispatch = dispatch
this.renderComponent() this.renderComponent()
} }
render() { render() {
return ( return (
<div className="bg-dark" id="remixGuide"> <div className="bg-dark" id="remixGuide">
@ -76,168 +82,88 @@ export class RemixGuidePlugin extends ViewPlugin {
this.dispatch({ this.dispatch({
...this, ...this,
...this.payload, ...this.payload,
themeStyle: this.themeStyle, showVideo: this.showVideo,
theme: this.theme videoID: this.videoID
}) })
} }
updateComponent(state: any) { updateComponent(state: any) {
return ( return (
<RemixUIGridView <div className='d-flex'>
plugin={this} <RemixUIGridView
styleList={""}
logo='/assets/img/YouTubeLogo.webp'
enableFilter={true}
showUntagged={true}
showPin={true}
tagList={[
['beginner', 'danger'],
['advanced', 'warning'],
['AI', 'success'],
['plugins', 'secondary'],
['solidity', 'primary'],
['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}
>
<RemixUIGridSection
plugin={this} plugin={this}
title='Basics' styleList={""}
hScrollable= {true} logo='/assets/img/YouTubeLogo.webp'
enableFilter={true}
showUntagged={true}
showPin={false}
tagList={[
['beginner', 'danger'],
['advanced', 'warning'],
['AI', 'success'],
['plugins', 'secondary'],
['solidity', 'primary'],
['vyper', 'info'],
['L2', 'danger']
]}
title={Data.title}
description={Data.description}
> >
<RemixUIGridCell { Data.sections.map(section => {
plugin={this} return <RemixUIGridSection
title="first item" plugin={this}
tagList={['L2', 'AI']} title={section.title}
logo='/assets/img/soliditySurvey2023.webp' hScrollable= {true}
> >
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> { section.cells.map(cell => {
</RemixUIGridCell> return <RemixUIGridCell
<RemixUIGridCell plugin={this}
plugin={this} title={cell.title}
title="next" tagList={cell.tagList}
pinned={true} expandViewEl={
tagList={['L2', 'plugins']} cell.expandViewElement
> }
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> handleExpand={() => {
</RemixUIGridCell> <RemixUIGridCell this.showVideo = true
plugin={this} this.videoID = cell.expandViewElement.videoID
title="something" this.renderComponent()
pinned={false} }}
tagList={['solidity', 'plugins']} 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">
</RemixUIGridCell> <img src={"//img.youtube.com/vi/" + this.videoID + "/0.jpg"} style={{ height: '70px', width: '70px' }}></img>
<RemixUIGridCell </a>
plugin={this} </RemixUIGridCell>
title="1" })}
tagList={['solidity']} </RemixUIGridSection>
> })}
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> </RemixUIGridView>
</RemixUIGridCell> <RemixUIGridCell { state.showVideo && <div
plugin={this} data-id={`EnterModalDialogContainer-react`}
title="1" data-backdrop="static"
> data-keyboard="false"
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> className={"modal d-flex"}
</RemixUIGridCell> role="dialog"
<RemixUIGridCell style={{ justifyContent: "center" }}
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 <div className="align-self-center pb-4" role="document">
plugin={this} <div
title="first item" tabIndex={-1}
logo='/assets/img/soliditySurvey2023.webp' className={'modal-content remixModalContent mb-4'}
> >
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> <div className="text-break remixModalBody d-flex flex-column p-3 justify-content-between" data-id={`EnterModalDialogModalBody-react`}>
</RemixUIGridCell> <iframe style={{ minHeight: "500px", minWidth: "1000px" }} width="1000" height="500" src={"https://www.youtube.com/embed/" + this.videoID + "?si=ZdckOaSPR7VsLj_2"} allowFullScreen></iframe>
<RemixUIGridCell </div>
plugin={this} <div className="modal-footer d-flex flex-column">
title="next" <button onClick={() => {
> this.showVideo = false
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> this.renderComponent()
</RemixUIGridCell> <RemixUIGridCell }}>Close</button>
plugin={this} </div>
title="something" </div>
> </div>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> </div>}
</RemixUIGridCell> </div>
<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"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
</RemixUIGridSection>
</RemixUIGridView>
) )
} }

@ -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()) { if (Registry.getInstance().get('platform').api.isDesktop()) {
requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep'] requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep']
} }
} }
async canActivatePlugin(from, to) { async canActivatePlugin(from, to) {

@ -16,15 +16,16 @@ export const CustomCheckbox = (props: CustomCheckboxProps) => {
if (!textColor || textColor == '') textColor = filterCon.keyValueMap[props.label].color if (!textColor || textColor == '') textColor = filterCon.keyValueMap[props.label].color
return ( 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 <input
className="custom-control-input" className="custom-control-input"
id={"GVCheckbox" + props.label} id={"GVCheckbox" + props.label}
defaultChecked={defChecked} defaultChecked={defChecked}
onChange={e => { onChange={e => {
if (props.label == 'no tag') filterCon.updateValue(props.label, e.target.checked, textColor)}}
filterCon.showUntagged = ! filterCon.showUntagged
else filterCon.updateValue(props.label, e.target.checked, textColor)}}
type="checkbox" type="checkbox"
/> />
<label <label

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

@ -38,6 +38,12 @@
top: 0.1rem; top: 0.1rem;
} }
.remixui_grid_cell_tags_no_pin {
position: relative;
right: 0rem;
top: 0.1rem;
}
.remixui_grid_cell_tag { .remixui_grid_cell_tag {
font-size: x-small; font-size: x-small;
font-weight: bolder; 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 './remix-ui-grid-cell.css'
import FiltersContext from "./filtersContext" import FiltersContext from "./filtersContext"
@ -16,67 +16,97 @@ interface RemixUIGridCellProps {
pinned?: boolean pinned?: boolean
pinStateCallback?: any pinStateCallback?: any
logo?: string logo?: string
title?: string title: string
tagList?: string[] // max 8, others will be ignored tagList?: string[] // max 8, others will be ignored
classList?: string classList?: string
styleList?: any styleList?: any
children?: ReactNode children?: ReactNode
expandViewEl?: any
handleExpand?: any
} }
export const RemixUIGridCell = (props: RemixUIGridCellProps) => { export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
const filterCon = useContext(FiltersContext) const filterCon = useContext(FiltersContext)
const [anyEnabled, setAnyEnabled] = useState(false) const [anyEnabled, setAnyEnabled] = useState(false)
const [expand, setExpand] = useState(false)
const [pinned, setPinned] = useState<boolean>(props.pinned) const [pinned, setPinned] = useState<boolean>(props.pinned)
useEffect(() => { useEffect(() => {
if (props.tagList) setAnyEnabled(props.tagList.some((key) => filterCon.keyValueMap[key]?.enabled)) 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]) }, [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 ( return (
<div className='mr-2 mt-3'> <div className='mr-2 mt-3' onClick={() => {
{ anyEnabled && <div className='d-flex flex-grid'> if (props.expandViewEl)
<div className={"d-flex mx-0 p-2 bg-light border border-secondary remixui_grid_cell_container " + props.classList || ''} data-id={"remixUIGS" + props.title}> props.handleExpand(!expand)
<div className="d-flex remixui_grid_cell flex-column"> else return
<div className='d-flex flex-row pb-1 align-items-end' style={{ width: '8rem', height: '1rem' }}> }}>
{ props.logo && <img className='remixui_grid_view_logo mr-1' src={props.logo} style={{ width: '1rem', height: '1rem' }}/> } { anyEnabled && <div className='d-flex flex-column'>
{ props.title && <label <div className='d-flex flex-grid'>
className='m-0 p-0 align-items-left' <div className={"d-flex mx-0 p-2 bg-light border border-secondary remixui_grid_cell_container " + props.classList || ''} data-id={"remixUIGS" + props.title}>
style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 'xx-small' }} <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' }}>
{ props.title } { props.logo && <img className='remixui_grid_view_logo mr-1' src={props.logo} style={{ width: '1rem', height: '1rem' }}/> }
</label> } { props.title && <label
className='m-0 p-0 align-items-left'
style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 'xx-small' }}
>
{ props.title }
</label> }
</div>
{ props.children }
</div> </div>
{ props.children }
</div> </div>
</div> { filterCon.showPin && <button
{ filterCon.showPin && <button className={`${pinned ? 'fa-duotone' : 'fa-light'}` + ` fa-map-pin text-info border-0 mb-0 remixui_grid_cell_pin`}
className={`${pinned ? 'fa-duotone' : 'fa-light'}` + ` fa-map-pin text-info border-0 mb-0 remixui_grid_cell_pin`} onClick={() => {
onClick={() => { setPinned(!pinned)
setPinned(!pinned) props.pinStateCallback()
props.pinStateCallback() }}
}} ></button>}
></button>} { props.tagList && <div className={`d-flex flex-column align-items-begin ` +`${filterCon.showPin ? 'remixui_grid_cell_tags' : 'remixui_grid_cell_tags_no_pin'}`}>
{ props.tagList && <div className='d-flex flex-column align-items-begin remixui_grid_cell_tags'> { Object.keys(props.tagList).map((key) => (
{ Object.keys(props.tagList).map((key) => ( filterCon.keyValueMap[props.tagList[key]].enabled && (
filterCon.keyValueMap[props.tagList[key]].enabled && ( <CustomTooltip
<CustomTooltip placement="right"
placement="right" tooltipId="pluginManagerInactiveTitleLinkToDoc"
tooltipId="pluginManagerInactiveTitleLinkToDoc" tooltipClasses="text-nowrap"
tooltipClasses="text-nowrap" tooltipText={props.tagList[key]}
tooltipText={props.tagList[key]}
>
<span key={props.tagList[key]}
className={'remixui_grid_cell_tag bg-' + filterCon.keyValueMap[props.tagList[key]].color}
> >
</span> <span key={props.tagList[key]}
</CustomTooltip> className={'remixui_grid_cell_tag bg-' + filterCon.keyValueMap[props.tagList[key]].color}
) >
)) } </span>
</div> } </CustomTooltip>
{ !props.tagList && <span )
className={'remixui_grid_cell_tags'}> )) }
</span> } </div> }
{ !props.tagList && <span
className={'remixui_grid_cell_tags'}>
</span> }
</div>
{ expand && <div>
{ props.expandViewEl }
</div>
}
</div> } </div> }
</div> </div>
) )

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

@ -1,7 +1,6 @@
import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line
import './remix-ui-grid-view.css' import './remix-ui-grid-view.css'
import { ThemeContext, themes } from './themeContext'
import CustomCheckbox from './components/customCheckbox' import CustomCheckbox from './components/customCheckbox'
import FiltersContext from "./filtersContext" import FiltersContext from "./filtersContext"
@ -28,7 +27,7 @@ interface RemixUIGridViewProps {
export const RemixUIGridView = (props: RemixUIGridViewProps) => { export const RemixUIGridView = (props: RemixUIGridViewProps) => {
const [keyValueMap, setKeyValueMap] = useState<Record<string, { enabled: boolean; color: string; }>>({}); const [keyValueMap, setKeyValueMap] = useState<Record<string, { enabled: boolean; color: string; }>>({});
const [filter, setFilter] = useState("")
const showUntagged = props.showUntagged || false const showUntagged = props.showUntagged || false
const showPin = props.showPin || false const showPin = props.showPin || false
const updateValue = (key: string, enabled: boolean, color?: string) => { 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) => { const addValue = (key: string, enabled: boolean, color: string) => {
// Check if the key already exists, if so, do not add // Check if the key already exists, if so, do not add
if (key in keyValueMap) { 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 // Initialize filters context with data from props
useEffect(() => { useEffect(() => {
document.addEventListener('keyup', (e) => handleSearchKeyDown(e))
if (props.tagList && Array.isArray(props.tagList)) { if (props.tagList && Array.isArray(props.tagList)) {
const initialKeyValueMap: Record<string, { enabled: boolean; color: string; }> = {}; const initialKeyValueMap: Record<string, { enabled: boolean; color: string; }> = {};
@ -74,71 +90,52 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
if (showUntagged) initialKeyValueMap['no tag'] = { enabled: true, color: 'primary' } if (showUntagged) initialKeyValueMap['no tag'] = { enabled: true, color: 'primary' }
setKeyValueMap(initialKeyValueMap) setKeyValueMap(initialKeyValueMap)
} }
return () => {
document.removeEventListener('keyup', handleSearchKeyDown)
}
}, []) }, [])
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
}
})
})
}, [plugin])
return ( 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"> <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 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 p-4 bg-light flex-column remixui_grid_view_titlebar'> <div className='d-flex flex-row align-items-center mb-2'>
<div className='d-flex flex-row align-items-center mb-2'> { props.logo && <img className='remixui_grid_view_logo mr-2' src={props.logo} /> }
{ props.logo && <img className='remixui_grid_view_logo mr-2' src={props.logo} /> } { props.title && <h3 className='mb-0'>{ props.title }</h3> }
{ props.title && <h3 className='mb-0'>{ props.title }</h3> }
</div>
{ props.description && <div className='pb-3 remixui_grid_view_title'>{ props.description }</div> }
{ props.enableFilter && <div className='d-flex flex-row'>
<div className="d-flex flex-row pr-2 pb-1 align-items-center justify-content-between">
<div className='d-flex' id="GVFilter">
<button
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) => {
_paq.push(['trackEvent', 'GridView' + props.title ? props.title : '', 'filter', searchInputRef.current.value])
//setstate
}}
></button>
<input
ref={searchInputRef}
type="text"
style={{ minWidth: '100px' }}
className="border form-control border-right-0 mr-4"
id="GVFilterInput"
placeholder={"Filter the list"}
data-id="RemixGVFilterInput"
/>
</div>
<div className='d-flex flex-row'>
{ Object.keys(keyValueMap).map((key) => (
<CustomCheckbox label={key} />
)) }
</div>
</div>
</div> }
</div> </div>
{ props.children } { props.description && <div className='pb-3 remixui_grid_view_title'>{ props.description }</div> }
{ props.enableFilter && <div className='d-flex flex-row'>
<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])
}}
></button>
<input
ref={searchInputRef}
type="text"
style={{ minWidth: '100px' }}
className="border form-control border-right-0 mr-4"
id="GVFilterInput"
placeholder={"Filter the list"}
data-id="RemixGVFilterInput"
/>
</div>
<div className='d-flex flex-row'>
{ Object.keys(keyValueMap).map((key) => (
<CustomCheckbox label={key} />
)) }
</div>
</div>
</div> }
</div> </div>
</ThemeContext.Provider> { props.children }
</div>
</div> </div>
</FiltersContext.Provider> </FiltersContext.Provider>
) )

Loading…
Cancel
Save