From d4cc5e9c5b69e95954a5cf68fed965d9740df4b9 Mon Sep 17 00:00:00 2001 From: undefined Date: Sat, 31 Dec 2022 18:59:19 +0800 Subject: [PATCH] ui: add omni-search --- .../components/omnisearch/index.page.tsx | 125 ++++++++++++++++ .../components/omnisearch/omnibar.page.styl | 133 ++++++++++++++++++ packages/ui-default/misc/icons/pie-chart.svg | 1 + 3 files changed, 259 insertions(+) create mode 100644 packages/ui-default/components/omnisearch/index.page.tsx create mode 100644 packages/ui-default/components/omnisearch/omnibar.page.styl create mode 100644 packages/ui-default/misc/icons/pie-chart.svg diff --git a/packages/ui-default/components/omnisearch/index.page.tsx b/packages/ui-default/components/omnisearch/index.page.tsx new file mode 100644 index 000000000..64c10b982 --- /dev/null +++ b/packages/ui-default/components/omnisearch/index.page.tsx @@ -0,0 +1,125 @@ +import { debounce } from 'lodash'; +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { STATUS_CODES } from 'vj/constant/record'; +import { AutoloadPage } from 'vj/misc/Page'; +import { + api, gql, i18n, request, +} from 'vj/utils'; + +export default new AutoloadPage('omnibar', () => { + const $search = $(` + + `); + const $entry = $(` + + `); + $(document.body).append($search); + $(document.body).append($entry); + const $input = $('.omnibar input') as JQuery; + + const prefix = window.location.href.startsWith('/d/') ? `/d/${UiContext.domainId}` : ''; + let setSearching; + let pdocs = []; + let psdict = {}; + let udocs = []; + function SearchResult() { + const [searching, set] = React.useState(false); + setSearching = set; + return ( + <> + {searching &&
Searching...
} + {pdocs.length > 0 &&
{i18n('Problems')}
} + {pdocs.map(({ + domainId, docId, pid, title, nSubmit, nAccept, + }) => ( + +
+ + + +
{title}
+
+
+ {domainId !== UiContext.domainId ? `${domainId}#` : ''}{pid || docId} + {nAccept}/{nSubmit} +
+ + ))} + {udocs.length > 0 &&
{i18n('Users')}
} + {udocs.map(({ + _id, uname, avatarUrl, + }) => ( + +
+ {uname} +
+ UID {_id}
+
{uname}
+
+
+
+ ))} + + ); + } + ReactDOM.createRoot(document.getElementById('omnibar-content')!).render(); + + async function search(query: string) { + if (!query?.trim()) { + pdocs = []; + setSearching?.(false); + return; + } + setSearching?.(true); + [{ pdocs, psdict }, udocs] = await Promise.all([ + request.get(`/d/${UiContext.domainId}/p?q=${encodeURIComponent(query)}&limit=10`), + api(gql` + users(search: ${query}) { + _id + uname + displayName + avatarUrl + } + `, ['data', 'users']), + ]); + setSearching?.(false); + } + $input.on('input', debounce((ev) => search(ev.target.value), 1000)); + + let open = false; + const show = () => { + $search.show(); + setTimeout(() => { + $search.css('opacity', '1'); + $input.focus(); + }, 20); + open = true; + }; + const hide = () => { + $search.css('opacity', '0'); + setTimeout(() => $search.hide(), 200); + open = false; + }; + $entry.on('click', () => (open ? hide() : show())); + $input.on('click', (ev) => ev.stopPropagation()); + $('.omnibar-main').on('click', '.omnibar-content-section', (ev) => ev.stopPropagation()); + $search.on('click', hide); +}); diff --git a/packages/ui-default/components/omnisearch/omnibar.page.styl b/packages/ui-default/components/omnisearch/omnibar.page.styl new file mode 100644 index 000000000..89af0e366 --- /dev/null +++ b/packages/ui-default/components/omnisearch/omnibar.page.styl @@ -0,0 +1,133 @@ +.omnibar { + position: fixed; + top: 0px; + left: 0px; + height: 100%; + width: 100%; + background-color: rgba(0, 0, 0, 0.1); + backdrop-filter: blur(3px); + z-index: 999; + transition: 0.2s; +} +.omnibar-main { + height: min(600px, 100% - 20px); + width: min(750px, 60%); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: flex; + flex-direction: column; + >div:first-child { + transition: 0.2s; + margin-bottom: 10px; + input { + background-color: #fff; + border-radius: 5px; + border: 2px solid #aaa; + padding-left: 42px; + height: 48px; + width: 100%; + font-size: 18px; + outline: 0; + box-sizing: border-box; + } + span { + height: 48px; + width: 48px; + position: absolute; + display: grid; + scale: 1.5; + place-items: center; + top: 0px; + left: 0px; + } + } + >div:last-child { + flex: 1; + scrollbar-width: none; + -ms-overflow-style: none; + overflow-x: hidden; + overflow-y: auto; + &::-webkit-scrollbar { width: 0 !important; } + } +} +.omnibar-toggle { + z-index: 99; + background: white; + display: grid; + place-items: center; + width: 40px; + height: 40px; + position: fixed; + right: 20px; + bottom: 30px; + transition: 0.2s; + border-radius: 5px; + &:hover { + background: #ccc; + } + span { + scale: 1.5; + } +} +#omnibar-content { + overscroll-behavior: none; +} +.omnibar-content-title { + text-align: left; + margin-bottom: 10px; + width: 100%; + font-size: 18px; + font-weight: bold; +} +.omnibar-content-section { + background: white; + border-radius: 10px; + display: flex; + flex-direction: column; + cursor: pointer; + padding: 10px; + line-height: 1; + margin-bottom: 10px; + border: 2px solid #888; + box-sizing: border-box; + width: 100%; + text-decoration: none !important; + &:hover { + background: #eee; + } + >div { + width: 100%; + display: flex; + flex-direction: row; + } +} + +.omnibar-user img { + height: 36px; + width: 36px; + border-radius: 50%; + margin-right: 10px; +} +.omnibar-user-info { + >span { + font-size: 14px; + margin-bottom: 3px; + display: inline-block; + } + >div { + display: flex; + flex-direction: row; + } +} + +.omnibar-problem a { + margin: 2px 0px 3px 0px; +} +.omnibar-problem .icon { + margin-right: 3px; + &:not(:first-child) { + margin-left: 10px; + } +} diff --git a/packages/ui-default/misc/icons/pie-chart.svg b/packages/ui-default/misc/icons/pie-chart.svg new file mode 100644 index 000000000..dbd648b81 --- /dev/null +++ b/packages/ui-default/misc/icons/pie-chart.svg @@ -0,0 +1 @@ + \ No newline at end of file