parent
27ae1dc6e8
commit
577fa4c3f8
@ -0,0 +1,4 @@ |
||||
export * from './lib/remix-ui-grid-view' |
||||
export * from './lib/remix-ui-grid-section' |
||||
export * from './lib/remix-ui-grid-cell' |
||||
|
@ -0,0 +1,42 @@ |
||||
/* eslint-disable @typescript-eslint/no-unused-vars */ |
||||
import React from 'react' |
||||
import { useContext } from 'react' |
||||
import FiltersContext from ".././filtersContext" |
||||
|
||||
interface CustomCheckboxProps { |
||||
label: string |
||||
color?: string |
||||
} |
||||
|
||||
export const CustomCheckbox = (props: CustomCheckboxProps) => { |
||||
const filterCon = useContext(FiltersContext) |
||||
let textColor = props.color |
||||
let defChecked = true |
||||
if (filterCon.keyValueMap[props.label]) defChecked = filterCon.keyValueMap[props.label].enabled |
||||
if (!textColor || textColor == '') textColor = filterCon.keyValueMap[props.label].color |
||||
|
||||
return ( |
||||
<div 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)}} |
||||
type="checkbox" |
||||
/> |
||||
<label |
||||
className={"form-check-label custom-control-label text-nowrap text-" + textColor} |
||||
style={{ paddingTop: '0.125rem' }} |
||||
htmlFor={"GVCheckbox" + props.label} |
||||
data-id={"GVCheckboxLabel" + props.label} |
||||
> |
||||
{ props.label } |
||||
</label> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
export default CustomCheckbox |
@ -0,0 +1,16 @@ |
||||
import React, { createContext, useState, useContext } from 'react'; |
||||
|
||||
interface FilterContextType { |
||||
showUntagged: boolean |
||||
keyValueMap: Record<string, { enabled: boolean; color: string; }>; |
||||
updateValue: (key: string, enabled: boolean, color: string) => void |
||||
addValue: (key: string, enabled: boolean, color: string) => void |
||||
} |
||||
const FiltersContext = createContext<FilterContextType>({ |
||||
showUntagged: false, |
||||
keyValueMap: {}, |
||||
updateValue: () => {}, |
||||
addValue: () => {}, |
||||
}); |
||||
|
||||
export default FiltersContext |
@ -0,0 +1,21 @@ |
||||
.remixui_grid_cell { |
||||
font-weight: normal; |
||||
} |
||||
|
||||
.remixui_grid_cell_container { |
||||
width: fit-content; |
||||
} |
||||
|
||||
.remixui_grid_cell_title{ |
||||
font-size: 0.8rem; |
||||
font-style: italic; |
||||
} |
||||
|
||||
.remixui_grid_cell_btn { |
||||
width: 32px; |
||||
} |
||||
|
||||
.remixui_grid_cell_logo { |
||||
width: 3rem; |
||||
height: 3rem; |
||||
} |
@ -0,0 +1,71 @@ |
||||
import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line
|
||||
|
||||
import './remix-ui-grid-cell.css' |
||||
import FiltersContext from "./filtersContext" |
||||
import { CustomTooltip } from '@remix-ui/helper' |
||||
|
||||
|
||||
declare global { |
||||
interface Window { |
||||
_paq: any |
||||
} |
||||
} |
||||
const _paq = window._paq = window._paq || [] |
||||
|
||||
interface RemixUIGridCellProps { |
||||
plugin: any |
||||
logo?: string |
||||
title?: string
|
||||
tagList?: string[] // max 8, others will be ignored
|
||||
classList?: string |
||||
styleList?: any |
||||
children?: ReactNode |
||||
} |
||||
|
||||
export const RemixUIGridCell = (props: RemixUIGridCellProps) => { |
||||
const filterCon = useContext(FiltersContext) |
||||
const [anyEnabled, setAnyEnabled] = useState(false) |
||||
|
||||
useEffect(() => { |
||||
if (props.tagList) setAnyEnabled(props.tagList.some((key) => filterCon.keyValueMap[key]?.enabled)) |
||||
else setAnyEnabled(filterCon.showUntagged)
|
||||
}, [filterCon, props.tagList]) |
||||
|
||||
return ( |
||||
<div className='mr-2 mt-2'> |
||||
{ anyEnabled && <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: '4.4rem', height: '1rem'}}> |
||||
{ props.logo && <img className='remixui_grid_view_logo mr-1' src={props.logo} style={{width: '1rem', height: '1rem'}}/> } |
||||
{ 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.tagList && <div className='d-flex flex-column align-items-begin' style={{position: 'relative', right: '0.4rem', top: '0.1rem'}}> |
||||
{ Object.keys(props.tagList).map((key) => ( |
||||
filterCon.keyValueMap[props.tagList[key]].enabled && ( |
||||
<CustomTooltip |
||||
placement="right" |
||||
tooltipId="pluginManagerInactiveTitleLinkToDoc" |
||||
tooltipClasses="text-nowrap" |
||||
tooltipText={props.tagList[key]} |
||||
> |
||||
<span key={props.tagList[key]} |
||||
className={'bg-' + filterCon.keyValueMap[props.tagList[key]].color} |
||||
style={{ fontSize: 'x-small', fontWeight: 'bolder' , width: '0.4rem', height: '1.2rem'}}> |
||||
</span> |
||||
</CustomTooltip> |
||||
) |
||||
)) } |
||||
</div> } |
||||
{ !props.tagList && <span |
||||
style={{ fontSize: 'x-small', fontWeight: 'bolder' , width: '0.4rem', height: '1.2rem'}}> |
||||
</span> } |
||||
</div> } |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
export default RemixUIGridCell |
@ -0,0 +1,26 @@ |
||||
.remixui_grid_section { |
||||
font-weight: normal; |
||||
} |
||||
|
||||
.remixui_grid_section_container { |
||||
width: fit-content; |
||||
} |
||||
|
||||
.remixui_grid_section_title{ |
||||
font-size: 0.8rem; |
||||
font-style: italic; |
||||
} |
||||
|
||||
.remixui_grid_section_btn { |
||||
width: 32px; |
||||
} |
||||
|
||||
.remixui_grid_section_logo { |
||||
width: 3rem; |
||||
height: 3rem; |
||||
} |
||||
|
||||
* { |
||||
scrollbar-width: thin; |
||||
scrollbar-color: var(--bg-dark) var(--bg-light); |
||||
} |
@ -0,0 +1,70 @@ |
||||
import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line
|
||||
|
||||
import './remix-ui-grid-section.css' |
||||
import {ThemeContext, themes} from './themeContext' |
||||
|
||||
declare global { |
||||
interface Window { |
||||
_paq: any |
||||
} |
||||
} |
||||
const _paq = window._paq = window._paq || [] |
||||
|
||||
interface RemixUIGridSectionProps { |
||||
plugin: any |
||||
title?: string |
||||
hScrollable: boolean |
||||
classList?: string |
||||
styleList?: any |
||||
children?: ReactNode |
||||
} |
||||
|
||||
export const RemixUIGridSection = (props: RemixUIGridSectionProps) => { |
||||
const {plugin} = props.plugin |
||||
const searchInputRef = useRef(null) |
||||
|
||||
console.log('props.hScrollable ', props.hScrollable) |
||||
const [state, setState] = useState<{ |
||||
themeQuality: {filter: string; name: string} |
||||
}>({ |
||||
themeQuality: themes.light |
||||
}) |
||||
|
||||
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 ( |
||||
<div |
||||
className={`d-flex px-4 py-2 flex-column w-100 remixui_grid_section_container ${props.classList}`} |
||||
data-id={"remixUIGS" + props.title} |
||||
style={{ overflowX: 'auto' }} |
||||
> |
||||
<div className="d-flex flex-column w-100 remixui_grid_section"> |
||||
{ props.title && <h6 className='mt-1 mb-0 align-items-left '>{ props.title }</h6> } |
||||
<div className={(props.hScrollable) ? `d-flex flex-row pb-2 overflow-auto` : `d-flex flex-wrap`}> |
||||
{ props.children } |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
export default RemixUIGridSection |
@ -0,0 +1,25 @@ |
||||
.remixui_grid_view { |
||||
font-weight: normal; |
||||
} |
||||
|
||||
.remixui_grid_view_container { |
||||
overflow: auto; |
||||
} |
||||
|
||||
.remixui_grid_view_title{ |
||||
font-size: 0.8rem; |
||||
font-style: italic; |
||||
} |
||||
|
||||
.remixui_grid_view_btn { |
||||
width: 32px; |
||||
} |
||||
|
||||
.remixui_grid_view_logo { |
||||
width: 3rem; |
||||
height: 3rem; |
||||
} |
||||
|
||||
.remixui_grid_view_titlebar { |
||||
min-width: max-content; |
||||
} |
@ -0,0 +1,145 @@ |
||||
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" |
||||
|
||||
declare global { |
||||
interface Window { |
||||
_paq: any |
||||
} |
||||
} |
||||
const _paq = window._paq = window._paq || [] |
||||
|
||||
interface RemixUIGridViewProps { |
||||
plugin: any |
||||
logo?: string |
||||
title?: string |
||||
enableFilter?: boolean |
||||
tagList?: [string, string][] // max 8, others will be ignored
|
||||
showUntagged?: boolean |
||||
classList?: string |
||||
styleList?: any |
||||
description?: string |
||||
children?: ReactNode |
||||
} |
||||
|
||||
export const RemixUIGridView = (props: RemixUIGridViewProps) => { |
||||
const [keyValueMap, setKeyValueMap] = useState<Record<string, { enabled: boolean; color: string; }>>({}); |
||||
|
||||
const showUntagged = props.showUntagged || false |
||||
const updateValue = (key: string, enabled: boolean, color?: string) => { |
||||
if (!color || color === '') color = setKeyValueMap[key].color |
||||
setKeyValueMap((prevMap) => ({ |
||||
...prevMap, |
||||
[key]: {color, enabled}, |
||||
})) |
||||
} |
||||
|
||||
const addValue = (key: string, enabled: boolean, color: string) => { |
||||
// Check if the key already exists, if so, do not add
|
||||
if (key in keyValueMap) { |
||||
return |
||||
} |
||||
|
||||
// Add the new key-value pair
|
||||
setKeyValueMap((prevMap) => ({ |
||||
...prevMap, |
||||
[key]: { enabled, color }, |
||||
})) |
||||
} |
||||
|
||||
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(() => { |
||||
if (props.tagList && Array.isArray(props.tagList)) { |
||||
const initialKeyValueMap: Record<string, { enabled: boolean; color: string; }> = {}; |
||||
|
||||
// Limit to first 8 elements, ignoring the rest
|
||||
for (let i = 0; i < props.tagList.length; i++) { |
||||
const [key, color] = props.tagList[i] |
||||
initialKeyValueMap[key] = { enabled: true, color }
|
||||
} |
||||
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 |
||||
} |
||||
}) |
||||
}) |
||||
}, [plugin]) |
||||
|
||||
return ( |
||||
<FiltersContext.Provider value={{ showUntagged, keyValueMap, updateValue, addValue }}> |
||||
<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'> |
||||
{ props.logo && <img className='remixui_grid_view_logo mr-2' src={props.logo} /> } |
||||
{ 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> |
||||
{ props.children } |
||||
</div> |
||||
</ThemeContext.Provider> |
||||
</div> |
||||
</FiltersContext.Provider> |
||||
) |
||||
} |
||||
|
||||
export default RemixUIGridView |
@ -0,0 +1,16 @@ |
||||
import React from 'react' // eslint-disable-line
|
||||
|
||||
export const themes = { |
||||
light: { |
||||
filter: 'invert(0)', |
||||
name: 'light' |
||||
}, |
||||
dark: { |
||||
filter: 'invert(1)', |
||||
name: 'dark' |
||||
} |
||||
} |
||||
|
||||
export const ThemeContext = React.createContext( |
||||
themes.dark // default value
|
||||
) |
Loading…
Reference in new issue