Skip to content
This repository was archived by the owner on Apr 1, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions browser/src/Plugins/PluginInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export class YarnPluginInstaller implements IPluginInstaller {
}

private async _runYarnCommand(command: string, args: string[]): Promise<void> {
Log.info(`[PluginInstaller] Running yarn command: ${command}...`)
const yarnPath = this._getYarnPath()

const workingDirectory = getUserConfigFolderPath()
Expand All @@ -140,6 +141,7 @@ export class YarnPluginInstaller implements IPluginInstaller {
return
}

Log.info(`[PluginInstaller] Yarn command completely successfully.`)
resolve()
},
)
Expand Down
84 changes: 84 additions & 0 deletions browser/src/Plugins/PluginRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* PluginRepository.ts
*
* Interface for querying various 'plugin repositories':
* - Vim plugins via VimAwesome
* - TODO: Oni plugins via NPM
* - TODO: VSCode plugins via their API
*/

export interface PluginInfo {
name: string
author: string
description: string
yarnInstallPackageName: string

// TODO:
// Icon?
}

/**
* PluginRepository
*
* General interface for querying for plugins
*/
export interface PluginRepository {
searchPlugins(query: string): Promise<PluginInfo[]>
}

export interface VimAwesomePluginResult {
github_repo_name: string
github_owner: string
name: string
github_author: string
short_desc: string
}

export interface VimAwesomeResult {
total_results: number
results_per_page: number
total_pages: number
plugins: VimAwesomePluginResult[]
}

const mapVimAwesomePluginsToPluginInfo = (
vimAwesomePluginInfo: VimAwesomePluginResult[],
): PluginInfo[] => {
return vimAwesomePluginInfo.map(vpi => ({
name: vpi.name,
author: vpi.github_author,
description: vpi.short_desc,
yarnInstallPackageName: `${vpi.github_owner}/${vpi.github_repo_name}`,
}))
}

export class VimAwesomePluginRepository {
public async searchPlugins(query: string): Promise<PluginInfo[]> {
const initialResult = await fetch(
`https://vimawesome.com/api/plugins?page=1&query=${query}`,
)
const info = (await initialResult.json()) as VimAwesomeResult

// TODO: Iterate through pages until we get them all!
return mapVimAwesomePluginsToPluginInfo(info.plugins)
}
}

/**
* CompositePluginRepository
*
* Implementation of PluginRepository that queries against multiple providers simulatenously
*/
export class CompositePluginRepository implements PluginRepository {
private _repositories: PluginRepository[] = []

constructor() {
this._repositories.push(new VimAwesomePluginRepository())
}

public async searchPlugins(query: string): Promise<PluginInfo[]> {
const allResults = this._repositories.map(repository => repository.searchPlugins(query))
const pluginResults = await Promise.all(allResults)
return pluginResults.reduce((prev, cur) => [...prev, ...cur], [])
}
}
134 changes: 111 additions & 23 deletions browser/src/Plugins/PluginSidebarPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,34 @@ import * as React from "react"
import { Event, IDisposable, IEvent } from "oni-types"

import { Configuration } from "./../Services/Configuration"
import { SearchTextBox } from "./../Services/Search/SearchTextBox"
import { SidebarManager, SidebarPane } from "./../Services/Sidebar"

import { SidebarContainerView, SidebarItemView } from "./../UI/components/SidebarItemView"
import { VimNavigator } from "./../UI/components/VimNavigator"

import { PluginManager } from "./../Plugins/PluginManager"
import {
CompositePluginRepository,
PluginInfo,
PluginRepository,
} from "./../Plugins/PluginRepository"

import { noop } from "./../Utility"

import * as Common from "./../UI/components/common"

import styled from "styled-components"

const PluginIconWrapper = styled.div`
background-color: rgba(0, 0, 0, 0.1);
width: 36px;
height: 36px;
`
// const PluginIconWrapper = styled.div`
// background-color: rgba(0, 0, 0, 0.1);
// width: 36px;
// height: 36px;
// `

const PluginCommandsWrapper = styled.div`
flex: 0 0 auto;
`
// const PluginCommandsWrapper = styled.div`
// flex: 0 0 auto;
// `

const PluginInfoWrapper = styled.div`
flex: 1 1 auto;
Expand All @@ -44,25 +50,29 @@ const PluginInfoWrapper = styled.div`

const PluginTitleWrapper = styled.div`
font-size: 1.1em;
font-weight: 700;
`

const PluginDescription = styled.div`
font-size: 0.8em;
white-space: pre-wrap;
`

export interface PluginSidebarItemViewProps {
name: string
description?: string | null
}

