├── .all-contributorsrc
├── .editorconfig
├── .github
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .nvmrc
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── appveyor.yml
├── circle.yml
├── contribution.json
├── keymaps
└── project-viewer.json
├── menus
└── project-viewer.json
├── package-lock.json
├── package.json
├── spec
├── api-spec.js
├── colours-spec.js
├── common-spec.js
├── database-spec.js
├── db-spec.js
├── dom-builder-spec.js
├── group-model-spec.js
├── group-view-spec.js
├── main-spec.js
└── project-model-spec.js
├── src
├── api.js
├── colours.js
├── common.js
├── config.js
├── constants.js
├── database.js
├── db.js
├── dom-builder.js
├── editor-view.js
├── group-view.js
├── json
│ ├── devicons.json
│ ├── octicons.json
│ └── release-notes.json
├── main-view.js
├── main.js
├── map.js
├── model.js
├── packages.js
├── project-view.js
├── projects-list-view.js
├── projects-list.js
├── status-bar.js
└── workers
│ └── github.js
└── styles
├── project-viewer.less
└── pv-variables.less
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "atom-project-viewer",
3 | "projectOwner": "jccguimaraes",
4 | "files": [
5 | "README.md"
6 | ],
7 | "imageSize": 100,
8 | "commit": true,
9 | "contributors": [
10 | {
11 | "login": "jccguimaraes",
12 | "name": "João Guimarães",
13 | "avatar_url": "https://avatars.githubusercontent.com/u/14871650?v=3",
14 | "profile": "https://github.com/jccguimaraes",
15 | "contributions": [
16 | "question",
17 | "bug",
18 | "code",
19 | "design",
20 | "doc",
21 | "review"
22 | ]
23 | },
24 | {
25 | "login": "Hammster",
26 | "name": "Hans Koch",
27 | "avatar_url": "https://avatars.githubusercontent.com/u/1093709?v=3",
28 | "profile": "https://github.com/Hammster",
29 | "contributions": [
30 | "code"
31 | ]
32 | },
33 | {
34 | "login": "DamnedScholar",
35 | "name": "Holland Wilson",
36 | "avatar_url": "https://avatars.githubusercontent.com/u/4084322?v=3",
37 | "profile": "https://github.com/DamnedScholar",
38 | "contributions": [
39 | "code"
40 | ]
41 | },
42 | {
43 | "login": "amilor",
44 | "name": "Roman Huba",
45 | "avatar_url": "https://avatars.githubusercontent.com/u/7261682?v=3",
46 | "profile": "https://github.com/amilor",
47 | "contributions": [
48 | "code"
49 | ]
50 | },
51 | {
52 | "login": "lneveu",
53 | "name": "Loann Neveu",
54 | "avatar_url": "https://avatars.githubusercontent.com/u/10619585?v=3",
55 | "profile": "https://github.com/lneveu",
56 | "contributions": [
57 | "code",
58 | "bug"
59 | ]
60 | },
61 | {
62 | "login": "bitkris-dev",
63 | "name": "Kristian Barrese",
64 | "avatar_url": "https://avatars0.githubusercontent.com/u/12634286?v=3&s=460",
65 | "profile": "https://github.com/bitkris-dev",
66 | "contributions": [
67 | "design",
68 | "bug"
69 | ]
70 | },
71 | {
72 | "login": "girlandhercode",
73 | "name": "Nicole",
74 | "avatar_url": "https://avatars.githubusercontent.com/u/2183606?v=3",
75 | "profile": "https://github.com/girlandhercode",
76 | "contributions": [
77 | "bug"
78 | ]
79 | },
80 | {
81 | "login": "colorful-tones",
82 | "name": "Damon Cook",
83 | "avatar_url": "https://avatars.githubusercontent.com/u/405912?v=3",
84 | "profile": "http://www.damonacook.com",
85 | "contributions": [
86 | "bug"
87 | ]
88 | },
89 | {
90 | "login": "zhudock",
91 | "name": "Zach Hudock",
92 | "avatar_url": "https://avatars.githubusercontent.com/u/12414689?v=3",
93 | "profile": "https://github.com/zhudock",
94 | "contributions": [
95 | "bug"
96 | ]
97 | },
98 | {
99 | "login": "CKLFP",
100 | "name": "Filip Paço",
101 | "avatar_url": "https://avatars.githubusercontent.com/u/5192692?v=3",
102 | "profile": "https://github.com/CKLFP",
103 | "contributions": [
104 | "bug"
105 | ]
106 | },
107 | {
108 | "login": "stephen-last",
109 | "name": "Stephen Last",
110 | "avatar_url": "https://avatars2.githubusercontent.com/u/16349203?v=3",
111 | "profile": "https://github.com/stephen-last",
112 | "contributions": [
113 | "bug"
114 | ]
115 | },
116 | {
117 | "login": "GreenGremlin",
118 | "name": "Jonathan",
119 | "avatar_url": "https://avatars2.githubusercontent.com/u/647452?v=3",
120 | "profile": "https://github.com/GreenGremlin",
121 | "contributions": [
122 | "bug"
123 | ]
124 | },
125 | {
126 | "login": "skratchdot",
127 | "name": "◬",
128 | "avatar_url": "https://avatars2.githubusercontent.com/u/434470?v=3",
129 | "profile": "http://skratchdot.com/",
130 | "contributions": [
131 | "bug"
132 | ]
133 | },
134 | {
135 | "login": "erikgeiser",
136 | "name": "Erik G.",
137 | "avatar_url": "https://avatars0.githubusercontent.com/u/14264874?v=3",
138 | "profile": "https://github.com/erikgeiser",
139 | "contributions": [
140 | "code"
141 | ]
142 | },
143 | {
144 | "login": "rgawenda",
145 | "name": "netizen",
146 | "avatar_url": "https://avatars2.githubusercontent.com/u/3426685?v=4",
147 | "profile": "https://github.com/rgawenda",
148 | "contributions": [
149 | "code"
150 | ]
151 | },
152 | {
153 | "login": "mdeuerlein",
154 | "name": "Markus M. Deuerlein",
155 | "avatar_url": "https://avatars0.githubusercontent.com/u/14030524?v=4",
156 | "profile": "https://entidia.de",
157 | "contributions": [
158 | "bug",
159 | "code"
160 | ]
161 | },
162 | {
163 | "login": "audrummer15",
164 | "name": "Adam Brown",
165 | "avatar_url": "https://avatars2.githubusercontent.com/u/1392689?v=4",
166 | "profile": "https://github.com/audrummer15",
167 | "contributions": [
168 | "code"
169 | ]
170 | }
171 | ]
172 | }
173 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = auto
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Issue
2 | Description of the **issue**.
3 |
4 | ## Environment
5 | - **OS**: ...;
6 | - **Atom**: ...;
7 | - Any other particularity;
8 |
9 | ## Steps
10 | Please try to describe **step by step** how to get to the issue
11 |
12 | > and if possible provide a screenshot. :+1:
13 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Pull Request
2 | Description of the **pull request**.
3 |
4 | ## Reason
5 | Please try to describe **the reason** for this. Thanks!
6 |
7 | > and if possible provide a screenshot. :+1:
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | npm-debug.log
3 | node_modules
4 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 10.2.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ### Project specific config ###
2 | language: generic
3 |
4 | env:
5 | global:
6 | - APM_TEST_PACKAGES=""
7 | - ATOM_LINT_WITH_BUNDLED_NODE="true"
8 |
9 | matrix:
10 | - ATOM_CHANNEL=stable
11 | - ATOM_CHANNEL=beta
12 |
13 | os:
14 | - linux
15 | - osx
16 |
17 | dist: trusty
18 |
19 | ### Generic setup follows ###
20 | script:
21 | - curl -s -O https://raw.githubusercontent.com/atom/ci/master/build-package.sh
22 | - chmod u+x build-package.sh
23 | - ./build-package.sh
24 |
25 | notifications:
26 | email:
27 | on_success: never
28 | on_failure: change
29 |
30 | branches:
31 | only:
32 | - master
33 |
34 | git:
35 | depth: 10
36 |
37 | sudo: false
38 |
39 | addons:
40 | apt:
41 | sources:
42 | - ubuntu-toolchain-r-test
43 | packages:
44 | - g++-6
45 | - fakeroot
46 | - git
47 | - libsecret-1-dev
48 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
4 |
5 | There are two main ways to contribute to this package. Either open an issue on the [*github*]() page or **fork** the package and send a **Pull Request**.
6 |
7 | ## Opening Issues
8 |
9 | For helping me maintain this package, please follow the next guidelines when opening issues on [*github*]().
10 |
11 | > **NOTE** Please provide screenshots if possible!
12 |
13 | #### Feature Requests
14 | Please start an issue with the following title `(feature): description`.
15 |
16 | #### Issues / Defects
17 | Please start an issue with the following title `(issue): description`.
18 |
19 | #### Performance / Refactor
20 | Please start an issue with the following title `(perf): description`.
21 |
22 | ## Making a Pull Request
23 |
24 | Just **fork** the project, enjoy coding and send a **Pull Request** to me. Will haply inspect and accept it.
25 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 João Guimarães
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # README
2 |
3 | [](https://gitter.im/jccguimaraes/atom-project-viewer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 | [](#contributors)
5 |
6 | [](https://atom.io/packages/project-viewer/)
7 | [](https://atom.io/packages/project-viewer/)
8 | [](https://atom.io/packages/project-viewer/)
9 |
10 | [](https://travis-ci.org/jccguimaraes/atom-project-viewer)
11 | [](https://ci.appveyor.com/project/jccguimaraes/atom-project-viewer)
13 | [](https://circleci.com/gh/jccguimaraes/atom-project-viewer)
15 |
16 | ## Table Of Contents
17 |
18 | * [Introduction](#introduction)
19 | * [Installation](#installation)
20 | * [Features](#features)
21 | * [Shortcuts](#shortcuts)
22 | * [Settings](#settings)
23 | * [Local File manipulation](#local-file-manipulation)
24 | * [Group Schema](#group-schema)
25 | * [Project Schema](#project-schema)
26 | * [Contributors](#contributors)
27 | * [Contacts](#contacts)
28 | * [A Special Thank You!](#a-special-thank-you)
29 |
30 | ## Introduction
31 |
32 | This is a package built for and by the Atom community. For contribution read [below](#contributors).
33 |
34 | This package has grown so much over the last year that I felt the need to make it more stable and community friendly. And this required a more deep refactor with lots of new ideas and improvements, also huge amount of :heart: and :sweat_drops:.
35 |
36 | So here it is! **Enjoy and contribute!** :earth_africa:
37 |
38 | > Please keep in mind that after **Atom `1.17.0`** some functionalities changed, and implementations of this package are still being tested for stability.
39 |
40 | ## Installation
41 |
42 | In a terminal / command line write the following line `apm install project-viewer`.
43 |
44 | Or just find the package by accessing the menu **Atom → Preferences... → Install** and search for ***project-viewer***.
45 |
46 | ## Features
47 |
48 | - Group nesting;
49 | - > Infinite nesting of `groups` which can contain also `projects`;
50 | - > `projects` can be at any level.
51 | - Sidebar Left / Right (first or last) position;
52 | - Auto hide sidebar with hover behavior;
53 | - Resizable panel;
54 | - > *Double click* to default width;
55 | - Hide header for more space;
56 | - > This is available through a config option, default is *not autohide*.
57 | - Focus toggle;
58 | - > Toggling focus will switch between current active element and the panel.
59 | - `SelectListView` integration;
60 | - > Only shows `projects`.
61 | - Traverse and select `projects` with `up` and `down` keys;
62 | - Toggle collapse / expand of `groups` with `left` and `right` keys;
63 | - `status-bar` with the `project`'s' *breadcrumb* path;
64 | - Drag & Drop `groups` and `projects`;
65 | - Drag and drop a `group` or `project` into a `group` will add it as a child;
66 | - Drag and drop a `group` or `project` into an `project` will add it as sibling of the dropped item;
67 | - Drag and drop a `group` or `project` into a clear space in the panel will add it as a root child;
68 | - Order dragged `group` / `project` accordingly with dropped `group` sorting.
69 | - Open the local database file for direct editing;
70 | - Old database schemas conversion tools;
71 | - Backup services (**GitHub's *private* gist**);
72 | - Editor for `groups` / `projects` creation and update;
73 | - Create, update and remove `group` or `project`;
74 | - Automatic set it's name according to first path base name added;
75 | - Batch operation on a `project` creation;
76 | - > Ability to create individual `projects` when more than one path is provided;
77 | - > Each project will automatically have it's name set to it's path base name.
78 | - Filtering icons;
79 | - List of icons in editor as *only icons* or *icon and description*;
80 | - > This is available through a config option, default is *icon and description*.
81 | - Sort children `groups` / `projects`.
82 | - > Sorting root `groups` / `projects` is done through a config option.
83 | - Context menu for delete, update and create new `group` or `project`;
84 | - > Create option is only available in `groups` or the `root`.
85 | - Show the given path in a file manager. (in `finder` or `explorer`'s alike');
86 | - Empty `groups` and / or `projects` list message;
87 | - Custom colors for `groups` and `projects`;
88 | - Custom colors for main title, for hovering on a `project` and for selected `project`;
89 | - Option to open a `project` in a new window or vice versa;
90 | - > This is available through a config option which will switch between what is the primary option, defaults to open in *same window*;
91 | - > Context menu switching also available.
92 | - Elevate current opened folders in `tree-view` to a `project`;
93 | - `Add Project Folder` and `Remove Project Folder` will update current selected project as well;
94 | - Keep context when switching from `projects`.
95 | - > This is available through a config option, default is *switch contexts*.
96 |
97 | ## Shortcuts
98 |
99 | - `shift-ctrl-alt-c` toggles sidebar autohide;
100 | - `shift-ctrl-alt-v` toggles sidebar visibility;
101 | - `shift-ctrl-alt-n` open the editor tab;
102 | - `shift-ctrl-alt-m` toggle focus from active panel and the sidebar;
103 | - `shift-ctrl-alt-l` toggle the select list modal;
104 |
105 | ## Settings
106 |
107 | Settings | Type | Description | Default
108 | ---------|------|-------------|--------
109 | `visibilityOption` | `String` | Define what would be the default action for **project-viewer** visibility on startup. | `Display on startup`
110 | `visibilityActive` | `Boolean` | Relative to the interaction option selected above. | `true`
111 | `panelPosition` | `String` | Position the panel to the left or right of the main pane. | `Right`
112 | `autoHide` | `Boolean` | Panel has auto hide with hover behavior. | `false`
113 | `hideHeader` | `Boolean` | You can have more space for the list by hiding the header. | `false`
114 | `keepContext` | `Boolean` | When switching from items, if set to `true`, will keep current context. Also will not save contexts between switching. | `false`
115 | `openNewWindow` | `Boolean` | Always open items in a new window. | `false`
116 | `statusBar` | `Boolean` | Will show the breadcrumb to the current opened project in the `status-bar`. | `false`
117 | `customWidth` | `Integer` | Define a custom width for the panel.
*double clicking* on the resizer will reset the width | 200
118 | `customHotZone` | `Integer` | Cursor movement within this width will make a hidden panel appear | 20
119 | `rootSortBy` | `Array` | Sets the root sort by. | `position`
120 | `onlyIcons` | `Boolean` | Will show only the icons in the icon\'s list | `true`
121 | `customPalette` | `String` | Custom palette to use on editor | `#F1E4E8, #F7B05B, #595959, #CD5334, #EDB88B, #23282E, #263655, #F75468, #FF808F, #FFDB80, #292E1E, #248232, #2BA84A, #D8DAD3, #FCFFFC, #8EA604, #F5BB00, #EC9F05, #FF5722, #BF3100`
122 | `customSelectedColor` | `String` | Set custom selected project color | `''`
123 | `customHoverColor` | `String` | Set custom hover project color | `''`
124 | `customTitleColor` | `String` | Set custom main title color | `''`
125 | `packagesReload` | `String` | List of packages to reload | `status-bar, linter, linter-ui-default`
126 | `disclaimer` | `Object` | Show release notes on startup | `true`
127 |
128 | > Keep in mind that this package uses Atom's Storage to save all groups and projects. It is wise to save it to the cloud (ex: you can import and export a private Gist through this package!).
129 |
130 | ## Local File manipulation
131 |
132 | Change it at your own risk! :speak_no_evil:
133 |
134 | ### Group Schema
135 |
136 | Parameter | Type | Description | Default | Required
137 | ----------|------|-------------|---------|---------
138 | `type` | `String` | The type of the model | `group` | `true`
139 | `name` | `String` | The name of the project | In theory... any string / emoji | `true`
140 | `sortBy` | `String` | Sorting of the nested `groups` and `projects` | Possible options are `position`, `reserve-position`, `alphabetically` and `reverse-alphabetically` | `true`
141 | `icon` | `String` | Custom icon `octicons` or `devicons` | `''` | `false`
142 | `color` | `String` | Custom color | `''` | `false`
143 | `expanded` | `Boolean` | `group` is collapsed or expanded | `false` | `true`
144 | `list` | `Array` | An array of models (`group` or `project` | `[]` | `true`
145 |
146 | ### Project Schema
147 |
148 | Parameter | Type | Description | Default | Required
149 | ----------|------|-------------|---------|---------
150 | `type` | `String` | The type of the model | `project` | `true`
151 | `name` | `String` | The name of the project | In theory... any strj g / emoji | `true`
152 | `icon` | `String` | Custom icon `octicons` or `devicons` | `''` | `false`
153 | `color` | `String` | Custom color | `''` | `false`
154 | `devMode` | `Boolean` | *Not working for now* | `false` | `false`
155 | `config` | `Object` | *Not working for now* | `{}` | `false`
156 | `paths` | `Array` | An array of the root files beloging to the project | `[]` | `true`
157 |
158 | ## Contributors
159 |
160 | Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):
161 |
162 |
163 | | [
João Guimarães](https://github.com/jccguimaraes)
💬 [🐛](https://github.com/jccguimaraes/atom-project-viewer/issues?q=author%3Ajccguimaraes) [💻](https://github.com/jccguimaraes/atom-project-viewer/commits?author=jccguimaraes) 🎨 [📖](https://github.com/jccguimaraes/atom-project-viewer/commits?author=jccguimaraes) 👀 | [
Hans Koch](https://github.com/Hammster)
[💻](https://github.com/jccguimaraes/atom-project-viewer/commits?author=Hammster) | [
Holland Wilson](https://github.com/DamnedScholar)
[💻](https://github.com/jccguimaraes/atom-project-viewer/commits?author=DamnedScholar) | [
Roman Huba](https://github.com/amilor)
[💻](https://github.com/jccguimaraes/atom-project-viewer/commits?author=amilor) | [
Loann Neveu](https://github.com/lneveu)
[💻](https://github.com/jccguimaraes/atom-project-viewer/commits?author=lneveu) [🐛](https://github.com/jccguimaraes/atom-project-viewer/issues?q=author%3Alneveu) | [
Kristian Barrese](https://github.com/bitkris-dev)
🎨 [🐛](https://github.com/jccguimaraes/atom-project-viewer/issues?q=author%3Abitkris-dev) | [
Nicole](https://github.com/girlandhercode)
[🐛](https://github.com/jccguimaraes/atom-project-viewer/issues?q=author%3Agirlandhercode) |
164 | | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
165 | | [
Damon Cook](http://www.damonacook.com)
[🐛](https://github.com/jccguimaraes/atom-project-viewer/issues?q=author%3Acolorful-tones) | [
Zach Hudock](https://github.com/zhudock)
[🐛](https://github.com/jccguimaraes/atom-project-viewer/issues?q=author%3Azhudock) | [
Filip Paço](https://github.com/CKLFP)
[🐛](https://github.com/jccguimaraes/atom-project-viewer/issues?q=author%3ACKLFP) | [
Stephen Last](https://github.com/stephen-last)
[🐛](https://github.com/jccguimaraes/atom-project-viewer/issues?q=author%3Astephen-last) | [
Jonathan](https://github.com/GreenGremlin)
[🐛](https://github.com/jccguimaraes/atom-project-viewer/issues?q=author%3AGreenGremlin) | [
◬](http://skratchdot.com/)
[🐛](https://github.com/jccguimaraes/atom-project-viewer/issues?q=author%3Askratchdot) | [
Erik G.](https://github.com/erikgeiser)
[💻](https://github.com/jccguimaraes/atom-project-viewer/commits?author=erikgeiser) |
166 | | [
netizen](https://github.com/rgawenda)
[💻](https://github.com/jccguimaraes/atom-project-viewer/commits?author=rgawenda) | [
Markus M. Deuerlein](https://entidia.de)
[🐛](https://github.com/jccguimaraes/atom-project-viewer/issues?q=author%3Amdeuerlein) [💻](https://github.com/jccguimaraes/atom-project-viewer/commits?author=mdeuerlein) |
167 |
168 |
169 | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
170 |
171 | > If you feel you were left out, just shout!
172 |
173 | ## Contacts
174 |
175 | You can follow me on [Twitter](https://twitter.com/jccguimaraes)
176 |
177 | ## A Special Thank You!
178 |
179 | I thank you all for giving such great feedback! :beers: & :bear: for everyone.
180 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | ### Project specific config ###
2 | environment:
3 | APM_TEST_PACKAGES:
4 | ATOM_LINT_WITH_BUNDLED_NODE: "true"
5 |
6 | matrix:
7 | - ATOM_CHANNEL: stable
8 | - ATOM_CHANNEL: beta
9 |
10 | ### Generic setup follows ###
11 | build_script:
12 | - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1'))
13 |
14 | branches:
15 | only:
16 | - master
17 |
18 | version: "{build}"
19 | platform: x64
20 | clone_depth: 10
21 | skip_tags: true
22 | test: off
23 | deploy: off
24 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | environment:
3 | ATOM_LINT_WITH_BUNDLED_NODE: "true"
4 | APM_TEST_PACKAGES: ""
5 |
6 | dependencies:
7 | override:
8 | - curl -s -O https://raw.githubusercontent.com/atom/ci/master/build-package.sh
9 | - chmod u+x build-package.sh
10 |
11 | test:
12 | override:
13 | - ./build-package.sh
14 |
--------------------------------------------------------------------------------
/contribution.json:
--------------------------------------------------------------------------------
1 | // https://gitmagic.io/rules
2 | {
3 | "commit": {
4 | "subject_cannot_be_empty": true,
5 | "subject_must_be_longer_than": 4,
6 | "subject_must_be_shorter_than": 101,
7 | "subject_lines_must_be_shorter_than": 51,
8 | "subject_must_be_single_line": true,
9 | "subject_must_be_in_tense": "imperative",
10 | "subject_must_start_with_case": "lower",
11 | "subject_must_not_end_with_dot": true,
12 |
13 | "body_lines_must_be_shorter_than": 73
14 | },
15 | "pull_request": {
16 | "subject_cannot_be_empty": true,
17 | "subject_must_be_longer_than": 4,
18 | "subject_must_be_shorter_than": 101,
19 | "subject_must_be_in_tense": "imperative",
20 | "subject_must_start_with_case": "upper",
21 | "subject_must_not_end_with_dot": true,
22 |
23 | "body_cannot_be_empty": true,
24 | "body_must_include_verification_steps": true,
25 | "body_must_include_screenshot": ["html", "css"]
26 | },
27 | "issue": {
28 | "subject_cannot_be_empty": true,
29 | "subject_must_be_longer_than": 4,
30 | "subject_must_be_shorter_than": 101,
31 | "subject_must_be_in_tense": "imperative",
32 | "subject_must_start_with_case": "upper",
33 | "subject_must_not_end_with_dot": true,
34 |
35 | "body_cannot_be_empty": true,
36 | "body_must_include_reproduction_steps": ["bug"],
37 |
38 | "label_must_be_set": true
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/keymaps/project-viewer.json:
--------------------------------------------------------------------------------
1 | {
2 | ".platform-darwin, .platform-win32, .platform-linux": {
3 | "shift-ctrl-alt-c": "project-viewer:autohidePanel",
4 | "shift-ctrl-alt-v": "project-viewer:togglePanel",
5 | "shift-ctrl-alt-n": "project-viewer:openEditor",
6 | "shift-ctrl-alt-m": "project-viewer:focusPanel",
7 | "shift-ctrl-alt-l": "project-viewer:toggleSelectList"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/menus/project-viewer.json:
--------------------------------------------------------------------------------
1 | {
2 | "context-menu": {},
3 | "menu": [
4 | {
5 | "label": "Packages",
6 | "submenu": [
7 | {
8 | "label": "Project Viewer",
9 | "submenu": [
10 | {
11 | "label": "Toggle Panel",
12 | "command": "project-viewer:togglePanel"
13 | },
14 | {
15 | "label": "Open editor...",
16 | "command": "project-viewer:openEditor"
17 | },
18 | {
19 | "label": "Toggle Select View...",
20 | "command": "project-viewer:toggleSelectList"
21 | },
22 | {"type": "separator"},
23 | {
24 | "label": "Import / Export...",
25 | "submenu": [
26 | {
27 | "label": "Import from a private gist",
28 | "command": "project-viewer:gistImport"
29 | },
30 | {
31 | "label": "Export to a private gist",
32 | "command": "project-viewer:gistExport"
33 | }
34 | ]
35 | },
36 | {
37 | "label": "Utilities...",
38 | "submenu": [
39 | {
40 | "label": "Open database file",
41 | "command": "project-viewer:openDatabase"
42 | },
43 | {
44 | "label": "Convert from 0.3.x local database",
45 | "command": "project-viewer:migrate03x"
46 | },
47 | {
48 | "label": "Clear all projects stored states",
49 | "command": "project-viewer:clearStates"
50 | }
51 | ]
52 | }
53 | ]
54 | }
55 | ]
56 | }
57 | ]
58 | }
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "project-viewer",
3 | "main": "./src/main",
4 | "version": "1.4.0",
5 | "description": "A project manager that lets you add, edit and remove groups and projects as well as switching between them.",
6 | "keywords": [
7 | "project",
8 | "productivity",
9 | "management",
10 | "settings",
11 | "workflow"
12 | ],
13 | "repository": "https://github.com/jccguimaraes/atom-project-viewer",
14 | "license": "MIT",
15 | "engines": {
16 | "atom": ">=1.39.1",
17 | "node": ">=10.2.0"
18 | },
19 | "dependencies": {
20 | "atom-select-list": "latest",
21 | "devicons": "1.8.0"
22 | },
23 | "devDependencies": {
24 | "all-contributors-cli": "latest"
25 | },
26 | "consumedServices": {
27 | "status-bar": {
28 | "versions": {
29 | "^1.0.0": "provideStatusBar"
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/spec/api-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const api = require('./../src/api');
4 |
5 | xdescribe ('api', function () {
6 |
7 | it ('should create a group model', function () {
8 | const candidate = {name: 'group #1'};
9 | const groupModel = api.group.createModel(candidate);
10 | expect(groupModel.name).toBe('group #1');
11 | });
12 |
13 | it ('should create a project model', function () {
14 | const candidate = {name: 'project #1'};
15 | const groupModel = api.project.createModel(candidate);
16 | expect(groupModel.name).toBe('project #1');
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/spec/colours-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const colours = require('../src/colours');
4 |
5 | describe ('module: colours', function () {
6 |
7 | describe ('on initialization', function () {
8 |
9 | it ('should add only one stylesheet', function () {
10 | colours.initialize();
11 | expect(colours.mapper.element).not.toBeUndefined();
12 | const styles = document.querySelector('head atom-styles').children.length;
13 | colours.initialize();
14 | const stylesEqual = document.querySelector('head atom-styles').children.length;
15 |
16 | expect(styles).toEqual(stylesEqual);
17 | });
18 | });
19 |
20 | describe ('on destruction', function () {
21 |
22 | it ('should remove the stylesheet', function () {
23 | colours.initialize();
24 | const styles = document.querySelector('head atom-styles').children.length;
25 | colours.destroy();
26 | const stylesEqual = document.querySelector('head atom-styles').children.length;
27 |
28 | expect(colours.mapper.element).toBeUndefined();
29 | expect(styles).not.toEqual(stylesEqual);
30 | });
31 | });
32 |
33 | describe ('setting a color', function () {
34 |
35 | it ('should add the rule', function () {
36 | colours.initialize();
37 | const id = 'pv_1';
38 | expect(colours.mapper.element.sheet.cssRules).toHaveLength(0);
39 | colours.addRule(id, 'group', '#ccc');
40 | expect(colours.mapper.element.sheet.cssRules).toHaveLength(1);
41 | expect(colours.mapper.element.sheet.cssRules[0].selectorText)
42 | .toEqual(colours.mapper.selectorTexts[id]);
43 | colours.destroy();
44 | });
45 |
46 | it ('should remove the rule', function () {
47 | colours.initialize();
48 | const id = 'pv_1';
49 | expect(colours.mapper.element.sheet.cssRules).toHaveLength(0);
50 | colours.addRule(id, 'group', '#ccc');
51 | expect(colours.mapper.element.sheet.cssRules).toHaveLength(1);
52 | colours.removeRule(id);
53 | expect(colours.mapper.element.sheet.cssRules).toHaveLength(0);
54 | colours.destroy();
55 | });
56 |
57 | it ('should not duplicate rules for same itemId', function () {
58 | colours.initialize();
59 | const id = 'pv_1';
60 | colours.addRule(id, 'group', '#ccc');
61 | colours.addRule(id, 'group', '#ddd');
62 | expect(colours.mapper.element.sheet.cssRules).toHaveLength(1);
63 | colours.destroy();
64 | });
65 | });
66 |
67 | });
68 |
--------------------------------------------------------------------------------
/spec/common-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const cleanConfig = require('../src/common').cleanConfig;
4 | const getModel = require('../src/common').getModel;
5 | const getView = require('../src/common').getView;
6 | const sortList = require('../src/common').sortList;
7 | const model = require('../src/model');
8 | const view = require('../src/project-view');
9 |
10 | describe ('common', function () {
11 |
12 | it ('should clean a config entry not defined', function () {
13 | atom.config.set('project-viewer.dummy-config', 'dummy-config');
14 | expect(atom.config.get('project-viewer.dummy-config')).toBe('dummy-config');
15 | cleanConfig();
16 | expect(atom.config.get('project-viewer.dummy-config')).toBeUndefined();
17 | });
18 |
19 | it ('should not get any view if not a valid view', function () {
20 | const itemView = document.createElement('div');
21 | expect(getView(itemView)).toBeNull();
22 | });
23 |
24 | it ('should get the view associated with a valid child view', function () {
25 | const itemModel = model.createProject();
26 | const itemView = view.createView(itemModel);
27 | itemView.initialize();
28 | itemView.render();
29 | const insideView = itemView.querySelector('span');
30 | expect(getView(insideView)).toBe(itemView);
31 | });
32 |
33 | it ('should get the view passed', function () {
34 | const itemModel = model.createProject();
35 | const itemView = view.createView(itemModel);
36 | itemView.initialize();
37 | itemView.render();
38 | expect(getView(itemView)).toBe(itemView);
39 | });
40 |
41 | it ('should not get any model if not a valid view', function () {
42 | const itemView = document.createElement('div');
43 | expect(getModel(itemView)).toBeUndefined();
44 | });
45 |
46 | it ('should get the model associated with a valid view', function () {
47 | const itemModel = model.createProject();
48 | const itemView = view.createView(itemModel);
49 | expect(getModel(itemView)).toBe(itemModel);
50 | });
51 |
52 | xdescribe ('#sortList', function () {
53 | const listPosition = [
54 | { name: 'bbbb'},
55 | { name: 'zzzz'},
56 | { name: 'aaaa'},
57 | { name: 'tttt'}
58 | ];
59 | const listReversePosition = [
60 | { name: 'tttt'},
61 | { name: 'aaaa'},
62 | { name: 'zzzz'},
63 | { name: 'bbbb'}
64 | ];
65 | const listAlphabetically = [
66 | { name: 'aaaa'},
67 | { name: 'bbbb'},
68 | { name: 'tttt'},
69 | { name: 'zzzz'}
70 | ];
71 | const listReverseAlphabetically = [
72 | { name: 'zzzz'},
73 | { name: 'tttt'},
74 | { name: 'bbbb'},
75 | { name: 'aaaa'}
76 | ];
77 |
78 | let list;
79 |
80 | beforeEach(function () {
81 | list = Array.from(listPosition);
82 | });
83 |
84 | it ('should keep the list\'s position as it is', function () {
85 | sortList(list, 'position');
86 | expect(list).toEqual(listPosition);
87 | });
88 |
89 | it ('should reverse the list\'s position', function () {
90 | sortList(list, 'reverse-position');
91 | expect(list).toEqual(listReversePosition);
92 | });
93 |
94 | it ('should sort the list in alphabetically order', function () {
95 | sortList(list, 'alphabetically');
96 | expect(list).toEqual(listAlphabetically);
97 | });
98 |
99 | it ('should sort the list in reverse alphabetically order', function () {
100 | sortList(list, 'reverse-alphabetically');
101 | expect(list).toEqual(listReverseAlphabetically);
102 | });
103 | });
104 |
105 | });
106 |
--------------------------------------------------------------------------------
/spec/database-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const database = require('../src/database');
4 | const path = require('path');
5 | const fs = require('fs');
6 |
7 | xdescribe ('database', function () {
8 | //
9 | // let fsReadFileError;
10 | // let fsReadFileData;
11 | //
12 | // beforeEach (function () {
13 | // spyOn(fs, 'readFile').andCallFake(function (file, options, cb) {
14 | // cb(fsReadFileError, fsReadFileData);
15 | // });
16 | // spyOn(fs, 'writeFile').andCallFake(function (file, content) {
17 | // // console.log(file, content);
18 | // });
19 | //
20 | // spyOn(atom.notifications, 'addError');
21 | // spyOn(atom.notifications, 'addWarning');
22 | // spyOn(atom.notifications, 'addSuccess');
23 | //
24 | // spyOn(atom, 'open');
25 | // });
26 |
27 | it ('should open local file', function () {
28 | spyOn(atom, 'open');
29 | database.openDatabase();
30 | expect(atom.open).toHaveBeenCalledWith({
31 | pathsToOpen: path.join(atom.getConfigDirPath(), 'project-viewer.json'),
32 | newWindow: false
33 | });
34 | });
35 |
36 | describe ('subscription and unsubscription workflow', function () {
37 |
38 | it ('should subscribe and unsubscribe a callback', function () {
39 | const cb = jasmine.createSpy('subscribeCallBack');
40 | const unsubscription = database.subscribe(cb);
41 | database.runSubscribers();
42 | expect(cb).toHaveBeenCalled();
43 | cb.reset();
44 |
45 | const result = unsubscription();
46 | database.runSubscribers();
47 |
48 | expect(result).toBe(true);
49 | expect(cb).not.toHaveBeenCalled();
50 | });
51 | });
52 |
53 | describe ('directory watching', function () {
54 | const spy = spyOn(fs, 'watch');
55 | const pathArg = atom.getConfigDirPath();
56 | database.activate();
57 | expect(spy).toHaveBeenCalled();
58 | expect(spy.calls.length).toEqual(1);
59 | expect(spy.calls[0].args[0]).toBe(pathArg);
60 | expect(typeof spy.calls[0].args[1]).toBe('function');
61 | });
62 |
63 |
64 |
65 |
66 |
67 |
68 | // it ('should return an empty array if no refresh', function () {
69 | // expect(database.fetch()).toEqual([]);
70 | // });
71 | //
72 | // it ('should not exist', function () {
73 | // fsReadFileError = {};
74 | // database.refresh();
75 | // expect(database.fetch()).toEqual([]);
76 | // expect(atom.notifications.addWarning).toHaveBeenCalled();
77 | // });
78 | //
79 | // it ('should exist but empty', function () {
80 | // fsReadFileError = null;
81 | // fsReadFileData = '';
82 | // database.refresh();
83 | // expect(database.fetch()).toEqual([]);
84 | // // expect(atom.notifications.addWarning).toHaveBeenCalled();
85 | // });
86 | //
87 | // it ('should exist but with wrong schema', function () {
88 | // returnValue = JSON.stringify({});
89 | // expect(database.refresh(returnValue)).toEqual([]);
90 | // });
91 | //
92 | // it ('should exist and with a good schema', function () {
93 | // // mimic the atom.getStorageFolder().load
94 | // const mockedRawDB = fs.readFileSync(
95 | // `${__dirname}/mocks/project-viewer.json`, 'utf8'
96 | // );
97 | // returnValue = JSON.parse(mockedRawDB);
98 | // const store = database.refresh(returnValue);
99 | // expect(store.length).toBe(5);
100 | // });
101 | //
102 | // describe ('the hierachy', function () {
103 | //
104 | // let mockedRawDB;
105 | //
106 | // beforeEach (function () {
107 | // // mimic the atom.getStorageFolder().load
108 | // mockedRawDB = fs.readFileSync(
109 | // `${__dirname}/mocks/project-viewer.json`, 'utf8'
110 | // );
111 | // returnValue = JSON.parse(mockedRawDB);
112 | // });
113 | //
114 | // it ('should have all prototypes defined', function () {
115 | // const store = database.refresh(returnValue);
116 | // expect(Object.getPrototypeOf(store[0])).toBe(Object.prototype);
117 | // expect(Object.getPrototypeOf(store[1])).toBe(store[0]);
118 | // expect(Object.getPrototypeOf(store[2])).toBe(store[1]);
119 | // expect(Object.getPrototypeOf(store[3])).toBe(store[0]);
120 | // expect(Object.getPrototypeOf(store[4])).toBe(Object.prototype);
121 | // });
122 | //
123 | // it ('should move a model from one parent to another', function () {
124 | // const store = database.refresh(returnValue);
125 | // expect(Object.getPrototypeOf(store[2])).toBe(store[1]);
126 | // const result = database.moveTo(store[2], store[0]);
127 | // expect(result).toBe(true);
128 | // expect(Object.getPrototypeOf(store[2])).toBe(store[0]);
129 | // });
130 | //
131 | // it ('should save and load the same content if no changes', function () {
132 | // const store = database.refresh(returnValue);
133 | // const oldStore = store.slice(0);
134 | // // database.save();
135 | // expect(oldStore).toEqual(store);
136 | // });
137 | //
138 | // it ('should update the local file', function () {
139 | // const store = database.refresh(returnValue);
140 | // const oldStore = store.slice(0);
141 | // database.moveTo(store[2], store[0]);
142 | // expect(oldStore).not.toBe(store);
143 | // });
144 | //
145 | // it ('sould remove and/or add a model from a parent', function () {
146 | // const store = database.refresh(returnValue);
147 | // const oldStore = store.slice(0);
148 | // const projectsDeleted = database.remove(store[2]);
149 | // expect(oldStore).not.toEqual(store);
150 | // expect(oldStore).toContain(projectsDeleted);
151 | // expect(store).not.toContain(projectsDeleted);
152 | //
153 | // database.addTo(projectsDeleted);
154 | // expect(store).toContain(projectsDeleted);
155 | //
156 | // const groupsDeleted = database.remove(store[0]);
157 | // expect(oldStore).toContain(groupsDeleted);
158 | // expect(store).not.toContain(groupsDeleted);
159 | // database.addTo(groupsDeleted, projectsDeleted);
160 | // expect(store).not.toContain(groupsDeleted);
161 | // database.addTo(groupsDeleted);
162 | // expect(store).toContain(groupsDeleted);
163 | // });
164 |
165 | // });
166 | });
167 |
--------------------------------------------------------------------------------
/spec/db-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const database = require('../src/db');
5 |
6 | describe ('database', function () {
7 |
8 | const toBeMethod = function _toBeMethod() {
9 | return typeof this.actual === 'function';
10 | }
11 |
12 | beforeEach(function () {
13 | this.addMatchers({
14 | toBeMethod
15 | });
16 | });
17 |
18 | it ('should have the following methods', function () {
19 | expect(database.initialize).toBeMethod();
20 | expect(database.destroy).toBeMethod();
21 | expect(database.addToStore).toBeMethod();
22 | expect(database.removeFromStore).toBeMethod();
23 | expect(database.moveInStore).toBeMethod();
24 | });
25 |
26 | describe ('store workflow', function () {
27 |
28 | beforeEach(function () {});
29 |
30 | it ('should add an unique item', function () {
31 | // database.addToStore();
32 | });
33 |
34 | it ('should remove an item', function () {
35 | // database.removeFromStore();
36 | });
37 |
38 | it ('should move an item inside', function () {
39 | // database.moveInStore();
40 | });
41 |
42 | describe('moving items in the store', function () {
43 | // note: this is one of the most important
44 | // test case suit in this module, don't let your OCD get to you!
45 | let item_1, item_2, item_3, item_4, item_5;
46 | let item_6, item_7, item_8, item_9, item_10;
47 |
48 | beforeEach(function () {
49 | item_1 = {type: 'group', name: 'group #1'};
50 | item_2 = {type: 'group', name: 'group #1.1'};
51 | item_3 = {type: 'project', name: 'project #1.1'};
52 | item_4 = {type: 'group', name: 'group #1.1.1'};
53 | item_5 = {type: 'project', name: 'project #1.1.1'};
54 | item_6 = {type: 'group', name: 'group #2'};
55 | item_7 = {type: 'group', name: 'group #2.1'};
56 | item_8 = {type: 'project', name: 'project #2.1'};
57 | item_9 = {type: 'group', name: 'group #2.1.1'};
58 | item_10 = {type: 'project', name: 'project #2.1.1'};
59 |
60 | Object.setPrototypeOf(item_1, Object.prototype);
61 | Object.setPrototypeOf(item_2, item_1);
62 | Object.setPrototypeOf(item_3, item_2);
63 | Object.setPrototypeOf(item_4, item_2);
64 | Object.setPrototypeOf(item_5, item_4);
65 | Object.setPrototypeOf(item_6, Object.prototype);
66 | Object.setPrototypeOf(item_7, item_6);
67 | Object.setPrototypeOf(item_8, item_7);
68 | Object.setPrototypeOf(item_9, item_7);
69 | Object.setPrototypeOf(item_10, item_9);
70 |
71 | database.clearStore();
72 |
73 | database.addToStore([
74 | item_1, item_2, item_3, item_4, item_5,
75 | item_6, item_7, item_8, item_9, item_10
76 | ]);
77 | });
78 |
79 | it ('should not move anything', function () {
80 | const storeAfter = [
81 | item_1, item_2, item_3, item_4, item_5,
82 | item_6, item_7, item_8, item_9, item_10
83 | ]
84 | database.move();
85 | expect(database.listStore()).toEqual(storeAfter);
86 | });
87 |
88 | it ('should not move item_3 when moving to item_8', function () {
89 | const storeAfter = [
90 | item_1, item_2, item_3, item_4, item_5,
91 | item_6, item_7, item_8, item_9, item_10
92 | ]
93 | database.move(item_3, item_8);
94 | expect(database.listStore()).toEqual(storeAfter);
95 | });
96 |
97 | it ('should place item_3 in item_4', function () {
98 | const storeAfter = [
99 | item_1, item_2, item_4, item_5, item_3,
100 | item_6, item_7, item_8, item_9, item_10
101 | ]
102 | database.move(item_3, item_4);
103 | expect(database.listStore()).toEqual(storeAfter);
104 | });
105 |
106 | it ('should place item_4 in item_7', function () {
107 | const storeAfter = [
108 | item_1, item_2, item_3, item_6, item_7,
109 | item_8, item_9, item_10, item_4, item_5
110 | ]
111 | database.move(item_4, item_7);
112 | expect(database.listStore()).toEqual(storeAfter);
113 | });
114 |
115 | it ('should place item_3 after item_8', function () {
116 | const storeAfter = [
117 | item_1, item_2, item_4, item_5, item_6,
118 | item_7, item_8, item_3, item_9, item_10
119 | ]
120 | database.move(item_3, item_8, false);
121 | expect(database.listStore()).toEqual(storeAfter);
122 | });
123 |
124 | it ('should place item_3 before item_8', function () {
125 | const storeAfter = [
126 | item_1, item_2, item_4, item_5, item_6,
127 | item_7, item_3, item_8, item_9, item_10
128 | ]
129 | database.move(item_3, item_8, true);
130 | expect(database.listStore()).toEqual(storeAfter);
131 | });
132 |
133 | it ('should place item_4 after item_8', function () {
134 | const storeAfter = [
135 | item_1, item_2, item_3, item_6, item_7,
136 | item_8, item_4, item_5, item_9, item_10
137 | ]
138 | database.move(item_4, item_8, false);
139 | expect(database.listStore()).toEqual(storeAfter);
140 | });
141 |
142 | it ('should place item_4 before item_8', function () {
143 | const storeAfter = [
144 | item_1, item_2, item_3, item_6, item_7,
145 | item_4, item_5, item_8, item_9, item_10
146 | ]
147 | database.move(item_4, item_8, true);
148 | expect(database.listStore()).toEqual(storeAfter);
149 | });
150 |
151 | it ('should place item_2 after item_9', function () {
152 | const storeAfter = [
153 | item_1, item_6, item_7, item_8, item_9,
154 | item_10, item_2, item_3, item_4, item_5
155 | ]
156 | database.move(item_2, item_9, false);
157 | expect(database.listStore()).toEqual(storeAfter);
158 | });
159 |
160 | it ('should place item_2 before item_9', function () {
161 | const storeAfter = [
162 | item_1, item_6, item_7, item_8, item_2,
163 | item_3, item_4, item_5, item_9, item_10
164 | ]
165 | database.move(item_2, item_9, true);
166 | expect(database.listStore()).toEqual(storeAfter);
167 | });
168 |
169 | it ('should place item_7 after item_3', function () {
170 | const storeAfter = [
171 | item_1, item_2, item_3, item_7, item_8,
172 | item_9, item_10, item_4, item_5, item_6
173 | ]
174 | database.move(item_7, item_3, false);
175 | expect(database.listStore()).toEqual(storeAfter);
176 | });
177 |
178 | it ('should place item_7 before item_3', function () {
179 | const storeAfter = [
180 | item_1, item_2, item_7, item_8, item_9,
181 | item_10, item_3, item_4, item_5, item_6
182 | ]
183 | database.move(item_7, item_3, true);
184 | expect(database.listStore()).toEqual(storeAfter);
185 | });
186 | });
187 | });
188 |
189 | describe ('directory watcher', function () {
190 |
191 | const fsWatchClose = jasmine.createSpy('fsWatchClose');
192 |
193 | beforeEach(function () {
194 | spyOn(fs, 'watch').andReturn({
195 | close: fsWatchClose
196 | });
197 |
198 | spyOn(atom.notifications, 'addError');
199 | spyOn(atom.notifications, 'addSuccess');
200 | });
201 |
202 | it ('should start watching the directory', function () {
203 | expect(database._watcher).toBeUndefined();
204 | database._startWatcher();
205 | expect(fs.watch.callCount).toBe(1);
206 | expect(fs.watch).toHaveBeenCalledWith(
207 | atom.getConfigDirPath(),
208 | database._watcherAware
209 | );
210 | expect(database._watcher).not.toBeUndefined();
211 | });
212 |
213 | it ('should stop watching the directory', function () {
214 | database._startWatcher();
215 | database._closeWatcher();
216 | expect(fsWatchClose).toHaveBeenCalled();
217 | expect(database._watcher).toBeUndefined();
218 | });
219 |
220 | it ('should _watcherAware', function () {
221 | let eventType;
222 | let filename;
223 | let result;
224 |
225 | result = database._watcherAware(eventType, filename);
226 | expect(result).toBeUndefined();
227 |
228 | filename = 'dummy-file';
229 | result = database._watcherAware(eventType, filename);
230 | expect(result).toBeUndefined();
231 |
232 | filename = 'project-viewer.js';
233 | result = database._watcherAware(eventType, filename);
234 | expect(result).toBeUndefined();
235 |
236 | eventType = 'change';
237 | filename = 'project-viewer.js';
238 | result = database._watcherAware(eventType, filename);
239 | expect(result).toBeUndefined();
240 |
241 | eventType = 'rename';
242 | filename = 'project-viewer.js';
243 | result = database._watcherAware(eventType, filename);
244 | expect(result).toBe(true);
245 | })
246 | });
247 | });
248 |
--------------------------------------------------------------------------------
/spec/dom-builder-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const domBuilder = require('../src/dom-builder');
4 | const map = require('./../src/map');
5 |
6 | describe ('dom-builder', function () {
7 |
8 | it ('should not build an element', function () {
9 | let view = domBuilder.createView(
10 | 'pv-dummy'
11 | );
12 | expect(view).toBeUndefined();
13 | });
14 |
15 | it ('should build an element', function () {
16 | let view = domBuilder.createView(
17 | {
18 | tagIs: 'pv-dummy'
19 | }
20 | );
21 | expect(view).not.toBeUndefined();
22 | expect(view instanceof HTMLElement).toBe(true);
23 | expect(view.nodeName).toBe('PV-DUMMY');
24 | });
25 |
26 | it ('should build an element with custom methods', function () {
27 | let methods = {
28 | doStuff: function () { return 'stuff'; }
29 | };
30 | let view = domBuilder.createView(
31 | {
32 | tagIs: 'pv-dummy-2'
33 | },
34 | methods
35 | );
36 | expect(view.doStuff()).toBe('stuff');
37 | });
38 |
39 | it ('should build an element that extends DIV tag', function () {
40 | let view = domBuilder.createView(
41 | {
42 | tagIs: 'pv-dummy-4',
43 | tagExtends: 'div'
44 | }
45 | );
46 | expect(view.nodeName).toBe('DIV');
47 | });
48 |
49 | it ('should build an element with an associated model', function () {
50 | let model = {
51 | doStuff: function () { return 'stuff'; }
52 | };
53 | let view = domBuilder.createView(
54 | {
55 | tagIs: 'pv-dummy-3'
56 | },
57 | undefined,
58 | model
59 | );
60 | let sameModel = map.get(view);
61 | expect(sameModel).toBe(model);
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/spec/group-model-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const modelRef = require('../src/model');
4 |
5 | xdescribe ('group-model', function () {
6 | it ('should have a type of group right?', function () {
7 | const model = modelRef.createGroup();
8 | expect(model.type).toBe('group');
9 | model.type = 'not a group';
10 | expect(model.type).toBe('group');
11 | });
12 |
13 | it ('should not assign unknown properties', function () {
14 | const model = modelRef.createGroup();
15 | expect(model.dummy).toBeNull();
16 | model.dummy = 'dummy value';
17 | expect(model.dummy).toBeNull();
18 | });
19 |
20 | it ('should assign a name', function () {
21 | const model = modelRef.createGroup();
22 | expect(model.name).toBe('unnamed');
23 | model.name = 'group #1';
24 | expect(model.name).toBe('group #1');
25 | });
26 |
27 | it ('should not assign a name if empty string', function () {
28 | const model = modelRef.createGroup({
29 | name: ''
30 | });
31 | expect(model.name).toBe('unnamed');
32 | });
33 |
34 | it ('should keep the last name if setting an invalid', function () {
35 | const model = modelRef.createGroup();
36 | model.name = 1;
37 | expect(model.name).toBe('unnamed');
38 | model.name = '';
39 | expect(model.name).toBe('unnamed');
40 | model.name = '🍺';
41 | expect(model.name).toBe('🍺');
42 | model.name = 'group #1';
43 | model.name = 1;
44 | expect(model.name).toBe('group #1');
45 | model.name = '
dummy
';
46 | expect(model.name).toBe('group #1');
47 | });
48 |
49 | it ('should not set the name in the prototype chain', function () {
50 | const model1 = modelRef.createGroup();
51 | const model2 = modelRef.createGroup();
52 | model2.name = 'group #2';
53 | expect(model1.name).toBe('unnamed');
54 | });
55 |
56 | it ('should assign a sortBy', function () {
57 | const model = modelRef.createGroup();
58 | expect(model.sortBy).toBe('position');
59 | model.sortBy = 'alphabetically';
60 | expect(model.sortBy).toBe('alphabetically');
61 | });
62 |
63 | it ('should keep the last sort if setting an invalid', function () {
64 | const model = modelRef.createGroup();
65 | model.sortBy = 'dummy';
66 | expect(model.sortBy).toBe('position');
67 | model.sortBy = 'alphabetically';
68 | model.sortBy = 'dummy';
69 | expect(model.sortBy).toBe('alphabetically');
70 | });
71 |
72 | it ('should assign an icon', function () {
73 | const model = modelRef.createGroup();
74 | expect(model.icon).toBe('');
75 | model.icon = 'octicon-mark-github';
76 | expect(model.icon).toBe('octicon-mark-github');
77 | model.icon = 'devicon-angular';
78 | expect(model.icon).toBe('devicon-angular');
79 | });
80 |
81 | it ('should keep the last icon if setting an invalid', function () {
82 | const model = modelRef.createGroup();
83 | model.icon = 'dummy';
84 | expect(model.icon).toBe('');
85 | model.icon = 'octicon-mark-github';
86 | model.icon = 'dummy';
87 | expect(model.icon).toBe('octicon-mark-github');
88 | });
89 |
90 | it ('should assign a color', function () {
91 | const model = modelRef.createGroup();
92 | expect(model.color).toBe('');
93 | model.color = '#fff000';
94 | expect(model.color).toBe('#fff000');
95 | model.color = '#fff';
96 | expect(model.color).toBe('#fff');
97 | });
98 |
99 | it ('should keep the last color if setting an invalid', function () {
100 | const model = modelRef.createGroup();
101 | model.color = '#fff000';
102 | model.color = 'dummy';
103 | expect(model.color).toBe('#fff000');
104 | model.color = '#ffff';
105 | expect(model.color).toBe('#fff000');
106 | });
107 |
108 | it ('should have false as the default expanded state', function () {
109 | const model = modelRef.createGroup();
110 | expect(model.expanded).toBe(false);
111 | });
112 |
113 | it ('should set expanded state as true or false', function () {
114 | const model = modelRef.createGroup();
115 | model.expanded = true;
116 | expect(model.expanded).toBe(true);
117 | model.expanded = 'dummy';
118 | expect(model.expanded).toBe(true);
119 | model.expanded = false;
120 | expect(model.expanded).toBe(false);
121 | });
122 |
123 | it ('should get the group\'s breadcrumb', function () {
124 | const model1 = modelRef.createGroup();
125 | const model2 = modelRef.createGroup();
126 | expect(model1.breadcrumb()).toEqual('unnamed');
127 | Object.setPrototypeOf(model1, model2);
128 | expect(model1.breadcrumb()).toEqual('unnamed / unnamed');
129 | model1.name = 'group #1';
130 | model2.name = 'group #2';
131 | expect(model1.breadcrumb()).toEqual('group #2 / group #1');
132 | });
133 |
134 | it ('should set as prototype of another group model only', function () {
135 | const model1 = modelRef.createGroup();
136 | const model2 = modelRef.createGroup();
137 | model2.name = 'group #2';
138 | const setPrototype = function (target, proto) {
139 | return Object.setPrototypeOf(target, proto);
140 | };
141 | expect(setPrototype.bind(null, model1, model2)).not.toThrow();
142 | expect(Object.getPrototypeOf(model1)).toEqual(model2);
143 | expect(setPrototype.bind(null, model1, {})).toThrow();
144 | expect(Object.getPrototypeOf(model1)).toEqual(model2);
145 | });
146 |
147 | it ('should create a model if a candidate is not valid', function () {
148 | const candidate = {
149 | asdasd: 'group candidate #1'
150 | };
151 | const model = modelRef.createGroup(candidate);
152 | expect(model.name).toBe('unnamed');
153 | });
154 |
155 | it ('should create a model if a candidate is valid', function () {
156 | const candidate = {
157 | name: 'group candidate #1'
158 | };
159 | const model = modelRef.createGroup(candidate);
160 | expect(model.name).toBe(candidate.name);
161 | });
162 | });
163 |
--------------------------------------------------------------------------------
/spec/group-view-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const groupView = require('../src/group-view');
4 | const groupModel = require('../src/model');
5 |
6 | xdescribe ('group-view', function () {
7 |
8 | it ('if no valid model is passed it will throw errors', function () {
9 | const view = groupView.createView();
10 | const fnInitialize = function _fnInitialize () {
11 | return view.initialize;
12 | }
13 | const fnRender = function _fnRender () {
14 | return view.render;
15 | }
16 |
17 | expect(view).toBeUndefined();
18 | expect(fnInitialize).toThrow();
19 | expect(fnRender).toThrow();
20 | });
21 |
22 | it ('should return an HTMLElement', function () {
23 | const model = {
24 | uuid: 'pv_' + Math.ceil(Date.now() * Math.random()),
25 | type: 'group'
26 | };
27 | const view = groupView.createView(model);
28 |
29 | expect(view).toBeInstanceOf(HTMLElement);
30 | expect(view.nodeName).toBe('LI');
31 | expect(view.getAttribute('is')).toBe('project-viewer-group');
32 | });
33 |
34 | it ('should initialize the view', function () {
35 | const model = {
36 | uuid: 'pv_' + Math.ceil(Date.now() * Math.random()),
37 | type: 'group'
38 | };
39 | const view = groupView.createView(model);
40 | const state = view.initialize();
41 | expect(state).toBe(true);
42 | });
43 |
44 | it ('should not render a span if model has no icon', function () {
45 | const model = groupModel.createGroup();
46 | const view = groupView.createView(model);
47 | view.initialize();
48 | view.render();
49 | const listItem = view.querySelector('.list-item');
50 | expect(listItem.childNodes).toHaveLength(1);
51 | expect(listItem.childNodes[0].nodeName).toBe('#text');
52 | expect(listItem.childNodes[0].classList).toBeUndefined();
53 | expect(listItem.childNodes[0].textContent).toBe(model.name);
54 | });
55 |
56 | it ('should render a span if model has icon', function () {
57 | const model = groupModel.createGroup({
58 | icon: 'devicon-atom'
59 | });
60 | const view = groupView.createView(model);
61 | view.initialize();
62 | view.render();
63 | const listItem = view.querySelector('.list-item');
64 | expect(listItem.childNodes).toHaveLength(1);
65 | expect(listItem.childNodes[0].nodeName).toBe('SPAN');
66 | expect(listItem.childNodes[0].classList.contains(model.icon)).toBe(true);
67 | expect(listItem.childNodes[0].textContent).toBe(model.name);
68 | });
69 |
70 | it ('should (un)render a span if icon is added or removed', function () {
71 | const icon = 'devicon-atom';
72 | const model = groupModel.createGroup({
73 | icon: icon
74 | });
75 | const view = groupView.createView(model);
76 | view.initialize();
77 | view.render();
78 | let listItem = view.querySelector('.list-item');
79 | expect(listItem.childNodes).toHaveLength(1);
80 | expect(listItem.childNodes[0].nodeName).toBe('SPAN');
81 | expect(listItem.childNodes[0].classList.contains(icon)).toBe(true);
82 | expect(listItem.childNodes[0].textContent).toBe(model.name);
83 |
84 | delete model.icon;
85 | view.render();
86 |
87 | expect(listItem.childNodes).toHaveLength(1);
88 | expect(listItem.childNodes[0].nodeName).toBe('#text');
89 | expect(listItem.childNodes[0].classList).toBeUndefined();
90 | expect(listItem.childNodes[0].textContent).toBe(model.name);
91 | });
92 |
93 | it ('should remove click event listener when detached', function () {
94 | const model = {
95 | uuid: 'pv_' + Math.ceil(Date.now() * Math.random()),
96 | type: 'group'
97 | };
98 | const father = document.createElement('div');
99 | const view = groupView.createView(model);
100 | view.initialize();
101 | view.render();
102 | father.appendChild(view);
103 | var evt = new MouseEvent("click", {
104 | bubbles: true,
105 | cancelable: true,
106 | view: window
107 | });
108 | expect(view).not.toHaveClass('collapsed');
109 | view.querySelector('.list-item').dispatchEvent(evt);
110 | expect(view).toHaveClass('collapsed');
111 | father.removeChild(view);
112 | view.querySelector('.list-item').dispatchEvent(evt);
113 | expect(view).toHaveClass('collapsed');
114 | });
115 |
116 | it ('should toggle collapsed', function () {
117 | const model = {
118 | uuid: 'pv_' + Math.ceil(Date.now() * Math.random()),
119 | type: 'group'
120 | };
121 | const view = groupView.createView(model);
122 | view.initialize();
123 | view.render();
124 | expect(view).not.toHaveClass('collapsed');
125 | view.toggle();
126 | expect(view).toHaveClass('collapsed');
127 | view.toggle();
128 | expect(view).not.toHaveClass('collapsed');
129 | });
130 | });
131 |
--------------------------------------------------------------------------------
/spec/main-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | xdescribe ('project-viewer', function () {
4 |
5 | let mainElement;
6 |
7 | beforeEach (function () {
8 | mainElement = atom.views.getView(atom.workspace);
9 | jasmine.attachToDOM(mainElement);
10 | });
11 |
12 | describe ('activate', function () {
13 |
14 | it ('should append only one project-viewer', function () {
15 | waitsFor (function () {
16 | return atom.packages.activatePackage('project-viewer');
17 | });
18 |
19 | runs(function () {
20 | const projectViewer = mainElement.querySelectorAll('project-viewer');
21 | expect(projectViewer).toHaveLength(1);
22 | atom.workspace.getActivePane().splitRight({copyActiveItem: true});
23 | expect(projectViewer).toHaveLength(1);
24 | });
25 | });
26 | });
27 |
28 | describe ('deactivate', function () {
29 |
30 | it ('should remove project-viewer', function () {
31 | waitsFor (function () {
32 | return atom.packages.activatePackage('project-viewer');
33 | });
34 |
35 | runs(function () {
36 | atom.packages.deactivatePackage('project-viewer');
37 | const projectViewer = mainElement.querySelector('project-viewer');
38 | expect(projectViewer).toBeNull();
39 | });
40 | })
41 | });
42 |
43 | describe ('project-viewer:togglePanel', function () {
44 |
45 | it ('should be visible if visibilityActive is set', function () {
46 | waitsFor (function () {
47 | return atom.packages.activatePackage('project-viewer');
48 | });
49 |
50 | runs(function () {
51 | const projectViewer = mainElement.querySelector('project-viewer');
52 | const parentNode = projectViewer.parentNode;
53 | expect(parentNode).toBeVisible();
54 | atom.commands.dispatch(mainElement, 'project-viewer:togglePanel');
55 | expect(parentNode).toBeHidden();
56 | });
57 | });
58 |
59 | it ('should not be visible if visibilityActive is unset', function () {
60 | waitsFor (function () {
61 | atom.config.set('project-viewer.visibilityActive', false);
62 | return atom.packages.activatePackage('project-viewer');
63 | });
64 |
65 | runs(function () {
66 | const projectViewer = mainElement.querySelector('project-viewer');
67 | const parentNode = projectViewer.parentNode;
68 | expect(parentNode).toBeHidden();
69 | atom.commands.dispatch(mainElement, 'project-viewer:togglePanel');
70 | expect(parentNode).toBeVisible();
71 | });
72 | });
73 | });
74 |
75 | describe ('config changes', function () {
76 |
77 | xdescribe ('changing visibilityOption', function () {});
78 |
79 | describe ('changing visibilityActive', function () {
80 |
81 | it ('should be visible if visibilityActive is set', function () {
82 |
83 | waitsFor (function () {
84 | return atom.packages.activatePackage('project-viewer');
85 | });
86 |
87 | runs(function () {
88 | const projectViewer = mainElement.querySelector('project-viewer');
89 | const parent = projectViewer.parentNode;
90 | expect(parent).toBeVisible();
91 | });
92 | });
93 |
94 | it ('should not be visible if visibilityActive is unset', function () {
95 |
96 | waitsFor (function () {
97 | atom.config.set('project-viewer.visibilityActive', false);
98 | return atom.packages.activatePackage('project-viewer');
99 | });
100 |
101 | runs(function () {
102 | const projectViewer = mainElement.querySelector('project-viewer');
103 | const parent = projectViewer.parentNode;
104 | expect(parent).toBeHidden();
105 | });
106 | });
107 | });
108 |
109 | describe ('changing panelPosition', function () {
110 |
111 | it ('should start by default as a right panel', function () {
112 |
113 | waitsFor (function () {
114 | return atom.packages.activatePackage('project-viewer');
115 | });
116 |
117 | runs(function () {
118 | const projectViewer = mainElement.querySelector('project-viewer');
119 | let panels = [];
120 | atom.workspace.getRightPanels().forEach(function (panel) {
121 | if (panel.getItem() !== projectViewer) { return; }
122 | panels.push(panel.getItem());
123 | });
124 | expect(panels).toHaveLength(1);
125 |
126 | panels = [];
127 | atom.workspace.getLeftPanels().forEach(function (panel) {
128 | if (panel.getItem() !== projectViewer) { return; }
129 | panels.push(panel.getItem());
130 | });
131 | expect(panels).toHaveLength(0);
132 | });
133 | });
134 |
135 | it ('should start as a left panel if set to Left', function () {
136 |
137 | waitsFor (function () {
138 | atom.config.set('project-viewer.panelPosition', 'Left');
139 | return atom.packages.activatePackage('project-viewer');
140 | });
141 |
142 | runs(function () {
143 | const projectViewer = mainElement.querySelector('project-viewer');
144 | let panels = [];
145 | atom.workspace.getRightPanels().forEach(function (panel) {
146 | if (panel.getItem() !== projectViewer) { return; }
147 | panels.push(panel.getItem());
148 | });
149 | expect(panels).toHaveLength(0);
150 |
151 | panels = [];
152 | atom.workspace.getLeftPanels().forEach(function (panel) {
153 | if (panel.getItem() !== projectViewer) { return; }
154 | panels.push(panel.getItem());
155 | });
156 | expect(panels).toHaveLength(1);
157 |
158 | atom.config.set('project-viewer.panelPosition', 'Right');
159 |
160 | panels = [];
161 | atom.workspace.getRightPanels().forEach(function (panel) {
162 | if (panel.getItem() !== projectViewer) { return; }
163 | panels.push(panel.getItem());
164 | });
165 | expect(panels).toHaveLength(1);
166 |
167 | panels = [];
168 | atom.workspace.getLeftPanels().forEach(function (panel) {
169 | if (panel.getItem() !== projectViewer) { return; }
170 | panels.push(panel.getItem());
171 | });
172 | expect(panels).toHaveLength(0);
173 | });
174 | });
175 | });
176 |
177 | describe ('changing autoHide', function () {
178 |
179 | it ('should not be partially hidden if autoHide is unset', function () {
180 |
181 | waitsFor (function () {
182 | return atom.packages.activatePackage('project-viewer');
183 | });
184 |
185 | runs(function () {
186 | const projectViewer = mainElement.querySelector('project-viewer');
187 | expect(projectViewer).not.toHaveClass('autohide');
188 | atom.config.set('project-viewer.autoHide', true);
189 | expect(projectViewer).toHaveClass('autohide');
190 | });
191 | });
192 |
193 | it ('should be partially hidden if autoHide is set', function () {
194 |
195 | waitsFor (function () {
196 | atom.config.set('project-viewer.autoHide', true);
197 | return atom.packages.activatePackage('project-viewer');
198 | });
199 |
200 | runs(function () {
201 | const projectViewer = mainElement.querySelector('project-viewer');
202 | expect(projectViewer).toHaveClass('autohide');
203 | atom.config.set('project-viewer.autoHide', false);
204 | expect(projectViewer).not.toHaveClass('autohide');
205 | });
206 | });
207 | });
208 |
209 | describe ('changing hideHeader', function () {
210 |
211 | it ('should show the title by default', function () {
212 |
213 | waitsFor (function () {
214 | return atom.packages.activatePackage('project-viewer');
215 | });
216 |
217 | runs(function () {
218 | const projectViewer = mainElement.querySelector('project-viewer');
219 | const title = projectViewer.querySelector('.heading');
220 | expect(title).not.toHaveClass('hidden');
221 | atom.config.set('project-viewer.hideHeader', true);
222 | expect(title).toHaveClass('hidden');
223 | });
224 | });
225 |
226 | it ('should not show the title if set to false', function () {
227 |
228 | waitsFor (function () {
229 | atom.config.set('project-viewer.hideHeader', true);
230 | return atom.packages.activatePackage('project-viewer');
231 | });
232 |
233 | runs(function () {
234 | const projectViewer = mainElement.querySelector('project-viewer');
235 | const title = projectViewer.querySelector('.heading');
236 | expect(title).toHaveClass('hidden');
237 | atom.config.set('project-viewer.hideHeader', false);
238 | expect(title).not.toHaveClass('hidden');
239 | });
240 | });
241 | });
242 |
243 | xdescribe ('changing keepContext', function () {});
244 |
245 | xdescribe ('changing openNewWindow', function () {});
246 |
247 | xdescribe ('changing statusBar', function () {});
248 | });
249 |
250 | describe ('project-viewer:autohidePanel', function () {
251 |
252 | it ('should toggle partially hidden state', function () {
253 |
254 | waitsFor (function () {
255 | return atom.packages.activatePackage('project-viewer');
256 | });
257 |
258 | runs(function () {
259 | const projectViewer = mainElement.querySelector('project-viewer');
260 | const state = projectViewer.classList.contains('autohide');
261 | atom.commands.dispatch(mainElement, 'project-viewer:autohidePanel');
262 | expect(projectViewer.classList.contains('autohide')).not.toBe(state);
263 | });
264 | });
265 | });
266 |
267 | xdescribe ('project-viewer:openForm', function () {});
268 |
269 | describe ('project-viewer:focusPanel', function () {
270 |
271 | it ('should focus panel', function () {
272 |
273 | waitsFor (function () {
274 | return atom.packages.activatePackage('project-viewer');
275 | });
276 |
277 | runs(function () {
278 | const projectViewer = mainElement.querySelector('project-viewer');
279 | expect(projectViewer).not.toBe(document.activeElement);
280 | atom.commands.dispatch(mainElement, 'project-viewer:focusPanel');
281 | expect(projectViewer).toBe(document.activeElement);
282 | atom.commands.dispatch(mainElement, 'project-viewer:focusPanel');
283 | expect(projectViewer).not.toBe(document.activeElement);
284 | });
285 | });
286 | });
287 |
288 | xdescribe ('project-viewer:clearState', function () {});
289 |
290 | xdescribe ('project-viewer:clearStates', function () {});
291 |
292 | });
293 |
--------------------------------------------------------------------------------
/spec/project-model-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const modelRef = require('../src/model');
4 |
5 | xdescribe ('project-model', function () {
6 | it ('should have a type of project right?', function () {
7 | const model = modelRef.createProject();
8 | expect(model.type).toBe('project');
9 | model.type = 'not a project';
10 | expect(model.type).toBe('project');
11 | });
12 |
13 | it ('should not assign unknown properties', function () {
14 | const model = modelRef.createProject();
15 | expect(model.dummy).toBeNull();
16 | model.dummy = 'dummy value';
17 | expect(model.dummy).toBeNull();
18 | });
19 |
20 | it ('should assign a name', function () {
21 | const model = modelRef.createProject();
22 | expect(model.name).toBe('unnamed');
23 | model.name = 'project #1';
24 | expect(model.name).toBe('project #1');
25 | });
26 |
27 | it ('should keep the last name if setting an invalid', function () {
28 | const model = modelRef.createProject();
29 | model.name = 1;
30 | expect(model.name).toBe('unnamed');
31 | model.name = '🍺';
32 | expect(model.name).toBe('🍺');
33 | model.name = 'project #1';
34 | model.name = 1;
35 | expect(model.name).toBe('project #1');
36 | model.name = 'dummy
';
37 | expect(model.name).toBe('project #1');
38 | });
39 |
40 | it ('should not set the name in the prototype chain', function () {
41 | const model1 = modelRef.createProject();
42 | const model2 = modelRef.createProject();
43 | model2.name = 'project #2';
44 | expect(model1.name).toBe('unnamed');
45 | });
46 |
47 | it ('should assign a valid array of paths', function () {
48 | const model = modelRef.createProject();
49 | expect(model.paths).toEqual([]);
50 | // model.paths = ['path/to/somewhere'];
51 | // expect(model.paths).toEqual([]);
52 | // model.paths.push('path/to/somewhere');
53 | // expect(model.paths).toEqual([]);
54 | model.addPaths(1);
55 | model.addPaths({});
56 | expect(model.paths).toEqual([]);
57 | model.addPaths('path/to/somewhere');
58 | expect(model.paths).toEqual(['path/to/somewhere']);
59 | model.addPaths('another/path/to/somewhere');
60 | expect(model.paths).toEqual(
61 | ['path/to/somewhere', 'another/path/to/somewhere']
62 | );
63 | let removedPaths = model.clearPaths();
64 | expect(model.paths).toEqual([]);
65 | expect(removedPaths).toEqual(
66 | ['path/to/somewhere', 'another/path/to/somewhere']
67 | );
68 | model.addPaths(
69 | ['path/to/somewhere', 'another/path/to/somewhere']
70 | );
71 | expect(model.paths).toEqual(
72 | ['path/to/somewhere', 'another/path/to/somewhere']
73 | );
74 | model.addPaths(
75 | [1, {}]
76 | );
77 | expect(model.paths).toEqual(
78 | ['path/to/somewhere', 'another/path/to/somewhere']
79 | );
80 | model.removePaths(
81 | ['path/to/somewhere', 'another/path/to/somewhere']
82 | );
83 | expect(model.paths).toEqual([]);
84 | });
85 |
86 | it ('should assign a valid array of paths to each item', function () {
87 | const model1 = modelRef.createProject();
88 | const model2 = modelRef.createProject();
89 | model1.addPaths('path/to/somewhere');
90 | expect(model1.paths).toEqual(['path/to/somewhere']);
91 | expect(model2.paths).toEqual([]);
92 | model2.addPaths('another/path/to/somewhere');
93 | expect(model1.paths).toEqual(['path/to/somewhere']);
94 | expect(model2.paths).toEqual(['another/path/to/somewhere']);
95 | model1.clearPaths();
96 | expect(model1.paths).toEqual([]);
97 | model1.addPaths(['path/to/somewhere', 'another/path/to/somewhere']);
98 | model1.removePath('path/to/somewhere');
99 | expect(model1.paths).toEqual(['another/path/to/somewhere']);
100 | model1.addPaths(['path/to/somewhere', 'another/path/to/somewhere']);
101 | model1.removePaths(['path/to/somewhere', 'another/path/to/somewhere']);
102 | expect(model1.paths).toEqual([]);
103 | });
104 |
105 | it ('should assign an icon', function () {
106 | const model = modelRef.createProject();
107 | expect(model.icon).toBe('');
108 | model.icon = 'octicon-mark-github';
109 | expect(model.icon).toBe('octicon-mark-github');
110 | model.icon = 'devicon-angular';
111 | expect(model.icon).toBe('devicon-angular');
112 | });
113 |
114 | it ('should keep the last icon if setting an invalid', function () {
115 | const model = modelRef.createProject();
116 | model.icon = 'dummy';
117 | expect(model.icon).toBe('');
118 | model.icon = 'octicon-mark-github';
119 | model.icon = 'dummy';
120 | expect(model.icon).toBe('octicon-mark-github');
121 | });
122 |
123 | it ('should assign a color', function () {
124 | const model = modelRef.createProject();
125 | expect(model.color).toBe('');
126 | model.color = '#fff000';
127 | expect(model.color).toBe('#fff000');
128 | model.color = '#fff';
129 | expect(model.color).toBe('#fff');
130 | });
131 |
132 | it ('should keep the last color if setting an invalid', function () {
133 | const model = modelRef.createProject();
134 | model.color = '#fff000';
135 | model.color = 'dummy';
136 | expect(model.color).toBe('#fff000');
137 | model.color = '#ffff';
138 | expect(model.color).toBe('#fff000');
139 | });
140 |
141 | it ('should have false as the default devMode state', function () {
142 | const model = modelRef.createProject();
143 | expect(model.devMode).toBe(false);
144 | });
145 |
146 | it ('should set devMode state as true or false', function () {
147 | const model = modelRef.createProject();
148 | model.devMode = true;
149 | expect(model.devMode).toBe(true);
150 | model.devMode = 'dummy';
151 | expect(model.devMode).toBe(true);
152 | model.devMode = false;
153 | expect(model.devMode).toBe(false);
154 | });
155 |
156 | it ('should set as prototype of another project model only', function () {
157 | const model1 = modelRef.createProject();
158 | const model2 = modelRef.createProject();
159 | model2.name = 'project #2';
160 | const setPrototype = function (target, proto) {
161 | return Object.setPrototypeOf(target, proto);
162 | };
163 | expect(setPrototype.bind(null, model1, model2)).toThrow();
164 | expect(Object.getPrototypeOf(model1)).toEqual(Object.prototype);
165 | expect(setPrototype.bind(null, model1, {})).toThrow();
166 | expect(Object.getPrototypeOf(model1)).toEqual(Object.prototype);
167 | });
168 |
169 | it ('should get the project\'s breadcrumb', function () {
170 | const model1 = modelRef.createProject();
171 | const model2 = modelRef.createGroup();
172 | expect(model1.breadcrumb()).toEqual('unnamed');
173 | model1.name = 'project #1';
174 | expect(model1.breadcrumb()).toEqual('project #1');
175 | Object.setPrototypeOf(model1, model2);
176 | expect(model1.breadcrumb()).toEqual('unnamed / project #1');
177 | model2.name = 'group #1';
178 | expect(model1.breadcrumb()).toEqual('group #1 / project #1');
179 | });
180 |
181 | it ('should create a model if a candidate is not valid', function () {
182 | const candidate = {
183 | asdasd: 'project candidate #1'
184 | };
185 | const model = modelRef.createProject(candidate);
186 | expect(model.name).toBe('unnamed');
187 | });
188 |
189 | it ('should create a model if a candidate is valid', function () {
190 | const candidate = {
191 | name: 'project candidate #1'
192 | };
193 | const model = modelRef.createProject(candidate);
194 | expect(model.name).toBe(candidate.name);
195 | });
196 | });
197 |
--------------------------------------------------------------------------------
/src/api.js:
--------------------------------------------------------------------------------
1 | const model = require('./model');
2 | const groupComponent = require('./group-view');
3 | const projectComponent = require('./project-view');
4 | const editorComponent = require('./editor-view');
5 |
6 | const groupModel = function _groupModel (candidate) {
7 | return model.createGroup(candidate);
8 | };
9 |
10 | const groupView = function _groupView (model) {
11 | return groupComponent.createView(model);
12 | };
13 |
14 | const projectModel = function _projectModel (candidate) {
15 | return model.createProject(candidate);
16 | };
17 |
18 | const projectView = function _projectView (model) {
19 | return projectComponent.createView(model);
20 | };
21 |
22 | const editorView = function _editorView () {
23 | return editorComponent.createView();
24 | };
25 |
26 | const group = {
27 | createModel: groupModel,
28 | createView: groupView
29 | };
30 |
31 | const project = {
32 | createModel: projectModel,
33 | createView: projectView
34 | };
35 |
36 | const editor = {
37 | createView: editorView
38 | };
39 |
40 | const api = {
41 | group,
42 | project,
43 | editor
44 | };
45 |
46 | module.exports = api;
47 |
--------------------------------------------------------------------------------
/src/colours.js:
--------------------------------------------------------------------------------
1 | // =============================================================================
2 | // requires
3 | // =============================================================================
4 |
5 | // =============================================================================
6 | // properties
7 | // =============================================================================
8 |
9 | const mapper = {};
10 |
11 | // =============================================================================
12 | // methods
13 | // =============================================================================
14 |
15 | /**
16 | * Initializes a stylesheet specific for project-viewer
17 | * @public
18 | * @since 1.0.0
19 | */
20 | const initialize = function _initialize () {
21 | if (!mapper.element) {
22 | mapper.element = document.createElement('style');
23 | document.querySelector('head atom-styles')
24 | .appendChild(mapper.element);
25 | atom.styles.addStyleElement(mapper.element);
26 | mapper.element.setAttribute('source-path', 'project-viewer-styles');
27 | mapper.element.setAttribute('priority', 3);
28 | }
29 | mapper.selectorTexts = {};
30 | mapper.rules = {};
31 | };
32 |
33 | /**
34 | * Destroys the stylesheet created and removes it from the DOM
35 | * @public
36 | * @since 1.0.0
37 | */
38 | const destroy = function _destroy () {
39 | if (mapper.element) {
40 | mapper.element.remove();
41 | }
42 | delete mapper.element;
43 | delete mapper.selectorTexts;
44 | delete mapper.rules;
45 | };
46 |
47 | /**
48 | *
49 | * @public
50 | * @since 1.0.0
51 | */
52 | const addRule = function _addRule (itemId, type, value) {
53 | if (!itemId) { return; }
54 | if (!mapper.element || !mapper.element.sheet) { return; }
55 |
56 | if (!value) {
57 | delete mapper.rules[itemId];
58 | return;
59 | }
60 |
61 | let selectorText;
62 | let rule;
63 |
64 | switch (type) {
65 | case 'app':
66 | selectorText = `project-viewer, project-viewer.autohide:hover`;
67 | rule = `${selectorText} { width: ${value}px}`;
68 | break;
69 | case 'hotzone':
70 | selectorText = 'project-viewer.autohide';
71 | rule = `${selectorText} { width: ${value}px}`;
72 | break;
73 | case 'group':
74 | selectorText = `project-viewer .list-tree.has-collapsable-children.pv-has-custom-icons li[data-project-viewer-uuid="${itemId}"] .list-item`;
75 | rule = `${selectorText} { color: ${value}}`;
76 | break;
77 | case 'project':
78 | selectorText = `project-viewer .list-tree.has-collapsable-children.pv-has-custom-icons li[data-project-viewer-uuid="${itemId}"].list-item`;
79 | rule = `${selectorText} { color: ${value}}`;
80 | break;
81 | case 'project-hover':
82 | selectorText = `project-viewer .list-tree.has-collapsable-children.pv-has-custom-icons li.list-item:hover, project-viewer .list-tree.has-collapsable-children.pv-has-custom-icons li .list-item:hover`
83 | rule = `${selectorText} { color: ${value}}`;
84 | break;
85 | case 'project-hover-before':
86 | selectorText = `project-viewer .list-tree.has-collapsable-children.pv-has-custom-icons li.list-item:not(.selected)::before`
87 | rule = `${selectorText} { background-color: ${value}}`;
88 | break;
89 | case 'project-selected':
90 | selectorText = `project-viewer .list-tree.has-collapsable-children.pv-has-custom-icons li.list-item.selected`
91 | rule = `${selectorText} { color: ${value}}`;
92 | break;
93 | case 'title':
94 | selectorText = `project-viewer .heading`;
95 | rule = `${selectorText} { color: ${value}}`;
96 | break;
97 | }
98 |
99 | mapper.selectorTexts[itemId] = selectorText;
100 |
101 | if (mapper.rules[itemId]) {
102 | this.removeRule(itemId);
103 | }
104 | mapper.rules[itemId] = rule;
105 |
106 | mapper.element.sheet.insertRule(
107 | rule,
108 | mapper.element.sheet.cssRules.length
109 | );
110 | };
111 |
112 | /**
113 | *
114 | * @public
115 | * @since 1.0.0
116 | */
117 | const removeRule = function _removeRule (itemId) {
118 |
119 | if (!mapper || !mapper.selectorTexts) { return; }
120 |
121 | const selectorText = mapper.selectorTexts[itemId];
122 |
123 | if (!selectorText) { return; }
124 |
125 | Array.from(mapper.element.sheet.cssRules).forEach(
126 | function (cssRule, idx) {
127 | if (cssRule.selectorText === selectorText) {
128 | if (mapper.element && mapper.element.sheet) {
129 | mapper.element.sheet.deleteRule(idx);
130 | }
131 | mapper.selectorTexts[itemId];
132 | mapper.rules[itemId];
133 | }
134 | }
135 | );
136 | };
137 |
138 | // =============================================================================
139 | // instantiation
140 | // =============================================================================
141 |
142 | const colours = {
143 | // properties
144 | mapper,
145 | // privates
146 | // publics
147 | initialize,
148 | destroy,
149 | addRule,
150 | removeRule
151 | };
152 |
153 | /**
154 | * colours module
155 | * @module database
156 | */
157 | module.exports = colours;
158 |
--------------------------------------------------------------------------------
/src/common.js:
--------------------------------------------------------------------------------
1 | const map = require('./map');
2 | const config = require('./config');
3 |
4 | const cleanConfig = function _cleanConfig () {
5 | const values = Object.keys(atom.config.getAll('project-viewer')[0].value);
6 |
7 | values.forEach(
8 | value => {
9 | if (value === 'disclaimer') {
10 | const versions = atom.config.get('project-viewer.disclaimer');
11 | for (let v in versions) {
12 | if (v !== Object.keys(config.disclaimer.properties)[0]) {
13 | atom.config.unset(`project-viewer.disclaimer.${v}`);
14 | }
15 | }
16 | }
17 | if (config.hasOwnProperty(value)) {
18 | return;
19 | }
20 | atom.config.unset(`project-viewer.${value}`);
21 | }
22 | );
23 | };
24 |
25 | const getModel = function _getModel (view) {
26 | if (!view) { return undefined; }
27 | return map.get(getView(view));
28 | };
29 |
30 | const getView = function _getView (view) {
31 | if (!view) { return undefined; }
32 | while (view && view.nodeName !== 'LI') {
33 | view = view.parentNode;
34 | }
35 | return view;
36 | };
37 |
38 | const getViewFromModel = function _getViewFromModel (model) {
39 | return document.querySelector(
40 | `project-viewer li[data-project-viewer-uuid="${model.uuid}"]`
41 | );
42 | };
43 |
44 | const getSelectedProject = function _getSelectedProject () {
45 | return document.querySelector(
46 | 'project-viewer li[is="project-viewer-project"].selected'
47 | );
48 | };
49 |
50 | const getCurrentOpenedProject = function _getCurrentOpenedProject (model) {
51 | return model && Array.isArray(model.paths) &&
52 | atom.project.getPaths().length > 0 && model.paths.length > 0 &&
53 | model.paths.length === atom.project.getPaths().length &&
54 | atom.project.getPaths().every(path => model.paths.indexOf(path) !== -1);
55 | };
56 |
57 | const buildBlock = function _buildBlock () {
58 | const view = document.createElement('div');
59 | view.classList.add('block', 'pv-editor-block');
60 | return view;
61 | };
62 |
63 | const buildHeader = function _buildHeader (text) {
64 | const view = document.createElement('h2');
65 | view.textContent = text;
66 | view.classList.add('pv-editor-header');
67 | return view;
68 | };
69 |
70 | const buildInput = function _buildInput (type, block) {
71 | const view = document.createElement('input');
72 | view.setAttribute('type', type);
73 | view.classList.add(`input-${type}`, `pv-input-${block}`);
74 | return view;
75 | };
76 |
77 | const buildButton = function _buildButton (text, action) {
78 | const view = document.createElement('button');
79 | view.textContent = text;
80 | view.classList.add('inline-block', 'btn', `btn-${action}`);
81 | return view;
82 | };
83 |
84 | const buildLabel = function _buildLabel (text, type, child) {
85 | const view = document.createElement('label');
86 | view.classList.add('input-label', `pv-input-label-${type}`);
87 | const textNode = document.createTextNode(text);
88 | if (child) { view.appendChild(child); }
89 | view.appendChild(textNode);
90 | return view;
91 | };
92 |
93 | const sorter = function _sorter (reversed, previousView, currentView) {
94 | return (reversed ? -1 : 1) * new Intl.Collator().compare(
95 | getModel(previousView).name,
96 | getModel(currentView).name
97 | );
98 | };
99 |
100 | const sortList = function _sortList (list, sortBy) {
101 | const reversed = sortBy.includes('reverse');
102 | const byPosition = sortBy.includes('position');
103 | if (!byPosition) {
104 | list = list.sort(sorter.bind(null, reversed))
105 | return;
106 | }
107 | if (reversed) {
108 | list.reverse();
109 | }
110 | };
111 |
112 | exports.cleanConfig = cleanConfig;
113 | exports.getModel = getModel;
114 | exports.getViewFromModel = getViewFromModel;
115 | exports.getSelectedProject = getSelectedProject;
116 | exports.getCurrentOpenedProject = getCurrentOpenedProject;
117 | exports.getView = getView;
118 | exports.buildBlock = buildBlock;
119 | exports.buildHeader = buildHeader;
120 | exports.buildInput = buildInput;
121 | exports.buildButton = buildButton;
122 | exports.buildLabel = buildLabel;
123 | exports.sortList = sortList;
124 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | 'dockOrPanel': {
3 | title: 'Old Panel integration versus Dock',
4 | description: 'If you want to use the old panel integration, just leave uncheck',
5 | type: 'boolean',
6 | default: false,
7 | order: 0
8 | },
9 | 'visibilityOption': {
10 | title: 'Panel visibility interaction option',
11 | description: 'Define what would be the default action for **project-viewer** visibility on startup.',
12 | type: 'string',
13 | default: 'Display on startup',
14 | enum: [
15 | 'Display on startup',
16 | 'Remember state'
17 | ],
18 | order: 1
19 | },
20 | 'visibilityActive': {
21 | title: 'Panel visibility interaction state',
22 | description: 'Relative to the interaction option selected above.',
23 | type: 'boolean',
24 | default: true,
25 | order: 2
26 | },
27 | 'panelPosition': {
28 | title: 'Panel Position',
29 | description: 'Position the panel to the left or right of the main pane.',
30 | type: 'string',
31 | default: 'Right (last)',
32 | enum: [
33 | 'Left (first)',
34 | 'Left (last)',
35 | 'Right (first)',
36 | 'Right (last)'
37 | ],
38 | order: 3
39 | },
40 | 'autoHide': {
41 | title: 'Sidebar auto hidding',
42 | description: 'Panel has auto hide with hover behavior.',
43 | type: 'boolean',
44 | default: false,
45 | order: 4
46 | },
47 | 'autoHideAbsolute': {
48 | title: 'Makes the Sidebar auto hidding as an absolute',
49 | description: 'This will not make the workspace change width.',
50 | type: 'boolean',
51 | default: false,
52 | order: 5
53 | },
54 | 'hideHeader': {
55 | title: 'Hide the header',
56 | description: 'You can have more space for the list by hiding the header.',
57 | type: 'boolean',
58 | default: false,
59 | order: 6
60 | },
61 | 'keepContext': {
62 | title: 'Keep Context',
63 | description: 'When switching from items, if set to `true`, will keep current context. Also will not save contexts between switching.',
64 | type: 'boolean',
65 | default: false,
66 | order: 7
67 | },
68 | 'keepWindowSize': {
69 | title: 'Keep Window Size',
70 | description: 'When changing projects, if set to `true`, the window size will not change.',
71 | type: 'boolean',
72 | default: false,
73 | order: 7
74 | },
75 | 'openNewWindow': {
76 | title: 'Open in a new window',
77 | description: 'Always open items in a new window.',
78 | type: 'boolean',
79 | default: false,
80 | order: 8
81 | },
82 | 'statusBar': {
83 | title: 'Show current project in the status-bar',
84 | description: 'Will show the breadcrumb to the current opened project in the `status-bar`.',
85 | type: 'boolean',
86 | default: false,
87 | order: 9
88 | },
89 | 'customWidth': {
90 | title: 'Set a custom panel width',
91 | description: 'Define a custom width for the panel.
*double clicking* on the resizer will reset the width',
92 | type: 'number',
93 | default: 200,
94 | order: 10
95 | },
96 | 'customHotZone': {
97 | title: 'Set a custom hot zone width',
98 | description: 'Cursor movement within this width will make a hidden panel appear',
99 | type: 'number',
100 | default: 20,
101 | order: 10
102 | },
103 | 'rootSortBy': {
104 | title: 'Root SortBy',
105 | description: 'Sets the root sort by',
106 | type: 'string',
107 | default: 'position',
108 | enum: [
109 | 'position',
110 | 'reverse-position',
111 | 'alphabetically',
112 | 'reverse-alphabetically'
113 | ],
114 | order: 11
115 | },
116 | 'githubAccessToken': {
117 | title: 'GitHub Access Token',
118 | description: 'Your personal and private GitHub access token. This is useful if you want to save/backup your projects to a remote place (as a gist). *note*: keep in mind that this token should have only permissions to `rw` gists as well as that any package can access this token string.',
119 | type: 'string',
120 | default: '',
121 | order: 12
122 | },
123 | 'gistId': {
124 | title: 'Gist ID',
125 | description: 'ID of the gist used as a backup storage.',
126 | type: 'string',
127 | default: '',
128 | order: 13
129 | },
130 | 'setName': {
131 | description: 'Name of your working set, for example \'work\' or \'home\'. As each working set is backed up into a separate file in gist, you can have multiple Group/Project sets on different machines and have them all safely backed up on gist.',
132 | type: 'string',
133 | default: 'default',
134 | order: 14
135 | },
136 | 'onlyIcons': {
137 | title: 'Icons list without description',
138 | description: 'Will show only the icons in the icon\'s list',
139 | type: 'boolean',
140 | default: true,
141 | order: 15
142 | },
143 | 'customPalette': {
144 | title: 'Custom palette to use on editor',
145 | description: 'This can be filled with custom colors',
146 | type: 'array',
147 | default: ['#F1E4E8', '#F7B05B', '#595959', '#CD5334', '#EDB88B', '#23282E', '#263655',
148 | '#F75468', '#FF808F', '#FFDB80', '#292E1E', '#248232', '#2BA84A', '#D8DAD3',
149 | '#FCFFFC', '#8EA604', '#F5BB00', '#EC9F05', '#FF5722', '#BF3100'],
150 | items: {
151 | type: 'string'
152 | },
153 | order: 16
154 | },
155 | 'customSelectedColor': {
156 | description: 'Only allows for hexadecimal colors',
157 | type: 'string',
158 | default: '',
159 | order: 17
160 | },
161 | 'customHoverColor': {
162 | description: 'Only allows for hexadecimal colors',
163 | type: 'string',
164 | default: '',
165 | order: 18
166 | },
167 | 'customTitleColor': {
168 | description: 'Only allows for hexadecimal colors',
169 | type: 'string',
170 | default: '',
171 | order: 19
172 | },
173 | 'packagesReload': {
174 | title: 'List of packages to reload',
175 | description: 'This is an attempt to reload any package that stays in the *limbo* of the context switching\n\nExample: pigments, colorio\n\n **Keep in mind that some packages could not work properly. If this happens, please contact me via a feature issue asking to investigate**',
176 | type: 'array',
177 | default: [],
178 | items: {
179 | type: 'string'
180 | },
181 | order: 20
182 | },
183 | 'disclaimer': {
184 | title: 'Show release notes on startup',
185 | type: 'object',
186 | properties: {
187 | 'v140': {
188 | title: "for v1.4.0",
189 | type: 'boolean',
190 | default: true
191 | }
192 | },
193 | order: 21
194 | }
195 | };
196 |
197 | module.exports = config;
198 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | const constants = Object.create(null);
2 |
3 | constants.packageName = 'project-viewer';
4 |
5 | // this makes constants immutable
6 | Object.freeze(constants);
7 |
8 | /**
9 | * Package common constants
10 | * @module constants
11 | */
12 | module.exports = constants;
13 |
--------------------------------------------------------------------------------
/src/database.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const model = require('./model');
4 |
5 | const PACKAGE_NAME = 'Project-Viewer';
6 | const WORKSPACE_URI = 'atom://atom-project-viewer';
7 | let KEEP_CONTEXT = false;
8 |
9 | const version = '1.1.0';
10 | const file = 'project-viewer.json';
11 | const filepath = path.join(atom.getConfigDirPath(), file);
12 | let store = [];
13 | let listeners = [];
14 | let watcher;
15 | let hasLocalFile = false;
16 |
17 | /**
18 | * Maps each model to it's schema object
19 | * @returns {Undefined} cancel if entry has no type or type is not allowed
20 | * @since 1.0.0
21 | */
22 | const processStorageEntry = function _processStorageEntry (reference, entry) {
23 | let prototypeOf = Object.getPrototypeOf(entry);
24 | let obj;
25 |
26 | if (!entry.hasOwnProperty('type')) {
27 | return;
28 | }
29 |
30 | if (entry.type === 'group') {
31 | obj = model.createGroupSchema(entry);
32 | obj.list = [];
33 | }
34 | else if (entry.type === 'project') {
35 | obj = model.createProjectSchema(entry);
36 | }
37 | else {
38 | return;
39 | }
40 |
41 | reference[entry.uuid] = obj;
42 |
43 | if (prototypeOf === Object.prototype) {
44 | this.push(obj);
45 | return;
46 | }
47 | let prototypeOfObj = reference[prototypeOf.uuid];
48 | if (!prototypeOfObj) { return; }
49 |
50 | prototypeOfObj.list.push(obj);
51 | };
52 |
53 | /**
54 | * Changes the current store to be an array with depth depending on each
55 | * entry model's prototype
56 | * @returns {Array} the storage array to be saved locally
57 | * @since 1.0.0
58 | */
59 | const processStore = function _processStore () {
60 | let storage = [];
61 | const reference = {};
62 | store.forEach(processStorageEntry.bind(storage, reference));
63 | return storage;
64 | };
65 |
66 | /**
67 | * Processes the listed model as a group or project
68 | * @param {Object} listed - a candidate to a model
69 | * @param {Object} parentModel - the model where the candidate will be placed
70 | * @since 1.0.0
71 | */
72 | const processList = function _processList (parentModel, listed) {
73 | if (listed.type === 'group') {
74 | processGroup(parentModel, listed);
75 | }
76 | if (listed.type === 'project') {
77 | processProject(parentModel, listed);
78 | }
79 | };
80 |
81 | /**
82 | * Creates a group model from an object data
83 | * @param {Object} protoModel - if exists, it's a referente to a group model
84 | * @param {Object} data - an object with a candidate to become a group
85 | * @since 1.0.0
86 | */
87 | const processGroup = function _processGroup (protoModel, data) {
88 | if (!data) { return; }
89 | let groupModel = model.createGroup(data);
90 |
91 | addTo(groupModel, protoModel);
92 |
93 | data.list.forEach(processList.bind(null, groupModel));
94 | };
95 |
96 | /**
97 | * Creates a project model from an object data
98 | * @param {Object} protoModel - if exists, it's a referente to a group model
99 | * @param {Object} data - an object with a candidate to become a project
100 | * @since 1.0.0
101 | */
102 | const processProject = function _processProject (protoModel, data) {
103 | if (!data) { return; }
104 | let projectModel = model.createProject(data);
105 | addTo(projectModel, protoModel);
106 | };
107 |
108 | /**
109 | * Fetches the current store
110 | * @returns {Array} the current store state
111 | * @public
112 | * @since 1.0.0
113 | */
114 | const fetch = function _fetch () {
115 | return store;
116 | };
117 |
118 | /**
119 | * Writes content to the local database file
120 | * @param {Object} content - The store content
121 | * @since 1.0.0
122 | */
123 | const writeToDB = function _writeToDB (content) {
124 | fs.writeFile(
125 | filepath,
126 | JSON.stringify(content, null, 2),
127 | function writeToDBCallback (err) {
128 | if (err) {
129 | atom.notifications.addError('Local database corrupted', {
130 | detail: '😱! Something when wrong while writing to local file!',
131 | icon: 'database'
132 | });
133 | }
134 | setTimeout(directoryWatch, 1000);
135 | }
136 | );
137 | };
138 |
139 | /**
140 | * Updates the local database file with the current content of the store
141 | * @public
142 | * @since 1.0.0
143 | */
144 | const save = function _save () {
145 | directoryUnwatch();
146 | writeToDB(exportDB());
147 | runSubscribers();
148 | };
149 |
150 | /**
151 | * Processes the content retrieved from reading the file
152 | * @param {String} result - the content that was retrieved in the file
153 | * @returns {Array} the store
154 | * @since 1.0.0
155 | */
156 | const processFileContent = function _processFileContent(result) {
157 | try {
158 | let serialized = JSON.parse(result);
159 | store.length = 0;
160 | serialized.root.forEach(processList.bind(null, undefined));
161 | runSubscribers();
162 | } catch (e) {
163 | atom.notifications.addError('Local database corrupted', {
164 | detail: 'Please check the content of the local database',
165 | icon: 'database'
166 | });
167 | }
168 | return store;
169 | }
170 |
171 | /**
172 | * Loads the local database and processes it
173 | * @public
174 | * @since 1.0.0
175 | */
176 | const refresh = function _refresh () {
177 | fs.readFile(filepath, 'utf8', function (err, data) {
178 | if (err) {
179 | atom.notifications.addWarning('Local database not found', {
180 | description: 'Please go to Packages -> Project Viewer -> Utilities -> Convert from 0.3.x local database if you come from a version previous to 1.0.0',
181 | icon: 'database'
182 | });
183 | return;
184 | }
185 | hasLocalFile = true;
186 | processFileContent(data);
187 | });
188 | };
189 |
190 | /**
191 | * Moves a model from one prototype to another
192 | * @param {Object} childModel - a model object of a group or a project that will
193 | * have it's prototype changed
194 | * @param {Object} protoModel - a model object of a group to be the new prototype
195 | * @return {Null|Boolean} Null if not moved and Boolean if success
196 | * @public
197 | * @since 1.0.0
198 | */
199 | const moveTo = function _moveTo (movingItem, targetedItem, insertBefore) {
200 | // this is basic stuff
201 | if (!movingItem || !targetedItem) { return; }
202 |
203 | // does this actually happen? as in a DnD action? :see_no_evil:
204 | if (movingItem === targetedItem) { return; }
205 |
206 | // if `isBefore` is `undefined` this means that we are moving an item
207 | // to the targetItem's list and not near it
208 | // and the targetItem must be a group
209 | if (insertBefore === undefined && targetedItem.type !== 'group') { return; }
210 |
211 | let prototypeOfmovingItem;
212 |
213 | // if `isBefore` is `undefined` this means that we are moving an item
214 | // to the targetItem's list and not near it
215 | if (insertBefore === undefined) {
216 | prototypeOfmovingItem = Object.getPrototypeOf(movingItem);
217 | }
218 |
219 | // does this actually happen? as in a DnD action? :see_no_evil:
220 | if (prototypeOfmovingItem === targetedItem) {
221 | return;
222 | }
223 |
224 | let movingItemIdx = store.indexOf(movingItem);
225 | const movingItems = [movingItem];
226 |
227 | store.slice(movingItemIdx + 1).some(
228 | storeItem => {
229 | const storeItemPrototype = Object.getPrototypeOf(storeItem);
230 |
231 | // this means that no more children of movingItem
232 | if (movingItems.indexOf(storeItemPrototype) === -1) {
233 | return true;
234 | }
235 |
236 | // add to the moving items array
237 | if (movingItems.indexOf(storeItem) === -1) {
238 | movingItems.push(storeItem);
239 | }
240 |
241 | // and we remove from the store
242 | store.splice(store.indexOf(storeItem), 1);
243 |
244 | // keep searching
245 | return false;
246 | }
247 | );
248 |
249 | store.splice(movingItemIdx, 1);
250 |
251 | let targetedItemIdx = store.indexOf(targetedItem);
252 | let targetedItems = [targetedItem];
253 | let lastTargetedChildIdx = targetedItemIdx;
254 |
255 | store.slice(targetedItemIdx + 1).some(
256 | storeItem => {
257 | const storeItemPrototype = Object.getPrototypeOf(storeItem);
258 |
259 | if (targetedItems.indexOf(storeItemPrototype) === -1) {
260 | // this means that no more children of targetedItem
261 | return true;
262 | }
263 |
264 | // get the last child's index in the current store
265 | // this is a substore so internal index reference is not valid
266 | lastTargetedChildIdx++;
267 |
268 | // add to the targeted items array
269 | if (targetedItems.indexOf(storeItem) === -1) {
270 | targetedItems.push(storeItem);
271 | }
272 |
273 | // keep searching
274 | return false;
275 | }
276 | );
277 |
278 | switch (insertBefore) {
279 | case undefined:
280 | store.splice(lastTargetedChildIdx + 1, 0, ...movingItems);
281 | Object.setPrototypeOf(movingItem, targetedItem);
282 | break;
283 | case true:
284 | store.splice(targetedItemIdx, 0, ...movingItems);
285 | Object.setPrototypeOf(movingItem, Object.getPrototypeOf(targetedItem));
286 | break;
287 | case false:
288 | store.splice(lastTargetedChildIdx + 1, 0, ...movingItems);
289 | Object.setPrototypeOf(movingItem, Object.getPrototypeOf(targetedItem));
290 | break;
291 | }
292 | };
293 |
294 | /**
295 | * Removes a model from the store
296 | * @param {Object} model - the model object to remove from the store
297 | * @returns {Null|Object} Undefined if model is an Array, the model if success
298 | * @public
299 | * @since 1.0.0
300 | */
301 | const remove = function _remove (model) {
302 | const idx = store.indexOf(model);
303 | if (idx === -1) {
304 | return null;
305 | }
306 | const list = store.splice(idx, 1);
307 |
308 | if (list.length === 0) {
309 | return null;
310 | }
311 |
312 | return list[0];
313 | };
314 |
315 | /**
316 | * Add a model in the store
317 | * @param {Object} model - the model object candidate to add to the store
318 | * @param {Object} protoModel - group model object to be the prototype of model
319 | * @returns {Undefined|Boolean} Undefined if model is an Array, true if success
320 | * @public
321 | * @since 1.0.0
322 | */
323 | const addTo = function _addTo (model, protoModel) {
324 | if (Array.isArray(model)) {
325 | model.forEach(
326 | entry => addTo.bind(entry, protoModel)
327 | );
328 | return;
329 | }
330 |
331 | if (protoModel && protoModel.type === 'project') { return; }
332 |
333 | if (protoModel) {
334 | Object.setPrototypeOf(model, protoModel);
335 | }
336 |
337 | store.push(model);
338 |
339 | return true;
340 | };
341 |
342 | const applyMigration03x = async function _applyMigration03x (importedDB) {
343 |
344 | const store03x = importedDB || await atom.stateStore.load(file);
345 |
346 | if (!store03x) {
347 | atom.notifications.addInfo('Old database file not found!', {
348 | icon: 'database',
349 | description: 'Could not find any old database file!'
350 | });
351 | return;
352 | }
353 |
354 | const convertedStore = [];
355 |
356 | function processOldGroup (parentModel, group) {
357 | const groupModel = model.createGroup(group);
358 | convertedStore.push(groupModel);
359 | Object.setPrototypeOf(groupModel, parentModel);
360 | if (group.hasOwnProperty('groups')) {
361 | group.groups.forEach(processOldGroup.bind(null, groupModel));
362 | }
363 | if (group.hasOwnProperty('projects')) {
364 | group.projects.forEach(processOldProject.bind(null, groupModel));
365 | }
366 | }
367 |
368 | function processOldProject (parentModel, project) {
369 | const projectModel = model.createProject(project);
370 | convertedStore.push(projectModel);
371 | Object.setPrototypeOf(projectModel, parentModel);
372 | }
373 |
374 | store03x.clients.forEach(processOldGroup.bind(null, Object.prototype));
375 | store03x.groups.forEach(processOldGroup.bind(null, Object.prototype));
376 | store03x.projects.forEach(processOldProject.bind(null, Object.prototype));
377 |
378 | store = convertedStore;
379 | save();
380 | runSubscribers();
381 | }
382 |
383 | /**
384 | * Migrate old 0.3.x local database to 1.0.0
385 | * @since 1.0.0
386 | */
387 | const migrate03x = function _migrate03x (importedDB) {
388 | if (store) {
389 | const notification = atom.notifications.addWarning('Local database found!', {
390 | icon: 'database',
391 | description: 'There is already an **active** database, are you sure you **want** to loose it?',
392 | dismissable: true,
393 | buttons: [
394 | {
395 | className: 'btn btn-error',
396 | onDidClick: function () {
397 | notification.dismiss();
398 | },
399 | text: 'abort'
400 | },
401 | {
402 | className: 'btn btn-info',
403 | onDidClick: function () {
404 | notification.dismiss();
405 | applyMigration03x.call(this, importedDB);
406 | },
407 | text: 'continue'
408 | }
409 | ]
410 | });
411 | }
412 | };
413 |
414 | const importDB = function _importDB (importedDB) {
415 | writeToDB(importedDB);
416 | };
417 |
418 | const exportDB = function _exportDB () {
419 | const storeProcessed = {
420 | info: {
421 | version,
422 | updated: new Date()
423 | },
424 | root: processStore(store)
425 | };
426 | return storeProcessed;
427 | };
428 |
429 | /**
430 | * Deactivation of the database module
431 | * Clears out the directory watcher
432 | * @public
433 | * @since 1.0.0
434 | */
435 | const deactivate = function _deactivate () {
436 | directoryUnwatch();
437 | };
438 |
439 | /**
440 | * Activation of the database module
441 | * Activates the directory watcher
442 | * @public
443 | * @since 1.0.0
444 | */
445 | const activate = function _activate () {
446 | directoryWatch();
447 | };
448 |
449 | /**
450 | * Each watch notification passes through here where it validates if it was
451 | * a change or a rename/deletion.
452 | * @param {String} event - The event occured in the local database,
453 | * values are change and rename
454 | * @param {String} filename - The listener callback
455 | * @since 1.0.0
456 | */
457 | const directoryWatcher = function _directoryWatcher (event, filename) {
458 | if (filename !== file) { return; }
459 | if (event === 'change') {
460 | refresh();
461 | return;
462 | }
463 |
464 | if (hasLocalFile) {
465 | hasLocalFile = false;
466 | atom.notifications.addError('Local database not found', {
467 | detail: 'it is possible that the file has been renamed or deleted',
468 | icon: 'database'
469 | });
470 | }
471 | else {
472 | atom.notifications.addSuccess('Local database found!', {
473 | icon: 'database'
474 | });
475 | hasLocalFile = true;
476 | refresh();
477 | }
478 | };
479 |
480 | /**
481 | * Unwatches for changes in the atom's config directory
482 | * @since 1.0.0
483 | */
484 | const directoryUnwatch = function _directoryUnwatch () {
485 | if (!watcher) { return; }
486 | watcher.close();
487 | watcher = undefined;
488 | };
489 |
490 | /**
491 | * Watches for changes in the atom's config directory
492 | * @since 1.0.0
493 | */
494 | const directoryWatch = function _directoryWatch () {
495 | if (watcher) {
496 | watcher.close();
497 | watcher = undefined;
498 | }
499 | watcher = fs.watch(atom.getConfigDirPath(), directoryWatcher);
500 | };
501 |
502 | /**
503 | * Runs a subscriber callback when a change in the store is made
504 | * @callback subscriber
505 | * @param {subscriber} listener - The listener callback
506 | * @since 1.0.0
507 | */
508 | const runSubscriber = function _runSubscriber (listener) {
509 | listener(store);
510 | };
511 |
512 | /**
513 | * Runs all subscribers callback
514 | * @since 1.0.0
515 | */
516 | const runSubscribers = function _runSubscribers () {
517 | listeners.forEach(runSubscriber);
518 | };
519 |
520 | /**
521 | * Unsubscribes the callback
522 | * @callback subscriber
523 | * @param {subscriber} listener - The listener callback
524 | * @public
525 | * @since 1.0.0
526 | */
527 | const unsubscribe = function _unsubscribe (listener) {
528 | const idx = listeners.indexOf(listener);
529 | if (idx === -1) { return; }
530 | listeners.splice(idx, 1);
531 | return true;
532 | };
533 |
534 | /**
535 | * Subscribes the callback to be invoked on store changes
536 | * @callback subscriber
537 | * @param {subscriber} listener - The listener callback
538 | * @public
539 | * @since 1.0.0
540 | */
541 | const subscribe = function _subscribe (listener) {
542 | if (listeners.indexOf(listener) !== -1) {
543 | return;
544 | }
545 | listeners.push(listener);
546 | return unsubscribe.bind(this, listener);
547 | };
548 |
549 | /**
550 | * Opens the local database file
551 | * @since 1.0.0
552 | */
553 | const openDatabase = function _openDatabase () {
554 | atom.open({
555 | pathsToOpen: filepath,
556 | newWindow: false
557 | })
558 | };
559 |
560 | const database = Object.create(null);
561 |
562 | database.pathsChangedBypass = false;
563 | database.runSubscribers = runSubscribers;
564 | database.subscribe = subscribe;
565 | database.unsubscribe = unsubscribe;
566 | database.openDatabase = openDatabase;
567 |
568 | database.activate = activate;
569 | database.deactivate = deactivate;
570 | database.fetch = fetch;
571 | database.save = save;
572 | database.refresh = refresh;
573 | database.moveTo = moveTo;
574 | database.remove = remove;
575 | database.addTo = addTo;
576 | database.migrate03x = migrate03x;
577 | database.importDB = importDB;
578 | database.exportDB = exportDB;
579 |
580 | database.PACKAGE_NAME = PACKAGE_NAME;
581 | database.WORKSPACE_URI = WORKSPACE_URI;
582 | database.KEEP_CONTEXT = KEEP_CONTEXT;
583 |
584 | /**
585 | * Database / Store module
586 | * @module database
587 | */
588 | module.exports = database;
589 |
--------------------------------------------------------------------------------
/src/db.js:
--------------------------------------------------------------------------------
1 | // =============================================================================
2 | // requires
3 | // =============================================================================
4 |
5 | const fs = require('fs');
6 |
7 | // =============================================================================
8 | // properties
9 | // =============================================================================
10 |
11 | const dbfile = 'project-viewer.js';
12 | const store = [];
13 | let _worker;
14 |
15 | // =============================================================================
16 | // methods
17 | // =============================================================================
18 |
19 | /**
20 | *
21 | * @private
22 | * @since 1.0.0
23 | */
24 | const _watcherAware = function _watcherAware (eventType, filename) {
25 | if (!eventType || filename !== dbfile) { return; }
26 | if (eventType === 'change') { return; }
27 | return true;
28 | };
29 |
30 | /**
31 | *
32 | * @private
33 | * @since 1.0.0
34 | */
35 | const _startWatcher = function _startWatcher () {
36 | this._closeWatcher();
37 | this._watcher = fs.watch(
38 | atom.getConfigDirPath(),
39 | this._watcherAware
40 | );
41 | };
42 |
43 | /**
44 | *
45 | * @private
46 | * @since 1.0.0
47 | */
48 | const _closeWatcher = function _closeWatcher () {
49 | if (!this._watcher) { return; }
50 | this._watcher.close();
51 | this._watcher = undefined;
52 | return true;
53 | };
54 |
55 | /**
56 | *
57 | * @public
58 | * @since 1.0.0
59 | */
60 | const initialize = function _initialize () {};
61 |
62 | /**
63 | *
64 | * @since 1.0.0
65 | */
66 | const destroy = function _destroy () {};
67 |
68 | /**
69 | *
70 | * @public
71 | * @since 1.0.0
72 | */
73 | const listStore = function _listStore () {
74 | return store;
75 | };
76 |
77 | /**
78 | *
79 | * @public
80 | * @since 1.0.0
81 | */
82 | const clearStore = function _clearStore () {
83 | store.length = 0;
84 | };
85 |
86 | /**
87 | *
88 | * @public
89 | * @since 1.0.0
90 | */
91 | const addToStore = function _addToStore (entry/*, delegator*/) {
92 | if (!entry) return;
93 | if (Array.isArray(entry)) {
94 | entry.forEach(this.addToStore);
95 | return;
96 | }
97 | // if (delegator)
98 | // return entry;
99 | store.push(entry);
100 | };
101 |
102 | /**
103 | *
104 | * @public
105 | * @since 1.0.0
106 | */
107 | const removeFromStore = function _removeFromStore (entry) {
108 | return entry;
109 | };
110 |
111 | /**
112 | *
113 | * @public
114 | * @since 1.0.0
115 | */
116 | const moveInStore = function _moveInStore (entry, delegator) {
117 | if (delegator)
118 | return entry;
119 | };
120 |
121 | const move = function _move (movingItem, targetedItem, insertBefore) {
122 | // this is basic stuff
123 | if (!movingItem || !targetedItem) { return; }
124 |
125 | // does this actually happen? as in a DnD action? :see_no_evil:
126 | if (movingItem === targetedItem) { return; }
127 |
128 | // if `isBefore` is `undefined` this means that we are moving an item
129 | // to the targetItem's list and not near it
130 | // and the targetItem must be a group
131 | if (insertBefore === undefined && targetedItem.type !== 'group') { return; }
132 |
133 | let prototypeOfmovingItem;
134 |
135 | // if `isBefore` is `undefined` this means that we are moving an item
136 | // to the targetItem's list and not near it
137 | if (insertBefore === undefined) {
138 | prototypeOfmovingItem = Object.getPrototypeOf(movingItem);
139 | }
140 |
141 | // does this actually happen? as in a DnD action? :see_no_evil:
142 | if (prototypeOfmovingItem === targetedItem) {
143 | return;
144 | }
145 |
146 | let movingItemIdx = store.indexOf(movingItem);
147 | const movingItems = [movingItem];
148 |
149 | store.slice(movingItemIdx + 1).some(
150 | storeItem => {
151 | const storeItemPrototype = Object.getPrototypeOf(storeItem);
152 |
153 | // this means that no more children of movingItem
154 | if (movingItems.indexOf(storeItemPrototype) === -1) {
155 | return true;
156 | }
157 |
158 | // add to the moving items array
159 | if (movingItems.indexOf(storeItem) === -1) {
160 | movingItems.push(storeItem);
161 | }
162 |
163 | // and we remove from the store
164 | store.splice(store.indexOf(storeItem), 1);
165 |
166 | // keep searching
167 | return false;
168 | }
169 | );
170 |
171 | store.splice(movingItemIdx, 1);
172 |
173 | let targetedItemIdx = store.indexOf(targetedItem);
174 | let targetedItems = [targetedItem];
175 | let lastTargetedChildIdx = targetedItemIdx;
176 |
177 | store.slice(targetedItemIdx + 1).some(
178 | storeItem => {
179 | const storeItemPrototype = Object.getPrototypeOf(storeItem);
180 |
181 | if (targetedItems.indexOf(storeItemPrototype) === -1) {
182 | // this means that no more children of targetedItem
183 | return true;
184 | }
185 |
186 | // get the last child's index in the current store
187 | // this is a substore so internal index reference is not valid
188 | lastTargetedChildIdx++;
189 |
190 | // add to the targeted items array
191 | if (targetedItems.indexOf(storeItem) === -1) {
192 | targetedItems.push(storeItem);
193 | }
194 |
195 | // keep searching
196 | return false;
197 | }
198 | );
199 |
200 | switch (insertBefore) {
201 | case undefined:
202 | store.splice(lastTargetedChildIdx + 1, 0, ...movingItems);
203 | Object.setPrototypeOf(movingItem, targetedItem);
204 | break;
205 | case true:
206 | store.splice(targetedItemIdx, 0, ...movingItems);
207 | Object.setPrototypeOf(movingItem, Object.getPrototypeOf(targetedItem));
208 | break;
209 | case false:
210 | store.splice(lastTargetedChildIdx + 1, 0, ...movingItems);
211 | Object.setPrototypeOf(movingItem, Object.getPrototypeOf(targetedItem));
212 | break;
213 | }
214 | };
215 |
216 | // =============================================================================
217 | // instantiation
218 | // =============================================================================
219 |
220 | const database = {
221 | // properties
222 | _worker,
223 | // privates
224 | _watcherAware,
225 | _startWatcher,
226 | _closeWatcher,
227 | // publics
228 | initialize,
229 | destroy,
230 | clearStore,
231 | listStore,
232 | addToStore,
233 | removeFromStore,
234 | moveInStore,
235 | move
236 | };
237 |
238 | /**
239 | * Database / Store module
240 | * @module database
241 | */
242 | module.exports = database;
243 |
--------------------------------------------------------------------------------
/src/dom-builder.js:
--------------------------------------------------------------------------------
1 | const map = require('./map');
2 |
3 | const createView = function _createView (element, methods, model) {
4 | const tagExtends = element.tagExtends;
5 | const tagIs = element.tagIs;
6 | let view;
7 | let options = {};
8 |
9 | if (!tagIs) {
10 | return;
11 | }
12 |
13 | if (methods) {
14 | options.prototype = methods;
15 | }
16 |
17 | if (tagExtends) {
18 | options.extends = tagExtends;
19 | }
20 |
21 | try {
22 | const viewConstructor = document.registerElement(
23 | tagIs,
24 | options
25 | );
26 | Object.setPrototypeOf(methods, HTMLElement.prototype);
27 | view = new viewConstructor();
28 | } catch (e) {
29 | if (tagExtends) {
30 | view = document.createElement(tagExtends, tagIs);
31 | }
32 | else {
33 | view = document.createElement(tagIs);
34 | }
35 | }
36 |
37 | if (model) {
38 | map.set(view, model);
39 | }
40 | return view;
41 | };
42 |
43 | module.exports = {
44 | createView: createView
45 | };
46 |
--------------------------------------------------------------------------------
/src/group-view.js:
--------------------------------------------------------------------------------
1 | const map = require('./map');
2 | const database = require('./database');
3 | const domBuilder = require('./dom-builder');
4 | const colours = require('./colours');
5 | const {getModel, getView, sortList} = require('./common');
6 |
7 | const dragstart = function _dragstart (evt) {
8 | evt.dataTransfer.setData(
9 | "text/plain",
10 | getModel(evt.target).uuid
11 | );
12 | // evt.dataTransfer.dropEffect = "move";
13 | const view = getView(evt.target);
14 | view.classList.add('dragged');
15 | evt.stopPropagation();
16 | };
17 |
18 | const dragover = function _dragover (evt) {
19 | const view = getView(evt.target);
20 |
21 | const threshold = view.querySelector('.list-item').clientHeight / 3;
22 |
23 | if (view.offsetTop + threshold > evt.layerY) {
24 | view.classList.add('above');
25 | view.classList.remove('center');
26 | view.classList.remove('below');
27 | }
28 | else if (view.offsetTop + (2 * threshold) > evt.layerY) {
29 | view.classList.remove('above');
30 | view.classList.add('center');
31 | view.classList.remove('below');
32 | }
33 | else {
34 | view.classList.remove('above');
35 | view.classList.remove('center');
36 | view.classList.add('below');
37 | }
38 |
39 | evt.preventDefault();
40 | };
41 |
42 | const dragleave = function _dragleave (evt) {
43 | const view = getView(evt.target);
44 | view.classList.remove('above', 'center', 'below');
45 | evt.stopPropagation();
46 | };
47 |
48 | const dragenter = function _dragenter (evt) {
49 | evt.stopPropagation();
50 | };
51 |
52 | const dragend = function _dragend (evt) {
53 | const view = getView(evt.target);
54 | view.classList.remove('above', 'center', 'below', 'dragged');
55 | evt.stopPropagation();
56 | };
57 |
58 | const drop = function _drop (evt) {
59 | evt.stopPropagation();
60 | const uuid = evt.dataTransfer.getData("text/plain");
61 | const draggedView = document.querySelector(
62 | `project-viewer li[data-project-viewer-uuid="${uuid}"]`
63 | );
64 |
65 | if (!draggedView) { return; }
66 |
67 | const droppedModel = getModel(evt.target);
68 | const draggedModel = getModel(draggedView);
69 |
70 | const droppedView = getView(evt.target);
71 |
72 | if (!droppedView) { return; }
73 |
74 | if (droppedModel.type !== 'group') { return; }
75 |
76 | if (droppedView === draggedView) { return; }
77 |
78 | // a bit hacky
79 | let insertBefore = undefined;
80 |
81 | if (droppedView.classList.contains('above')) {
82 | insertBefore = true;
83 | }
84 | else if (droppedView.classList.contains('below')) {
85 | insertBefore = false;
86 | }
87 |
88 | this.classList.remove('dropping', 'below', 'above', 'center');
89 |
90 | database.moveTo(draggedModel, droppedModel, insertBefore);
91 | database.save();
92 | };
93 |
94 | const viewMethods = {
95 | attachedCallback: function _attachedCallback () {
96 | this.addEventListener('dragstart', dragstart, false);
97 | this.addEventListener('dragover', dragover, false);
98 | this.addEventListener('dragleave', dragleave, false);
99 | this.addEventListener('dragenter', dragenter, false);
100 | this.addEventListener('dragend', dragend, false);
101 | this.addEventListener('drop', drop, false);
102 | },
103 | detachedCallback: function _detachedCallback () {
104 | let contentNode = this.querySelector('.list-item');
105 | if (contentNode) {
106 | contentNode.removeEventListener('click', this.expandOrCollapse.bind(this));
107 | }
108 | this.removeEventListener('dragstart', dragstart, false);
109 | this.removeEventListener('dragover', dragover, false);
110 | this.removeEventListener('dragleave', dragleave, false);
111 | this.removeEventListener('dragenter', dragenter, false);
112 | this.removeEventListener('dragend', dragend, false);
113 | this.removeEventListener('drop', drop, false);
114 | },
115 | expandOrCollapse: function _expandOrCollapse (evt) {
116 | evt.preventDefault();
117 | evt.stopPropagation();
118 | const model = map.get(getView(evt.target));
119 | if (!model) { return; }
120 | model.expanded = !model.expanded;
121 | this.classList.toggle('collapsed');
122 | database.save();
123 | },
124 | initialize: function _initialize () {
125 |
126 | const model = map.get(this);
127 | if (!model) { return; }
128 |
129 | let listItem = document.createElement('div');
130 | listItem.classList.add('list-item');
131 | listItem.addEventListener('click', this.expandOrCollapse.bind(this));
132 |
133 | this.classList.add('list-nested-item');
134 | this.classList.toggle('collapsed', !model.expanded);
135 | this.setAttribute('data-project-viewer-uuid', model.uuid);
136 | this.setAttribute('draggable', 'true');
137 | this.appendChild(listItem);
138 |
139 | return true;
140 | },
141 | render: function _render () {
142 | const model = map.get(this);
143 |
144 | if (!model) {
145 | return;
146 | }
147 |
148 | let spanNode = this.querySelector('.list-item span');
149 | let contentNode = this.querySelector('.list-item');
150 |
151 | if (spanNode && spanNode.parentNode !== this) {
152 | spanNode = undefined;
153 | }
154 |
155 | if (!contentNode) {
156 | return;
157 | }
158 |
159 | if (model.icon && !spanNode) {
160 | contentNode.textContent = '';
161 | spanNode = document.createElement('span');
162 | contentNode.appendChild(spanNode);
163 | }
164 |
165 | if (model.icon) {
166 | contentNode = spanNode;
167 | if (model.icon.startsWith('devicons-')) {
168 | contentNode.classList.add('devicons', model.icon);
169 | }
170 | else {
171 | contentNode.classList.add('icon', model.icon);
172 | }
173 | }
174 | else if (spanNode) {
175 | contentNode.removeChild(spanNode);
176 | }
177 |
178 | if (model.name) {
179 | contentNode.textContent = model.name;
180 | }
181 |
182 | if (model.color) {
183 | colours.addRule(model.uuid, model.type, model.color);
184 | } else {
185 | colours.removeRule(model.uuid);
186 | }
187 |
188 | let listTree = this.querySelector('.list-tree');
189 |
190 | if (!listTree) {
191 | listTree = document.createElement('ul');
192 | listTree.classList.add('list-tree');
193 | this.appendChild(listTree);
194 | }
195 | },
196 | sortChildren: function _sortChildren () {
197 | const listTree = this.querySelector('.list-tree');
198 | if (!listTree) { return; }
199 | const children = Array.from(listTree.children);
200 | if (!children || children.length === 0) { return; }
201 |
202 | sortList(children, map.get(this).sortBy);
203 | children.forEach(view => listTree.appendChild(view));
204 | },
205 | attachChild: function _attachChild (node) {
206 | const listTree = this.querySelector('.list-tree');
207 | if (!listTree) { return; }
208 | listTree.appendChild(node);
209 | },
210 | detachChild: function _detachChild (node) {
211 | let listTree = this.querySelector('.list-tree');
212 | if (!listTree) {
213 | return;
214 | }
215 | listTree.removeChild(node);
216 | },
217 | sorting: function _sorting () {
218 | const model = map.get(this);
219 |
220 | if (!model) { return; }
221 |
222 | return model.name;
223 | }
224 | };
225 |
226 | const createView = function _createView (model) {
227 | let options = {
228 | tagExtends: 'li',
229 | tagIs: 'project-viewer-group'
230 | };
231 | if (!model) {
232 | return;
233 | }
234 | return domBuilder.createView(options, viewMethods, model);
235 | };
236 |
237 | module.exports = {
238 | createView: createView
239 | };
240 |
--------------------------------------------------------------------------------
/src/json/devicons.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.8.0",
3 | "list": [
4 | "devicons-git",
5 | "devicons-git_compare",
6 | "devicons-git_branch",
7 | "devicons-git_commit",
8 | "devicons-git_pull_request",
9 | "devicons-git_merge",
10 | "devicons-bitbucket",
11 | "devicons-github_alt",
12 | "devicons-github_badge",
13 | "devicons-github",
14 | "devicons-github_full",
15 | "devicons-java",
16 | "devicons-ruby",
17 | "devicons-scala",
18 | "devicons-python",
19 | "devicons-go",
20 | "devicons-ruby_on_rails",
21 | "devicons-django",
22 | "devicons-markdown",
23 | "devicons-php",
24 | "devicons-mysql",
25 | "devicons-streamline",
26 | "devicons-database",
27 | "devicons-laravel",
28 | "devicons-javascript",
29 | "devicons-angular",
30 | "devicons-backbone",
31 | "devicons-coffeescript",
32 | "devicons-jquery",
33 | "devicons-modernizr",
34 | "devicons-jquery_ui",
35 | "devicons-ember",
36 | "devicons-dojo",
37 | "devicons-nodejs",
38 | "devicons-nodejs_small",
39 | "devicons-javascript_shield",
40 | "devicons-bootstrap",
41 | "devicons-sass",
42 | "devicons-css3_full",
43 | "devicons-css3",
44 | "devicons-html5",
45 | "devicons-html5_multimedia",
46 | "devicons-html5_device_access",
47 | "devicons-html5_3d_effects",
48 | "devicons-html5_connectivity",
49 | "devicons-ghost_small",
50 | "devicons-ghost",
51 | "devicons-magento",
52 | "devicons-joomla",
53 | "devicons-jekyll_small",
54 | "devicons-drupal",
55 | "devicons-wordpress",
56 | "devicons-grunt",
57 | "devicons-bower",
58 | "devicons-npm",
59 | "devicons-yahoo_small",
60 | "devicons-yahoo",
61 | "devicons-bing_small",
62 | "devicons-windows",
63 | "devicons-linux",
64 | "devicons-ubuntu",
65 | "devicons-android",
66 | "devicons-apple",
67 | "devicons-appstore",
68 | "devicons-phonegap",
69 | "devicons-blackberry",
70 | "devicons-stackoverflow",
71 | "devicons-techcrunch",
72 | "devicons-codrops",
73 | "devicons-css_tricks",
74 | "devicons-smashing_magazine",
75 | "devicons-netmagazine",
76 | "devicons-codepen",
77 | "devicons-cssdeck",
78 | "devicons-hackernews",
79 | "devicons-dropbox",
80 | "devicons-google_drive",
81 | "devicons-visualstudio",
82 | "devicons-unity_small",
83 | "devicons-raspberry_pi",
84 | "devicons-chrome",
85 | "devicons-ie",
86 | "devicons-firefox",
87 | "devicons-opera",
88 | "devicons-safari",
89 | "devicons-swift",
90 | "devicons-symfony",
91 | "devicons-symfony_badge",
92 | "devicons-less",
93 | "devicons-stylus",
94 | "devicons-trello",
95 | "devicons-atlassian",
96 | "devicons-jira",
97 | "devicons-envato",
98 | "devicons-snap_svg",
99 | "devicons-raphael",
100 | "devicons-google_analytics",
101 | "devicons-compass",
102 | "devicons-onedrive",
103 | "devicons-gulp",
104 | "devicons-atom",
105 | "devicons-cisco",
106 | "devicons-nancy",
107 | "devicons-clojure",
108 | "devicons-clojure_alt",
109 | "devicons-perl",
110 | "devicons-celluloid",
111 | "devicons-w3c",
112 | "devicons-redis",
113 | "devicons-postgresql",
114 | "devicons-webplatform",
115 | "devicons-jenkins",
116 | "devicons-requirejs",
117 | "devicons-opensource",
118 | "devicons-typo3",
119 | "devicons-uikit",
120 | "devicons-doctrine",
121 | "devicons-groovy",
122 | "devicons-nginx",
123 | "devicons-haskell",
124 | "devicons-zend",
125 | "devicons-gnu",
126 | "devicons-yeoman",
127 | "devicons-heroku",
128 | "devicons-debian",
129 | "devicons-travis",
130 | "devicons-dotnet",
131 | "devicons-codeigniter",
132 | "devicons-javascript_badge",
133 | "devicons-yii",
134 | "devicons-msql_server",
135 | "devicons-composer",
136 | "devicons-krakenjs_badge",
137 | "devicons-krakenjs",
138 | "devicons-mozilla",
139 | "devicons-firebase",
140 | "devicons-sizzlejs",
141 | "devicons-creativecommons",
142 | "devicons-creativecommons_badge",
143 | "devicons-mitlicence",
144 | "devicons-senchatouch",
145 | "devicons-bugsense",
146 | "devicons-extjs",
147 | "devicons-mootools_badge",
148 | "devicons-mootools",
149 | "devicons-ruby_rough",
150 | "devicons-komodo",
151 | "devicons-coda",
152 | "devicons-bintray",
153 | "devicons-terminal",
154 | "devicons-code",
155 | "devicons-responsive",
156 | "devicons-dart",
157 | "devicons-aptana",
158 | "devicons-mailchimp",
159 | "devicons-netbeans",
160 | "devicons-dreamweaver",
161 | "devicons-brackets",
162 | "devicons-eclipse",
163 | "devicons-cloud9",
164 | "devicons-scrum",
165 | "devicons-prolog",
166 | "devicons-terminal_badge",
167 | "devicons-code_badge",
168 | "devicons-mongodb",
169 | "devicons-meteor",
170 | "devicons-meteorfull",
171 | "devicons-fsharp",
172 | "devicons-rust",
173 | "devicons-ionic",
174 | "devicons-sublime"
175 | ]
176 | }
177 |
--------------------------------------------------------------------------------
/src/json/octicons.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "4.4.0",
3 | "list": [
4 | "icon-alert",
5 | "icon-alignment-align",
6 | "icon-alignment-aligned-to",
7 | "icon-alignment-unalign",
8 | "icon-arrow-down",
9 | "icon-arrow-left",
10 | "icon-arrow-right",
11 | "icon-arrow-small-down",
12 | "icon-arrow-small-left",
13 | "icon-arrow-small-right",
14 | "icon-arrow-small-up",
15 | "icon-arrow-up",
16 | "icon-beaker",
17 | "icon-beer",
18 | "icon-bell",
19 | "icon-bold",
20 | "icon-book",
21 | "icon-bookmark",
22 | "icon-briefcase",
23 | "icon-broadcast",
24 | "icon-browser",
25 | "icon-bug",
26 | "icon-calendar",
27 | "icon-check",
28 | "icon-checklist",
29 | "icon-chevron-down",
30 | "icon-chevron-left",
31 | "icon-chevron-right",
32 | "icon-chevron-up",
33 | "icon-circle-slash",
34 | "icon-circuit-board",
35 | "icon-clippy",
36 | "icon-clock",
37 | "icon-cloud-download",
38 | "icon-cloud-upload",
39 | "icon-code",
40 | "icon-color-mode",
41 | "icon-comment-add",
42 | "icon-comment-discussion",
43 | "icon-comment",
44 | "icon-credit-card",
45 | "icon-dash",
46 | "icon-dashboard",
47 | "icon-database",
48 | "icon-desktop-download",
49 | "icon-device-camera-video",
50 | "icon-device-camera",
51 | "icon-device-desktop",
52 | "icon-device-mobile",
53 | "icon-diff-added",
54 | "icon-diff-ignored",
55 | "icon-diff-modified",
56 | "icon-diff-removed",
57 | "icon-diff-renamed",
58 | "icon-diff",
59 | "icon-ellipses",
60 | "icon-ellipsis",
61 | "icon-eye-unwatch",
62 | "icon-eye-watch",
63 | "icon-eye",
64 | "icon-file-add",
65 | "icon-file-binary",
66 | "icon-file-code",
67 | "icon-file-directory-create",
68 | "icon-file-directory",
69 | "icon-file-media",
70 | "icon-file-pdf",
71 | "icon-file-submodule",
72 | "icon-file-symlink-directory",
73 | "icon-file-symlink-file",
74 | "icon-file-text",
75 | "icon-file-zip",
76 | "icon-file",
77 | "icon-flame",
78 | "icon-fold",
79 | "icon-gear",
80 | "icon-gift",
81 | "icon-gist-fork",
82 | "icon-gist-new",
83 | "icon-gist-private",
84 | "icon-gist-secret",
85 | "icon-gist",
86 | "icon-git-branch-create",
87 | "icon-git-branch-delete",
88 | "icon-git-branch",
89 | "icon-git-commit",
90 | "icon-git-compare",
91 | "icon-git-fork-private",
92 | "icon-git-merge",
93 | "icon-git-pull-request-abandoned",
94 | "icon-git-pull-request",
95 | "icon-globe",
96 | "icon-grabber",
97 | "icon-graph",
98 | "icon-heart",
99 | "icon-history",
100 | "icon-home",
101 | "icon-horizontal-rule",
102 | "icon-hourglass",
103 | "icon-hubot",
104 | "icon-inbox",
105 | "icon-info",
106 | "icon-issue-closed",
107 | "icon-issue-opened",
108 | "icon-issue-reopened",
109 | "icon-italic",
110 | "icon-jersey",
111 | "icon-jump-down",
112 | "icon-jump-left",
113 | "icon-jump-right",
114 | "icon-jump-up",
115 | "icon-key",
116 | "icon-keyboard",
117 | "icon-law",
118 | "icon-light-bulb",
119 | "icon-link-external",
120 | "icon-link",
121 | "icon-list-ordered",
122 | "icon-list-unordered",
123 | "icon-location",
124 | "icon-lock",
125 | "icon-log-in",
126 | "icon-log-out",
127 | "icon-logo-gist",
128 | "icon-mail-read",
129 | "icon-mail-reply",
130 | "icon-mail",
131 | "icon-mark-github",
132 | "icon-markdown",
133 | "icon-megaphone",
134 | "icon-mention",
135 | "icon-microscope",
136 | "icon-milestone",
137 | "icon-mirror-private",
138 | "icon-mirror-public",
139 | "icon-mirror",
140 | "icon-mortar-board",
141 | "icon-move-down",
142 | "icon-move-left",
143 | "icon-move-right",
144 | "icon-move-up",
145 | "icon-mute",
146 | "icon-no-newline",
147 | "icon-octoface",
148 | "icon-organization",
149 | "icon-package",
150 | "icon-paintcan",
151 | "icon-pencil",
152 | "icon-person-add",
153 | "icon-person-follow",
154 | "icon-person",
155 | "icon-pin",
156 | "icon-playback-fast-forward",
157 | "icon-playback-pause",
158 | "icon-playback-play",
159 | "icon-playback-rewind",
160 | "icon-plug",
161 | "icon-plus-small",
162 | "icon-plus",
163 | "icon-podium",
164 | "icon-primitive-dot",
165 | "icon-primitive-square",
166 | "icon-pulse",
167 | "icon-puzzle",
168 | "icon-question",
169 | "icon-quote",
170 | "icon-radio-tower",
171 | "icon-remove-close",
172 | "icon-reply",
173 | "icon-repo-clone",
174 | "icon-repo-create",
175 | "icon-repo-delete",
176 | "icon-repo-force-push",
177 | "icon-repo-forked",
178 | "icon-repo-pull",
179 | "icon-repo-push",
180 | "icon-repo-sync",
181 | "icon-repo",
182 | "icon-rocket",
183 | "icon-rss",
184 | "icon-ruby",
185 | "icon-screen-full",
186 | "icon-screen-normal",
187 | "icon-search-save",
188 | "icon-search",
189 | "icon-server",
190 | "icon-settings",
191 | "icon-shield",
192 | "icon-sign-in",
193 | "icon-sign-out",
194 | "icon-smiley",
195 | "icon-split",
196 | "icon-squirrel",
197 | "icon-star-add",
198 | "icon-star-delete",
199 | "icon-star",
200 | "icon-steps",
201 | "icon-stop",
202 | "icon-sync",
203 | "icon-tag-add",
204 | "icon-tag-remove",
205 | "icon-tag",
206 | "icon-tasklist",
207 | "icon-telescope",
208 | "icon-terminal",
209 | "icon-text-size",
210 | "icon-three-bars",
211 | "icon-thumbsdown",
212 | "icon-thumbsup",
213 | "icon-tools",
214 | "icon-trashcan",
215 | "icon-triangle-down",
216 | "icon-triangle-left",
217 | "icon-triangle-right",
218 | "icon-triangle-up",
219 | "icon-unfold",
220 | "icon-unmute",
221 | "icon-unverified",
222 | "icon-verified",
223 | "icon-versions",
224 | "icon-watch",
225 | "icon-x",
226 | "icon-zap"
227 | ]
228 | }
229 |
--------------------------------------------------------------------------------
/src/json/release-notes.json:
--------------------------------------------------------------------------------
1 | {
2 | "v140": "## [1.4.0] - 2019-07-07\n\n### Fixed\n\n- Issue ([#213](https://github.com/jccguimaraes/atom-project-viewer/issues/213))."
3 | }
4 |
--------------------------------------------------------------------------------
/src/main-view.js:
--------------------------------------------------------------------------------
1 | const map = require('./map');
2 | const domBuilder = require('./dom-builder');
3 | const api = require('./api');
4 | const colours = require('./colours');
5 | const database = require('./database');
6 | const {getModel, sortList} = require('./common');
7 |
8 | const viewsRef = {};
9 | let startX;
10 | let startWidth;
11 | let dragListener;
12 | let stopListener;
13 |
14 | const invertResizer = function _invertResizer (inverted) {
15 | if (inverted) {
16 | viewsRef['resizer'].classList.add('invert');
17 | }
18 | else {
19 | viewsRef['resizer'].classList.remove('invert');
20 | }
21 | };
22 |
23 | const resizerResetDrag = function _resizerResetDrag () {
24 | this.removeAttribute('style');
25 | atom.config.set('project-viewer.customWidth', undefined);
26 | };
27 |
28 | const resizerInitializeDrag = function _resizerInitializeDrag (event) {
29 | this.classList.add('resizing');
30 | startX = event.clientX;
31 | startWidth = parseInt(window.getComputedStyle(this).width, 10);
32 | document.addEventListener('mousemove', dragListener, false);
33 | document.addEventListener('mouseup', stopListener, false);
34 | };
35 |
36 | const resizerDoDrag = function _resizerDoDrag (event) {
37 | let variation;
38 | if (atom.config.get('project-viewer.panelPosition').includes('Left')) {
39 | variation = event.clientX - startX;
40 | }
41 | else {
42 | variation = startX - event.clientX;
43 | }
44 | this.setAttribute('style', `width:${startWidth + variation}px;`);
45 | };
46 |
47 | const resizerStopDrag = function _resizerStopDrag () {
48 | this.classList.remove('resizing');
49 | document.removeEventListener('mousemove', dragListener, false);
50 | document.removeEventListener('mouseup', stopListener, false);
51 | let value = parseInt(window.getComputedStyle(this).width, 10);
52 | if (value === 200) { value = undefined; }
53 | this.removeAttribute('style');
54 | atom.config.set('project-viewer.customWidth', value);
55 | };
56 |
57 | const buildViews = function _buildViews (model) {
58 | let view;
59 | if (model.type === 'group') {
60 | view = api.group.createView(model);
61 | }
62 | else if (model.type === 'project') {
63 | view = api.project.createView(model);
64 | }
65 | view.initialize();
66 | view.render();
67 | viewsRef[model.uuid] = view;
68 |
69 | const parentModel = Object.getPrototypeOf(model);
70 | if (parentModel === Object.prototype) {
71 | this.attachChild(view);
72 | }
73 | else if (viewsRef.hasOwnProperty(parentModel.uuid)) {
74 | viewsRef[parentModel.uuid].attachChild(view);
75 | }
76 | };
77 |
78 | const toggleTitle = function _toggleTitle (visibility) {
79 | const title = this.querySelector('.heading');
80 | if (!title) { return; }
81 | title.classList.toggle('hidden', visibility);
82 | };
83 |
84 | const validateActivePaneItem = function _validateActivePaneItem (item) {
85 | return item.nodeName === 'PROJECT-VIEWER-EDITOR' &&
86 | item.getAttribute('data-pv-uuid') === this.uuid;
87 | };
88 |
89 | const isAlreadyEditing = function _isAlreadyEditing (model) {
90 | return model && atom.workspace.getActivePane().items.some(
91 | validateActivePaneItem, model
92 | );
93 | };
94 |
95 | const openEditor = function _openEditor (model, prefill) {
96 | if (isAlreadyEditing(model)) { return; }
97 | const activePane = atom.workspace.getCenter().getActivePane();
98 | const editorItem = api.editor.createView();
99 | editorItem.tabIndex = 0;
100 | editorItem.initialize(model, prefill);
101 | activePane.addItem(editorItem);
102 | activePane.activateItem(editorItem);
103 | };
104 |
105 | const reset = function _reset () {
106 | const listTree = this.querySelector('ul.list-tree');
107 | if (!listTree) {
108 | return;
109 | }
110 | while (listTree.firstChild) {
111 | listTree.removeChild(listTree.firstChild);
112 | }
113 | };
114 |
115 | const populate = function _populate (list) {
116 | if (!list || !Array.isArray(list)) {
117 | return;
118 | }
119 | this.reset();
120 |
121 | if (list.length === 0) {
122 | const emptyMessage = this.querySelector('.background-message');
123 | emptyMessage.classList.remove('hidden');
124 | return;
125 | }
126 |
127 | list.forEach(buildViews.bind(this));
128 | this.sortChildren();
129 | for (let ref in viewsRef) {
130 | const model = getModel(viewsRef[ref]);
131 | if (model && model.type === 'group') {
132 | viewsRef[ref].sortChildren();
133 | }
134 | }
135 | };
136 |
137 | const traverse = function _traverse (direction) {
138 | const selectionsUnfiltered = this.querySelectorAll(
139 | `li[is="project-viewer-group"],
140 | li[is="project-viewer-project"]`
141 | );
142 |
143 | let selectionsFiltered = Array.from(selectionsUnfiltered).filter(
144 | selection => {
145 | let isVisible = true;
146 | let parent = selection.parentNode;
147 | if (!parent) { return; }
148 | while(!parent.classList.contains('body-content')) {
149 | parent = parent.parentNode;
150 | if (!parent || parent.classList.contains('collapsed')) {
151 | selection.classList.remove('active');
152 | isVisible = false;
153 | break;
154 | }
155 | }
156 | return isVisible;
157 | }
158 | );
159 |
160 | let nextIdx = 0;
161 |
162 | selectionsFiltered.some(
163 | (selection, idx) => {
164 | if (selection.classList.contains('active')) {
165 | selection.classList.remove('active');
166 | nextIdx = direction === '☝️' ? idx - 1 : idx + 1;
167 | return true;
168 | }
169 | }
170 | );
171 |
172 | if (direction === '☝️' && nextIdx === -1) {
173 | nextIdx = selectionsFiltered.length - 1;
174 | }
175 | else if (
176 | (direction === '👇' && nextIdx === selectionsFiltered.length) ||
177 | direction === undefined
178 | ) {
179 | nextIdx = 0;
180 | }
181 | if (!selectionsFiltered[nextIdx]) { return; }
182 | selectionsFiltered[nextIdx].classList.add('active');
183 | };
184 |
185 | const setAction = function _setAction (action) {
186 | const selectedView = this.querySelector(
187 | `li[is="project-viewer-group"].active,
188 | li[is="project-viewer-project"].active`
189 | );
190 |
191 | if (!selectedView) { return false; }
192 |
193 | const model = map.get(selectedView);
194 |
195 | if (!model) { return false; }
196 |
197 | if (model.type === 'group' && action === '📪') {
198 | selectedView.classList.add('collapsed');
199 | }
200 | else if (model.type === 'group' && action === '📭') {
201 | selectedView.classList.remove('collapsed');
202 | }
203 | else if (model.type === 'project' && action === '✅') {
204 | if (atom.config.get('project-viewer.autoHide')) {
205 | this.toggleFocus();
206 | }
207 | selectedView.openOnWorkspace();
208 | }
209 | else if (model.type === 'group' && action === '✅') {
210 | selectedView.classList.toggle('collapsed');
211 | }
212 | };
213 |
214 | const autohide = function _autohide (option) {
215 | if (option) {
216 | this.classList.add('autohide');
217 | }
218 | else if (option === false) {
219 | this.classList.remove('autohide');
220 | }
221 | else {
222 | this.classList.toggle('autohide');
223 | }
224 | };
225 |
226 | const autoHideAbsolute = function _autoHideAbsolute (option) {
227 | if (option) {
228 | this.classList.add('position-absolute');
229 | }
230 | else {
231 | this.classList.remove('position-absolute');
232 | }
233 |
234 | if (option && atom.config.get('project-viewer.panelPosition').startsWith('Left')) {
235 | this.classList.remove('position-right');
236 | this.classList.add('position-left');
237 | }
238 | else if (option && atom.config.get('project-viewer.panelPosition').startsWith('Right')) {
239 | this.classList.add('position-right');
240 | this.classList.remove('position-left');
241 | }
242 | else {
243 | this.classList.remove('position-left', 'position-right');
244 | }
245 | };
246 |
247 | const toggleFocus = function _toggleFocus () {
248 | const panel = atom.workspace.panelForItem(this);
249 | if (!panel) { return false; }
250 | const item = panel.getItem();
251 | if (!item) { return false; }
252 |
253 | if (document.activeElement === item) {
254 | if (atom.config.get('project-viewer.autoHide')) {
255 | item.classList.add('autohide');
256 | }
257 | atom.workspace.getActivePane().activate();
258 | const selectedView = this.querySelector(
259 | `li[is="project-viewer-project"].active,
260 | li[is="project-viewer-project"].active`
261 | );
262 | if (selectedView) {
263 | selectedView.classList.remove('active');
264 | }
265 | } else {
266 | if (atom.config.get('project-viewer.autoHide')) {
267 | item.classList.remove('autohide');
268 | }
269 | const activeView = this.querySelector(
270 | 'li[is="project-viewer-project"].selected'
271 | );
272 | if (activeView) {
273 | activeView.classList.add('active');
274 | }
275 | item.focus();
276 | }
277 | };
278 |
279 | const viewUnfocus = function _viewUnfocus () {
280 | if (atom.config.get('project-viewer.autoHide')) {
281 | this.classList.add('autohide');
282 | }
283 | const selectedView = this.querySelector(
284 | `li[is="project-viewer-group"].active,
285 | li[is="project-viewer-project"].active`
286 | );
287 | if (!selectedView) { return; }
288 |
289 | selectedView.classList.remove('active');
290 | }
291 |
292 | const initialize = function _initialize () {
293 |
294 | this.addEventListener('blur', viewUnfocus.bind(this));
295 |
296 | this.setAttribute('tabindex', -1);
297 | this.classList.add('pv-has-icons');
298 |
299 | let hiddenBlock = document.createElement('div');
300 | hiddenBlock.classList.add('hidden-block');
301 |
302 | let pvResizer = document.createElement('div');
303 | pvResizer.classList.add('pv-resizer');
304 | viewsRef['resizer'] = pvResizer;
305 |
306 | let customWidth = atom.config.get('project-viewer.customWidth');
307 |
308 | if (customWidth !== 200) {
309 | // this.setAttribute('style', `width:${atom.config.get('project-viewer.customWidth')}px;`);
310 | colours.removeRule('app');
311 | colours.addRule('app', 'app', customWidth);
312 | }
313 |
314 | let customHotZone = Math.min(atom.config.get('project-viewer.customHotZone'), customWidth);
315 |
316 | if (customHotZone !== 20) {
317 | colours.removeRule('hotzone');
318 | colours.addRule('hotzone', 'hotzone', customHotZone);
319 | }
320 |
321 | let panelHeading = document.createElement('h2');
322 | panelHeading.classList.add('heading');
323 | panelHeading.textContent = 'Project-Viewer';
324 |
325 | let panelBody = document.createElement('div');
326 | panelBody.classList.add('body-content');
327 |
328 | let emptyMessage = document.createElement('ul');
329 | emptyMessage.classList.add('background-message', 'centered');
330 | let emptyMessageText = document.createElement('li');
331 | emptyMessageText.textContent = 'No groups or projects';
332 |
333 | let listTree = document.createElement('ul');
334 | listTree.classList.add(
335 | 'list-tree',
336 | 'has-collapsable-children',
337 | 'pv-has-custom-icons'
338 | );
339 |
340 | this.addEventListener('dragstart', (evt) => {
341 | evt.stopPropagation();
342 | }, false);
343 |
344 | this.addEventListener('dragover', (evt) => {
345 | evt.preventDefault();
346 | }, false);
347 |
348 | this.addEventListener('dragleave', (evt) => {
349 | evt.stopPropagation();
350 | }, false);
351 |
352 | this.addEventListener('dragenter', (evt) => {
353 | evt.stopPropagation();
354 | }, false);
355 |
356 | this.addEventListener('dragend', (evt) => {
357 | evt.target.classList.remove('dragged');
358 | evt.stopPropagation();
359 | }, false);
360 |
361 | this.addEventListener('drop', (evt) => {
362 | const uuid = evt.dataTransfer.getData("text/plain");
363 | const view = viewsRef[uuid];
364 | if (!view) { return; }
365 | const draggedModel = getModel(view);
366 | database.moveTo(draggedModel);
367 | database.save();
368 | }, false);
369 |
370 | emptyMessage.appendChild(emptyMessageText);
371 | panelBody.appendChild(listTree);
372 | panelBody.appendChild(emptyMessage);
373 | hiddenBlock.appendChild(panelHeading);
374 | hiddenBlock.appendChild(panelBody);
375 | hiddenBlock.appendChild(pvResizer);
376 | this.appendChild(hiddenBlock);
377 |
378 | dragListener = resizerDoDrag.bind(this);
379 | stopListener = resizerStopDrag.bind(this);
380 | pvResizer.addEventListener('mousedown', resizerInitializeDrag.bind(this), false);
381 | pvResizer.addEventListener("dblclick", resizerResetDrag.bind(this), false);
382 | };
383 |
384 | const sortChildren = function _sortChildren () {
385 | const listTree = this.querySelector('.list-tree');
386 | if (!listTree) { return; }
387 | const children = Array.from(listTree.children);
388 | if (!children || children.length === 0) { return; }
389 |
390 | sortList(children, atom.config.get('project-viewer.rootSortBy'));
391 | children.forEach(view => listTree.appendChild(view));
392 | };
393 |
394 | const attachChild = function _attachChild (node) {
395 | const listTree = this.querySelector('.list-tree');
396 |
397 | if (!listTree) { return; }
398 |
399 | if (listTree.children.length === 0) {
400 | const emptyMessage = this.querySelector('.background-message');
401 | emptyMessage.classList.add('hidden');
402 | }
403 |
404 | listTree.appendChild(node);
405 | };
406 |
407 | const detachChild = function _detachChild (node) {
408 | let listTree = this.querySelector('.list-tree');
409 | if (!listTree) {
410 | return;
411 | }
412 | listTree.removeChild(node);
413 | };
414 |
415 | const getTitle = function _getTitle () {
416 | return database.PACKAGE_NAME;
417 | };
418 |
419 | const getURI = function _getURI () {
420 | return database.WORKSPACE_URI;
421 | };
422 |
423 | const getDefaultLocation = function _getDefaultLocation () {
424 | return 'right';
425 | };
426 |
427 | const getAllowedLocations = function _getAllowedLocations () {
428 | return ['left', 'right'];
429 | };
430 |
431 | const isPermanentDockItem = function _isPermanentDockItem () {
432 | return true;
433 | };
434 |
435 | const viewMethods = {
436 | attachChild,
437 | sortChildren,
438 | detachChild,
439 | autohide,
440 | autoHideAbsolute,
441 | initialize,
442 | openEditor,
443 | populate,
444 | reset,
445 | setAction,
446 | toggleFocus,
447 | toggleTitle,
448 | traverse,
449 | invertResizer,
450 | getTitle,
451 | getURI,
452 | getDefaultLocation,
453 | getAllowedLocations,
454 | isPermanentDockItem,
455 | getPreferredWidth: () => {
456 | if (this.list && this.list.style) {
457 | this.list.style.width = '200px';
458 | }
459 | }
460 | };
461 |
462 | const createView = function _createView (model) {
463 | let options = {
464 | tagIs: 'project-viewer'
465 | };
466 | return domBuilder.createView(options, viewMethods, model);
467 | };
468 |
469 | module.exports = {
470 | createView: createView
471 | };
472 |
--------------------------------------------------------------------------------
/src/map.js:
--------------------------------------------------------------------------------
1 | const map = new WeakMap();
2 |
3 | module.exports = map;
4 |
--------------------------------------------------------------------------------
/src/model.js:
--------------------------------------------------------------------------------
1 | const Path = require('path');
2 |
3 | const defaults = {
4 | name: 'unnamed',
5 | sortBy: 'position',
6 | icon: '',
7 | color: '',
8 | expanded: false,
9 | devMode: false,
10 | config: {}
11 | };
12 |
13 | const groupModel = {
14 | type: 'group',
15 | name: defaults.name,
16 | sortBy: defaults.sortBy,
17 | icon: defaults.icon,
18 | color: defaults.color,
19 | expanded: defaults.expanded
20 | };
21 |
22 | const projectModel = {
23 | type: 'project',
24 | name: defaults.name,
25 | icon: defaults.icon,
26 | color: defaults.color,
27 | devMode: defaults.devMode,
28 | config: defaults.config
29 | };
30 |
31 | const methods = {
32 | breadcrumb: function _breadcrumb () {
33 | let proto = Object.getPrototypeOf(this);
34 | let protoName = '';
35 | if (proto !== Object.prototype) {
36 | protoName = proto.breadcrumb();
37 | }
38 | return protoName.length === 0 ? this.name : `${protoName} / ${this.name}`;
39 | }
40 | };
41 |
42 | const groupMethods = {};
43 |
44 | const projectMethods = {
45 | clearPaths: function _clearPaths () {
46 | const removedPaths = this.paths.filter(
47 | () => true
48 | );
49 | this.paths.length = 0;
50 | return removedPaths;
51 | },
52 | addPaths: function _addPaths (paths) {
53 | if (!paths) {
54 | return;
55 | }
56 | if (Array.isArray(paths)) {
57 | paths.forEach(
58 | (path) => this.addPaths(path)
59 | );
60 | return;
61 | }
62 | if (typeof paths !== 'string') {
63 | return;
64 | }
65 | const normalizedPath = Path.normalize(paths);
66 | if (this.paths.indexOf(normalizedPath) === -1) {
67 | this.paths.push(normalizedPath);
68 | }
69 | },
70 | removePath: function _removePath (path) {
71 | const idx = this.paths.indexOf(path);
72 | if (idx === -1) {
73 | return;
74 | }
75 | this.paths.splice(idx, 1);
76 | },
77 | removePaths: function _removePaths (paths) {
78 | let idxsToRemove = [];
79 | this.paths.forEach(
80 | (path, idx) => {
81 | if (paths.indexOf(path) !== -1) {
82 | idxsToRemove.push(idx);
83 | }
84 | }
85 | );
86 | idxsToRemove.sort().reverse().forEach(
87 | (idxToRemove) => this.paths.splice(idxToRemove, 1)
88 | );
89 | }
90 | };
91 |
92 | Object.assign(groupMethods, methods);
93 | Object.assign(projectMethods, methods);
94 |
95 | const setPrototypeOf = function _setPrototypeOf (target, prototype) {
96 | if (
97 | prototype === Object.prototype ||
98 | (target.type === 'group' && target.type === prototype.type) ||
99 | (prototype.type === 'group' && target.type === 'project')
100 | ) {
101 | Object.setPrototypeOf(target, prototype);
102 | return true;
103 | }
104 | return false;
105 | };
106 |
107 | const handler = {
108 | setPrototypeOf: setPrototypeOf,
109 | get: function _get (target, property) {
110 | if (target.hasOwnProperty(property)) {
111 | return target[property];
112 | }
113 | if (typeof target[property] === 'function') {
114 | return target[property];
115 | }
116 | return null;
117 | },
118 | set: function _set (target, property, value) {
119 | const allowedProps = [
120 | 'name',
121 | 'sortBy',
122 | 'expanded',
123 | 'icon',
124 | 'color',
125 | 'devMode',
126 | 'config'
127 | ];
128 | if (allowedProps.indexOf(property) === -1) {
129 | return true;
130 | }
131 | let cleanValue;
132 |
133 | if (value === undefined) {
134 | target[property] = defaults[property];
135 | return true;
136 | }
137 | if (property === 'name') {
138 | // TODO: make this in a helper function?
139 | const UNSAFE_CHARS_PATTERN = /[<>\/\u2028\u2029]/g;
140 | const UNICODE_CHARS = {
141 | '<': '\\u003C',
142 | '>': '\\u003E',
143 | '/': '\\u002F',
144 | '\u2028': '\\u2028',
145 | '\u2029': '\\u2029'
146 | };
147 | cleanValue = value.length > 0 && value.replace && value.replace(
148 | UNSAFE_CHARS_PATTERN,
149 | function (unsafeChar) { return UNICODE_CHARS[unsafeChar]; }
150 | ) === value ? value : target[property];
151 | }
152 | else if (target.type === 'group' && property === 'sortBy') {
153 | const allowed = [
154 | 'position',
155 | 'reverse-position',
156 | 'alphabetically',
157 | 'reverse-alphabetically'
158 | ];
159 | cleanValue = allowed.indexOf(value) !== -1 ? value : target[property];
160 | }
161 | else if (target.type === 'group' && property === 'expanded') {
162 | cleanValue = Boolean(value) === value ? value : target[property];
163 | }
164 | else if (property === 'icon') {
165 | const allowed = [
166 | 'icon-',
167 | 'devicons-'
168 | ];
169 | cleanValue = allowed.map(
170 | (val) => value && value.startsWith(val) ? value : undefined
171 | ).filter(
172 | (val) => val !== undefined
173 | );
174 |
175 | cleanValue = cleanValue.length === 1 ? cleanValue[0] : target[property];
176 | }
177 | else if (property === 'color') {
178 | const regEx = new RegExp('^#(?:[0-9a-f]{3}){1,2}$', 'i');
179 | cleanValue = regEx.exec(value) !== null ? value : target[property];
180 | }
181 | else if (target.type === 'project' && property === 'devMode') {
182 | cleanValue = Boolean(value) === value ? value : target[property];
183 | }
184 | else if (target.type === 'project' && property === 'config') {
185 | cleanValue = target[property];
186 | }
187 | target[property] = cleanValue;
188 | return true;
189 | }
190 | };
191 |
192 | module.exports = {
193 | createGroup: function _createGroup (candidate) {
194 | const group = Object.assign(groupModel);
195 | const model = Object.assign({}, group, groupMethods);
196 | model.uuid = 'pv_' + Math.ceil(Date.now() * Math.random());
197 | const proxy = new Proxy(model, handler);
198 | if (candidate) {
199 | Object.assign(proxy, candidate);
200 | }
201 | return proxy;
202 | },
203 | createProject: function _createproject (candidate) {
204 | const project = Object.assign(projectModel);
205 | project.paths = []
206 | const model = Object.assign({}, project, projectMethods);
207 | model.uuid = 'pv_' + Math.ceil(Date.now() * Math.random());
208 | const proxy = new Proxy(model, handler);
209 | if (candidate) {
210 | Object.assign(proxy, candidate);
211 | proxy.addPaths(candidate.paths);
212 | }
213 | return proxy;
214 | },
215 | createGroupSchema: function _createGroupSchema (
216 | {
217 | type = 'group',
218 | name = groupModel.name === defaults.name ? '' : groupModel.name,
219 | sortBy = groupModel.sortBy,
220 | icon = groupModel.icon,
221 | color = groupModel.color,
222 | expanded = groupModel.expanded
223 | } = {}
224 | ) {
225 | return { type, name, sortBy, icon, color, expanded };
226 | },
227 | createProjectSchema: function _createProjectSchema (
228 | {
229 | type = 'project',
230 | name = projectModel.name === defaults.name ? '' : projectModel.name,
231 | icon = projectModel.icon,
232 | color = projectModel.color,
233 | devMode = projectModel.devMode,
234 | config = projectModel.config,
235 | paths = []
236 | } = {}
237 | ) {
238 | return { type, name, icon, color, devMode, config, paths };
239 | }
240 | };
241 |
--------------------------------------------------------------------------------
/src/packages.js:
--------------------------------------------------------------------------------
1 | const showPackage = function _showPackage (name, pkg) {
2 | if (name === 'tree-view') {
3 | pkg.mainModule.treeView.show();
4 | }
5 | };
6 |
7 | /**
8 | * Enables / activates a list of packages
9 | *
10 | * @param {Array} list an array of strings with all the packages name
11 | * @returns {void}
12 | */
13 | const enablePackages = function _enablePackages (list) {
14 | list.forEach(pkg => {
15 | if (!pkg || pkg.trim().length === 0) { return; }
16 | const enabledPackage = atom.packages.enablePackage(pkg);
17 | showPackage(pkg, enabledPackage);
18 | });
19 | };
20 |
21 | const disablePackages = function _disablePackages (list) {
22 | list.forEach(pkg => {
23 | if (!pkg || pkg.trim().length === 0) { return; }
24 | atom.packages.disablePackage(pkg);
25 | });
26 | };
27 |
28 | const getTreeViewState = function _getTreeViewState () {
29 | const treeView = atom.packages.getActivePackage('tree-view');
30 | if (!treeView || !treeView.mainModule || !treeView.mainModule.treeView) {
31 | return {};
32 | }
33 | return treeView.mainModule.treeView.serialize();
34 | };
35 |
36 | const setTreeViewState = function _setTreeViewState (state) {
37 | if (!state) { return; }
38 | const pkg = atom.packages.getActivePackage('tree-view');
39 | if (!pkg || !pkg.mainModule || !pkg.mainModule.treeView) {
40 | return;
41 | }
42 |
43 | if (!pkg.mainModule.treeView) {
44 | pkg.mainModule.createView(state.directoryExpansionStates);
45 | } else {
46 | pkg.mainModule.treeView.updateRoots(state.directoryExpansionStates);
47 | }
48 |
49 | const element = pkg.mainModule.treeView.element;
50 |
51 | if (state.width > 0) {
52 | element.style.width = `${state.width}px`;
53 | }
54 |
55 | element.scrollTop = state.scrollTop;
56 | element.scrollLeft = state.scrollLeft;
57 | }
58 |
59 | module.exports = {
60 | treeView: {
61 | getState: getTreeViewState,
62 | setState: setTreeViewState
63 | },
64 | state: {
65 | enable: enablePackages,
66 | disable: disablePackages
67 | }
68 | };
69 |
--------------------------------------------------------------------------------
/src/project-view.js:
--------------------------------------------------------------------------------
1 | const {
2 | getCurrentOpenedProject, getModel, getViewFromModel,
3 | getSelectedProject, getView
4 | } = require('./common');
5 | const colours = require('./colours');
6 | const database = require('./database');
7 | const domBuilder = require('./dom-builder');
8 | const map = require('./map');
9 | const remote = require('remote');
10 | const statusBar = require('./status-bar');
11 | const packages = require('./packages');
12 |
13 | let checkedAll = [];
14 |
15 | const onClickEvent = function _onClickEvent (model) {
16 | if (!model) { return null; }
17 | this.openOnWorkspace();
18 | };
19 |
20 | const dragstart = function _dragstart (evt) {
21 | const view = getView(evt.target);
22 | const model = getModel(evt.target);
23 | view.classList.add('dragging');
24 | evt.dataTransfer.setData('text/plain', model.uuid);
25 | evt.stopPropagation();
26 | };
27 |
28 | const dragover = function _dragover (evt) {
29 | const view = getView(evt.target);
30 | const middle = view.clientHeight / 2;
31 |
32 | if (evt.target.offsetTop + middle > evt.layerY) {
33 | evt.target.classList.add('above');
34 | evt.target.classList.remove('below');
35 | }
36 | else {
37 | evt.target.classList.remove('above');
38 | evt.target.classList.add('below');
39 | }
40 |
41 | evt.preventDefault();
42 | evt.stopPropagation();
43 | };
44 |
45 | const dragleave = function _dragleave (evt) {
46 | evt.preventDefault();
47 | this.classList.remove('dropping', 'below', 'above');
48 | };
49 |
50 | const dragenter = function _dragenter (evt) {
51 | evt.preventDefault();
52 | const uuid = evt.dataTransfer.getData('text/plain');
53 | const draggedView = document.querySelector(
54 | `project-viewer li[data-project-viewer-uuid="${uuid}"]`
55 | );
56 | const view = getView(evt.target);
57 | if (view === draggedView) { return; }
58 |
59 | this.classList.add('dropping');
60 | };
61 |
62 | const dragend = function _dragend (evt) {
63 | const view = getView(evt.target);
64 | view.classList.remove('dragging');
65 | evt.stopPropagation();
66 | };
67 |
68 | const drop = function _drop (evt) {
69 | evt.stopPropagation();
70 |
71 | const uuid = evt.dataTransfer.getData('text/plain');
72 |
73 | const draggedView = document.querySelector(
74 | `project-viewer li[data-project-viewer-uuid="${uuid}"]`
75 | );
76 | const droppedView = getView(evt.target);
77 |
78 | if (droppedView === draggedView) { return; }
79 |
80 | const droppedModel = getModel(evt.target);
81 | const draggedModel = getModel(draggedView);
82 |
83 | // a bit hacky
84 | const insertBefore = droppedView.classList.contains('above');
85 | this.classList.remove('dropping', 'below', 'above');
86 |
87 | database.moveTo(draggedModel, droppedModel, insertBefore);
88 | database.save();
89 | };
90 |
91 | const attachedCallback = function _attachedCallback () {
92 | this.addEventListener('dragstart', dragstart, false);
93 | this.addEventListener('dragover', dragover, false);
94 | this.addEventListener('dragleave', dragleave, false);
95 | this.addEventListener('dragenter', dragenter, false);
96 | this.addEventListener('dragend', dragend, false);
97 | this.addEventListener('drop', drop, false);
98 | };
99 |
100 | const detachedCallback = function _detachedCallback () {
101 | this.removeEventListener('dragstart', dragstart, false);
102 | this.removeEventListener('dragover', dragover, false);
103 | this.removeEventListener('dragleave', dragleave, false);
104 | this.removeEventListener('dragenter', dragenter, false);
105 | this.removeEventListener('dragend', dragend, false);
106 | this.removeEventListener('drop', drop, false);
107 | };
108 |
109 | const initialize = function _initialize () {
110 | const model = map.get(this);
111 |
112 | if (!model) { return; }
113 |
114 | this.classList.add('list-item');
115 |
116 | this.setAttribute('data-project-viewer-uuid', model.uuid);
117 | this.setAttribute('draggable', 'true');
118 |
119 | this.addEventListener(
120 | 'click',
121 | onClickEvent.bind(this, model)
122 | );
123 | };
124 |
125 | const render = function _render () {
126 | const model = map.get(this);
127 |
128 | if (!model) {
129 | return;
130 | }
131 |
132 | let spanNode = this.querySelector('span');
133 | let contentNode = this;
134 |
135 | if (!spanNode) {
136 | contentNode.textContent = '';
137 | spanNode = document.createElement('span');
138 | contentNode.appendChild(spanNode);
139 | }
140 |
141 | if (model.icon) {
142 | contentNode = spanNode;
143 | if (model.icon.startsWith('devicons-')) {
144 | contentNode.classList.add('devicons', model.icon);
145 | }
146 | else {
147 | contentNode.classList.add('icon', model.icon);
148 | }
149 | }
150 | else if (spanNode) {
151 | contentNode = spanNode;
152 | }
153 |
154 | if (model.name) {
155 | contentNode.textContent = model.name;
156 | }
157 |
158 | if (model.color) {
159 | colours.addRule(model.uuid, model.type, model.color);
160 | } else {
161 | colours.removeRule(model.uuid);
162 | }
163 |
164 | this.classList.toggle('no-paths', model.paths.length === 0);
165 |
166 | const currentOpenedProject = getCurrentOpenedProject(model);
167 |
168 | this.classList.toggle(
169 | 'selected',
170 | currentOpenedProject
171 | );
172 |
173 | if (currentOpenedProject) {
174 | statusBar.update(model.breadcrumb());
175 | }
176 | };
177 |
178 | const sorting = function _sorting () {
179 | const model = map.get(this);
180 |
181 | if (!model) {
182 | return;
183 | }
184 | return model.name;
185 | };
186 |
187 | const checkIfOpened = function _checkIfOpened (event, model, title, opened, action) {
188 | const wcs = remote.webContents;
189 |
190 | checkedAll.push({
191 | title,
192 | opened
193 | });
194 |
195 | if (wcs.length !== checkedAll.length) {
196 | if (action) {
197 | atom.open({
198 | pathsToOpen: model.paths,
199 | // newWindow: true,
200 | devMode: model.devMode,
201 | safeMode: false
202 | });
203 | }
204 | return;
205 | }
206 |
207 | remote.ipcMain.removeListener(`channel-${model.uuid}`, checkIfOpened);
208 |
209 | const openNew = checkedAll.find(checked => checked.opened);
210 | checkedAll = [];
211 |
212 | if (!openNew) {
213 | atom.open({
214 | pathsToOpen: model.paths,
215 | // newWindow: true,
216 | devMode: model.devMode,
217 | safeMode: false
218 | });
219 | return;
220 | }
221 |
222 | wcs.some(wc => {
223 | if (wc.getTitle() === openNew.title) {
224 | wc.focus();
225 | return true;
226 | }
227 | });
228 | };
229 |
230 | const openOnWorkspace = async function _openOnWorkspace (reverseOption) {
231 | const model = map.get(this);
232 |
233 | if (!model) { return false; }
234 |
235 | if (model.paths.length === 0) { return false; }
236 |
237 | const selectedProject = getSelectedProject();
238 |
239 | if (selectedProject === getViewFromModel(model)) { return; }
240 |
241 | const action = reverseOption ?
242 | !atom.config.get('project-viewer.openNewWindow') :
243 | atom.config.get('project-viewer.openNewWindow');
244 |
245 | if (action) {
246 | remote.ipcMain.on(`channel-${model.uuid}`, checkIfOpened);
247 | remote.webContents.getAllWebContents().forEach(wc => {
248 | if (!wc.browserWindowOptions) { return; }
249 | wc.webContents.send(
250 | 'pv-check-if-opened',
251 | model, wc.getTitle(),
252 | action
253 | );
254 | });
255 | return false;
256 | }
257 |
258 | const packagesList = atom.config.get('project-viewer.packagesReload');
259 |
260 | if (!atom.config.get('project-viewer.openNewWindow')) {
261 | packages.state.disable(packagesList);
262 | }
263 |
264 | if (selectedProject) {
265 | selectedProject.classList.remove('selected');
266 | }
267 |
268 | this.classList.add('selected');
269 |
270 | let projectSHA;
271 | let serialization;
272 |
273 | projectSHA = atom.getStateKey(atom.project.getPaths());
274 |
275 | // We need to ensure that the projectSHA is valid (i.e. the project contains at least one path)
276 | if (projectSHA !== null) {
277 | if (atom.config.get('project-viewer.keepContext') || database.KEEP_CONTEXT) {
278 | database.KEEP_CONTEXT = false;
279 | serialization = await atom.stateStore.load(projectSHA);
280 |
281 | // Ensure that the serialized file exists
282 | if (typeof serialization === 'undefined') {
283 | serialization = atom.serialize();
284 | }
285 | } else {
286 | serialization = atom.serialize();
287 | }
288 |
289 | serialization.treeView = packages.treeView.getState();
290 | await atom.stateStore.save(projectSHA, serialization);
291 | }
292 |
293 | statusBar.update(model.breadcrumb());
294 |
295 | projectSHA = atom.getStateKey(model.paths);
296 |
297 | const state = await atom.stateStore.load(projectSHA);
298 |
299 | database.pathsChangedBypass = true;
300 |
301 | if (!state || !state.workspace.paneContainers) {
302 | atom.project.setPaths(model.paths);
303 | atom.workspace.getCenter().paneContainer.activePane.destroy();
304 | }
305 | else {
306 | if (state.workspace.paneContainers && state.workspace.paneContainers.left) {
307 | state.workspace.paneContainers.left.paneContainer = {};
308 | }
309 | if (state.workspace.paneContainers && state.workspace.paneContainers.left) {
310 | state.workspace.paneContainers.right.paneContainer = {};
311 | }
312 | if (state.workspace.paneContainers && state.workspace.paneContainers.left) {
313 | state.workspace.paneContainers.bottom.paneContainer = {};
314 | }
315 |
316 | if (atom.config.get('project-viewer.keepContext')) {
317 | state.workspace.paneContainers.center = {};
318 | }
319 | if (atom.config.get('project-viewer.keepWindowSize')) {
320 | state.windowDimensions = atom.getWindowDimensions();
321 | state.fullScreen = atom.isFullScreen();
322 | }
323 |
324 | atom.deserialize(state);
325 | // atom.restoreStateIntoThisEnvironment(state);
326 |
327 | packages.treeView.setState(state.treeView);
328 | if (!atom.config.get('project-viewer.openNewWindow')) {
329 | packages.state.enable(packagesList);
330 | }
331 | }
332 |
333 |
334 |
335 | database.pathsChangedBypass = false;
336 |
337 | return true;
338 | };
339 |
340 | const viewMethods = {
341 | attachedCallback,
342 | detachedCallback,
343 | initialize,
344 | render,
345 | sorting,
346 | openOnWorkspace
347 | };
348 |
349 | const createView = function _createView (model) {
350 | let options = {
351 | tagExtends: 'li',
352 | tagIs: 'project-viewer-project'
353 | };
354 | if (!model) {
355 | return;
356 | }
357 | return domBuilder.createView(options, viewMethods, model);
358 | };
359 |
360 | module.exports = {
361 | createView: createView
362 | };
363 |
--------------------------------------------------------------------------------
/src/projects-list-view.js:
--------------------------------------------------------------------------------
1 | const {CompositeDisposable} = require('atom');
2 | const SelectList = require('atom-select-list');
3 |
4 | class SelectListView {
5 |
6 | constructor () {
7 | this.items = [];
8 | this.selectListView = new SelectList({
9 | items: this.items,
10 | emptyMessage: 'There are no projects...',
11 | didCancelSelection: () => this.cancel(),
12 | didConfirmEmptySelection: () => this.confirm(),
13 | didConfirmSelection: item => this.confirmSelection(item),
14 | filterKeyForItem: item => item.breadcrumb(),
15 | elementForItem: item => this.createItem(item)
16 | });
17 |
18 | this.subscriptions = new CompositeDisposable()
19 | }
20 |
21 | get element () {
22 | return this.selectListView.element
23 | }
24 |
25 | destroy () {
26 | if (this.panel) {
27 | this.panel.destroy()
28 | }
29 |
30 | if (this.subscriptions) {
31 | this.subscriptions.dispose()
32 | this.subscriptions = null
33 | }
34 |
35 | return this.selectListView.destroy()
36 | }
37 |
38 | cancel () {
39 | this.selectListView.reset()
40 | this.hide()
41 | }
42 |
43 | confirm (item) {
44 | this.cancel();
45 | }
46 |
47 | show () {
48 | this.previouslyFocusedElement = document.activeElement
49 | if (!this.panel) {
50 | this.panel = atom.workspace.addModalPanel({item: this})
51 | }
52 | this.panel.show()
53 | this.selectListView.focus()
54 | }
55 |
56 | hide () {
57 | if (this.panel) {
58 | this.panel.hide()
59 | }
60 |
61 | if (this.previouslyFocusedElement) {
62 | this.previouslyFocusedElement.focus()
63 | this.previouslyFocusedElement = null
64 | }
65 | }
66 |
67 | setItems (items) {
68 | this.selectListView.update({items, loadingMessage: null, loadingBadge: null});
69 | }
70 | }
71 |
72 | module.exports = SelectListView;
73 |
--------------------------------------------------------------------------------
/src/projects-list.js:
--------------------------------------------------------------------------------
1 | const ProjectsListView = require('./projects-list-view');
2 | const {getViewFromModel} = require('./common');
3 |
4 | class ProjectsList extends ProjectsListView {
5 | toggle () {
6 | if (this.panel && this.panel.isVisible()) {
7 | this.cancel()
8 | } else {
9 | this.show()
10 | }
11 | }
12 |
13 | createItem (item) {
14 | const element = document.createElement('li');
15 | element.className = 'item';
16 | element.textContent = item.breadcrumb();
17 | return element;
18 | }
19 |
20 | update (items) {
21 | this.setItems(items);
22 | }
23 |
24 | confirmSelection (item) {
25 | const view = getViewFromModel(item);
26 | if (view) {
27 | view.openOnWorkspace();
28 | }
29 | this.cancel();
30 | }
31 | }
32 |
33 | module.exports = ProjectsList;
34 |
--------------------------------------------------------------------------------
/src/status-bar.js:
--------------------------------------------------------------------------------
1 | const map = require('./map');
2 |
3 | const update = function _update (text) {
4 | if (!text) { return; }
5 | if (!statusBar.view || typeof statusBar.view.getItem !== 'function') {
6 | return;
7 | }
8 | const item = statusBar.view.getItem();
9 | if (!item) { return; }
10 | item.textContent = text;
11 | };
12 |
13 | const toggle = function _toggle (value) {
14 | const service = map.get(this);
15 | let item;
16 | if (!statusBar.view) {
17 | item = document.createElement('div');
18 | item.classList.add('inline-block', 'pv-status-bar');
19 | statusBar.view = service.addRightTile({ item });
20 | }
21 |
22 | statusBar.view.destroy();
23 |
24 | if (value) {
25 | statusBar.view = service.addRightTile({
26 | item: statusBar.view.getItem()
27 | });
28 | }
29 | };
30 |
31 | const statusBar = Object.create(null);
32 |
33 | statusBar.update = update;
34 | statusBar.toggle = toggle;
35 |
36 | module.exports = statusBar;
37 |
--------------------------------------------------------------------------------
/src/workers/github.js:
--------------------------------------------------------------------------------
1 | const api = {
2 | url: 'https://api.github.com/gists',
3 | gistDescription: 'atom.io project-viewer backup files',
4 | token: undefined,
5 | gistId: undefined,
6 | setName: 'default',
7 | get gistFileName() {
8 | return `project-viewer-${this.setName}.json`;
9 | },
10 | oldBackupFileName: 'project-viewer.json',
11 | connectionError: function connectionError(error) {
12 | return {
13 | type: 'addError',
14 | message: `Failed to connect to GitHub servers: ${error.message}`,
15 | options: {
16 | icon: 'mark-github'
17 | }
18 | };
19 | },
20 | // helper function to check whether given configuration value is defined
21 | checkConfig: function checkConfig(value, name) {
22 | const promise = new Promise((resolve, reject) => {
23 |
24 | // check if given value is defined
25 | if (value) {
26 | resolve();
27 | return;
28 | }
29 |
30 | reject({
31 | type: 'addWarning',
32 | message: `No ${name} was provided, please check the configuration.`,
33 | options: {
34 | icon: 'mark-github'
35 | }
36 | });
37 | });
38 |
39 | return promise;
40 | },
41 | getGist: function getGist() {
42 | const promise = new Promise((resolve, reject) => {
43 | let url = this.url + '/' + this.gistId;
44 |
45 | let headers = new Headers();
46 | headers.append('Accept', 'application/vnd.github.v3+json');
47 | headers.append('Authorization', `token ${this.token}`);
48 |
49 | let parameters = {
50 | method: 'GET',
51 | headers: headers
52 | };
53 |
54 | fetch(url, parameters)
55 | .then(this.toJson.bind(null, 200))
56 | .then(data => {
57 | if (data && data.files && (data.files.hasOwnProperty(this.gistFileName) || data.files.hasOwnProperty(this.oldBackupFileName))) {
58 | let fileName;
59 | let rename = false;
60 | // if new backup file exists select it for import
61 | if (data.files.hasOwnProperty(this.gistFileName)) {
62 | fileName = this.gistFileName;
63 | } else if (data.files.hasOwnProperty(this.oldBackupFileName)) {
64 | // otherwise fallback to old backup file and queue rename operation
65 | fileName = this.oldBackupFileName;
66 | rename = true;
67 | }
68 |
69 | // backup found, returning
70 | resolve({
71 | type: 'addSuccess',
72 | message: 'Retrieved DB from GitHub successfully.',
73 | db: JSON.parse(data.files[fileName].content),
74 | options: {
75 | icon: 'mark-github'
76 | }
77 | });
78 |
79 | if (rename) {
80 | // rename the file to conform with new backup system
81 | this.renameBackupFile(this.oldBackupFileName, this.gistFileName).then(postMessage).catch(postMessage);
82 | }
83 | return;
84 | }
85 |
86 | // backup not found, response status code was not 200 OK
87 | // either gist with given ID doesnt exist (user hasnt created the gist yet or it has been deleted) or user has no existing backup
88 | reject({
89 | type: 'addWarning',
90 | message: `No backup found under gist ID [${this.gistId}] for set [${this.setName}]. Make sure that gist with given ID exists under your private gists and that you have an existing backup (call backup -> call import).`,
91 | options: {
92 | icon: 'mark-github',
93 | dismissable: true
94 | }
95 | });
96 | }).catch(error => {
97 | reject(this.connectionError(error));
98 | });
99 | });
100 |
101 | return promise;
102 | },
103 | renameBackupFile: function renameBackupFile(oldValue, newValue) {
104 | const promise = new Promise((resolve, reject) => {
105 | let url = this.url + '/' + this.gistId;
106 |
107 | let headers = new Headers();
108 | headers.append('Accept', 'application/vnd.github.v3+json');
109 | headers.append('Authorization', `token ${this.token}`);
110 |
111 | let files = {};
112 | files[oldValue] = {
113 | filename: newValue
114 | };
115 |
116 | let body = JSON.stringify({
117 | description: this.gistDescription,
118 | public: false,
119 | files: files
120 | });
121 |
122 | parameters = {
123 | method: 'PATCH',
124 | headers: headers,
125 | body: body
126 | };
127 |
128 | fetch(url, parameters)
129 | .then((response) => {
130 | if (response.status == 200) {
131 | // gist file successfully renamed
132 | resolve({
133 | type: 'addSuccess',
134 | message: 'Successfully converted old backup file to a new one.',
135 | options: {
136 | icon: 'mark-github'
137 | }
138 | });
139 | return;
140 | }
141 |
142 | // failed to update gist, reponse status code was not 200 OK
143 | reject({
144 | type: 'addWarning',
145 | message: 'Failed to convert old backup file to a new one.',
146 | options: {
147 | icon: 'mark-github'
148 | }
149 | });
150 | }).catch(error => {
151 | reject(this.connectionError(error));
152 | });
153 | });
154 |
155 | return promise;
156 | },
157 | updateGist: function updateGist(value, currentFiles) {
158 | const promise = new Promise((resolve, reject) => {
159 | let url = this.url + '/' + this.gistId;
160 |
161 | let headers = new Headers();
162 | headers.append('Accept', 'application/vnd.github.v3+json');
163 | headers.append('Authorization', `token ${this.token}`);
164 |
165 | let files = {};
166 | files[this.gistFileName] = {
167 | content: JSON.stringify(value)
168 | };
169 |
170 | if (currentFiles && currentFiles.hasOwnProperty(this.oldBackupFileName)) {
171 | // found old backup file, it will be deleted
172 | files[this.oldBackupFileName] = null;
173 | }
174 |
175 | let body = JSON.stringify({
176 | description: this.gistDescription,
177 | public: false,
178 | files: files
179 | });
180 |
181 | parameters = {
182 | method: 'PATCH',
183 | headers: headers,
184 | body: body
185 | };
186 |
187 | fetch(url, parameters)
188 | .then((response) => {
189 | if (response.status == 200) {
190 | // gist successfully updated
191 | resolve({
192 | type: 'addSuccess',
193 | message: 'Successfully backed up the DB.',
194 | options: {
195 | icon: 'mark-github'
196 | }
197 | });
198 | return;
199 | }
200 |
201 | // failed to update gist, reponse status code was not 200 OK
202 | reject({
203 | type: 'addWarning',
204 | message: 'Failed to update gist.',
205 | options: {
206 | icon: 'mark-github'
207 | }
208 | });
209 | }).catch(error => {
210 | reject(this.connectionError(error));
211 | });
212 | });
213 |
214 | return promise;
215 | },
216 | checkIfGistExists: function checkIfGistExists() {
217 | const promise = new Promise((resolve, reject) => {
218 | let url = this.url + '/' + this.gistId;
219 |
220 | let headers = new Headers();
221 | headers.append('Accept', 'application/vnd.github.v3+json');
222 | headers.append('Authorization', `token ${this.token}`);
223 |
224 | let parameters = {
225 | method: 'GET',
226 | headers: headers,
227 | };
228 |
229 | fetch(url, parameters)
230 | .then(this.toJson.bind(null, 200))
231 | .then(data => {
232 | if (data) {
233 | // gist exists
234 | resolve(data);
235 | return;
236 | }
237 |
238 | // gist doesn't exist
239 | reject({
240 | type: 'addWarning',
241 | message: `No gist found with ID [${this.gistId}]. Specify valid gist ID or specify empty gist ID and we will create a gist for you.`,
242 | options: {
243 | icon: 'mark-github',
244 | dismissable: true
245 | }
246 | });
247 | }).catch(error => {
248 | reject(this.connectionError(error));
249 | });
250 | });
251 |
252 | return promise;
253 | },
254 | createNewGist: function createNewGist(value) {
255 | const promise = new Promise((resolve, reject) => {
256 | let headers = new Headers();
257 | headers.append('Accept', 'application/vnd.github.v3+json');
258 | headers.append('Authorization', `token ${this.token}`);
259 |
260 | let files = {};
261 | files[this.gistFileName] = {
262 | content: JSON.stringify(value)
263 | };
264 |
265 | let body = JSON.stringify({
266 | description: this.gistDescription,
267 | public: false,
268 | files: files
269 | });
270 |
271 | let parameters = {
272 | method: 'POST',
273 | headers: headers,
274 | body: body
275 | };
276 |
277 | fetch(this.url, parameters)
278 | .then(this.toJson.bind(null, 201))
279 | .then(data => {
280 | if (data && data.id) {
281 | // gist successfully created
282 | this.gistId = data.id;
283 |
284 | resolve({
285 | type: 'addSuccess',
286 | message: `Successfully created gist ID [${data.id}] and backed up the DB.`,
287 | options: {
288 | icon: 'mark-github'
289 | },
290 | gistId: data.id
291 | });
292 | return;
293 | }
294 |
295 | // failed to craete gist, response status code was not 201 Created
296 | reject({
297 | type: 'addWarning',
298 | message: 'Failed to create gist.',
299 | options: {
300 | icon: 'mark-github'
301 | }
302 | });
303 | }).catch(error => {
304 | reject(this.connectionError(error));
305 | });
306 | });
307 |
308 | return promise;
309 | },
310 | // orchestration function for update operation
311 | updateOperation: function updateOperation(value) {
312 | const promise = new Promise((resolve, reject) => {
313 | if (this.gistId) {
314 | // user provided gist id, check if gist exists (if yes update, otherwise reject)
315 | this.checkIfGistExists()
316 | .then(data => {
317 | this.updateGist(value, data.files).then(resolve).catch(reject);
318 | }).catch(reject);
319 | } else {
320 | // user didnt specify gist id, create gist for him and set it in config
321 | this.createNewGist(value).then(resolve).catch(reject);
322 | }
323 | });
324 |
325 | return promise;
326 | },
327 | // orchestration function for fetch operation
328 | fetchOperation: function fetchOperation() {
329 | const promise = new Promise((resolve, reject) => {
330 | this.getGist().then(resolve).catch(reject);
331 | });
332 |
333 | return promise;
334 | },
335 | // helper function to check whether the response status code equals the requiredStatusCode and return json promise if so
336 | // we then check in Promise.then() if data exists (exists only if response respones status code matched)
337 | toJson: function toJson(requiredStatusCode, response) {
338 | if (response.status == requiredStatusCode) {
339 | return response.json();
340 | }
341 | return Promise.resolve(undefined);
342 | }
343 | };
344 |
345 | onmessage = function(e) {
346 | if (!e.data || e.data.length === 0) {
347 | return;
348 | }
349 |
350 | if (e.data[0].hasOwnProperty('token')) {
351 | api.token = e.data[0].token;
352 | }
353 |
354 | if (e.data[0].hasOwnProperty('gistId')) {
355 | api.gistId = e.data[0].gistId;
356 | }
357 |
358 | if (e.data[0].hasOwnProperty('setName')) {
359 | api.setName = e.data[0].setName;
360 | }
361 |
362 | if (e.data[0].action === 'fetch') {
363 | Promise.all([api.checkConfig(api.token, 'Github Access Token'), api.checkConfig(api.gistId, 'Gist ID')])
364 | .then(() => {
365 | api.fetchOperation()
366 | .then(postMessage)
367 | .catch(postMessage);
368 | })
369 | .catch(postMessage);
370 | } else if (e.data[0].action === 'update') {
371 | Promise.all([api.checkConfig(api.token, 'Github Access Token')])
372 | .then(() => {
373 | api.updateOperation(e.data[0].value)
374 | .then(postMessage)
375 | .catch(postMessage);
376 | })
377 | .catch(postMessage);
378 | }
379 | }
380 |
--------------------------------------------------------------------------------
/styles/project-viewer.less:
--------------------------------------------------------------------------------
1 | @import "ui-variables";
2 | @import "pv-variables";
3 | @import (inline) "../node_modules/devicons/css/devicons.min.css";
4 |
5 | li[is="tabs-tab"][data-type="project-viewer-editor"] {
6 | &:before {
7 | background-color: @project-viewer_item_color;
8 | }
9 | }
10 |
11 | project-viewer-editor {
12 | background-color: @tab-background-color;
13 | padding: @component-padding * 4 0;
14 |
15 | .panel-head {
16 | width: 80%;
17 | margin: 0 auto;
18 | }
19 |
20 | .panel-body {
21 | margin: 0 auto;
22 | height: 100%;
23 | overflow: scroll;
24 | padding: 0 10% 32px 10%;
25 | }
26 |
27 | .pv-editor-header::before {
28 | content: '';
29 | border: 2px solid @project-viewer_item_color;
30 | margin-right: @component-padding;
31 | }
32 |
33 | .pv-editor-block {
34 | padding-bottom: @component-padding * 2;
35 |
36 | &.hidden {
37 | display: none;
38 | }
39 |
40 | .list-tree.groups-list {
41 | position: relative;
42 | }
43 |
44 | .list-item {
45 | padding-left: @component-padding * 2;
46 | }
47 | }
48 |
49 | input,
50 | .btn {
51 | margin-bottom: @component-padding / 2;
52 | margin-right: @component-padding / 2;
53 | }
54 |
55 | .input-color {
56 | border: none;
57 | cursor: pointer;
58 | transition: opacity @pv_transition-duration-default;
59 |
60 | &[disabled] {
61 | cursor: not-allowed;
62 | opacity: 0.1;
63 | }
64 | }
65 |
66 | .input-select {
67 | cursor: pointer;
68 | }
69 |
70 | .input-checkbox {
71 | cursor: pointer;
72 | }
73 |
74 | .pv-color-palette {
75 | display: inline-block;
76 | margin-left: @component-padding * 2;
77 | margin-bottom: @component-padding;
78 | vertical-align: middle;
79 | transition: opacity @pv_transition-duration-default;
80 |
81 | &[hidden] {
82 | opacity: 0.1;
83 |
84 | .pv-palette {
85 | cursor: not-allowed;
86 | }
87 | }
88 | }
89 |
90 | .pv-palette-info {
91 | margin-bottom: 2px;
92 | }
93 |
94 | .pv-path-remove {
95 | cursor: pointer;
96 | }
97 |
98 | .pv-palette {
99 | display: inline-block;
100 | height: 20px;
101 | width: 20px;
102 | border-radius: 5px;
103 | margin: 0 2px;
104 | cursor: pointer;
105 | }
106 |
107 | .input-checkbox.input-checkbox:focus,
108 | .input-radio.input-radio:focus {
109 | outline: 1px solid;
110 | }
111 |
112 | .input-label {
113 | cursor: pointer;
114 | display: block;
115 | width: -webkit-max-content;
116 | margin: 0 1em 1em 0;
117 |
118 | &.inliner {
119 | display: inline-block;
120 | }
121 | }
122 |
123 | .list-group {
124 | padding-top: @component-padding;
125 | }
126 |
127 | .icons-list {
128 | margin-top: @component-padding;
129 | height: 200px;
130 | overflow: scroll;
131 | padding: @component-padding / 2;
132 | background-color: @tool-panel-background-color;
133 | position: relative;
134 | flex: 1 1 300px;
135 | min-width: 0;
136 | border: 1px solid @tool-panel-border-color;
137 | display: flex;
138 | flex-wrap: wrap;
139 | align-content: flex-start;
140 |
141 | &.only-icons {
142 | .icon, .devicons {
143 | flex: 0 0 32px;
144 | height: 32px;
145 | margin: 3px;
146 | text-align: center;
147 | padding: 0;
148 |
149 | &:before{
150 | margin: 0;
151 | font-size: 32px;
152 | width: 32px;
153 | height: 32px;
154 | vertical-align: middle;
155 | }
156 | }
157 | }
158 |
159 | .icon, .devicons {
160 | cursor: pointer;
161 | flex: 1 0 200px;
162 | padding: @component-padding/2 @component-padding;
163 | transition: all @pv_transition-duration-default;
164 |
165 | &:hover:not(.highlight-success) {
166 | color: @text-color-highlight;
167 | background-color: @button-background-color-hover;
168 | }
169 |
170 | &.hidden {
171 | display: none;
172 | }
173 | }
174 | }
175 |
176 | .list-tree {
177 | .list-nested-item {
178 | -webkit-user-select: none;
179 | pointer-events: none;
180 |
181 | .list-item {
182 | pointer-events: all;
183 | cursor: pointer;
184 | transition: color @pv_transition-duration-default ease-in 0s;
185 |
186 | &:hover {
187 | color: @project-viewer_item_color;
188 | }
189 | }
190 |
191 | .list-tree {
192 | pointer-events: none;
193 | }
194 | }
195 | }
196 | }
197 |
198 | .pv-has-icons {
199 |
200 | .devicons {
201 | font-family: @font-family;
202 | font-size: @font-size;
203 | display: inline-block;
204 | text-align: left;
205 | top: 0;
206 |
207 | &:before {
208 | font-family: 'devicons';
209 | font-size: 16px;
210 | margin-right: 5px;
211 | vertical-align: middle;
212 | height: inherit;
213 | }
214 | }
215 |
216 | .list-group .primary-line.devicons {
217 | display: block;
218 | text-align: left;
219 | }
220 | }
221 |
222 | project-viewer {
223 |
224 | &.position-absolute {
225 | position: absolute;
226 | top: 0;
227 | bottom: 0;
228 | z-index: 100;
229 |
230 | &.position-right {
231 | right: 0;
232 | }
233 |
234 | &.position-left {
235 | left: 0;
236 | }
237 | }
238 |
239 | .devicons:before {
240 | font-family: 'devicons';
241 | font-size: @component-icon-size;
242 | margin-right: @component-padding / 2;
243 | vertical-align: middle;
244 | height: inherit;
245 | }
246 |
247 | -webkit-user-select: none;
248 | font-size: @font-size;
249 | font-family: @font-family;
250 | display: flex;
251 | flex-direction: column;
252 | // transition: width 0.5s;
253 | width: @project-viewer_main_width-max;
254 | overflow: hidden;
255 |
256 | &.resizing {
257 | transition: none;
258 | }
259 |
260 | .heading {
261 | color: @project-viewer_item_color;
262 | padding-left: @component-padding;
263 | white-space: nowrap;
264 |
265 | &.hidden {
266 | visibility: hidden;
267 | }
268 | }
269 |
270 | &.autohide {
271 | width: @project-viewer_main_width-min;
272 |
273 | .hidden-block {
274 | opacity: 0;
275 | }
276 |
277 | &:hover {
278 | width: @project-viewer_main_width-max;
279 |
280 | .hidden-block {
281 | opacity: 1;
282 | }
283 | }
284 | }
285 |
286 | .hidden-block {
287 | background-color: @tool-panel-background-color;
288 | position: absolute;
289 | height: 100%;
290 | width: 100%;
291 | z-index: 1;
292 | transition: opacity 0.5s;
293 | overflow: hidden;
294 | }
295 |
296 | .body-content {
297 | overflow: scroll;
298 | height: calc(~"100% - 50px");
299 | width: 100%;
300 | flex-grow: 1;
301 | position: absolute;
302 | }
303 |
304 | > .list-tree {
305 | position: relative;
306 | padding: @component-padding @component-padding/2;
307 | }
308 |
309 | .background-message {
310 | transition: opacity @pv_transition-duration-default;
311 |
312 | &.hidden {
313 | opacity: 0;
314 | }
315 | }
316 |
317 | .has-collapsable-children {
318 | margin-left: 10px;
319 | }
320 |
321 | li[is="project-viewer-group"] {
322 |
323 | &.above {
324 | > .list-item {
325 | color: @project-viewer_item_color;
326 |
327 | &::after {
328 | position: absolute;
329 | content: "";
330 | left: 0;
331 | height: @component-line-height / 2;
332 | width: @component-line-height / 3;
333 | border-color: @project-viewer_item_color;
334 | border-style: solid;
335 | border-width: thin;
336 | border-bottom: transparent;
337 | border-right: transparent;
338 | }
339 | }
340 | }
341 |
342 | &.below {
343 |
344 | > .list-item {
345 | color: @project-viewer_item_color;
346 |
347 | &::after {
348 | position: absolute;
349 | content: "";
350 | left: 0;
351 | height: @component-line-height / 2;
352 | width: @component-line-height / 3;
353 | margin-top: @component-line-height / 2;
354 | border-color: @project-viewer_item_color;
355 | border-style: solid;
356 | border-width: thin;
357 | border-top: transparent;
358 | border-right: transparent;
359 | }
360 | }
361 | }
362 |
363 | &.center {
364 | > .list-item {
365 | color: @project-viewer_item_color;
366 |
367 | &::after {
368 | position: absolute;
369 | content: "";
370 | left: 0;
371 | height: @component-line-height / 2;
372 | margin-top: @component-line-height / 3;
373 | border-color: @project-viewer_item_color;
374 | border-style: solid;
375 | border-width: thin;
376 | border-top: transparent;
377 | border-right: transparent;
378 | border-bottom: transparent;
379 | }
380 | }
381 | }
382 | }
383 |
384 | li[is="project-viewer-group"].list-nested-item,
385 | li[is="project-viewer-project"].list-item {
386 | transition: opacity @pv_transition-duration-default;
387 |
388 | &.dragged {
389 | opacity: 0.5;
390 |
391 | > .list-item {
392 | color: @project-viewer_item_color_dragged !important;
393 | }
394 |
395 | .list-tree {
396 | display: none;
397 | }
398 | }
399 |
400 | &:not(.selected)::before {
401 | position: absolute;
402 | left: 0;
403 | height: @component-line-height;
404 | content: "";
405 | width: 2px;
406 | background-color: @project-viewer_item_background-color;
407 | visibility: hidden;
408 | transform: scaleY(0);
409 | color: @project-viewer_item_color;
410 | transition: transform @pv_transition-duration-default, visibility @pv_transition-duration-default;
411 | padding-left: 0;
412 | }
413 |
414 | &.active {
415 | color: @project-viewer_item_color;
416 |
417 | > .list-item {
418 | color: @project-viewer_item_color;
419 | }
420 |
421 | &:not(.selected):not(.no-paths)::before {
422 | visibility: visible !important;
423 | transform: scaleY(1) !important;
424 | }
425 | }
426 | }
427 |
428 | li[is="project-viewer-group"].list-nested-item {
429 | -webkit-user-drag: element;
430 | pointer-events: none;
431 | transition: color @pv_transition-duration-default ease-in 0s;
432 |
433 | > .list-item {
434 | pointer-events: all;
435 | cursor: pointer;
436 |
437 | &:hover {
438 | color: @project-viewer_item_color;
439 | }
440 | }
441 |
442 | .list-tree {
443 | pointer-events: none;
444 | }
445 | }
446 |
447 | li[is="project-viewer-project"].list-item {
448 | -webkit-user-drag: element;
449 | -webkit-user-select: none;
450 | cursor: pointer;
451 | transition: color @pv_transition-duration-default ease-in 0s, background-color @pv_transition-duration-default ease-in 0s, padding @pv_transition-duration-fast ease-in 0s;
452 | pointer-events: all;
453 |
454 | &.no-paths,
455 | &.no-paths span {
456 | text-decoration: line-through;
457 | color: @text-color-subtle;
458 | cursor: not-allowed;
459 | }
460 |
461 | &:not(.selected):not(.no-paths)::before {
462 | position: absolute;
463 | left: 0;
464 | height: @component-line-height;
465 | content: "";
466 | width: 2px;
467 | background-color: @project-viewer_item_background-color;
468 | visibility: hidden;
469 | transform: scaleY(0);
470 | transition: transform @pv_transition-duration-default, visibility @pv_transition-duration-default;
471 | }
472 |
473 | &:not(.selected):not(.no-paths):hover {
474 | color: @project-viewer_item_color;
475 | padding-left: 0;
476 | }
477 |
478 | &:not(.dragging):not(.selected):hover::before {
479 | visibility: visible;
480 | transform: scaleY(1);
481 | }
482 |
483 | span {
484 | pointer-events: none;
485 | }
486 |
487 | &.dropping {
488 | padding-top: 0;
489 | padding-bottom: 0;
490 | color: @project-viewer_item_color;
491 |
492 | &.above {
493 | padding-top: 5px;
494 | padding-bottom: 0;
495 | }
496 |
497 | &.below {
498 | padding-top: 0;
499 | padding-bottom: 5px;
500 | }
501 | }
502 | }
503 | }
504 |
505 | .pv-resizer {
506 | position: absolute;
507 | cursor: ew-resize;
508 | top: 0;
509 | width: 4px;
510 | height: 100%;
511 | display: block;
512 | opacity: 1;
513 | transition: background-color @pv_transition-duration-default;
514 |
515 | &.invert {
516 | right: 0;
517 | }
518 |
519 | &:hover {
520 | opacity: 0.5;
521 | background-color: @background-color-highlight;
522 | }
523 | }
524 |
--------------------------------------------------------------------------------
/styles/pv-variables.less:
--------------------------------------------------------------------------------
1 | @pv_transition-duration-default: 0.3s;
2 | @pv_transition-duration-fast: 0.1s;
3 |
4 | @project-viewer_main_width-max: 200px;
5 | //@project-viewer_main_width-max: 100%;
6 | @project-viewer_main_width-min: 20px;
7 | @project-viewer_item_background-color: @ui-site-color-2;
8 | @project-viewer_item_color: @ui-site-color-2;
9 | @project-viewer_item_color_dragged: @ui-site-color-1;
10 |
--------------------------------------------------------------------------------