New dropdown implementation

This commit is contained in:
Elio Struyf
2020-12-09 19:01:07 +01:00
parent 0c4050ac78
commit f7bf0e904a
8 changed files with 148 additions and 25 deletions
+20 -11
View File
@@ -34,7 +34,6 @@
"version": "7.12.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
}
@@ -1135,6 +1134,11 @@
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
"dev": true
},
"compute-scroll-into-view": {
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.16.tgz",
"integrity": "sha512-a85LHKY81oQnikatZYA90pufpZ6sQx++BoCxOEMsjpZx+ZnaKGQnCyCehTRr/1p9GBIAHTjcU9k71kSYWloLiQ=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1489,6 +1493,17 @@
"tslib": "^1.10.0"
}
},
"downshift": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/downshift/-/downshift-6.0.6.tgz",
"integrity": "sha512-tmLab3cXCn6PtZYl9V8r/nB2m+7/nCNrwo0B3kTHo/2lRBHr+1en1VNOQt2wIt0ajanAnxquZ00WPCyxe6cNFQ==",
"requires": {
"@babel/runtime": "^7.11.2",
"compute-scroll-into-view": "^1.0.14",
"prop-types": "^15.7.2",
"react-is": "^16.13.1"
}
},
"duplexify": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
@@ -2736,8 +2751,7 @@
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.13.1",
@@ -2908,7 +2922,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
@@ -3223,8 +3236,7 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-copy": {
"version": "0.1.0",
@@ -3521,7 +3533,6 @@
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -3650,8 +3661,7 @@
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-transition-group": {
"version": "4.4.1",
@@ -3693,8 +3703,7 @@
"regenerator-runtime": {
"version": "0.13.7",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
"dev": true
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
},
"regex-not": {
"version": "1.0.2",
+3 -1
View File
@@ -246,5 +246,7 @@
"webpack": "4.44.2",
"webpack-cli": "3.3.12"
},
"dependencies": {}
"dependencies": {
"downshift": "6.0.6"
}
}
+11 -4
View File
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
import { Article, Settings, StatusListener } from './commands';
import { Template } from './commands/Template';
import { TaxonomyType } from './models';
import { TagType } from './viewpanel/TagType';
import { ExplorerView } from './webview/ExplorerView';
let frontMatterStatusBar: vscode.StatusBarItem;
@@ -18,12 +19,18 @@ export function activate({ subscriptions, extensionUri }: vscode.ExtensionContex
}
});
let insertTags = vscode.commands.registerCommand('frontMatter.insertTags', () => {
Article.insert(TaxonomyType.Tag);
let insertTags = vscode.commands.registerCommand('frontMatter.insertTags', async () => {
// Article.insert(TaxonomyType.Tag);
await vscode.commands.executeCommand('workbench.view.extension.frontmatter-explorer');
await vscode.commands.executeCommand('workbench.action.focusSideBar');
explorerSidebar.triggerInputFocus(TagType.tags);
});
let insertCategories = vscode.commands.registerCommand('frontMatter.insertCategories', () => {
Article.insert(TaxonomyType.Category);
let insertCategories = vscode.commands.registerCommand('frontMatter.insertCategories', async () => {
// Article.insert(TaxonomyType.Category);
await vscode.commands.executeCommand('workbench.view.extension.frontmatter-explorer');
await vscode.commands.executeCommand('workbench.action.focusSideBar');
explorerSidebar.triggerInputFocus(TagType.categories);
});
let createTag = vscode.commands.registerCommand('frontMatter.createTag', () => {
+3 -1
View File
@@ -1,5 +1,7 @@
export enum Command {
loading = "loading",
metadata = "metadata",
settings = "settings"
settings = "settings",
focusOnTags = "focusOnTags",
focusOnCategories = "focusOnCategories"
}
+17 -3
View File
@@ -10,7 +10,7 @@ export interface IViewPanelProps {
}
export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React.PropsWithChildren<IViewPanelProps>) => {
const { loading, metadata, settings } = useMessages();
const { loading, metadata, settings, focusElm, unsetFocus } = useMessages();
if (loading) {
return (
@@ -35,10 +35,24 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
settings && metadata && <Actions metadata={metadata} settings={settings} />
}
{
(settings && settings.tags && settings.tags.length > 0) && <TagPicker type={TagType.tags} crntSelected={metadata.tags || []} options={settings.tags} freeform={settings.freeform} />
(settings && settings.tags && settings.tags.length > 0) && (
<TagPicker type={TagType.tags}
crntSelected={metadata.tags || []}
options={settings.tags}
freeform={settings.freeform}
focussed={focusElm === TagType.tags}
unsetFocus={unsetFocus} />
)
}
{
(settings && settings.categories && settings.categories.length > 0) && <TagPicker type={TagType.categories} crntSelected={metadata.categories || []} options={settings.categories} freeform={settings.freeform} />
(settings && settings.categories && settings.categories.length > 0) && (
<TagPicker type={TagType.categories}
crntSelected={metadata.categories || []}
options={settings.categories}
freeform={settings.freeform}
focussed={focusElm === TagType.categories}
unsetFocus={unsetFocus} />
)
}
</div>
);
+68 -3
View File
@@ -5,18 +5,26 @@ import { usePrevious } from '../hooks/usePrevious';
import { CommandToCode } from '../CommandToCode';
import { TagType } from '../TagType';
import { MessageHelper } from '../helper/MessageHelper';
import { TextField } from '@material-ui/core';
import Downshift from 'downshift'
export interface ITagPickerProps {
type: string;
crntSelected: string[];
options: string[];
freeform: boolean;
focussed: boolean;
unsetFocus: () => void;
}
// https://v4-2-1.material-ui.com/components/autocomplete/
// https://github.com/downshift-js/downshift/issues/751
export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsWithChildren<ITagPickerProps>) => {
const { type, crntSelected, options, freeform } = props;
const { type, crntSelected, options, freeform, focussed, unsetFocus } = props;
const [ selected, setSelected ] = React.useState<string[]>([]);
const prevSelected = usePrevious(crntSelected);
const inputRef = React.useRef<HTMLInputElement | null>(null);
const { getRootProps, getInputProps, getListboxProps, getOptionProps, groupedOptions } = useAutocomplete({
id: 'use-autocomplete',
@@ -47,6 +55,18 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
const cmdType = type === TagType.tags ? CommandToCode.updateTags : CommandToCode.updateCategories;
MessageHelper.sendMessage(cmdType, values);
};
const triggerFocus = () => {
if (focussed && inputRef && inputRef.current) {
inputRef.current.focus();
}
}
React.useEffect(() => {
setTimeout(() => {
triggerFocus();
}, 100);
}, [focussed]);
React.useEffect(() => {
if (prevSelected !== crntSelected) {
@@ -56,10 +76,55 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
return (
<div className={`article__tags`}>
<h3>{type}</h3>
<h3>{type} - {focussed ? 'true' : 'false'}</h3>
<Downshift onChange={selection =>
alert(selection ? `You selected ${selection.value}` : 'Selection Cleared')
}
itemToString={item => (item ? item.value : '')}>
{({
getInputProps,
getItemProps,
getMenuProps,
isOpen,
inputValue,
highlightedIndex,
selectedItem,
getRootProps,
openMenu
}) => (
<>
<div
style={{display: 'inline-block'}}
{...getRootProps(undefined, {suppressRefError: true})}
>
<input {...getInputProps({ ref: inputRef, onFocus: openMenu })} onBlur={unsetFocus} />
</div>
<ul {...getMenuProps()}>
{ isOpen ? options.filter(item => !inputValue || item.includes(inputValue)).map((item, index) => (
<li
{...getItemProps({
key: item,
index,
item,
style: {
backgroundColor: highlightedIndex === index ? 'lightgray' : 'white',
fontWeight: selectedItem === item ? 'bold' : 'normal',
}
})}
>
{item}
</li>
))
: null}
</ul>
</>
)}
</Downshift>
<div {...getRootProps()}>
<input {...getInputProps()} placeholder={`Pick your ${type.toLowerCase()}`} />
<TextField {...getInputProps()} placeholder={`Pick your ${type.toLowerCase()}`} />
</div>
{
+11 -1
View File
@@ -3,6 +3,7 @@ import { PanelSettings } from '../../models/PanelSettings';
import { Command } from '../Command';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { TagType } from '../TagType';
const vscode = MessageHelper.getVsCodeAPI();
@@ -10,6 +11,7 @@ export default function useMessages() {
const [metadata, setMetadata] = useState<any>({});
const [settings, setSettings] = useState<PanelSettings>();
const [loading, setLoading] = useState<boolean>(false);
const [focusElm, setFocus] = useState<TagType | null>(null);
window.addEventListener('message', event => {
const message = event.data;
@@ -26,6 +28,12 @@ export default function useMessages() {
case Command.loading:
setLoading(message.data);
break;
case Command.focusOnTags:
setFocus(TagType.tags);
break;
case Command.focusOnCategories:
setFocus(TagType.categories);
break;
}
});
@@ -37,6 +45,8 @@ export default function useMessages() {
return {
metadata,
settings,
loading
focusElm,
loading,
unsetFocus: () => { setFocus(null) }
};
}
+15 -1
View File
@@ -54,6 +54,8 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
*/
public async resolveWebviewView(webviewView: WebviewView, context: WebviewViewResolveContext, token: CancellationToken): Promise<void> {
console.log(context);
this.panel = webviewView;
webviewView.webview.options = {
@@ -124,6 +126,18 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
this.postWebviewMessage({ command: Command.metadata, data: metadata });
}
/**
* Allows the webview panel to focus on tags or categories input
* @param tagType
*/
public triggerInputFocus(tagType: TagType) {
if (tagType === TagType.tags) {
this.postWebviewMessage({ command: Command.focusOnTags });
} else {
this.postWebviewMessage({ command: Command.focusOnCategories });
}
}
/**
* Retrieve the extension settings
*/
@@ -204,7 +218,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
* Post data to the panel
* @param msg
*/
private postWebviewMessage(msg: { command: Command, data: any }) {
private postWebviewMessage(msg: { command: Command, data?: any }) {
this.panel!.webview.postMessage(msg);
}