export class PluginSidebarItemView extends React.PureComponent<PluginSidebarItemViewProps, {}> {
public render(): JSX.Element {
return (
<Common.Container direction={"horizontal"} fullWidth={true}>
<Common.Fixed style={{ width: "40px", height: "40px" }}>
<Common.Center>
<PluginIconWrapper />
</Common.Center>
</Common.Fixed>
<PluginInfoWrapper>
<PluginTitleWrapper>{this.props.name}</PluginTitleWrapper>
<PluginDescription>
{this.props.description ? this.props.description : " "}
</PluginDescription>
</PluginInfoWrapper>
<PluginCommandsWrapper />
</Common.Container>
)
}
Expand All @@ -71,6 +81,7 @@ export class PluginSidebarItemView extends React.PureComponent<PluginSidebarItem
export class PluginsSidebarPane implements SidebarPane {
private _onEnter = new Event<void>()
private _onLeave = new Event<void>()
private _pluginDiscovery = new CompositePluginRepository()

public get id(): string {
return "oni.sidebar.plugins"
Expand All @@ -96,6 +107,7 @@ export class PluginsSidebarPane implements SidebarPane {
onEnter={this._onEnter}
onLeave={this._onLeave}
pluginManager={this._pluginManager}
pluginDiscovery={this._pluginDiscovery}
/>
)
}
Expand All @@ -106,10 +118,14 @@ export interface IPluginsSidebarPaneViewProps {
onLeave: IEvent<void>

pluginManager: PluginManager
pluginDiscovery: PluginRepository
}

export interface IPluginsSidebarPaneViewState {
isActive: boolean
isSearchTextFocused: boolean
searchText: string
searchResults: PluginInfo[]
defaultPluginsExpanded: boolean
userPluginsExpanded: boolean
workspacePluginsExpanded: boolean
Expand All @@ -126,6 +142,9 @@ export class PluginsSidebarPaneView extends React.PureComponent<

this.state = {
isActive: false,
isSearchTextFocused: false,
searchText: "",
searchResults: [],
defaultPluginsExpanded: false,
userPluginsExpanded: true,
workspacePluginsExpanded: false,
Expand Down Expand Up @@ -156,13 +175,21 @@ export class PluginsSidebarPaneView extends React.PureComponent<
: []
const userPluginIds = this.state.userPluginsExpanded ? userPlugins.map(p => p.id) : []

const allIds = [
"container.default",
...defaultPluginIds,
"container.workspace",
"container.user",
...userPluginIds,
]
const isSearchActive = !!this.state.searchText
const allIds = isSearchActive
? [
"textbox.query",
"container.searchResults",
...this.state.searchResults.map(sr => sr.yarnInstallPackageName),
]
: [
"textbox.query",
"container.default",
...defaultPluginIds,
"container.workspace",
"container.user",
...userPluginIds,
]

return (
<VimNavigator
Expand Down Expand Up @@ -190,8 +217,31 @@ export class PluginsSidebarPaneView extends React.PureComponent<
/>
))

return (
<div>
const pluginItems = isSearchActive ? (
<SidebarContainerView
text={"Search Results"}
isContainer={true}
isExpanded={true}
isFocused={selectedId === "container.searchResults"}
onClick={() => this._onSelect("container.searchResults")}
>
{this.state.searchResults.map(sr => (
<SidebarItemView
indentationLevel={0}
isFocused={selectedId === sr.yarnInstallPackageName}
isContainer={false}
text={
<PluginSidebarItemView
name={sr.name}
description={sr.description}
/>
}
onClick={noop}
/>
))}
</SidebarContainerView>
) : (
<React.Fragment>
<SidebarContainerView
text={"Bundled"}
isContainer={true}
Expand Down Expand Up @@ -219,13 +269,48 @@ export class PluginsSidebarPaneView extends React.PureComponent<
>
{userPluginItems}
</SidebarContainerView>
</React.Fragment>
)

return (
<div>
<SearchTextBox
val={this.state.searchText || "Type to search.."}
onChangeText={newText => this._onSearchTextChanged(newText)}
onCommit={() => this._clearSearchText()}
onDismiss={() => this._clearSearchText()}
isFocused={selectedId === "textbox.query"}
isActive={this.state.isSearchTextFocused}
onClick={() => this._onSelect("textbox.query")}
/>
{pluginItems}
</div>
)
}}
/>
)
}

private _onSearchTextChanged(searchText: string): void {
this.setState({
searchText,
})

const currentSearchText = searchText
this.props.pluginDiscovery.searchPlugins(searchText).then(result => {
if (searchText === currentSearchText && this.state.searchText) {
this.setState({ searchResults: result })
}
})
}

private _clearSearchText(): void {
this.setState({
searchText: null,
isSearchTextFocused: false,
})
}

private _onSelect(id: string): void {
switch (id) {
case "container.default":
Expand All @@ -234,6 +319,9 @@ export class PluginsSidebarPaneView extends React.PureComponent<
case "container.user":
this._toggleUserPluginsExpanded()
return
case "textbox.query":
this.setState({ isSearchTextFocused: true })
return
}
}

Expand Down
3 changes: 2 additions & 1 deletion browser/src/Services/Explorer/ExplorerFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export class FileSystem implements IFileSystem {
this._fs = {
readdir: promisify(nfs.readdir.bind(nfs)),
stat: promisify(nfs.stat.bind(nfs)),
exists: promisify(nfs.exists.bind(nfs)),
exists: filePath =>
new Promise(resolve => nfs.exists(filePath, result => resolve(result))),
}

this.init()
Expand Down