diff --git a/kahuna/public/js/window.ts b/kahuna/public/js/window.ts index f015022300..d69634b6cb 100644 --- a/kahuna/public/js/window.ts +++ b/kahuna/public/js/window.ts @@ -9,6 +9,9 @@ declare global { featureSwitches: Array; maybeOrgOwnedValue: string | undefined; announcements: Array; + usePermissionsFilter: boolean; + permissionsOptions?: string | undefined; + permissionsDefault?: string | undefined; } } } diff --git a/kahuna/public/stylesheets/main.css b/kahuna/public/stylesheets/main.css index aedcbe8a1b..4c483effb6 100644 --- a/kahuna/public/stylesheets/main.css +++ b/kahuna/public/stylesheets/main.css @@ -1015,6 +1015,9 @@ textarea.ng-invalid { cursor: pointer; } +.search__advanced-toggle-lbl { + margin-right: 6px; +} @media screen and (max-width: 850px) { .search__advanced-toggle { @@ -1392,7 +1395,7 @@ textarea.ng-invalid { width: 100%; border-bottom: 1px solid #565656; position: fixed; - z-index: 30; + z-index: 20; left: 0; } From 2dcfa292ee3a7b6fe82fd956490d15c1ed011fe9 Mon Sep 17 00:00:00 2001 From: AndyKilmory Date: Thu, 11 Apr 2024 17:38:11 +0100 Subject: [PATCH 02/14] adding interim filters --- .../gr-my-uploads/gr-my-uploads.css | 40 +++++ .../gr-my-uploads/gr-my-uploads.tsx | 42 +++++ .../gr-permissions-filter-config.ts | 101 ++++++++++++ .../gr-permissions-filter.css | 65 ++++++++ .../gr-permissions-filter.tsx | 155 ++++++++++++++++++ 5 files changed, 403 insertions(+) create mode 100644 kahuna/public/js/components/gr-my-uploads/gr-my-uploads.css create mode 100644 kahuna/public/js/components/gr-my-uploads/gr-my-uploads.tsx create mode 100644 kahuna/public/js/components/gr-permissions-filter/gr-permissions-filter-config.ts create mode 100644 kahuna/public/js/components/gr-permissions-filter/gr-permissions-filter.css create mode 100644 kahuna/public/js/components/gr-permissions-filter/gr-permissions-filter.tsx diff --git a/kahuna/public/js/components/gr-my-uploads/gr-my-uploads.css b/kahuna/public/js/components/gr-my-uploads/gr-my-uploads.css new file mode 100644 index 0000000000..79c79dc33e --- /dev/null +++ b/kahuna/public/js/components/gr-my-uploads/gr-my-uploads.css @@ -0,0 +1,40 @@ +.my-uploads-container { + display: flex; + align-items: center; + height: 50px; + padding-left: 12px; + padding-right: 12px; + fill: #ccc; +} + +.custom-checkbox { + cursor: pointer; +} + +.label-wrapper { + display: flex; + align-items: center; + padding-bottom: 22px; +} + +.custom-checkbox input { + visibility: hidden; + width: 0; + height: 0; + margin: 0; + padding: 0; +} + +.custom-checkbox .label-wrapper .custom-span { + height: 14px; + width: 14px; + border: 1px solid grey; + display: inline-block; + background: white; + margin-right: 4px; +} + +[type=checkbox]:checked + .label-wrapper .custom-span { + background: #4eaae8; + content: url('data:image/svg+xml,'); +} diff --git a/kahuna/public/js/components/gr-my-uploads/gr-my-uploads.tsx b/kahuna/public/js/components/gr-my-uploads/gr-my-uploads.tsx new file mode 100644 index 0000000000..395db9aea1 --- /dev/null +++ b/kahuna/public/js/components/gr-my-uploads/gr-my-uploads.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; +import * as angular from "angular"; +import { react2angular } from "react2angular"; +import { useState } from "react"; + +import "./gr-my-uploads.css"; + +const MY_UPLOADS = "My uploads"; + +export interface MyUploadsProps { + onChange: (selected: boolean) => void; +} + +export interface MyUploadsWrapperProps { + props: MyUploadsProps; +} + +const MyUploads: React.FC = ({ props }) => { + + const [myUploads, setMyUploads] = useState(false); + + const handleCheckboxClick = () => { + const chkd = myUploads; + setMyUploads(!chkd); + props.onChange(!chkd); + }; + + return ( +
+ +
+ ); +}; + +export const myUploads = angular.module('gr.myUploads', []) + .component('myUploads', react2angular(MyUploads, ["props"])); diff --git a/kahuna/public/js/components/gr-permissions-filter/gr-permissions-filter-config.ts b/kahuna/public/js/components/gr-permissions-filter/gr-permissions-filter-config.ts new file mode 100644 index 0000000000..171c0c2b0c --- /dev/null +++ b/kahuna/public/js/components/gr-permissions-filter/gr-permissions-filter-config.ts @@ -0,0 +1,101 @@ +//***permissions constants*** + +//-chip query content- +const CHARGEABLE_QUERY = " category:chargeable"; + +//-permissions options- +const CHARGEABLE_OPT = "chargeable"; +const ALL_PERMISSIONS_OPT = "allPermissions"; + +//-permissions labels- +const CHARGEABLE_LABEL = "Chargeable"; +const ALL_PERMISSIONS_LABEL = "All Permissions"; + +export type PermissionOption = { + id: string, + label: string, + mapping: string, + payable: string +} + +function getPermissionsOptions():Array { + try { + if (window._clientConfig.permissionsOptions) { + return JSON.parse(window._clientConfig.permissionsOptions); + } else { + return []; + } + } catch (e) { + console.log("Error Parsing Permissions Options"); + console.log("Options String: " + window._clientConfig.permissionsOptions); + console.log("Error: " + e); + return []; + } +} + +const pOpts = getPermissionsOptions(); + +export function permissionsDefaultOpt():string{ + if (window._clientConfig.permissionsDefault && pOpts.length > 0) { + return window._clientConfig.permissionsDefault; + } else { + return ALL_PERMISSIONS_OPT; + } +} + +export function permissionsQueries():string[] { + if (pOpts.length > 0) { + return pOpts.map(c => c.mapping.split(',')).flatMap(c => c); + } else { + return [ + CHARGEABLE_QUERY + ]; + } +} + +export function permissionsPayable():{opt:string, payable:string}[] { + if (pOpts.length > 0) { + return pOpts.map(c => { return {opt: c.id, payable: c.payable};}); + } else { + const pPayable: { opt: string, payable: string }[] = [ + {opt: CHARGEABLE_OPT, payable: 'none'}, + {opt: ALL_PERMISSIONS_OPT, payable: 'none'} + ]; + return pPayable; + } +} + +//-options and labels- +export function permissionsOptions():{label:string, value:string}[] { + if (pOpts.length > 0) { + return pOpts.map(c => { + return { + label: c.label, + value: c.id + }; + }); + } else { + const permOpts: { label: string, value: string }[] = [ + {label: ALL_PERMISSIONS_LABEL, value: ALL_PERMISSIONS_OPT}, + {label: CHARGEABLE_LABEL, value: CHARGEABLE_OPT} + ]; + return permOpts; + } +} + +export function permissionsMappings():{opt:string, query:string[]}[] { + if (pOpts.length > 0) { + return pOpts.map(c => { + return { + opt: c.id, + query: c.mapping.split(",").map(q => " " + q).filter(q => q.trim() != "") + }; + }); + } else { + const permMappings: { opt: string, query: string[] }[] = [ + {opt: CHARGEABLE_OPT, query: [CHARGEABLE_QUERY]}, + {opt: ALL_PERMISSIONS_OPT, query: []} + ]; + return permMappings; + } +} diff --git a/kahuna/public/js/components/gr-permissions-filter/gr-permissions-filter.css b/kahuna/public/js/components/gr-permissions-filter/gr-permissions-filter.css new file mode 100644 index 0000000000..6b1645fc6a --- /dev/null +++ b/kahuna/public/js/components/gr-permissions-filter/gr-permissions-filter.css @@ -0,0 +1,65 @@ +.outer-permissions-filters { + display: flex; + align-items: center; + height: 50px; + fill: #ccc; +} + +.permissions-dropdown { + height: 50px; + padding-right: 0px; +} + +.permissions-selection { + display: flex; + align-items: center; + height: 50px; + fill: #ccc; +} + +.permissions-selection:hover { + color: white; + fill: #fff; +} + +.permissions-selection-label { + font-size: larger; +} + +.permissions-selection-icon { + padding-top: 3px; + transition: fill 0.3s; +} + +.permissions-dropdown-menu { + width: 250px; + border: 1px solid #ccc; + border-collapse: collapse; + background-color: #333; + position: absolute; +} + +.permissions-dropdown-item { + border: 1px solid #ccc; +} + +.permissions-dropdown-item:hover { + background-color: #008fc5; + color: white; + fill: #fff; + cursor: pointer; +} + +.permissions-dropdown-cell { + border: 0px; +} + +.permissions-dropdown-cell-tick { + border: 0px; + stroke: #ccc; + transition: stroke 0.3ms; +} + +.permissions-dropdown-cell-tick:hover { + stroke: #fff; +} diff --git a/kahuna/public/js/components/gr-permissions-filter/gr-permissions-filter.tsx b/kahuna/public/js/components/gr-permissions-filter/gr-permissions-filter.tsx new file mode 100644 index 0000000000..3079ef05dd --- /dev/null +++ b/kahuna/public/js/components/gr-permissions-filter/gr-permissions-filter.tsx @@ -0,0 +1,155 @@ +import * as React from "react"; +import * as angular from "angular"; +import { react2angular } from "react2angular"; +import { useEffect, useRef, useState } from "react"; +import * as PermissionsConf from "./gr-permissions-filter-config"; + +import "./gr-permissions-filter.css"; +import "./gr-toggle-switch.css"; + +const SHOW_CHARGEABLE = "Show payable images"; +const SELECT_OPTION = "Select an option"; + +const chevronIcon = () => + + + ; + +const emptyIcon = () => + + + ; + +const tickIcon = () => + + + ; + +export interface PermissionsDropdownOption { + value: string; + label: string; +} + +export interface PermissionsDropdownProps { + options: PermissionsDropdownOption[]; + selectedOption?: PermissionsDropdownOption | null; + onSelect: (option: PermissionsDropdownOption, showPayable: boolean) => void; + onChargeable: (showChargeable: boolean) => void; + chargeable: boolean; + query?: string | ""; +} + +export interface PermissionsWrapperProps { + props: PermissionsDropdownProps; +} + +// *** functional react component *** +const PermissionsFilter: React.FC = ({ props }) => { + const options:PermissionsDropdownOption[] = props.options; + const defOptVal:string = PermissionsConf.permissionsDefaultOpt(); + const payableDefaults = PermissionsConf.permissionsPayable(); + const defPerms:PermissionsDropdownOption = options.filter(opt => opt.value == defOptVal)[0]; + const propsRef = useRef(props); + + const [isOpen, setIsOpen] = useState(false); + const [isChargeable, setIsChargeable] = useState(props.chargeable); + const [selectedOption, setSelection] = useState(defPerms); + + const handleQueryChange = (e: any ) => { + const newQuery = e.detail.query ? (" " + e.detail.query) : ""; + + //-check chargeable- + const logoClick = window.sessionStorage.getItem("logoClick") ? window.sessionStorage.getItem("logoClick") : ""; + if (logoClick.includes("logoClick")) { + setIsChargeable(false); + window.sessionStorage.setItem("logoClick", ""); + } + + if (propsRef.current.query !== newQuery) { + propsRef.current.query = newQuery; + const permMaps = PermissionsConf.permissionsMappings(); + for (let i = 0; i < permMaps.length; i++) { + if (permMaps[i].query.length > 0 && permMaps[i].query.filter(q => newQuery.includes(q)).length == permMaps[i].query.length) { + const sel = options.filter(opt => opt.value == permMaps[i].opt)[0]; + setSelection(sel); + return; + } + } + + //-default- + const lDefOptVal:string = PermissionsConf.permissionsDefaultOpt(); + const lDefPerms:PermissionsDropdownOption = props.options.filter(opt => opt.value == lDefOptVal)[0]; + setSelection(options.filter(opt => opt.value == lDefPerms.value)[0]); + } + }; + + useEffect(() => { + window.addEventListener('queryChangeEvent', handleQueryChange); + setSelection(defPerms); + + // Clean up the event listener when the component unmounts + return () => { + window.removeEventListener('queryChangeEvent', handleQueryChange); + }; + }, []); + + const handleOptionClick = (option: PermissionsDropdownOption) => { + const payableDef = payableDefaults.filter(pd => pd.opt === option.value)[0]; + if (payableDef.payable === 'false' || payableDef.payable === 'true') { + const payableOn = payableDef.payable === 'false' ? false : true; + setIsChargeable(payableOn); + props.onSelect(option, payableOn); + } else { + props.onSelect(option, isChargeable); + } + setSelection(option); + setIsOpen(false); + }; + + useEffect(() => { + props.onChargeable(isChargeable); + }, [isChargeable]); + + const handleToggle = () => { + setIsChargeable(prevState => !prevState); + }; + + return ( +
+
+ + {isOpen && ( + + + {options.map((option) => ( + handleOptionClick(option)}> + + + + ))} + +
+ {(selectedOption.value == option.value) ? tickIcon() : emptyIcon()} + + {option.label} +
+ )} +
+
+
{SHOW_CHARGEABLE}
+ +
+
+ ); +}; + +export const permissionsFilter = angular.module('gr.permissionsFilter', []) + .component('permissionsFilter', react2angular(PermissionsFilter, ["props"])); From 0bed27179e083c7e0ecde28a7e489044a1a142c1 Mon Sep 17 00:00:00 2001 From: AndyKilmory Date: Wed, 17 Apr 2024 18:47:50 +0100 Subject: [PATCH 03/14] adding keyboard control and width adaptations to permissions filter --- .../gr-my-uploads/gr-my-uploads.tsx | 23 +++-- .../gr-permissions-filter.css | 15 ++++ .../gr-permissions-filter.tsx | 89 ++++++++++++++++--- .../gr-sort-control/gr-sort-control.css | 2 +- 4 files changed, 110 insertions(+), 19 deletions(-) diff --git a/kahuna/public/js/components/gr-my-uploads/gr-my-uploads.tsx b/kahuna/public/js/components/gr-my-uploads/gr-my-uploads.tsx index 395db9aea1..9f43226139 100644 --- a/kahuna/public/js/components/gr-my-uploads/gr-my-uploads.tsx +++ b/kahuna/public/js/components/gr-my-uploads/gr-my-uploads.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as angular from "angular"; import { react2angular } from "react2angular"; -import { useState } from "react"; +import { useState, KeyboardEvent } from "react"; import "./gr-my-uploads.css"; @@ -20,16 +20,25 @@ const MyUploads: React.FC = ({ props }) => { const [myUploads, setMyUploads] = useState(false); const handleCheckboxClick = () => { - const chkd = myUploads; - setMyUploads(!chkd); - props.onChange(!chkd); + setMyUploads(prevChkd => { + props.onChange(!prevChkd); + return !prevChkd; + }); + }; + + const handleKeyboard = (event:KeyboardEvent) => { + if (event.code === 'Space') { + event.preventDefault(); + event.stopPropagation(); + handleCheckboxClick(); + } }; return ( -
+