From dbdd4a4cd36956558e0c56b5d3f659b5fe33732d Mon Sep 17 00:00:00 2001 From: danactive Date: Sun, 10 Sep 2023 12:32:50 -0700 Subject: [PATCH 01/24] feat(Next > Organize Previews): Add page to sort thumbnail previews --- next/package-lock.json | 124 +++++++++++++++++- next/package.json | 1 + next/pages/admin/walk.tsx | 28 ++-- .../OrganizePreviews/PreviewColumn.tsx | 110 ++++++++++++++++ .../OrganizePreviews/PreviewItem.tsx | 59 +++++++++ .../src/components/OrganizePreviews/index.tsx | 85 ++++++++++++ next/src/utils/preview.ts | 60 +++++++++ 7 files changed, 458 insertions(+), 9 deletions(-) create mode 100644 next/src/components/OrganizePreviews/PreviewColumn.tsx create mode 100644 next/src/components/OrganizePreviews/PreviewItem.tsx create mode 100644 next/src/components/OrganizePreviews/index.tsx create mode 100644 next/src/utils/preview.ts diff --git a/next/package-lock.json b/next/package-lock.json index d300c5b8..1bbc69f7 100644 --- a/next/package-lock.json +++ b/next/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@hello-pangea/dnd": "^16.3.0", "@mui/joy": "^5.0.0-alpha.89", "@types/react-image-gallery": "^1.2.0", "@types/xml2js": "^0.4.12", @@ -2293,6 +2294,24 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.1.tgz", "integrity": "sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==" }, + "node_modules/@hello-pangea/dnd": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-16.3.0.tgz", + "integrity": "sha512-RYQ/K8shtJoyNPvFWz0gfXIK7HF3P3mL9UZFGMuHB0ljRSXVgMjVFI/FxcZmakMzw6tO7NflWLriwTNBow/4vw==", + "dependencies": { + "@babel/runtime": "^7.22.5", + "css-box-model": "^1.2.1", + "memoize-one": "^6.0.0", + "raf-schd": "^4.0.3", + "react-redux": "^8.1.1", + "redux": "^4.2.1", + "use-memo-one": "^1.1.3" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -3746,6 +3765,15 @@ "node": ">= 10" } }, + "node_modules/@types/@hello-pangea/dnd": { + "version": "13.1.4", + "resolved": "https://registry.npmjs.org/@types/@hello-pangea/dnd/-/@hello-pangea/dnd-13.1.4.tgz", + "integrity": "sha512-4bIBdzOr0aavN+88q3C7Pgz+xkb7tz3whORYrmSj77wfVEMfiWiooIwVWFR7KM2e+uGTe5BVrXqSfb0aHeflJA==", + "extraneous": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/aria-query": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", @@ -3813,6 +3841,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -3951,7 +3988,7 @@ "version": "18.2.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } @@ -4008,6 +4045,11 @@ "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", "dev": true }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/xml2js": { "version": "0.4.12", "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.12.tgz", @@ -5715,6 +5757,14 @@ "node": ">= 8" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", @@ -11573,6 +11623,11 @@ "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -11632,6 +11687,44 @@ } } }, + "node_modules/react-redux": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz", + "integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4 || ^5.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-select": { "version": "5.7.4", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.4.tgz", @@ -11848,6 +11941,14 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -13292,6 +13393,11 @@ "readable-stream": "3" } }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, "node_modules/tinyqueue": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", @@ -13813,6 +13919,22 @@ } } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/next/package.json b/next/package.json index 07d6b82f..d3b06fa7 100644 --- a/next/package.json +++ b/next/package.json @@ -18,6 +18,7 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@hello-pangea/dnd": "^16.3.0", "@mui/joy": "^5.0.0-alpha.89", "@types/react-image-gallery": "^1.2.0", "@types/xml2js": "^0.4.12", diff --git a/next/pages/admin/walk.tsx b/next/pages/admin/walk.tsx index b0d11e64..20ad5138 100644 --- a/next/pages/admin/walk.tsx +++ b/next/pages/admin/walk.tsx @@ -1,7 +1,8 @@ -import { List, ListDivider, ListItem } from '@mui/joy' +import { List, ListDivider } from '@mui/joy' import { useRouter } from 'next/router' import { Fragment, useEffect, useState } from 'react' +import OrganizePreviews, { DraggableThumb } from '../../src/components/OrganizePreviews' import ListFile from '../../src/components/Walk/ListFile' import type { Filesystem, FilesystemBody } from '../../src/lib/filesystems' import { @@ -21,7 +22,8 @@ type ItemFile = Partial & { function WalkPage() { const { asPath } = useRouter() - const [data, setData] = useState(null) + const [fileList, setFileList] = useState(null) + const [previewList, setPreviewList] = useState(null) const [isLoading, setLoading] = useState(false) const pathQs = parseHash('path', asPath) @@ -30,17 +32,18 @@ function WalkPage() { fetch(`/api/admin/filesystems?path=${pathQs ?? '/'}`) .then((response) => response.json()) .then((result: FilesystemBody) => { - setData(result) setLoading(false) + setFileList(result.files) + const itemImages = result.files.filter((file) => isImage(file)) + setPreviewList(itemImages) }) }, [asPath]) if (isLoading) return

Loading...

- if (!data) return

No filesystem data

+ if (!fileList) return

No filesystem data

