├── common ├── .gitignore ├── package.json ├── tsconfig.json └── src │ ├── webviewMessages.ts │ └── fileSystemConfig.ts ├── .gitattributes ├── media ├── paypal.png ├── hop-config.png ├── terminals.png ├── config-editor.png ├── sftp-config.png ├── shell-tasks.png ├── prompt-username.png ├── proxy-settings.png ├── workspace-folder.png └── add-workspace-folder.png ├── resources ├── Logo.png ├── Icons.psd ├── config │ ├── Idle.png │ ├── Active.png │ ├── Deleted.png │ ├── Error.png │ └── Connecting.png └── icon.svg ├── .yarn └── sdks │ ├── integrations.yml │ ├── prettier │ ├── package.json │ ├── index.js │ └── bin-prettier.js │ └── typescript │ ├── package.json │ ├── lib │ ├── typescript.js │ ├── tsc.js │ ├── tsserver.js │ └── tsserverlibrary.js │ └── bin │ ├── tsc │ └── tsserver ├── .gitignore ├── .vscodeignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── webview ├── src │ ├── data │ │ ├── state.ts │ │ ├── index.ts │ │ ├── reducers.ts │ │ └── actions.ts │ ├── ConfigEditor │ │ ├── configGroupField.tsx │ │ ├── index.css │ │ ├── proxyFields.tsx │ │ └── index.tsx │ ├── index.tsx │ ├── FieldTypes │ │ ├── string.tsx │ │ ├── path.tsx │ │ ├── group.tsx │ │ ├── number.tsx │ │ ├── checkbox.tsx │ │ ├── config.tsx │ │ ├── dropdown.tsx │ │ ├── dropdownwithinput.tsx │ │ ├── list.tsx │ │ ├── umask.tsx │ │ ├── base.tsx │ │ └── index.css │ ├── ConfigList │ │ ├── index.css │ │ └── index.tsx │ ├── router.tsx │ ├── view │ │ ├── index.ts │ │ ├── state.ts │ │ ├── reducers.ts │ │ └── actions.ts │ ├── react-app-env.d.ts │ ├── ConfigLocator.tsx │ ├── tests │ │ └── redux.tsx │ ├── index.css │ ├── Startscreen.tsx │ ├── redux.ts │ ├── NewConfig.tsx │ └── vscode.ts ├── .gitignore ├── tslint.json ├── public │ └── index.html ├── tsconfig.json ├── package.json └── webpack.config.js ├── .yarnrc.yml ├── tsconfig.json ├── tslint.json ├── enhance-changelog.js ├── .github └── workflows │ ├── publish-extension.yml │ └── build-extension.yml ├── map-error.js ├── src ├── proxy.ts ├── putty.ts ├── fileSystemRouter.ts ├── treeViewManager.ts ├── utils.ts ├── webview.ts ├── extension.ts ├── ui-utils.ts ├── shellConfig.ts └── flags.ts ├── webpack.config.js ├── webpack.plugin.js └── README.md /common/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Build output 3 | out/ 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/releases/** binary 2 | /.yarn/plugins/** binary 3 | -------------------------------------------------------------------------------- /media/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/media/paypal.png -------------------------------------------------------------------------------- /resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/resources/Logo.png -------------------------------------------------------------------------------- /media/hop-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/media/hop-config.png -------------------------------------------------------------------------------- /media/terminals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/media/terminals.png -------------------------------------------------------------------------------- /resources/Icons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/resources/Icons.psd -------------------------------------------------------------------------------- /media/config-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/media/config-editor.png -------------------------------------------------------------------------------- /media/sftp-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/media/sftp-config.png -------------------------------------------------------------------------------- /media/shell-tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/media/shell-tasks.png -------------------------------------------------------------------------------- /media/prompt-username.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/media/prompt-username.png -------------------------------------------------------------------------------- /media/proxy-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/media/proxy-settings.png -------------------------------------------------------------------------------- /resources/config/Idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/resources/config/Idle.png -------------------------------------------------------------------------------- /media/workspace-folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/media/workspace-folder.png -------------------------------------------------------------------------------- /resources/config/Active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/resources/config/Active.png -------------------------------------------------------------------------------- /resources/config/Deleted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/resources/config/Deleted.png -------------------------------------------------------------------------------- /resources/config/Error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/resources/config/Error.png -------------------------------------------------------------------------------- /media/add-workspace-folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/media/add-workspace-folder.png -------------------------------------------------------------------------------- /resources/config/Connecting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchoofsKelvin/vscode-sshfs/HEAD/resources/config/Connecting.png -------------------------------------------------------------------------------- /.yarn/sdks/integrations.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by @yarnpkg/sdks. 2 | # Manual changes might be lost! 3 | 4 | integrations: 5 | - vscode 6 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prettier", 3 | "version": "2.8.7-sdk", 4 | "main": "./index.js", 5 | "type": "commonjs", 6 | "bin": "./bin-prettier.js" 7 | } 8 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript", 3 | "version": "5.7.3-sdk", 4 | "main": "./lib/typescript.js", 5 | "type": "commonjs", 6 | "bin": { 7 | "tsc": "./bin/tsc", 8 | "tsserver": "./bin/tsserver" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Build output 3 | dist 4 | util 5 | 6 | # Build artifacts 7 | *.vsix 8 | stats.json 9 | 10 | # Yarn 11 | .yarn/* 12 | !.yarn/patches 13 | !.yarn/releases 14 | !.yarn/plugins 15 | !.yarn/sdks 16 | !.yarn/versions 17 | !.yarn/yarn.lock 18 | .pnp.* 19 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | 2 | ** 3 | 4 | !package.json 5 | !README.md 6 | !LICENSE.txt 7 | !dist/*.js 8 | !util 9 | !resources/config 10 | !resources/Logo.png 11 | !resources/icon.svg 12 | !media 13 | !webview/build/index.html 14 | !webview/build/static/css/*.css 15 | !webview/build/static/js/*.js 16 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "amodio.tsl-problem-matcher", 6 | "arcanis.vscode-zipfs", 7 | "esbenp.prettier-vscode" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /webview/src/data/state.ts: -------------------------------------------------------------------------------- 1 | import type { ConfigLocation, FileSystemConfig } from 'common/fileSystemConfig'; 2 | 3 | export interface IState { 4 | configs: FileSystemConfig[]; 5 | locations: ConfigLocation[]; 6 | } 7 | 8 | export const DEFAULT_STATE: IState = { 9 | configs: [], 10 | locations: [], 11 | } 12 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableGlobalCache: true 2 | 3 | nodeLinker: pnp 4 | 5 | packageExtensions: 6 | eslint-plugin-flowtype@*: 7 | peerDependenciesMeta: 8 | "@babel/plugin-syntax-flow": 9 | optional: true 10 | "@babel/plugin-transform-react-jsx": 11 | optional: true 12 | 13 | preferInteractive: true 14 | 15 | yarnPath: .yarn/releases/yarn-4.6.0.cjs 16 | -------------------------------------------------------------------------------- /webview/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | .eslintcache 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /webview/src/data/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { Store } from 'redux'; 3 | import { addListener } from '../vscode'; 4 | import * as actions from './actions'; 5 | 6 | export { reducer } from './reducers'; 7 | export * from './state'; 8 | export { actions }; 9 | 10 | export function initStore(store: Store) { 11 | addListener((msg) => store.dispatch(actions.receivedData(msg.configs, msg.locations)), 'responseData'); 12 | } 13 | -------------------------------------------------------------------------------- /webview/src/ConfigEditor/configGroupField.tsx: -------------------------------------------------------------------------------- 1 | import { getGroups } from 'common/fileSystemConfig'; 2 | import { FieldDropdownWithInput } from '../FieldTypes/dropdownwithinput'; 3 | import { connect } from '../redux'; 4 | 5 | export interface StateProps { 6 | values: string[]; 7 | } 8 | 9 | export default connect(FieldDropdownWithInput)( 10 | state => ({ values: getGroups(state.data.configs).sort() }), 11 | ); 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "target": "ES2019", 7 | "outDir": "out", 8 | "lib": [ 9 | "ES2019" 10 | ], 11 | "sourceMap": true, 12 | "rootDir": "src" 13 | }, 14 | "compileOnSave": true, 15 | "include": [ 16 | "src" 17 | ] 18 | } -------------------------------------------------------------------------------- /webview/src/ConfigEditor/index.css: -------------------------------------------------------------------------------- 1 | 2 | div.ConfigEditor { 3 | padding: 5px; 4 | } 5 | 6 | div.ConfigEditor div.header { 7 | display: flex; 8 | } 9 | div.ConfigEditor div.title { 10 | margin: 6px; 11 | height: 100%; 12 | } 13 | 14 | div.ConfigEditor h3 { 15 | margin: 0; 16 | } 17 | div.ConfigEditor h4 { 18 | margin: 0; 19 | font-size: 85%; 20 | } 21 | 22 | div.ConfigEditor > div.divider { 23 | margin-top: 20px; 24 | } 25 | -------------------------------------------------------------------------------- /common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common", 3 | "version": "0.1.0", 4 | "private": true, 5 | "exports": { 6 | "./*": "./out/*" 7 | }, 8 | "typesVersions": { 9 | ">=0": { 10 | "*": [ 11 | "out/*" 12 | ] 13 | } 14 | }, 15 | "scripts": { 16 | "watch": "tsc -w", 17 | "build": "tsc" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^20.12.1", 21 | "@types/ssh2": "^0.5.41", 22 | "typescript": "~5.7.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /webview/src/data/reducers.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionType } from './actions'; 2 | import { DEFAULT_STATE, IState } from './state'; 3 | 4 | export function reducer(state = DEFAULT_STATE, action: Action): IState { 5 | console.log('data reducer', action); 6 | switch (action.type) { 7 | case ActionType.RECEIVED_DATA: { 8 | return { ...state, configs: action.configs, locations: action.locations }; 9 | } 10 | } 11 | return state; 12 | } 13 | 14 | export default reducer; 15 | -------------------------------------------------------------------------------- /webview/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as ReactDOM from 'react-dom'; 2 | import { Provider } from 'react-redux'; 3 | import './index.css'; 4 | import { STORE } from './redux'; 5 | import Router from './router'; 6 | import { API } from './vscode'; 7 | 8 | ReactDOM.render( 9 | 10 |
11 | 12 |
13 |
, 14 | document.getElementById('root') as HTMLElement 15 | ); 16 | 17 | API.postMessage({ type: 'requestData' }); 18 | -------------------------------------------------------------------------------- /common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "target": "ES2019", 7 | "outDir": "out", 8 | "lib": [ 9 | "ES2019" 10 | ], 11 | "sourceMap": true, 12 | "declaration": true, 13 | "declarationMap": true, 14 | "rootDir": "src" 15 | }, 16 | "compileOnSave": true, 17 | "include": [ 18 | "src" 19 | ] 20 | } -------------------------------------------------------------------------------- /webview/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-react", 5 | "tslint-config-prettier" 6 | ], 7 | "rules": { 8 | "interface-name": false, 9 | "curly": false, 10 | "no-console": false, 11 | "array-type": [ 12 | true, 13 | "array" 14 | ] 15 | }, 16 | "linterOptions": { 17 | "exclude": [ 18 | "config/**/*.js", 19 | "node_modules/**/*.ts", 20 | "react-dev-utils/*", 21 | "coverage/lcov-report/*.js" 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /webview/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React App 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /webview/src/data/actions.ts: -------------------------------------------------------------------------------- 1 | import type { ConfigLocation, FileSystemConfig } from 'common/fileSystemConfig'; 2 | 3 | export enum ActionType { 4 | RECEIVED_DATA = 'RECEIVED_DATA', 5 | } 6 | 7 | interface IAction { 8 | type: ActionType; 9 | } 10 | 11 | export type Action = IActionReceivedConfigs; 12 | 13 | export interface IActionReceivedConfigs extends IAction { 14 | configs: FileSystemConfig[]; 15 | locations: ConfigLocation[]; 16 | } 17 | export function receivedData(configs: FileSystemConfig[], locations: ConfigLocation[]): IActionReceivedConfigs { 18 | return { configs, locations, type: ActionType.RECEIVED_DATA }; 19 | } 20 | -------------------------------------------------------------------------------- /webview/src/FieldTypes/string.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FieldBase } from './base'; 3 | 4 | export class FieldString extends FieldBase { 5 | public getInitialSubState({ value }: FieldString['props']): FieldString['state'] { 6 | value = value || undefined; 7 | return { oldValue: value, newValue: value }; 8 | } 9 | public renderInput() { 10 | return ; 11 | } 12 | public onChangeEvent = (event: React.ChangeEvent) => { 13 | this.onChange(event.target.value || undefined); 14 | } 15 | } -------------------------------------------------------------------------------- /webview/src/ConfigList/index.css: -------------------------------------------------------------------------------- 1 | 2 | div.ConfigList { 3 | border: 1px solid var(--vscode-settings-dropdownBorder); 4 | border-radius: 4px; 5 | padding: 0; 6 | } 7 | 8 | div.ConfigList > ul { 9 | padding: 0; 10 | margin: 0; 11 | } 12 | 13 | div.ConfigList > ul > li { 14 | display: block; 15 | margin: 0; 16 | padding: 5px; 17 | border-bottom: 1px solid var(--vscode-settings-dropdownListBorder); 18 | background: var(--vscode-settings-dropdownBackground); 19 | color: var(--vscode-settings-dropdownForeground); 20 | } 21 | div.ConfigList > ul > li:hover { 22 | background: var(--vscode-list-hoverBackground); 23 | } 24 | div.ConfigList > ul > li:last-of-type { 25 | border: none; 26 | } 27 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended", 5 | "tslint-config-airbnb" 6 | ], 7 | "jsRules": {}, 8 | "rules": { 9 | "no-empty": false, 10 | "no-console": false, 11 | "curly": [ 12 | true, 13 | "ignore-same-line" 14 | ], 15 | "max-line-length": false, 16 | "array-type": false, 17 | "no-else-after-return": false, 18 | "import-name": false, 19 | "object-literal-sort-keys": false, 20 | "no-parameter-reassignment": false, 21 | "no-var-requires": false, 22 | "interface-name": false, 23 | "max-classes-per-file": false 24 | }, 25 | "rulesDirectory": [] 26 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": [ 11 | "--extensionDevelopmentPath=${workspaceRoot}" 12 | ], 13 | "sourceMaps": true, 14 | "outFiles": [ 15 | "${workspaceRoot}/dist/**/*.js", 16 | "${workspaceRoot}/common/out/**/*.js" 17 | ], 18 | "env": { 19 | "VSCODE_SSHFS_DEBUG": "TRUE" 20 | }, 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /webview/src/router.tsx: -------------------------------------------------------------------------------- 1 | import ConfigEditor from './ConfigEditor'; 2 | import ConfigLocator from './ConfigLocator'; 3 | import NewConfig from './NewConfig'; 4 | import { connect, State } from './redux'; 5 | import Startscreen from './Startscreen'; 6 | 7 | interface StateProps { 8 | view: State['view']['view']; 9 | } 10 | function Router(props: StateProps) { 11 | switch (props.view) { 12 | case 'configeditor': 13 | return ; 14 | case 'configlocator': 15 | return ; 16 | case 'newconfig': 17 | return ; 18 | case 'startscreen': 19 | default: 20 | return ; 21 | } 22 | } 23 | 24 | export default connect(Router)(state => ({ view: state.view.view })); 25 | -------------------------------------------------------------------------------- /resources/icon.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | > _ 11 | 12 | -------------------------------------------------------------------------------- /webview/src/FieldTypes/path.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { promptPath } from '../vscode'; 3 | import { FieldBase } from './base'; 4 | 5 | export class FieldPath extends FieldBase { 6 | public renderInput() { 7 | return
8 | 9 | 10 |
; 11 | } 12 | public onChangeEvent = (event: React.ChangeEvent) => { 13 | this.onChange(event.target.value || undefined); 14 | } 15 | public prompt = async () => { 16 | try { 17 | this.onChange(await promptPath()); 18 | } catch (e) { 19 | console.log('Error while prompting file path', e); 20 | } 21 | }; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /webview/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build/dist", 4 | "module": "esnext", 5 | "target": "es5", 6 | "lib": [ 7 | "es6", 8 | "dom" 9 | ], 10 | "sourceMap": true, 11 | "allowJs": true, 12 | "jsx": "react-jsx", 13 | "moduleResolution": "node", 14 | "rootDir": "src", 15 | "forceConsistentCasingInFileNames": true, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true, 18 | "noImplicitAny": true, 19 | "importHelpers": true, 20 | "strictNullChecks": true, 21 | "noUnusedLocals": true, 22 | "skipLibCheck": true, 23 | "esModuleInterop": true, 24 | "allowSyntheticDefaultImports": true, 25 | "strict": true, 26 | "noFallthroughCasesInSwitch": true, 27 | "resolveJsonModule": true, 28 | "isolatedModules": true 29 | }, 30 | "include": [ 31 | "src", 32 | "public" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /webview/src/view/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { Store } from 'redux'; 3 | import { addListener } from '../vscode'; 4 | import * as actions from './actions'; 5 | 6 | export { reducer } from './reducers'; 7 | export * from './state'; 8 | export { actions }; 9 | 10 | export function initStore(store: Store) { 11 | addListener((msg) => { 12 | const { navigation } = msg; 13 | switch (navigation.type) { 14 | case 'newconfig': 15 | return store.dispatch(actions.openNewConfig()); 16 | case 'editconfig': { 17 | let { config } = navigation; 18 | if (Array.isArray(config)) { 19 | if (config.length !== 1) { 20 | return store.dispatch(actions.openConfigLocator(config, config[0].name)); 21 | } 22 | config = config[0]; 23 | } 24 | return store.dispatch(actions.openConfigEditor(config)); 25 | } 26 | } 27 | }, 'navigate'); 28 | } 29 | -------------------------------------------------------------------------------- /webview/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | declare namespace NodeJS { 6 | interface ProcessEnv { 7 | readonly NODE_ENV: 'development' | 'production' | 'test'; 8 | } 9 | } 10 | 11 | declare module '*.avif' { 12 | const src: string; 13 | export default src; 14 | } 15 | 16 | declare module '*.bmp' { 17 | const src: string; 18 | export default src; 19 | } 20 | 21 | declare module '*.gif' { 22 | const src: string; 23 | export default src; 24 | } 25 | 26 | declare module '*.jpg' { 27 | const src: string; 28 | export default src; 29 | } 30 | 31 | declare module '*.jpeg' { 32 | const src: string; 33 | export default src; 34 | } 35 | 36 | declare module '*.png' { 37 | const src: string; 38 | export default src; 39 | } 40 | 41 | declare module '*.webp' { 42 | const src: string; 43 | export default src; 44 | } 45 | -------------------------------------------------------------------------------- /webview/src/FieldTypes/group.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FieldBase } from './base'; 3 | 4 | const CONTEXT = React.createContext(undefined); 5 | 6 | export class FieldGroup extends React.Component { 7 | public static Consumer = CONTEXT.Consumer; 8 | protected static CONTEXT = CONTEXT; 9 | protected fields: FieldBase[] = []; 10 | public register(field: FieldBase) { 11 | this.fields.push(field); 12 | } 13 | public getErrors(): string[] { 14 | return this.fields.map(f => f.getError()).filter(e => e) as string[];; 15 | } 16 | public mapValues(): { [key: string]: T } { 17 | const res = {} as any; 18 | this.fields.forEach(f => res[f.getLabel()] = f.getValue()); 19 | return res; 20 | } 21 | public render() { 22 | this.fields = []; 23 | return {this.props.children} 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /webview/src/view/state.ts: -------------------------------------------------------------------------------- 1 | import type { ConfigLocation, FileSystemConfig } from 'common/fileSystemConfig'; 2 | 3 | interface IViewState { 4 | view: V; 5 | } 6 | 7 | export interface IStartScreenState extends IViewState<'startscreen'> { 8 | groupBy: string; 9 | } 10 | 11 | export interface INewConfigState extends IViewState<'newconfig'> { 12 | location?: ConfigLocation; 13 | name: string; 14 | } 15 | 16 | export interface IConfigEditorState extends IViewState<'configeditor'> { 17 | oldConfig: FileSystemConfig; 18 | newConfig: FileSystemConfig; 19 | statusMessage?: string; 20 | } 21 | 22 | export interface IConfigLocatorState extends IViewState<'configlocator'> { 23 | configs: FileSystemConfig[]; 24 | name: string; 25 | } 26 | 27 | export type IState = IStartScreenState | INewConfigState | IConfigEditorState | IConfigLocatorState; 28 | 29 | export const DEFAULT_STATE: IState = { 30 | groupBy: 'group', 31 | view: 'startscreen', 32 | } 33 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": ".yarn/sdks/typescript/lib", 3 | "files.exclude": { 4 | "*.vsix": true, 5 | "**/*.lock": true, 6 | "**/node_modules/": true, 7 | "dist/": true, 8 | "util/": true, 9 | "common/out": true, 10 | "webview/build/": true, 11 | ".yarn/": true, 12 | ".yarnrc.yml": true, 13 | ".pnp.*": true, 14 | "LICENSE.txt": true, 15 | "**/.*ignore": true, 16 | "**/.gitattributes": true, 17 | "**/.eslintcache": true, 18 | "**/webpack.config.js": true, 19 | "webpack.plugin.js": true, 20 | "**/tslint.json": true, 21 | "**/tsconfig.json": true 22 | }, 23 | "search.exclude": { 24 | "**/.yarn": true, 25 | "**/.pnp.*": true 26 | }, 27 | "typescript.enablePromptUseWorkspaceTsdk": true, 28 | "typescript.preferences.quoteStyle": "single", 29 | "prettier.prettierPath": ".yarn/sdks/prettier/index.js", 30 | "markdownlint.config": { 31 | "no-duplicate-header": { 32 | "allow_different_nesting": true 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /webview/src/ConfigLocator.tsx: -------------------------------------------------------------------------------- 1 | import { FileSystemConfig, formatConfigLocation } from 'common/fileSystemConfig'; 2 | import * as React from 'react'; 3 | import ConfigList from './ConfigList'; 4 | import { connect, pickProperties } from './redux'; 5 | import type { IConfigLocatorState } from './view'; 6 | 7 | function displayName(config: FileSystemConfig) { 8 | return formatConfigLocation(config._location); 9 | } 10 | 11 | interface StateProps { 12 | configs: FileSystemConfig[]; 13 | name: string; 14 | } 15 | class ConfigLocator extends React.Component { 16 | public render() { 17 | const { configs, name } = this.props; 18 | return
19 |

Locations of {name}

20 | 21 |
; 22 | } 23 | } 24 | 25 | interface SubState { view: IConfigLocatorState } 26 | export default connect(ConfigLocator)( 27 | state => pickProperties(state.view, 'configs', 'name'), 28 | ); 29 | -------------------------------------------------------------------------------- /webview/src/tests/redux.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { connect } from '../redux'; 3 | 4 | function nop(...args: any) { 5 | return; 6 | } 7 | 8 | { 9 | // Check if type stuff is right for our connect thing 10 | // The wrapper components should act as if it has all its props, except the ones 11 | // that get provided by the stateToProps/dispatchToProps passed to connect 12 | interface S { s: 123 }; 13 | interface D { d: 456 }; 14 | interface P { p: 789 }; 15 | class Test extends React.Component { } 16 | const TestC1 = connect(Test)(state => ({ s: 123 })); 17 | nop(); 18 | const TestC2 = connect(Test)(state => ({ s: 123 }), dispatch => ({ d: 456 })); 19 | nop(); 20 | const TestC3 = connect(Test)(state => ({ s: 123 })); 21 | nop(); 22 | const TestC4 = connect(Test)(state => ({ s: 123 }), dispatch => ({ d: 456 })); 23 | nop(); 24 | } 25 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, register} = require(`module`); 5 | const {resolve} = require(`path`); 6 | const {pathToFileURL} = require(`url`); 7 | 8 | const relPnpApiPath = "../../../.pnp.cjs"; 9 | 10 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 11 | const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); 12 | const absRequire = createRequire(absPnpApiPath); 13 | 14 | const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); 15 | const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); 16 | 17 | if (existsSync(absPnpApiPath)) { 18 | if (!process.versions.pnp) { 19 | // Setup the environment to be able to require prettier 20 | require(absPnpApiPath).setup(); 21 | if (isPnpLoaderEnabled && register) { 22 | register(pathToFileURL(absPnpLoaderPath)); 23 | } 24 | } 25 | } 26 | 27 | const wrapWithUserWrapper = existsSync(absUserWrapperPath) 28 | ? exports => absRequire(absUserWrapperPath)(exports) 29 | : exports => exports; 30 | 31 | // Defer to the real prettier your application uses 32 | module.exports = wrapWithUserWrapper(absRequire(`prettier`)); 33 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/typescript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, register} = require(`module`); 5 | const {resolve} = require(`path`); 6 | const {pathToFileURL} = require(`url`); 7 | 8 | const relPnpApiPath = "../../../../.pnp.cjs"; 9 | 10 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 11 | const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); 12 | const absRequire = createRequire(absPnpApiPath); 13 | 14 | const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); 15 | const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); 16 | 17 | if (existsSync(absPnpApiPath)) { 18 | if (!process.versions.pnp) { 19 | // Setup the environment to be able to require typescript 20 | require(absPnpApiPath).setup(); 21 | if (isPnpLoaderEnabled && register) { 22 | register(pathToFileURL(absPnpLoaderPath)); 23 | } 24 | } 25 | } 26 | 27 | const wrapWithUserWrapper = existsSync(absUserWrapperPath) 28 | ? exports => absRequire(absUserWrapperPath)(exports) 29 | : exports => exports; 30 | 31 | // Defer to the real typescript your application uses 32 | module.exports = wrapWithUserWrapper(absRequire(`typescript`)); 33 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, register} = require(`module`); 5 | const {resolve} = require(`path`); 6 | const {pathToFileURL} = require(`url`); 7 | 8 | const relPnpApiPath = "../../../../.pnp.cjs"; 9 | 10 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 11 | const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); 12 | const absRequire = createRequire(absPnpApiPath); 13 | 14 | const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); 15 | const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); 16 | 17 | if (existsSync(absPnpApiPath)) { 18 | if (!process.versions.pnp) { 19 | // Setup the environment to be able to require typescript/bin/tsc 20 | require(absPnpApiPath).setup(); 21 | if (isPnpLoaderEnabled && register) { 22 | register(pathToFileURL(absPnpLoaderPath)); 23 | } 24 | } 25 | } 26 | 27 | const wrapWithUserWrapper = existsSync(absUserWrapperPath) 28 | ? exports => absRequire(absUserWrapperPath)(exports) 29 | : exports => exports; 30 | 31 | // Defer to the real typescript/bin/tsc your application uses 32 | module.exports = wrapWithUserWrapper(absRequire(`typescript/bin/tsc`)); 33 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, register} = require(`module`); 5 | const {resolve} = require(`path`); 6 | const {pathToFileURL} = require(`url`); 7 | 8 | const relPnpApiPath = "../../../../.pnp.cjs"; 9 | 10 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 11 | const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); 12 | const absRequire = createRequire(absPnpApiPath); 13 | 14 | const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); 15 | const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); 16 | 17 | if (existsSync(absPnpApiPath)) { 18 | if (!process.versions.pnp) { 19 | // Setup the environment to be able to require typescript/lib/tsc.js 20 | require(absPnpApiPath).setup(); 21 | if (isPnpLoaderEnabled && register) { 22 | register(pathToFileURL(absPnpLoaderPath)); 23 | } 24 | } 25 | } 26 | 27 | const wrapWithUserWrapper = existsSync(absUserWrapperPath) 28 | ? exports => absRequire(absUserWrapperPath)(exports) 29 | : exports => exports; 30 | 31 | // Defer to the real typescript/lib/tsc.js your application uses 32 | module.exports = wrapWithUserWrapper(absRequire(`typescript/lib/tsc.js`)); 33 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/bin-prettier.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, register} = require(`module`); 5 | const {resolve} = require(`path`); 6 | const {pathToFileURL} = require(`url`); 7 | 8 | const relPnpApiPath = "../../../.pnp.cjs"; 9 | 10 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 11 | const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); 12 | const absRequire = createRequire(absPnpApiPath); 13 | 14 | const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); 15 | const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); 16 | 17 | if (existsSync(absPnpApiPath)) { 18 | if (!process.versions.pnp) { 19 | // Setup the environment to be able to require prettier/bin-prettier.js 20 | require(absPnpApiPath).setup(); 21 | if (isPnpLoaderEnabled && register) { 22 | register(pathToFileURL(absPnpLoaderPath)); 23 | } 24 | } 25 | } 26 | 27 | const wrapWithUserWrapper = existsSync(absUserWrapperPath) 28 | ? exports => absRequire(absUserWrapperPath)(exports) 29 | : exports => exports; 30 | 31 | // Defer to the real prettier/bin-prettier.js your application uses 32 | module.exports = wrapWithUserWrapper(absRequire(`prettier/bin-prettier.js`)); 33 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, register} = require(`module`); 5 | const {resolve} = require(`path`); 6 | const {pathToFileURL} = require(`url`); 7 | 8 | const relPnpApiPath = "../../../../.pnp.cjs"; 9 | 10 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 11 | const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); 12 | const absRequire = createRequire(absPnpApiPath); 13 | 14 | const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); 15 | const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); 16 | 17 | if (existsSync(absPnpApiPath)) { 18 | if (!process.versions.pnp) { 19 | // Setup the environment to be able to require typescript/bin/tsserver 20 | require(absPnpApiPath).setup(); 21 | if (isPnpLoaderEnabled && register) { 22 | register(pathToFileURL(absPnpLoaderPath)); 23 | } 24 | } 25 | } 26 | 27 | const wrapWithUserWrapper = existsSync(absUserWrapperPath) 28 | ? exports => absRequire(absUserWrapperPath)(exports) 29 | : exports => exports; 30 | 31 | // Defer to the real typescript/bin/tsserver your application uses 32 | module.exports = wrapWithUserWrapper(absRequire(`typescript/bin/tsserver`)); 33 | -------------------------------------------------------------------------------- /webview/src/FieldTypes/number.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FieldBase } from './base'; 3 | 4 | export class FieldNumber extends FieldBase { 5 | public renderInput() { 6 | return ; 7 | } 8 | public getError() { 9 | const { newValue } = this.state; 10 | const { validator, optional } = this.props; 11 | if (newValue === undefined) { 12 | if (optional) return null; 13 | return 'No value given'; 14 | } else if (!Number(newValue)) { 15 | return 'Not a number'; 16 | } 17 | return validator ? validator(newValue!) : null; 18 | } 19 | public getValue(): number | undefined { 20 | const { newValue, oldValue } = this.state; 21 | if (newValue === undefined) { 22 | return this.props.optional ? newValue : Number(oldValue); 23 | } 24 | return Number(newValue) || undefined; 25 | } 26 | public onChange = (newValue?: number) => { 27 | newValue = Number(newValue) || undefined; 28 | this.setState({ newValue }, () => this.props.onChange(newValue)); 29 | } 30 | public onChangeEvent = (event: React.ChangeEvent) => { 31 | this.onChange(event.target.value as any || undefined); 32 | } 33 | } -------------------------------------------------------------------------------- /webview/src/index.css: -------------------------------------------------------------------------------- 1 | 2 | /* vscode CSS variables: https://code.visualstudio.com/api/references/theme-color#base-colors */ 3 | 4 | body { 5 | margin: 0; 6 | padding: 0; 7 | font-family: sans-serif; 8 | } 9 | 10 | div.App { 11 | max-width: 800px; 12 | padding: 20px; 13 | } 14 | 15 | div.Window { 16 | padding: 5px; 17 | margin: 5px; 18 | } 19 | 20 | button { 21 | background: var(--vscode-settings-dropdownBackground); 22 | border: 1px solid var(--vscode-settings-dropdownBorder); 23 | color: var(--vscode-settings-dropdownForeground); 24 | font-weight: 200; 25 | border-radius: 3px; 26 | padding: 8px; 27 | margin: 5px; 28 | width: auto; 29 | cursor: pointer; 30 | } 31 | button:disabled { 32 | opacity: 0.5; 33 | cursor: inherit; 34 | } 35 | button:hover { 36 | font-weight: 500; 37 | } 38 | button.cancel { 39 | background-color: rgb(107, 107, 107); 40 | border-color: rgb(177, 177, 177); 41 | color: rgb(255, 255, 255); 42 | } 43 | button.delete { 44 | background-color: rgb(156, 14, 14); 45 | border-color: rgb(179, 17, 17); 46 | color: rgb(255, 255, 255); 47 | } 48 | button.confirm { 49 | background-color: rgb(14, 99, 156); 50 | border-color: rgb(18, 118, 185); 51 | color: rgb(255, 255, 255); 52 | } 53 | 54 | /* Theme colors, *especially* dropdown ones, don't always play nice */ 55 | body.vscode-light { 56 | --vscode-settings-dropdownForeground: var(--vscode-settings-textInputForeground); 57 | } 58 | 59 | /* Also missing "general" button colors for warn, error/delete, save/confirm/ok, ... */ 60 | -------------------------------------------------------------------------------- /enhance-changelog.js: -------------------------------------------------------------------------------- 1 | const { execSync, spawnSync } = require("child_process"); 2 | const fs = require("fs"); 3 | 4 | const tag = execSync("git describe --tags --abbrev=0").toString().trim(); 5 | console.log("Checking commits since", tag); 6 | 7 | const args = ["-n", "1", `${tag}..HEAD`, "--abbrev=7", "--pretty=%h", "--", "CHANGELOG.md"]; 8 | function findCommit(line) { 9 | const result = spawnSync("git", ["log", "-S", line, ...args], { shell: false }); 10 | if (result.status === 0) return result.stdout.toString().trim(); 11 | throw new Error(result.stderr.toString()); 12 | } 13 | 14 | const lines = fs.readFileSync("CHANGELOG.md").toString().split(/\r?\n/g); 15 | 16 | let enhanced = 0; 17 | let shouldEnhance = false; 18 | for (let i = 0; i < lines.length; i++) { 19 | const line = lines[i]; 20 | if (line.startsWith("## Unreleased")) shouldEnhance = true; 21 | else if (line.startsWith("## ")) break; 22 | if (!line.startsWith("- ")) continue; 23 | const commit = findCommit(line); 24 | if (!commit) continue; 25 | console.log(line, "=>", commit); 26 | const brackets = line.match(/ \((.*?)\)$/); 27 | if (brackets) { 28 | if (brackets[1].match(/[\da-fA-F]{7}/)) continue; 29 | if (!brackets[1].includes(" ")) { 30 | lines[i] = line.replace(/\(.*?\)$/, `(${commit}, ${brackets[1]})`); 31 | enhanced++; 32 | continue; 33 | } 34 | } 35 | lines[i] = `${line} (${commit})`; 36 | enhanced++; 37 | } 38 | 39 | console.log(`Enhanced ${enhanced} lines`); 40 | fs.writeFileSync("CHANGELOG.md", lines.join("\n")); 41 | -------------------------------------------------------------------------------- /.github/workflows/publish-extension.yml: -------------------------------------------------------------------------------- 1 | name: Publish extension 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | openvsx: 9 | name: "Open VSX Registry" 10 | if: endsWith(github.event.release.assets[0].name, '.vsix') 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Download release artifact 14 | run: "curl -L -H 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' -H 'Accept: application/octet-stream' ${{ github.event.release.assets[0].url }} --output extension.vsix" 15 | - name: Validate extension file 16 | run: unzip -f extension.vsix extension/package.json 17 | - name: Publish to Open VSX Registry 18 | uses: HaaLeo/publish-vscode-extension@v1 19 | with: 20 | pat: ${{ secrets.OPEN_VSX_TOKEN }} 21 | extensionFile: extension.vsix 22 | vs: 23 | name: "Visual Studio Marketplace" 24 | if: endsWith(github.event.release.assets[0].name, '.vsix') 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Download release artifact 28 | run: "curl -L -H 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' -H 'Accept: application/octet-stream' ${{ github.event.release.assets[0].url }} --output extension.vsix" 29 | - name: Validate extension file 30 | run: unzip -f extension.vsix extension/package.json 31 | - name: Publish to Visual Studio Marketplace 32 | uses: HaaLeo/publish-vscode-extension@v1 33 | with: 34 | pat: ${{ secrets.VS_MARKETPLACE_TOKEN }} 35 | registryUrl: https://marketplace.visualstudio.com 36 | extensionFile: extension.vsix 37 | -------------------------------------------------------------------------------- /webview/src/ConfigList/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FileSystemConfig } from 'common/fileSystemConfig'; 2 | import * as React from 'react'; 3 | import { connect } from '../redux'; 4 | import { openConfigEditor } from '../view/actions'; 5 | import './index.css'; 6 | 7 | interface StateProps { 8 | list: FileSystemConfig[]; 9 | } 10 | interface DispatchProps { 11 | editConfig(config: FileSystemConfig): void; 12 | } 13 | interface OwnProps { 14 | configs?: FileSystemConfig[]; 15 | displayName?(config: FileSystemConfig): string | undefined; 16 | } 17 | class ConfigList extends React.Component { 18 | public render() { 19 | const { list } = this.props; 20 | if (!list.length) return

No configurations found

; 21 | return
22 |
    23 | {list.map(this.editConfigClickHandler, this)} 24 |
25 |
; 26 | } 27 | public editConfigClickHandler(config: FileSystemConfig) { 28 | const { displayName } = this.props; 29 | const name = displayName?.(config) || config.label || config.name; 30 | const onClick = () => this.props.editConfig(config); 31 | return
  • {name}
  • ; 32 | } 33 | } 34 | 35 | export default connect(ConfigList)( 36 | (state, props: OwnProps) => ({ list: props.configs || state.data.configs }), 37 | dispatch => ({ 38 | editConfig(config: FileSystemConfig) { 39 | dispatch(openConfigEditor(config)); 40 | }, 41 | }) 42 | ); 43 | -------------------------------------------------------------------------------- /webview/src/FieldTypes/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import { FieldBase } from './base'; 2 | 3 | interface Props { 4 | optional?: false; 5 | } 6 | export class FieldCheckbox extends FieldBase { 7 | protected getClassName() { 8 | return super.getClassName() + ' FieldCheckbox'; 9 | } 10 | protected getValueClassName() { 11 | return super.getValueClassName() + ' checkbox-box'; 12 | } 13 | public renderInput() { 14 | const { description } = this.props; 15 | return <> 16 |
    17 | {description &&
    {this.props.description}
    } 18 | ; 19 | } 20 | public getError() { 21 | const { newValue } = this.state; 22 | const { validator, optional } = this.props; 23 | if (newValue === undefined) { 24 | if (optional) return null; 25 | return 'No value given'; 26 | } else if (typeof newValue !== 'boolean') { 27 | return 'Not a boolean'; 28 | } 29 | return validator ? validator(newValue!) : null; 30 | } 31 | public getValue(): boolean | undefined { 32 | const { newValue, oldValue } = this.state; 33 | if (newValue === undefined) { 34 | return this.props.optional ? newValue : oldValue; 35 | } 36 | return newValue; 37 | } 38 | public onChange = (newValue?: boolean) => { 39 | this.setState({ newValue }, () => this.props.onChange(newValue)); 40 | } 41 | public onClick = () => this.onChange(!this.getValue()); 42 | } -------------------------------------------------------------------------------- /.github/workflows/build-extension.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Build extension 3 | 4 | on: 5 | push: 6 | tags: ['**'] 7 | branches: 8 | - '*' 9 | - 'issue/**' 10 | - 'feature/**' 11 | - 'release/**' 12 | pull_request: 13 | types: [opened, synchronize] 14 | branches: 15 | - '*' 16 | - 'issue/**' 17 | - 'feature/**' 18 | - 'release/**' 19 | workflow_dispatch: 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | name: Build and package 25 | timeout-minutes: 10 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Event Utilities 29 | uses: SchoofsKelvin/event-utilities@v1.1.0 30 | id: utils 31 | with: 32 | artifact_prefix: "vscode-sshfs" 33 | artifact_extension: "vsix" 34 | - name: Use Node.js 20 35 | uses: actions/setup-node@v4 36 | with: 37 | node-version: 20 38 | cache: yarn 39 | cache-dependency-path: yarn.lock 40 | - name: Install dependencies 41 | run: yarn --immutable 42 | - name: Build extension 43 | run: yarn vsce package -o ${{ steps.utils.outputs.artifact_name }} --yarn --no-dependencies 44 | - name: Upload a Build Artifact 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: ${{ steps.utils.outputs.artifact_name }} 48 | path: ${{ steps.utils.outputs.artifact_name }} 49 | if-no-files-found: error 50 | - name: Create release with artifact 51 | if: ${{ success() && steps.utils.outputs.tag_version }} 52 | uses: softprops/action-gh-release@v2 53 | with: 54 | name: Release ${{ steps.utils.outputs.tag_version }} 55 | draft: true 56 | files: ${{ steps.utils.outputs.artifact_name }} 57 | -------------------------------------------------------------------------------- /webview/src/FieldTypes/config.tsx: -------------------------------------------------------------------------------- 1 | import { FileSystemConfig, formatConfigLocation } from 'common/fileSystemConfig'; 2 | import { connect } from '../redux'; 3 | import type { Props as FieldBaseProps } from './base'; 4 | import { FieldDropdown, Props as FieldDropdownProps } from './dropdown'; 5 | 6 | type Props = Omit, 'value'> & FieldDropdownProps & { 7 | /** 8 | * Defaults to `'full'`. Determines how `values` and the default `displayName`. 9 | * In which way to display configs in the dropdown and how to handle duplicate: 10 | * - `full`: Display `