From a53b68703806df5f4afbe123dafb52fe83e958d4 Mon Sep 17 00:00:00 2001 From: Marcel Kloubert Date: Thu, 23 Nov 2017 23:14:56 +0100 Subject: [PATCH] started to implement switch target --- CHANGELOG.md | 1 + package.json | 5 + src/deploy.ts | 12 ++ src/extension.ts | 10 ++ src/i18.ts | 10 ++ src/lang/de.ts | 10 ++ src/lang/en.ts | 10 ++ src/plugins/switch.ts | 254 ++++++++++++++++++++++++++++++++++++ src/switch.ts | 291 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 603 insertions(+) create mode 100644 src/plugins/switch.ts create mode 100644 src/switch.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 391292d..dd1bde8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 11.2.0 (?????, 2017; switch target and multi root support) * added `autoSelectWorkspace` setting, which can select the current workspace by active text editor automatically +* added [switch target](https://github.com/mkloubert/vs-deploy/wiki/target_switch) ## 11.1.0 (November 20th, 2017; finished multi root support) diff --git a/package.json b/package.json index de0b3f9..a52d743 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,11 @@ "command": "extension.deploy.selectWorkspace", "title": "Select workspace", "category": "Deploy" + }, + { + "command": "extension.deploy.changeSwitch", + "title": "Change switch", + "category": "Deploy" } ], "keybindings": [ diff --git a/src/deploy.ts b/src/deploy.ts index a5c0d47..e23fa33 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -34,6 +34,7 @@ import * as deploy_objects from './objects'; import * as deploy_operations from './operations'; import * as deploy_packages from './packages'; import * as deploy_plugins from './plugins'; +import * as deploy_switch from './switch'; import * as deploy_sync from './sync'; import * as deploy_targets from './targets'; import * as deploy_templates from './templates'; @@ -393,6 +394,14 @@ export class Deployer extends Events.EventEmitter implements vscode.Disposable { }); } + /** + * Changes a switch target. + */ + public async changeSwitch() { + return await deploy_switch.changeSwitch + .apply(this, arguments); + } + /** * Clears the output on startup depending on the current configuration. */ @@ -4195,6 +4204,9 @@ export class Deployer extends Events.EventEmitter implements vscode.Disposable { ME._config = cfg; try { + deploy_switch.reloadTargetStates + .apply(ME, []); + try { ME._QUICK_DEPLOY_STATUS_ITEM.hide(); if (cfg.button) { diff --git a/src/extension.ts b/src/extension.ts index 635b2f7..83e4679 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -242,6 +242,15 @@ export function activate(context: vscode.ExtensionContext) { } }); + let changeSwitch = vscode.commands.registerCommand('extension.deploy.changeSwitch', async () => { + try { + await deployer.changeSwitch(); + } + catch (e) { + vscode.window.showErrorMessage(`[CHANGE SWITCH ERROR]: ${deploy_helpers.toStringSafe(e)}`); + } + }); + let htmlViewer = vscode.workspace.registerTextDocumentContentProvider('vs-deploy-html', new deploy_content.HtmlTextDocumentContentProvider(deployer)); @@ -257,6 +266,7 @@ export function activate(context: vscode.ExtensionContext) { deployer)); context.subscriptions.push(deployer, + changeSwitch, compareFiles, deploy, deployFileOrFolder, deployFilesTo, getTargets, htmlViewer, diff --git a/src/i18.ts b/src/i18.ts index bad15af..2c52646 100644 --- a/src/i18.ts +++ b/src/i18.ts @@ -293,6 +293,15 @@ export interface Translation { invalidFile?: string; unknownEngine?: string; }, + switch?: { + defaultName?: string; + defaultOptionName?: string; + description?: string; + noDefined?: string; + noOptionsDefined?: string; + selectOption?: string; + selectSwitch?: string; + }, test?: { description?: string; }, @@ -370,6 +379,7 @@ export interface Translation { couldNotResolve?: string; isEmpty?: string; }, + selected?: string; sync?: { file?: { doesNotExistOnRemote?: string; diff --git a/src/lang/de.ts b/src/lang/de.ts index 442f031..14361ee 100644 --- a/src/lang/de.ts +++ b/src/lang/de.ts @@ -284,6 +284,15 @@ export const translation: Translation = { invalidFile: 'Datei ist ungültig!', unknownEngine: 'Unbekannter Typ {0:trim,surround}!', }, + switch: { + defaultName: 'Schalter #{0:trim}', + defaultOptionName: 'Schalter-Option #{0:trim}', + description: 'Schaltet zwischen anderen existierenden Zielen um', + noDefined: 'Es wurden keine Schalter gefunden!', + noOptionsDefined: 'Es wurden keine Optionen für den Schalter {0:trim,surround} definiert!', + selectOption: 'Wählen Sie eine Option für den Schalter {0:trim,surround}...', + selectSwitch: 'Wählen Sie einen Schalter aus...', + }, test: { description: 'Ein Test-PlugIn, welches lediglich anzeigt, welche Dateien bereitgestellt würden', }, @@ -361,6 +370,7 @@ export const translation: Translation = { couldNotResolve: "Der relative Pfad für {0:trim,surround} konnte nicht ermittelt werden!", isEmpty: 'Der relative Pfad für {0:trim,surround} is leer!', }, + selected: 'ausgewählt', sync: { file: { doesNotExistOnRemote: '[entfernte Datei existiert nicht]', diff --git a/src/lang/en.ts b/src/lang/en.ts index febc22d..88d539e 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -286,6 +286,15 @@ export const translation: Translation = { invalidFile: 'File is invalid!', unknownEngine: 'Unknown engine {0:trim,surround}!', }, + switch: { + defaultName: 'Switch #{0:trim}', + defaultOptionName: 'Switch option #{0:trim}', + description: 'Switches between existing targets', + noDefined: 'No swicthes available!', + noOptionsDefined: 'No options were defined for the switch {0:trim,surround}!', + selectOption: 'Select an option for the switch {0:trim,surround}...', + selectSwitch: 'Select a switch...', + }, test: { description: 'A mock deployer that only displays what files would be deployed', }, @@ -363,6 +372,7 @@ export const translation: Translation = { couldNotResolve: "Could not get relative path for {0:trim,surround}!", isEmpty: 'Relative path for {0:trim,surround} file is empty!', }, + selected: 'selected', sync: { file: { doesNotExistOnRemote: '[remote does not exist]', diff --git a/src/plugins/switch.ts b/src/plugins/switch.ts new file mode 100644 index 0000000..9b92b66 --- /dev/null +++ b/src/plugins/switch.ts @@ -0,0 +1,254 @@ +/// + +// The MIT License (MIT) +// +// vs-deploy (https://github.com/mkloubert/vs-deploy) +// Copyright (c) Marcel Joachim Kloubert +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +import * as deploy_contracts from '../contracts'; +import * as deploy_helpers from '../helpers'; +import * as deploy_objects from '../objects'; +import * as Enumerable from 'node-enumerable'; +import * as i18 from '../i18'; + + +/** + * The current repository of selected switch options. + */ +export let switchStates: SelectedSwitchOptions = {}; + + +/** + * A switch target. + */ +export interface DeployTargetSwitch extends deploy_contracts.DeployTarget { + /** + * One or more options for the switch. + */ + options?: DeployTargetSwitchOptionValue | DeployTargetSwitchOptionValue[]; +} + +/** + * An option entry for of a switch target. + */ +export interface DeployTargetSwitchOption extends deploy_contracts.Sortable { + /** + * [INTERNAL] DO NOT DEFINE OR OVERWRITE THIS PROPERTY BY YOUR OWN! + * + * Gets the ID of that option. + */ + __id?: any; + + /** + * The description. + */ + description?: string; + /** + * Is default or not. + */ + isDefault?: boolean; + /** + * The (display) name. + */ + name?: string; + /** + * One or more other target names. + */ + targets?: string | string[]; +} + +/** + * A switch option value. + */ +export type DeployTargetSwitchOptionValue = DeployTargetSwitchOption | string; + +/** + * Repository of selected switch options. + */ +export type SelectedSwitchOptions = { [name: string]: DeployTargetSwitchOption }; + + +/** + * Returns the current option of a target. + * + * @param {DeployTargetSwitch} target The target. + * @param {TDefault} [defaultValue] The custom default value. + * + * @return {DeployTargetSwitchOption|TDefault} The option (if found). + */ +export function getCurrentOptionOf(target: DeployTargetSwitch, + defaultValue = false): DeployTargetSwitchOption | TDefault { + if (!target) { + return target; + } + + const TARGET_NAME = deploy_helpers.normalizeString( target.name ); + + const STATES = switchStates; + if (STATES) { + const OPTION = STATES[TARGET_NAME]; + if ('object' === typeof OPTION) { + return OPTION; // found + } + else { + // get first (default) one + // instead + + return Enumerable.from( + getTargetOptionsOf(target) + ).orderBy(o => { + return deploy_helpers.toBooleanSafe(o.isDefault) ? 0 : 1; + }).firstOrDefault(x => true, + defaultValue); + } + } + + return defaultValue; +} + +/** + * Returns the options of a switch target. + * + * @param {DeployTargetSwitch} target The target. + * + * @return {DeployTargetSwitchOption[]} The options. + */ +export function getTargetOptionsOf(target: DeployTargetSwitch): DeployTargetSwitchOption[] { + if (deploy_helpers.isNullOrUndefined(target)) { + return target; + } + + const TARGET_NAME = deploy_helpers.normalizeString(target.name); + + const OPTIONS: DeployTargetSwitchOption[] = []; + + Enumerable.from( deploy_helpers.asArray(target.options) ).where(v => { + return !deploy_helpers.isNullOrUndefined(v); + }).select(v => { + v = deploy_helpers.cloneObject(v); + + if ('object' !== typeof v) { + v = { + targets: [ deploy_helpers.normalizeString(v) ] + }; + } + + v.__id = `${target.__id}\n` + + `${deploy_helpers.normalizeString(deploy_helpers.getSortValue(v))}\n` + + `${deploy_helpers.normalizeString(v.name)}`; + + v.targets = Enumerable.from( deploy_helpers.asArray(v.targets) ).select(t => { + return deploy_helpers.normalizeString(t); + }).where(t => '' !== t && + TARGET_NAME !== t) + .distinct() + .toArray(); + + return v; + }) + .pushTo(OPTIONS); + + return OPTIONS.sort((x, y) => { + return deploy_helpers.compareValuesBy(x, y, + o => deploy_helpers.getSortValue(o)); + }); +} + +/** + * Resets the switch states. + */ +export function resetStates() { + switchStates = {}; +} + +/** + * Sets the current option for a switch target. + * + * @param {DeployTargetSwitch} target The target. + * @param {DeployTargetSwitchOption} option The option to set. + * + * @return {Object} The new data. + */ +export function setCurrentOptionFor(target: DeployTargetSwitch, option: DeployTargetSwitchOption): { option: DeployTargetSwitchOption, target: DeployTargetSwitch } { + if (!target) { + return target; + } + + const NAME = deploy_helpers.normalizeString( target.name ); + + const STATES = switchStates; + if (STATES) { + STATES[NAME] = option; + + return { + option: STATES[NAME], + target: target, + }; + } +} + +class SwitchPlugin extends deploy_objects.DeployPluginBase { + public get canGetFileInfo(): boolean { + return true; + } + + public get canPull(): boolean { + return true; + } + + public deployFile(file: string, target: DeployTargetSwitch, opts?: deploy_contracts.DeployFileOptions): void { + //TODO + } + + private findCurrentTargetsFor(target: DeployTargetSwitch): deploy_contracts.DeployTarget[] { + const FOUND_TARGETS: deploy_contracts.DeployTarget[] = []; + + //TODO + + return FOUND_TARGETS; + } + + public async getFileInfo(file: string, target: DeployTargetSwitch, opts?: deploy_contracts.DeployFileOptions): Promise { + //TODO + return; + } + + public info(): deploy_contracts.DeployPluginInfo { + return { + description: i18.t('plugins.switch.description'), + }; + } + + public pullFile(file: string, target: DeployTargetSwitch, opts?: deploy_contracts.DeployFileOptions): void { + //TODO + } +} + +/** + * Creates a new Plugin. + * + * @param {deploy_contracts.DeployContext} ctx The deploy context. + * + * @returns {deploy_contracts.DeployPlugin} The new instance. + */ +export function createPlugin(ctx: deploy_contracts.DeployContext): deploy_contracts.DeployPlugin { + return new SwitchPlugin(ctx); +} diff --git a/src/switch.ts b/src/switch.ts new file mode 100644 index 0000000..594a154 --- /dev/null +++ b/src/switch.ts @@ -0,0 +1,291 @@ +/// + +// The MIT License (MIT) +// +// vs-deploy (https://github.com/mkloubert/vs-deploy) +// Copyright (c) Marcel Joachim Kloubert +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +import * as deploy_contracts from './contracts'; +import * as deploy_helpers from './helpers'; +import * as deploy_plugins_switch from './plugins/switch'; +import * as Enumerable from 'node-enumerable'; +import * as i18 from './i18'; +import * as vs_deploy from './deploy'; +import * as vscode from 'vscode'; +import { resetStates } from './plugins/switch'; + + +const KEY_SWITCH_STATES = 'vsdSwitchStates'; + +type SavedStates = { [switchName: string]: string }; + + +/** + * Changes a switch target. + */ +export async function changeSwitch() { + const ME: vs_deploy.Deployer = this; + + const TARGETS = ME.getTargets().filter(t => { + return isSwitch(t); + }); + + let selectedOption: deploy_plugins_switch.DeployTargetSwitchOption; + let selectedTarget: deploy_plugins_switch.DeployTargetSwitch; + + const SELECT_OPTION = async () => { + if (!selectedOption) { + return; + } + + deploy_plugins_switch.setCurrentOptionFor(selectedTarget, selectedOption); + await saveStates.apply(ME, []); + }; + + const SELECT_TARGET_OPTION = async (index: number) => { + if (!selectedTarget) { + return; + } + + const SWITCH_NAME = getSwitchName(selectedTarget, index); + + const OPTIONS = Enumerable.from( deploy_plugins_switch.getTargetOptionsOf(selectedTarget) ).orderBy(o => { + }).toArray() + .sort((x, y) => { + return deploy_helpers.compareValuesBy(x, y, + i => deploy_helpers.getSortValue(i, + () => ME.name)); + }); + + const OPTION_QUICK_PICKS: deploy_contracts.DeployActionQuickPick[] = OPTIONS.map((o, i) => { + const LABEL = getSwitchOptionName(o, i); + const DESCRIPTION = deploy_helpers.toStringSafe(o.description).trim(); + + let details = ''; + let isSelected = false; + + const SELECTED_OPTION_OF_TARGET = deploy_plugins_switch.getCurrentOptionOf(selectedTarget); + if (SELECTED_OPTION_OF_TARGET) { + if (o.__id === SELECTED_OPTION_OF_TARGET.__id) { + isSelected = true; + } + } + + return { + action: async () => { + selectedOption = o; + + await SELECT_OPTION(); + }, + description: DESCRIPTION, + detail: isSelected ? `(${i18.t('selected')})` : '', + label: LABEL, + }; + }); + + if (OPTION_QUICK_PICKS.length < 1) { + vscode.window.showWarningMessage( + '[vs-deploy] ' + i18.t('plugins.switch.noOptionsDefined', + SWITCH_NAME), + ); + + return; + } + + let action: Function; + + if (1 === OPTION_QUICK_PICKS.length) { + action = OPTION_QUICK_PICKS[0].action; + } + else { + const SELECTED_ITEM = await vscode.window.showQuickPick(OPTION_QUICK_PICKS, { + placeHolder: i18.t('plugins.switch.selectOption', + SWITCH_NAME), + }); + if (SELECTED_ITEM) { + action = SELECTED_ITEM.action; + } + } + + if (action) { + await Promise.resolve(action()); + } + }; + + const QUICK_PICKS: deploy_contracts.DeployActionQuickPick[] = TARGETS.map((t, i) => { + const LABEL = getSwitchName(t, i); + const DESCRIPTION = deploy_helpers.toStringSafe(t.description).trim(); + + return { + action: async () => { + selectedTarget = t; + + await SELECT_TARGET_OPTION(i); + }, + description: DESCRIPTION, + label: LABEL, + }; + }); + + if (QUICK_PICKS.length < 1) { + vscode.window.showWarningMessage( + '[vs-deploy] ' + i18.t('plugins.switch.noDefined'), + ); + + return; + } + + let targetAction: Function; + + if (1 === QUICK_PICKS.length) { + targetAction = QUICK_PICKS[0].action; + } + else { + const SELECTED_ITEM = await vscode.window.showQuickPick(QUICK_PICKS, { + placeHolder: i18.t('plugins.switch.selectSwitch'), + }); + if (SELECTED_ITEM) { + targetAction = SELECTED_ITEM.action; + } + } + + if (targetAction) { + await Promise.resolve( + targetAction() + ); + } +} + +function getSwitches(): deploy_plugins_switch.DeployTargetSwitch[] { + const ME: vs_deploy.Deployer = this; + + return ME.getTargets().filter(t => { + return isSwitch(t); + }); +} + +function getSwitchName(target: deploy_plugins_switch.DeployTargetSwitch, index: number): string { + if (!target) { + return target; + } + + let name = deploy_helpers.toStringSafe(target.name).trim(); + if ('' === name) { + name = i18.t('plugins.switch.defaultName', + index + 1); + } + + return name; +} + +function getSwitchOptionName(target: deploy_plugins_switch.DeployTargetSwitchOption, index: number): string { + if (!target) { + return target; + } + + let name = deploy_helpers.toStringSafe(target.name).trim(); + if ('' === name) { + name = i18.t('plugins.switch.defaultOptionName', + index + 1); + } + + return name; +} + +function isSwitch(target: deploy_contracts.DeployTarget): target is deploy_plugins_switch.DeployTargetSwitch { + if (target) { + return [ + 'switch' + ].indexOf( deploy_helpers.normalizeString(target.type) ) > -1; + } + + return false; +} + +/** + * Reloads the target states for switches. + */ +export function reloadTargetStates() { + const ME: vs_deploy.Deployer = this; + + resetTargetStates(); + try { + const STATES = ME.context.workspaceState.get(KEY_SWITCH_STATES); + if (STATES) { + const SWITCHES: deploy_plugins_switch.DeployTargetSwitch[] = getSwitches.apply(ME, []); + + for (let p in STATES) { + const OPTION_ID = STATES[p]; + if (deploy_helpers.isEmptyString(OPTION_ID)) { + continue; + } + + const TARGET_NAME = deploy_helpers.normalizeString(p); + + SWITCHES.filter(s => { + return TARGET_NAME === deploy_helpers.normalizeString(s.name); + }).forEach(s => { + Enumerable.from( deploy_plugins_switch.getTargetOptionsOf(s) ).where(o => { + return o.__id === OPTION_ID; + }).forEach(o => { + deploy_plugins_switch.setCurrentOptionFor(s, o); + }); + }); + } + } + } + catch (e) { + ME.log(`[ERROR :: vs-deploy] switch.reloadTargetStates(): ${deploy_helpers.toStringSafe(e)}`); + } +} + +/** + * Resets all target states for switches. + */ +export function resetTargetStates() { + return deploy_plugins_switch.resetStates(); +} + +/** + * Saves the states to the current workspace. + */ +export async function saveStates() { + const ME: vs_deploy.Deployer = this; + + try { + let newValue: SavedStates; + + const STATES = deploy_plugins_switch.switchStates; + if (STATES) { + newValue = {}; + + for (let p in STATES) { + newValue[p] = STATES[p].__id; + } + } + + await ME.context.workspaceState.update(KEY_SWITCH_STATES, + newValue); + } + catch (e) { + ME.log(`[ERROR :: vs-deploy] switch.saveStates(): ${deploy_helpers.toStringSafe(e)}`); + } +}