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