- const itemImages = data.files.filter((file) => isImage(file)) - const hasImages = !isLoading && itemImages.length > 0 - const fsItems = addParentDirectoryNav(organizeByMedia(data.files), pathQs) + const hasImages = !isLoading && previewList && previewList.length > 0 + const fsItems = addParentDirectoryNav(organizeByMedia(fileList), pathQs) return ( <> @@ -52,7 +55,16 @@ function WalkPage() { ))} - {hasImages && (
TODO display OrganizeThumbs
)} + {hasImages && ( + ({ + ...item, + content: , + }))} + /> + )} ) } diff --git a/next/src/components/OrganizePreviews/PreviewColumn.tsx b/next/src/components/OrganizePreviews/PreviewColumn.tsx new file mode 100644 index 00000000..0bdb243f --- /dev/null +++ b/next/src/components/OrganizePreviews/PreviewColumn.tsx @@ -0,0 +1,110 @@ +import React from 'react' +import { Draggable, Droppable, type DroppableProvided } from '@hello-pangea/dnd' +import styled from 'styled-components' + +import { type Filesystem } from '../../lib/filesystems' +import PreviewItem from './PreviewItem' + +const grid = 4 + +export const getBackgroundColor = (isDraggingOver: boolean, isDraggingFrom: boolean) => { + // Colour swatch #545454 compound https://color.adobe.com/create/color-wheel + if (isDraggingOver) { + return '#BA8570' + } + if (isDraggingFrom) { + return '#496149' + } + return '#545454' // base colour +} + +const Wrapper = styled.div<{ $isDraggingOver: boolean, $isDraggingFrom: boolean }>` + background-color: ${(props) => getBackgroundColor(props.$isDraggingOver, props.$isDraggingFrom)} + display: flex + flex-direction: column + padding: ${grid}px + border: ${grid}px + padding-bottom: 0 + transition: background-color 0.2s ease, opacity 0.1s ease + user-select: none + width: 285px +` + +const scrollContainerHeight = 250 + +const DropZone = styled.div` + /* stop the list collapsing when empty */ + min-height: ${scrollContainerHeight}px + /* + not relying on the items for a margin-bottom + as it will collapse when the list is empty + */ + padding-bottom: ${grid}px +` + +// eslint-disable-next-line prefer-arrow-callback +const InnerPreviewColumn = React.memo(function InnerPreviewColumn( + { items }: + { items: Filesystem[] }, +) { + return items.map((item, index) => ( + + {(dragProvided, dragSnapshot) => ( + + )} + + )) +}) + +function InnerList( + { items, dropProvided }: + { items: Filesystem[], dropProvided: DroppableProvided }, +) { + return ( + + + {dropProvided.placeholder} + + ) +} + +export default function PreviewColumn({ + ignoreContainerClipping, + isCombineEnabled, + columnId = 'LIST', + style, + items, +}: { + ignoreContainerClipping?: boolean, + isCombineEnabled?: boolean, + columnId?: string, + style?: React.CSSProperties, + items: Filesystem[], +}) { + return ( + + {(dropProvided, dropSnapshot) => ( + + + + )} + + ) +} diff --git a/next/src/components/OrganizePreviews/PreviewItem.tsx b/next/src/components/OrganizePreviews/PreviewItem.tsx new file mode 100644 index 00000000..1f904036 --- /dev/null +++ b/next/src/components/OrganizePreviews/PreviewItem.tsx @@ -0,0 +1,59 @@ +import React from 'react' +import { type DraggableProvided } from '@hello-pangea/dnd' +import styled from 'styled-components' + +import { type Filesystem } from '../../lib/filesystems' + +const getBorderColor = (isDragging: boolean) => (isDragging ? '#e6df55' : 'transparent') +const getBackgroundColor = (isDragging: boolean) => (isDragging ? '#877e7a' : 'transparent') + +const Container = styled.div<{ $isDragging: boolean }>` + border: ${(props) => getBorderColor(props.$isDragging)} 5px solid + background-color: ${(props) => getBackgroundColor(props.$isDragging)} + box-sizing: border-box + padding: 4px +` + +function getStyle(provided: DraggableProvided, style?: React.CSSProperties) { + if (!style) { + return provided.draggableProps.style + } + + return { + ...provided.draggableProps.style, + ...style, + } +} + +function PreviewItem( + { + item, + isDragging, + provided, + style, + index, + }: + { + item: Filesystem, + isDragging: boolean, + provided: DraggableProvided, + style?: React.CSSProperties, + index: number, + }, +) { + return ( + + {item.label} + + ) +} + +export default React.memo(PreviewItem) diff --git a/next/src/components/OrganizePreviews/index.tsx b/next/src/components/OrganizePreviews/index.tsx new file mode 100644 index 00000000..b48fae90 --- /dev/null +++ b/next/src/components/OrganizePreviews/index.tsx @@ -0,0 +1,85 @@ +import { DragDropContext, type DraggableLocation } from '@hello-pangea/dnd' +import type { Dispatch, SetStateAction } from 'react' +import styled from 'styled-components' + +import config from '../../../../config.json' +import type { Filesystem } from '../../lib/filesystems' +import { groupIntoColumns, reorderOnRelease } from '../../utils/preview' +import Img from '../Img' +import Link from '../Link' +import PreviewColumn from './PreviewColumn' + +const grid = 4 + +const Column = styled.div` + margin: 0 ${grid * 2}px; +` + +const HorizontalScrollContainer = styled.div` + display: flex; + justify-content: flex-start; + align-items: flex-start; + padding: ${grid}px; + overflow: auto; +` + +export function DraggableThumb({ item }: { item: Filesystem }) { + const { filename } = item + if (!filename) throw new ReferenceError('Filename is missing') + return ( + <> + + + {filename} + + + No preview yet + + ) +} + +export default function OrganizePreviews( + { items, setItems }: + { items: Filesystem[], setItems: Dispatch> }, +) { + const columnItems = groupIntoColumns(items) + + function onDragEnd({ source, destination }: { source: DraggableLocation, destination: DraggableLocation | null | undefined }) { + // dropped nowhere + if (!destination) { + return + } + + setItems( + reorderOnRelease({ + columnItems, + source, + destination, + }), + ) + } + + const makeColumn = (columnItem: Filesystem[], index: number) => ( + + + + ) + + return ( + onDragEnd({ source, destination })}> + + {columnItems.map(makeColumn)} + + + ) +} diff --git a/next/src/utils/preview.ts b/next/src/utils/preview.ts new file mode 100644 index 00000000..5b6b322d --- /dev/null +++ b/next/src/utils/preview.ts @@ -0,0 +1,60 @@ +import { type DraggableLocation } from '@hello-pangea/dnd' + +import { type Filesystem } from '../lib/filesystems' + +export function groupIntoColumns(items: Filesystem[]) { + const IMAGE_PER_COLUMN = 4 + const columnCount = Math.ceil(items.length / IMAGE_PER_COLUMN) + + return [...Array(columnCount).keys()].map((index) => items.slice(IMAGE_PER_COLUMN * index, IMAGE_PER_COLUMN * (index + 1))) +} + +export function reorder(list: Filesystem[], startIndex: number, endIndex: number) { + const result = Array.from(list) + const [removed] = result.splice(startIndex, 1) + result.splice(endIndex, 0, removed) + + return result +} + +function ungroupToItems(columnItems: Record) { + return Object.keys(columnItems).reduce( + (acc, val: any) => acc.concat(columnItems[val]), + [] as Filesystem[], + ) +} + +export function reorderOnRelease( + { columnItems, source, destination }: + { columnItems: ReturnType, source: DraggableLocation, destination: DraggableLocation }, +) { + const current = [...columnItems[Number(source.droppableId)]] + const next = [...columnItems[Number(destination.droppableId)]] + const target = current[source.index] + + // moving to same list + if (source.droppableId === destination.droppableId) { + const reordered = reorder(current, source.index, destination.index) + const result = { + ...columnItems, + [source.droppableId]: reordered, + } + + return ungroupToItems(result) + } + + // moving to different list + + // remove from original + current.splice(source.index, 1) + // insert into next + next.splice(destination.index, 0, target) + + const result = { + ...columnItems, + [source.droppableId]: current, + [destination.droppableId]: next, + } + + return ungroupToItems(result) +} From a67f0418bae1074bb15bbb373babc10c6aad4a82 Mon Sep 17 00:00:00 2001 From: danactive Date: Sun, 10 Sep 2023 14:26:36 -0700 Subject: [PATCH 02/24] Next > Organize Previews: Move Filesystem to modal --- next/pages/admin/walk.tsx | 11 +---- next/src/components/Img/index.tsx | 2 - .../OrganizePreviews/PreviewColumn.tsx | 4 +- .../{PreviewItem.tsx => PreviewImage.tsx} | 31 ++++++++++++-- .../src/components/OrganizePreviews/index.tsx | 24 ----------- next/src/lib/filesystems.ts | 29 +------------- next/src/lib/utils.ts | 1 - next/src/models/filesystems.ts | 40 +++++++++++++++++++ next/src/utils/__tests__/walk.test.ts | 1 + next/src/utils/preview.ts | 2 +- 10 files changed, 75 insertions(+), 70 deletions(-) rename next/src/components/OrganizePreviews/{PreviewItem.tsx => PreviewImage.tsx} (64%) create mode 100644 next/src/models/filesystems.ts diff --git a/next/pages/admin/walk.tsx b/next/pages/admin/walk.tsx index 20ad5138..5f039cdd 100644 --- a/next/pages/admin/walk.tsx +++ b/next/pages/admin/walk.tsx @@ -2,7 +2,7 @@ import { List, ListDivider } from '@mui/joy' import { useRouter } from 'next/router' import { Fragment, useEffect, useState } from 'react' -import OrganizePreviews, { DraggableThumb } from '../../src/components/OrganizePreviews' +import OrganizePreviews from '../../src/components/OrganizePreviews' import ListFile from '../../src/components/Walk/ListFile' import type { Filesystem, FilesystemBody } from '../../src/lib/filesystems' import { @@ -56,14 +56,7 @@ function WalkPage() { ))} {hasImages && ( - ({ - ...item, - content: , - }))} - /> + )} ) diff --git a/next/src/components/Img/index.tsx b/next/src/components/Img/index.tsx index 3608a03c..5d8571bf 100755 --- a/next/src/components/Img/index.tsx +++ b/next/src/components/Img/index.tsx @@ -17,8 +17,6 @@ function Img({ height?: number; title?: string; }) { - // Next.js image is now handling alt attribute check - // if (!alt) console.error('Missing ALT attribute on IMG') // eslint-disable-line no-console return {alt} } diff --git a/next/src/components/OrganizePreviews/PreviewColumn.tsx b/next/src/components/OrganizePreviews/PreviewColumn.tsx index 0bdb243f..5ba88d54 100644 --- a/next/src/components/OrganizePreviews/PreviewColumn.tsx +++ b/next/src/components/OrganizePreviews/PreviewColumn.tsx @@ -3,7 +3,7 @@ import { Draggable, Droppable, type DroppableProvided } from '@hello-pangea/dnd' import styled from 'styled-components' import { type Filesystem } from '../../lib/filesystems' -import PreviewItem from './PreviewItem' +import PreviewImage from './PreviewImage' const grid = 4 @@ -50,7 +50,7 @@ const InnerPreviewColumn = React.memo(function InnerPreviewColumn( return items.map((item, index) => ( {(dragProvided, dragSnapshot) => ( - (isDragging ? '#e6df55' : 'transparent') const getBackgroundColor = (isDragging: boolean) => (isDragging ? '#877e7a' : 'transparent') +function DraggableThumb({ item }: { item: Filesystem }) { + const { filename, absolutePath } = item + return ( + <> + + + {filename} + + + No preview yet + + ) +} + const Container = styled.div<{ $isDragging: boolean }>` border: ${(props) => getBorderColor(props.$isDragging)} 5px solid background-color: ${(props) => getBackgroundColor(props.$isDragging)} @@ -25,7 +48,7 @@ function getStyle(provided: DraggableProvided, style?: React.CSSProperties) { } } -function PreviewItem( +function PreviewImage( { item, isDragging, @@ -51,9 +74,9 @@ function PreviewItem( data-is-dragging={isDragging} data-index={index} > - {item.label} + ) } -export default React.memo(PreviewItem) +export default React.memo(PreviewImage) diff --git a/next/src/components/OrganizePreviews/index.tsx b/next/src/components/OrganizePreviews/index.tsx index b48fae90..c56aa3e6 100644 --- a/next/src/components/OrganizePreviews/index.tsx +++ b/next/src/components/OrganizePreviews/index.tsx @@ -2,11 +2,8 @@ import { DragDropContext, type DraggableLocation } from '@hello-pangea/dnd' import type { Dispatch, SetStateAction } from 'react' import styled from 'styled-components' -import config from '../../../../config.json' import type { Filesystem } from '../../lib/filesystems' import { groupIntoColumns, reorderOnRelease } from '../../utils/preview' -import Img from '../Img' -import Link from '../Link' import PreviewColumn from './PreviewColumn' const grid = 4 @@ -23,27 +20,6 @@ const HorizontalScrollContainer = styled.div` overflow: auto; ` -export function DraggableThumb({ item }: { item: Filesystem }) { - const { filename } = item - if (!filename) throw new ReferenceError('Filename is missing') - return ( - <> - - - {filename} - - - No preview yet - - ) -} - export default function OrganizePreviews( { items, setItems }: { items: Filesystem[], setItems: Dispatch> }, diff --git a/next/src/lib/filesystems.ts b/next/src/lib/filesystems.ts index cfd8bca8..b393fbee 100644 --- a/next/src/lib/filesystems.ts +++ b/next/src/lib/filesystems.ts @@ -2,6 +2,7 @@ import { glob } from 'glob' import path from 'node:path' import utilsFactory from './utils' +import transform, { type Filesystem } from '../models/filesystems' type ErrorOptionalMessage = { files: object[]; error?: { message: string } } const errorSchema = (message: string): ErrorOptionalMessage => { @@ -10,16 +11,6 @@ const errorSchema = (message: string): ErrorOptionalMessage => { return { ...out, error: { message } } } -type Filesystem = { - id: string; - label: string; - ext: string; - name: string; - filename: string; - path: string; - mediumType: string; -} - type FilesystemBody = { files: Filesystem[]; destinationPath: string; @@ -65,23 +56,7 @@ async function get( const files = await glob(decodeURI(`${globPath}/*`)) - const webPaths = files.map((file): Filesystem => { - const fileExt = utils.type(file) // case-insensitive - const fileName = path.basename(file, `.${fileExt}`) - const mediumType = utils.mediumType(utils.mimeType(fileExt)) - const filePath = file.replace(globPath, destinationPath) - const filename = (fileExt === '') ? fileName : `${fileName}.${fileExt}` - - return { - filename, - label: filename, - mediumType: mediumType || 'folder', - id: filePath, - path: filePath, - ext: fileExt, - name: fileName, - } - }).sort((a, b) => a.name.localeCompare(b.name)) + const webPaths = files.map((file) => transform(file, { destinationPath, globPath })).sort((a, b) => a.name.localeCompare(b.name)) const body = { files: webPaths, destinationPath } if (returnEnvelope) { diff --git a/next/src/lib/utils.ts b/next/src/lib/utils.ts index 9db4d86a..30240aa0 100644 --- a/next/src/lib/utils.ts +++ b/next/src/lib/utils.ts @@ -6,7 +6,6 @@ import type { AlbumMeta, Item, XmlItem, - XmlMeta, } from '../types/common' const { IMAGE_BASE_URL = '' } = process.env diff --git a/next/src/models/filesystems.ts b/next/src/models/filesystems.ts new file mode 100644 index 00000000..43d9ece2 --- /dev/null +++ b/next/src/models/filesystems.ts @@ -0,0 +1,40 @@ +import path from 'node:path' + +import utilsFactory from '../lib/utils' + +const { IMAGE_BASE_URL = '' } = process.env + +type Filesystem = { + id: string; + label: string; + ext: string; + name: string; + filename: string; + path: string; + absolutePath: string; // Temporary until next folder moves to root + mediumType: string; +} + +const utils = utilsFactory() + +function transform(file: string, { destinationPath, globPath }: { destinationPath: string, globPath: string }): Filesystem { + const fileExt = utils.type(file) // case-insensitive + const fileName = path.basename(file, `.${fileExt}`) + const mediumType = utils.mediumType(utils.mimeType(fileExt)) + const filePath = file.replace(globPath, destinationPath) + const filename = (fileExt === '') ? fileName : `${fileName}.${fileExt}` + + return { + filename, + label: filename, + mediumType: mediumType || 'folder', + id: filePath, + path: filePath, + absolutePath: `${IMAGE_BASE_URL}${filePath}`, + ext: fileExt, + name: fileName, + } +} + +export { type Filesystem } +export default transform diff --git a/next/src/utils/__tests__/walk.test.ts b/next/src/utils/__tests__/walk.test.ts index 27d59510..9305d2e8 100644 --- a/next/src/utils/__tests__/walk.test.ts +++ b/next/src/utils/__tests__/walk.test.ts @@ -81,6 +81,7 @@ describe('Walk - util', () => { name: 'Name', filename: 'Filename', path: 'Path', + absolutePath: 'Absolute Path', } describe('isImage', () => { test('detect JPG', () => { diff --git a/next/src/utils/preview.ts b/next/src/utils/preview.ts index 5b6b322d..692b7686 100644 --- a/next/src/utils/preview.ts +++ b/next/src/utils/preview.ts @@ -1,6 +1,6 @@ import { type DraggableLocation } from '@hello-pangea/dnd' -import { type Filesystem } from '../lib/filesystems' +import { type Filesystem } from '../models/filesystems' export function groupIntoColumns(items: Filesystem[]) { const IMAGE_PER_COLUMN = 4 From e5da75e3887cfbf17027ec9d48c5363ca5972497 Mon Sep 17 00:00:00 2001 From: danactive Date: Mon, 11 Sep 2023 20:13:13 -0700 Subject: [PATCH 03/24] Next > Organize Previews: Fix background colour --- .../OrganizePreviews/PreviewColumn.tsx | 22 +++++++++---------- .../OrganizePreviews/PreviewImage.tsx | 10 ++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/next/src/components/OrganizePreviews/PreviewColumn.tsx b/next/src/components/OrganizePreviews/PreviewColumn.tsx index 5ba88d54..471a33cc 100644 --- a/next/src/components/OrganizePreviews/PreviewColumn.tsx +++ b/next/src/components/OrganizePreviews/PreviewColumn.tsx @@ -19,27 +19,27 @@ export const getBackgroundColor = (isDraggingOver: boolean, isDraggingFrom: bool } const Wrapper = styled.div<{ $isDraggingOver: boolean, $isDraggingFrom: boolean }>` - background-color: ${(props) => getBackgroundColor(props.$isDraggingOver, props.$isDraggingFrom)} - display: flex - flex-direction: column - padding: ${grid}px - border: ${grid}px - padding-bottom: 0 - transition: background-color 0.2s ease, opacity 0.1s ease - user-select: none - width: 285px + background-color: ${(props) => getBackgroundColor(props.$isDraggingOver, props.$isDraggingFrom)}; + display: flex; + flex-direction: column; + padding: ${grid}px; + border: ${grid}px; + padding-bottom: 0; + transition: background-color 0.2s ease, opacity 0.1s ease; + user-select: none; + width: 285px; ` const scrollContainerHeight = 250 const DropZone = styled.div` /* stop the list collapsing when empty */ - min-height: ${scrollContainerHeight}px + min-height: ${scrollContainerHeight}px; /* not relying on the items for a margin-bottom as it will collapse when the list is empty */ - padding-bottom: ${grid}px + padding-bottom: ${grid}px; ` // eslint-disable-next-line prefer-arrow-callback diff --git a/next/src/components/OrganizePreviews/PreviewImage.tsx b/next/src/components/OrganizePreviews/PreviewImage.tsx index c3fdccd9..2164c887 100644 --- a/next/src/components/OrganizePreviews/PreviewImage.tsx +++ b/next/src/components/OrganizePreviews/PreviewImage.tsx @@ -15,7 +15,7 @@ function DraggableThumb({ item }: { item: Filesystem }) { return ( <> - + {filename} @@ -31,10 +31,10 @@ function DraggableThumb({ item }: { item: Filesystem }) { } const Container = styled.div<{ $isDragging: boolean }>` - border: ${(props) => getBorderColor(props.$isDragging)} 5px solid - background-color: ${(props) => getBackgroundColor(props.$isDragging)} - box-sizing: border-box - padding: 4px + border: ${(props) => getBorderColor(props.$isDragging)} 5px solid; + background-color: ${(props) => getBackgroundColor(props.$isDragging)}; + box-sizing: border-box; + padding: 4px; ` function getStyle(provided: DraggableProvided, style?: React.CSSProperties) { From 3d3a680421337630fa814dd19806db7bd8980a80 Mon Sep 17 00:00:00 2001 From: danactive Date: Sun, 29 Oct 2023 12:33:33 -0700 Subject: [PATCH 04/24] feat(Next > Organize Previews > Rename): Allow todo photos to be renamed --- next/pages/admin/walk.tsx | 2 +- .../OrganizePreviews/ActionButtons.tsx | 45 +++++++++++++++++++ .../src/components/OrganizePreviews/index.tsx | 14 +++--- next/src/utils/index.ts | 2 +- 4 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 next/src/components/OrganizePreviews/ActionButtons.tsx diff --git a/next/pages/admin/walk.tsx b/next/pages/admin/walk.tsx index 5f039cdd..9b650b1a 100644 --- a/next/pages/admin/walk.tsx +++ b/next/pages/admin/walk.tsx @@ -56,7 +56,7 @@ function WalkPage() { ))} {hasImages && ( - + )} ) diff --git a/next/src/components/OrganizePreviews/ActionButtons.tsx b/next/src/components/OrganizePreviews/ActionButtons.tsx new file mode 100644 index 00000000..c88bf16a --- /dev/null +++ b/next/src/components/OrganizePreviews/ActionButtons.tsx @@ -0,0 +1,45 @@ +import Button from '@mui/joy/Button' +import { useRouter } from 'next/router' + +import type { Filesystem } from '../../lib/filesystems' +import { parseHash } from '../../utils/walk' + +export default function ActionButtons( + { items }: + { items: Filesystem[] }, +) { + const { asPath } = useRouter() + const pathQs = parseHash('path', asPath) + const path = pathQs ?? '/' + function rename() { + // eslint-disable-next-line no-alert + const date = window.prompt('Date of images (YYYY-MM-DD)?') + // TODO POST http://localhost:8000/admin/rename + + const postBody = { + filenames: items.map((i) => i.filename), + prefix: date, + source_folder: path, + preview: false, + raw: true, + rename_associated: true, + } + + const options = { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify(postBody), + }; + /* + curl -d '{"filenames":["a.jpg","b.jpg"], "prefix": "2020-06-13", + "source_folder": "/todo/doit", "preview": "false", "raw": "true", "rename_associated": "true"}' + -i http://127.0.0.1:8000/admin/rename -H "Content-Type: application/json" + */ + + return fetch('/api/admin/rename', options).then((s) => console.log(s)) + } + + return
+} diff --git a/next/src/components/OrganizePreviews/index.tsx b/next/src/components/OrganizePreviews/index.tsx index c56aa3e6..c1ba9291 100644 --- a/next/src/components/OrganizePreviews/index.tsx +++ b/next/src/components/OrganizePreviews/index.tsx @@ -4,6 +4,7 @@ import styled from 'styled-components' import type { Filesystem } from '../../lib/filesystems' import { groupIntoColumns, reorderOnRelease } from '../../utils/preview' +import ActionButtons from './ActionButtons' import PreviewColumn from './PreviewColumn' const grid = 4 @@ -52,10 +53,13 @@ export default function OrganizePreviews( ) return ( - onDragEnd({ source, destination })}> - - {columnItems.map(makeColumn)} - - + <> + + onDragEnd({ source, destination })}> + + {columnItems.map(makeColumn)} + + + ) } diff --git a/next/src/utils/index.ts b/next/src/utils/index.ts index 78e87fa0..7ffabc86 100644 --- a/next/src/utils/index.ts +++ b/next/src/utils/index.ts @@ -16,7 +16,7 @@ export const removeUndefinedFields = (obj: T): T => Object.keys(obj as object ) export function isNotEmpty(value: unknown): value is string { - if (typeof value !== 'undefined' || value !== null || value !== '') { + if (typeof value !== 'undefined' && value !== null && value !== '' && value !== undefined) { return true } return false From 53771018c9db5bf828d9724b4ccb56603d2b5914 Mon Sep 17 00:00:00 2001 From: danactive Date: Sun, 29 Oct 2023 16:27:58 -0700 Subject: [PATCH 05/24] feat(Next > Search): Add country or province to search keyword --- next/pages/[gallery].tsx | 6 +++--- next/pages/[gallery]/[album].tsx | 11 ++++++----- next/pages/[gallery]/all.tsx | 9 +++++---- next/pages/[gallery]/today.tsx | 9 +++++---- next/pages/index.tsx | 6 +++--- next/src/lib/search.ts | 7 +++++++ 6 files changed, 29 insertions(+), 19 deletions(-) diff --git a/next/pages/[gallery].tsx b/next/pages/[gallery].tsx index e66b026a..8df8bf4c 100644 --- a/next/pages/[gallery].tsx +++ b/next/pages/[gallery].tsx @@ -17,7 +17,7 @@ interface ServerSideAlbumItem extends GalleryAlbum { corpus: string; } -type Props = { +type ComponentProps = { gallery: NonNullable; albums: ServerSideAlbumItem[]; indexedKeywords: object[]; @@ -27,7 +27,7 @@ interface Params extends ParsedUrlQuery { gallery: NonNullable } -export const getStaticProps: GetStaticProps = async (context) => { +export const getStaticProps: GetStaticProps = async (context) => { const params = context.params! const { albums } = await getAlbums(params.gallery) const preparedAlbums = albums.map((album): ServerSideAlbumItem => ({ @@ -74,7 +74,7 @@ const AlbumYear = styled.h3` color: #8B5A2B; ` -function AlbumsPage({ gallery, albums, indexedKeywords }: Props) { +function AlbumsPage({ gallery, albums, indexedKeywords }: ComponentProps) { const { filtered, searchBox, diff --git a/next/pages/[gallery]/[album].tsx b/next/pages/[gallery]/[album].tsx index 66ac26d0..367c76bf 100644 --- a/next/pages/[gallery]/[album].tsx +++ b/next/pages/[gallery]/[album].tsx @@ -5,7 +5,7 @@ import AlbumPageComponent from '../../src/components/AlbumPage' import getAlbum from '../../src/lib/album' import getAlbums from '../../src/lib/albums' import getGalleries from '../../src/lib/galleries' -import indexKeywords from '../../src/lib/search' +import indexKeywords, { addGeographyToSearch } from '../../src/lib/search' import type { AlbumMeta, Item } from '../../src/types/common' async function buildStaticPaths() { @@ -21,7 +21,7 @@ interface ServerSideAlbumItem extends Item { corpus: string; } -type Props = { +type ComponentProps = { items?: ServerSideAlbumItem[]; meta: AlbumMeta; indexedKeywords: object[]; @@ -32,16 +32,17 @@ interface Params extends ParsedUrlQuery { album: NonNullable } -export const getStaticProps: GetStaticProps = async (context) => { +export const getStaticProps: GetStaticProps = async (context) => { const params = context.params! const { album: { items, meta } } = await getAlbum(params.gallery, params.album) const preparedItems = items.map((item) => ({ ...item, + search: addGeographyToSearch(item), corpus: [item.description, item.caption, item.location, item.city, item.search].join(' '), })) return { - props: { items: preparedItems, meta, ...indexKeywords(items) }, + props: { items: preparedItems, meta, ...indexKeywords(preparedItems) }, } } @@ -53,7 +54,7 @@ export const getStaticPaths: GetStaticPaths = async () => ( } ) -function AlbumPage({ items = [], meta, indexedKeywords }: Props) { +function AlbumPage({ items = [], meta, indexedKeywords }: ComponentProps) { return } diff --git a/next/pages/[gallery]/all.tsx b/next/pages/[gallery]/all.tsx index 302caf12..de5b9032 100644 --- a/next/pages/[gallery]/all.tsx +++ b/next/pages/[gallery]/all.tsx @@ -9,7 +9,7 @@ import config from '../../../config.json' import getAlbum from '../../src/lib/album' import getAlbums from '../../src/lib/albums' import getGalleries from '../../src/lib/galleries' -import indexKeywords from '../../src/lib/search' +import indexKeywords, { addGeographyToSearch } from '../../src/lib/search' import AlbumContext from '../../src/components/Context' import Img from '../../src/components/Img' @@ -34,7 +34,7 @@ interface ServerSideAllItem extends Item { coordinateAccuracy: NonNullable['zoom']; } -type Props = { +type ComponentProps = { items: ServerSideAllItem[]; indexedKeywords: object[]; } @@ -43,7 +43,7 @@ interface Params extends ParsedUrlQuery { gallery: NonNullable } -export const getStaticProps: GetStaticProps = async (context) => { +export const getStaticProps: GetStaticProps = async (context) => { const params = context.params! const { albums } = await getAlbums(params.gallery) @@ -60,6 +60,7 @@ export const getStaticProps: GetStaticProps = async (context) => album: albumName, corpus: [item.description, item.caption, item.location, item.city, item.search].join(' '), coordinateAccuracy: item.coordinateAccuracy ?? albumCoordinateAccuracy, + search: addGeographyToSearch(item), })) // reverse order for albums in ascending order (oldest on top) @@ -90,7 +91,7 @@ export const getStaticPaths: GetStaticPaths = async () => { } } -function AllPage({ items = [], indexedKeywords }: Props) { +function AllPage({ items = [], indexedKeywords }: ComponentProps) { const refImageGallery = useRef(null) const [memoryIndex, setMemoryIndex] = useState(0) const { diff --git a/next/pages/[gallery]/today.tsx b/next/pages/[gallery]/today.tsx index 742aa4f5..dba71736 100644 --- a/next/pages/[gallery]/today.tsx +++ b/next/pages/[gallery]/today.tsx @@ -5,7 +5,7 @@ import config from '../../../config.json' import getAlbum from '../../src/lib/album' import getAlbums from '../../src/lib/albums' import getGalleries from '../../src/lib/galleries' -import indexKeywords from '../../src/lib/search' +import indexKeywords, { addGeographyToSearch } from '../../src/lib/search' import AlbumPageComponent from '../../src/components/AlbumPage' import type { AlbumMeta, Item } from '../../src/types/common' @@ -16,7 +16,7 @@ interface ServerSideTodayItem extends Item { coordinateAccuracy: NonNullable['zoom']; } -type Props = { +type ComponentProps = { items: ServerSideTodayItem[]; indexedKeywords: object[]; } @@ -25,7 +25,7 @@ interface Params extends ParsedUrlQuery { gallery: NonNullable } -export const getStaticProps: GetStaticProps = async (context) => { +export const getStaticProps: GetStaticProps = async (context) => { const params = context.params! const { albums } = await getAlbums(params.gallery) @@ -41,6 +41,7 @@ export const getStaticProps: GetStaticProps = async (context) => album: albumName, corpus: [item.description, item.caption, item.location, item.city, item.search].join(' '), coordinateAccuracy: item.coordinateAccuracy ?? albumCoordinateAccuracy, + search: addGeographyToSearch(item), })) // reverse order for albums in ascending order (oldest on top) @@ -73,7 +74,7 @@ export const getStaticPaths: GetStaticPaths = async () => { } } -function Today({ items, indexedKeywords }: Props) { +function Today({ items, indexedKeywords }: ComponentProps) { return } diff --git a/next/pages/index.tsx b/next/pages/index.tsx index f90800eb..183ce649 100644 --- a/next/pages/index.tsx +++ b/next/pages/index.tsx @@ -6,11 +6,11 @@ import { Fragment } from 'react' import Link from '../src/components/Link' import getGalleries, { type Gallery } from '../src/lib/galleries' -type Props = { +type ComponentProps = { galleries: { id: Gallery; gallery: Gallery }[] } -export const getStaticProps: GetStaticProps = async () => { +export const getStaticProps: GetStaticProps = async () => { const { galleries } = await getGalleries() return { @@ -20,7 +20,7 @@ export const getStaticProps: GetStaticProps = async () => { } } -function Home({ galleries }: Props) { +function Home({ galleries }: ComponentProps) { return ( <> diff --git a/next/src/lib/search.ts b/next/src/lib/search.ts index b8b09ce1..5923074a 100644 --- a/next/src/lib/search.ts +++ b/next/src/lib/search.ts @@ -34,3 +34,10 @@ function indexKeywords(items: { search: Item['search'] | GalleryAlbum['search'] } export default indexKeywords + +export function addGeographyToSearch(item: Item) { + const hasComma = item.city.lastIndexOf(',') !== -1 + const country = hasComma ? item.city.substring(item.city.lastIndexOf(', ') + 1).trim() : item.city.trim() + + return item.search === null ? country : `${item.search}, ${country}` +} From ad0d764d0c2f998afc130be621164c91b7a0297e Mon Sep 17 00:00:00 2001 From: danactive Date: Sun, 5 Nov 2023 09:08:09 -0800 Subject: [PATCH 06/24] feat(Next > Search): Add search support to gallery search keyword --- next/pages/[gallery].tsx | 4 ++-- next/public/galleries/demo/gallery.xml | 1 + public/galleries/demo/gallery.xml | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/next/pages/[gallery].tsx b/next/pages/[gallery].tsx index 8df8bf4c..e7fded24 100644 --- a/next/pages/[gallery].tsx +++ b/next/pages/[gallery].tsx @@ -32,10 +32,10 @@ export const getStaticProps: GetStaticProps = async (con const { albums } = await getAlbums(params.gallery) const preparedAlbums = albums.map((album): ServerSideAlbumItem => ({ ...album, - corpus: [album.h1, album.h2, album.year].join(' '), + corpus: [album.h1, album.h2, album.year, album.search].join(' '), })) return { - props: { gallery: params.gallery, albums: preparedAlbums, ...indexKeywords(albums) }, + props: { gallery: params.gallery, albums: preparedAlbums, ...indexKeywords(preparedAlbums) }, } } diff --git a/next/public/galleries/demo/gallery.xml b/next/public/galleries/demo/gallery.xml index fa53d6d4..305e0966 100644 --- a/next/public/galleries/demo/gallery.xml +++ b/next/public/galleries/demo/gallery.xml @@ -8,5 +8,6 @@ British Columbia 2001-2005 2004-01-04-01.jpg + 2001, 2004, British Columbia diff --git a/public/galleries/demo/gallery.xml b/public/galleries/demo/gallery.xml index fa53d6d4..305e0966 100644 --- a/public/galleries/demo/gallery.xml +++ b/public/galleries/demo/gallery.xml @@ -8,5 +8,6 @@ British Columbia 2001-2005 2004-01-04-01.jpg + 2001, 2004, British Columbia From a538734d3b96c0605f2d3b35a1618cd188706153 Mon Sep 17 00:00:00 2001 From: danactive Date: Sun, 12 Nov 2023 15:18:37 -0800 Subject: [PATCH 07/24] feat(Next > Search): Sort by keyword count then name --- .../OrganizePreviews/ActionButtons.tsx | 2 +- next/src/lib/__tests__/albums.test.ts | 2 +- next/src/lib/__tests__/search.test.ts | 76 +++++++++++++++++++ next/src/lib/search.ts | 26 ++++--- 4 files changed, 95 insertions(+), 11 deletions(-) diff --git a/next/src/components/OrganizePreviews/ActionButtons.tsx b/next/src/components/OrganizePreviews/ActionButtons.tsx index c88bf16a..2de48ec1 100644 --- a/next/src/components/OrganizePreviews/ActionButtons.tsx +++ b/next/src/components/OrganizePreviews/ActionButtons.tsx @@ -31,7 +31,7 @@ export default function ActionButtons( }, method: 'POST', body: JSON.stringify(postBody), - }; + } /* curl -d '{"filenames":["a.jpg","b.jpg"], "prefix": "2020-06-13", "source_folder": "/todo/doit", "preview": "false", "raw": "true", "rename_associated": "true"}' diff --git a/next/src/lib/__tests__/albums.test.ts b/next/src/lib/__tests__/albums.test.ts index 327ec73a..a962b1f9 100644 --- a/next/src/lib/__tests__/albums.test.ts +++ b/next/src/lib/__tests__/albums.test.ts @@ -17,7 +17,7 @@ describe('Albums library', () => { version: '1.8', thumbPath: '/galleries/demo/media/thumbs/2004/2004-01-04-01.jpg', year: '2001-2005', - search: null, + search: '2001, 2004, British Columbia', }, ], } diff --git a/next/src/lib/__tests__/search.test.ts b/next/src/lib/__tests__/search.test.ts index 9d0148d6..f8893b00 100644 --- a/next/src/lib/__tests__/search.test.ts +++ b/next/src/lib/__tests__/search.test.ts @@ -98,4 +98,80 @@ describe('Search hook', () => { expect(actual.indexedKeywords).toStrictEqual(expected.indexedKeywords) }) }) + + describe('Numbers and alphabet', () => { + test('Sort by count for years', () => { + const keywordYear1 = '1999' + const keywordYear23 = '2023' + const keywordYear22 = '2022' + const actual = indexKeywords([{ + ...mockItem, + search: keywordYear23, + }, { + ...mockItem, + search: keywordYear1, + }, { + ...mockItem, + search: keywordYear23, + }, { + ...mockItem, + search: keywordYear1, + }, { + ...mockItem, + search: keywordYear1, + }, { + ...mockItem, + search: keywordYear22, + }, { + ...mockItem, + search: keywordYear22, + }, { + ...mockItem, + search: keywordYear22, + }, { + ...mockItem, + search: keywordYear23, + }]) + const expected = { + indexedKeywords: [ + { label: `${keywordYear23} (3)`, value: keywordYear23 }, + { label: `${keywordYear22} (3)`, value: keywordYear22 }, + { label: `${keywordYear1} (3)`, value: keywordYear1 }, + ], + } + expect(actual.indexedKeywords).toStrictEqual(expected.indexedKeywords) + }) + + test('Year latest, then asc', () => { + const keywordYear1 = '1999' + const keywordYear2 = '2023' + const keywordAlpha = 'Apple' + const keywordBeta = 'Banana' + const actual = indexKeywords([{ + ...mockItem, + search: keywordAlpha, + }, { + ...mockItem, + search: keywordYear1, + }, { + ...mockItem, + search: keywordBeta, + }, { + ...mockItem, + search: keywordAlpha, + }, { + ...mockItem, + search: keywordYear2, + }]) + const expected = { + indexedKeywords: [ + { label: `${keywordAlpha} (2)`, value: keywordAlpha }, + { label: `${keywordYear2} (1)`, value: keywordYear2 }, + { label: `${keywordYear1} (1)`, value: keywordYear1 }, + { label: `${keywordBeta} (1)`, value: keywordBeta }, + ], + } + expect(actual.indexedKeywords).toStrictEqual(expected.indexedKeywords) + }) + }) }) diff --git a/next/src/lib/search.ts b/next/src/lib/search.ts index 5923074a..fee8fcb1 100644 --- a/next/src/lib/search.ts +++ b/next/src/lib/search.ts @@ -16,18 +16,26 @@ function indexKeywords(items: { search: Item['search'] | GalleryAlbum['search'] return out }, {} as Record) - const sortedKeywords = Object.fromEntries( - Object.entries(summedKeywords).sort(([, a], [, b]) => { - if (b < a) return -1 - if (a < b) return 1 - return 0 - }), - ) + function isNum(n: string) { + return Number.isFinite(Number(n)) + } + + const sortedKeywords = Object.entries(summedKeywords).sort(([nameA, numA], [nameB, numB]) => { + if (numB - numA !== 0) { + return numB - numA + } + + if (isNum(nameA) && isNum(nameB)) { + return Number(nameB) - Number(nameA) + } + + return nameA.localeCompare(nameB) + }) // prepare for react-select in useSearch custom hook return { - indexedKeywords: Object.keys(sortedKeywords).map((key) => ({ - label: `${key} (${sortedKeywords[key]})`, + indexedKeywords: sortedKeywords.map(([key, count]) => ({ + label: `${key} (${count})`, value: key, })), } From 00920972ea331dcbf02fea8eef3c9e4dd1a574ca Mon Sep 17 00:00:00 2001 From: danactive Date: Sun, 12 Nov 2023 17:46:10 -0800 Subject: [PATCH 08/24] feat(Next > Today): Fix order of today items so morning to night still oldest album to recent --- next/pages/[gallery]/today.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/next/pages/[gallery]/today.tsx b/next/pages/[gallery]/today.tsx index dba71736..8cb14653 100644 --- a/next/pages/[gallery]/today.tsx +++ b/next/pages/[gallery]/today.tsx @@ -56,7 +56,8 @@ export const getStaticProps: GetStaticProps = async (con albumCoordinateAccuracy, items: itemsMatchDate, }) - return prevItems.concat(preparedItems) + // ensure the photos are morning to night + return prevItems.concat(preparedItems.reverse()) }, Promise.resolve([] as ServerSideTodayItem[]))).reverse() return { From 5d7eab202d80b8206cbdf1835826aeb45ac17462 Mon Sep 17 00:00:00 2001 From: danactive Date: Sun, 12 Nov 2023 19:27:39 -0800 Subject: [PATCH 09/24] todo whole word --- next/src/hooks/useSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/next/src/hooks/useSearch.tsx b/next/src/hooks/useSearch.tsx index f58496b8..992f6014 100644 --- a/next/src/hooks/useSearch.tsx +++ b/next/src/hooks/useSearch.tsx @@ -97,7 +97,7 @@ function useSearch( const corpusWithoutAccentLow = corpus.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase() return (k: string) => { const keywordWithoutAccentLow = k.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase() - return corpusWithoutAccentLow.indexOf(keywordWithoutAccentLow) !== -1 + return new RegExp(`\\b${keywordWithoutAccentLow}\\b`).test(corpusWithoutAccentLow) } } const filtered = items.filter((item) => { From 9884b4eb4e274d7df376428ec1c67651a8cde07e Mon Sep 17 00:00:00 2001 From: danactive Date: Sun, 12 Nov 2023 20:06:43 -0800 Subject: [PATCH 10/24] todo displays --- next/package-lock.json | 139 ++++++++++---------- next/package.json | 3 +- next/src/components/ComboBox.tsx | 212 +++++++++++++++++++++++++++++++ next/src/hooks/useSearch.tsx | 4 +- next/src/lib/search.ts | 2 +- 5 files changed, 282 insertions(+), 78 deletions(-) create mode 100644 next/src/components/ComboBox.tsx diff --git a/next/package-lock.json b/next/package-lock.json index 1bbc69f7..c08da0ba 100644 --- a/next/package-lock.json +++ b/next/package-lock.json @@ -12,7 +12,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@hello-pangea/dnd": "^16.3.0", - "@mui/joy": "^5.0.0-alpha.89", + "@mui/joy": "^5.0.0-beta.14", "@types/react-image-gallery": "^1.2.0", "@types/xml2js": "^0.4.12", "boom": "^7.3.0", @@ -1991,9 +1991,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2278,9 +2278,9 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", - "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", "dependencies": { "@floating-ui/dom": "^1.5.1" }, @@ -3046,19 +3046,17 @@ } }, "node_modules/@mui/base": { - "version": "5.0.0-beta.13", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.13.tgz", - "integrity": "sha512-uC0l97pBspfDAp+iz2cJq8YZ8Sd9i73V77+WzUiOAckIVEyCm5dyVDZCCO2/phmzckVEeZCGcytybkjMQuhPQw==", - "dependencies": { - "@babel/runtime": "^7.22.10", - "@emotion/is-prop-valid": "^1.2.1", - "@floating-ui/react-dom": "^2.0.1", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.7", + "version": "5.0.0-beta.23", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.23.tgz", + "integrity": "sha512-9L8SQUGAWtd/Qi7Qem26+oSSgpY7f2iQTuvcz/rsGpyZjSomMMO6lwYeQSA0CpWM7+aN7eGoSY/WV6wxJiIxXw==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@floating-ui/react-dom": "^2.0.2", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.17", "@popperjs/core": "^2.11.8", "clsx": "^2.0.0", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "prop-types": "^15.8.1" }, "engines": { "node": ">=12.0.0" @@ -3079,29 +3077,27 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.14.7", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.7.tgz", - "integrity": "sha512-sCWTUNElBPgB30iLvWe3PU7SIlTKZNf6/E/sko85iHVeHCM6WPkDw+y89CrZYjhFNmPqt2fIQM/pZu+rP2lFLA==", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.17.tgz", + "integrity": "sha512-eE0uxrpJAEL2ZXkeGLKg8HQDafsiXY+6eNpP4lcv3yIjFfGbU6Hj9/P7Adt8jpU+6JIhmxvILGj2r27pX+zdrQ==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui" } }, "node_modules/@mui/joy": { - "version": "5.0.0-beta.4", - "resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.4.tgz", - "integrity": "sha512-/Vrwkc2juRBSTa+Wqkd4CxidGi2FvVar9ypvhnalODyyhL019ikhTcMqgD/1EqBXOhTZDoptPq2WyEIUlldjqw==", - "dependencies": { - "@babel/runtime": "^7.22.10", - "@mui/base": "5.0.0-beta.13", - "@mui/core-downloads-tracker": "^5.14.7", - "@mui/system": "^5.14.7", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.7", + "version": "5.0.0-beta.14", + "resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.14.tgz", + "integrity": "sha512-5N4tHgla/YGLRArcDb2GIgz8keXGXOeIdFVyP3hW1IZac0xJpiOuQNelxG9CVcZfEEAZ2V3r22ai7O2yq/8q+A==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/base": "5.0.0-beta.23", + "@mui/core-downloads-tracker": "^5.14.17", + "@mui/system": "^5.14.17", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.17", "clsx": "^2.0.0", - "csstype": "^3.1.2", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "prop-types": "^15.8.1" }, "engines": { "node": ">=12.0.0" @@ -3130,12 +3126,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.14.7", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.7.tgz", - "integrity": "sha512-Y86+hmDnJab2Ka42PgxKpK3oL7EiacbeeX3X/lG9LGO0wSc45wZjHeTfIlVSkkUCkexiMKEJp5NlSjZhr27NRQ==", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.17.tgz", + "integrity": "sha512-u4zxsCm9xmQrlhVPug+Ccrtsjv7o2+rehvrgHoh0siSguvVgVQq5O3Hh10+tp/KWQo2JR4/nCEwquSXgITS1+g==", "dependencies": { - "@babel/runtime": "^7.22.10", - "@mui/utils": "^5.14.7", + "@babel/runtime": "^7.23.2", + "@mui/utils": "^5.14.17", "prop-types": "^15.8.1" }, "engines": { @@ -3156,11 +3152,11 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.14.7", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.7.tgz", - "integrity": "sha512-hKBETEDsIAkL8/mBwPiQj/vw28OeIhMXC3Tvj4J2bb9snxAKpiZioR1PwqP+6P41twsC/GKBd0Vr9oaWYaHuMg==", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.17.tgz", + "integrity": "sha512-AqpVjBEA7wnBvKPW168bNlqB6EN7HxTjLOY7oi275AzD/b1C7V0wqELy6NWoJb2yya5sRf7ENf4iNi3+T5cOgw==", "dependencies": { - "@babel/runtime": "^7.22.10", + "@babel/runtime": "^7.23.2", "@emotion/cache": "^11.11.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" @@ -3187,15 +3183,15 @@ } }, "node_modules/@mui/system": { - "version": "5.14.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.7.tgz", - "integrity": "sha512-jeZtHglc+Pi6qjGoopT6O4RqYXVBMqHVOsjMGP0hxGSSPm1T4gsAu7jU8eqGx9YwwjvvJ0eotTjFqw7iJ6qE2Q==", - "dependencies": { - "@babel/runtime": "^7.22.10", - "@mui/private-theming": "^5.14.7", - "@mui/styled-engine": "^5.14.7", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.7", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.17.tgz", + "integrity": "sha512-Ccz3XlbCqka6DnbHfpL3o3TfOeWQPR+ewvNAgm8gnS9M0yVMmzzmY6z0w/C1eebb+7ZP7IoLUj9vojg/GBaTPg==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/private-theming": "^5.14.17", + "@mui/styled-engine": "^5.14.17", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.17", "clsx": "^2.0.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" @@ -3226,11 +3222,11 @@ } }, "node_modules/@mui/types": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", - "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.8.tgz", + "integrity": "sha512-9u0ji+xspl96WPqvrYJF/iO+1tQ1L5GTaDOeG3vCR893yy7VcWwRNiVMmPdPNpMDqx0WV1wtEW9OMwK9acWJzQ==", "peerDependencies": { - "@types/react": "*" + "@types/react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -3239,13 +3235,12 @@ } }, "node_modules/@mui/utils": { - "version": "5.14.7", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.7.tgz", - "integrity": "sha512-RtheP/aBoPogVdi8vj8Vo2IFnRa4mZVmnD0RGlVZ49yF60rZs+xP4/KbpIrTr83xVs34QmHQ2aQ+IX7I0a0dDw==", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.17.tgz", + "integrity": "sha512-yxnWgSS4J6DMFPw2Dof85yBkG02VTbEiqsikymMsnZnXDurtVGTIhlNuV24GTmFTuJMzEyTTU9UF+O7zaL8LEQ==", "dependencies": { - "@babel/runtime": "^7.22.10", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^18.2.1", + "@babel/runtime": "^7.23.2", + "@types/prop-types": "^15.7.9", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -3257,7 +3252,13 @@ "url": "https://opencollective.com/mui" }, "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@next/env": { @@ -3970,9 +3971,9 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.10", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.10.tgz", + "integrity": "sha512-mxSnDQxPqsZxmeShFH+uwQ4kO4gcJcGahjjMFeLbKE95IAZiiZyiEepGZjtXJ7hN/yfu0bu9xN2ajcU0JcxX6A==" }, "node_modules/@types/react": { "version": "18.2.21", @@ -4001,14 +4002,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-is": { - "version": "18.2.1", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.1.tgz", - "integrity": "sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-transition-group": { "version": "4.4.6", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", diff --git a/next/package.json b/next/package.json index d3b06fa7..d334e85d 100644 --- a/next/package.json +++ b/next/package.json @@ -19,7 +19,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@hello-pangea/dnd": "^16.3.0", - "@mui/joy": "^5.0.0-alpha.89", + "@mui/joy": "^5.0.0-beta.14", "@types/react-image-gallery": "^1.2.0", "@types/xml2js": "^0.4.12", "boom": "^7.3.0", @@ -33,7 +33,6 @@ "react-dom": "^18.2.0", "react-image-gallery": "^1.3.0", "react-map-gl": "^7.1.5", - "react-select": "^5.7.4", "styled-components": "^6.0.7", "stylis": "^4.3.0", "xml2js": "^0.6.2" diff --git a/next/src/components/ComboBox.tsx b/next/src/components/ComboBox.tsx new file mode 100644 index 00000000..5e9d7c92 --- /dev/null +++ b/next/src/components/ComboBox.tsx @@ -0,0 +1,212 @@ +import * as React from 'react' +import FormControl from '@mui/joy/FormControl' +import FormLabel from '@mui/joy/FormLabel' +import Autocomplete, { createFilterOptions } from '@mui/joy/Autocomplete' +import AutocompleteOption from '@mui/joy/AutocompleteOption' +import ListItemDecorator from '@mui/joy/ListItemDecorator' + +interface FilmOptionType { + inputValue?: string; + title: string; + year?: number; +} + +const filter = createFilterOptions() + +export default function FreeSoloCreateOption() { + const [value, setValue] = React.useState(null) + + return ( + + Free solo with text demo + { + if (typeof newValue === 'string') { + setValue({ + title: newValue, + }) + } else if (newValue && newValue.inputValue) { + // Create a new value from the user input + setValue({ + title: newValue.inputValue, + }) + } else { + setValue(newValue) + } + }} + filterOptions={(options, params) => { + const filtered = filter(options, params) + + const { inputValue } = params + // Suggest the creation of a new value + const isExisting = options.some((option) => inputValue === option.title) + if (inputValue !== '' && !isExisting) { + filtered.push({ + inputValue, + title: `Add "${inputValue}"`, + }) + } + + return filtered + }} + selectOnFocus + clearOnBlur + handleHomeEndKeys + freeSolo + options={top100Films} + getOptionLabel={(option) => { + // Value selected with enter, right from the input + if (typeof option === 'string') { + return option + } + // Add "xxx" option created dynamically + if (option.inputValue) { + return option.inputValue + } + // Regular option + return option.title + }} + renderOption={(props, option) => ( + + {option.title?.startsWith('Add "') && ( + + Add icon + + )} + {option.title} + + )} + sx={{ width: 300 }} + /> + + ) +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films: readonly FilmOptionType[] = [ + { title: 'The Shawshank Redemption', year: 1994 }, + { title: 'The Godfather', year: 1972 }, + { title: 'The Godfather: Part II', year: 1974 }, + { title: 'The Dark Knight', year: 2008 }, + { title: '12 Angry Men', year: 1957 }, + { title: "Schindler's List", year: 1993 }, + { title: 'Pulp Fiction', year: 1994 }, + { + title: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { title: 'The Good, the Bad and the Ugly', year: 1966 }, + { title: 'Fight Club', year: 1999 }, + { + title: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + title: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { title: 'Forrest Gump', year: 1994 }, + { title: 'Inception', year: 2010 }, + { + title: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { title: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { title: 'Goodfellas', year: 1990 }, + { title: 'The Matrix', year: 1999 }, + { title: 'Seven Samurai', year: 1954 }, + { + title: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { title: 'City of God', year: 2002 }, + { title: 'Se7en', year: 1995 }, + { title: 'The Silence of the Lambs', year: 1991 }, + { title: "It's a Wonderful Life", year: 1946 }, + { title: 'Life Is Beautiful', year: 1997 }, + { title: 'The Usual Suspects', year: 1995 }, + { title: 'Léon: The Professional', year: 1994 }, + { title: 'Spirited Away', year: 2001 }, + { title: 'Saving Private Ryan', year: 1998 }, + { title: 'Once Upon a Time in the West', year: 1968 }, + { title: 'American History X', year: 1998 }, + { title: 'Interstellar', year: 2014 }, + { title: 'Casablanca', year: 1942 }, + { title: 'City Lights', year: 1931 }, + { title: 'Psycho', year: 1960 }, + { title: 'The Green Mile', year: 1999 }, + { title: 'The Intouchables', year: 2011 }, + { title: 'Modern Times', year: 1936 }, + { title: 'Raiders of the Lost Ark', year: 1981 }, + { title: 'Rear Window', year: 1954 }, + { title: 'The Pianist', year: 2002 }, + { title: 'The Departed', year: 2006 }, + { title: 'Terminator 2: Judgment Day', year: 1991 }, + { title: 'Back to the Future', year: 1985 }, + { title: 'Whiplash', year: 2014 }, + { title: 'Gladiator', year: 2000 }, + { title: 'Memento', year: 2000 }, + { title: 'The Prestige', year: 2006 }, + { title: 'The Lion King', year: 1994 }, + { title: 'Apocalypse Now', year: 1979 }, + { title: 'Alien', year: 1979 }, + { title: 'Sunset Boulevard', year: 1950 }, + { + title: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { title: 'The Great Dictator', year: 1940 }, + { title: 'Cinema Paradiso', year: 1988 }, + { title: 'The Lives of Others', year: 2006 }, + { title: 'Grave of the Fireflies', year: 1988 }, + { title: 'Paths of Glory', year: 1957 }, + { title: 'Django Unchained', year: 2012 }, + { title: 'The Shining', year: 1980 }, + { title: 'WALL·E', year: 2008 }, + { title: 'American Beauty', year: 1999 }, + { title: 'The Dark Knight Rises', year: 2012 }, + { title: 'Princess Mononoke', year: 1997 }, + { title: 'Aliens', year: 1986 }, + { title: 'Oldboy', year: 2003 }, + { title: 'Once Upon a Time in America', year: 1984 }, + { title: 'Witness for the Prosecution', year: 1957 }, + { title: 'Das Boot', year: 1981 }, + { title: 'Citizen Kane', year: 1941 }, + { title: 'North by Northwest', year: 1959 }, + { title: 'Vertigo', year: 1958 }, + { + title: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { title: 'Reservoir Dogs', year: 1992 }, + { title: 'Braveheart', year: 1995 }, + { title: 'M', year: 1931 }, + { title: 'Requiem for a Dream', year: 2000 }, + { title: 'Amélie', year: 2001 }, + { title: 'A Clockwork Orange', year: 1971 }, + { title: 'Like Stars on Earth', year: 2007 }, + { title: 'Taxi Driver', year: 1976 }, + { title: 'Lawrence of Arabia', year: 1962 }, + { title: 'Double Indemnity', year: 1944 }, + { + title: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { title: 'Amadeus', year: 1984 }, + { title: 'To Kill a Mockingbird', year: 1962 }, + { title: 'Toy Story 3', year: 2010 }, + { title: 'Logan', year: 2017 }, + { title: 'Full Metal Jacket', year: 1987 }, + { title: 'Dangal', year: 2016 }, + { title: 'The Sting', year: 1973 }, + { title: '2001: A Space Odyssey', year: 1968 }, + { title: "Singin' in the Rain", year: 1952 }, + { title: 'Toy Story', year: 1995 }, + { title: 'Bicycle Thieves', year: 1948 }, + { title: 'The Kid', year: 1921 }, + { title: 'Inglourious Basterds', year: 2009 }, + { title: 'Snatch', year: 2000 }, + { title: '3 Idiots', year: 2009 }, + { title: 'Monty Python and the Holy Grail', year: 1975 }, +] diff --git a/next/src/hooks/useSearch.tsx b/next/src/hooks/useSearch.tsx index 992f6014..ea277bd9 100644 --- a/next/src/hooks/useSearch.tsx +++ b/next/src/hooks/useSearch.tsx @@ -1,7 +1,7 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' -import Select from 'react-select/creatable' import styled from 'styled-components' +import ComboBox from '../components/ComboBox' interface ServerSideItem { corpus: string; @@ -11,7 +11,7 @@ const Row = styled.div` display: flex; align-items: center; ` -const AutoComplete = styled(Select)` +const AutoComplete = styled(ComboBox)` width: 20rem; ` const SearchCount = styled.h3` diff --git a/next/src/lib/search.ts b/next/src/lib/search.ts index fee8fcb1..7ba257e3 100644 --- a/next/src/lib/search.ts +++ b/next/src/lib/search.ts @@ -32,7 +32,7 @@ function indexKeywords(items: { search: Item['search'] | GalleryAlbum['search'] return nameA.localeCompare(nameB) }) - // prepare for react-select in useSearch custom hook + // prepare for combo box in useSearch custom hook return { indexedKeywords: sortedKeywords.map(([key, count]) => ({ label: `${key} (${count})`, From f4e00be3815bd90e2fc82cc047f54b25ca5cb8b1 Mon Sep 17 00:00:00 2001 From: danactive Date: Mon, 13 Nov 2023 17:21:48 -0800 Subject: [PATCH 11/24] rename --- next/pages/[gallery].tsx | 4 +- next/pages/[gallery]/[album].tsx | 4 +- next/pages/[gallery]/all.tsx | 4 +- next/pages/[gallery]/today.tsx | 4 +- next/src/components/AlbumPage/index.tsx | 4 +- next/src/components/ComboBox.tsx | 234 ++++++++++++------------ next/src/hooks/useSearch.tsx | 12 +- next/src/lib/search.ts | 12 +- next/src/types/common.d.ts | 6 + 9 files changed, 143 insertions(+), 141 deletions(-) diff --git a/next/pages/[gallery].tsx b/next/pages/[gallery].tsx index e7fded24..8624d8ed 100644 --- a/next/pages/[gallery].tsx +++ b/next/pages/[gallery].tsx @@ -11,7 +11,7 @@ import indexKeywords from '../src/lib/search' import Img from '../src/components/Img' import Link from '../src/components/Link' import useSearch from '../src/hooks/useSearch' -import type { AlbumMeta, GalleryAlbum } from '../src/types/common' +import type { AlbumMeta, GalleryAlbum, IndexedKeywords } from '../src/types/common' interface ServerSideAlbumItem extends GalleryAlbum { corpus: string; @@ -20,7 +20,7 @@ interface ServerSideAlbumItem extends GalleryAlbum { type ComponentProps = { gallery: NonNullable; albums: ServerSideAlbumItem[]; - indexedKeywords: object[]; + indexedKeywords: IndexedKeywords[]; } interface Params extends ParsedUrlQuery { diff --git a/next/pages/[gallery]/[album].tsx b/next/pages/[gallery]/[album].tsx index 367c76bf..753eedde 100644 --- a/next/pages/[gallery]/[album].tsx +++ b/next/pages/[gallery]/[album].tsx @@ -6,7 +6,7 @@ import getAlbum from '../../src/lib/album' import getAlbums from '../../src/lib/albums' import getGalleries from '../../src/lib/galleries' import indexKeywords, { addGeographyToSearch } from '../../src/lib/search' -import type { AlbumMeta, Item } from '../../src/types/common' +import type { AlbumMeta, IndexedKeywords, Item } from '../../src/types/common' async function buildStaticPaths() { const { galleries } = await getGalleries() @@ -24,7 +24,7 @@ interface ServerSideAlbumItem extends Item { type ComponentProps = { items?: ServerSideAlbumItem[]; meta: AlbumMeta; - indexedKeywords: object[]; + indexedKeywords: IndexedKeywords[]; } interface Params extends ParsedUrlQuery { diff --git a/next/pages/[gallery]/all.tsx b/next/pages/[gallery]/all.tsx index de5b9032..b6ecc04f 100644 --- a/next/pages/[gallery]/all.tsx +++ b/next/pages/[gallery]/all.tsx @@ -18,7 +18,7 @@ import SplitViewer from '../../src/components/SplitViewer' import useMemory from '../../src/hooks/useMemory' import useSearch from '../../src/hooks/useSearch' -import { AlbumMeta, Item } from '../../src/types/common' +import { AlbumMeta, IndexedKeywords, Item } from '../../src/types/common' const AlbumName = styled.b` margin-right: 1rem; @@ -36,7 +36,7 @@ interface ServerSideAllItem extends Item { type ComponentProps = { items: ServerSideAllItem[]; - indexedKeywords: object[]; + indexedKeywords: IndexedKeywords[]; } interface Params extends ParsedUrlQuery { diff --git a/next/pages/[gallery]/today.tsx b/next/pages/[gallery]/today.tsx index 8cb14653..1a22e2d2 100644 --- a/next/pages/[gallery]/today.tsx +++ b/next/pages/[gallery]/today.tsx @@ -8,7 +8,7 @@ import getGalleries from '../../src/lib/galleries' import indexKeywords, { addGeographyToSearch } from '../../src/lib/search' import AlbumPageComponent from '../../src/components/AlbumPage' -import type { AlbumMeta, Item } from '../../src/types/common' +import type { AlbumMeta, IndexedKeywords, Item } from '../../src/types/common' interface ServerSideTodayItem extends Item { album?: NonNullable; @@ -18,7 +18,7 @@ interface ServerSideTodayItem extends Item { type ComponentProps = { items: ServerSideTodayItem[]; - indexedKeywords: object[]; + indexedKeywords: IndexedKeywords[]; } interface Params extends ParsedUrlQuery { diff --git a/next/src/components/AlbumPage/index.tsx b/next/src/components/AlbumPage/index.tsx index b3583a1f..ac20b9f2 100644 --- a/next/src/components/AlbumPage/index.tsx +++ b/next/src/components/AlbumPage/index.tsx @@ -9,7 +9,7 @@ import AlbumContext from '../Context' import SplitViewer from '../SplitViewer' import ThumbImg from '../ThumbImg' -import type { Item } from '../../types/common' +import type { IndexedKeywords, Item } from '../../types/common' interface ServerSidePhotoItem extends Item { corpus: string; @@ -22,7 +22,7 @@ const Wrapper = styled.ul` function AlbumPage( { items = [], meta, indexedKeywords }: - { items: ServerSidePhotoItem[], meta?: object, indexedKeywords: object[] }, + { items: ServerSidePhotoItem[], meta?: object, indexedKeywords: IndexedKeywords[] }, ) { const refImageGallery = useRef(null) const [memoryIndex, setMemoryIndex] = useState(0) diff --git a/next/src/components/ComboBox.tsx b/next/src/components/ComboBox.tsx index 5e9d7c92..9da3b6ee 100644 --- a/next/src/components/ComboBox.tsx +++ b/next/src/components/ComboBox.tsx @@ -7,8 +7,8 @@ import ListItemDecorator from '@mui/joy/ListItemDecorator' interface FilmOptionType { inputValue?: string; - title: string; - year?: number; + label: string; + value?: number; } const filter = createFilterOptions() @@ -24,12 +24,12 @@ export default function FreeSoloCreateOption() { onChange={(event, newValue) => { if (typeof newValue === 'string') { setValue({ - title: newValue, + label: newValue, }) } else if (newValue && newValue.inputValue) { // Create a new value from the user input setValue({ - title: newValue.inputValue, + label: newValue.inputValue, }) } else { setValue(newValue) @@ -40,11 +40,11 @@ export default function FreeSoloCreateOption() { const { inputValue } = params // Suggest the creation of a new value - const isExisting = options.some((option) => inputValue === option.title) + const isExisting = options.some((option) => inputValue === option.label) if (inputValue !== '' && !isExisting) { filtered.push({ inputValue, - title: `Add "${inputValue}"`, + label: `Add "${inputValue}"`, }) } @@ -65,16 +65,16 @@ export default function FreeSoloCreateOption() { return option.inputValue } // Regular option - return option.title + return option.label }} renderOption={(props, option) => ( - {option.title?.startsWith('Add "') && ( + {option.label?.startsWith('Add "') && ( Add icon )} - {option.title} + {option.label} )} sx={{ width: 300 }} @@ -85,128 +85,128 @@ export default function FreeSoloCreateOption() { // Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top const top100Films: readonly FilmOptionType[] = [ - { title: 'The Shawshank Redemption', year: 1994 }, - { title: 'The Godfather', year: 1972 }, - { title: 'The Godfather: Part II', year: 1974 }, - { title: 'The Dark Knight', year: 2008 }, - { title: '12 Angry Men', year: 1957 }, - { title: "Schindler's List", year: 1993 }, - { title: 'Pulp Fiction', year: 1994 }, + { label: 'The Shawshank Redemption', value: 1994 }, + { label: 'The Godfather', value: 1972 }, + { label: 'The Godfather: Part II', value: 1974 }, + { label: 'The Dark Knight', value: 2008 }, + { label: '12 Angry Men', value: 1957 }, + { label: "Schindler's List", value: 1993 }, + { label: 'Pulp Fiction', value: 1994 }, { - title: 'The Lord of the Rings: The Return of the King', - year: 2003, + label: 'The Lord of the Rings: The Return of the King', + value: 2003, }, - { title: 'The Good, the Bad and the Ugly', year: 1966 }, - { title: 'Fight Club', year: 1999 }, + { label: 'The Good, the Bad and the Ugly', value: 1966 }, + { label: 'Fight Club', value: 1999 }, { - title: 'The Lord of the Rings: The Fellowship of the Ring', - year: 2001, + label: 'The Lord of the Rings: The Fellowship of the Ring', + value: 2001, }, { - title: 'Star Wars: Episode V - The Empire Strikes Back', - year: 1980, + label: 'Star Wars: Episode V - The Empire Strikes Back', + value: 1980, }, - { title: 'Forrest Gump', year: 1994 }, - { title: 'Inception', year: 2010 }, + { label: 'Forrest Gump', value: 1994 }, + { label: 'Inception', value: 2010 }, { - title: 'The Lord of the Rings: The Two Towers', - year: 2002, + label: 'The Lord of the Rings: The Two Towers', + value: 2002, }, - { title: "One Flew Over the Cuckoo's Nest", year: 1975 }, - { title: 'Goodfellas', year: 1990 }, - { title: 'The Matrix', year: 1999 }, - { title: 'Seven Samurai', year: 1954 }, + { label: "One Flew Over the Cuckoo's Nest", value: 1975 }, + { label: 'Goodfellas', value: 1990 }, + { label: 'The Matrix', value: 1999 }, + { label: 'Seven Samurai', value: 1954 }, { - title: 'Star Wars: Episode IV - A New Hope', - year: 1977, + label: 'Star Wars: Episode IV - A New Hope', + value: 1977, }, - { title: 'City of God', year: 2002 }, - { title: 'Se7en', year: 1995 }, - { title: 'The Silence of the Lambs', year: 1991 }, - { title: "It's a Wonderful Life", year: 1946 }, - { title: 'Life Is Beautiful', year: 1997 }, - { title: 'The Usual Suspects', year: 1995 }, - { title: 'Léon: The Professional', year: 1994 }, - { title: 'Spirited Away', year: 2001 }, - { title: 'Saving Private Ryan', year: 1998 }, - { title: 'Once Upon a Time in the West', year: 1968 }, - { title: 'American History X', year: 1998 }, - { title: 'Interstellar', year: 2014 }, - { title: 'Casablanca', year: 1942 }, - { title: 'City Lights', year: 1931 }, - { title: 'Psycho', year: 1960 }, - { title: 'The Green Mile', year: 1999 }, - { title: 'The Intouchables', year: 2011 }, - { title: 'Modern Times', year: 1936 }, - { title: 'Raiders of the Lost Ark', year: 1981 }, - { title: 'Rear Window', year: 1954 }, - { title: 'The Pianist', year: 2002 }, - { title: 'The Departed', year: 2006 }, - { title: 'Terminator 2: Judgment Day', year: 1991 }, - { title: 'Back to the Future', year: 1985 }, - { title: 'Whiplash', year: 2014 }, - { title: 'Gladiator', year: 2000 }, - { title: 'Memento', year: 2000 }, - { title: 'The Prestige', year: 2006 }, - { title: 'The Lion King', year: 1994 }, - { title: 'Apocalypse Now', year: 1979 }, - { title: 'Alien', year: 1979 }, - { title: 'Sunset Boulevard', year: 1950 }, + { label: 'City of God', value: 2002 }, + { label: 'Se7en', value: 1995 }, + { label: 'The Silence of the Lambs', value: 1991 }, + { label: "It's a Wonderful Life", value: 1946 }, + { label: 'Life Is Beautiful', value: 1997 }, + { label: 'The Usual Suspects', value: 1995 }, + { label: 'Léon: The Professional', value: 1994 }, + { label: 'Spirited Away', value: 2001 }, + { label: 'Saving Private Ryan', value: 1998 }, + { label: 'Once Upon a Time in the West', value: 1968 }, + { label: 'American History X', value: 1998 }, + { label: 'Interstellar', value: 2014 }, + { label: 'Casablanca', value: 1942 }, + { label: 'City Lights', value: 1931 }, + { label: 'Psycho', value: 1960 }, + { label: 'The Green Mile', value: 1999 }, + { label: 'The Intouchables', value: 2011 }, + { label: 'Modern Times', value: 1936 }, + { label: 'Raiders of the Lost Ark', value: 1981 }, + { label: 'Rear Window', value: 1954 }, + { label: 'The Pianist', value: 2002 }, + { label: 'The Departed', value: 2006 }, + { label: 'Terminator 2: Judgment Day', value: 1991 }, + { label: 'Back to the Future', value: 1985 }, + { label: 'Whiplash', value: 2014 }, + { label: 'Gladiator', value: 2000 }, + { label: 'Memento', value: 2000 }, + { label: 'The Prestige', value: 2006 }, + { label: 'The Lion King', value: 1994 }, + { label: 'Apocalypse Now', value: 1979 }, + { label: 'Alien', value: 1979 }, + { label: 'Sunset Boulevard', value: 1950 }, { - title: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', - year: 1964, + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + value: 1964, }, - { title: 'The Great Dictator', year: 1940 }, - { title: 'Cinema Paradiso', year: 1988 }, - { title: 'The Lives of Others', year: 2006 }, - { title: 'Grave of the Fireflies', year: 1988 }, - { title: 'Paths of Glory', year: 1957 }, - { title: 'Django Unchained', year: 2012 }, - { title: 'The Shining', year: 1980 }, - { title: 'WALL·E', year: 2008 }, - { title: 'American Beauty', year: 1999 }, - { title: 'The Dark Knight Rises', year: 2012 }, - { title: 'Princess Mononoke', year: 1997 }, - { title: 'Aliens', year: 1986 }, - { title: 'Oldboy', year: 2003 }, - { title: 'Once Upon a Time in America', year: 1984 }, - { title: 'Witness for the Prosecution', year: 1957 }, - { title: 'Das Boot', year: 1981 }, - { title: 'Citizen Kane', year: 1941 }, - { title: 'North by Northwest', year: 1959 }, - { title: 'Vertigo', year: 1958 }, + { label: 'The Great Dictator', value: 1940 }, + { label: 'Cinema Paradiso', value: 1988 }, + { label: 'The Lives of Others', value: 2006 }, + { label: 'Grave of the Fireflies', value: 1988 }, + { label: 'Paths of Glory', value: 1957 }, + { label: 'Django Unchained', value: 2012 }, + { label: 'The Shining', value: 1980 }, + { label: 'WALL·E', value: 2008 }, + { label: 'American Beauty', value: 1999 }, + { label: 'The Dark Knight Rises', value: 2012 }, + { label: 'Princess Mononoke', value: 1997 }, + { label: 'Aliens', value: 1986 }, + { label: 'Oldboy', value: 2003 }, + { label: 'Once Upon a Time in America', value: 1984 }, + { label: 'Witness for the Prosecution', value: 1957 }, + { label: 'Das Boot', value: 1981 }, + { label: 'Citizen Kane', value: 1941 }, + { label: 'North by Northwest', value: 1959 }, + { label: 'Vertigo', value: 1958 }, { - title: 'Star Wars: Episode VI - Return of the Jedi', - year: 1983, + label: 'Star Wars: Episode VI - Return of the Jedi', + value: 1983, }, - { title: 'Reservoir Dogs', year: 1992 }, - { title: 'Braveheart', year: 1995 }, - { title: 'M', year: 1931 }, - { title: 'Requiem for a Dream', year: 2000 }, - { title: 'Amélie', year: 2001 }, - { title: 'A Clockwork Orange', year: 1971 }, - { title: 'Like Stars on Earth', year: 2007 }, - { title: 'Taxi Driver', year: 1976 }, - { title: 'Lawrence of Arabia', year: 1962 }, - { title: 'Double Indemnity', year: 1944 }, + { label: 'Reservoir Dogs', value: 1992 }, + { label: 'Braveheart', value: 1995 }, + { label: 'M', value: 1931 }, + { label: 'Requiem for a Dream', value: 2000 }, + { label: 'Amélie', value: 2001 }, + { label: 'A Clockwork Orange', value: 1971 }, + { label: 'Like Stars on Earth', value: 2007 }, + { label: 'Taxi Driver', value: 1976 }, + { label: 'Lawrence of Arabia', value: 1962 }, + { label: 'Double Indemnity', value: 1944 }, { - title: 'Eternal Sunshine of the Spotless Mind', - year: 2004, + label: 'Eternal Sunshine of the Spotless Mind', + value: 2004, }, - { title: 'Amadeus', year: 1984 }, - { title: 'To Kill a Mockingbird', year: 1962 }, - { title: 'Toy Story 3', year: 2010 }, - { title: 'Logan', year: 2017 }, - { title: 'Full Metal Jacket', year: 1987 }, - { title: 'Dangal', year: 2016 }, - { title: 'The Sting', year: 1973 }, - { title: '2001: A Space Odyssey', year: 1968 }, - { title: "Singin' in the Rain", year: 1952 }, - { title: 'Toy Story', year: 1995 }, - { title: 'Bicycle Thieves', year: 1948 }, - { title: 'The Kid', year: 1921 }, - { title: 'Inglourious Basterds', year: 2009 }, - { title: 'Snatch', year: 2000 }, - { title: '3 Idiots', year: 2009 }, - { title: 'Monty Python and the Holy Grail', year: 1975 }, + { label: 'Amadeus', value: 1984 }, + { label: 'To Kill a Mockingbird', value: 1962 }, + { label: 'Toy Story 3', value: 2010 }, + { label: 'Logan', value: 2017 }, + { label: 'Full Metal Jacket', value: 1987 }, + { label: 'Dangal', value: 2016 }, + { label: 'The Sting', value: 1973 }, + { label: '2001: A Space Odyssey', value: 1968 }, + { label: "Singin' in the Rain", value: 1952 }, + { label: 'Toy Story', value: 1995 }, + { label: 'Bicycle Thieves', value: 1948 }, + { label: 'The Kid', value: 1921 }, + { label: 'Inglourious Basterds', value: 2009 }, + { label: 'Snatch', value: 2000 }, + { label: '3 Idiots', value: 2009 }, + { label: 'Monty Python and the Holy Grail', value: 1975 }, ] diff --git a/next/src/hooks/useSearch.tsx b/next/src/hooks/useSearch.tsx index ea277bd9..7a394aa1 100644 --- a/next/src/hooks/useSearch.tsx +++ b/next/src/hooks/useSearch.tsx @@ -2,6 +2,7 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import styled from 'styled-components' import ComboBox from '../components/ComboBox' +import { IndexedKeywords } from '../types/common'; interface ServerSideItem { corpus: string; @@ -28,7 +29,7 @@ interface ReactSelectNewValue { function useSearch( { items, setMemoryIndex, indexedKeywords }: - { items: ItemType[]; setMemoryIndex?: Function; indexedKeywords: object[] }, + { items: ItemType[]; setMemoryIndex?: Function; indexedKeywords: IndexedKeywords[] }, ): { filtered: ItemType[]; keyword: string; setKeyword: Function; searchBox: JSX.Element; } { const router = useRouter() const [keyword, setKeyword] = useState(router.query.keyword?.toString() || '') @@ -55,14 +56,7 @@ function useSearch(
Search results {filtered?.length} of {items?.length}{keywordResultLabel} - + {getShareUrlStem()} diff --git a/next/src/lib/search.ts b/next/src/lib/search.ts index 7ba257e3..9a32ad1c 100644 --- a/next/src/lib/search.ts +++ b/next/src/lib/search.ts @@ -1,4 +1,4 @@ -import type { GalleryAlbum, Item } from '../types/common' +import type { GalleryAlbum, IndexedKeywords, Item } from '../types/common' /** * Index search keywords from search xml element and dedupe @@ -32,12 +32,14 @@ function indexKeywords(items: { search: Item['search'] | GalleryAlbum['search'] return nameA.localeCompare(nameB) }) + const indexedKeywords: IndexedKeywords[] = sortedKeywords.map(([key, count]) => ({ + label: `${key} (${count})`, + value: key, + })) + // prepare for combo box in useSearch custom hook return { - indexedKeywords: sortedKeywords.map(([key, count]) => ({ - label: `${key} (${count})`, - value: key, - })), + indexedKeywords, } } diff --git a/next/src/types/common.d.ts b/next/src/types/common.d.ts index ba16153d..8b31104a 100644 --- a/next/src/types/common.d.ts +++ b/next/src/types/common.d.ts @@ -100,6 +100,11 @@ type GalleryAlbum = { search: string | null; } +type IndexedKeywords = { + label: string; + value: string; +} + // https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/54659 declare module 'react-image-gallery' { interface ReactImageGalleryProps { @@ -137,4 +142,5 @@ export type { XmlAlbum, Item, ItemReferenceSource, + IndexedKeywords, } From 9aef3d500b55de5436ed7af5e434e02cfc5eb4c8 Mon Sep 17 00:00:00 2001 From: danactive Date: Mon, 13 Nov 2023 18:47:16 -0800 Subject: [PATCH 12/24] swap data set --- next/src/components/ComboBox.tsx | 136 +------------------------------ 1 file changed, 4 insertions(+), 132 deletions(-) diff --git a/next/src/components/ComboBox.tsx b/next/src/components/ComboBox.tsx index 9da3b6ee..f315fa6e 100644 --- a/next/src/components/ComboBox.tsx +++ b/next/src/components/ComboBox.tsx @@ -4,16 +4,16 @@ import FormLabel from '@mui/joy/FormLabel' import Autocomplete, { createFilterOptions } from '@mui/joy/Autocomplete' import AutocompleteOption from '@mui/joy/AutocompleteOption' import ListItemDecorator from '@mui/joy/ListItemDecorator' +import { IndexedKeywords } from '../types/common' -interface FilmOptionType { +interface FilmOptionType extends Partial { inputValue?: string; label: string; - value?: number; } const filter = createFilterOptions() -export default function FreeSoloCreateOption() { +export default function FreeSoloCreateOption({ options: propOptions }: { options: FilmOptionType[] }) { const [value, setValue] = React.useState(null) return ( @@ -54,7 +54,7 @@ export default function FreeSoloCreateOption() { clearOnBlur handleHomeEndKeys freeSolo - options={top100Films} + options={propOptions} getOptionLabel={(option) => { // Value selected with enter, right from the input if (typeof option === 'string') { @@ -82,131 +82,3 @@ export default function FreeSoloCreateOption() { ) } - -// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top -const top100Films: readonly FilmOptionType[] = [ - { label: 'The Shawshank Redemption', value: 1994 }, - { label: 'The Godfather', value: 1972 }, - { label: 'The Godfather: Part II', value: 1974 }, - { label: 'The Dark Knight', value: 2008 }, - { label: '12 Angry Men', value: 1957 }, - { label: "Schindler's List", value: 1993 }, - { label: 'Pulp Fiction', value: 1994 }, - { - label: 'The Lord of the Rings: The Return of the King', - value: 2003, - }, - { label: 'The Good, the Bad and the Ugly', value: 1966 }, - { label: 'Fight Club', value: 1999 }, - { - label: 'The Lord of the Rings: The Fellowship of the Ring', - value: 2001, - }, - { - label: 'Star Wars: Episode V - The Empire Strikes Back', - value: 1980, - }, - { label: 'Forrest Gump', value: 1994 }, - { label: 'Inception', value: 2010 }, - { - label: 'The Lord of the Rings: The Two Towers', - value: 2002, - }, - { label: "One Flew Over the Cuckoo's Nest", value: 1975 }, - { label: 'Goodfellas', value: 1990 }, - { label: 'The Matrix', value: 1999 }, - { label: 'Seven Samurai', value: 1954 }, - { - label: 'Star Wars: Episode IV - A New Hope', - value: 1977, - }, - { label: 'City of God', value: 2002 }, - { label: 'Se7en', value: 1995 }, - { label: 'The Silence of the Lambs', value: 1991 }, - { label: "It's a Wonderful Life", value: 1946 }, - { label: 'Life Is Beautiful', value: 1997 }, - { label: 'The Usual Suspects', value: 1995 }, - { label: 'Léon: The Professional', value: 1994 }, - { label: 'Spirited Away', value: 2001 }, - { label: 'Saving Private Ryan', value: 1998 }, - { label: 'Once Upon a Time in the West', value: 1968 }, - { label: 'American History X', value: 1998 }, - { label: 'Interstellar', value: 2014 }, - { label: 'Casablanca', value: 1942 }, - { label: 'City Lights', value: 1931 }, - { label: 'Psycho', value: 1960 }, - { label: 'The Green Mile', value: 1999 }, - { label: 'The Intouchables', value: 2011 }, - { label: 'Modern Times', value: 1936 }, - { label: 'Raiders of the Lost Ark', value: 1981 }, - { label: 'Rear Window', value: 1954 }, - { label: 'The Pianist', value: 2002 }, - { label: 'The Departed', value: 2006 }, - { label: 'Terminator 2: Judgment Day', value: 1991 }, - { label: 'Back to the Future', value: 1985 }, - { label: 'Whiplash', value: 2014 }, - { label: 'Gladiator', value: 2000 }, - { label: 'Memento', value: 2000 }, - { label: 'The Prestige', value: 2006 }, - { label: 'The Lion King', value: 1994 }, - { label: 'Apocalypse Now', value: 1979 }, - { label: 'Alien', value: 1979 }, - { label: 'Sunset Boulevard', value: 1950 }, - { - label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', - value: 1964, - }, - { label: 'The Great Dictator', value: 1940 }, - { label: 'Cinema Paradiso', value: 1988 }, - { label: 'The Lives of Others', value: 2006 }, - { label: 'Grave of the Fireflies', value: 1988 }, - { label: 'Paths of Glory', value: 1957 }, - { label: 'Django Unchained', value: 2012 }, - { label: 'The Shining', value: 1980 }, - { label: 'WALL·E', value: 2008 }, - { label: 'American Beauty', value: 1999 }, - { label: 'The Dark Knight Rises', value: 2012 }, - { label: 'Princess Mononoke', value: 1997 }, - { label: 'Aliens', value: 1986 }, - { label: 'Oldboy', value: 2003 }, - { label: 'Once Upon a Time in America', value: 1984 }, - { label: 'Witness for the Prosecution', value: 1957 }, - { label: 'Das Boot', value: 1981 }, - { label: 'Citizen Kane', value: 1941 }, - { label: 'North by Northwest', value: 1959 }, - { label: 'Vertigo', value: 1958 }, - { - label: 'Star Wars: Episode VI - Return of the Jedi', - value: 1983, - }, - { label: 'Reservoir Dogs', value: 1992 }, - { label: 'Braveheart', value: 1995 }, - { label: 'M', value: 1931 }, - { label: 'Requiem for a Dream', value: 2000 }, - { label: 'Amélie', value: 2001 }, - { label: 'A Clockwork Orange', value: 1971 }, - { label: 'Like Stars on Earth', value: 2007 }, - { label: 'Taxi Driver', value: 1976 }, - { label: 'Lawrence of Arabia', value: 1962 }, - { label: 'Double Indemnity', value: 1944 }, - { - label: 'Eternal Sunshine of the Spotless Mind', - value: 2004, - }, - { label: 'Amadeus', value: 1984 }, - { label: 'To Kill a Mockingbird', value: 1962 }, - { label: 'Toy Story 3', value: 2010 }, - { label: 'Logan', value: 2017 }, - { label: 'Full Metal Jacket', value: 1987 }, - { label: 'Dangal', value: 2016 }, - { label: 'The Sting', value: 1973 }, - { label: '2001: A Space Odyssey', value: 1968 }, - { label: "Singin' in the Rain", value: 1952 }, - { label: 'Toy Story', value: 1995 }, - { label: 'Bicycle Thieves', value: 1948 }, - { label: 'The Kid', value: 1921 }, - { label: 'Inglourious Basterds', value: 2009 }, - { label: 'Snatch', value: 2000 }, - { label: '3 Idiots', value: 2009 }, - { label: 'Monty Python and the Holy Grail', value: 1975 }, -] From 8a9b7756f6ca84e45a873d326e66a168ec8540c3 Mon Sep 17 00:00:00 2001 From: danactive Date: Mon, 13 Nov 2023 19:58:07 -0800 Subject: [PATCH 13/24] search based combobox! --- next/src/components/ComboBox.tsx | 18 +++++++++--------- next/src/hooks/useSearch.tsx | 17 ++++++++--------- next/src/types/common.d.ts | 6 ++++++ 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/next/src/components/ComboBox.tsx b/next/src/components/ComboBox.tsx index f315fa6e..27e3e791 100644 --- a/next/src/components/ComboBox.tsx +++ b/next/src/components/ComboBox.tsx @@ -4,18 +4,18 @@ import FormLabel from '@mui/joy/FormLabel' import Autocomplete, { createFilterOptions } from '@mui/joy/Autocomplete' import AutocompleteOption from '@mui/joy/AutocompleteOption' import ListItemDecorator from '@mui/joy/ListItemDecorator' -import { IndexedKeywords } from '../types/common' - -interface FilmOptionType extends Partial { - inputValue?: string; - label: string; -} +import { IndexedKeywords, FilmOptionType } from '../types/common' const filter = createFilterOptions() -export default function FreeSoloCreateOption({ options: propOptions }: { options: FilmOptionType[] }) { - const [value, setValue] = React.useState(null) - +export default function FreeSoloCreateOption( + { options: propOptions, onChange: setValue, value }: + { + options: FilmOptionType[], + onChange: Function, + value: FilmOptionType, + }, +) { return ( Free solo with text demo diff --git a/next/src/hooks/useSearch.tsx b/next/src/hooks/useSearch.tsx index 7a394aa1..d761a608 100644 --- a/next/src/hooks/useSearch.tsx +++ b/next/src/hooks/useSearch.tsx @@ -2,7 +2,7 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import styled from 'styled-components' import ComboBox from '../components/ComboBox' -import { IndexedKeywords } from '../types/common'; +import { FilmOptionType, IndexedKeywords } from '../types/common'; interface ServerSideItem { corpus: string; @@ -22,18 +22,13 @@ const ShareLink = styled.nav` margin-left: 1rem; ` -interface ReactSelectNewValue { - value: string; - label: string; -} - function useSearch( { items, setMemoryIndex, indexedKeywords }: { items: ItemType[]; setMemoryIndex?: Function; indexedKeywords: IndexedKeywords[] }, ): { filtered: ItemType[]; keyword: string; setKeyword: Function; searchBox: JSX.Element; } { const router = useRouter() const [keyword, setKeyword] = useState(router.query.keyword?.toString() || '') - const [selectedOption, setSelectedOption] = useState(null) + const [selectedOption, setSelectedOption] = useState(null) const getShareUrlStem = () => { if (router.asPath.includes('keyword=')) { @@ -56,7 +51,11 @@ function useSearch( Search results {filtered?.length} of {items?.length}{keywordResultLabel} - + {getShareUrlStem()} @@ -73,7 +72,7 @@ function useSearch( useEffect(() => { if (router.isReady && router.query.keyword) { setKeyword(router.query.keyword?.toString()) - const newValue: ReactSelectNewValue = { + const newValue: FilmOptionType = { label: Array.isArray(router.query.keyword) ? router.query.keyword[0] : router.query.keyword, value: Array.isArray(router.query.keyword) ? router.query.keyword[0] : router.query.keyword, } diff --git a/next/src/types/common.d.ts b/next/src/types/common.d.ts index 8b31104a..582a022d 100644 --- a/next/src/types/common.d.ts +++ b/next/src/types/common.d.ts @@ -131,6 +131,11 @@ declare global { } } +interface FilmOptionType extends Partial { + inputValue?: string; + label: string; +} + export type { AlbumMeta, XmlMeta, @@ -143,4 +148,5 @@ export type { Item, ItemReferenceSource, IndexedKeywords, + FilmOptionType, } From 5472556bd5eca20213546aaa59b68d7689565c53 Mon Sep 17 00:00:00 2001 From: danactive Date: Tue, 14 Nov 2023 20:30:24 -0800 Subject: [PATCH 14/24] seems to search fine, best^ is unknown --- next/src/components/ComboBox.tsx | 19 +++++++++---------- next/src/hooks/useSearch.tsx | 6 +++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/next/src/components/ComboBox.tsx b/next/src/components/ComboBox.tsx index 27e3e791..e1213f32 100644 --- a/next/src/components/ComboBox.tsx +++ b/next/src/components/ComboBox.tsx @@ -9,11 +9,15 @@ import { IndexedKeywords, FilmOptionType } from '../types/common' const filter = createFilterOptions() export default function FreeSoloCreateOption( - { options: propOptions, onChange: setValue, value }: + { + options: propOptions, + onChange, + value, + }: { options: FilmOptionType[], onChange: Function, - value: FilmOptionType, + value: string | null, }, ) { return ( @@ -23,16 +27,11 @@ export default function FreeSoloCreateOption( value={value} onChange={(event, newValue) => { if (typeof newValue === 'string') { - setValue({ - label: newValue, - }) + onChange(newValue) } else if (newValue && newValue.inputValue) { - // Create a new value from the user input - setValue({ - label: newValue.inputValue, - }) + onChange(newValue.inputValue) } else { - setValue(newValue) + onChange(newValue?.value) } }} filterOptions={(options, params) => { diff --git a/next/src/hooks/useSearch.tsx b/next/src/hooks/useSearch.tsx index d761a608..a7a96e91 100644 --- a/next/src/hooks/useSearch.tsx +++ b/next/src/hooks/useSearch.tsx @@ -2,7 +2,7 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import styled from 'styled-components' import ComboBox from '../components/ComboBox' -import { FilmOptionType, IndexedKeywords } from '../types/common'; +import { FilmOptionType, IndexedKeywords } from '../types/common' interface ServerSideItem { corpus: string; @@ -28,7 +28,7 @@ function useSearch( ): { filtered: ItemType[]; keyword: string; setKeyword: Function; searchBox: JSX.Element; } { const router = useRouter() const [keyword, setKeyword] = useState(router.query.keyword?.toString() || '') - const [selectedOption, setSelectedOption] = useState(null) + const [selectedOption, setSelectedOption] = useState('') const getShareUrlStem = () => { if (router.asPath.includes('keyword=')) { @@ -42,7 +42,7 @@ function useSearch( function handleSubmit(event: React.FormEvent) { event.preventDefault() - setKeyword(selectedOption?.value ?? '') + setKeyword(selectedOption ?? '') setMemoryIndex?.(0) } From 3fe6daee90da30e11ff5896e9053597534156eb4 Mon Sep 17 00:00:00 2001 From: danactive Date: Wed, 15 Nov 2023 21:05:25 -0800 Subject: [PATCH 15/24] keywords search without special characters, so best^ fails --- next/src/components/ComboBox.tsx | 11 +++++++---- next/src/hooks/useSearch.tsx | 16 +++++++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/next/src/components/ComboBox.tsx b/next/src/components/ComboBox.tsx index e1213f32..0487f639 100644 --- a/next/src/components/ComboBox.tsx +++ b/next/src/components/ComboBox.tsx @@ -17,7 +17,7 @@ export default function FreeSoloCreateOption( { options: FilmOptionType[], onChange: Function, - value: string | null, + value: FilmOptionType | null, }, ) { return ( @@ -27,11 +27,14 @@ export default function FreeSoloCreateOption( value={value} onChange={(event, newValue) => { if (typeof newValue === 'string') { - onChange(newValue) + console.log('1newValue', newValue) + onChange({ value: newValue }) } else if (newValue && newValue.inputValue) { - onChange(newValue.inputValue) + console.log('2newValue', newValue.inputValue) + onChange({ value: newValue.inputValue }) } else { - onChange(newValue?.value) + console.log('3newValue', newValue?.value) + onChange(newValue) } }} filterOptions={(options, params) => { diff --git a/next/src/hooks/useSearch.tsx b/next/src/hooks/useSearch.tsx index a7a96e91..49854db2 100644 --- a/next/src/hooks/useSearch.tsx +++ b/next/src/hooks/useSearch.tsx @@ -22,13 +22,17 @@ const ShareLink = styled.nav` margin-left: 1rem; ` +function escapeRegExp(string: string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string +} + function useSearch( { items, setMemoryIndex, indexedKeywords }: { items: ItemType[]; setMemoryIndex?: Function; indexedKeywords: IndexedKeywords[] }, ): { filtered: ItemType[]; keyword: string; setKeyword: Function; searchBox: JSX.Element; } { const router = useRouter() const [keyword, setKeyword] = useState(router.query.keyword?.toString() || '') - const [selectedOption, setSelectedOption] = useState('') + const [selectedOption, setSelectedOption] = useState(null) const getShareUrlStem = () => { if (router.asPath.includes('keyword=')) { @@ -42,7 +46,8 @@ function useSearch( function handleSubmit(event: React.FormEvent) { event.preventDefault() - setKeyword(selectedOption ?? '') + console.log(`Filter "${selectedOption?.value ?? ''}"`) + setKeyword(selectedOption?.value ?? '') setMemoryIndex?.(0) } @@ -90,7 +95,12 @@ function useSearch( const corpusWithoutAccentLow = corpus.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase() return (k: string) => { const keywordWithoutAccentLow = k.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase() - return new RegExp(`\\b${keywordWithoutAccentLow}\\b`).test(corpusWithoutAccentLow) + // console.log('escapeRegExp(keywordWithoutAccentLow)', escapeRegExp(keywordWithoutAccentLow), `\\b${escapeRegExp(keywordWithoutAccentLow)}\\b`) + return corpusWithoutAccentLow.indexOf(keywordWithoutAccentLow) !== -1 + // console.log('corpusWithoutAccentLow', corpusWithoutAccentLow) + // return new RegExp(`\b${escapeRegExp(keywordWithoutAccentLow)}\b`).test(corpusWithoutAccentLow) + // return /\bBC\b/.test(corpusWithoutAccentLow) + // return (/\bkeywordWithoutAccentLow\b/g).test(corpusWithoutAccentLow) } } const filtered = items.filter((item) => { From 23d378e8aa92945085153ce6909e506714fe7ead Mon Sep 17 00:00:00 2001 From: danactive Date: Thu, 16 Nov 2023 21:18:57 -0800 Subject: [PATCH 16/24] undo --- api/server/plugins/rename/lib/index.js | 2 +- api/server/plugins/rename/lib/rename.js | 2 +- ui/app/containers/Walk/Menu.jsx | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/api/server/plugins/rename/lib/index.js b/api/server/plugins/rename/lib/index.js index 97fc7cf4..cc619cd8 100644 --- a/api/server/plugins/rename/lib/index.js +++ b/api/server/plugins/rename/lib/index.js @@ -3,7 +3,7 @@ const filenamer = require('./filenames'); const renamer = require('./rename'); const routes = require('../../../lib/routes'); const validation = require('../../../lib/validation'); - +// TODO copy to Next const formatJson = (json) => ({ xml: json.xml, filenames: json.filenames }); const handler = (request) => new Promise((reply) => { diff --git a/api/server/plugins/rename/lib/rename.js b/api/server/plugins/rename/lib/rename.js index 13a0aa6c..04e9197f 100644 --- a/api/server/plugins/rename/lib/rename.js +++ b/api/server/plugins/rename/lib/rename.js @@ -5,7 +5,7 @@ const path = require('path'); const exists = require('../../exists/lib/exists'); const utils = require('../../utils'); - +// TODO copy to Next /* Reassign associated filename based on file without extension diff --git a/ui/app/containers/Walk/Menu.jsx b/ui/app/containers/Walk/Menu.jsx index dcb67cce..075fa71e 100644 --- a/ui/app/containers/Walk/Menu.jsx +++ b/ui/app/containers/Walk/Menu.jsx @@ -21,6 +21,7 @@ function Menu({ showMenu, imageFilenames, path }) { const [date, setDate] = useState(moment().hour(1)); function handleRename() { + // TODO get this POST /admin/rename const postBody = { filenames: imageFilenames, prefix: date.toISOString().split('T')[0], From 2e69ff984bcb74988150f27dd64cd6b77c04cec9 Mon Sep 17 00:00:00 2001 From: danactive Date: Sat, 18 Nov 2023 16:34:55 -0800 Subject: [PATCH 17/24] dropdown choices work --- next/src/hooks/useSearch.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/next/src/hooks/useSearch.tsx b/next/src/hooks/useSearch.tsx index 49854db2..a2581e2a 100644 --- a/next/src/hooks/useSearch.tsx +++ b/next/src/hooks/useSearch.tsx @@ -96,11 +96,15 @@ function useSearch( return (k: string) => { const keywordWithoutAccentLow = k.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase() // console.log('escapeRegExp(keywordWithoutAccentLow)', escapeRegExp(keywordWithoutAccentLow), `\\b${escapeRegExp(keywordWithoutAccentLow)}\\b`) - return corpusWithoutAccentLow.indexOf(keywordWithoutAccentLow) !== -1 + // return corpusWithoutAccentLow.indexOf(keywordWithoutAccentLow) !== -1 // console.log('corpusWithoutAccentLow', corpusWithoutAccentLow) // return new RegExp(`\b${escapeRegExp(keywordWithoutAccentLow)}\b`).test(corpusWithoutAccentLow) // return /\bBC\b/.test(corpusWithoutAccentLow) - // return (/\bkeywordWithoutAccentLow\b/g).test(corpusWithoutAccentLow) + const specials = /[ `!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~]/ + if (specials.test(keywordWithoutAccentLow)) { + return corpusWithoutAccentLow.indexOf(keywordWithoutAccentLow) !== -1 + } + return new RegExp(`\\b${keywordWithoutAccentLow}\\b`, 'g').test(corpusWithoutAccentLow) } } const filtered = items.filter((item) => { From cdd27d3b32eb00638fd3b93d91e1719726a3e1e9 Mon Sep 17 00:00:00 2001 From: danactive Date: Fri, 16 Feb 2024 16:54:04 +0300 Subject: [PATCH 18/24] try types --- next/src/components/ComboBox.tsx | 21 +++++++++++++-------- next/src/hooks/useSearch.tsx | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/next/src/components/ComboBox.tsx b/next/src/components/ComboBox.tsx index 0487f639..7699d4aa 100644 --- a/next/src/components/ComboBox.tsx +++ b/next/src/components/ComboBox.tsx @@ -6,35 +6,40 @@ import AutocompleteOption from '@mui/joy/AutocompleteOption' import ListItemDecorator from '@mui/joy/ListItemDecorator' import { IndexedKeywords, FilmOptionType } from '../types/common' +export const TYPES = { + A: 'A', + B: 'B', + C: 'C', +} as const + const filter = createFilterOptions() -export default function FreeSoloCreateOption( +export default function ComboBox( { options: propOptions, onChange, - value, + value: valueText, }: { options: FilmOptionType[], - onChange: Function, + onChange: ({ value, label, type }: { value: string; label: string; type: keyof typeof TYPES, }) => void, value: FilmOptionType | null, }, ) { return ( - Free solo with text demo { if (typeof newValue === 'string') { console.log('1newValue', newValue) - onChange({ value: newValue }) + onChange({ value: newValue, label: newValue, type: TYPES.A }) } else if (newValue && newValue.inputValue) { console.log('2newValue', newValue.inputValue) - onChange({ value: newValue.inputValue }) + onChange({ value: newValue.inputValue, label: newValue.inputValue, type: TYPES.B }) } else { console.log('3newValue', newValue?.value) - onChange(newValue) + onChange({ ...newValue, type: TYPES.C }) } }} filterOptions={(options, params) => { diff --git a/next/src/hooks/useSearch.tsx b/next/src/hooks/useSearch.tsx index a2581e2a..e7aea7db 100644 --- a/next/src/hooks/useSearch.tsx +++ b/next/src/hooks/useSearch.tsx @@ -1,7 +1,7 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import styled from 'styled-components' -import ComboBox from '../components/ComboBox' +import ComboBox, { type TYPES } from '../components/ComboBox' import { FilmOptionType, IndexedKeywords } from '../types/common' interface ServerSideItem { From b9a8649e3d77e0f2f25aa49c51b862778207acb9 Mon Sep 17 00:00:00 2001 From: danactive Date: Sat, 6 Apr 2024 10:43:01 -0700 Subject: [PATCH 19/24] Remove types and console logs --- next/package-lock.json | 242 ++++++++++++------------------- next/package.json | 6 +- next/src/components/ComboBox.tsx | 13 +- next/src/hooks/useSearch.tsx | 8 +- 4 files changed, 100 insertions(+), 169 deletions(-) diff --git a/next/package-lock.json b/next/package-lock.json index c08da0ba..e8788098 100644 --- a/next/package-lock.json +++ b/next/package-lock.json @@ -12,9 +12,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@hello-pangea/dnd": "^16.3.0", - "@mui/joy": "^5.0.0-beta.14", - "@types/react-image-gallery": "^1.2.0", - "@types/xml2js": "^0.4.12", + "@mui/joy": "^5.0.0-beta.32", "boom": "^7.3.0", "camelcase": "^6.3.0", "color-thief-react": "^2.1.0", @@ -26,7 +24,6 @@ "react-dom": "^18.2.0", "react-image-gallery": "^1.3.0", "react-map-gl": "^7.1.5", - "react-select": "^5.7.4", "styled-components": "^6.0.7", "stylis": "^4.3.0", "xml2js": "^0.6.2" @@ -42,6 +39,8 @@ "@types/node": "^20.5.9", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", + "@types/react-image-gallery": "^1.2.0", + "@types/xml2js": "^0.4.12", "eslint": "^8.48.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-next": "^13.4.19", @@ -1991,9 +1990,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", - "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2269,20 +2268,25 @@ } }, "node_modules/@floating-ui/dom": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.1.tgz", - "integrity": "sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "dependencies": { - "@floating-ui/core": "^1.4.1", - "@floating-ui/utils": "^0.1.1" + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" } }, + "node_modules/@floating-ui/dom/node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", - "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "dependencies": { - "@floating-ui/dom": "^1.5.1" + "@floating-ui/dom": "^1.6.1" }, "peerDependencies": { "react": ">=16.8.0", @@ -3046,16 +3050,16 @@ } }, "node_modules/@mui/base": { - "version": "5.0.0-beta.23", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.23.tgz", - "integrity": "sha512-9L8SQUGAWtd/Qi7Qem26+oSSgpY7f2iQTuvcz/rsGpyZjSomMMO6lwYeQSA0CpWM7+aN7eGoSY/WV6wxJiIxXw==", - "dependencies": { - "@babel/runtime": "^7.23.2", - "@floating-ui/react-dom": "^2.0.2", - "@mui/types": "^7.2.8", - "@mui/utils": "^5.14.17", + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", "@popperjs/core": "^2.11.8", - "clsx": "^2.0.0", + "clsx": "^2.1.0", "prop-types": "^15.8.1" }, "engines": { @@ -3063,7 +3067,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", @@ -3077,26 +3081,26 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.14.17", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.17.tgz", - "integrity": "sha512-eE0uxrpJAEL2ZXkeGLKg8HQDafsiXY+6eNpP4lcv3yIjFfGbU6Hj9/P7Adt8jpU+6JIhmxvILGj2r27pX+zdrQ==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz", + "integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==", "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/joy": { - "version": "5.0.0-beta.14", - "resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.14.tgz", - "integrity": "sha512-5N4tHgla/YGLRArcDb2GIgz8keXGXOeIdFVyP3hW1IZac0xJpiOuQNelxG9CVcZfEEAZ2V3r22ai7O2yq/8q+A==", - "dependencies": { - "@babel/runtime": "^7.23.2", - "@mui/base": "5.0.0-beta.23", - "@mui/core-downloads-tracker": "^5.14.17", - "@mui/system": "^5.14.17", - "@mui/types": "^7.2.8", - "@mui/utils": "^5.14.17", - "clsx": "^2.0.0", + "version": "5.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.32.tgz", + "integrity": "sha512-QJW5Mu2GTJUX4sXjxt4nQBugpJAiSkUT49S/bwoKCCWx8bCfsEyplTzZPK+FraweiGhGgZWExWOTAPpxH83RUQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/core-downloads-tracker": "^5.15.14", + "@mui/system": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "clsx": "^2.1.0", "prop-types": "^15.8.1" }, "engines": { @@ -3104,7 +3108,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", @@ -3126,12 +3130,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.14.17", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.17.tgz", - "integrity": "sha512-u4zxsCm9xmQrlhVPug+Ccrtsjv7o2+rehvrgHoh0siSguvVgVQq5O3Hh10+tp/KWQo2JR4/nCEwquSXgITS1+g==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz", + "integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==", "dependencies": { - "@babel/runtime": "^7.23.2", - "@mui/utils": "^5.14.17", + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.15.14", "prop-types": "^15.8.1" }, "engines": { @@ -3139,7 +3143,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", @@ -3152,13 +3156,13 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.14.17", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.17.tgz", - "integrity": "sha512-AqpVjBEA7wnBvKPW168bNlqB6EN7HxTjLOY7oi275AzD/b1C7V0wqELy6NWoJb2yya5sRf7ENf4iNi3+T5cOgw==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", + "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", "dependencies": { - "@babel/runtime": "^7.23.2", + "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", - "csstype": "^3.1.2", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { @@ -3166,7 +3170,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.4.1", @@ -3183,17 +3187,17 @@ } }, "node_modules/@mui/system": { - "version": "5.14.17", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.17.tgz", - "integrity": "sha512-Ccz3XlbCqka6DnbHfpL3o3TfOeWQPR+ewvNAgm8gnS9M0yVMmzzmY6z0w/C1eebb+7ZP7IoLUj9vojg/GBaTPg==", - "dependencies": { - "@babel/runtime": "^7.23.2", - "@mui/private-theming": "^5.14.17", - "@mui/styled-engine": "^5.14.17", - "@mui/types": "^7.2.8", - "@mui/utils": "^5.14.17", - "clsx": "^2.0.0", - "csstype": "^3.1.2", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz", + "integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.15.14", + "@mui/styled-engine": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { @@ -3201,7 +3205,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", @@ -3222,9 +3226,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.8", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.8.tgz", - "integrity": "sha512-9u0ji+xspl96WPqvrYJF/iO+1tQ1L5GTaDOeG3vCR893yy7VcWwRNiVMmPdPNpMDqx0WV1wtEW9OMwK9acWJzQ==", + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", + "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0" }, @@ -3235,12 +3239,12 @@ } }, "node_modules/@mui/utils": { - "version": "5.14.17", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.17.tgz", - "integrity": "sha512-yxnWgSS4J6DMFPw2Dof85yBkG02VTbEiqsikymMsnZnXDurtVGTIhlNuV24GTmFTuJMzEyTTU9UF+O7zaL8LEQ==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz", + "integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==", "dependencies": { - "@babel/runtime": "^7.23.2", - "@types/prop-types": "^15.7.9", + "@babel/runtime": "^7.23.9", + "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -3249,7 +3253,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", @@ -3957,7 +3961,8 @@ "node_modules/@types/node": { "version": "20.5.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.9.tgz", - "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==" + "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==", + "dev": true }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -3971,9 +3976,9 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "node_modules/@types/prop-types": { - "version": "15.7.10", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.10.tgz", - "integrity": "sha512-mxSnDQxPqsZxmeShFH+uwQ4kO4gcJcGahjjMFeLbKE95IAZiiZyiEepGZjtXJ7hN/yfu0bu9xN2ajcU0JcxX6A==" + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "node_modules/@types/react": { "version": "18.2.21", @@ -3998,14 +4003,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/react-image-gallery/-/react-image-gallery-1.2.0.tgz", "integrity": "sha512-y5OEficUWcnGEc6KvflRXgvhtaMSsDH6JGT3/SJs0FV/RgtKuIgrUpGbDw5MdzyUwe/D8sJTo+aw8NOkaX0nIA==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-transition-group": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", - "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "dev": true, "dependencies": { "@types/react": "*" } @@ -4047,6 +4045,7 @@ "version": "0.4.12", "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.12.tgz", "integrity": "sha512-CZPpQKBZ8db66EP5hCjwvYrLThgZvnyZrPXK2W+UI1oOaWezGt34iOaUCX4Jah2X8+rQqjvl9VKEIT8TR1I0rA==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -5297,9 +5296,9 @@ } }, "node_modules/clsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", - "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", "engines": { "node": ">=6" } @@ -5812,9 +5811,9 @@ "dev": true }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/cwise-compiler": { "version": "1.1.3", @@ -6088,15 +6087,6 @@ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -11718,41 +11708,6 @@ } } }, - "node_modules/react-select": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.4.tgz", - "integrity": "sha512-NhuE56X+p9QDFh4BgeygHFIvJJszO1i1KSkg/JPcIJrbovyRtI+GuOEa4XzFCEpZRAEoEI8u/cAHK+jG/PgUzQ==", - "dependencies": { - "@babel/runtime": "^7.12.0", - "@emotion/cache": "^11.4.0", - "@emotion/react": "^11.8.1", - "@floating-ui/dom": "^1.0.1", - "@types/react-transition-group": "^4.4.0", - "memoize-one": "^6.0.0", - "prop-types": "^15.6.0", - "react-transition-group": "^4.3.0", - "use-isomorphic-layout-effect": "^1.1.2" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -13899,19 +13854,6 @@ "requires-port": "^1.0.0" } }, - "node_modules/use-isomorphic-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/use-memo-one": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", diff --git a/next/package.json b/next/package.json index d334e85d..5236969e 100644 --- a/next/package.json +++ b/next/package.json @@ -19,9 +19,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@hello-pangea/dnd": "^16.3.0", - "@mui/joy": "^5.0.0-beta.14", - "@types/react-image-gallery": "^1.2.0", - "@types/xml2js": "^0.4.12", + "@mui/joy": "^5.0.0-beta.32", "boom": "^7.3.0", "camelcase": "^6.3.0", "color-thief-react": "^2.1.0", @@ -48,6 +46,8 @@ "@types/node": "^20.5.9", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", + "@types/react-image-gallery": "^1.2.0", + "@types/xml2js": "^0.4.12", "eslint": "^8.48.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-next": "^13.4.19", diff --git a/next/src/components/ComboBox.tsx b/next/src/components/ComboBox.tsx index 7699d4aa..e9c3b64d 100644 --- a/next/src/components/ComboBox.tsx +++ b/next/src/components/ComboBox.tsx @@ -22,7 +22,7 @@ export default function ComboBox( }: { options: FilmOptionType[], - onChange: ({ value, label, type }: { value: string; label: string; type: keyof typeof TYPES, }) => void, + onChange: ({ label, value }: { label: string; value: string; }) => void, value: FilmOptionType | null, }, ) { @@ -32,14 +32,9 @@ export default function ComboBox( value={valueText} onChange={(event, newValue) => { if (typeof newValue === 'string') { - console.log('1newValue', newValue) - onChange({ value: newValue, label: newValue, type: TYPES.A }) - } else if (newValue && newValue.inputValue) { - console.log('2newValue', newValue.inputValue) - onChange({ value: newValue.inputValue, label: newValue.inputValue, type: TYPES.B }) - } else { - console.log('3newValue', newValue?.value) - onChange({ ...newValue, type: TYPES.C }) + onChange({ label: newValue, value: newValue }) + } else if (newValue?.label && newValue?.value) { + onChange({ label: newValue.label, value: newValue.value }) } }} filterOptions={(options, params) => { diff --git a/next/src/hooks/useSearch.tsx b/next/src/hooks/useSearch.tsx index e7aea7db..5a616dd8 100644 --- a/next/src/hooks/useSearch.tsx +++ b/next/src/hooks/useSearch.tsx @@ -46,7 +46,6 @@ function useSearch( function handleSubmit(event: React.FormEvent) { event.preventDefault() - console.log(`Filter "${selectedOption?.value ?? ''}"`) setKeyword(selectedOption?.value ?? '') setMemoryIndex?.(0) } @@ -79,7 +78,7 @@ function useSearch( setKeyword(router.query.keyword?.toString()) const newValue: FilmOptionType = { label: Array.isArray(router.query.keyword) ? router.query.keyword[0] : router.query.keyword, - value: Array.isArray(router.query.keyword) ? router.query.keyword[0] : router.query.keyword, + inputValue: Array.isArray(router.query.keyword) ? router.query.keyword[0] : router.query.keyword, } setSelectedOption(newValue) } @@ -95,11 +94,6 @@ function useSearch( const corpusWithoutAccentLow = corpus.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase() return (k: string) => { const keywordWithoutAccentLow = k.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase() - // console.log('escapeRegExp(keywordWithoutAccentLow)', escapeRegExp(keywordWithoutAccentLow), `\\b${escapeRegExp(keywordWithoutAccentLow)}\\b`) - // return corpusWithoutAccentLow.indexOf(keywordWithoutAccentLow) !== -1 - // console.log('corpusWithoutAccentLow', corpusWithoutAccentLow) - // return new RegExp(`\b${escapeRegExp(keywordWithoutAccentLow)}\b`).test(corpusWithoutAccentLow) - // return /\bBC\b/.test(corpusWithoutAccentLow) const specials = /[ `!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~]/ if (specials.test(keywordWithoutAccentLow)) { return corpusWithoutAccentLow.indexOf(keywordWithoutAccentLow) !== -1 From 8db35ca9549dced14adbd3ee6c6d087aecfd89d6 Mon Sep 17 00:00:00 2001 From: danactive Date: Sat, 6 Apr 2024 12:47:04 -0700 Subject: [PATCH 20/24] Undo to previous working regex special character --- next/src/hooks/useSearch.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/next/src/hooks/useSearch.tsx b/next/src/hooks/useSearch.tsx index 5a616dd8..750a87fb 100644 --- a/next/src/hooks/useSearch.tsx +++ b/next/src/hooks/useSearch.tsx @@ -22,10 +22,6 @@ const ShareLink = styled.nav` margin-left: 1rem; ` -function escapeRegExp(string: string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string -} - function useSearch( { items, setMemoryIndex, indexedKeywords }: { items: ItemType[]; setMemoryIndex?: Function; indexedKeywords: IndexedKeywords[] }, @@ -94,11 +90,7 @@ function useSearch( const corpusWithoutAccentLow = corpus.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase() return (k: string) => { const keywordWithoutAccentLow = k.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase() - const specials = /[ `!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~]/ - if (specials.test(keywordWithoutAccentLow)) { - return corpusWithoutAccentLow.indexOf(keywordWithoutAccentLow) !== -1 - } - return new RegExp(`\\b${keywordWithoutAccentLow}\\b`, 'g').test(corpusWithoutAccentLow) + return corpusWithoutAccentLow.indexOf(keywordWithoutAccentLow) !== -1 } } const filtered = items.filter((item) => { From 3c825fdaf6a2b89dcb82bf2bf61f167f4ea297e8 Mon Sep 17 00:00:00 2001 From: danactive Date: Sat, 6 Apr 2024 19:26:17 -0700 Subject: [PATCH 21/24] fix autocomplete clear, still needs submit --- next/src/components/ComboBox.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/next/src/components/ComboBox.tsx b/next/src/components/ComboBox.tsx index e9c3b64d..fc10cc11 100644 --- a/next/src/components/ComboBox.tsx +++ b/next/src/components/ComboBox.tsx @@ -30,11 +30,13 @@ export default function ComboBox( { + onChange={(event, newValue): void => { if (typeof newValue === 'string') { onChange({ label: newValue, value: newValue }) } else if (newValue?.label && newValue?.value) { onChange({ label: newValue.label, value: newValue.value }) + } else if (newValue === null) { // clear + onChange({ label: '', value: '' }) } }} filterOptions={(options, params) => { From 1150e7dec001a70ec0c9ea32350507473b9f0693 Mon Sep 17 00:00:00 2001 From: danactive Date: Sun, 7 Apr 2024 10:33:11 -0700 Subject: [PATCH 22/24] fix free text search --- next/src/components/ComboBox.tsx | 26 +++++++++----------------- next/src/hooks/useSearch.tsx | 11 ++++++----- next/src/types/common.d.ts | 6 ------ 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/next/src/components/ComboBox.tsx b/next/src/components/ComboBox.tsx index fc10cc11..aad4425b 100644 --- a/next/src/components/ComboBox.tsx +++ b/next/src/components/ComboBox.tsx @@ -1,18 +1,10 @@ -import * as React from 'react' import FormControl from '@mui/joy/FormControl' -import FormLabel from '@mui/joy/FormLabel' import Autocomplete, { createFilterOptions } from '@mui/joy/Autocomplete' import AutocompleteOption from '@mui/joy/AutocompleteOption' import ListItemDecorator from '@mui/joy/ListItemDecorator' -import { IndexedKeywords, FilmOptionType } from '../types/common' +import { IndexedKeywords } from '../types/common' -export const TYPES = { - A: 'A', - B: 'B', - C: 'C', -} as const - -const filter = createFilterOptions() +const filter = createFilterOptions() export default function ComboBox( { @@ -21,9 +13,9 @@ export default function ComboBox( value: valueText, }: { - options: FilmOptionType[], + options: IndexedKeywords[], onChange: ({ label, value }: { label: string; value: string; }) => void, - value: FilmOptionType | null, + value: IndexedKeywords | null, }, ) { return ( @@ -31,9 +23,9 @@ export default function ComboBox( { - if (typeof newValue === 'string') { + if (typeof newValue === 'string') { // free text onChange({ label: newValue, value: newValue }) - } else if (newValue?.label && newValue?.value) { + } else if (newValue?.label && newValue?.value) { // selected keyword onChange({ label: newValue.label, value: newValue.value }) } else if (newValue === null) { // clear onChange({ label: '', value: '' }) @@ -47,7 +39,7 @@ export default function ComboBox( const isExisting = options.some((option) => inputValue === option.label) if (inputValue !== '' && !isExisting) { filtered.push({ - inputValue, + value: inputValue, label: `Add "${inputValue}"`, }) } @@ -65,8 +57,8 @@ export default function ComboBox( return option } // Add "xxx" option created dynamically - if (option.inputValue) { - return option.inputValue + if (option.value) { + return option.value } // Regular option return option.label diff --git a/next/src/hooks/useSearch.tsx b/next/src/hooks/useSearch.tsx index 750a87fb..33a2c9a5 100644 --- a/next/src/hooks/useSearch.tsx +++ b/next/src/hooks/useSearch.tsx @@ -2,7 +2,7 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import styled from 'styled-components' import ComboBox, { type TYPES } from '../components/ComboBox' -import { FilmOptionType, IndexedKeywords } from '../types/common' +import { IndexedKeywords } from '../types/common' interface ServerSideItem { corpus: string; @@ -28,7 +28,7 @@ function useSearch( ): { filtered: ItemType[]; keyword: string; setKeyword: Function; searchBox: JSX.Element; } { const router = useRouter() const [keyword, setKeyword] = useState(router.query.keyword?.toString() || '') - const [selectedOption, setSelectedOption] = useState(null) + const [selectedOption, setSelectedOption] = useState(null) const getShareUrlStem = () => { if (router.asPath.includes('keyword=')) { @@ -72,9 +72,10 @@ function useSearch( useEffect(() => { if (router.isReady && router.query.keyword) { setKeyword(router.query.keyword?.toString()) - const newValue: FilmOptionType = { - label: Array.isArray(router.query.keyword) ? router.query.keyword[0] : router.query.keyword, - inputValue: Array.isArray(router.query.keyword) ? router.query.keyword[0] : router.query.keyword, + const value = Array.isArray(router.query.keyword) ? router.query.keyword[0] : router.query.keyword + const newValue: IndexedKeywords = { + label: value, + value, } setSelectedOption(newValue) } diff --git a/next/src/types/common.d.ts b/next/src/types/common.d.ts index 582a022d..8b31104a 100644 --- a/next/src/types/common.d.ts +++ b/next/src/types/common.d.ts @@ -131,11 +131,6 @@ declare global { } } -interface FilmOptionType extends Partial { - inputValue?: string; - label: string; -} - export type { AlbumMeta, XmlMeta, @@ -148,5 +143,4 @@ export type { Item, ItemReferenceSource, IndexedKeywords, - FilmOptionType, } From 2e6bceddbf5bebc40bee919352d6ab595a0fb56a Mon Sep 17 00:00:00 2001 From: danactive Date: Sun, 7 Apr 2024 11:43:44 -0700 Subject: [PATCH 23/24] Dropdown search and free text should produce different results --- next/public/galleries/demo/sample.xml | 2 +- public/galleries/demo/sample.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/next/public/galleries/demo/sample.xml b/next/public/galleries/demo/sample.xml index 3699c531..b93950eb 100644 --- a/next/public/galleries/demo/sample.xml +++ b/next/public/galleries/demo/sample.xml @@ -85,7 +85,7 @@ Brass Fish Tavern Tavern Located in the iconic art deco Marine building - best^ + best^, fish Marine_Building wikipedia diff --git a/public/galleries/demo/sample.xml b/public/galleries/demo/sample.xml index 3699c531..b93950eb 100644 --- a/public/galleries/demo/sample.xml +++ b/public/galleries/demo/sample.xml @@ -85,7 +85,7 @@ Brass Fish Tavern Tavern Located in the iconic art deco Marine building - best^ + best^, fish Marine_Building wikipedia From 1e057bdcdf8aecc2496ab072a3b53d62766540a9 Mon Sep 17 00:00:00 2001 From: danactive Date: Sun, 7 Apr 2024 11:46:08 -0700 Subject: [PATCH 24/24] oops --- next/src/hooks/useSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/next/src/hooks/useSearch.tsx b/next/src/hooks/useSearch.tsx index 33a2c9a5..809d02f1 100644 --- a/next/src/hooks/useSearch.tsx +++ b/next/src/hooks/useSearch.tsx @@ -1,7 +1,7 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import styled from 'styled-components' -import ComboBox, { type TYPES } from '../components/ComboBox' +import ComboBox from '../components/ComboBox' import { IndexedKeywords } from '../types/common' interface ServerSideItem {