├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── @types
└── image-size
│ └── index.d.ts
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── TECHNICAL_OVERVIEW.md
├── package-lock.json
├── package.json
├── resources
├── dark
│ ├── icon-add.svg
│ ├── icon-calendar.svg
│ ├── icon-info.svg
│ ├── icon-jira.svg
│ ├── icon-link-external.svg
│ ├── icon-pin.svg
│ ├── icon-pinned.svg
│ ├── icon-refresh.svg
│ └── icon-search.svg
├── docs
│ └── sample.gif
├── icon-sidebar.svg
├── icon-zeplin.png
└── light
│ ├── icon-add.svg
│ ├── icon-calendar.svg
│ ├── icon-info.svg
│ ├── icon-jira.svg
│ ├── icon-link-external.svg
│ ├── icon-pin.svg
│ ├── icon-pinned.svg
│ ├── icon-refresh.svg
│ └── icon-search.svg
├── src
├── analytics
│ ├── Analytics.ts
│ └── util
│ │ ├── MixpanelHelper.ts
│ │ └── analyticsTypeUtil.ts
├── coco
│ ├── barrel
│ │ ├── codeLens
│ │ │ └── AddBarrelCodeLensCreator.ts
│ │ ├── command
│ │ │ ├── AddProjectCommand.ts
│ │ │ └── AddStyleguideCommand.ts
│ │ ├── diagnostic
│ │ │ └── BarrelDiagnosticCreator.ts
│ │ ├── flow
│ │ │ └── barrelFlow.ts
│ │ └── hover
│ │ │ ├── BarrelHoverCreator.ts
│ │ │ └── BarrelsPropertyHoverData.ts
│ ├── common
│ │ └── flow
│ │ │ └── commonFlow.ts
│ ├── component
│ │ ├── codeLens
│ │ │ ├── AddComponentsCodeLensCreator.ts
│ │ │ ├── ComponentCodeLensCreator.ts
│ │ │ └── ComponentCodeLensProvider.ts
│ │ ├── command
│ │ │ ├── AddComponentCommand.ts
│ │ │ ├── AddComponentsCommand.ts
│ │ │ └── ShowComponentInConfigCommand.ts
│ │ ├── data
│ │ │ └── ComponentPathStore.ts
│ │ ├── diagnostic
│ │ │ └── ComponentDiagnosticCreator.ts
│ │ ├── documentLink
│ │ │ └── ComponentLinkProvider.ts
│ │ ├── fileChange
│ │ │ └── componentRenameUtil.ts
│ │ ├── flow
│ │ │ └── componentFlow.ts
│ │ ├── hover
│ │ │ └── ComponentsPropertyHoverData.ts
│ │ ├── model
│ │ │ ├── Component.ts
│ │ │ └── ComponentPath.ts
│ │ └── util
│ │ │ └── componentUtil.ts
│ ├── config
│ │ ├── codeLens
│ │ │ ├── ConfigCodeLensProvider.ts
│ │ │ ├── ConfigStatisticsCodeLensCreator.ts
│ │ │ ├── CustomConfigCodeLensProvider.ts
│ │ │ ├── SetConfigCodeLensCreator.ts
│ │ │ └── SetConfigRootCodeLensCreator.ts
│ │ ├── command
│ │ │ ├── CreateConfigCommand.ts
│ │ │ ├── OpenConfigCommand.ts
│ │ │ ├── SetConfigCommand.ts
│ │ │ ├── SetConfigRootCommand.ts
│ │ │ └── UnsetConfigCommand.ts
│ │ ├── diagnostic
│ │ │ └── ConfigDiagnosticsProvider.ts
│ │ ├── fileChange
│ │ │ └── configRenameUtil.ts
│ │ ├── flow
│ │ │ └── configFlow.ts
│ │ ├── hover
│ │ │ └── ConfigHoverCreator.ts
│ │ ├── model
│ │ │ └── Config.ts
│ │ └── util
│ │ │ ├── ConfigFolderPaths.ts
│ │ │ ├── ConfigPaths.ts
│ │ │ ├── CustomConfigs.ts
│ │ │ └── configUtil.ts
│ ├── link
│ │ ├── command
│ │ │ └── AddLinkCommand.ts
│ │ ├── flow
│ │ │ └── linkFlow.ts
│ │ ├── hover
│ │ │ └── LinksPropertyHoverData.ts
│ │ └── model
│ │ │ └── Link.ts
│ ├── plugin
│ │ ├── command
│ │ │ └── AddPluginCommand.ts
│ │ ├── flow
│ │ │ └── pluginFlow.ts
│ │ ├── hover
│ │ │ └── PluginsPropertyHoverData.ts
│ │ └── model
│ │ │ └── Plugin.ts
│ ├── repository
│ │ ├── command
│ │ │ └── AddRepositoryCommand.ts
│ │ ├── flow
│ │ │ └── repositoryFlow.ts
│ │ ├── hover
│ │ │ └── RepositoryPropertyHoverData.ts
│ │ ├── model
│ │ │ ├── Repository.ts
│ │ │ └── RepositoryType.ts
│ │ └── util
│ │ │ └── repositoryUtil.ts
│ └── zeplinComponent
│ │ ├── codeLens
│ │ ├── AddZeplinComponentsCodeLensCreator.ts
│ │ └── MigrateZeplinComponentsCodeLensCreator.ts
│ │ ├── command
│ │ ├── AddZeplinComponentsCommand.ts
│ │ └── MigrateZeplinComponentsCommand.ts
│ │ ├── data
│ │ ├── ComponentStore.ts
│ │ ├── ConfigBarrelsStore.ts
│ │ ├── ConfigZeplinComponentsStore.ts
│ │ └── ZeplinComponentStore.ts
│ │ ├── diagnostic
│ │ └── ZeplinComponentDiagnosticCreator.ts
│ │ ├── fileOperation
│ │ └── askZeplinComponentMigrationUtil.ts
│ │ ├── flow
│ │ ├── migrateZeplinComponentsFlow.ts
│ │ └── zeplinComponentFlow.ts
│ │ ├── hover
│ │ └── ZeplinComponentHoverCreator.ts
│ │ ├── model
│ │ └── ZeplinComponentMigrationResult.ts
│ │ └── util
│ │ ├── zeplinComponentMigrationUtil.ts
│ │ └── zeplinComponentUtil.ts
├── common
│ ├── domain
│ │ ├── api
│ │ │ └── api.ts
│ │ ├── barrel
│ │ │ ├── Barrel.ts
│ │ │ ├── BarrelType.ts
│ │ │ ├── data
│ │ │ │ ├── BarrelDetailsStore.ts
│ │ │ │ ├── BarrelStore.ts
│ │ │ │ ├── BarrelsStoreProvider.ts
│ │ │ │ ├── OrganizationProjectsStore.ts
│ │ │ │ ├── OrganizationStyleguidesStore.ts
│ │ │ │ ├── PersonalProjectsStore.ts
│ │ │ │ ├── PersonalStyleguidesStore.ts
│ │ │ │ ├── ProjectDetailsStore.ts
│ │ │ │ ├── StyleguideDetailsStore.ts
│ │ │ │ └── WorkspacesStore.ts
│ │ │ ├── flow
│ │ │ │ └── barrelFlow.ts
│ │ │ ├── model
│ │ │ │ ├── BarrelDetailsResponse.ts
│ │ │ │ ├── OrganizationsResponse.ts
│ │ │ │ ├── ProjectDetailsResponse.ts
│ │ │ │ ├── ProjectsResponse.ts
│ │ │ │ ├── ResponseBarrel.ts
│ │ │ │ ├── StyleguideDetailsResponse.ts
│ │ │ │ ├── StyleguidesResponse.ts
│ │ │ │ └── Workspace.ts
│ │ │ └── util
│ │ │ │ ├── barrelUi.ts
│ │ │ │ └── barrelUtil.ts
│ │ ├── componentLike
│ │ │ └── model
│ │ │ │ ├── ComponentLike.ts
│ │ │ │ ├── User.ts
│ │ │ │ └── Version.ts
│ │ ├── error
│ │ │ ├── BaseError.ts
│ │ │ └── errorUi.ts
│ │ ├── extension
│ │ │ ├── configuration.ts
│ │ │ ├── extension.ts
│ │ │ └── zeplinExtensionUtil.ts
│ │ ├── hover
│ │ │ ├── ConfigProperty.ts
│ │ │ ├── ConfigPropertyHoverCreator.ts
│ │ │ ├── ConfigPropertyHoverData.ts
│ │ │ └── zeplinHoverUtil.ts
│ │ ├── image
│ │ │ └── zeplinImageUtil.ts
│ │ ├── jira
│ │ │ ├── model
│ │ │ │ ├── Jira.ts
│ │ │ │ ├── JiraAttachable.ts
│ │ │ │ └── JiraType.ts
│ │ │ └── util
│ │ │ │ └── jiraUtil.ts
│ │ ├── openInZeplin
│ │ │ ├── command
│ │ │ │ └── OpenInZeplinCommand.ts
│ │ │ ├── flow
│ │ │ │ └── openInZeplinFlow.ts
│ │ │ ├── model
│ │ │ │ ├── ApplicationType.ts
│ │ │ │ ├── ZeplinLinkType.ts
│ │ │ │ ├── ZeplinUriProvider.ts
│ │ │ │ └── ZeplinUris.ts
│ │ │ └── util
│ │ │ │ ├── openInZeplinUtil.ts
│ │ │ │ └── zeplinUris.ts
│ │ ├── screen
│ │ │ └── model
│ │ │ │ └── ResponseScreenSection.ts
│ │ ├── store
│ │ │ ├── BasicStore.ts
│ │ │ ├── CacheHolder.ts
│ │ │ ├── Result.ts
│ │ │ ├── StaticStore.ts
│ │ │ └── Store.ts
│ │ ├── tree
│ │ │ └── TreeItemContext.ts
│ │ ├── uri
│ │ │ └── UriHandler.ts
│ │ └── zeplinComponent
│ │ │ ├── data
│ │ │ ├── BarrelDetailsStoreProvider.ts
│ │ │ └── ZeplinComponentsStore.ts
│ │ │ ├── model
│ │ │ ├── BarrelDetails.ts
│ │ │ ├── BarrelError.ts
│ │ │ ├── ResponseZeplinComponent.ts
│ │ │ ├── ZeplinComponent.ts
│ │ │ ├── ZeplinComponentDescriptors.ts
│ │ │ └── ZeplinComponentSection.ts
│ │ │ └── util
│ │ │ ├── zeplinComponentUi.ts
│ │ │ └── zeplinComponentUtil.ts
│ ├── general
│ │ ├── arrayUtil.ts
│ │ ├── booleanUtil.ts
│ │ ├── dateTimeUtil.ts
│ │ ├── iconPathUtil.ts
│ │ ├── imageUtil.ts
│ │ ├── pathUtil.ts
│ │ ├── promiseUtil.ts
│ │ └── stringUtil.ts
│ └── vscode
│ │ ├── codeLens
│ │ ├── CodeLensCreator.ts
│ │ ├── CodeLensProvider.ts
│ │ └── codeLensUtil.ts
│ │ ├── command
│ │ └── Command.ts
│ │ ├── diagnostic
│ │ ├── DiagnosticCreator.ts
│ │ └── diagnosticsUtil.ts
│ │ ├── editor
│ │ ├── editorUtil.ts
│ │ └── textDocumentUtil.ts
│ │ ├── extension
│ │ ├── ContextProvider.ts
│ │ └── extensionUtil.ts
│ │ ├── fileChange
│ │ └── FileChanges.ts
│ │ ├── hover
│ │ ├── HoverBuilder.ts
│ │ ├── HoverProvider.ts
│ │ └── hoverUtil.ts
│ │ ├── ide
│ │ ├── builtinCommands.ts
│ │ ├── vscodeFeatureVersions.ts
│ │ └── vscodeUtil.ts
│ │ ├── message
│ │ ├── MessageBuilder.ts
│ │ └── MessageType.ts
│ │ ├── quickPick
│ │ └── QuickPickerProvider.ts
│ │ ├── tree
│ │ ├── ExpandedErrorTreeItem.ts
│ │ ├── TreeDataProvider.ts
│ │ ├── TreeItem.ts
│ │ └── TreeItemContextProvider.ts
│ │ ├── uri
│ │ └── uriUtil.ts
│ │ └── workspace
│ │ └── workspaceUtil.ts
├── localization
│ ├── index.ts
│ └── localization.ts
├── log
│ ├── Logger.ts
│ ├── command
│ │ └── SaveLogsCommand.ts
│ └── util
│ │ └── logUtil.ts
├── preferences
│ └── Preferences.ts
├── session
│ ├── Session.ts
│ ├── codeLens
│ │ ├── ClearCacheCodeLensCreator.ts
│ │ └── LoginCodeLensCreator.ts
│ ├── command
│ │ ├── ClearCacheCommand.ts
│ │ ├── LoginCommand.ts
│ │ ├── LogoutCommand.ts
│ │ └── ManualLoginCommand.ts
│ ├── flow
│ │ └── sessionFlow.ts
│ ├── tokenStorage
│ │ ├── KeychainTokenStorage.ts
│ │ ├── TokenStorage.ts
│ │ ├── VscodeTokenStorage.ts
│ │ └── index.ts
│ └── util
│ │ ├── Refresher.ts
│ │ └── SessionInitializer.ts
└── sidebar
│ ├── activity
│ ├── data
│ │ └── ActivityStore.ts
│ ├── model
│ │ ├── Activity.ts
│ │ ├── ComponentActivity.ts
│ │ └── ScreenActivity.ts
│ ├── tree
│ │ ├── ActivityErrorsTreeItem.ts
│ │ ├── ActivitySlotTreeItem.ts
│ │ ├── ActivityTreeDataProvider.ts
│ │ └── ActivityTreeItem.ts
│ └── util
│ │ └── activityUi.ts
│ ├── barrel
│ ├── command
│ │ ├── AddBarrelToSidebarCommand.ts
│ │ ├── AddProjectToSidebarCommand.ts
│ │ ├── AddStyleguideToSidebarCommand.ts
│ │ └── RemoveBarrelFromSidebarCommand.ts
│ ├── flow
│ │ └── barrelFlow.ts
│ ├── tree
│ │ ├── AddBarrelTreeItem.ts
│ │ ├── BarrelTreeDataProvider.ts
│ │ ├── BarrelTreeItem.ts
│ │ └── NoBarrelTreeItem.ts
│ └── util
│ │ └── barrelUtil.ts
│ ├── jira
│ ├── command
│ │ └── OpenJiraLinkCommand.ts
│ └── flow
│ │ └── jiraFlow.ts
│ ├── jumpTo
│ ├── command
│ │ └── JumpToSidebarItemCommand.ts
│ ├── data
│ │ └── JumpableItemsStore.ts
│ ├── flow
│ │ └── jumpToFlow.ts
│ └── tree
│ │ └── JumpToTreeItem.ts
│ ├── openInZeplin
│ └── command
│ │ └── OpenInZeplinOnDoubleClickCommand.ts
│ ├── pin
│ ├── command
│ │ ├── PinComponentToSidebarCommand.ts
│ │ ├── PinScreenToSidebarCommand.ts
│ │ ├── PinToSidebarCommand.ts
│ │ ├── UnpinAllFromSidebarCommand.ts
│ │ └── UnpinFromSidebarCommand.ts
│ ├── data
│ │ ├── PinnableComponentsStore.ts
│ │ └── PinnableScreensStore.ts
│ ├── flow
│ │ └── pinFlow.ts
│ ├── model
│ │ ├── ComponentPinData.ts
│ │ ├── PinData.ts
│ │ ├── PinType.ts
│ │ └── ScreenPinData.ts
│ ├── tree
│ │ ├── NoPinnedItemTreeItem.ts
│ │ └── PinTreeDataProvider.ts
│ └── util
│ │ └── pinUtil.ts
│ ├── refresh
│ ├── command
│ │ └── RefreshSidebarCommand.ts
│ └── util
│ │ └── refreshUtil.ts
│ ├── screen
│ ├── data
│ │ ├── ProjectScreensStore.ts
│ │ ├── ScreensStore.ts
│ │ └── ScreensStoreProvider.ts
│ ├── model
│ │ ├── ProjectScreens.ts
│ │ ├── ResponseScreen.ts
│ │ ├── Screen.ts
│ │ ├── ScreenSection.ts
│ │ ├── ScreensError.ts
│ │ └── ScreensResponse.ts
│ ├── tree
│ │ ├── ScreenSectionTreeItem.ts
│ │ ├── ScreenTreeItem.ts
│ │ └── ScreensTreeItem.ts
│ └── util
│ │ └── screenUi.ts
│ └── zeplinComponent
│ ├── data
│ └── CumulativeBarrelDetailsStore.ts
│ └── tree
│ ├── BarrelZeplinComponentsTreeItem.ts
│ ├── ZeplinComponentSectionTreeItem.ts
│ ├── ZeplinComponentTreeItem.ts
│ ├── ZeplinComponentsTreeItem.ts
│ └── zeplinComponentTreeUtil.ts
├── tsconfig.json
└── webpack.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [src/**.{ts,json,js}]
4 | indent_style = space
5 | indent_size = 4
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 | end_of_line = lf
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | node: true,
4 | jest: true
5 | },
6 | extends: [
7 | "@zeplin/eslint-config/node",
8 | "plugin:import/errors",
9 | "plugin:import/warnings",
10 | "plugin:import/typescript",
11 | "plugin:@typescript-eslint/recommended"
12 | ],
13 | parser: "@typescript-eslint/parser",
14 | parserOptions: {
15 | "ecmaVersion": 2018,
16 | "sourceType": "module"
17 | },
18 | plugins: ["@typescript-eslint"],
19 | settings: {
20 | "import/resolver": {
21 | typescript: { directory: "./tsconfig.json" },
22 | }
23 | },
24 | rules: {
25 | "capitalized-comments": "error",
26 | "arrow-body-style": ["error", "as-needed"],
27 | "@typescript-eslint/explicit-member-accessibility": "error",
28 | "no-sync": "off",
29 | "no-process-exit": "off",
30 | "no-process-env": "off",
31 | "@typescript-eslint/no-explicit-any": ["error", { "ignoreRestArgs": true }],
32 | "@typescript-eslint/camelcase": ["error", { "properties": "never", "ignoreDestructuring": true }],
33 | "class-methods-use-this": "off",
34 | "no-else-return": "off",
35 | "prefer-destructuring": "off",
36 | "@typescript-eslint/no-non-null-assertion": "off",
37 | "@typescript-eslint/explicit-function-return-type": "off",
38 | "import/no-unresolved": "off",
39 | "no-undefined": "off",
40 | "@typescript-eslint/no-use-before-define": "off",
41 | "semi": "off", // On by next line for ts
42 | "@typescript-eslint/semi": "error",
43 | "no-await-in-loop": "off",
44 | "no-invalid-this": "off",
45 | "no-magic-numbers": "off", // On by next line for ts
46 | "@typescript-eslint/no-magic-numbers": ["error", { "ignore": [0, 1], "ignoreEnums": true }],
47 | "valid-jsdoc": "off", // This rule is disabled because "requireReturn: false" results in false positives
48 | "@typescript-eslint/no-empty-interface": "off",
49 | "handle-callback-err": "off"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | out
3 | node_modules
4 | .vscode-test/
5 | *.vsix
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-vscode.vscode-typescript-tslint-plugin"
4 | ]
5 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Run Extension",
6 | "type": "extensionHost",
7 | "request": "launch",
8 | "runtimeExecutable": "${execPath}",
9 | "args": [
10 | "--extensionDevelopmentPath=${workspaceFolder}"
11 | ],
12 | "outFiles": [
13 | "${workspaceFolder}/dist/*.js"
14 | ],
15 | "preLaunchTask": "npm: compile"
16 | },
17 | {
18 | "name": "Extension Tests",
19 | "type": "extensionHost",
20 | "request": "launch",
21 | "runtimeExecutable": "${execPath}",
22 | "args": [
23 | "--extensionDevelopmentPath=${workspaceFolder}",
24 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
25 | ],
26 | "outFiles": [
27 | "${workspaceFolder}/out/test/**/*.js"
28 | ],
29 | "preLaunchTask": "npm: watch"
30 | }
31 | ]
32 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "out": false
4 | },
5 | "search.exclude": {
6 | "out": true
7 | },
8 | "typescript.tsc.autoDetect": "off"
9 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "npm",
6 | "script": "watch",
7 | "problemMatcher": "$tsc-watch",
8 | "isBackground": true,
9 | "presentation": {
10 | "reveal": "never"
11 | },
12 | "group": {
13 | "kind": "build",
14 | "isDefault": true
15 | }
16 | }
17 | ]
18 | }
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out
4 | src/**
5 | resources/docs/**
6 | CONTRIBUTING.md
7 | TECHNICAL_OVERVIEW.md
8 | .gitignore
9 | vsc-extension-quickstart.md
10 | node_modules
11 | tsconfig.json
12 | tslint.json
13 | webpack.config.js
14 | **/*.map
15 | **/*.ts
--------------------------------------------------------------------------------
/@types/image-size/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "image-size" {
2 | function imageSize(buffer: Buffer): Promise<{ width: number; height: number }>;
3 |
4 | export = imageSize;
5 | }
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 2.1.0
2 |
3 | - Adds double clicking an item on sidebar tree view opens item on Zeplin: Resolves [#76](https://github.com/zeplin/vscode-extension/issues/76)
4 | - Adds hover support for wildcard on Zeplin components for [Connected Components](https://zpl.io/connected-components) configuration file: Discussed in [#73](https://github.com/zeplin/vscode-extension/issues/73)
5 | - Adds online validation for [Connected Components](https://zpl.io/connected-components) configuration file.
6 | - Fixes blocking clear terminal shortcut: Resolves [#90](https://github.com/zeplin/vscode-extension/issues/90)
7 |
8 | ## 2.0.0
9 |
10 | - Adds Zeplin tree view to VS Code Sidebar
11 |
12 | ## 1.0.0
13 |
14 | - Initial release with [Connected Components](https://zpl.io/connected-components) configuration creation
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Zeplin, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/resources/dark/icon-add.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/dark/icon-calendar.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/dark/icon-info.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/dark/icon-jira.svg:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/resources/dark/icon-link-external.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/dark/icon-pin.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/dark/icon-pinned.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/dark/icon-refresh.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/dark/icon-search.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/docs/sample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zeplin/vscode-extension/7444b74fc789b96c0c25f1bfb8a6d8ca8c635ad9/resources/docs/sample.gif
--------------------------------------------------------------------------------
/resources/icon-sidebar.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/icon-zeplin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zeplin/vscode-extension/7444b74fc789b96c0c25f1bfb8a6d8ca8c635ad9/resources/icon-zeplin.png
--------------------------------------------------------------------------------
/resources/light/icon-add.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/light/icon-calendar.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/light/icon-info.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/light/icon-jira.svg:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/resources/light/icon-link-external.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/light/icon-pin.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/light/icon-pinned.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/light/icon-refresh.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/light/icon-search.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/analytics/Analytics.ts:
--------------------------------------------------------------------------------
1 | import MixpanelHelper from "./util/MixpanelHelper";
2 | import BarrelType from "../common/domain/barrel/BarrelType";
3 | import PinType from "../sidebar/pin/model/PinType";
4 | import ZeplinLinkType from "../common/domain/openInZeplin/model/ZeplinLinkType";
5 | import JiraType from "../common/domain/jira/model/JiraType";
6 | import {
7 | getAddableTypeName, getPinnableTypeName, getZeplinLinkTypeName, getJiraTypeName
8 | } from "./util/analyticsTypeUtil";
9 |
10 | class Analytics {
11 | public authenticated() {
12 | MixpanelHelper.track("Authenticated in VS Code extension");
13 | }
14 |
15 | public resourceAdded(type: BarrelType) {
16 | MixpanelHelper.track(
17 | "Added Zeplin resource in VS Code extension",
18 | { Type: getAddableTypeName(type) }
19 | );
20 | }
21 |
22 | public resourcePinned(type: PinType) {
23 | MixpanelHelper.track(
24 | "Pinned Zeplin resource in VS Code extension",
25 | { Type: getPinnableTypeName(type) }
26 | );
27 | }
28 |
29 | public zeplinLinkOpened(type: ZeplinLinkType) {
30 | MixpanelHelper.track(
31 | "Clicked Zeplin resource in VS Code extension",
32 | { Type: getZeplinLinkTypeName(type) }
33 | );
34 | }
35 |
36 | public jiraLinkOpened(type: JiraType) {
37 | MixpanelHelper.track(
38 | "Clicked Jira issue from VS Code extension",
39 | { Type: getJiraTypeName(type) }
40 | );
41 | }
42 | }
43 |
44 | export default new Analytics();
45 |
--------------------------------------------------------------------------------
/src/analytics/util/MixpanelHelper.ts:
--------------------------------------------------------------------------------
1 | import Mixpanel from "mixpanel";
2 | import { decode } from "jsonwebtoken";
3 | import { getExtensionVersion } from "../../common/vscode/extension/extensionUtil";
4 | import Session from "../../session/Session";
5 | import Logger from "../../log/Logger";
6 | import configuration from "../../common/domain/extension/configuration";
7 | import Preferences from "../../preferences/Preferences";
8 |
9 | class MixpanelHelper {
10 | private readonly mixpanel = Mixpanel.init(configuration.mixpanelToken);
11 |
12 | public async track(event: string, additionalProperties?: Mixpanel.PropertyDict, callback?: Mixpanel.Callback) {
13 | if (!Preferences.EnableTelemetry.get()) {
14 | return;
15 | }
16 |
17 | const properties: Mixpanel.PropertyDict = {
18 | "Client": "VS Code extension",
19 | "Client Version": getExtensionVersion(),
20 | ...additionalProperties
21 | };
22 |
23 | if (Session.isLoggedIn()) {
24 | const token = (await Session.getToken())!;
25 | const decodedToken = decode(token) as { sub: string } | null;
26 | if (decodedToken) {
27 | const userId = decodedToken.sub;
28 | properties.distinct_id = userId;
29 | properties.$user_id = userId;
30 | }
31 | }
32 |
33 | this.mixpanel.track(
34 | event,
35 | properties,
36 | error => {
37 | callback?.(error);
38 | if (error) {
39 | Logger.error(`Mixpanel event '${event}' could not be tracked`, error);
40 | }
41 | }
42 | );
43 | }
44 | }
45 |
46 | export default new MixpanelHelper();
47 |
--------------------------------------------------------------------------------
/src/analytics/util/analyticsTypeUtil.ts:
--------------------------------------------------------------------------------
1 | import BarrelType from "../../common/domain/barrel/BarrelType";
2 | import PinType from "../../sidebar/pin/model/PinType";
3 | import ZeplinLinkType from "../../common/domain/openInZeplin/model/ZeplinLinkType";
4 | import JiraType from "../../common/domain/jira/model/JiraType";
5 |
6 | function getAddableTypeName(type: BarrelType) {
7 | switch (type) {
8 | case BarrelType.Project:
9 | return "Project";
10 | case BarrelType.Styleguide:
11 | return "Styleguide";
12 | default:
13 | throw new Error(`Unhandled barrel type: ${type}`);
14 | }
15 | }
16 |
17 | function getPinnableTypeName(type: PinType) {
18 | switch (type) {
19 | case PinType.Screen:
20 | return "Screen";
21 | case PinType.Component:
22 | return "Component";
23 | default:
24 | throw new Error(`Unhandled pin type: ${type}`);
25 | }
26 | }
27 |
28 | function getZeplinLinkTypeName(type: ZeplinLinkType) {
29 | switch (type) {
30 | case ZeplinLinkType.Project:
31 | return "Project";
32 | case ZeplinLinkType.Styleguide:
33 | return "Styleguide";
34 | case ZeplinLinkType.ScreenSection:
35 | return "Screen Section";
36 | case ZeplinLinkType.ComponentSection:
37 | return "Component Section";
38 | case ZeplinLinkType.Screen:
39 | return "Screen";
40 | case ZeplinLinkType.Component:
41 | return "Component";
42 | default:
43 | throw new Error(`Unhandled Zeplin link type: ${type}`);
44 | }
45 | }
46 |
47 | function getJiraTypeName(type: JiraType) {
48 | switch (type) {
49 | case JiraType.Project:
50 | return "Project";
51 | case JiraType.ScreenSection:
52 | return "Screen Section";
53 | case JiraType.Screen:
54 | return "Screen";
55 | default:
56 | throw new Error(`Unhandled Jira type: ${type}`);
57 | }
58 | }
59 |
60 | export {
61 | getAddableTypeName,
62 | getPinnableTypeName,
63 | getZeplinLinkTypeName,
64 | getJiraTypeName
65 | };
66 |
--------------------------------------------------------------------------------
/src/coco/barrel/codeLens/AddBarrelCodeLensCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import CodeLensCreator from "../../../common/vscode/codeLens/CodeLensCreator";
3 | import Session from "../../../session/Session";
4 | import AddProjectCommand from "../command/AddProjectCommand";
5 | import localization from "../../../localization";
6 | import AddStyleguideCommand from "../command/AddStyleguideCommand";
7 | import { getPositionsOfProperty } from "../../../common/vscode/editor/textDocumentUtil";
8 | import { createCodeLenses } from "../../../common/vscode/codeLens/codeLensUtil";
9 | import BarrelType from "../../../common/domain/barrel/BarrelType";
10 |
11 | const KEY_PROJECTS = "projects";
12 | const KEY_STYLEGUIDES = "styleguides";
13 |
14 | class AddBarrelCodeLensCreator implements CodeLensCreator {
15 | public create(document: vscode.TextDocument): vscode.CodeLens[] {
16 | if (!Session.isLoggedIn()) {
17 | return [];
18 | } else {
19 | const projectCodeLenses = createCodeLenses(
20 | getPositionsOfProperty(KEY_PROJECTS, document),
21 | {
22 | command: AddProjectCommand.name,
23 | title: localization.common.barrel.add(BarrelType.Project)
24 | }
25 | );
26 | const styleguideCodeLenses = createCodeLenses(
27 | getPositionsOfProperty(KEY_STYLEGUIDES, document),
28 | {
29 | command: AddStyleguideCommand.name,
30 | title: localization.common.barrel.add(BarrelType.Styleguide)
31 | }
32 | );
33 |
34 | return projectCodeLenses.concat(styleguideCodeLenses);
35 | }
36 | }
37 | }
38 |
39 | export default new AddBarrelCodeLensCreator();
40 |
--------------------------------------------------------------------------------
/src/coco/barrel/command/AddProjectCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startAddProjectFlow } from "../flow/barrelFlow";
3 |
4 | class AddProjectCommand implements Command {
5 | public name = "zeplin.addProject";
6 | public execute = startAddProjectFlow;
7 | }
8 |
9 | export default new AddProjectCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/barrel/command/AddStyleguideCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startAddStyleguideFlow } from "../flow/barrelFlow";
3 |
4 | class AddStyleguideCommand implements Command {
5 | public name = "zeplin.addStyleguide";
6 | public execute = startAddStyleguideFlow;
7 | }
8 |
9 | export default new AddStyleguideCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/barrel/diagnostic/BarrelDiagnosticCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import DiagnosticCreator from "../../../common/vscode/diagnostic/DiagnosticCreator";
3 | import { getConfig } from "../../config/util/configUtil";
4 | import { isFirstOccurence, flatten } from "../../../common/general/arrayUtil";
5 | import { isBarrelIdFormatValid } from "../../../common/domain/barrel/util/barrelUtil";
6 | import localization from "../../../localization";
7 | import { createDiagnostics } from "../../../common/vscode/diagnostic/diagnosticsUtil";
8 | import BarrelType from "../../../common/domain/barrel/BarrelType";
9 | import { Config } from "../../config/model/Config";
10 | import ConfigBarrelsStore from "../../zeplinComponent/data/ConfigBarrelsStore";
11 |
12 | class BarrelDiagnosticCreator implements DiagnosticCreator {
13 | public async create(document: vscode.TextDocument): Promise {
14 | const configPath = document.uri.fsPath;
15 | const config = getConfig(configPath);
16 | const invalidFormatBarrelDiagnostics = [
17 | ...this.createInvalidFormatBarrelDiagnostics(config, BarrelType.Project, document),
18 | ...this.createInvalidFormatBarrelDiagnostics(config, BarrelType.Styleguide, document)
19 | ];
20 |
21 | const validFormatBarrelErrors = (await new ConfigBarrelsStore(configPath).get()).errors ?? [];
22 | const validFormatBarrelDiagnostics = flatten(validFormatBarrelErrors.map(
23 | ({ id, message }) => createDiagnostics(id, message, document, vscode.DiagnosticSeverity.Warning)
24 | ));
25 |
26 | return [...invalidFormatBarrelDiagnostics, ...validFormatBarrelDiagnostics];
27 | }
28 |
29 | private createInvalidFormatBarrelDiagnostics(config: Config, type: BarrelType, document: vscode.TextDocument):
30 | vscode.Diagnostic[] {
31 | return createDiagnostics(
32 | config.getBarrels(type).filter(barrelId => !isBarrelIdFormatValid(barrelId)).filter(isFirstOccurence),
33 | localization.coco.barrel.formatNotValid(type),
34 | document
35 | );
36 | }
37 | }
38 |
39 | export default new BarrelDiagnosticCreator();
40 |
--------------------------------------------------------------------------------
/src/coco/barrel/flow/barrelFlow.ts:
--------------------------------------------------------------------------------
1 | import localization from "../../../localization";
2 | import BarrelType from "../../../common/domain/barrel/BarrelType";
3 | import * as configUtil from "../../config/util/configUtil";
4 | import { showInEditor } from "../../../common/vscode/editor/editorUtil";
5 | import { selectAndValidateConfig } from "../../common/flow/commonFlow";
6 | import MessageBuilder from "../../../common/vscode/message/MessageBuilder";
7 | import MessageType from "../../../common/vscode/message/MessageType";
8 | import { pickBarrel } from "../../../common/domain/barrel/flow/barrelFlow";
9 |
10 | function startAddProjectFlow() {
11 | return startAddBarrelFlow(BarrelType.Project);
12 | }
13 | function startAddStyleguideFlow() {
14 | return startAddBarrelFlow(BarrelType.Styleguide);
15 | }
16 |
17 | async function startAddBarrelFlow(type: BarrelType) {
18 | const title = localization.common.barrel.add(type);
19 |
20 | // Validate login and select config, fail if a modifiable config is not selected
21 | const configPath = await selectAndValidateConfig(title);
22 | if (!configPath) {
23 | return;
24 | }
25 |
26 | // Picker barrel
27 | const barrel = await pickBarrel(title, type);
28 |
29 | // Fail if no barrel is selected
30 | if (!barrel) {
31 | return;
32 | }
33 |
34 | // Fail if config contains barrel
35 | if (configUtil.containsBarrel(configPath, barrel)) {
36 | MessageBuilder.with(localization.common.barrel.alreadyAdded(type)).show();
37 | showInEditor(configPath, { text: barrel.id });
38 | return;
39 | }
40 |
41 | // Add barrel
42 | configUtil.addBarrel(configPath, barrel);
43 | MessageBuilder.with(localization.coco.barrel.added(type)).setType(MessageType.Info).show();
44 | showInEditor(configPath, { text: barrel.id, onAdd: true });
45 | }
46 |
47 | export {
48 | startAddProjectFlow,
49 | startAddStyleguideFlow
50 | };
51 |
--------------------------------------------------------------------------------
/src/coco/barrel/hover/BarrelsPropertyHoverData.ts:
--------------------------------------------------------------------------------
1 | import ConfigPropertyHoverData from "../../../common/domain/hover/ConfigPropertyHoverData";
2 | import BarrelType from "../../../common/domain/barrel/BarrelType";
3 | import AddProjectCommand from "../command/AddProjectCommand";
4 | import AddStyleguideCommand from "../command/AddStyleguideCommand";
5 | import Command from "../../../common/vscode/command/Command";
6 | import localization from "../../../localization";
7 |
8 | class BarrelsPropertyHoverData implements ConfigPropertyHoverData {
9 | public info = localization.coco.barrel.propInfo(this.type);
10 | public extraInfo = localization.coco.barrel.propExtraInfo(this.type);
11 | public optional = false;
12 | public command: { name: string; text: string };
13 |
14 | public constructor(private type: BarrelType, public key: string, command: Command) {
15 | this.command = {
16 | name: command.name,
17 | text: localization.common.barrel.add(type)
18 | };
19 | }
20 | }
21 |
22 | export default {
23 | ForProjects: new BarrelsPropertyHoverData(BarrelType.Project, "projects", AddProjectCommand),
24 | ForStyleguides: new BarrelsPropertyHoverData(BarrelType.Styleguide, "styleguides", AddStyleguideCommand)
25 | };
26 |
--------------------------------------------------------------------------------
/src/coco/common/flow/commonFlow.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import * as configUtil from "../../config/util/configUtil";
3 | import { showInEditor } from "../../../common/vscode/editor/editorUtil";
4 | import Session from "../../../session/Session";
5 | import localization from "../../../localization";
6 | import { showNotLoggedInError, showNoConfigError } from "../../../common/domain/error/errorUi";
7 | import MessageBuilder from "../../../common/vscode/message/MessageBuilder";
8 | import { isFileDirty } from "../../../common/vscode/editor/textDocumentUtil";
9 | import ConfigPaths from "../../config/util/ConfigPaths";
10 |
11 | async function selectAndValidateConfig(pickerTitle: string, loginRequired = true):
12 | Promise {
13 | // Check if there are any configs, fail if not so
14 | if (!ConfigPaths.any()) {
15 | showNoConfigError();
16 | return;
17 | }
18 |
19 | // Check if user is logged, fail if not so
20 | if (loginRequired && !Session.isLoggedIn()) {
21 | showNotLoggedInError();
22 | return;
23 | }
24 |
25 | // Get active config or ask the user to do so, fail if none selected
26 | const configPath = ConfigPaths.getActive() ?? await showConfigPicker(pickerTitle);
27 | if (!configPath) {
28 | return;
29 | }
30 |
31 | // Check if selected config file is saved, fail if not so
32 | if (isFileDirty(configPath)) {
33 | MessageBuilder.with(localization.coco.common.configNotSaved).show();
34 | showInEditor(configPath);
35 | return;
36 | }
37 |
38 | // Check if selected config is valid, fail if not so
39 | if (!configUtil.isConfigValid(configPath)) {
40 | MessageBuilder.with(localization.coco.common.configInvalid).show();
41 | showInEditor(configPath);
42 | return;
43 | }
44 |
45 | return configPath;
46 | }
47 |
48 | // TODO: Add title
49 | // TODO: Show only names for selection
50 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
51 | function showConfigPicker(pickerTitle: string): Thenable {
52 | return vscode.window.showQuickPick(
53 | ConfigPaths.getAll(),
54 | { placeHolder: localization.common.selectFolder }
55 | );
56 | }
57 |
58 | export {
59 | selectAndValidateConfig
60 | };
61 |
--------------------------------------------------------------------------------
/src/coco/component/codeLens/AddComponentsCodeLensCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import CodeLensCreator from "../../../common/vscode/codeLens/CodeLensCreator";
3 | import Session from "../../../session/Session";
4 | import { getPositionsOfProperty } from "../../../common/vscode/editor/textDocumentUtil";
5 | import AddComponentsCommand from "../command/AddComponentsCommand";
6 | import localization from "../../../localization";
7 | import { createCodeLenses } from "../../../common/vscode/codeLens/codeLensUtil";
8 |
9 | const KEY_COMPONENTS = "components";
10 |
11 | class AddComponentsCodeLensCreator implements CodeLensCreator {
12 | public create(document: vscode.TextDocument): vscode.CodeLens[] {
13 | if (!Session.isLoggedIn()) {
14 | return [];
15 | } else {
16 | const codeLenses = createCodeLenses(
17 | getPositionsOfProperty(KEY_COMPONENTS, document),
18 | {
19 | command: AddComponentsCommand.name,
20 | title: localization.coco.component.addMultiple
21 | }
22 | );
23 |
24 | return codeLenses;
25 | }
26 | }
27 | }
28 |
29 | export default new AddComponentsCodeLensCreator();
30 |
--------------------------------------------------------------------------------
/src/coco/component/codeLens/ComponentCodeLensCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import CodeLensCreator from "../../../common/vscode/codeLens/CodeLensCreator";
3 | import * as configUtil from "../../config/util/configUtil";
4 | import ShowComponentInConfigCommand from "../command/ShowComponentInConfigCommand";
5 | import localization from "../../../localization";
6 | import ConfigPaths from "../../config/util/ConfigPaths";
7 |
8 | class ComponentCodeLensCreator implements CodeLensCreator {
9 | public create(document: vscode.TextDocument): vscode.CodeLens[] {
10 | const filePath = document.uri.fsPath;
11 | if (!ConfigPaths.hasForItem(filePath) ||
12 | !configUtil.isConfigValid(ConfigPaths.getForItem(filePath)) ||
13 | !configUtil.isComponentAddedToConfig(filePath)) {
14 | return [];
15 | }
16 |
17 | return [new vscode.CodeLens(
18 | new vscode.Range(0, 0, 0, 0),
19 | {
20 | command: ShowComponentInConfigCommand.name,
21 | title: localization.coco.component.zeplinComponentCount(
22 | configUtil.getZeplinComponentsCountOfComponent(filePath)
23 | ),
24 | arguments: [filePath]
25 | }
26 | )];
27 | }
28 | }
29 |
30 | export default new ComponentCodeLensCreator();
31 |
--------------------------------------------------------------------------------
/src/coco/component/codeLens/ComponentCodeLensProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import CodeLensCreator from "../../../common/vscode/codeLens/CodeLensCreator";
3 | import CodeLensProvider from "../../../common/vscode/codeLens/CodeLensProvider";
4 | import ComponentCodeLensCreator from "./ComponentCodeLensCreator";
5 |
6 | class ComponentCodeLensProvider extends CodeLensProvider {
7 | public getDocumentSelector(): vscode.DocumentSelector {
8 | return { pattern: `**` };
9 | }
10 |
11 | protected getCodeLensCreators(): CodeLensCreator[] {
12 | return [
13 | ComponentCodeLensCreator
14 | ];
15 | }
16 | }
17 |
18 | export default new ComponentCodeLensProvider();
19 |
--------------------------------------------------------------------------------
/src/coco/component/command/AddComponentCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startAddComponentFlow } from "../flow/componentFlow";
3 |
4 | class AddComponentCommand implements Command {
5 | public name = "zeplin.addComponent";
6 | public execute = startAddComponentFlow;
7 | }
8 |
9 | export default new AddComponentCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/component/command/AddComponentsCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startAddComponentsFlow } from "../flow/componentFlow";
3 |
4 | class AddComponentsCommand implements Command {
5 | public name = "zeplin.addComponents";
6 | public execute = startAddComponentsFlow;
7 | }
8 |
9 | export default new AddComponentsCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/component/command/ShowComponentInConfigCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { showComponentInConfig } from "../flow/componentFlow";
3 |
4 | class ShowComponentInConfigCommand implements Command {
5 | public name = "openInConfig";
6 |
7 | public execute(item: string) {
8 | showComponentInConfig(item);
9 | return Promise.resolve();
10 | }
11 | }
12 |
13 | export default new ShowComponentInConfigCommand();
14 |
--------------------------------------------------------------------------------
/src/coco/component/diagnostic/ComponentDiagnosticCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import DiagnosticCreator from "../../../common/vscode/diagnostic/DiagnosticCreator";
3 | import { getConfig } from "../../config/util/configUtil";
4 | import { createDiagnostics } from "../../../common/vscode/diagnostic/diagnosticsUtil";
5 | import localization from "../../../localization";
6 | import { doesComponentExist } from "../util/componentUtil";
7 | import { isFirstOccurence } from "../../../common/general/arrayUtil";
8 | import ConfigPaths from "../../config/util/ConfigPaths";
9 |
10 | class ComponentDiagnosticCreator implements DiagnosticCreator {
11 | public create(document: vscode.TextDocument): vscode.Diagnostic[] {
12 | const configPath = document.uri.fsPath;
13 | const config = getConfig(configPath);
14 | const rootPath = ConfigPaths.getRootOf(configPath);
15 | const componentRelativePaths = config.getComponents().map(component => component.path);
16 | const nonExistentComponentRelativePaths = componentRelativePaths
17 | .filter(isFirstOccurence)
18 | .filter(relativePath => !doesComponentExist(rootPath, relativePath));
19 |
20 | return createDiagnostics(
21 | nonExistentComponentRelativePaths,
22 | localization.coco.component.componentNotFound, document
23 | );
24 | }
25 | }
26 |
27 | export default new ComponentDiagnosticCreator();
28 |
--------------------------------------------------------------------------------
/src/coco/component/documentLink/ComponentLinkProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import * as path from "path";
3 | import { getConfig, isConfigValid } from "../../config/util/configUtil";
4 | import { isFirstOccurence, flatten } from "../../../common/general/arrayUtil";
5 | import { toProperty, getRangesOf } from "../../../common/vscode/editor/textDocumentUtil";
6 | import localization from "../../../localization";
7 | import { doesComponentExist } from "../util/componentUtil";
8 | import { wrapWithLogs } from "../../../log/util/logUtil";
9 | import ConfigPaths from "../../config/util/ConfigPaths";
10 |
11 | class ComponentLinkProvider implements vscode.DocumentLinkProvider {
12 | public getDocumentSelector(): vscode.DocumentSelector {
13 | return { pattern: "**" };
14 | }
15 |
16 | public provideDocumentLinks(document: vscode.TextDocument): vscode.DocumentLink[] {
17 | return wrapWithLogs(
18 | () => {
19 | const configPath = document.uri.fsPath;
20 | if (ConfigPaths.include(configPath) && !isConfigValid(configPath)) {
21 | return [];
22 | }
23 |
24 | const rootPath = ConfigPaths.getRootOf(configPath);
25 | const componentRelativePaths = getConfig(configPath)
26 | .getComponents()
27 | .map(component => component.path)
28 | .filter(relativePath => doesComponentExist(rootPath, relativePath))
29 | .filter(isFirstOccurence);
30 | const links = componentRelativePaths
31 | .map(relativePath => this.getLinksForRelativePath(relativePath, rootPath, document));
32 | return flatten(links);
33 | },
34 | ComponentLinkProvider.name,
35 | true
36 | );
37 | }
38 |
39 | private getLinksForRelativePath(relativePath: string, rootPath: string, document: vscode.TextDocument):
40 | vscode.DocumentLink[] {
41 | const searchText = toProperty(relativePath);
42 | return getRangesOf(searchText, document).map(
43 | range => ({
44 | range,
45 | target: vscode.Uri.file(path.join(rootPath, relativePath)),
46 | tooltip: localization.coco.component.goToFile
47 | })
48 | );
49 | }
50 | }
51 |
52 | export default new ComponentLinkProvider();
53 |
--------------------------------------------------------------------------------
/src/coco/component/model/Component.ts:
--------------------------------------------------------------------------------
1 | import ZeplinComponentDescriptors from "../../../common/domain/zeplinComponent/model/ZeplinComponentDescriptors";
2 |
3 | export default interface Component extends ZeplinComponentDescriptors {
4 | path: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/coco/component/model/ComponentPath.ts:
--------------------------------------------------------------------------------
1 | export default interface ComponentPath {
2 | relative: string;
3 | fs: string;
4 | }
5 |
--------------------------------------------------------------------------------
/src/coco/component/util/componentUtil.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import * as path from "path";
3 |
4 | function doesComponentExist(rootPath: string, relativePath: string): boolean {
5 | return fs.existsSync(path.join(rootPath, relativePath));
6 | }
7 |
8 | export {
9 | doesComponentExist
10 | };
11 |
--------------------------------------------------------------------------------
/src/coco/config/codeLens/ConfigCodeLensProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import ConfigPaths from "../util/ConfigPaths";
3 | import CodeLensCreator from "../../../common/vscode/codeLens/CodeLensCreator";
4 | import ClearCacheCodeLensCreator from "../../../session/codeLens/ClearCacheCodeLensCreator";
5 | import LoginCodeLensCreator from "../../../session/codeLens/LoginCodeLensCreator";
6 | import ConfigStatisticsCodeLensCreator from "./ConfigStatisticsCodeLensCreator";
7 | import AddBarrelCodeLensCreator from "../../barrel/codeLens/AddBarrelCodeLensCreator";
8 | import AddComponentsCodeLensCreator from "../../component/codeLens/AddComponentsCodeLensCreator";
9 | import AddZeplinComponentsCodeLensCreator from "../../zeplinComponent/codeLens/AddZeplinComponentsCodeLensCreator";
10 | import CodeLensProvider from "../../../common/vscode/codeLens/CodeLensProvider";
11 | import SetConfigRootCodeLensCreator from "./SetConfigRootCodeLensCreator";
12 | import MigrateZeplinComponentsCodeLensCreator from "../../zeplinComponent/codeLens/MigrateZeplinComponentsCodeLensCreator";
13 |
14 | class ConfigCodeLensProvider extends CodeLensProvider {
15 | public createWatcher(): vscode.Disposable {
16 | return vscode.workspace.onDidSaveTextDocument(document => {
17 | if (ConfigPaths.include(document.uri.fsPath)) {
18 | this.refresh();
19 | }
20 | });
21 | }
22 |
23 | public getDocumentSelector(): vscode.DocumentSelector {
24 | return { pattern: "**" };
25 | }
26 |
27 | protected getCodeLensCreators(document: vscode.TextDocument): CodeLensCreator[] {
28 | if (!ConfigPaths.include(document.uri.fsPath)) {
29 | return [];
30 | }
31 |
32 | return [
33 | LoginCodeLensCreator,
34 | ClearCacheCodeLensCreator,
35 | ConfigStatisticsCodeLensCreator,
36 | SetConfigRootCodeLensCreator,
37 | AddBarrelCodeLensCreator,
38 | AddComponentsCodeLensCreator,
39 | AddZeplinComponentsCodeLensCreator,
40 | MigrateZeplinComponentsCodeLensCreator
41 | ];
42 | }
43 | }
44 |
45 | export default new ConfigCodeLensProvider();
46 |
--------------------------------------------------------------------------------
/src/coco/config/codeLens/ConfigStatisticsCodeLensCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import CodeLensCreator from "../../../common/vscode/codeLens/CodeLensCreator";
3 | import { getConfig, isConfigValid } from "../util/configUtil";
4 | import { isFileDirty } from "../../../common/vscode/editor/textDocumentUtil";
5 | import localization from "../../../localization";
6 |
7 | class ConfigStatisticsCodeLensCreator implements CodeLensCreator {
8 | public create(document: vscode.TextDocument): vscode.CodeLens[] {
9 | if (isFileDirty(document.uri.fsPath)) {
10 | return [this.getLens(localization.coco.config.info.dirty)];
11 | }
12 |
13 | if (!isConfigValid(document.uri.fsPath)) {
14 | return [this.getLens(localization.coco.config.info.notValid)];
15 | }
16 |
17 | const config = getConfig(document.uri.fsPath);
18 | const projects = config.getProjects();
19 | const styleguides = config.getStyleguides();
20 | const components = config.getComponents();
21 | const zeplinComponents = config.getAllZeplinComponentDescriptors();
22 | const zeplinComponentsCount = zeplinComponents.zeplinIds.length + zeplinComponents.zeplinNames.length;
23 | return [
24 | this.getLens(localization.coco.config.info.projectCount(projects.length)),
25 | this.getLens(localization.coco.config.info.styleguideCount(styleguides.length)),
26 | this.getLens(localization.coco.config.info.componentCount(components.length)),
27 | this.getLens(localization.coco.config.info.zeplinComponentCount(zeplinComponentsCount))
28 | ];
29 | }
30 |
31 | private getLens(title: string): vscode.CodeLens {
32 | return new vscode.CodeLens(
33 | new vscode.Range(0, 0, 0, 0),
34 | {
35 | command: "",
36 | title
37 | }
38 | );
39 | }
40 | }
41 |
42 | export default new ConfigStatisticsCodeLensCreator();
43 |
--------------------------------------------------------------------------------
/src/coco/config/codeLens/CustomConfigCodeLensProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import CodeLensCreator from "../../../common/vscode/codeLens/CodeLensCreator";
3 | import CodeLensProvider from "../../../common/vscode/codeLens/CodeLensProvider";
4 | import SetConfigCodeLensCreator from "./SetConfigCodeLensCreator";
5 | import ConfigPaths from "../util/ConfigPaths";
6 |
7 | class CustomConfigCodeLensProvider extends CodeLensProvider {
8 | public createWatcher(): vscode.Disposable {
9 | return vscode.workspace.onDidSaveTextDocument(document => {
10 | if (!ConfigPaths.include(document.uri.fsPath)) {
11 | this.refresh();
12 | }
13 | });
14 | }
15 |
16 | public getDocumentSelector(): vscode.DocumentSelector {
17 | return { pattern: "**" };
18 | }
19 |
20 | protected getCodeLensCreators(): CodeLensCreator[] {
21 | return [
22 | SetConfigCodeLensCreator
23 | ];
24 | }
25 | }
26 |
27 | export default new CustomConfigCodeLensProvider();
28 |
--------------------------------------------------------------------------------
/src/coco/config/codeLens/SetConfigCodeLensCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import CodeLensCreator from "../../../common/vscode/codeLens/CodeLensCreator";
3 | import localization from "../../../localization";
4 | import SetConfigCommand from "../command/SetConfigCommand";
5 | import ConfigPaths from "../util/ConfigPaths";
6 | import { isConfigValid } from "../util/configUtil";
7 |
8 | class SetConfigCodeLensCreator implements CodeLensCreator {
9 | public create(document: vscode.TextDocument): vscode.CodeLens[] {
10 | const filePath = document.uri.fsPath;
11 | if (ConfigPaths.include(filePath) || document.isDirty || !isConfigValid(filePath)) {
12 | return [];
13 | }
14 |
15 | return [new vscode.CodeLens(
16 | new vscode.Range(0, 0, 0, 0),
17 | {
18 | command: SetConfigCommand.name,
19 | title: localization.coco.config.custom.setConfig,
20 | arguments: [filePath]
21 | }
22 | )];
23 | }
24 | }
25 |
26 | export default new SetConfigCodeLensCreator();
27 |
--------------------------------------------------------------------------------
/src/coco/config/codeLens/SetConfigRootCodeLensCreator.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path";
2 | import * as vscode from "vscode";
3 | import CodeLensCreator from "../../../common/vscode/codeLens/CodeLensCreator";
4 | import localization from "../../../localization";
5 | import SetConfigRootCommand from "../command/SetConfigRootCommand";
6 | import CustomConfigs from "../util/CustomConfigs";
7 | import ConfigPaths from "../util/ConfigPaths";
8 | import { getRelativePathToRootFolder } from "../../../common/vscode/workspace/workspaceUtil";
9 |
10 | class SetConfigRootCodeLensCreator implements CodeLensCreator {
11 | public create(document: vscode.TextDocument): vscode.CodeLens[] {
12 | const filePath = document.uri.fsPath;
13 | if (!CustomConfigs.include(filePath)) {
14 | return [];
15 | }
16 |
17 | const rootPath = ConfigPaths.getRootOf(filePath);
18 | const relativeRootPath = getRelativePathToRootFolder(rootPath);
19 | return [new vscode.CodeLens(
20 | new vscode.Range(0, 0, 0, 0),
21 | {
22 | command: SetConfigRootCommand.name,
23 | title: localization.coco.config.custom.currentRoot(path.join(".", relativeRootPath)),
24 | arguments: [filePath]
25 | }
26 | )];
27 | }
28 | }
29 |
30 | export default new SetConfigRootCodeLensCreator();
31 |
--------------------------------------------------------------------------------
/src/coco/config/command/CreateConfigCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { tryCreateConfig } from "../flow/configFlow";
3 |
4 | class CreateConfigCommand implements Command {
5 | public name = "zeplin.createConfig";
6 | public execute = tryCreateConfig;
7 | }
8 |
9 | export default new CreateConfigCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/config/command/OpenConfigCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { tryOpenConfig } from "../flow/configFlow";
3 |
4 | class OpenConfigCommand implements Command {
5 | public name = "zeplin.openConfig";
6 | public execute = tryOpenConfig;
7 | }
8 |
9 | export default new OpenConfigCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/config/command/SetConfigCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startSetConfigFlow } from "../flow/configFlow";
3 |
4 | class SetConfigCommand implements Command {
5 | public name = "zeplin.setConfig";
6 | public execute = startSetConfigFlow;
7 | }
8 |
9 | export default new SetConfigCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/config/command/SetConfigRootCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startSetConfigRootFlow } from "../flow/configFlow";
3 |
4 | class SetConfigRootCommand implements Command {
5 | public name = "zeplin.setConfigRoot";
6 | public execute = startSetConfigRootFlow;
7 | }
8 |
9 | export default new SetConfigRootCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/config/command/UnsetConfigCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startUnsetConfigFlow } from "../flow/configFlow";
3 |
4 | class UnsetConfigCommand implements Command {
5 | public name = "zeplin.unsetConfig";
6 | public execute = startUnsetConfigFlow;
7 | }
8 |
9 | export default new UnsetConfigCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/config/hover/ConfigHoverCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 |
3 | export default interface ConfigHoverCreator {
4 | isApplicable(configPath: string, word: string): boolean;
5 | create(configPath: string, word: string): Promise;
6 | }
7 |
--------------------------------------------------------------------------------
/src/coco/config/util/ConfigFolderPaths.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import { getRootFolderPaths } from "../../../common/vscode/workspace/workspaceUtil";
3 | import ConfigPaths from "./ConfigPaths";
4 | import CustomConfigs from "./CustomConfigs";
5 | import { isFirstOccurence } from "../../../common/general/arrayUtil";
6 | import { compareLength } from "../../../common/general/stringUtil";
7 | import { toUnixPath, removeLeadingPathSeparator } from "../../../common/general/pathUtil";
8 |
9 | class ConfigFolderPaths {
10 | private getAll(): string[] {
11 | return CustomConfigs
12 | .getAll()
13 | .map(({ rootPath }) => rootPath)
14 | .concat(getRootFolderPaths())
15 | .filter(isFirstOccurence);
16 | }
17 |
18 | public getAllWithConfig(): string[] {
19 | return CustomConfigs
20 | .getAll()
21 | .map(({ rootPath }) => rootPath)
22 | .concat(getRootFolderPaths().filter(rootPath => fs.existsSync(this.getConfigOf(rootPath))))
23 | .filter(isFirstOccurence);
24 | }
25 |
26 | public getAllWithNoConfig(): string[] {
27 | return getRootFolderPaths().filter(rootPath => !fs.existsSync(this.getConfigOf(rootPath)));
28 | }
29 |
30 | public exactlyOneWithNoConfig(): boolean {
31 | return this.getAllWithNoConfig().length === 1;
32 | }
33 |
34 | public allHaveConfig(): boolean {
35 | return getRootFolderPaths().map(this.getConfigOf).every(fs.existsSync);
36 | }
37 |
38 | public get(itemPath: string): string {
39 | return this.getAll().sort(compareLength).find(folderPath => itemPath.startsWith(folderPath))!;
40 | }
41 |
42 | public getRelativePathOf(itemPath: string): string {
43 | const rootPath = this.get(itemPath);
44 | const relativePath = toUnixPath(removeLeadingPathSeparator(itemPath.replace(rootPath, "")));
45 | return relativePath;
46 | }
47 |
48 | public getConfigOf(rootPath: string): string {
49 | return CustomConfigs.getAll().find(descriptor => rootPath === descriptor.rootPath)?.path ??
50 | ConfigPaths.getDefault(rootPath);
51 | }
52 | }
53 |
54 | export default new ConfigFolderPaths();
55 |
--------------------------------------------------------------------------------
/src/coco/link/command/AddLinkCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startAddLinkFlow } from "../flow/linkFlow";
3 |
4 | class AddLinkCommand implements Command {
5 | public name = "zeplin.addLink";
6 | public execute = startAddLinkFlow;
7 | }
8 |
9 | export default new AddLinkCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/link/flow/linkFlow.ts:
--------------------------------------------------------------------------------
1 | import MessageBuilder from "../../../common/vscode/message/MessageBuilder";
2 | import MessageType from "../../../common/vscode/message/MessageType";
3 | import { selectAndValidateConfig } from "../../common/flow/commonFlow";
4 | import { addLink } from "../../config/util/configUtil";
5 | import { showInEditor } from "../../../common/vscode/editor/editorUtil";
6 | import localization from "../../../localization";
7 |
8 | async function startAddLinkFlow() {
9 | const configPath = await selectAndValidateConfig(localization.coco.link.add, false);
10 | if (!configPath) {
11 | return;
12 | }
13 |
14 | addLink(configPath);
15 | MessageBuilder.with(localization.coco.link.added).setType(MessageType.Info).show();
16 | showInEditor(configPath, { text: "", onAdd: true });
17 | }
18 |
19 | export {
20 | startAddLinkFlow
21 | };
22 |
--------------------------------------------------------------------------------
/src/coco/link/hover/LinksPropertyHoverData.ts:
--------------------------------------------------------------------------------
1 | import AddLinkCommand from "../command/AddLinkCommand";
2 | import localization from "../../../localization";
3 | import ConfigPropertyHoverData from "../../../common/domain/hover/ConfigPropertyHoverData";
4 |
5 | class LinksPropertyHoverData implements ConfigPropertyHoverData {
6 | public key = "links";
7 | public info = localization.coco.link.propInfo;
8 | public optional = true;
9 | public properties: ConfigPropertyHoverData[] = [
10 | {
11 | key: "name",
12 | info: localization.coco.link.propNameInfo,
13 | optional: true
14 | },
15 | {
16 | key: "type",
17 | info: localization.coco.link.propTypeInfo,
18 | extraInfo: localization.coco.link.propTypeExtraInfo,
19 | optional: false
20 | },
21 | {
22 | key: "url",
23 | info: localization.coco.link.propUrlInfo,
24 | optional: false
25 | }
26 | ];
27 | public command = {
28 | name: AddLinkCommand.name,
29 | text: localization.coco.link.add
30 | };
31 | }
32 |
33 | export default new LinksPropertyHoverData();
34 |
--------------------------------------------------------------------------------
/src/coco/link/model/Link.ts:
--------------------------------------------------------------------------------
1 | export default interface Link {
2 | name?: string;
3 | type: string;
4 | url: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/coco/plugin/command/AddPluginCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startAddPluginFlow } from "../flow/pluginFlow";
3 |
4 | class AddPluginCommand implements Command {
5 | public name = "zeplin.addPlugin";
6 | public execute = startAddPluginFlow;
7 | }
8 |
9 | export default new AddPluginCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/plugin/flow/pluginFlow.ts:
--------------------------------------------------------------------------------
1 | import { selectAndValidateConfig } from "../../common/flow/commonFlow";
2 | import { addPlugin } from "../../config/util/configUtil";
3 | import { showInEditor } from "../../../common/vscode/editor/editorUtil";
4 | import MessageBuilder from "../../../common/vscode/message/MessageBuilder";
5 | import MessageType from "../../../common/vscode/message/MessageType";
6 | import localization from "../../../localization";
7 |
8 | async function startAddPluginFlow() {
9 | const configPath = await selectAndValidateConfig(localization.coco.plugin.add, false);
10 | if (!configPath) {
11 | return;
12 | }
13 |
14 | addPlugin(configPath);
15 | MessageBuilder.with(localization.coco.plugin.added).setType(MessageType.Info).show();
16 | showInEditor(configPath, { text: "", onAdd: true });
17 | }
18 |
19 | export {
20 | startAddPluginFlow
21 | };
22 |
--------------------------------------------------------------------------------
/src/coco/plugin/hover/PluginsPropertyHoverData.ts:
--------------------------------------------------------------------------------
1 | import AddPluginCommand from "../command/AddPluginCommand";
2 | import localization from "../../../localization";
3 | import ConfigPropertyHoverData from "../../../common/domain/hover/ConfigPropertyHoverData";
4 |
5 | class PluginsPropertyHoverData implements ConfigPropertyHoverData {
6 | public key = "plugins";
7 | public info = localization.coco.plugin.propInfo;
8 | public extraInfo = localization.coco.plugin.propExtraInfo;
9 | public optional = true;
10 | public properties: ConfigPropertyHoverData[] = [
11 | {
12 | key: "name",
13 | info: localization.coco.plugin.propNameInfo,
14 | optional: false
15 | },
16 | {
17 | key: "config",
18 | info: localization.coco.plugin.propConfigInfo,
19 | optional: true
20 | }
21 | ];
22 | public command = {
23 | name: AddPluginCommand.name,
24 | text: localization.coco.plugin.add
25 | };
26 | }
27 |
28 | export default new PluginsPropertyHoverData();
29 |
--------------------------------------------------------------------------------
/src/coco/plugin/model/Plugin.ts:
--------------------------------------------------------------------------------
1 | export default interface Plugin {
2 | name: string;
3 | config?: {
4 | [key: string]: unknown;
5 | };
6 | }
7 |
--------------------------------------------------------------------------------
/src/coco/repository/command/AddRepositoryCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startAddRepositoryFlow } from "../flow/repositoryFlow";
3 |
4 | class AddRepositoryCommand implements Command {
5 | public name = "zeplin.addRepository";
6 | public execute = startAddRepositoryFlow;
7 | }
8 |
9 | export default new AddRepositoryCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/repository/hover/RepositoryPropertyHoverData.ts:
--------------------------------------------------------------------------------
1 | import ConfigPropertyHoverData from "../../../common/domain/hover/ConfigPropertyHoverData";
2 | import localization from "../../../localization";
3 | import RepositoryType from "../model/RepositoryType";
4 |
5 | class RepositoryPropertyHoverData implements ConfigPropertyHoverData {
6 | public info = localization.coco.repository.propInfo(this.type);
7 | public optional = false;
8 | public properties: ConfigPropertyHoverData[] = [
9 | {
10 | key: "branch",
11 | info: localization.coco.repository.propBranchInfo(this.type),
12 | optional: true
13 | },
14 | {
15 | key: "repository",
16 | info: localization.coco.repository.propRepositoryInfo(this.type),
17 | optional: false
18 | },
19 | {
20 | key: "url",
21 | info: localization.coco.repository.propUrlInfo(this.url),
22 | optional: true
23 | }
24 | ];
25 |
26 | public constructor(private type: RepositoryType, public key: string, private url: string) { }
27 | }
28 |
29 | export default {
30 | ForGithub: new RepositoryPropertyHoverData(RepositoryType.Github, "github", "https://github.com"),
31 | ForGitlab: new RepositoryPropertyHoverData(RepositoryType.Gitlab, "gitlab", "https://gitlab.com"),
32 | ForBitbucket: new RepositoryPropertyHoverData(RepositoryType.Bitbucket, "bitbucket", "https://bitbucket.org")
33 | };
34 |
--------------------------------------------------------------------------------
/src/coco/repository/model/Repository.ts:
--------------------------------------------------------------------------------
1 | export default interface Repository {
2 | branch?: string;
3 | repository: string;
4 | url?: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/coco/repository/model/RepositoryType.ts:
--------------------------------------------------------------------------------
1 | enum RepositoryType {
2 | Github,
3 | Gitlab,
4 | Bitbucket
5 | }
6 |
7 | export default RepositoryType;
8 |
--------------------------------------------------------------------------------
/src/coco/zeplinComponent/codeLens/MigrateZeplinComponentsCodeLensCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import CodeLensCreator from "../../../common/vscode/codeLens/CodeLensCreator";
3 | import Session from "../../../session/Session";
4 | import localization from "../../../localization";
5 | import MigrateZeplinComponentsCommand from "../command/MigrateZeplinComponentsCommand";
6 | import { containsExplicitZeplinNames } from "../util/zeplinComponentMigrationUtil";
7 |
8 | class MigrateZeplinComponentsCodeLensCreator implements CodeLensCreator {
9 | public create(document: vscode.TextDocument): vscode.CodeLens[] {
10 | if (!Session.isLoggedIn()) {
11 | return [];
12 | }
13 |
14 | const configPath = document.uri.fsPath;
15 | if (!containsExplicitZeplinNames(configPath)) {
16 | return [];
17 | }
18 |
19 | return [new vscode.CodeLens(
20 | new vscode.Range(0, 0, 0, 0),
21 | {
22 | command: MigrateZeplinComponentsCommand.name,
23 | title: localization.coco.zeplinComponent.migrate,
24 | arguments: [configPath]
25 | }
26 | )];
27 | }
28 | }
29 |
30 | export default new MigrateZeplinComponentsCodeLensCreator();
31 |
--------------------------------------------------------------------------------
/src/coco/zeplinComponent/command/AddZeplinComponentsCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startAddZeplinComponentsFlow } from "../flow/zeplinComponentFlow";
3 |
4 | class AddZeplinComponentsCommand implements Command {
5 | public name = "zeplin.addZeplinComponents";
6 | public execute = startAddZeplinComponentsFlow;
7 | }
8 |
9 | export default new AddZeplinComponentsCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/zeplinComponent/command/MigrateZeplinComponentsCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startMigrateZeplinComponentsFlow } from "../flow/migrateZeplinComponentsFlow";
3 |
4 | class MigrateZeplinComponentsCommand implements Command {
5 | public name = "zeplin.migrateZeplinComponents";
6 | public execute = startMigrateZeplinComponentsFlow;
7 | }
8 |
9 | export default new MigrateZeplinComponentsCommand();
10 |
--------------------------------------------------------------------------------
/src/coco/zeplinComponent/data/ComponentStore.ts:
--------------------------------------------------------------------------------
1 | import Store from "../../../common/domain/store/Store";
2 | import { getConfig } from "../../config/util/configUtil";
3 | import Component from "../../component/model/Component";
4 | import Result from "../../../common/domain/store/Result";
5 |
6 | export class ComponentStore implements Store {
7 | public constructor(private configPath: string) { }
8 |
9 | public get = (): Promise> => Promise.resolve({
10 | data: getConfig(this.configPath).getComponents()
11 | });
12 |
13 | public refresh = this.get;
14 | }
15 |
--------------------------------------------------------------------------------
/src/coco/zeplinComponent/data/ConfigBarrelsStore.ts:
--------------------------------------------------------------------------------
1 | import Store from "../../../common/domain/store/Store";
2 | import { getConfig } from "../../config/util/configUtil";
3 | import BarrelDetailsStoreProvider from "../../../common/domain/zeplinComponent/data/BarrelDetailsStoreProvider";
4 | import BarrelDetails from "../../../common/domain/zeplinComponent/model/BarrelDetails";
5 | import Result from "../../../common/domain/store/Result";
6 | import { flatten } from "../../../common/general/arrayUtil";
7 | import BarrelError from "../../../common/domain/zeplinComponent/model/BarrelError";
8 |
9 | export default class ConfigBarrelsStore implements Store {
10 | public constructor(private path: string) { }
11 |
12 | public get = async (): Promise> => {
13 | const config = getConfig(this.path);
14 | const barrels = config.getValidBarrelsWithTypes();
15 | const results = await Promise.all(
16 | barrels.map(({ id, type }) => BarrelDetailsStoreProvider.get(id, type).get())
17 | );
18 |
19 | return {
20 | data: results.filter(result => result.data).map(result => result.data!),
21 | errors: flatten(results.map(result => result.errors))
22 | };
23 | };
24 |
25 | public refresh = (): Promise> => {
26 | BarrelDetailsStoreProvider.clearCache();
27 | return this.get();
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/src/coco/zeplinComponent/data/ConfigZeplinComponentsStore.ts:
--------------------------------------------------------------------------------
1 | import Result from "../../../common/domain/store/Result";
2 | import Store from "../../../common/domain/store/Store";
3 | import BarrelDetailsStoreProvider from "../../../common/domain/zeplinComponent/data/BarrelDetailsStoreProvider";
4 | import ZeplinComponentsStore from "../../../common/domain/zeplinComponent/data/ZeplinComponentsStore";
5 | import BarrelError from "../../../common/domain/zeplinComponent/model/BarrelError";
6 | import ZeplinComponent from "../../../common/domain/zeplinComponent/model/ZeplinComponent";
7 | import { flatten } from "../../../common/general/arrayUtil";
8 | import { getConfig } from "../../config/util/configUtil";
9 |
10 | export default class ConfigZeplinComponentsStore implements Store {
11 | public constructor(private configPath: string) { }
12 |
13 | public get = async (): Promise> => {
14 | const config = getConfig(this.configPath);
15 | const barrels = config.getValidBarrelsWithTypes();
16 | const results = await Promise.all(barrels.map(({ id, type }) => new ZeplinComponentsStore(id, type).get()));
17 |
18 | return {
19 | data: flatten(results.map(result => result.data)),
20 | errors: flatten(results.map(result => result.errors))
21 | };
22 | };
23 |
24 | public refresh = (): Promise> => {
25 | BarrelDetailsStoreProvider.clearCache();
26 | return this.get();
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/src/coco/zeplinComponent/fileOperation/askZeplinComponentMigrationUtil.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import ConfigPaths from "../../config/util/ConfigPaths";
3 | import { askZeplinComponentMigrationIfNeeded } from "../flow/migrateZeplinComponentsFlow";
4 |
5 | function askZeplinComponentMigrationOnConfigOpen(document: vscode.TextDocument) {
6 | const filePath = document.uri.fsPath;
7 | if (ConfigPaths.include(filePath)) {
8 | askZeplinComponentMigrationIfNeeded(filePath, false);
9 | }
10 | }
11 |
12 | export {
13 | askZeplinComponentMigrationOnConfigOpen
14 | };
15 |
--------------------------------------------------------------------------------
/src/coco/zeplinComponent/model/ZeplinComponentMigrationResult.ts:
--------------------------------------------------------------------------------
1 | enum ZeplinComponentMigrationResult {
2 | Migrated,
3 | NotMigrated,
4 | Canceled
5 | }
6 |
7 | export default ZeplinComponentMigrationResult;
8 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/Barrel.ts:
--------------------------------------------------------------------------------
1 | import BarrelType from "./BarrelType";
2 | import Jira from "../jira/model/Jira";
3 | import JiraAttachable from "../jira/model/JiraAttachable";
4 |
5 | export default interface Barrel extends JiraAttachable {
6 | id: string;
7 | name: string;
8 | type: BarrelType;
9 | platform: string;
10 | densityScale?: number;
11 | thumbnail?: string;
12 | itemJiras: {
13 | ofScreens: Jira[];
14 | ofScreenSections: Jira[];
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/BarrelType.ts:
--------------------------------------------------------------------------------
1 | enum BarrelType {
2 | Project,
3 | Styleguide
4 | }
5 |
6 | export default BarrelType;
7 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/data/BarrelDetailsStore.ts:
--------------------------------------------------------------------------------
1 | import BasicStore from "../../store/BasicStore";
2 | import BarrelDetails from "../../zeplinComponent/model/BarrelDetails";
3 | import BarrelDetailsResponse from "../model/BarrelDetailsResponse";
4 | import BarrelType from "../BarrelType";
5 | import { getComponentsFromSections } from "../../../../coco/zeplinComponent/util/zeplinComponentUtil";
6 | import BaseError from "../../error/BaseError";
7 | import { getProjectJiras, getProjectItemJiras } from "../../jira/util/jiraUtil";
8 | import BarrelError from "../../zeplinComponent/model/BarrelError";
9 |
10 | export default abstract class BarrelDetailsStore
11 | extends BasicStore {
12 | protected abstract type: BarrelType;
13 |
14 | public constructor(private id: string) {
15 | super();
16 | }
17 |
18 | protected async fetchData(): Promise {
19 | const result = await this.fetchBarrelDetails(this.id);
20 | return result instanceof BaseError
21 | ? new BarrelError(this.type, this.id, result.message, result.code)
22 | : result;
23 | }
24 |
25 | protected abstract fetchBarrelDetails(id: string): Promise;
26 |
27 | protected abstract getParent(response: T): string | undefined;
28 |
29 | protected extractData(response: T): BarrelDetails {
30 | return {
31 | id: response._id,
32 | name: response.name,
33 | type: this.type,
34 | platform: response.type,
35 | densityScale: response.densityScale,
36 | thumbnail: response.thumbnail,
37 | parentId: this.getParent(response),
38 | description: response.description,
39 | components: getComponentsFromSections(response, this.type),
40 | componentSections: response.componentSections,
41 | screenSections: response.sections,
42 | jiras: getProjectJiras(response),
43 | itemJiras: getProjectItemJiras(response)
44 | };
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/data/BarrelStore.ts:
--------------------------------------------------------------------------------
1 | import ResponseBarrel from "../model/ResponseBarrel";
2 | import BasicStore from "../../store/BasicStore";
3 | import Barrel from "../Barrel";
4 | import BarrelType from "../BarrelType";
5 | import { getProjectItemJiras, getProjectJiras } from "../../jira/util/jiraUtil";
6 |
7 | export default abstract class BarrelStore extends BasicStore {
8 | protected abstract type: BarrelType;
9 |
10 | protected abstract extractBarrels(response: R): ResponseBarrel[];
11 |
12 | protected extractData(response: R): Barrel[] {
13 | const responseBarrels = this.extractBarrels(response);
14 |
15 | return responseBarrels.map(responseBarrel => ({
16 | id: responseBarrel._id,
17 | name: responseBarrel.name,
18 | type: this.type,
19 | platform: responseBarrel.type,
20 | densityScale: responseBarrel.densityScale,
21 | thumbnail: responseBarrel.thumbnail,
22 | jiras: getProjectJiras(responseBarrel),
23 | itemJiras: getProjectItemJiras(responseBarrel)
24 | }));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/data/BarrelsStoreProvider.ts:
--------------------------------------------------------------------------------
1 | import OrganizationProjectsStore from "./OrganizationProjectsStore";
2 | import OrganizationStyleguidesStore from "./OrganizationStyleguidesStore";
3 | import PersonalProjectsStore from "./PersonalProjectsStore";
4 | import PersonalStyleguidesStore from "./PersonalStyleguidesStore";
5 | import Barrel from "../Barrel";
6 | import BarrelType from "../BarrelType";
7 | import Store from "../../store/Store";
8 | import CacheHolder from "../../store/CacheHolder";
9 |
10 | class BarrelsStoreProvider implements CacheHolder {
11 | private personalProjectsStore = new PersonalProjectsStore();
12 | private personalStyleguidesStore = new PersonalStyleguidesStore();
13 | private projectsStoreCache: { [organizationId: string]: Store } = {};
14 | private styleguidesStoreCache: { [organizationId: string]: Store } = {};
15 |
16 | public get(type: BarrelType, organizationId?: string): Store {
17 | return type === BarrelType.Project
18 | ? this.getProjectsStore(organizationId)
19 | : this.getStyleguidesStore(organizationId);
20 | }
21 |
22 | private getProjectsStore(organizationId?: string): Store {
23 | if (!organizationId) {
24 | return this.personalProjectsStore;
25 | } else {
26 | if (!this.projectsStoreCache[organizationId]) {
27 | this.projectsStoreCache[organizationId] = new OrganizationProjectsStore(organizationId);
28 | }
29 |
30 | return this.projectsStoreCache[organizationId];
31 | }
32 | }
33 |
34 | private getStyleguidesStore(organizationId?: string): Store {
35 | if (!organizationId) {
36 | return this.personalStyleguidesStore;
37 | } else {
38 | if (!this.styleguidesStoreCache[organizationId]) {
39 | this.styleguidesStoreCache[organizationId] = new OrganizationStyleguidesStore(organizationId);
40 | }
41 |
42 | return this.styleguidesStoreCache[organizationId];
43 | }
44 | }
45 |
46 | public clearCache() {
47 | this.personalProjectsStore.clearCache();
48 | this.personalStyleguidesStore.clearCache();
49 | this.projectsStoreCache = {};
50 | this.styleguidesStoreCache = {};
51 | }
52 | }
53 |
54 | export default new BarrelsStoreProvider();
55 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/data/OrganizationProjectsStore.ts:
--------------------------------------------------------------------------------
1 | import { getOrganizationProjects } from "../../api/api";
2 | import BarrelType from "../BarrelType";
3 | import BarrelStore from "./BarrelStore";
4 | import ResponseBarrel from "../model/ResponseBarrel";
5 | import ProjectsResponse from "../model/ProjectsResponse";
6 | import BaseError from "../../error/BaseError";
7 |
8 | export default class OrganizationProjectsStore extends BarrelStore {
9 | protected type = BarrelType.Project;
10 |
11 | public constructor(private id: string) {
12 | super();
13 | }
14 |
15 | protected fetchData(): Promise {
16 | return getOrganizationProjects(this.id);
17 | }
18 |
19 | protected extractBarrels(response: ProjectsResponse): ResponseBarrel[] {
20 | return response.projects;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/data/OrganizationStyleguidesStore.ts:
--------------------------------------------------------------------------------
1 | import { getOrganizationStyleguides } from "../../api/api";
2 | import BarrelType from "../BarrelType";
3 | import BarrelStore from "./BarrelStore";
4 | import ResponseBarrel from "../model/ResponseBarrel";
5 | import StyleguidesResponse from "../model/StyleguidesResponse";
6 | import BaseError from "../../error/BaseError";
7 |
8 | export default class OrganizationStyleguidesStore extends BarrelStore {
9 | protected type = BarrelType.Styleguide;
10 |
11 | public constructor(private id: string) {
12 | super();
13 | }
14 |
15 | protected fetchData(): Promise {
16 | return getOrganizationStyleguides(this.id);
17 | }
18 |
19 | protected extractBarrels(response: StyleguidesResponse): ResponseBarrel[] {
20 | return response.styleguides;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/data/PersonalProjectsStore.ts:
--------------------------------------------------------------------------------
1 | import { getPersonalProjects } from "../../api/api";
2 | import BarrelType from "../BarrelType";
3 | import BarrelStore from "./BarrelStore";
4 | import ResponseBarrel from "../model/ResponseBarrel";
5 | import ProjectsResponse from "../model/ProjectsResponse";
6 |
7 | export default class PersonalProjectsStore extends BarrelStore {
8 | protected type = BarrelType.Project;
9 |
10 | protected fetchData = getPersonalProjects;
11 |
12 | protected extractBarrels(response: ProjectsResponse): ResponseBarrel[] {
13 | return response.projects;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/data/PersonalStyleguidesStore.ts:
--------------------------------------------------------------------------------
1 | import { getPersonalStyleguides } from "../../api/api";
2 | import BarrelType from "../BarrelType";
3 | import BarrelStore from "./BarrelStore";
4 | import ResponseBarrel from "../model/ResponseBarrel";
5 | import StyleguidesResponse from "../model/StyleguidesResponse";
6 |
7 | export default class PersonalStyleguidesStore extends BarrelStore {
8 | protected type = BarrelType.Styleguide;
9 |
10 | protected fetchData = getPersonalStyleguides;
11 |
12 | protected extractBarrels(response: StyleguidesResponse): ResponseBarrel[] {
13 | return response.styleguides;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/data/ProjectDetailsStore.ts:
--------------------------------------------------------------------------------
1 | import { getProjectDetails } from "../../api/api";
2 | import BarrelDetailsStore from "./BarrelDetailsStore";
3 | import BarrelType from "../BarrelType";
4 | import ProjectDetailsResponse from "../model/ProjectDetailsResponse";
5 |
6 | export default class ProjectDetailsStore extends BarrelDetailsStore {
7 | protected type = BarrelType.Project;
8 |
9 | protected fetchBarrelDetails = getProjectDetails;
10 |
11 | protected getParent(response: ProjectDetailsResponse): string | undefined {
12 | return response.styleguide;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/data/StyleguideDetailsStore.ts:
--------------------------------------------------------------------------------
1 | import { getStyleguideDetails } from "../../api/api";
2 | import BarrelDetailsStore from "./BarrelDetailsStore";
3 | import BarrelType from "../BarrelType";
4 | import StyleguideDetailsResponse from "../model/StyleguideDetailsResponse";
5 |
6 | export default class StyleguideDetailsStore extends BarrelDetailsStore {
7 | protected type = BarrelType.Styleguide;
8 | private childId?: string;
9 | private childType?: BarrelType;
10 |
11 | protected fetchBarrelDetails(id: string) {
12 | return getStyleguideDetails(id, this.childId, this.childType);
13 | }
14 |
15 | protected getParent(response: StyleguideDetailsResponse): string | undefined {
16 | return response.parent;
17 | }
18 |
19 | public setChild(id: string, type: BarrelType) {
20 | this.childId = id;
21 | this.childType = type;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/data/WorkspacesStore.ts:
--------------------------------------------------------------------------------
1 | import Workspace from "../model/Workspace";
2 | import * as api from "../../api/api";
3 | import BasicStore from "../../store/BasicStore";
4 | import localization from "../../../../localization";
5 | import OrganizationsResponse from "../model/OrganizationsResponse";
6 |
7 | class WorkspacesStore extends BasicStore {
8 | protected fetchData = api.getOrganizations;
9 |
10 | protected extractData(response: OrganizationsResponse): Workspace[] {
11 | return [
12 | { name: localization.coco.barrel.personalWorkspace },
13 | ...response.organizations
14 | ];
15 | }
16 | }
17 |
18 | export default new WorkspacesStore();
19 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/flow/barrelFlow.ts:
--------------------------------------------------------------------------------
1 | import Barrel from "../Barrel";
2 | import QuickPickProvider from "../../../vscode/quickPick/QuickPickerProvider";
3 | import WorkspacesStore from "../data/WorkspacesStore";
4 | import localization from "../../../../localization";
5 | import BarrelsStoreProvider from "../data/BarrelsStoreProvider";
6 | import { getBarrelDetailRepresentation } from "../util/barrelUi";
7 | import BarrelType from "../BarrelType";
8 |
9 | async function pickBarrel(title: string, type: BarrelType): Promise {
10 | // Show workspace picker
11 | const workspaceQuickPickProvider = new QuickPickProvider(
12 | WorkspacesStore,
13 | workspace => ({
14 | label: workspace.name
15 | }),
16 | localization.common.barrel.noWorkspaceFound
17 | );
18 | workspaceQuickPickProvider.get().title = title;
19 | workspaceQuickPickProvider.get().placeholder = localization.common.barrel.selectWorkspace;
20 | const workspace = await workspaceQuickPickProvider.startSingleSelection();
21 |
22 | // Fail if no workspace is selected
23 | if (!workspace) {
24 | return;
25 | }
26 |
27 | // Show barrel picker
28 | const barrelQuickPickProvider = new QuickPickProvider(
29 | BarrelsStoreProvider.get(type, workspace._id),
30 | barrel => ({
31 | label: barrel.name,
32 | detail: getBarrelDetailRepresentation(barrel)
33 | }),
34 | localization.common.barrel.noneFound(type)
35 | );
36 | barrelQuickPickProvider.get().title = title;
37 | barrelQuickPickProvider.get().placeholder = localization.common.barrel.select(type, workspace.name);
38 | return barrelQuickPickProvider.startSingleSelection();
39 | }
40 |
41 | export {
42 | pickBarrel
43 | };
44 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/model/BarrelDetailsResponse.ts:
--------------------------------------------------------------------------------
1 | import ZeplinComponentSection from "../../zeplinComponent/model/ZeplinComponentSection";
2 | import ResponseBarrel from "./ResponseBarrel";
3 | import ResponseScreenSection from "../../screen/model/ResponseScreenSection";
4 |
5 | export default interface BarrelDetailsResponse extends ResponseBarrel {
6 | description?: string;
7 | componentSections: ZeplinComponentSection[];
8 | sections?: ResponseScreenSection[];
9 | }
10 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/model/OrganizationsResponse.ts:
--------------------------------------------------------------------------------
1 | import Workspace from "./Workspace";
2 |
3 | export default interface OrganizationsResponse {
4 | organizations: Workspace[];
5 | }
6 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/model/ProjectDetailsResponse.ts:
--------------------------------------------------------------------------------
1 | import BarrelDetailsResponse from "./BarrelDetailsResponse";
2 |
3 | export default interface ProjectDetailsResponse extends BarrelDetailsResponse {
4 | styleguide?: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/model/ProjectsResponse.ts:
--------------------------------------------------------------------------------
1 | import ResponseBarrel from "./ResponseBarrel";
2 |
3 | export default interface ProjectsResponse {
4 | projects: ResponseBarrel[];
5 | }
6 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/model/ResponseBarrel.ts:
--------------------------------------------------------------------------------
1 | import Jira from "../../jira/model/Jira";
2 |
3 | export default interface ResponseBarrel {
4 | _id: string;
5 | name: string;
6 | type: string;
7 | densityScale?: number;
8 | thumbnail?: string;
9 | integration?: {
10 | jira?: {
11 | attachments?: Jira[];
12 | };
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/model/StyleguideDetailsResponse.ts:
--------------------------------------------------------------------------------
1 | import BarrelDetailsResponse from "./BarrelDetailsResponse";
2 |
3 | export default interface StyleguideDetailsResponse extends BarrelDetailsResponse {
4 | parent?: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/model/StyleguidesResponse.ts:
--------------------------------------------------------------------------------
1 | import ResponseBarrel from "./ResponseBarrel";
2 |
3 | export default interface StyleguidesResponse {
4 | styleguides: ResponseBarrel[];
5 | }
6 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/model/Workspace.ts:
--------------------------------------------------------------------------------
1 | export default interface Workspace {
2 | _id?: string;
3 | name: string;
4 | }
5 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/util/barrelUi.ts:
--------------------------------------------------------------------------------
1 | import Barrel from "../Barrel";
2 | import localization from "../../../../localization";
3 |
4 | const DETAIL_ITEM_SEPARATOR = ", ";
5 | const PLATFORM_ANDROID = "android";
6 | const PLATFORM_REPRESENTATIONS: { [apiValue: string]: string } = Object.freeze({
7 | android: "Android",
8 | base: "Base",
9 | ios: "iOS",
10 | osx: "macOS",
11 | web: "Web"
12 | });
13 | const DENSITY_SCALE_REPRESENTATIONS_ANDROID: { [s: number]: string } = Object.freeze({
14 | 1: "mdpi",
15 | 1.5: "hdpi",
16 | 2: "xhdpi",
17 | 3: "xxhdpi",
18 | 4: "xxxhdpi"
19 | });
20 |
21 | function getPlatformRepresentation(platform: string): string | undefined {
22 | return PLATFORM_REPRESENTATIONS[platform] ?? platform;
23 | }
24 |
25 | function getDensityScaleRepresentation(platform: string, densityScale?: number): string | undefined {
26 | if (densityScale === null || densityScale === undefined) {
27 | return undefined;
28 | } else if (platform === PLATFORM_ANDROID) {
29 | return DENSITY_SCALE_REPRESENTATIONS_ANDROID[densityScale];
30 | } else {
31 | return `${densityScale}x`;
32 | }
33 | }
34 |
35 | function getBarrelDetailRepresentation(barrel: Barrel): string {
36 | return mergeRepresentations(
37 | getPlatformRepresentation(barrel.platform),
38 | getDensityScaleRepresentation(barrel.platform, barrel.densityScale)
39 | );
40 | }
41 |
42 | function getBarrelDetailRepresentationWithType(barrel: Barrel): string {
43 | return mergeRepresentations(
44 | localization.common.barrel.barrel(barrel.type),
45 | getPlatformRepresentation(barrel.platform),
46 | getDensityScaleRepresentation(barrel.platform, barrel.densityScale)
47 | );
48 | }
49 |
50 | function mergeRepresentations(...representations: (string | undefined)[]) {
51 | return representations
52 | .filter(representation => representation)
53 | .join(DETAIL_ITEM_SEPARATOR);
54 | }
55 |
56 | export {
57 | getBarrelDetailRepresentation,
58 | getBarrelDetailRepresentationWithType
59 | };
60 |
--------------------------------------------------------------------------------
/src/common/domain/barrel/util/barrelUtil.ts:
--------------------------------------------------------------------------------
1 | const BARREL_ID_VALIDATION_REGEX = /^[0-9A-Fa-f]{24}$/;
2 |
3 | function isBarrelIdFormatValid(barrelId: string): boolean {
4 | return BARREL_ID_VALIDATION_REGEX.test(barrelId);
5 | }
6 |
7 | export {
8 | isBarrelIdFormatValid
9 | };
10 |
--------------------------------------------------------------------------------
/src/common/domain/componentLike/model/ComponentLike.ts:
--------------------------------------------------------------------------------
1 | import Version from "./Version";
2 |
3 | export default interface ComponentLike {
4 | _id: string;
5 | name: string;
6 | description?: string;
7 | latestVersion: Version;
8 | }
9 |
--------------------------------------------------------------------------------
/src/common/domain/componentLike/model/User.ts:
--------------------------------------------------------------------------------
1 | export default interface User {
2 | username: string;
3 | avatar?: string;
4 | emotar?: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/common/domain/componentLike/model/Version.ts:
--------------------------------------------------------------------------------
1 | import User from "./User";
2 |
3 | export default interface Version {
4 | created: string;
5 | creator?: User;
6 | snapshot: {
7 | url: string;
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/src/common/domain/error/BaseError.ts:
--------------------------------------------------------------------------------
1 | import localization from "../../../localization";
2 |
3 | /**
4 | * Error data for api and store usage.
5 | */
6 | export default class BaseError {
7 | public constructor(
8 | public message: string = localization.common.defaultError,
9 | public code?: number
10 | ) {
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/common/domain/extension/configuration.ts:
--------------------------------------------------------------------------------
1 | declare let WPC__API_URL: string;
2 | declare let WPC__APP_URI: string;
3 | declare let WPC__WEB_URL: string;
4 | declare let WPC__IMAGE_SERVER_URL: string;
5 | declare let WPC__OAUTH_CLIENT_ID: string;
6 | declare let WPC__CONSOLE_LOGS_ENABLED: string;
7 | declare let WPC__MIXPANEL_TOKEN: string;
8 |
9 | export default {
10 | /**
11 | * Zeplin API url.
12 | */
13 | apiUrl: WPC__API_URL,
14 | /**
15 | * Zeplin Windows and Mac app uri.
16 | */
17 | appUri: WPC__APP_URI,
18 | /**
19 | * Zepin Web app url.
20 | */
21 | webUrl: WPC__WEB_URL,
22 | /**
23 | * Zeplin image server url.
24 | */
25 | imageServerUrl: WPC__IMAGE_SERVER_URL,
26 | /**
27 | * Client id for OAuth authentication.
28 | */
29 | oauthClientId: WPC__OAUTH_CLIENT_ID,
30 | /**
31 | * Whether logs are enabled.
32 | */
33 | consoleLogsEnabled: WPC__CONSOLE_LOGS_ENABLED,
34 | /**
35 | * Mixpanel Token
36 | */
37 | mixpanelToken: WPC__MIXPANEL_TOKEN
38 | };
39 |
--------------------------------------------------------------------------------
/src/common/domain/extension/zeplinExtensionUtil.ts:
--------------------------------------------------------------------------------
1 | import { extensionInitialized, isExtensionInitialized } from "../../vscode/extension/extensionUtil";
2 | import { showLoginWarningAfterInstall } from "../../../session/flow/sessionFlow";
3 | import Session from "../../../session/Session";
4 |
5 | async function showWarningsAtStartUp() {
6 | if (!isExtensionInitialized() && !Session.isLoggedIn()) {
7 | showLoginWarningAfterInstall();
8 | }
9 |
10 | await extensionInitialized();
11 | }
12 |
13 | export {
14 | showWarningsAtStartUp
15 | };
16 |
--------------------------------------------------------------------------------
/src/common/domain/hover/ConfigProperty.ts:
--------------------------------------------------------------------------------
1 | export default interface ConfigProperty {
2 | key?: string;
3 | title?: string;
4 | info?: string;
5 | extraInfo?: string;
6 | optional: boolean;
7 | properties?: ConfigProperty[];
8 | }
9 |
--------------------------------------------------------------------------------
/src/common/domain/hover/ConfigPropertyHoverData.ts:
--------------------------------------------------------------------------------
1 | import ConfigProperty from "./ConfigProperty";
2 |
3 | export default interface ConfigPropertyHoverData extends ConfigProperty {
4 | command?: { name: string; text: string };
5 | }
6 |
--------------------------------------------------------------------------------
/src/common/domain/hover/zeplinHoverUtil.ts:
--------------------------------------------------------------------------------
1 | import { getMarkdownImage } from "../../vscode/hover/hoverUtil";
2 | import { isVscodeVersionSufficient, isVscodeVersionEqualTo } from "../../vscode/ide/vscodeUtil";
3 | import { HOVER_CODICONS_MIN_VERSION, HOVER_CODICONS_BROKEN_VERSION } from "../../vscode/ide/vscodefeatureVersions";
4 | import { IconTheme, getIconPath } from "../../general/iconPathUtil";
5 |
6 | function getMarkdownIcon(codicon: string, imagePath: string): string {
7 | return isVscodeVersionSufficient(HOVER_CODICONS_MIN_VERSION) &&
8 | !isVscodeVersionEqualTo(HOVER_CODICONS_BROKEN_VERSION)
9 | ? `$(${codicon})`
10 | : getMarkdownImage(getIconPath(imagePath, IconTheme.Dark));
11 | }
12 |
13 | function getMarkdownLinkExternalIcon(): string {
14 | return getMarkdownIcon("link-external", "icon-link-external.svg");
15 | }
16 |
17 | function getMarkdownRefreshIcon(): string {
18 | return getMarkdownIcon("refresh", "icon-refresh.svg");
19 | }
20 |
21 | export {
22 | getMarkdownLinkExternalIcon,
23 | getMarkdownRefreshIcon
24 | };
25 |
--------------------------------------------------------------------------------
/src/common/domain/image/zeplinImageUtil.ts:
--------------------------------------------------------------------------------
1 | import fetch from "node-fetch";
2 | import configuration from "../extension/configuration";
3 | import { snakeCaseToPascalCase } from "../../general/stringUtil";
4 |
5 | let cache: { [key: string]: string | undefined } = {};
6 |
7 | async function getCroppedImageUrl(url: string, width: number, height: number): Promise {
8 | try {
9 | if (cache[url]) {
10 | return cache[url];
11 | }
12 |
13 | const response = (await fetch(
14 | `${configuration.imageServerUrl}/${encodeURIComponent(url)}` +
15 | `?w=${width}&cropTop=0&cropLeft=0&cropWidth=${width}&cropHeight=${height}`
16 | ));
17 |
18 | cache[url] = response.ok ? response.url : undefined;
19 | return cache[url];
20 | } catch {
21 | return undefined;
22 | }
23 | }
24 |
25 | function resetCroppedImageUrlCache() {
26 | cache = {};
27 | }
28 |
29 | function getEmotarUrl(emotar: string) {
30 | const emotarFileName = emotar === "random"
31 | ? "icRandom.svg"
32 | : `emotar${snakeCaseToPascalCase(emotar)}.png`;
33 | return `${configuration.webUrl}/img/emotars/${emotarFileName}`;
34 | }
35 |
36 | export {
37 | getCroppedImageUrl,
38 | resetCroppedImageUrlCache,
39 | getEmotarUrl
40 | };
41 |
--------------------------------------------------------------------------------
/src/common/domain/jira/model/Jira.ts:
--------------------------------------------------------------------------------
1 | import JiraType from "./JiraType";
2 |
3 | export default interface Jira {
4 | _id: string;
5 | issueKey: string;
6 | issueUrl: string;
7 | itemId: string;
8 | type: JiraType;
9 | }
10 |
--------------------------------------------------------------------------------
/src/common/domain/jira/model/JiraAttachable.ts:
--------------------------------------------------------------------------------
1 | import Jira from "./Jira";
2 |
3 | export default interface JiraAttachable {
4 | jiras: Jira[];
5 | }
6 |
--------------------------------------------------------------------------------
/src/common/domain/jira/model/JiraType.ts:
--------------------------------------------------------------------------------
1 |
2 | enum JiraType {
3 | Project = "project",
4 | ScreenSection = "section",
5 | Screen = "screen"
6 | }
7 |
8 | export default JiraType;
9 |
--------------------------------------------------------------------------------
/src/common/domain/jira/util/jiraUtil.ts:
--------------------------------------------------------------------------------
1 | import JiraType from "../model/JiraType";
2 | import Jira from "../model/Jira";
3 | import ResponseBarrel from "../../barrel/model/ResponseBarrel";
4 |
5 | function getProjectJiras(project: ResponseBarrel): Jira[] {
6 | return project.integration?.jira?.attachments?.filter(
7 | jira => jira.type === JiraType.Project && jira.itemId === project._id
8 | ) ?? [];
9 | }
10 |
11 | function getProjectItemJiras(project: ResponseBarrel): { ofScreenSections: Jira[]; ofScreens: Jira[] } {
12 | const jiras = project.integration?.jira?.attachments ?? [];
13 | return {
14 | ofScreenSections: jiras.filter(jira => jira.type === JiraType.ScreenSection),
15 | ofScreens: jiras.filter(jira => jira.type === JiraType.Screen)
16 | };
17 | }
18 |
19 | export {
20 | getProjectJiras,
21 | getProjectItemJiras
22 | };
23 |
--------------------------------------------------------------------------------
/src/common/domain/openInZeplin/command/OpenInZeplinCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../vscode/command/Command";
2 | import { openInZeplin } from "../flow/openInZeplinFlow";
3 |
4 | class OpenInZeplinCommand implements Command {
5 | public name = "zeplin.openInZeplin";
6 | public execute = openInZeplin;
7 | }
8 |
9 | export default new OpenInZeplinCommand();
10 |
--------------------------------------------------------------------------------
/src/common/domain/openInZeplin/model/ApplicationType.ts:
--------------------------------------------------------------------------------
1 | enum ApplicationType {
2 | Web = "Web",
3 | App = "App"
4 | }
5 |
6 | export default ApplicationType;
7 |
--------------------------------------------------------------------------------
/src/common/domain/openInZeplin/model/ZeplinLinkType.ts:
--------------------------------------------------------------------------------
1 | enum ZeplinLinkType {
2 | Project,
3 | Styleguide,
4 | ScreenSection,
5 | Screen,
6 | ComponentSection,
7 | Component
8 | }
9 |
10 | export default ZeplinLinkType;
11 |
--------------------------------------------------------------------------------
/src/common/domain/openInZeplin/model/ZeplinUriProvider.ts:
--------------------------------------------------------------------------------
1 | import ApplicationType from "./ApplicationType";
2 | import ZeplinLinkType from "./ZeplinLinkType";
3 |
4 | export default interface ZeplinUriProvider {
5 | getZeplinUri(applicationType: ApplicationType): string;
6 |
7 | getZeplinLinkType(): ZeplinLinkType;
8 | }
9 |
--------------------------------------------------------------------------------
/src/common/domain/openInZeplin/model/ZeplinUris.ts:
--------------------------------------------------------------------------------
1 | import ZeplinLinkType from "./ZeplinLinkType";
2 |
3 | export default interface ZeplinUris {
4 | appUri: string;
5 | webUrl: string;
6 | type: ZeplinLinkType;
7 | }
8 |
--------------------------------------------------------------------------------
/src/common/domain/openInZeplin/util/openInZeplinUtil.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import ApplicationType from "../model/ApplicationType";
3 | import ContextProvider from "../../../vscode/extension/ContextProvider";
4 | import Preferences from "../../../../preferences/Preferences";
5 |
6 | const KEY_APP_TYPE_SELECTED = "preferredAppTypeSelected";
7 |
8 | function filterPreferredAppType(setting: unknown): ApplicationType {
9 | return setting === ApplicationType.App ? ApplicationType.App : ApplicationType.Web;
10 | }
11 |
12 | function isPreferredAppTypeSelected(): boolean {
13 | return ContextProvider.get().globalState.get(KEY_APP_TYPE_SELECTED) ?? false;
14 | }
15 |
16 | async function setPreferredAppTypeSelected(type: ApplicationType) {
17 | await Preferences.PreferredApplicationType.update(type, vscode.ConfigurationTarget.Global);
18 | await ContextProvider.get().globalState.update(KEY_APP_TYPE_SELECTED, true);
19 | }
20 |
21 | export {
22 | filterPreferredAppType,
23 | isPreferredAppTypeSelected,
24 | setPreferredAppTypeSelected
25 | };
26 |
--------------------------------------------------------------------------------
/src/common/domain/screen/model/ResponseScreenSection.ts:
--------------------------------------------------------------------------------
1 | export default interface ResponseScreenSection {
2 | _id: string;
3 | name: string;
4 | description?: string;
5 | screens: string[];
6 | }
7 |
--------------------------------------------------------------------------------
/src/common/domain/store/CacheHolder.ts:
--------------------------------------------------------------------------------
1 | export default interface CacheHolder {
2 | clearCache(): void;
3 | }
4 |
--------------------------------------------------------------------------------
/src/common/domain/store/Result.ts:
--------------------------------------------------------------------------------
1 | import BaseError from "../error/BaseError";
2 |
3 | /**
4 | * Result data for api and store usage.
5 | */
6 | export default interface Result {
7 | data?: T;
8 | errors?: E[];
9 | }
10 |
--------------------------------------------------------------------------------
/src/common/domain/store/StaticStore.ts:
--------------------------------------------------------------------------------
1 | import Store from "./Store";
2 | import Result from "./Result";
3 | import BaseError from "../error/BaseError";
4 |
5 | export default class StaticStore implements Store {
6 | public constructor(private data: T) { }
7 |
8 | public get = (): Promise> => Promise.resolve({
9 | data: this.data
10 | });
11 |
12 | public refresh = this.get;
13 | }
14 |
--------------------------------------------------------------------------------
/src/common/domain/store/Store.ts:
--------------------------------------------------------------------------------
1 | import Result from "./Result";
2 | import BaseError from "../error/BaseError";
3 |
4 | /**
5 | * Store for getting and refreshing data from any source.
6 | */
7 | export default interface Store {
8 | get: () => Promise>;
9 | refresh: () => Promise>;
10 | }
11 |
--------------------------------------------------------------------------------
/src/common/domain/tree/TreeItemContext.ts:
--------------------------------------------------------------------------------
1 | enum TreeItemContext {
2 | Barrel = "barrel",
3 | Screen = "screen",
4 | ScreenSection = "screenSection",
5 | ZeplinComponentBarrel = "zeplinComponentBarrel",
6 | ZeplinComponent = "zeplinComponent",
7 | ZeplinComponentSection = "zeplinComponentSection",
8 | Activity = "activity",
9 | ZeplinLink = "zeplinLink",
10 | Jira = "jira",
11 | Pinnable = "pinnable",
12 | Pinned = "pinned"
13 | }
14 |
15 | export default TreeItemContext;
16 |
--------------------------------------------------------------------------------
/src/common/domain/uri/UriHandler.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import { completeLogin } from "../../../session/flow/sessionFlow";
3 | import { URLSearchParams } from "url";
4 | import localization from "../../../localization/localization";
5 | import MessageBuilder from "../../vscode/message/MessageBuilder";
6 | import Logger from "../../../log/Logger";
7 |
8 | const AUTHORIZED = "/authorized";
9 | const QUERY_PARAMETER_TOKEN = "access_token";
10 |
11 | class UriHandler implements vscode.UriHandler {
12 | public handleUri(uri: vscode.Uri) {
13 | Logger.log(`Uri received: ${uri.path}`);
14 |
15 | const parameters = new URLSearchParams(uri.query);
16 | switch (uri.path) {
17 | case AUTHORIZED:
18 | completeLogin(parameters.get(QUERY_PARAMETER_TOKEN) as string);
19 | break;
20 | default:
21 | MessageBuilder.with(localization.common.wrongUri).show();
22 | break;
23 | }
24 | }
25 | }
26 |
27 | export default new UriHandler();
28 |
--------------------------------------------------------------------------------
/src/common/domain/zeplinComponent/data/BarrelDetailsStoreProvider.ts:
--------------------------------------------------------------------------------
1 | import BarrelType from "../../barrel/BarrelType";
2 | import ProjectDetailsStore from "../../barrel/data/ProjectDetailsStore";
3 | import StyleguideDetailsStore from "../../barrel/data/StyleguideDetailsStore";
4 | import CacheHolder from "../../store/CacheHolder";
5 | import BarrelDetailsStore from "../../barrel/data/BarrelDetailsStore";
6 | import BarrelDetailsResponse from "../../barrel/model/BarrelDetailsResponse";
7 | import { updateSidebarItems } from "../../../../sidebar/refresh/util/refreshUtil";
8 |
9 | class BarrelDetailsStoreProvider implements CacheHolder {
10 | private cache: { [id: string]: BarrelDetailsStore } = {};
11 |
12 | public get(id: string, type: BarrelType, childId?: string, childType?: BarrelType):
13 | BarrelDetailsStore {
14 | if (!this.cache[id]) {
15 | this.cache[id] = type === BarrelType.Project ? new ProjectDetailsStore(id) : new StyleguideDetailsStore(id);
16 | this.cache[id].onDataReceived(updateSidebarItems);
17 | }
18 |
19 | if (type === BarrelType.Styleguide && childId) {
20 | (this.cache[id] as StyleguideDetailsStore).setChild(childId, childType!);
21 | }
22 |
23 | return this.cache[id];
24 | }
25 |
26 | public clearCache() {
27 | Object.keys(this.cache).forEach(key => this.cache[key].dispose());
28 | this.cache = {};
29 | }
30 |
31 | public clearCacheFor(id: string) {
32 | let currentId: string | undefined = id;
33 | while (currentId && this.cache[currentId]) {
34 | const nextId: string | undefined = this.cache[currentId].getCache()?.data?.parentId;
35 | this.cache[currentId].dispose();
36 | delete this.cache[currentId];
37 | currentId = nextId;
38 | }
39 | }
40 | }
41 |
42 | export default new BarrelDetailsStoreProvider();
43 |
--------------------------------------------------------------------------------
/src/common/domain/zeplinComponent/model/BarrelDetails.ts:
--------------------------------------------------------------------------------
1 | import Barrel from "../../barrel/Barrel";
2 | import ZeplinComponent from "./ZeplinComponent";
3 | import ZeplinComponentSection from "./ZeplinComponentSection";
4 | import ResponseScreenSection from "../../screen/model/ResponseScreenSection";
5 |
6 | export default interface BarrelDetails extends Barrel {
7 | parentId?: string;
8 | description?: string;
9 | components: ZeplinComponent[];
10 | componentSections: ZeplinComponentSection[];
11 | screenSections?: ResponseScreenSection[];
12 | }
13 |
--------------------------------------------------------------------------------
/src/common/domain/zeplinComponent/model/BarrelError.ts:
--------------------------------------------------------------------------------
1 | import BaseError from "../../error/BaseError";
2 | import BarrelType from "../../barrel/BarrelType";
3 |
4 | export default class BarrelError extends BaseError {
5 | public constructor(
6 | public type: BarrelType,
7 | public id: string,
8 | message?: string,
9 | code?: number
10 | ) {
11 | super(message, code);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/common/domain/zeplinComponent/model/ResponseZeplinComponent.ts:
--------------------------------------------------------------------------------
1 | import ComponentLike from "../../componentLike/model/ComponentLike";
2 |
3 | export default interface ResponseZeplinComponent extends ComponentLike {
4 | }
5 |
--------------------------------------------------------------------------------
/src/common/domain/zeplinComponent/model/ZeplinComponent.ts:
--------------------------------------------------------------------------------
1 | import ResponseZeplinComponent from "./ResponseZeplinComponent";
2 | import BarrelType from "../../barrel/BarrelType";
3 |
4 | export default interface ZeplinComponent extends ResponseZeplinComponent {
5 | barrelId: string;
6 | barrelType: BarrelType;
7 | barrelName: string;
8 | sectionIds: string[];
9 | sectionNames: string[];
10 | }
11 |
--------------------------------------------------------------------------------
/src/common/domain/zeplinComponent/model/ZeplinComponentDescriptors.ts:
--------------------------------------------------------------------------------
1 | export default interface ZeplinComponentDescriptors {
2 | zeplinIds?: string[];
3 | zeplinNames?: string[];
4 | }
5 |
--------------------------------------------------------------------------------
/src/common/domain/zeplinComponent/model/ZeplinComponentSection.ts:
--------------------------------------------------------------------------------
1 | import ResponseZeplinComponent from "./ResponseZeplinComponent";
2 |
3 | export default interface ZeplinComponentSection {
4 | _id: string;
5 | name: string;
6 | description?: string;
7 | components: ResponseZeplinComponent[];
8 | componentSections: ZeplinComponentSection[];
9 | }
10 |
--------------------------------------------------------------------------------
/src/common/domain/zeplinComponent/util/zeplinComponentUi.ts:
--------------------------------------------------------------------------------
1 | import ZeplinComponent from "../model/ZeplinComponent";
2 | import localization from "../../../../localization";
3 |
4 | const DETAIL_ITEM_SEPARATOR = " ▸ ";
5 |
6 | function getZeplinComponentDetailRepresentation(zeplinComponent: ZeplinComponent, showLabel = false): string {
7 | return [
8 | zeplinComponent.barrelName,
9 | showLabel ? localization.common.zeplinComponent.zeplinComponents : undefined,
10 | ...zeplinComponent.sectionNames
11 | ]
12 | .filter(representation => representation)
13 | .join(DETAIL_ITEM_SEPARATOR);
14 | }
15 |
16 | export {
17 | getZeplinComponentDetailRepresentation
18 | };
19 |
--------------------------------------------------------------------------------
/src/common/domain/zeplinComponent/util/zeplinComponentUtil.ts:
--------------------------------------------------------------------------------
1 | const ZEPLIN_COMPONENT_ID_VALIDATION_REGEX = /^[0-9A-Fa-f]{24}$/;
2 |
3 | function isZeplinComponentIdFormatValid(zeplinComponentId: string): boolean {
4 | return ZEPLIN_COMPONENT_ID_VALIDATION_REGEX.test(zeplinComponentId);
5 | }
6 |
7 | export {
8 | isZeplinComponentIdFormatValid
9 | };
10 |
--------------------------------------------------------------------------------
/src/common/general/booleanUtil.ts:
--------------------------------------------------------------------------------
1 | function isTrue(value: unknown): boolean {
2 | return typeof value === "boolean" ? value : false;
3 | }
4 |
5 | export {
6 | isTrue
7 | };
8 |
--------------------------------------------------------------------------------
/src/common/general/dateTimeUtil.ts:
--------------------------------------------------------------------------------
1 | const DAYS_IN_WEEK = 7;
2 | const MS_IN_SECOND = 1000;
3 | const SECONDS_IN_MINUTE = 60;
4 | const MS_IN_MINUTE = MS_IN_SECOND * SECONDS_IN_MINUTE;
5 | const MINUTES_IN_HOUR = 60;
6 | const MS_IN_HOUR = MINUTES_IN_HOUR * MS_IN_MINUTE;
7 | const HOURS_IN_DAY = 24;
8 | const MS_IN_DAY = HOURS_IN_DAY * MS_IN_HOUR;
9 | const DAYS_IN_MONTH = 30;
10 | const MS_IN_MONTH = DAYS_IN_MONTH * MS_IN_DAY;
11 |
12 | /**
13 | * Returns index of monday-based day
14 | */
15 | function getWeekday(date: Date) {
16 | return (date.getDay() - 1) % DAYS_IN_WEEK;
17 | }
18 |
19 | export {
20 | DAYS_IN_WEEK,
21 | MS_IN_MINUTE,
22 | MS_IN_HOUR,
23 | MS_IN_DAY,
24 | MS_IN_MONTH,
25 | getWeekday
26 | };
27 |
--------------------------------------------------------------------------------
/src/common/general/iconPathUtil.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import * as path from "path";
3 | import ContextProvider from "../vscode/extension/ContextProvider";
4 |
5 | const LIGHT_RESOURCES_FOLDER_PATH = "resources/light";
6 | const DARK_RESOURCES_FOLDER_PATH = "resources/dark";
7 |
8 | enum IconTheme {
9 | Light,
10 | Dark
11 | }
12 |
13 | type ThemedIconUris = {
14 | light: vscode.Uri;
15 | dark: vscode.Uri;
16 | };
17 |
18 | function getIconPath(fileName: string, theme: IconTheme): string {
19 | const folderPath = theme === IconTheme.Light ? LIGHT_RESOURCES_FOLDER_PATH : DARK_RESOURCES_FOLDER_PATH;
20 |
21 | return ContextProvider.get().asAbsolutePath(path.join(folderPath, fileName));
22 | }
23 |
24 | function getIconUri(fileName: string, theme: IconTheme): vscode.Uri {
25 | return vscode.Uri.file(getIconPath(fileName, theme));
26 | }
27 |
28 | function getThemedIconUris(fileName: string): ThemedIconUris {
29 | return {
30 | light: getIconUri(fileName, IconTheme.Light),
31 | dark: getIconUri(fileName, IconTheme.Dark)
32 | };
33 | }
34 |
35 | export {
36 | IconTheme,
37 | getIconPath,
38 | getIconUri,
39 | getThemedIconUris
40 | };
41 |
--------------------------------------------------------------------------------
/src/common/general/imageUtil.ts:
--------------------------------------------------------------------------------
1 | import fetch from "node-fetch";
2 | import AbortController from "abort-controller";
3 | import imageSize from "image-size";
4 |
5 | /**
6 | * Return width and height of an image by retrieving its data until metadata is received.
7 | * @param url An image url.
8 | */
9 | async function getImageSize(url: string): Promise<{ width: number; height: number }> {
10 | const abortController = new AbortController();
11 | let imageDataBuffer: Buffer | undefined;
12 | const response = await fetch(url, { signal: abortController.signal });
13 | return new Promise(resolve => {
14 | response.body.on("data", (buffer: Buffer) => {
15 | imageDataBuffer = imageDataBuffer ? Buffer.concat([imageDataBuffer, buffer]) : buffer;
16 | try {
17 | const size = imageSize(imageDataBuffer);
18 | resolve(size);
19 | abortController.abort();
20 | } catch {
21 | // Do nothing, continue receiving data
22 | }
23 | });
24 | response.body.on("finish", () => resolve({ width: 0, height: 0 }));
25 | response.body.on("error", () => resolve({ width: 0, height: 0 }));
26 | });
27 | }
28 |
29 | export {
30 | getImageSize
31 | };
32 |
--------------------------------------------------------------------------------
/src/common/general/pathUtil.ts:
--------------------------------------------------------------------------------
1 | import { unknownToLowercaseStringArray } from "./arrayUtil";
2 | import { replaceAll } from "./stringUtil";
3 |
4 | /**
5 | * Unix path separator.
6 | */
7 | const PATH_SEPARATOR = "/";
8 | /**
9 | * Windows path separator.
10 | */
11 | const PATH_SEPARATOR_WINDOWS = "\\";
12 | /**
13 | * File extension separator.
14 | */
15 | const EXTENSION_SEPARATOR = ".";
16 |
17 | /**
18 | * Converts a path to Unix path.
19 | * @param path A path.
20 | */
21 | function toUnixPath(path: string): string {
22 | return replaceAll(path, PATH_SEPARATOR_WINDOWS, PATH_SEPARATOR);
23 | }
24 |
25 | /**
26 | * Removes a path's first character if it is Unix or Windows path separator.
27 | * @param path A path.
28 | */
29 | function removeLeadingPathSeparator(path: string): string {
30 | return path.length && (path.startsWith(PATH_SEPARATOR) || path.startsWith(PATH_SEPARATOR_WINDOWS))
31 | ? path.substring(1)
32 | : path;
33 | }
34 |
35 | /**
36 | * Turns path(s) object to lowercase and unix path array.
37 | * @param paths Path(s) to be normalized.
38 | */
39 | function normalizePaths(paths: unknown): string[] {
40 | return unknownToLowercaseStringArray(paths).map(toUnixPath);
41 | }
42 |
43 | export {
44 | PATH_SEPARATOR,
45 | EXTENSION_SEPARATOR,
46 | toUnixPath,
47 | removeLeadingPathSeparator,
48 | normalizePaths
49 | };
50 |
--------------------------------------------------------------------------------
/src/common/general/promiseUtil.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns a Promise that is
3 | * - resolved when original promise is rejected.
4 | * - rejected when original promise is resolved.
5 | * @param {Promise} promise A Promise to be reversed.
6 | */
7 | async function reverse(promise: Promise): Promise {
8 | let result;
9 | try {
10 | result = await promise;
11 | } catch (error) {
12 | return error;
13 | }
14 | throw result;
15 | }
16 |
17 | /**
18 | * Returns a Promise that is
19 | * - resolved when any of the provided Promises are resolved.
20 | * - rejects when all of the provided Promises are rejected.
21 | * @param {Promise} promises An array of Promises.
22 | */
23 | function promiseAny(promises: Promise[]): Promise {
24 | /*
25 | All promises are reversed before being supplied to Promise.all method. Thus, Promise.all will be:
26 | - resolved when all promises are rejected.
27 | - rejected when one of promises is resolved. (Will not wait all promises to settle because a rejection make
28 | Promise.all to reject.)
29 |
30 | Then, the result is reversed again and resulting promise will be:
31 | - rejected when all promises are rejected.
32 | - resolved when one of promises is resolved.
33 | */
34 | return reverse(Promise.all(promises.map(reverse))) as Promise;
35 | }
36 |
37 | export {
38 | promiseAny
39 | };
40 |
--------------------------------------------------------------------------------
/src/common/general/stringUtil.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Replaces all occurrences of a value in a string with another value.
3 | * @param originalString A string which search value is searched on.
4 | * @param searchValue A value to search in the original string.
5 | * @param replaceValue A value to replace the search value with.
6 | */
7 | function replaceAll(originalString: string, searchValue: string, replaceValue: string): string {
8 | return originalString.split(searchValue).join(replaceValue);
9 | }
10 |
11 | /**
12 | * Return indices of all occurrences of a value in a string.
13 | * @param originalString A string which search string is searched on.
14 | * @param searchValue A value to search in original string.
15 | */
16 | function allIndicesOf(originalString: string, searchValue: string): number[] {
17 | const searchLength = searchValue.length;
18 | if (searchLength === 0) {
19 | return [];
20 | }
21 |
22 | const foundIndices: number[] = [];
23 | let foundIndex;
24 | let searchIndex = 0;
25 | while ((foundIndex = originalString.indexOf(searchValue, searchIndex)) >= 0) {
26 | foundIndices.push(foundIndex);
27 | searchIndex = foundIndex + searchLength;
28 | }
29 |
30 | return foundIndices;
31 | }
32 |
33 | /**
34 | * Converts given snake_case string to PascalCase
35 | * @param str A string to be converted
36 | */
37 | function snakeCaseToPascalCase(str: string): string {
38 | return uppercaseFirst(str.replace(/_\w/g, word => word.charAt(1).toUpperCase()));
39 | }
40 |
41 | /**
42 | * Returns given string as its first character uppercased
43 | * @param str A string to be converted
44 | */
45 | function uppercaseFirst(str: string) {
46 | return !str.length ? str : str.charAt(0).toUpperCase() + str.substring(1);
47 | }
48 |
49 | function compareLength(str1: string, str2: string): number {
50 | return str2.length - str1.length;
51 | }
52 |
53 | export {
54 | replaceAll,
55 | allIndicesOf,
56 | snakeCaseToPascalCase,
57 | compareLength
58 | };
59 |
--------------------------------------------------------------------------------
/src/common/vscode/codeLens/CodeLensCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 |
3 | /**
4 | * A Code Lens creator to provide to CodeLensProvider.
5 | */
6 | export default interface CodeLensCreator {
7 | /**
8 | * Creates Code Lens on a text document.
9 | * @param document A text document to create Code Lens on.
10 | */
11 | create(document: vscode.TextDocument): vscode.CodeLens[];
12 | }
13 |
--------------------------------------------------------------------------------
/src/common/vscode/codeLens/CodeLensProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import CodeLensCreator from "./CodeLensCreator";
3 | import { wrapWithLogs } from "../../../log/util/logUtil";
4 | import { flatten, isFirstOccurence } from "../../general/arrayUtil";
5 | import Logger from "../../../log/Logger";
6 |
7 | /**
8 | * A code lens provider adds commands to source text. The commands will be shown as dedicated horizontal lines in
9 | * between the source text.
10 | */
11 | export default abstract class CodeLensProvider implements vscode.CodeLensProvider {
12 | private eventEmitter = new vscode.EventEmitter();
13 |
14 | public get onDidChangeCodeLenses(): vscode.Event {
15 | return this.eventEmitter.event;
16 | }
17 |
18 | /**
19 | * Re-provides code lenses.
20 | */
21 | public refresh() {
22 | this.eventEmitter.fire();
23 | }
24 |
25 | /**
26 | * Returns a selector that defines the documents this provider is applicable to.
27 | */
28 | public abstract getDocumentSelector(): vscode.DocumentSelector;
29 |
30 | /**
31 | * Returns Code Lens creators that this provider provides.
32 | */
33 | protected abstract getCodeLensCreators(document: vscode.TextDocument): CodeLensCreator[];
34 |
35 | /**
36 | * Returns Code Lenses that are this provider create via its Code Lens creators.
37 | * @param document A text document to create Code Lenses on.
38 | */
39 | public provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] {
40 | const codeLensArrays: vscode.CodeLens[][] = this.getCodeLensCreators(document).map(
41 | creator => wrapWithLogs(
42 | () => creator.create(document),
43 | `Code Lens "${creator.constructor.name}"`,
44 | true
45 | )
46 | );
47 | const codeLenses = flatten(codeLensArrays);
48 | const distinctCommandTitles = codeLenses
49 | .filter(codeLens => codeLens.command)
50 | .map(codeLens => codeLens.command!.title)
51 | .filter(isFirstOccurence);
52 | if (distinctCommandTitles.length) {
53 | Logger.log(`Shown Code Lenses: ${distinctCommandTitles.join(", ")}`);
54 | }
55 | return codeLenses;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/common/vscode/codeLens/codeLensUtil.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 |
3 | /**
4 | * Returns a Code Lens with a position and a command.
5 | * @param position A position to create Code Lens on.
6 | * @param command A command to assign to Code Lens.
7 | */
8 | function createCodeLens(position: vscode.Position, command: vscode.Command): vscode.CodeLens {
9 | return new vscode.CodeLens(new vscode.Range(position, position), command);
10 | }
11 |
12 | /**
13 | * Returns Code Lenses with given positions and a command.
14 | * @param positions Positions to create Code Lenses on.
15 | * @param command A command to assign to Code Lenses.
16 | */
17 | function createCodeLenses(positions: vscode.Position[], command: vscode.Command): vscode.CodeLens[] {
18 | return positions.map(position => createCodeLens(position, command));
19 | }
20 |
21 | export {
22 | createCodeLens,
23 | createCodeLenses
24 | };
25 |
--------------------------------------------------------------------------------
/src/common/vscode/command/Command.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Descriptor of a Command with name and action.
3 | */
4 | export default interface Command {
5 | /**
6 | * Name of the command, must be same as the command parameter in package.json.
7 | */
8 | name: string;
9 |
10 | /**
11 | * Executes action of this command with parameters.
12 | * @param args Parameters of action.
13 | */
14 | execute(...args: unknown[]): Promise;
15 | }
16 |
--------------------------------------------------------------------------------
/src/common/vscode/diagnostic/DiagnosticCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 |
3 | /**
4 | * A diagnostic creator to provide to Diagnostic providers.
5 | */
6 | export default interface DiagnosticCreator {
7 | /**
8 | * Creates diagnostics on a text document.
9 | * @param document A text document to create diagnostics on.
10 | */
11 | create(document: vscode.TextDocument): vscode.ProviderResult;
12 | }
13 |
--------------------------------------------------------------------------------
/src/common/vscode/diagnostic/diagnosticsUtil.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import { flatten } from "../../general/arrayUtil";
3 | import { getRangesOfProperty } from "../editor/textDocumentUtil";
4 |
5 | /**
6 | * Returns Diagnostics on a text document with given property and a message.
7 | * @param propertyName Property name to search for on given text document.
8 | * @param message Message of diagnostics to be created.
9 | * @param document A text document to create diagnostics on.
10 | * @param severity Severity of diagnostics to be created
11 | */
12 | function createDiagnostics(
13 | propertyName: string,
14 | message: string,
15 | document: vscode.TextDocument,
16 | severity?: vscode.DiagnosticSeverity
17 | ): vscode.Diagnostic[];
18 |
19 | /**
20 | * Returns Diagnostics on a text document with given properties and a message.
21 | * @param propertyNames Property names to search for on given text document.
22 | * @param message Message of diagnostics to be created.
23 | * @param document A text document to create diagnostics on.
24 | * @param severity Severity of diagnostics to be created
25 | */
26 | function createDiagnostics(
27 | propertyNames: string[],
28 | message: string,
29 | document: vscode.TextDocument,
30 | severity?: vscode.DiagnosticSeverity
31 | ): vscode.Diagnostic[];
32 |
33 | function createDiagnostics(
34 | propertyNameOrNames: string | string[],
35 | message: string,
36 | document: vscode.TextDocument,
37 | severity = vscode.DiagnosticSeverity.Error
38 | ): vscode.Diagnostic[] {
39 | const propertyNames = typeof propertyNameOrNames === "string" ? [propertyNameOrNames] : propertyNameOrNames;
40 |
41 | return flatten(
42 | propertyNames.map(
43 | property => getRangesOfProperty(property, document).map(
44 | range => new vscode.Diagnostic(range, message, severity)
45 | )
46 | )
47 | );
48 | }
49 |
50 | export {
51 | createDiagnostics
52 | };
53 |
--------------------------------------------------------------------------------
/src/common/vscode/extension/ContextProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 |
3 | /**
4 | * Provides context to use throughout the extension.
5 | */
6 | class ContextProvider {
7 | private context?: vscode.ExtensionContext;
8 |
9 | /**
10 | * Sets context to provide later.
11 | * @param context Context.
12 | */
13 | public initialize(context: vscode.ExtensionContext) {
14 | this.context = context;
15 | }
16 |
17 | /**
18 | * Returns context.
19 | */
20 | public get(): vscode.ExtensionContext {
21 | return this.context!;
22 | }
23 | }
24 |
25 | export default new ContextProvider();
26 |
--------------------------------------------------------------------------------
/src/common/vscode/extension/extensionUtil.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import Command from "../command/Command";
3 | import { wrapWithLogsAsync } from "../../../log/util/logUtil";
4 | import ContextProvider from "./ContextProvider";
5 |
6 | /**
7 | * Name of the publisher.
8 | */
9 | const EXTENSION_PUBLISHER = "Zeplin";
10 | /**
11 | * Name of this extension.
12 | */
13 | const EXTENSION_NAME = "zeplin";
14 | /**
15 | * Id of this extension.
16 | */
17 | const EXTENSION_IDENTIFIER = `${EXTENSION_PUBLISHER}.${EXTENSION_NAME}`;
18 | const KEY_INITIALIZED = "initialized";
19 |
20 | /**
21 | * Returns version of this extension.
22 | */
23 | function getExtensionVersion(): string {
24 | return vscode.extensions.getExtension(EXTENSION_IDENTIFIER)!.packageJSON.version;
25 | }
26 |
27 | /**
28 | * Checks whether this extension is initialized.
29 | */
30 | function isInitialized(): boolean {
31 | return ContextProvider.get().globalState.get(KEY_INITIALIZED) ?? false;
32 | }
33 |
34 | /**
35 | * Sets this extension is initialized.
36 | */
37 | function initialized() {
38 | return ContextProvider.get().globalState.update(KEY_INITIALIZED, true);
39 | }
40 |
41 | /**
42 | * Registers command with wrapping with logs.
43 | * @param command Command
44 | */
45 | function registerCommand(command: Command) {
46 | ContextProvider.get().subscriptions.push(
47 | vscode.commands.registerCommand(
48 | command.name,
49 | (...args) => wrapWithLogsAsync(
50 | () => command.execute(...args),
51 | `Command "${command.name}"`
52 | )
53 | )
54 | );
55 | }
56 |
57 | export {
58 | EXTENSION_NAME,
59 | getExtensionVersion,
60 | isInitialized as isExtensionInitialized,
61 | initialized as extensionInitialized,
62 | registerCommand
63 | };
64 |
--------------------------------------------------------------------------------
/src/common/vscode/fileChange/FileChanges.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 |
3 | export default interface FileChanges {
4 | oldUri: vscode.Uri;
5 | newUri: vscode.Uri;
6 | }
7 |
--------------------------------------------------------------------------------
/src/common/vscode/ide/builtinCommands.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 |
3 | const COMMAND_NAME_SET_CONTEXT = "setContext";
4 |
5 | function setContext(key: string, value: unknown) {
6 | vscode.commands.executeCommand(COMMAND_NAME_SET_CONTEXT, key, value);
7 | }
8 |
9 | export {
10 | setContext
11 | };
12 |
--------------------------------------------------------------------------------
/src/common/vscode/ide/vscodeFeatureVersions.ts:
--------------------------------------------------------------------------------
1 |
2 | const HOVER_CODICONS_MIN_VERSION = "1.42";
3 | const HOVER_CODICONS_BROKEN_VERSION = "1.46.0";
4 | const VIEWS_WELCOME_MIN_VERSION = "1.44";
5 |
6 | export {
7 | HOVER_CODICONS_MIN_VERSION,
8 | HOVER_CODICONS_BROKEN_VERSION,
9 | VIEWS_WELCOME_MIN_VERSION
10 | };
11 |
--------------------------------------------------------------------------------
/src/common/vscode/ide/vscodeUtil.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 |
3 | const VERSION_SEPARATOR = ".";
4 |
5 | function isVscodeVersionSufficient(requiredVersion: string): boolean {
6 | const splittedRequiredVersion = requiredVersion.split(VERSION_SEPARATOR).map(str => Number.parseInt(str, 10));
7 | const splittedCurrentVersion = vscode.version.split(VERSION_SEPARATOR).map(str => Number.parseInt(str, 10));
8 |
9 | for (let digitIndex = 0; digitIndex < splittedRequiredVersion.length; digitIndex++) {
10 | if (splittedCurrentVersion.length <= digitIndex) {
11 | return true;
12 | } else if (splittedCurrentVersion[digitIndex] < splittedRequiredVersion[digitIndex]) {
13 | return false;
14 | } else if (splittedCurrentVersion[digitIndex] > splittedRequiredVersion[digitIndex]) {
15 | return true;
16 | }
17 | }
18 |
19 | return splittedCurrentVersion.length >= splittedRequiredVersion.length;
20 | }
21 |
22 | function isVscodeVersionEqualTo(version: string) {
23 | return vscode.version === version;
24 | }
25 |
26 | export {
27 | isVscodeVersionSufficient,
28 | isVscodeVersionEqualTo
29 | };
30 |
--------------------------------------------------------------------------------
/src/common/vscode/message/MessageType.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Type of VS Code Message.
3 | */
4 | enum MessageType {
5 | /**
6 | * Show message as information.
7 | */
8 | Info = "Info",
9 | /**
10 | * Show message as warning.
11 | */
12 | Warning = "Warning",
13 | /**
14 | * Show message as error.
15 | */
16 | Error = "Error",
17 | }
18 |
19 | export default MessageType;
20 |
--------------------------------------------------------------------------------
/src/common/vscode/tree/ExpandedErrorTreeItem.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import TreeItem from "./TreeItem";
3 |
4 | export default class ExpandedErrorTreeItem extends TreeItem {
5 | public constructor(title: string, private message: string, parent: TreeItem | undefined) {
6 | super(title, parent, undefined, vscode.TreeItemCollapsibleState.Expanded);
7 | }
8 |
9 | public getChildren(): TreeItem[] {
10 | return [new TreeItem(this.message, this)];
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/common/vscode/tree/TreeDataProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import TreeItem from "./TreeItem";
3 | import Logger from "../../../log/Logger";
4 |
5 | export default abstract class TreeDataProvider implements vscode.TreeDataProvider {
6 | protected treeView?: vscode.TreeView;
7 | protected abstract viewId: string;
8 | protected showCollapseAll = false;
9 | private eventEmitter = new vscode.EventEmitter();
10 |
11 | public get onDidChangeTreeData(): vscode.Event {
12 | return this.eventEmitter.event;
13 | }
14 |
15 | public refresh() {
16 | this.eventEmitter.fire();
17 | }
18 |
19 | public refreshItem(item?: TreeItem) {
20 | this.eventEmitter.fire(item);
21 | }
22 |
23 | public getTreeItem(item: TreeItem): TreeItem {
24 | return item;
25 | }
26 |
27 | public getChildren(element?: TreeItem | undefined): vscode.ProviderResult {
28 | return element ? element.getChildren() : this.getRoots();
29 | }
30 |
31 | public register(): vscode.Disposable {
32 | this.treeView = vscode.window.createTreeView(this.viewId, {
33 | treeDataProvider: this,
34 | showCollapseAll: this.showCollapseAll
35 | });
36 | const visibilityChangeLogger = this.treeView!.onDidChangeVisibility(({ visible }) => {
37 | Logger.log(`Tree "${this.viewId}" visibility changed: ${visible}`);
38 | });
39 |
40 | return vscode.Disposable.from(this.treeView, visibilityChangeLogger);
41 | }
42 |
43 | public abstract getRoots(): vscode.ProviderResult;
44 |
45 | public getParent(element: TreeItem): TreeItem | undefined {
46 | return element.getParent();
47 | }
48 |
49 | protected reveal(treeItem: TreeItem | undefined, itemDescription: string) {
50 | if (treeItem) {
51 | this.treeView?.reveal(treeItem);
52 | } else {
53 | Logger.log(`${itemDescription} could not be located on ${this.viewId}`);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/common/vscode/tree/TreeItem.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import TreeItemContextProvider from "./TreeItemContextProvider";
3 | import { getCroppedImageUrl } from "../../domain/image/zeplinImageUtil";
4 | import { refreshItem } from "../../../sidebar/refresh/util/refreshUtil";
5 | import TreeItemContext from "../../domain/tree/TreeItemContext";
6 | import OpenInZeplinOnDoubleClickCommand from "../../../sidebar/openInZeplin/command/OpenInZeplinOnDoubleClickCommand";
7 |
8 | const ICON_SIZE = 32;
9 |
10 | export default class TreeItem extends vscode.TreeItem {
11 | public constructor(
12 | label: string,
13 | private parent: TreeItem | undefined,
14 | private contextProvider?: TreeItemContextProvider,
15 | collapsibleState?: vscode.TreeItemCollapsibleState
16 | ) {
17 | super(label, collapsibleState);
18 | this.contextValue = this.contextProvider?.get();
19 | if (contextProvider?.contains(TreeItemContext.ZeplinLink)) {
20 | this.command = {
21 | command: OpenInZeplinOnDoubleClickCommand.name,
22 | title: "",
23 | arguments: [this]
24 | };
25 | }
26 | }
27 |
28 | public containsContext(contextValue: string): boolean {
29 | return this.contextProvider?.contains(contextValue) ?? false;
30 | }
31 |
32 | public getChildren(): vscode.ProviderResult {
33 | return [];
34 | }
35 |
36 | public getParent(): TreeItem | undefined {
37 | return this.parent;
38 | }
39 |
40 | protected async setRemoteIconPath(path: string | undefined) {
41 | if (path) {
42 | const croppedImageUrl = await getCroppedImageUrl(path, ICON_SIZE, ICON_SIZE);
43 | this.iconPath = croppedImageUrl ? vscode.Uri.parse(croppedImageUrl) : undefined;
44 |
45 | refreshItem(this);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/common/vscode/tree/TreeItemContextProvider.ts:
--------------------------------------------------------------------------------
1 | const WORD_SEPARATOR = "/";
2 |
3 | export default class TreeItemContextProvider {
4 | private readonly contextValues: string[];
5 | private readonly context: string;
6 |
7 | public constructor(...contextValues: string[]) {
8 | this.contextValues = contextValues;
9 | this.context = contextValues.join(WORD_SEPARATOR);
10 | }
11 |
12 | public contains(contextValue: string): boolean {
13 | return this.contextValues.includes(contextValue);
14 | }
15 |
16 | public get(): string {
17 | return this.context;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/common/vscode/uri/uriUtil.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 |
3 | /**
4 | * Determines whether a text is a URI.
5 | * @param text A text to be checked.
6 | */
7 | function isUri(text: string): boolean {
8 | try {
9 | vscode.Uri.parse(text, true);
10 | return true;
11 | } catch (exception) {
12 | return false;
13 | }
14 | }
15 |
16 | export {
17 | isUri
18 | };
19 |
--------------------------------------------------------------------------------
/src/localization/index.ts:
--------------------------------------------------------------------------------
1 | import localization from "./localization";
2 |
3 | export default localization;
4 |
--------------------------------------------------------------------------------
/src/log/command/SaveLogsCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../common/vscode/command/Command";
2 | import Logger from "../Logger";
3 |
4 | class SaveLogsCommand implements Command {
5 | public name = "zeplin.saveLogs";
6 |
7 | public execute() {
8 | Logger.saveToFile();
9 | return Promise.resolve();
10 | }
11 | }
12 |
13 | export default new SaveLogsCommand();
14 |
--------------------------------------------------------------------------------
/src/log/util/logUtil.ts:
--------------------------------------------------------------------------------
1 | import Logger from "../Logger";
2 |
3 | const NONSEQUENTIAL_OPERATION_MARKER = "################";
4 |
5 | function wrapWithLogs(method: () => T, logMarker: string, onlyExceptions = false): T {
6 | try {
7 | log(`${logMarker} started`, onlyExceptions);
8 | const result = method();
9 | log(`${logMarker} finished`, onlyExceptions);
10 | return result;
11 | } catch (error) {
12 | logError(logMarker, error);
13 | throw error;
14 | }
15 | }
16 |
17 | async function wrapWithLogsAsync(method: () => Promise, logMarker: string, onlyExceptions = false): Promise {
18 | try {
19 | log(`${logMarker} started`, onlyExceptions);
20 | const result = await method();
21 | log(`${logMarker} finished`, onlyExceptions);
22 | return result;
23 | } catch (error) {
24 | logError(logMarker, error);
25 | throw error;
26 | }
27 | }
28 |
29 | function log(message: string, skip = false) {
30 | if (!skip) {
31 | Logger.log(`${NONSEQUENTIAL_OPERATION_MARKER} ${message}`);
32 | }
33 | }
34 |
35 | function logError(message: string, error: Error) {
36 | Logger.error(`${NONSEQUENTIAL_OPERATION_MARKER} ${message} failed`, error);
37 | }
38 |
39 | export {
40 | wrapWithLogs,
41 | wrapWithLogsAsync
42 | };
43 |
--------------------------------------------------------------------------------
/src/session/Session.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as vscode from "vscode";
3 | import { TokenStorage } from "./tokenStorage";
4 | import ContextProvider from "../common/vscode/extension/ContextProvider";
5 | import { setContext } from "../common/vscode/ide/builtinCommands";
6 |
7 | const STATE_KEY_LOGGED_IN = "zeplinLoggedIn";
8 | const CONTEXT_KEY_LOGGED_IN = "zeplin:loggedIn";
9 |
10 | /**
11 | * Session data storage wrapper.
12 | */
13 | class Session {
14 | private memento?: vscode.Memento;
15 | private tokenStorage?: TokenStorage;
16 |
17 | /**
18 | * Initializes session data with a token storage.
19 | * @param tokenStorage An available token storage mechanism.
20 | */
21 | public initialize(tokenStorage: TokenStorage) {
22 | this.memento = ContextProvider.get().globalState;
23 | this.tokenStorage = tokenStorage;
24 | setContext(CONTEXT_KEY_LOGGED_IN, this.isLoggedIn());
25 | }
26 |
27 | /**
28 | * Determines whether user is logged in.
29 | */
30 | public isLoggedIn(): boolean {
31 | return this.memento!.get(STATE_KEY_LOGGED_IN) ?? false;
32 | }
33 |
34 | /**
35 | * Returns session token.
36 | */
37 | public getToken(): Promise {
38 | return this.tokenStorage!.get();
39 | }
40 |
41 | /**
42 | * Saves session token.
43 | * @param token A session token to save.
44 | */
45 | public setToken(token: string) {
46 | this.memento!.update(STATE_KEY_LOGGED_IN, true);
47 | setContext(CONTEXT_KEY_LOGGED_IN, true);
48 | return this.tokenStorage!.set(token);
49 | }
50 |
51 | /**
52 | * Removes session token.
53 | */
54 | public removeToken() {
55 | this.memento!.update(STATE_KEY_LOGGED_IN, false);
56 | setContext(CONTEXT_KEY_LOGGED_IN, false);
57 | return this.tokenStorage!.remove();
58 | }
59 | }
60 |
61 | export default new Session();
62 |
--------------------------------------------------------------------------------
/src/session/codeLens/ClearCacheCodeLensCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import CodeLensCreator from "../../common/vscode/codeLens/CodeLensCreator";
3 | import localization from "../../localization/localization";
4 | import ClearCacheCommand from "../command/ClearCacheCommand";
5 | import Session from "../Session";
6 |
7 | class ClearCacheCodeLensCreator implements CodeLensCreator {
8 | public create(): vscode.CodeLens[] {
9 | if (!Session.isLoggedIn()) {
10 | return [];
11 | } else {
12 | return [new vscode.CodeLens(
13 | new vscode.Range(0, 0, 0, 0),
14 | {
15 | command: ClearCacheCommand.name,
16 | title: localization.session.clearCache
17 | }
18 | )];
19 | }
20 | }
21 | }
22 |
23 | export default new ClearCacheCodeLensCreator();
24 |
--------------------------------------------------------------------------------
/src/session/codeLens/LoginCodeLensCreator.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import CodeLensCreator from "../../common/vscode/codeLens/CodeLensCreator";
3 | import Session from "../Session";
4 | import LoginCommand from "../command/LoginCommand";
5 | import localization from "../../localization/localization";
6 |
7 | class LoginCodeLensCreator implements CodeLensCreator {
8 | public create(): vscode.CodeLens[] {
9 | if (Session.isLoggedIn()) {
10 | return [];
11 | } else {
12 | return [new vscode.CodeLens(
13 | new vscode.Range(0, 0, 0, 0),
14 | {
15 | command: LoginCommand.name,
16 | title: localization.session.loginToZeplin
17 | }
18 | )];
19 | }
20 | }
21 | }
22 |
23 | export default new LoginCodeLensCreator();
24 |
--------------------------------------------------------------------------------
/src/session/command/ClearCacheCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../common/vscode/command/Command";
2 | import { clearCacheAndNotify } from "../flow/sessionFlow";
3 |
4 | class ClearCacheCommand implements Command {
5 | public name = "zeplin.clearCache";
6 |
7 | public execute() {
8 | clearCacheAndNotify();
9 | return Promise.resolve();
10 | }
11 | }
12 |
13 | export default new ClearCacheCommand();
14 |
--------------------------------------------------------------------------------
/src/session/command/LoginCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../common/vscode/command/Command";
2 | import { tryLogin } from "../flow/sessionFlow";
3 |
4 | class LoginCommand implements Command {
5 | public name = "zeplin.login";
6 |
7 | public execute() {
8 | tryLogin();
9 | return Promise.resolve();
10 | }
11 | }
12 |
13 | export default new LoginCommand();
14 |
--------------------------------------------------------------------------------
/src/session/command/LogoutCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../common/vscode/command/Command";
2 | import { tryLogout } from "../flow/sessionFlow";
3 |
4 | class LogoutCommand implements Command {
5 | public name = "zeplin.logout";
6 |
7 | public execute() {
8 | tryLogout();
9 | return Promise.resolve();
10 | }
11 | }
12 |
13 | export default new LogoutCommand();
14 |
--------------------------------------------------------------------------------
/src/session/command/ManualLoginCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../common/vscode/command/Command";
2 | import { tryManualLogin } from "../flow/sessionFlow";
3 |
4 | class ManualLoginCommand implements Command {
5 | public name = "zeplin.manualLogin";
6 |
7 | public execute() {
8 | return tryManualLogin();
9 | }
10 | }
11 |
12 | export default new ManualLoginCommand();
13 |
--------------------------------------------------------------------------------
/src/session/tokenStorage/KeychainTokenStorage.ts:
--------------------------------------------------------------------------------
1 | import * as keytarType from "keytar";
2 | import { TokenStorage } from ".";
3 | import Logger from "../../log/Logger";
4 |
5 | const SERVICE_NAME = "zeplinTokenService";
6 | const ACCOUNT_NAME = "zeplinTokenAccount";
7 | const SERVICE_NAME_FOR_AVAILABILITY = "zeplinTokenServiceForAvailability";
8 | const ACCOUNT_NAME_FOR_AVAILABILITY = "zeplinTokenAccountForAvailability";
9 |
10 | /**
11 | * Native storage for session token.
12 | */
13 | class KeychainTokenStorage implements TokenStorage {
14 | public get(): Promise {
15 | return keytar!.getPassword(SERVICE_NAME, ACCOUNT_NAME);
16 | }
17 |
18 | public set(token: string): Promise {
19 | return keytar!.setPassword(SERVICE_NAME, ACCOUNT_NAME, token);
20 | }
21 |
22 | public async remove(): Promise {
23 | await keytar!.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
24 | }
25 | }
26 |
27 | type Keytar = {
28 | getPassword: typeof keytarType["getPassword"];
29 | setPassword: typeof keytarType["setPassword"];
30 | deletePassword: typeof keytarType["deletePassword"];
31 | };
32 |
33 | const keytar = getKeytar();
34 |
35 | function getKeytar(): Keytar | undefined {
36 | try {
37 | const vscodeRequire = eval("require"); // eslint-disable-line no-eval
38 | return vscodeRequire("keytar");
39 | } catch (err) {
40 | return undefined;
41 | }
42 | }
43 |
44 | /**
45 | * Determines whether native password storage could be used for session token.
46 | */
47 | async function isKeychainTokenStorageAvailable() {
48 | try {
49 | // Check if keytar is working
50 | if (!keytar) {
51 | Logger.log("Keychain is not available");
52 | return false;
53 | }
54 | await keytar.getPassword(SERVICE_NAME_FOR_AVAILABILITY, ACCOUNT_NAME_FOR_AVAILABILITY);
55 | Logger.log("Keychain is available");
56 | return true;
57 | } catch (error) {
58 | Logger.error("Keychain is not available", error);
59 | return false;
60 | }
61 | }
62 |
63 | export {
64 | KeychainTokenStorage,
65 | isKeychainTokenStorageAvailable
66 | };
67 |
--------------------------------------------------------------------------------
/src/session/tokenStorage/TokenStorage.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Storage for session token.
3 | */
4 | export default interface TokenStorage {
5 | get(): Promise;
6 | set(token: string): Promise;
7 | remove(): Promise;
8 | }
9 |
--------------------------------------------------------------------------------
/src/session/tokenStorage/VscodeTokenStorage.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import { TokenStorage } from ".";
3 | import ContextProvider from "../../common/vscode/extension/ContextProvider";
4 |
5 | const KEY_TOKEN = "zeplinToken";
6 |
7 | /**
8 | * VS Code storage for session token.
9 | */
10 | export default class VscodeTokenStorage implements TokenStorage {
11 | private readonly memento: vscode.Memento;
12 |
13 | public constructor() {
14 | this.memento = ContextProvider.get().globalState;
15 | }
16 |
17 | public get(): Promise {
18 | return new Promise(resolve => resolve(this.memento.get(KEY_TOKEN)));
19 | }
20 |
21 | public set(token: string): Promise {
22 | return new Promise(resolve => resolve(this.memento.update(KEY_TOKEN, token)));
23 | }
24 |
25 | public remove(): Promise {
26 | return new Promise(resolve => resolve(this.memento.update(KEY_TOKEN, null)));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/session/tokenStorage/index.ts:
--------------------------------------------------------------------------------
1 | import { KeychainTokenStorage, isKeychainTokenStorageAvailable } from "./KeychainTokenStorage";
2 | import VscodeTokenStorage from "./VscodeTokenStorage";
3 | import TokenStorage from "./TokenStorage";
4 |
5 | export {
6 | TokenStorage,
7 | KeychainTokenStorage,
8 | VscodeTokenStorage,
9 | isKeychainTokenStorageAvailable
10 | };
11 |
--------------------------------------------------------------------------------
/src/session/util/Refresher.ts:
--------------------------------------------------------------------------------
1 | import WorkspacesStore from "../../common/domain/barrel/data/WorkspacesStore";
2 | import BarrelsStoreProvider from "../../common/domain/barrel/data/BarrelsStoreProvider";
3 | import BarrelDetailsStoreProvider from "../../common/domain/zeplinComponent/data/BarrelDetailsStoreProvider";
4 | import ScreensStoreProvider from "../../sidebar/screen/data/ScreensStoreProvider";
5 | import BarrelTreeDataProvider from "../../sidebar/barrel/tree/BarrelTreeDataProvider";
6 | import ActivityTreeDataProvider from "../../sidebar/activity/tree/ActivityTreeDataProvider";
7 | import { MS_IN_HOUR } from "../../common/general/dateTimeUtil";
8 | import { resetCroppedImageUrlCache } from "../../common/domain/image/zeplinImageUtil";
9 | import ConfigDiagnosticsProvider from "../../coco/config/diagnostic/ConfigDiagnosticsProvider";
10 |
11 | class Refresher {
12 | private lastRefreshRequested?: number;
13 |
14 | public refresh() {
15 | this.lastRefreshRequested = Date.now();
16 |
17 | WorkspacesStore.clearCache();
18 | BarrelsStoreProvider.clearCache();
19 | BarrelDetailsStoreProvider.clearCache();
20 | ScreensStoreProvider.clearCache();
21 | resetCroppedImageUrlCache();
22 | ConfigDiagnosticsProvider.updateForOpenDocuments();
23 | BarrelTreeDataProvider.refresh();
24 | ActivityTreeDataProvider.refresh();
25 | }
26 |
27 | public requestRefresh() {
28 | if (this.refreshRequired()) {
29 | this.refresh();
30 | }
31 | }
32 |
33 | /**
34 | * Returns true if data is not refreshed in the last hour.
35 | */
36 | private refreshRequired(): boolean {
37 | return !this.lastRefreshRequested || this.lastRefreshRequested < Date.now() - MS_IN_HOUR;
38 | }
39 | }
40 |
41 | export default new Refresher();
42 |
--------------------------------------------------------------------------------
/src/session/util/SessionInitializer.ts:
--------------------------------------------------------------------------------
1 | import { isKeychainTokenStorageAvailable, KeychainTokenStorage, VscodeTokenStorage } from "../tokenStorage";
2 | import Session from "../Session";
3 |
4 | async function initializeSession() {
5 | Session.initialize(await isKeychainTokenStorageAvailable() ? new KeychainTokenStorage() : new VscodeTokenStorage());
6 | }
7 |
8 | export {
9 | initializeSession
10 | };
11 |
--------------------------------------------------------------------------------
/src/sidebar/activity/data/ActivityStore.ts:
--------------------------------------------------------------------------------
1 | import Store from "../../../common/domain/store/Store";
2 | import Result from "../../../common/domain/store/Result";
3 | import ScreensStoreProvider from "../../screen/data/ScreensStoreProvider";
4 | import BarrelDetailsStoreProvider from "../../../common/domain/zeplinComponent/data/BarrelDetailsStoreProvider";
5 | import { getSavedBarrels } from "../../barrel/util/barrelUtil";
6 | import BarrelType from "../../../common/domain/barrel/BarrelType";
7 | import ZeplinComponentsStore from "../../../common/domain/zeplinComponent/data/ZeplinComponentsStore";
8 | import Activity from "../model/Activity";
9 | import { flatten } from "../../../common/general/arrayUtil";
10 | import ScreenActivity from "../model/ScreenActivity";
11 | import ComponentActivity from "../model/ComponentActivity";
12 |
13 | class ActivityStore implements Store {
14 | public get = async (): Promise> => {
15 | const barrels = getSavedBarrels();
16 | const projects = barrels.filter(barrel => barrel.type === BarrelType.Project);
17 | const results = await Promise.all([
18 | ...projects.map(({ id }) => ScreensStoreProvider.get(id).get().then(
19 | result => ({
20 | data: result.data?.map(screen => new ScreenActivity(screen, id)),
21 | errors: result.errors
22 | } as Result)
23 | )),
24 | ...barrels.map(({ id, type }) => new ZeplinComponentsStore(id, type).get().then(
25 | result => ({
26 | data: result.data?.map(component => new ComponentActivity(component, id, type)),
27 | errors: result.errors
28 | } as Result)
29 | ))
30 | ]);
31 |
32 | return {
33 | data: flatten(results.map(result => result.data ?? [])),
34 | errors: flatten(results.map(result => result.errors ?? []))
35 | };
36 | };
37 |
38 | public refresh = (): Promise> => {
39 | BarrelDetailsStoreProvider.clearCache();
40 | ScreensStoreProvider.clearCache();
41 | return this.get();
42 | };
43 | }
44 |
45 | export default new ActivityStore();
46 |
--------------------------------------------------------------------------------
/src/sidebar/activity/model/Activity.ts:
--------------------------------------------------------------------------------
1 | import User from "../../../common/domain/componentLike/model/User";
2 | import Version from "../../../common/domain/componentLike/model/Version";
3 | import { getDateAgo } from "../util/activityUi";
4 | import ZeplinUriProvider from "../../../common/domain/openInZeplin/model/ZeplinUriProvider";
5 | import ApplicationType from "../../../common/domain/openInZeplin/model/ApplicationType";
6 | import ZeplinLinkType from "../../../common/domain/openInZeplin/model/ZeplinLinkType";
7 |
8 | export default abstract class Activity implements ZeplinUriProvider {
9 | public date: Date;
10 | public dateAgo: string;
11 | public user?: User;
12 |
13 | public constructor(public itemName: string, version: Version) {
14 | this.date = new Date(version.created);
15 | this.dateAgo = getDateAgo(this.date);
16 | this.user = version.creator;
17 | }
18 |
19 | public abstract getZeplinUri(applicationType: ApplicationType): string;
20 |
21 | public abstract getZeplinLinkType(): ZeplinLinkType;
22 | }
23 |
--------------------------------------------------------------------------------
/src/sidebar/activity/model/ComponentActivity.ts:
--------------------------------------------------------------------------------
1 | import ZeplinComponent from "../../../common/domain/zeplinComponent/model/ZeplinComponent";
2 | import Activity from "./Activity";
3 | import { getComponentUri } from "../../../common/domain/openInZeplin/util/zeplinUris";
4 | import ApplicationType from "../../../common/domain/openInZeplin/model/ApplicationType";
5 | import BarrelType from "../../../common/domain/barrel/BarrelType";
6 | import ZeplinLinkType from "../../../common/domain/openInZeplin/model/ZeplinLinkType";
7 |
8 | export default class ComponentActivity extends Activity {
9 | public constructor(private component: ZeplinComponent, private barrelId: string, private barrelType: BarrelType) {
10 | super(component.name, component.latestVersion);
11 | }
12 |
13 | public getZeplinUri(applicationType: ApplicationType): string {
14 | return getComponentUri(this.barrelId, this.barrelType, this.component._id, applicationType);
15 | }
16 |
17 | public getZeplinLinkType(): ZeplinLinkType {
18 | return ZeplinLinkType.Component;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/sidebar/activity/model/ScreenActivity.ts:
--------------------------------------------------------------------------------
1 | import Activity from "./Activity";
2 | import ResponseScreen from "../../screen/model/ResponseScreen";
3 | import { getScreenUri } from "../../../common/domain/openInZeplin/util/zeplinUris";
4 | import ApplicationType from "../../../common/domain/openInZeplin/model/ApplicationType";
5 | import ZeplinLinkType from "../../../common/domain/openInZeplin/model/ZeplinLinkType";
6 |
7 | export default class ScreenActivity extends Activity {
8 | public constructor(private screen: ResponseScreen, private projectId: string) {
9 | super(screen.name, screen.latestVersion);
10 | }
11 |
12 | public getZeplinUri(applicationType: ApplicationType): string {
13 | return getScreenUri(this.projectId, this.screen._id, applicationType);
14 | }
15 |
16 | public getZeplinLinkType(): ZeplinLinkType {
17 | return ZeplinLinkType.Screen;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/sidebar/activity/tree/ActivityErrorsTreeItem.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import TreeItem from "../../../common/vscode/tree/TreeItem";
3 | import localization from "../../../localization";
4 | import BaseError from "../../../common/domain/error/BaseError";
5 | import BarrelError from "../../../common/domain/zeplinComponent/model/BarrelError";
6 | import ScreensError from "../../screen/model/ScreensError";
7 |
8 | export default class ActivityErrorsTreeItem extends TreeItem {
9 | public constructor(private errors: BaseError[]) {
10 | super(localization.sidebar.activity.errors, undefined, undefined, vscode.TreeItemCollapsibleState.Collapsed);
11 | }
12 |
13 | public getChildren(): TreeItem[] {
14 | return this.errors.map(error => new TreeItem(this.getErrorMessage(error), this));
15 | }
16 |
17 | private getErrorMessage(error: BaseError) {
18 | if (error instanceof BarrelError) {
19 | return localization.sidebar.activity.componentsError(error.id);
20 | } else if (error instanceof ScreensError) {
21 | return localization.sidebar.activity.screensError(error.barrelId);
22 | } else {
23 | return error.message;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/sidebar/activity/tree/ActivitySlotTreeItem.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import TreeItem from "../../../common/vscode/tree/TreeItem";
3 | import Activity from "../model/Activity";
4 | import ActivityTreeItem from "./ActivityTreeItem";
5 | import { getThemedIconUris } from "../../../common/general/iconPathUtil";
6 |
7 | export default class ActivitySlotTreeItem extends TreeItem {
8 | public iconPath = getThemedIconUris("icon-calendar.svg");
9 |
10 | public constructor(slot: string, private activities: Activity[]) {
11 | super(slot, undefined, undefined, vscode.TreeItemCollapsibleState.Collapsed);
12 | }
13 |
14 | public getChildren(): TreeItem[] {
15 | return this.activities.map(activity => new ActivityTreeItem(activity, this));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/sidebar/activity/tree/ActivityTreeDataProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import TreeDataProvider from "../../../common/vscode/tree/TreeDataProvider";
3 | import TreeItem from "../../../common/vscode/tree/TreeItem";
4 | import ActivityStore from "../data/ActivityStore";
5 | import { getDateSlot } from "../util/activityUi";
6 | import Activity from "../model/Activity";
7 | import ActivitySlotTreeItem from "./ActivitySlotTreeItem";
8 | import ActivityErrorsTreeItem from "./ActivityErrorsTreeItem";
9 | import localization from "../../../localization";
10 | import Refresher from "../../../session/util/Refresher";
11 |
12 | class ActivityTreeDataProvider extends TreeDataProvider {
13 | protected viewId = "zeplin.views.activity";
14 | protected showCollapseAll = true;
15 |
16 | public register(): vscode.Disposable {
17 | const disposables = [super.register()];
18 | disposables.push(
19 | this.treeView!.onDidChangeVisibility(({ visible }) => {
20 | if (visible) {
21 | Refresher.requestRefresh();
22 | }
23 | })
24 | );
25 | return vscode.Disposable.from(...disposables);
26 | }
27 |
28 | public async getRoots(): Promise {
29 | const { data, errors } = await ActivityStore.get();
30 |
31 | const roots: TreeItem[] = errors?.length ? [new ActivityErrorsTreeItem(errors)] : [];
32 |
33 | const slots: { [slot: string]: Activity[] } = {};
34 | const activities = data!.sort((first, second) => second.date.getTime() - first.date.getTime());
35 | activities.forEach(activity => {
36 | const slot = getDateSlot(activity.date);
37 | if (!slots[slot]) {
38 | slots[slot] = [];
39 | }
40 | slots[slot].push(activity);
41 | });
42 |
43 | roots.push(...Object.keys(slots).map(slot => new ActivitySlotTreeItem(slot, slots[slot])));
44 | if (!roots.length) {
45 | roots.push(new TreeItem(localization.sidebar.activity.noneFound, undefined));
46 | }
47 | return roots;
48 | }
49 | }
50 |
51 | export default new ActivityTreeDataProvider();
52 |
--------------------------------------------------------------------------------
/src/sidebar/activity/tree/ActivityTreeItem.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import TreeItem from "../../../common/vscode/tree/TreeItem";
3 | import Activity from "../model/Activity";
4 | import { getEmotarUrl } from "../../../common/domain/image/zeplinImageUtil";
5 | import localization from "../../../localization";
6 | import TreeItemContextProvider from "../../../common/vscode/tree/TreeItemContextProvider";
7 | import TreeItemContext from "../../../common/domain/tree/TreeItemContext";
8 | import ZeplinUriProvider from "../../../common/domain/openInZeplin/model/ZeplinUriProvider";
9 | import ApplicationType from "../../../common/domain/openInZeplin/model/ApplicationType";
10 | import ZeplinLinkType from "../../../common/domain/openInZeplin/model/ZeplinLinkType";
11 |
12 | const contextProvider = new TreeItemContextProvider(
13 | TreeItemContext.Activity,
14 | TreeItemContext.ZeplinLink
15 | );
16 |
17 | export default class ActivityTreeItem extends TreeItem implements ZeplinUriProvider {
18 | public iconPath = this.getThumbnail();
19 | public description = this.activity.dateAgo;
20 | public tooltip = this.activity.date.toLocaleString();
21 |
22 | public constructor(public activity: Activity, parent: TreeItem | undefined) {
23 | super(ActivityTreeItem.getLabel(activity), parent, contextProvider);
24 | }
25 |
26 | private static getLabel(activity: Activity): string {
27 | return !activity.user
28 | ? localization.sidebar.activity.updated(activity.itemName)
29 | : localization.sidebar.activity.updatedByUser(activity.user.username, activity.itemName);
30 | }
31 |
32 | private getThumbnail(): vscode.Uri | undefined {
33 | if (this.activity.user?.avatar) {
34 | return vscode.Uri.parse(this.activity.user.avatar);
35 | } else if (this.activity.user?.emotar) {
36 | return vscode.Uri.parse(getEmotarUrl(this.activity.user.emotar));
37 | } else {
38 | return undefined;
39 | }
40 | }
41 |
42 | public getZeplinUri(applicationType: ApplicationType): string {
43 | return this.activity.getZeplinUri(applicationType);
44 | }
45 |
46 | public getZeplinLinkType(): ZeplinLinkType {
47 | return this.activity.getZeplinLinkType();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/sidebar/barrel/command/AddBarrelToSidebarCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startAddBarrelToSidebarFlowWithTypeSelection } from "../flow/barrelFlow";
3 |
4 | class AddBarrelToSidebarCommand implements Command {
5 | public name = "zeplin.sidebar.addBarrel";
6 | public execute = startAddBarrelToSidebarFlowWithTypeSelection;
7 | }
8 |
9 | export default new AddBarrelToSidebarCommand();
10 |
--------------------------------------------------------------------------------
/src/sidebar/barrel/command/AddProjectToSidebarCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startAddProjectToSidebarFlow } from "../flow/barrelFlow";
3 |
4 | class AddProjectToSidebarCommand implements Command {
5 | public name = "zeplin.sidebar.addProject";
6 | public execute = startAddProjectToSidebarFlow;
7 | }
8 |
9 | export default new AddProjectToSidebarCommand();
10 |
--------------------------------------------------------------------------------
/src/sidebar/barrel/command/AddStyleguideToSidebarCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startAddStyleguideToSidebarFlow } from "../flow/barrelFlow";
3 |
4 | class AddStyleguideToSidebarCommand implements Command {
5 | public name = "zeplin.sidebar.addStyleguide";
6 | public execute = startAddStyleguideToSidebarFlow;
7 | }
8 |
9 | export default new AddStyleguideToSidebarCommand();
10 |
--------------------------------------------------------------------------------
/src/sidebar/barrel/command/RemoveBarrelFromSidebarCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { removeBarrel } from "../util/barrelUtil";
3 | import { BarrelTreeItem } from "../tree/BarrelTreeItem";
4 |
5 | class RemoveBarrelFromSidebarCommand implements Command {
6 | public name = "zeplin.sidebar.removeBarrel";
7 |
8 | public execute(item: BarrelTreeItem) {
9 | removeBarrel(item.barrel);
10 | return Promise.resolve();
11 | }
12 | }
13 |
14 | export default new RemoveBarrelFromSidebarCommand();
15 |
--------------------------------------------------------------------------------
/src/sidebar/barrel/tree/AddBarrelTreeItem.ts:
--------------------------------------------------------------------------------
1 | import TreeItem from "../../../common/vscode/tree/TreeItem";
2 | import localization from "../../../localization";
3 | import AddBarrelToSidebarCommand from "../command/AddBarrelToSidebarCommand";
4 | import { getThemedIconUris } from "../../../common/general/iconPathUtil";
5 |
6 | export default class AddBarrelTreeItem extends TreeItem {
7 | public iconPath = getThemedIconUris("icon-add.svg");
8 | public command = {
9 | title: localization.sidebar.barrel.addAnother,
10 | command: AddBarrelToSidebarCommand.name
11 | };
12 |
13 | public constructor() {
14 | super(localization.sidebar.barrel.addAnother, undefined);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/sidebar/barrel/tree/BarrelTreeItem.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import TreeItem from "../../../common/vscode/tree/TreeItem";
3 | import Barrel from "../../../common/domain/barrel/Barrel";
4 | import ScreensTreeItem from "../../screen/tree/ScreensTreeItem";
5 | import ZeplinComponentsTreeItem from "../../zeplinComponent/tree/ZeplinComponentsTreeItem";
6 | import TreeItemContext from "../../../common/domain/tree/TreeItemContext";
7 | import TreeItemContextProvider from "../../../common/vscode/tree/TreeItemContextProvider";
8 | import BarrelType from "../../../common/domain/barrel/BarrelType";
9 | import ZeplinUriProvider from "../../../common/domain/openInZeplin/model/ZeplinUriProvider";
10 | import ApplicationType from "../../../common/domain/openInZeplin/model/ApplicationType";
11 | import { getBarrelUri } from "../../../common/domain/openInZeplin/util/zeplinUris";
12 | import ZeplinLinkType from "../../../common/domain/openInZeplin/model/ZeplinLinkType";
13 |
14 | function getContextProvider(barrel: Barrel): TreeItemContextProvider {
15 | const contexts = [TreeItemContext.Barrel, TreeItemContext.ZeplinLink];
16 | if (barrel.jiras.length) {
17 | contexts.push(TreeItemContext.Jira);
18 | }
19 | return new TreeItemContextProvider(...contexts);
20 | }
21 |
22 | export class BarrelTreeItem extends TreeItem implements ZeplinUriProvider {
23 | public constructor(public barrel: Barrel) {
24 | super(barrel.name, undefined, getContextProvider(barrel), vscode.TreeItemCollapsibleState.Collapsed);
25 | this.setRemoteIconPath(barrel.thumbnail);
26 | }
27 |
28 | public getChildren(): TreeItem[] {
29 | const children: TreeItem[] = [new ZeplinComponentsTreeItem(this.barrel, this)];
30 | if (this.barrel.type === BarrelType.Project) {
31 | children.unshift(new ScreensTreeItem(this.barrel, this));
32 | }
33 |
34 | return children;
35 | }
36 |
37 | public getZeplinUri(applicationType: ApplicationType): string {
38 | return getBarrelUri(this.barrel.id, this.barrel.type, applicationType);
39 | }
40 |
41 | public getZeplinLinkType(): ZeplinLinkType {
42 | return this.barrel.type === BarrelType.Project ? ZeplinLinkType.Project : ZeplinLinkType.Styleguide;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/sidebar/barrel/tree/NoBarrelTreeItem.ts:
--------------------------------------------------------------------------------
1 | import TreeItem from "../../../common/vscode/tree/TreeItem";
2 | import localization from "../../../localization";
3 | import { getThemedIconUris } from "../../../common/general/iconPathUtil";
4 |
5 | export default class NoBarrelTreeItem extends TreeItem {
6 | public iconPath = getThemedIconUris("icon-info.svg");
7 |
8 | public constructor() {
9 | super(localization.sidebar.barrel.emptyInfo, undefined);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/sidebar/barrel/util/barrelUtil.ts:
--------------------------------------------------------------------------------
1 | import Barrel from "../../../common/domain/barrel/Barrel";
2 | import ContextProvider from "../../../common/vscode/extension/ContextProvider";
3 | import BarrelTreeDataProvider from "../tree/BarrelTreeDataProvider";
4 | import ActivityTreeDataProvider from "../../activity/tree/ActivityTreeDataProvider";
5 | import { removeBarrelItemsFromPinnedItems } from "../../pin/util/pinUtil";
6 | import ScreensStoreProvider from "../../screen/data/ScreensStoreProvider";
7 | import BarrelDetailsStoreProvider from "../../../common/domain/zeplinComponent/data/BarrelDetailsStoreProvider";
8 |
9 | const KEY_SAVED_BARRELS = "sidebar.savedBarrels";
10 |
11 | function getSavedBarrels(): Barrel[] {
12 | return ContextProvider.get().workspaceState.get(KEY_SAVED_BARRELS) ?? [];
13 | }
14 |
15 | function saveBarrels(barrels: Barrel[]) {
16 | ContextProvider.get().workspaceState.update(KEY_SAVED_BARRELS, barrels);
17 |
18 | BarrelTreeDataProvider.refresh();
19 | ActivityTreeDataProvider.refresh();
20 | }
21 |
22 | function isBarrelSaved(barrel: Barrel): boolean {
23 | return getSavedBarrels().some(savedBarrel => savedBarrel.id === barrel.id);
24 | }
25 |
26 | function saveBarrel(barrel: Barrel) {
27 | const barrels = getSavedBarrels();
28 | const updateIndex = barrels.findIndex(savedBarrel => savedBarrel.id === barrel.id);
29 | if (updateIndex >= 0) {
30 | barrels[updateIndex] = barrel;
31 | } else {
32 | barrels.push(barrel);
33 | }
34 | saveBarrels(barrels);
35 | }
36 |
37 | function removeBarrel(barrel: Barrel) {
38 | const barrels = getSavedBarrels().filter(savedBarrel => savedBarrel.id !== barrel.id);
39 | saveBarrels(barrels);
40 | ScreensStoreProvider.clearCacheFor(barrel.id);
41 | BarrelDetailsStoreProvider.clearCacheFor(barrel.id);
42 | removeBarrelItemsFromPinnedItems(barrel);
43 | }
44 |
45 | export {
46 | getSavedBarrels,
47 | isBarrelSaved,
48 | saveBarrel,
49 | removeBarrel
50 | };
51 |
--------------------------------------------------------------------------------
/src/sidebar/jira/command/OpenJiraLinkCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startOpenJiraLinkFlow } from "../flow/jiraFlow";
3 |
4 | class OpenJiraLinkCommand implements Command {
5 | public name = "zeplin.sidebar.openJiraLink";
6 | public execute = startOpenJiraLinkFlow;
7 | }
8 |
9 | export default new OpenJiraLinkCommand();
10 |
--------------------------------------------------------------------------------
/src/sidebar/jumpTo/command/JumpToSidebarItemCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startJumpToFlow } from "../flow/jumpToFlow";
3 |
4 | class JumpToSidebarItemCommand implements Command {
5 | public name = "zeplin.sidebar.jumpTo";
6 | public execute = startJumpToFlow;
7 | }
8 |
9 | export default new JumpToSidebarItemCommand();
10 |
--------------------------------------------------------------------------------
/src/sidebar/jumpTo/tree/JumpToTreeItem.ts:
--------------------------------------------------------------------------------
1 | import TreeItem from "../../../common/vscode/tree/TreeItem";
2 | import localization from "../../../localization";
3 | import JumpToSidebarItemCommand from "../command/JumpToSidebarItemCommand";
4 | import { getThemedIconUris } from "../../../common/general/iconPathUtil";
5 |
6 | export default class JumpToTreeItem extends TreeItem {
7 | public iconPath = getThemedIconUris("icon-search.svg");
8 | public command = {
9 | title: localization.sidebar.jumpTo.jumpToItem,
10 | command: JumpToSidebarItemCommand.name
11 | };
12 |
13 | public constructor() {
14 | super(localization.sidebar.jumpTo.jumpToItem, undefined);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/sidebar/openInZeplin/command/OpenInZeplinOnDoubleClickCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { openInZeplin } from "../../../common/domain/openInZeplin/flow/openInZeplinFlow";
3 | import ZeplinUriProvider from "../../../common/domain/openInZeplin/model/ZeplinUriProvider";
4 |
5 | const DOUBLE_CLICK_DURATION = 500;
6 |
7 | class OpenInZeplinOnDoubleClickCommand implements Command {
8 | public name = "zeplin.sidebar.openInZeplinOnDoubleClick";
9 | private lastClickTime?: number;
10 | private lastClickedItem?: ZeplinUriProvider;
11 |
12 | public execute(uriProvider: ZeplinUriProvider) {
13 | if (
14 | this.lastClickTime && this.lastClickedItem && this.lastClickedItem === uriProvider &&
15 | this.lastClickTime > Date.now() - DOUBLE_CLICK_DURATION
16 | ) {
17 | this.lastClickTime = undefined;
18 | this.lastClickedItem = undefined;
19 | return openInZeplin(uriProvider);
20 | } else {
21 | this.lastClickTime = Date.now();
22 | this.lastClickedItem = uriProvider;
23 | return Promise.resolve();
24 | }
25 | }
26 | }
27 |
28 | export default new OpenInZeplinOnDoubleClickCommand();
29 |
--------------------------------------------------------------------------------
/src/sidebar/pin/command/PinComponentToSidebarCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startPinComponentFlow } from "../flow/pinFlow";
3 |
4 | class PinComponentToSidebarCommand implements Command {
5 | public name = "zeplin.sidebar.pinComponent";
6 | public execute = startPinComponentFlow;
7 | }
8 |
9 | export default new PinComponentToSidebarCommand();
10 |
--------------------------------------------------------------------------------
/src/sidebar/pin/command/PinScreenToSidebarCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { startPinScreenFlow } from "../flow/pinFlow";
3 |
4 | class PinScreenToSidebarCommand implements Command {
5 | public name = "zeplin.sidebar.pinScreen";
6 | public execute = startPinScreenFlow;
7 | }
8 |
9 | export default new PinScreenToSidebarCommand();
10 |
--------------------------------------------------------------------------------
/src/sidebar/pin/command/PinToSidebarCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { pinItem } from "../flow/pinFlow";
3 | import TreeItem from "../../../common/vscode/tree/TreeItem";
4 |
5 | class PinToSidebarCommand implements Command {
6 | public name = "zeplin.sidebar.pinItem";
7 |
8 | public execute(item: TreeItem) {
9 | pinItem(item);
10 | return Promise.resolve();
11 | }
12 | }
13 |
14 | export default new PinToSidebarCommand();
15 |
--------------------------------------------------------------------------------
/src/sidebar/pin/command/UnpinAllFromSidebarCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { askUnpinAll } from "../flow/pinFlow";
3 |
4 | class UnpinAllFromSidebarCommand implements Command {
5 | public name = "zeplin.sidebar.unpinAll";
6 |
7 | public execute() {
8 | askUnpinAll();
9 | return Promise.resolve();
10 | }
11 | }
12 |
13 | export default new UnpinAllFromSidebarCommand();
14 |
--------------------------------------------------------------------------------
/src/sidebar/pin/command/UnpinFromSidebarCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import { unpinItem } from "../flow/pinFlow";
3 | import TreeItem from "../../../common/vscode/tree/TreeItem";
4 |
5 | class UnpinFromSidebarCommand implements Command {
6 | public name = "zeplin.sidebar.unpinItem";
7 |
8 | public execute(item: TreeItem) {
9 | unpinItem(item);
10 | return Promise.resolve();
11 | }
12 | }
13 |
14 | export default new UnpinFromSidebarCommand();
15 |
--------------------------------------------------------------------------------
/src/sidebar/pin/data/PinnableComponentsStore.ts:
--------------------------------------------------------------------------------
1 | import Store from "../../../common/domain/store/Store";
2 | import Result from "../../../common/domain/store/Result";
3 | import BarrelDetailsStoreProvider from "../../../common/domain/zeplinComponent/data/BarrelDetailsStoreProvider";
4 | import { getPinnedItems } from "../util/pinUtil";
5 | import PinType from "../model/PinType";
6 | import ComponentPinData from "../model/ComponentPinData";
7 | import BarrelType from "../../../common/domain/barrel/BarrelType";
8 | import ZeplinComponentsStore from "../../../common/domain/zeplinComponent/data/ZeplinComponentsStore";
9 | import ZeplinComponent from "../../../common/domain/zeplinComponent/model/ZeplinComponent";
10 |
11 | export default class PinnableComponentsStore implements Store {
12 | public constructor(private barrelId: string, private barrelType: BarrelType) { }
13 |
14 | public get = async (): Promise> => {
15 | const { data: components, errors } = await new ZeplinComponentsStore(this.barrelId, this.barrelType).get();
16 |
17 | if (errors?.length) {
18 | return {
19 | errors
20 | };
21 | } else {
22 | const pinnedComponentIds = getPinnedItems()
23 | .filter(item => item.type === PinType.Component)
24 | .map(item => (item as ComponentPinData).component._id);
25 | return {
26 | data: components!.filter(component => !pinnedComponentIds.includes(component._id))
27 | };
28 | }
29 | };
30 |
31 | public refresh = (): Promise> => {
32 | BarrelDetailsStoreProvider.clearCache();
33 | return this.get();
34 | };
35 | }
36 |
--------------------------------------------------------------------------------
/src/sidebar/pin/data/PinnableScreensStore.ts:
--------------------------------------------------------------------------------
1 | import Store from "../../../common/domain/store/Store";
2 | import Result from "../../../common/domain/store/Result";
3 | import BarrelDetailsStoreProvider from "../../../common/domain/zeplinComponent/data/BarrelDetailsStoreProvider";
4 | import ScreensStoreProvider from "../../screen/data/ScreensStoreProvider";
5 | import Screen from "../../screen/model/Screen";
6 | import ProjectScreensStore from "../../screen/data/ProjectScreensStore";
7 | import { flatten } from "../../../common/general/arrayUtil";
8 | import { getPinnedItems } from "../util/pinUtil";
9 | import PinType from "../model/PinType";
10 | import ScreenPinData from "../model/ScreenPinData";
11 |
12 | export default class PinnableScreensStore implements Store {
13 | public constructor(private projectId: string) { }
14 |
15 | public get = async (): Promise> => {
16 | const { data: projectScreens, errors } = await new ProjectScreensStore(this.projectId).get();
17 |
18 | if (errors?.length) {
19 | return {
20 | errors
21 | };
22 | } else {
23 | const { screens, sections } = projectScreens!;
24 | const pinnedScreenIds = getPinnedItems()
25 | .filter(item => item.type === PinType.Screen)
26 | .map(item => (item as ScreenPinData).screen._id);
27 | return {
28 | data: flatten([screens, ...sections.map(section => section.screens)])
29 | .filter(screen => !pinnedScreenIds.includes(screen._id))
30 | };
31 | }
32 | };
33 |
34 | public refresh = (): Promise> => {
35 | BarrelDetailsStoreProvider.clearCache();
36 | ScreensStoreProvider.clearCache();
37 | return this.get();
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/src/sidebar/pin/model/ComponentPinData.ts:
--------------------------------------------------------------------------------
1 | import PinData from "./PinData";
2 | import ResponseZeplinComponent from "../../../common/domain/zeplinComponent/model/ResponseZeplinComponent";
3 |
4 | export default interface ComponentPinData extends PinData {
5 | component: ResponseZeplinComponent;
6 | }
7 |
--------------------------------------------------------------------------------
/src/sidebar/pin/model/PinData.ts:
--------------------------------------------------------------------------------
1 | import PinType from "./PinType";
2 | import Barrel from "../../../common/domain/barrel/Barrel";
3 |
4 | export default interface PinData {
5 | type: PinType;
6 | barrel: Barrel;
7 | }
8 |
--------------------------------------------------------------------------------
/src/sidebar/pin/model/PinType.ts:
--------------------------------------------------------------------------------
1 | enum PinType {
2 | Screen,
3 | Component
4 | }
5 |
6 | export default PinType;
7 |
--------------------------------------------------------------------------------
/src/sidebar/pin/model/ScreenPinData.ts:
--------------------------------------------------------------------------------
1 | import PinData from "./PinData";
2 | import Screen from "../../screen/model/Screen";
3 |
4 | export default interface ScreenPinData extends PinData {
5 | screen: Screen;
6 | }
7 |
--------------------------------------------------------------------------------
/src/sidebar/pin/tree/NoPinnedItemTreeItem.ts:
--------------------------------------------------------------------------------
1 | import TreeItem from "../../../common/vscode/tree/TreeItem";
2 | import localization from "../../../localization";
3 | import { getThemedIconUris } from "../../../common/general/iconPathUtil";
4 |
5 | export default class NoPinnedItemTreeItem extends TreeItem {
6 | public iconPath = getThemedIconUris("icon-pinned.svg");
7 |
8 | public constructor() {
9 | super(localization.sidebar.pin.emptyInfo, undefined);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/sidebar/refresh/command/RefreshSidebarCommand.ts:
--------------------------------------------------------------------------------
1 | import Command from "../../../common/vscode/command/Command";
2 | import Refresher from "../../../session/util/Refresher";
3 |
4 | class RefreshSidebarCommand implements Command {
5 | public name = "zeplin.sidebar.refresh";
6 |
7 | public execute() {
8 | Refresher.refresh();
9 | return Promise.resolve();
10 | }
11 | }
12 |
13 | export default new RefreshSidebarCommand();
14 |
--------------------------------------------------------------------------------
/src/sidebar/refresh/util/refreshUtil.ts:
--------------------------------------------------------------------------------
1 | import BarrelTreeDataProvider from "../../barrel/tree/BarrelTreeDataProvider";
2 | import ActivityTreeDataProvider from "../../activity/tree/ActivityTreeDataProvider";
3 | import BarrelDetails from "../../../common/domain/zeplinComponent/model/BarrelDetails";
4 | import { updatePinnedScreens, updatePinnedItems } from "../../pin/util/pinUtil";
5 | import ResponseScreen from "../../screen/model/ResponseScreen";
6 | import TreeItem from "../../../common/vscode/tree/TreeItem";
7 | import PinTreeDataProvider from "../../pin/tree/PinTreeDataProvider";
8 |
9 | function updateSidebarScreens(projectId: string, screens: ResponseScreen[]) {
10 | updatePinnedScreens(projectId, screens);
11 | BarrelTreeDataProvider.refresh();
12 | ActivityTreeDataProvider.refresh();
13 | }
14 |
15 | function updateSidebarItems(barrel: BarrelDetails) {
16 | updatePinnedItems(barrel);
17 | BarrelTreeDataProvider.refresh();
18 | ActivityTreeDataProvider.refresh();
19 | }
20 |
21 | function refreshItem(item: TreeItem) {
22 | BarrelTreeDataProvider.refreshItem(item);
23 | PinTreeDataProvider.refreshItem(item);
24 | ActivityTreeDataProvider.refreshItem(item);
25 | }
26 |
27 | export {
28 | updateSidebarScreens,
29 | updateSidebarItems,
30 | refreshItem
31 | };
32 |
--------------------------------------------------------------------------------
/src/sidebar/screen/data/ScreensStore.ts:
--------------------------------------------------------------------------------
1 | import BasicStore from "../../../common/domain/store/BasicStore";
2 | import { getScreens } from "../../../common/domain/api/api";
3 | import ResponseScreen from "../model/ResponseScreen";
4 | import ScreensResponse from "../model/ScreensResponse";
5 | import BaseError from "../../../common/domain/error/BaseError";
6 | import ScreensError from "../model/ScreensError";
7 |
8 | export default class ScreensStore extends BasicStore {
9 | public constructor(private projectId: string) {
10 | super();
11 | }
12 |
13 | protected async fetchData(): Promise {
14 | const result = await getScreens(this.projectId);
15 | return result instanceof BaseError
16 | ? new ScreensError(this.projectId, result.message, result.code)
17 | : result;
18 | }
19 |
20 | protected extractData(response: ScreensResponse): ResponseScreen[] {
21 | return response.screens;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/sidebar/screen/data/ScreensStoreProvider.ts:
--------------------------------------------------------------------------------
1 | import CacheHolder from "../../../common/domain/store/CacheHolder";
2 | import ScreensStore from "./ScreensStore";
3 | import { updateSidebarScreens } from "../../refresh/util/refreshUtil";
4 |
5 | class ScreensStoreProvider implements CacheHolder {
6 | private cache: { [id: string]: ScreensStore } = {};
7 |
8 | public get(id: string): ScreensStore {
9 | if (!this.cache[id]) {
10 | this.cache[id] = new ScreensStore(id);
11 | this.cache[id].onDataReceived(screens => updateSidebarScreens(id, screens));
12 | }
13 |
14 | return this.cache[id];
15 | }
16 |
17 | public clearCache() {
18 | Object.keys(this.cache).forEach(key => this.cache[key].dispose());
19 | this.cache = {};
20 | }
21 |
22 | public clearCacheFor(id: string) {
23 | this.cache[id]?.dispose();
24 | delete this.cache[id];
25 | }
26 | }
27 |
28 | export default new ScreensStoreProvider();
29 |
--------------------------------------------------------------------------------
/src/sidebar/screen/model/ProjectScreens.ts:
--------------------------------------------------------------------------------
1 | import Screen from "./Screen";
2 | import ScreenSection from "./ScreenSection";
3 |
4 | export default interface ProjectScreens {
5 | screens: Screen[];
6 | sections: ScreenSection[];
7 | }
8 |
--------------------------------------------------------------------------------
/src/sidebar/screen/model/ResponseScreen.ts:
--------------------------------------------------------------------------------
1 | import ComponentLike from "../../../common/domain/componentLike/model/ComponentLike";
2 |
3 | export default interface ResponseScreen extends ComponentLike {
4 | }
5 |
--------------------------------------------------------------------------------
/src/sidebar/screen/model/Screen.ts:
--------------------------------------------------------------------------------
1 | import JiraAttachable from "../../../common/domain/jira/model/JiraAttachable";
2 | import ResponseScreen from "./ResponseScreen";
3 |
4 | export default interface Screen extends ResponseScreen, JiraAttachable {
5 | barrelId: string;
6 | barrelName: string;
7 | sectionId?: string;
8 | sectionName?: string;
9 | }
10 |
--------------------------------------------------------------------------------
/src/sidebar/screen/model/ScreenSection.ts:
--------------------------------------------------------------------------------
1 | import Screen from "./Screen";
2 | import JiraAttachable from "../../../common/domain/jira/model/JiraAttachable";
3 |
4 | export default interface ScreenSection extends JiraAttachable {
5 | id: string;
6 | name: string;
7 | description?: string;
8 | screens: Screen[];
9 | }
10 |
--------------------------------------------------------------------------------
/src/sidebar/screen/model/ScreensError.ts:
--------------------------------------------------------------------------------
1 | import BaseError from "../../../common/domain/error/BaseError";
2 |
3 | export default class ScreensError extends BaseError {
4 | public constructor(public barrelId: string, message?: string, code?: number) {
5 | super(message, code);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/sidebar/screen/model/ScreensResponse.ts:
--------------------------------------------------------------------------------
1 | import ResponseScreen from "./ResponseScreen";
2 |
3 | export default interface ScreensResponse {
4 | screens: ResponseScreen[];
5 | }
6 |
--------------------------------------------------------------------------------
/src/sidebar/screen/tree/ScreenSectionTreeItem.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import TreeItem from "../../../common/vscode/tree/TreeItem";
3 | import ScreenSection from "../model/ScreenSection";
4 | import ScreenTreeItem from "./ScreenTreeItem";
5 | import TreeItemContextProvider from "../../../common/vscode/tree/TreeItemContextProvider";
6 | import TreeItemContext from "../../../common/domain/tree/TreeItemContext";
7 | import Barrel from "../../../common/domain/barrel/Barrel";
8 | import ZeplinUriProvider from "../../../common/domain/openInZeplin/model/ZeplinUriProvider";
9 | import ApplicationType from "../../../common/domain/openInZeplin/model/ApplicationType";
10 | import { getScreenSectionUri } from "../../../common/domain/openInZeplin/util/zeplinUris";
11 | import ZeplinLinkType from "../../../common/domain/openInZeplin/model/ZeplinLinkType";
12 |
13 | function getContextProvider(section: ScreenSection): TreeItemContextProvider {
14 | const contexts = [TreeItemContext.ScreenSection, TreeItemContext.ZeplinLink];
15 | if (section.jiras.length) {
16 | contexts.push(TreeItemContext.Jira);
17 | }
18 | return new TreeItemContextProvider(...contexts);
19 | }
20 |
21 | export default class ScreenSectionTreeItem extends TreeItem implements ZeplinUriProvider {
22 | public constructor(public section: ScreenSection, public project: Barrel, parent: TreeItem | undefined) {
23 | super(section.name, parent, getContextProvider(section), vscode.TreeItemCollapsibleState.Collapsed);
24 | }
25 |
26 | public getChildren(): TreeItem[] {
27 | return this.section.screens.map(screen => new ScreenTreeItem(screen, this.project, this));
28 | }
29 |
30 | public getZeplinUri(applicationType: ApplicationType): string {
31 | return getScreenSectionUri(this.project.id, this.section.id, applicationType);
32 | }
33 |
34 | public getZeplinLinkType(): ZeplinLinkType {
35 | return ZeplinLinkType.ScreenSection;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/sidebar/screen/tree/ScreenTreeItem.ts:
--------------------------------------------------------------------------------
1 | import Screen from "../model/Screen";
2 | import TreeItem from "../../../common/vscode/tree/TreeItem";
3 | import TreeItemContextProvider from "../../../common/vscode/tree/TreeItemContextProvider";
4 | import TreeItemContext from "../../../common/domain/tree/TreeItemContext";
5 | import Barrel from "../../../common/domain/barrel/Barrel";
6 | import { isScreenPinned } from "../../pin/util/pinUtil";
7 | import ZeplinUriProvider from "../../../common/domain/openInZeplin/model/ZeplinUriProvider";
8 | import ApplicationType from "../../../common/domain/openInZeplin/model/ApplicationType";
9 | import { getScreenUri } from "../../../common/domain/openInZeplin/util/zeplinUris";
10 | import ZeplinLinkType from "../../../common/domain/openInZeplin/model/ZeplinLinkType";
11 |
12 | function getContextProvider(screen: Screen): TreeItemContextProvider {
13 | const contexts = [
14 | TreeItemContext.Screen,
15 | TreeItemContext.ZeplinLink,
16 | isScreenPinned(screen) ? TreeItemContext.Pinned : TreeItemContext.Pinnable
17 | ];
18 | if (screen.jiras.length) {
19 | contexts.push(TreeItemContext.Jira);
20 | }
21 | return new TreeItemContextProvider(...contexts);
22 | }
23 |
24 | export default class ScreenTreeItem extends TreeItem implements ZeplinUriProvider {
25 | public constructor(public screen: Screen, public project: Barrel, parent: TreeItem | undefined) {
26 | super(screen.name, parent, getContextProvider(screen));
27 | this.setRemoteIconPath(screen.latestVersion.snapshot.url);
28 | }
29 |
30 | public getZeplinUri(applicationType: ApplicationType): string {
31 | return getScreenUri(this.project.id, this.screen._id, applicationType);
32 | }
33 |
34 | public getZeplinLinkType(): ZeplinLinkType {
35 | return ZeplinLinkType.Screen;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/sidebar/screen/tree/ScreensTreeItem.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import Barrel from "../../../common/domain/barrel/Barrel";
3 | import TreeItem from "../../../common/vscode/tree/TreeItem";
4 | import localization from "../../../localization";
5 | import ProjectScreensStore from "../data/ProjectScreensStore";
6 | import ScreenSection from "../model/ScreenSection";
7 | import ScreenTreeItem from "./ScreenTreeItem";
8 | import ScreenSectionTreeItem from "./ScreenSectionTreeItem";
9 |
10 | export default class ScreensTreeItem extends TreeItem {
11 | public constructor(private project: Barrel, parent: TreeItem | undefined) {
12 | super(localization.sidebar.screen.screens, parent, undefined, vscode.TreeItemCollapsibleState.Collapsed);
13 | }
14 |
15 | public async getChildren(): Promise {
16 | const { data, errors } = await new ProjectScreensStore(this.project.id).get();
17 | if (!data) {
18 | const error = errors![0];
19 | return [new TreeItem(error.message, this)];
20 | } else {
21 | const { screens, sections } = data;
22 | const filledSections = sections?.filter(this.isSectionFilled) ?? [];
23 | if (screens.length || filledSections.length) {
24 | return [
25 | ...screens.map(screen => new ScreenTreeItem(screen, this.project, this)),
26 | ...filledSections.map(section => new ScreenSectionTreeItem(section, this.project, this))
27 | ];
28 | } else {
29 | return [new TreeItem(localization.sidebar.screen.noneFound, this)];
30 | }
31 | }
32 | }
33 |
34 | private isSectionFilled(section: ScreenSection) {
35 | return !!section.screens.length;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/sidebar/screen/util/screenUi.ts:
--------------------------------------------------------------------------------
1 | import Screen from "../model/Screen";
2 | import localization from "../../../localization";
3 |
4 | const DETAIL_ITEM_SEPARATOR = " ▸ ";
5 |
6 | function getScreenDetailRepresentation(screen: Screen): string {
7 | return [screen.barrelName, localization.sidebar.screen.screens, screen.sectionName]
8 | .filter(representation => representation)
9 | .join(DETAIL_ITEM_SEPARATOR);
10 | }
11 |
12 | export {
13 | getScreenDetailRepresentation
14 | };
15 |
--------------------------------------------------------------------------------
/src/sidebar/zeplinComponent/data/CumulativeBarrelDetailsStore.ts:
--------------------------------------------------------------------------------
1 | import BarrelDetails from "../../../common/domain/zeplinComponent/model/BarrelDetails";
2 | import Store from "../../../common/domain/store/Store";
3 | import BarrelError from "../../../common/domain/zeplinComponent/model/BarrelError";
4 | import BarrelType from "../../../common/domain/barrel/BarrelType";
5 | import Result from "../../../common/domain/store/Result";
6 | import BarrelDetailsStoreProvider from "../../../common/domain/zeplinComponent/data/BarrelDetailsStoreProvider";
7 |
8 | export default class CumulativeBarrelDetailsStore implements Store {
9 | public constructor(private barrelId: string, private barrelType: BarrelType) { }
10 |
11 | public get = async (): Promise> => {
12 | const leafId = this.barrelId;
13 | const leafType = this.barrelType;
14 | let currentId: string | undefined = leafId;
15 | let currentType = leafType;
16 | const accumulatedResult: Result = {
17 | data: [],
18 | errors: []
19 | };
20 |
21 | while (currentId) {
22 | const childId: string | undefined = currentId === leafId ? undefined : leafId;
23 | const childType: BarrelType | undefined = currentId === leafId ? undefined : leafType;
24 |
25 | const { data, errors }: Result =
26 | await BarrelDetailsStoreProvider.get(currentId, currentType, childId, childType).get();
27 |
28 | if (errors) {
29 | accumulatedResult.errors!.push(...errors);
30 | currentId = undefined;
31 | }
32 |
33 | if (data) {
34 | accumulatedResult.data!.push(data);
35 | currentId = data.parentId;
36 | currentType = BarrelType.Styleguide;
37 | }
38 | }
39 |
40 | return accumulatedResult;
41 | };
42 |
43 | public refresh = (): Promise> => {
44 | BarrelDetailsStoreProvider.clearCache();
45 | return this.get();
46 | };
47 | }
48 |
--------------------------------------------------------------------------------
/src/sidebar/zeplinComponent/tree/BarrelZeplinComponentsTreeItem.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import TreeItem from "../../../common/vscode/tree/TreeItem";
3 | import BarrelDetails from "../../../common/domain/zeplinComponent/model/BarrelDetails";
4 | import { createList } from "./zeplinComponentTreeUtil";
5 | import TreeItemContextProvider from "../../../common/vscode/tree/TreeItemContextProvider";
6 | import TreeItemContext from "../../../common/domain/tree/TreeItemContext";
7 | import ZeplinUriProvider from "../../../common/domain/openInZeplin/model/ZeplinUriProvider";
8 | import ApplicationType from "../../../common/domain/openInZeplin/model/ApplicationType";
9 | import { getComponentsUri } from "../../../common/domain/openInZeplin/util/zeplinUris";
10 | import BarrelType from "../../../common/domain/barrel/BarrelType";
11 | import localization from "../../../localization";
12 | import ZeplinLinkType from "../../../common/domain/openInZeplin/model/ZeplinLinkType";
13 |
14 | const contextProvider = new TreeItemContextProvider(
15 | TreeItemContext.ZeplinComponentBarrel,
16 | TreeItemContext.ZeplinLink
17 | );
18 |
19 | export default class BarrelZeplinComponentsTreeItem extends TreeItem implements ZeplinUriProvider {
20 | public constructor(public barrel: BarrelDetails, parent: TreeItem | undefined) {
21 | super(
22 | barrel.type === BarrelType.Project ? localization.sidebar.zeplinComponent.localStyleguide : barrel.name,
23 | parent,
24 | contextProvider,
25 | vscode.TreeItemCollapsibleState.Collapsed
26 | );
27 | this.setRemoteIconPath(barrel.thumbnail);
28 | }
29 |
30 | public getChildren(): TreeItem[] {
31 | const [{ components }, ...sections] = this.barrel.componentSections;
32 | return createList(components, sections, this.barrel, this);
33 | }
34 |
35 | public getZeplinUri(applicationType: ApplicationType): string {
36 | return getComponentsUri(this.barrel.id, this.barrel.type, applicationType);
37 | }
38 |
39 | public getZeplinLinkType(): ZeplinLinkType {
40 | return this.barrel.type === BarrelType.Project ? ZeplinLinkType.Project : ZeplinLinkType.Styleguide;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/sidebar/zeplinComponent/tree/ZeplinComponentSectionTreeItem.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import TreeItem from "../../../common/vscode/tree/TreeItem";
3 | import ZeplinComponentSection from "../../../common/domain/zeplinComponent/model/ZeplinComponentSection";
4 | import { createList } from "./zeplinComponentTreeUtil";
5 | import Barrel from "../../../common/domain/barrel/Barrel";
6 | import TreeItemContextProvider from "../../../common/vscode/tree/TreeItemContextProvider";
7 | import TreeItemContext from "../../../common/domain/tree/TreeItemContext";
8 | import ZeplinUriProvider from "../../../common/domain/openInZeplin/model/ZeplinUriProvider";
9 | import ApplicationType from "../../../common/domain/openInZeplin/model/ApplicationType";
10 | import { getComponentSectionUri } from "../../../common/domain/openInZeplin/util/zeplinUris";
11 | import ZeplinLinkType from "../../../common/domain/openInZeplin/model/ZeplinLinkType";
12 |
13 | const contextProvider = new TreeItemContextProvider(
14 | TreeItemContext.ZeplinComponentSection,
15 | TreeItemContext.ZeplinLink
16 | );
17 |
18 | export default class ZeplinComponentSectionTreeItem extends TreeItem implements ZeplinUriProvider {
19 | public constructor(public section: ZeplinComponentSection, public barrel: Barrel, parent: TreeItem | undefined) {
20 | super(section.name, parent, contextProvider, vscode.TreeItemCollapsibleState.Collapsed);
21 | }
22 |
23 | public getChildren(): TreeItem[] {
24 | return createList(this.section.components, this.section.componentSections, this.barrel, this);
25 | }
26 |
27 | public getZeplinUri(applicationType: ApplicationType): string {
28 | return getComponentSectionUri(this.barrel.id, this.barrel.type, this.section._id, applicationType);
29 | }
30 |
31 | public getZeplinLinkType(): ZeplinLinkType {
32 | return ZeplinLinkType.ComponentSection;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/sidebar/zeplinComponent/tree/ZeplinComponentTreeItem.ts:
--------------------------------------------------------------------------------
1 | import TreeItem from "../../../common/vscode/tree/TreeItem";
2 | import ResponseZeplinComponent from "../../../common/domain/zeplinComponent/model/ResponseZeplinComponent";
3 | import Barrel from "../../../common/domain/barrel/Barrel";
4 | import TreeItemContextProvider from "../../../common/vscode/tree/TreeItemContextProvider";
5 | import TreeItemContext from "../../../common/domain/tree/TreeItemContext";
6 | import { isComponentPinned } from "../../pin/util/pinUtil";
7 | import ZeplinUriProvider from "../../../common/domain/openInZeplin/model/ZeplinUriProvider";
8 | import ApplicationType from "../../../common/domain/openInZeplin/model/ApplicationType";
9 | import { getComponentUri } from "../../../common/domain/openInZeplin/util/zeplinUris";
10 | import ZeplinLinkType from "../../../common/domain/openInZeplin/model/ZeplinLinkType";
11 |
12 | function getContextProvider(zeplinComponent: ResponseZeplinComponent): TreeItemContextProvider {
13 | return new TreeItemContextProvider(
14 | TreeItemContext.ZeplinComponent,
15 | TreeItemContext.ZeplinLink,
16 | isComponentPinned(zeplinComponent) ? TreeItemContext.Pinned : TreeItemContext.Pinnable
17 | );
18 | }
19 |
20 | export default class ZeplinComponentTreeItem extends TreeItem implements ZeplinUriProvider {
21 | public constructor(
22 | public zeplinComponent: ResponseZeplinComponent, public barrel: Barrel, parent: TreeItem | undefined
23 | ) {
24 | super(zeplinComponent.name, parent, getContextProvider(zeplinComponent));
25 | this.setRemoteIconPath(zeplinComponent.latestVersion.snapshot.url);
26 | }
27 |
28 | public getZeplinUri(applicationType: ApplicationType): string {
29 | return getComponentUri(this.barrel.id, this.barrel.type, this.zeplinComponent._id, applicationType);
30 | }
31 |
32 | public getZeplinLinkType(): ZeplinLinkType {
33 | return ZeplinLinkType.Component;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/sidebar/zeplinComponent/tree/ZeplinComponentsTreeItem.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import Barrel from "../../../common/domain/barrel/Barrel";
3 | import TreeItem from "../../../common/vscode/tree/TreeItem";
4 | import CumulativeBarrelDetailsStore from "../data/CumulativeBarrelDetailsStore";
5 | import ExpandedErrorTreeItem from "../../../common/vscode/tree/ExpandedErrorTreeItem";
6 | import BarrelZeplinComponentsTreeItem from "./BarrelZeplinComponentsTreeItem";
7 | import { createList } from "./zeplinComponentTreeUtil";
8 | import localization from "../../../localization";
9 |
10 | export default class ZeplinComponentsTreeItem extends TreeItem {
11 | public constructor(private barrel: Barrel, parent: TreeItem | undefined) {
12 | super(
13 | localization.common.zeplinComponent.zeplinComponents,
14 | parent,
15 | undefined,
16 | vscode.TreeItemCollapsibleState.Collapsed
17 | );
18 | }
19 |
20 | public async getChildren(): Promise {
21 | const { data, errors } = await new CumulativeBarrelDetailsStore(this.barrel.id, this.barrel.type).get();
22 |
23 | if (!data?.length) {
24 | const error = errors![0];
25 | return [new TreeItem(error.message, this)];
26 | } else if (data.length === 1 && !errors?.length) {
27 | const [{ components }, ...sections] = data[0].componentSections;
28 | return createList(components, sections, this.barrel, this);
29 | } else {
30 | const error = errors?.[0];
31 | const errorItems = error ? [new ExpandedErrorTreeItem(error.id, error.message, this)] : [];
32 | return [
33 | ...data.map(barrelDetails => new BarrelZeplinComponentsTreeItem(barrelDetails, this)),
34 | ...errorItems
35 | ];
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/sidebar/zeplinComponent/tree/zeplinComponentTreeUtil.ts:
--------------------------------------------------------------------------------
1 | import TreeItem from "../../../common/vscode/tree/TreeItem";
2 | import ZeplinComponentSection from "../../../common/domain/zeplinComponent/model/ZeplinComponentSection";
3 | import ResponseZeplinComponent from "../../../common/domain/zeplinComponent/model/ResponseZeplinComponent";
4 | import ZeplinComponentTreeItem from "./ZeplinComponentTreeItem";
5 | import ZeplinComponentSectionTreeItem from "./ZeplinComponentSectionTreeItem";
6 | import localization from "../../../localization";
7 | import Barrel from "../../../common/domain/barrel/Barrel";
8 |
9 | function createList(
10 | components: ResponseZeplinComponent[], sections: ZeplinComponentSection[], barrel: Barrel, parent: TreeItem):
11 | TreeItem[] {
12 | const filledSections = sections?.filter(isSectionFilled) ?? [];
13 | if (components.length || filledSections.length) {
14 | return [
15 | ...components.map(component => new ZeplinComponentTreeItem(component, barrel, parent)),
16 | ...filledSections.map(section => new ZeplinComponentSectionTreeItem(section, barrel, parent))
17 | ];
18 | } else {
19 | return [new TreeItem(localization.sidebar.zeplinComponent.noneFound, parent)];
20 | }
21 | }
22 |
23 | function isSectionFilled(section: ZeplinComponentSection): boolean {
24 | return !!section.components.length || section.componentSections.some(isSectionFilled);
25 | }
26 |
27 | export {
28 | createList
29 | };
30 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "target": "es6",
6 | "outDir": "out",
7 | "lib": [
8 | "es6"
9 | ],
10 | "sourceMap": true,
11 | "rootDir": "src",
12 | "strict": true
13 | },
14 | "exclude": [
15 | "node_modules",
16 | ".vscode-test"
17 | ]
18 | }
--------------------------------------------------------------------------------