Merge pull request #17893 from atom/dw-migrate-about

➡️ Migrate core package 'about' into ./packages
This commit is contained in:
David Wilson
2018-08-23 15:48:58 -07:00
committed by GitHub
24 changed files with 3667 additions and 8 deletions

9
package-lock.json generated
View File

@@ -491,8 +491,7 @@
"integrity": "sha1-hn2g/zn3eGEyQsRM/qg/CqTr35s="
},
"about": {
"version": "https://www.atom.io/api/packages/about/versions/1.10.0/tarball",
"integrity": "sha512-fM7e2HvlwwQ38lD5F2FFH6LaT6z4rm6q8XTeIpZOkIymtMx8YdNyZDl2WS0bP0+uBLPxeZdw2E+DJZmKxw9oRA==",
"version": "file:packages/about",
"requires": {
"etch": "0.9.0",
"semver": "^5.5.0"
@@ -500,13 +499,11 @@
"dependencies": {
"etch": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/etch/-/etch-0.9.0.tgz",
"integrity": "sha1-CSJpiPLO4GkL3yCMyyXkFNXfrV8="
"bundled": true
},
"semver": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
"integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw=="
"bundled": true
}
}
},

View File

@@ -17,7 +17,7 @@
"@atom/nsfw": "^1.0.18",
"@atom/source-map-support": "^0.3.4",
"@atom/watcher": "1.0.8",
"about": "https://www.atom.io/api/packages/about/versions/1.10.0/tarball",
"about": "file:packages/about",
"archive-view": "https://www.atom.io/api/packages/archive-view/versions/0.65.1/tarball",
"async": "0.2.6",
"atom-dark-syntax": "https://www.atom.io/api/packages/atom-dark-syntax/versions/0.29.0/tarball",
@@ -183,7 +183,7 @@
"one-light-syntax": "1.8.4",
"solarized-dark-syntax": "1.1.5",
"solarized-light-syntax": "1.1.5",
"about": "1.10.0",
"about": "file:./packages/about",
"archive-view": "0.65.1",
"autocomplete-atom-api": "0.10.7",
"autocomplete-css": "0.17.5",

194
packages/README.md Normal file
View File

@@ -0,0 +1,194 @@
# Atom Core Packages
This folder contains core packages that are bundled with Atom releases. Not all Atom core packages are kept here; please
see the table below for the location of every core Atom package.
> **NOTE:** There is an ongoing effort to migrate more Atom packages from their individual repositories to this folder.
See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate-core-packages.md) for more details.
| Package | Where to find it | Migration issue |
|---------|------------------|-----------------|
| **about** | [`./packages/about`](./about) | [#17832](https://github.com/atom/atom/issues/17832) |
| **atom-dark-syntax** | [`atom/atom-dark-syntax`][atom-dark-syntax] | [#17849](https://github.com/atom/atom/issues/17849) |
| **atom-dark-ui** | [`atom/atom-dark-ui`][atom-dark-ui] | [#17850](https://github.com/atom/atom/issues/17850) |
| **atom-light-syntax** | [`atom/atom-light-syntax`][atom-light-syntax] | [#17851](https://github.com/atom/atom/issues/17851) |
| **atom-light-ui** | [`atom/atom-light-ui`][atom-light-ui] | [#17852](https://github.com/atom/atom/issues/17852) |
| **autocomplete-atom-api** | [`atom/autocomplete-atom-api`][autocomplete-atom-api] | |
| **autocomplete-css** | [`atom/autocomplete-css`][autocomplete-css] | |
| **autocomplete-html** | [`atom/autocomplete-html`][autocomplete-html] | |
| **autocomplete-plus** | [`atom/autocomplete-plus`][autocomplete-plus] | |
| **autocomplete-snippets** | [`atom/autocomplete-snippets`][autocomplete-snippets] | |
| **autoflow** | [`atom/autoflow`][autoflow] | [#17833](https://github.com/atom/atom/issues/17833) |
| **autosave** | [`atom/autosave`][autosave] | [#17834](https://github.com/atom/atom/issues/17834) |
| **background-tips** | [`atom/background-tips`][background-tips] | [#17835](https://github.com/atom/atom/issues/17835) |
| **base16-tomorrow-dark-theme** | [`atom/base16-tomorrow-dark-theme`][base16-tomorrow-dark-theme] | [#17836](https://github.com/atom/atom/issues/17836) |
| **base16-tomorrow-light-theme** | [`atom/base16-tomorrow-light-theme`][base16-tomorrow-light-theme] | [#17837](https://github.com/atom/atom/issues/17837) |
| **bookmarks** | [`atom/bookmarks`][bookmarks] | |
| **bracket-matcher** | [`atom/bracket-matcher`][bracket-matcher] | |
| **command-palette** | [`atom/command-palette`][command-palette] | |
| **dalek** | [`atom/dalek`][dalek] | [#17838](https://github.com/atom/atom/issues/17838) |
| **deprecation-cop** | [`atom/deprecation-cop`][deprecation-cop] | [#17839](https://github.com/atom/atom/issues/17839) |
| **dev-live-reload** | [`atom/dev-live-reload`][dev-live-reload] | [#17840](https://github.com/atom/atom/issues/17840) |
| **encoding-selector** | [`atom/encoding-selector`][encoding-selector] | [#17841](https://github.com/atom/atom/issues/17841) |
| **exception-reporting** | [`atom/exception-reporting`][exception-reporting] | [#17842](https://github.com/atom/atom/issues/17842) |
| **find-and-replace** | [`atom/find-and-replace`][find-and-replace] | |
| **fuzzy-finder** | [`atom/fuzzy-finder`][fuzzy-finder] | |
| **github** | [`atom/github`][github] | |
| **git-diff** | [`atom/git-diff`][git-diff] | [#17843](https://github.com/atom/atom/issues/17843) |
| **go-to-line** | [`atom/go-to-line`][go-to-line] | [#17844](https://github.com/atom/atom/issues/17844) |
| **grammar-selector** | [`atom/grammar-selector`][grammar-selector] | [#17845](https://github.com/atom/atom/issues/17845) |
| **image-view** | [`atom/image-view`][image-view] | |
| **incompatible-packages** | [`atom/incompatible-packages`][incompatible-packages] | [#17846](https://github.com/atom/atom/issues/17846) |
| **keybinding-resolver** | [`atom/keybinding-resolver`][keybinding-resolver] | |
| **language-c** | [`atom/language-c`][language-c] | |
| **language-clojure** | [`atom/language-clojure`][language-clojure] | |
| **language-coffee-script** | [`atom/language-coffee-script`][language-coffee-script] | |
| **language-csharp** | [`atom/language-csharp`][language-csharp] | |
| **language-css** | [`atom/language-css`][language-css] | |
| **language-gfm** | [`atom/language-gfm`][language-gfm] | |
| **language-git** | [`atom/language-git`][language-git] | |
| **language-go** | [`atom/language-go`][language-go] | |
| **language-html** | [`atom/language-html`][language-html] | |
| **language-hyperlink** | [`atom/language-hyperlink`][language-hyperlink] | |
| **language-java** | [`atom/language-java`][language-java] | |
| **language-javascript** | [`atom/language-javascript`][language-javascript] | |
| **language-json** | [`atom/language-json`][language-json] | |
| **language-less** | [`atom/language-less`][language-less] | |
| **language-make** | [`atom/language-make`][language-make] | |
| **language-mustache** | [`atom/language-mustache`][language-mustache] | |
| **language-objective-c** | [`atom/language-objective-c`][language-objective-c] | |
| **language-perl** | [`atom/language-perl`][language-perl] | |
| **language-php** | [`atom/language-php`][language-php] | |
| **language-property-list** | [`atom/language-property-list`][language-property-list] | |
| **language-python** | [`atom/language-python`][language-python] | |
| **language-ruby** | [`atom/language-ruby`][language-ruby] | |
| **language-ruby-on-rails** | [`atom/language-ruby-on-rails`][language-ruby-on-rails] | |
| **language-sass** | [`atom/language-sass`][language-sass] | |
| **language-shellscript** | [`atom/language-shellscript`][language-shellscript] | |
| **language-source** | [`atom/language-source`][language-source] | |
| **language-sql** | [`atom/language-sql`][language-sql] | |
| **language-text** | [`atom/language-text`][language-text] | |
| **language-todo** | [`atom/language-todo`][language-todo] | |
| **language-toml** | [`atom/language-toml`][language-toml] | |
| **language-typescript** | [`atom/language-typescript`][language-typescript] | |
| **language-xml** | [`atom/language-xml`][language-xml] | |
| **language-yaml** | [`atom/language-yaml`][language-yaml] | |
| **line-ending-selector** | [`atom/line-ending-selector`][line-ending-selector] | [#17847](https://github.com/atom/atom/issues/17847) |
| **link** | [`atom/link`][link] | [#17848](https://github.com/atom/atom/issues/17848) |
| **markdown-preview** | [`atom/markdown-preview`][markdown-preview] | |
| **metrics** | [`atom/metrics`][metrics] | |
| **notifications** | [`atom/notifications`][notifications] | |
| **one-dark-syntax** | [`atom/one-dark-syntax`][one-dark-syntax] | [#17853](https://github.com/atom/atom/issues/17853) |
| **one-dark-ui** | [`atom/one-dark-ui`][one-dark-ui] | [#17854](https://github.com/atom/atom/issues/17854) |
| **one-light-syntax** | [`atom/one-light-syntax`][one-light-syntax] | [#17855](https://github.com/atom/atom/issues/17855) |
| **one-light-ui** | [`atom/one-light-ui`][one-light-ui] | |
| **open-on-github** | [`atom/open-on-github`][open-on-github] | |
| **package-generator** | [`atom/package-generator`][package-generator] | |
| **settings-view** | [`atom/settings-view`][settings-view] | |
| **snippets** | [`atom/snippets`][snippets] | |
| **solarized-dark-syntax** | [`atom/solarized-dark-syntax`][solarized-dark-syntax] | |
| **solarized-light-syntax** | [`atom/solarized-light-syntax`][solarized-light-syntax] | |
| **spell-check** | [`atom/spell-check`][spell-check] | |
| **status-bar** | [`atom/status-bar`][status-bar] | |
| **styleguide** | [`atom/styleguide`][styleguide] | |
| **symbols-view** | [`atom/symbols-view`][symbols-view] | |
| **tabs** | [`atom/tabs`][tabs] | |
| **timecop** | [`atom/timecop`][timecop] | |
| **tree-view** | [`atom/tree-view`][tree-view] | |
| **update-package-dependencies** | [`atom/update-package-dependencies`][update-package-dependencies] | |
| **welcome** | [`atom/welcome`][welcome] | |
| **whitespace** | [`atom/whitespace`][whitespace] | |
| **wrap-guide** | [`atom/wrap-guide`][wrap-guide] | |
[about]: https://github.com/atom/about
[archive-view]: https://github.com/atom/archive-view
[atom-dark-syntax]: https://github.com/atom/atom-dark-syntax
[atom-dark-ui]: https://github.com/atom/atom-dark-ui
[atom-light-syntax]: https://github.com/atom/atom-light-syntax
[atom-light-ui]: https://github.com/atom/atom-light-ui
[autocomplete-atom-api]: https://github.com/atom/autocomplete-atom-api
[autocomplete-css]: https://github.com/atom/autocomplete-css
[autocomplete-html]: https://github.com/atom/autocomplete-html
[autocomplete-plus]: https://github.com/atom/autocomplete-plus
[autocomplete-snippets]: https://github.com/atom/autocomplete-snippets
[autoflow]: https://github.com/atom/autoflow
[autosave]: https://github.com/atom/autosave
[background-tips]: https://github.com/atom/background-tips
[base16-tomorrow-dark-theme]: https://github.com/atom/base16-tomorrow-dark-theme
[base16-tomorrow-light-theme]: https://github.com/atom/base16-tomorrow-light-theme
[bookmarks]: https://github.com/atom/bookmarks
[bracket-matcher]: https://github.com/atom/bracket-matcher
[command-palette]: https://github.com/atom/command-palette
[dalek]: https://github.com/atom/dalek
[deprecation-cop]: https://github.com/atom/deprecation-cop
[dev-live-reload]: https://github.com/atom/dev-live-reload
[encoding-selector]: https://github.com/atom/encoding-selector
[exception-reporting]: https://github.com/atom/exception-reporting
[find-and-replace]: https://github.com/atom/find-and-replace
[fuzzy-finder]: https://github.com/atom/fuzzy-finder
[git-diff]: https://github.com/atom/git-diff
[github]: https://github.com/atom/github
[go-to-line]: https://github.com/atom/go-to-line
[grammar-selector]: https://github.com/atom/grammar-selector
[image-view]: https://github.com/atom/image-view
[incompatible-packages]: https://github.com/atom/incompatible-packages
[keybinding-resolver]: https://github.com/atom/keybinding-resolver
[language-c]: https://github.com/atom/language-c
[language-clojure]: https://github.com/atom/language-clojure
[language-coffee-script]: https://github.com/atom/language-coffee-script
[language-csharp]: https://github.com/atom/language-csharp
[language-css]: https://github.com/atom/language-css
[language-gfm]: https://github.com/atom/language-gfm
[language-git]: https://github.com/atom/language-git
[language-go]: https://github.com/atom/language-go
[language-html]: https://github.com/atom/language-html
[language-hyperlink]: https://github.com/atom/language-hyperlink
[language-java]: https://github.com/atom/language-java
[language-javascript]: https://github.com/atom/language-javascript
[language-json]: https://github.com/atom/language-json
[language-less]: https://github.com/atom/language-less
[language-make]: https://github.com/atom/language-make
[language-mustache]: https://github.com/atom/language-mustache
[language-objective-c]: https://github.com/atom/language-objective-c
[language-perl]: https://github.com/atom/language-perl
[language-php]: https://github.com/atom/language-php
[language-property-list]: https://github.com/atom/language-property-list
[language-python]: https://github.com/atom/language-python
[language-ruby]: https://github.com/atom/language-ruby
[language-ruby-on-rails]: https://github.com/atom/language-ruby-on-rails
[language-sass]: https://github.com/atom/language-sass
[language-shellscript]: https://github.com/atom/language-shellscript
[language-source]: https://github.com/atom/language-source
[language-sql]: https://github.com/atom/language-sql
[language-text]: https://github.com/atom/language-text
[language-todo]: https://github.com/atom/language-todo
[language-toml]: https://github.com/atom/language-toml
[language-typescript]: https://github.com/atom/language-typescript
[language-xml]: https://github.com/atom/language-xml
[language-yaml]: https://github.com/atom/language-yaml
[line-ending-selector]: https://github.com/atom/line-ending-selector
[link]: https://github.com/atom/link
[markdown-preview]: https://github.com/atom/markdown-preview
[metrics]: https://github.com/atom/metrics
[notifications]: https://github.com/atom/notifications
[one-dark-syntax]: https://github.com/atom/one-dark-syntax
[one-dark-ui]: https://github.com/atom/one-dark-ui
[one-light-syntax]: https://github.com/atom/one-light-syntax
[one-light-ui]: https://github.com/atom/one-light-ui
[open-on-github]: https://github.com/atom/open-on-github
[package-generator]: https://github.com/atom/package-generator
[settings-view]: https://github.com/atom/settings-view
[snippets]: https://github.com/atom/snippets
[solarized-dark-syntax]: https://github.com/atom/solarized-dark-syntax
[solarized-light-syntax]: https://github.com/atom/solarized-light-syntax
[spell-check]: https://github.com/atom/spell-check
[status-bar]: https://github.com/atom/status-bar
[styleguide]: https://github.com/atom/styleguide
[symbols-view]: https://github.com/atom/symbols-view
[tabs]: https://github.com/atom/tabs
[timecop]: https://github.com/atom/timecop
[tree-view]: https://github.com/atom/tree-view
[update-package-dependencies]: https://github.com/atom/update-package-dependencies
[welcome]: https://github.com/atom/welcome
[whitespace]: https://github.com/atom/whitespace
[wrap-guide]: https://github.com/atom/wrap-guide

3
packages/about/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.DS_Store
npm-debug.log
node_modules

20
packages/about/LICENSE.md Normal file
View File

@@ -0,0 +1,20 @@
Copyright (c) 2015 Machisté N. Quintana
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

21
packages/about/README.md Normal file
View File

@@ -0,0 +1,21 @@
# About package
View useful information about your Atom installation.
![About Atom](https://cloud.githubusercontent.com/assets/16760489/19395499/69bbb780-922d-11e6-9779-2b8327027ea5.png)
This is a package for [Atom](https://atom.io), a hackable text editor for the 21st Century.
## Usage
This package provides a cross-platform "About Atom" view that displays information about your Atom installation, which currently includes the current version, the license, and the Terms of Use.
## Contributing
Always feel free to help out! Whether it's filing bugs and feature requests
or working on some of the open issues, Atom's [contributing guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md)
will help get you started while the [guide for contributing to packages](https://github.com/atom/atom/blob/master/docs/contributing-to-packages.md)
has some extra information.
## License
[MIT License](https://opensource.org/licenses/MIT) - see the [LICENSE](https://github.com/atom/about/blob/master/LICENSE.md) for more details.

View File

@@ -0,0 +1,93 @@
const {CompositeDisposable, Emitter} = require('atom')
const AboutView = require('./components/about-view')
// Deferred requires
let shell
module.exports = class About {
constructor (initialState) {
this.subscriptions = new CompositeDisposable()
this.emitter = new Emitter()
this.state = initialState
this.views = {
aboutView: null
}
this.subscriptions.add(atom.workspace.addOpener((uriToOpen) => {
if (uriToOpen === this.state.uri) {
return this.deserialize()
}
}))
this.subscriptions.add(atom.commands.add('atom-workspace', 'about:view-release-notes', () => {
shell = shell || require('electron').shell
shell.openExternal(this.state.updateManager.getReleaseNotesURLForCurrentVersion())
}))
}
destroy () {
if (this.views.aboutView) this.views.aboutView.destroy()
this.views.aboutView = null
if (this.state.updateManager) this.state.updateManager.dispose()
this.setState({updateManager: null})
this.subscriptions.dispose()
}
setState (newState) {
if (newState && typeof newState === 'object') {
let {state} = this
this.state = Object.assign({}, state, newState)
this.didChange()
}
}
didChange () {
this.emitter.emit('did-change')
}
onDidChange (callback) {
this.emitter.on('did-change', callback)
}
deserialize (state) {
if (!this.views.aboutView) {
this.setState(state)
this.views.aboutView = new AboutView({
uri: this.state.uri,
updateManager: this.state.updateManager,
currentAtomVersion: this.state.currentAtomVersion,
currentElectronVersion: this.state.currentElectronVersion,
currentChromeVersion: this.state.currentChromeVersion,
currentNodeVersion: this.state.currentNodeVersion,
availableVersion: this.state.updateManager.getAvailableVersion()
})
this.handleStateChanges()
}
return this.views.aboutView
}
handleStateChanges () {
this.onDidChange(() => {
if (this.views.aboutView) {
this.views.aboutView.update({
updateManager: this.state.updateManager,
currentAtomVersion: this.state.currentAtomVersion,
currentElectronVersion: this.state.currentElectronVersion,
currentChromeVersion: this.state.currentChromeVersion,
currentNodeVersion: this.state.currentNodeVersion,
availableVersion: this.state.updateManager.getAvailableVersion()
})
}
})
this.state.updateManager.onDidChange(() => {
this.didChange()
})
}
}

View File

@@ -0,0 +1,30 @@
const {CompositeDisposable} = require('atom')
const etch = require('etch')
const EtchComponent = require('../etch-component')
const $ = etch.dom
module.exports =
class AboutStatusBar extends EtchComponent {
constructor () {
super()
this.subscriptions = new CompositeDisposable()
this.subscriptions.add(atom.tooltips.add(this.element, {title: 'An update will be installed the next time Atom is relaunched.<br/><br/>Click the squirrel icon for more information.'}))
}
handleClick () {
atom.workspace.open('atom://about')
}
render () {
return $.div({className: 'about-release-notes inline-block', onclick: this.handleClick.bind(this)},
$.span({type: 'button', className: 'icon icon-squirrel'})
)
}
destroy () {
super.destroy()
this.subscriptions.dispose()
}
}

View File

@@ -0,0 +1,159 @@
const {Disposable} = require('atom')
const etch = require('etch')
const shell = require('shell')
const AtomLogo = require('./atom-logo')
const EtchComponent = require('../etch-component')
const UpdateView = require('./update-view')
const $ = etch.dom
module.exports =
class AboutView extends EtchComponent {
handleAtomVersionClick (e) {
e.preventDefault()
atom.clipboard.write(this.props.currentAtomVersion)
}
handleElectronVersionClick (e) {
e.preventDefault()
atom.clipboard.write(this.props.currentElectronVersion)
}
handleChromeVersionClick (e) {
e.preventDefault()
atom.clipboard.write(this.props.currentChromeVersion)
}
handleNodeVersionClick (e) {
e.preventDefault()
atom.clipboard.write(this.props.currentNodeVersion)
}
handleReleaseNotesClick (e) {
e.preventDefault()
shell.openExternal(this.props.updateManager.getReleaseNotesURLForAvailableVersion())
}
handleLicenseClick (e) {
e.preventDefault()
atom.commands.dispatch(atom.views.getView(atom.workspace), 'application:open-license')
}
handleTermsOfUseClick (e) {
e.preventDefault()
shell.openExternal('https://atom.io/terms')
}
handleHowToUpdateClick (e) {
e.preventDefault()
shell.openExternal('https://flight-manual.atom.io/getting-started/sections/installing-atom/')
}
handleShowMoreClick (e) {
e.preventDefault()
var showMoreDiv = document.querySelector('.show-more')
var showMoreText = document.querySelector('.about-more-expand')
switch (showMoreText.textContent) {
case 'Show more':
showMoreDiv.classList.toggle('hidden')
showMoreText.textContent = 'Hide'
break
case 'Hide':
showMoreDiv.classList.toggle('hidden')
showMoreText.textContent = 'Show more'
break
}
}
render () {
return $.div({className: 'pane-item native-key-bindings about'},
$.div({className: 'about-container'},
$.header({className: 'about-header'},
$.a({className: 'about-atom-io', href: 'https://atom.io'},
$(AtomLogo)
),
$.div({className: 'about-header-info'},
$.span({className: 'about-version-container inline-block atom', onclick: this.handleAtomVersionClick.bind(this)},
$.span({className: 'about-version'}, `${this.props.currentAtomVersion} ${process.arch}`),
$.span({className: 'icon icon-clippy about-copy-version'})
),
$.a({className: 'about-header-release-notes', onclick: this.handleReleaseNotesClick.bind(this)}, 'Release Notes')
),
$.span({className: 'about-version-container inline-block show-more-expand', onclick: this.handleShowMoreClick.bind(this)},
$.span({className: 'about-more-expand'}, 'Show more')
),
$.div({className: 'show-more hidden about-more-info'},
$.div({className: 'about-more-info'},
$.span({className: 'about-version-container inline-block electron', onclick: this.handleElectronVersionClick.bind(this)},
$.span({className: 'about-more-version'}, `Electron: ${this.props.currentElectronVersion} `),
$.span({className: 'icon icon-clippy about-copy-version'})
)
),
$.div({className: 'about-more-info'},
$.span({className: 'about-version-container inline-block chrome', onclick: this.handleChromeVersionClick.bind(this)},
$.span({className: 'about-more-version'}, `Chrome: ${this.props.currentChromeVersion} `),
$.span({className: 'icon icon-clippy about-copy-version'})
)
),
$.div({className: 'about-more-info'},
$.span({className: 'about-version-container inline-block node', onclick: this.handleNodeVersionClick.bind(this)},
$.span({className: 'about-more-version'}, `Node: ${this.props.currentNodeVersion} `),
$.span({className: 'icon icon-clippy about-copy-version'})
)
)
)
)
),
$(UpdateView, {
updateManager: this.props.updateManager,
availableVersion: this.props.availableVersion,
viewUpdateReleaseNotes: this.handleReleaseNotesClick.bind(this),
viewUpdateInstructions: this.handleHowToUpdateClick.bind(this)
}),
$.div({className: 'about-actions group-item'},
$.div({className: 'btn-group'},
$.button({className: 'btn view-license', onclick: this.handleLicenseClick.bind(this)}, 'License'),
$.button({className: 'btn terms-of-use', onclick: this.handleTermsOfUseClick.bind(this)}, 'Terms of Use')
)
),
$.div({className: 'about-love group-start'},
$.span({className: 'icon icon-code'}),
$.span({className: 'inline'}, ' with '),
$.span({className: 'icon icon-heart'}),
$.span({className: 'inline'}, ' by '),
$.a({className: 'icon icon-logo-github', href: 'https://github.com'})
),
$.div({className: 'about-credits group-item'},
$.span({className: 'inline'}, 'And the awesome '),
$.a({href: 'https://github.com/atom/atom/contributors'}, 'Atom Community')
)
)
}
serialize () {
return {
deserializer: this.constructor.name,
uri: this.props.uri
}
}
onDidChangeTitle () {
return new Disposable()
}
onDidChangeModified () {
return new Disposable()
}
getTitle () {
return 'About'
}
getIconName () {
return 'info'
}
}

View File

@@ -0,0 +1,28 @@
const etch = require('etch')
const EtchComponent = require('../etch-component')
const $ = etch.dom
module.exports =
class AtomLogo extends EtchComponent {
render () {
return $.svg({className: 'about-logo', width: '330px', height: '68px', viewBox: '0 0 330 68'},
$.g({stroke: 'none', 'stroke-width': '1', fill: 'none', 'fill-rule': 'evenodd'},
$.g({transform: 'translate(2.000000, 1.000000)'},
$.g({transform: 'translate(96.000000, 8.000000)', fill: 'currentColor'},
$.path({d: 'M185.498,3.399 C185.498,2.417 186.34,1.573 187.324,1.573 L187.674,1.573 C188.447,1.573 189.01,1.995 189.5,2.628 L208.676,30.862 L227.852,2.628 C228.272,1.995 228.905,1.573 229.676,1.573 L230.028,1.573 C231.01,1.573 231.854,2.417 231.854,3.399 L231.854,49.403 C231.854,50.387 231.01,51.231 230.028,51.231 C229.044,51.231 228.202,50.387 228.202,49.403 L228.202,8.246 L210.151,34.515 C209.729,35.148 209.237,35.428 208.606,35.428 C207.973,35.428 207.481,35.148 207.061,34.515 L189.01,8.246 L189.01,49.475 C189.01,50.457 188.237,51.231 187.254,51.231 C186.27,51.231 185.498,50.458 185.498,49.475 L185.498,3.399 L185.498,3.399 Z'}),
$.path({d: 'M113.086,26.507 L113.086,26.367 C113.086,12.952 122.99,0.941 137.881,0.941 C152.77,0.941 162.533,12.811 162.533,26.225 L162.533,26.367 C162.533,39.782 152.629,51.792 137.74,51.792 C122.85,51.792 113.086,39.923 113.086,26.507 M158.74,26.507 L158.74,26.367 C158.74,14.216 149.89,4.242 137.74,4.242 C125.588,4.242 116.879,14.075 116.879,26.225 L116.879,26.367 C116.879,38.518 125.729,48.491 137.881,48.491 C150.031,48.491 158.74,38.658 158.74,26.507'}),
$.path({d: 'M76.705,5.155 L60.972,5.155 C60.06,5.155 59.287,4.384 59.287,3.469 C59.287,2.556 60.059,1.783 60.972,1.783 L96.092,1.783 C97.004,1.783 97.778,2.555 97.778,3.469 C97.778,4.383 97.005,5.155 96.092,5.155 L80.358,5.155 L80.358,49.405 C80.358,50.387 79.516,51.231 78.532,51.231 C77.55,51.231 76.706,50.387 76.706,49.405 L76.706,5.155 L76.705,5.155 Z'}),
$.path({d: 'M0.291,48.562 L21.291,3.05 C21.783,1.995 22.485,1.292 23.75,1.292 L23.891,1.292 C25.155,1.292 25.858,1.995 26.348,3.05 L47.279,48.421 C47.49,48.843 47.56,49.194 47.56,49.546 C47.56,50.458 46.788,51.231 45.803,51.231 C44.961,51.231 44.329,50.599 43.978,49.826 L38.219,37.183 L9.21,37.183 L3.45,49.897 C3.099,50.739 2.538,51.231 1.694,51.231 C0.781,51.231 0.008,50.529 0.008,49.685 C0.009,49.404 0.08,48.983 0.291,48.562 L0.291,48.562 Z M36.673,33.882 L23.749,5.437 L10.755,33.882 L36.673,33.882 L36.673,33.882 Z'})
),
$.g({},
$.path({d: 'M40.363,32.075 C40.874,34.44 39.371,36.77 37.006,37.282 C34.641,37.793 32.311,36.29 31.799,33.925 C31.289,31.56 32.791,29.23 35.156,28.718 C37.521,28.207 39.851,29.71 40.363,32.075', fill: 'currentColor'}),
$.path({d: 'M48.578,28.615 C56.851,45.587 58.558,61.581 52.288,64.778 C45.822,68.076 33.326,56.521 24.375,38.969 C15.424,21.418 13.409,4.518 19.874,1.221 C22.689,-0.216 26.648,1.166 30.959,4.629', stroke: 'currentColor', 'stroke-width': '3.08', 'stroke-linecap': 'round'}),
$.path({d: 'M7.64,39.45 C2.806,36.94 -0.009,33.915 0.154,30.79 C0.531,23.542 16.787,18.497 36.462,19.52 C56.137,20.544 71.781,27.249 71.404,34.497 C71.241,37.622 68.127,40.338 63.06,42.333', stroke: 'currentColor', 'stroke-width': '3.08', 'stroke-linecap': 'round'}),
$.path({d: 'M28.828,59.354 C23.545,63.168 18.843,64.561 15.902,62.653 C9.814,58.702 13.572,42.102 24.296,25.575 C35.02,9.048 48.649,-1.149 54.736,2.803 C57.566,4.639 58.269,9.208 57.133,15.232', stroke: 'currentColor', 'stroke-width': '3.08', 'stroke-linecap': 'round'})
)
)
)
)
}
}

View File

@@ -0,0 +1,121 @@
const etch = require('etch')
const EtchComponent = require('../etch-component')
const UpdateManager = require('../update-manager')
const $ = etch.dom
module.exports =
class UpdateView extends EtchComponent {
constructor (props) {
super(props)
if (this.props.updateManager.getAutoUpdatesEnabled() && this.props.updateManager.getState() === UpdateManager.State.Idle) {
this.props.updateManager.checkForUpdate()
}
}
handleAutoUpdateCheckbox (e) {
atom.config.set('core.automaticallyUpdate', e.target.checked)
}
shouldUpdateActionButtonBeDisabled () {
let {state} = this.props.updateManager
return state === UpdateManager.State.CheckingForUpdate || state === UpdateManager.State.DownloadingUpdate
}
executeUpdateAction () {
if (this.props.updateManager.state === UpdateManager.State.UpdateAvailableToInstall) {
this.props.updateManager.restartAndInstallUpdate()
} else {
this.props.updateManager.checkForUpdate()
}
}
renderUpdateStatus () {
let updateStatus = ''
switch (this.props.updateManager.state) {
case UpdateManager.State.Idle:
updateStatus = $.div({className: 'about-updates-item is-shown about-default-update-message'},
this.props.updateManager.getAutoUpdatesEnabled() ? 'Atom will check for updates automatically' : 'Automatic updates are disabled please check manually'
)
break
case UpdateManager.State.CheckingForUpdate:
updateStatus = $.div({className: 'about-updates-item app-checking-for-updates'},
$.span({className: 'about-updates-label icon icon-search'}, 'Checking for updates...')
)
break
case UpdateManager.State.DownloadingUpdate:
updateStatus = $.div({className: 'about-updates-item app-downloading-update'},
$.span({className: 'loading loading-spinner-tiny inline-block'}),
$.span({className: 'about-updates-label'}, 'Downloading update')
)
break
case UpdateManager.State.UpdateAvailableToInstall:
updateStatus = $.div({className: 'about-updates-item app-update-available-to-install'},
$.span({className: 'about-updates-label icon icon-squirrel'}, 'New update'),
$.span({className: 'about-updates-version'}, this.props.availableVersion),
$.a({className: 'about-updates-release-notes', onclick: this.props.viewUpdateReleaseNotes}, 'Release Notes')
)
break
case UpdateManager.State.UpToDate:
updateStatus = $.div({className: 'about-updates-item app-up-to-date'},
$.span({className: 'icon icon-check'}),
$.span({className: 'about-updates-label is-strong'}, 'Atom is up to date!')
)
break
case UpdateManager.State.Unsupported:
updateStatus = $.div({className: 'about-updates-item app-unsupported'},
$.span({className: 'about-updates-label is-strong'}, 'Your system does not support automatic updates'),
$.a({className: 'about-updates-instructions', onclick: this.props.viewUpdateInstructions}, 'How to update')
)
break
case UpdateManager.State.Error:
updateStatus = $.div({className: 'about-updates-item app-update-error'},
$.span({className: 'icon icon-x'}),
$.span({className: 'about-updates-label app-error-message is-strong'}, this.props.updateManager.getErrorMessage())
)
break
}
return updateStatus
}
render () {
return $.div({className: 'about-updates group-start'},
$.div({className: 'about-updates-box'},
$.div({className: 'about-updates-status'}, this.renderUpdateStatus()),
$.button(
{
className: 'btn about-update-action-button',
disabled: this.shouldUpdateActionButtonBeDisabled(),
onclick: this.executeUpdateAction.bind(this),
style: {
display: this.props.updateManager.state === UpdateManager.State.Unsupported ? 'none' : 'block'
}
},
this.props.updateManager.state === 'update-available' ? 'Restart and install' : 'Check now'
)
),
$.div(
{
className: 'about-auto-updates',
style: {
display: this.props.updateManager.state === UpdateManager.State.Unsupported ? 'none' : 'block'
}
},
$.label({},
$.input(
{
className: 'input-checkbox',
type: 'checkbox',
checked: this.props.updateManager.getAutoUpdatesEnabled(),
onchange: this.handleAutoUpdateCheckbox.bind(this)
}
),
$.span({}, 'Automatically download updates')
)
)
)
}
}

View File

@@ -0,0 +1,57 @@
const etch = require('etch')
/*
Public: Abstract class for handling the initialization
boilerplate of an Etch component.
*/
module.exports =
class EtchComponent {
constructor (props) {
this.props = props
etch.initialize(this)
EtchComponent.setScheduler(atom.views)
}
/*
Public: Gets the scheduler Etch uses for coordinating DOM updates.
Returns a {Scheduler}
*/
static getScheduler () {
return etch.getScheduler()
}
/*
Public: Sets the scheduler Etch uses for coordinating DOM updates.
* `scheduler` {Scheduler}
*/
static setScheduler (scheduler) {
etch.setScheduler(scheduler)
}
/*
Public: Updates the component's properties and re-renders it. Only the
properties you specify in this object will update any other properties
the component stores will be unaffected.
* `props` an {Object} representing the properties you want to update
*/
update (props) {
let oldProps = this.props
this.props = Object.assign({}, oldProps, props)
return etch.update(this)
}
/*
Public: Destroys the component, removing it from the DOM.
*/
destroy () {
etch.destroy(this)
}
render () {
throw new Error('Etch components must implement a `render` method')
}
}

View File

@@ -0,0 +1,96 @@
const {CompositeDisposable} = require('atom')
const semver = require('semver')
const UpdateManager = require('./update-manager')
const About = require('./about')
const StatusBarView = require('./components/about-status-bar')
let updateManager
// The local storage key for the available update version.
const AvailableUpdateVersion = 'about:version-available'
const AboutURI = 'atom://about'
module.exports = {
activate () {
this.subscriptions = new CompositeDisposable()
this.createModel()
let availableVersion = window.localStorage.getItem(AvailableUpdateVersion)
if (atom.getReleaseChannel() === 'dev' || (availableVersion && semver.lte(availableVersion, atom.getVersion()))) {
this.clearUpdateState()
}
this.subscriptions.add(updateManager.onDidChange(() => {
if (updateManager.getState() === UpdateManager.State.UpdateAvailableToInstall) {
window.localStorage.setItem(AvailableUpdateVersion, updateManager.getAvailableVersion())
this.showStatusBarIfNeeded()
}
}))
this.subscriptions.add(atom.commands.add('atom-workspace', 'about:clear-update-state', () => {
this.clearUpdateState()
}))
},
deactivate () {
this.model.destroy()
if (this.statusBarTile) this.statusBarTile.destroy()
if (updateManager) {
updateManager.dispose()
updateManager = undefined
}
},
clearUpdateState () {
window.localStorage.removeItem(AvailableUpdateVersion)
},
consumeStatusBar (statusBar) {
this.statusBar = statusBar
this.showStatusBarIfNeeded()
},
deserializeAboutView (state) {
if (!this.model) {
this.createModel()
}
return this.model.deserialize(state)
},
createModel () {
updateManager = updateManager || new UpdateManager()
this.model = new About({
uri: AboutURI,
currentAtomVersion: atom.getVersion(),
currentElectronVersion: process.versions.electron,
currentChromeVersion: process.versions.chrome,
currentNodeVersion: process.version,
updateManager: updateManager
})
},
isUpdateAvailable () {
let availableVersion = window.localStorage.getItem(AvailableUpdateVersion)
return availableVersion && semver.gt(availableVersion, atom.getVersion())
},
showStatusBarIfNeeded () {
if (this.isUpdateAvailable() && this.statusBar) {
let statusBarView = new StatusBarView()
if (this.statusBarTile) {
this.statusBarTile.destroy()
}
this.statusBarTile = this.statusBar.addRightTile({
item: statusBarView,
priority: -100
})
return this.statusBarTile
}
}
}

View File

@@ -0,0 +1,146 @@
const {Emitter, CompositeDisposable} = require('atom')
const Unsupported = 'unsupported'
const Idle = 'idle'
const CheckingForUpdate = 'checking'
const DownloadingUpdate = 'downloading'
const UpdateAvailableToInstall = 'update-available'
const UpToDate = 'no-update-available'
const ErrorState = 'error'
let UpdateManager = class UpdateManager {
constructor () {
this.emitter = new Emitter()
this.currentVersion = atom.getVersion()
this.availableVersion = atom.getVersion()
this.resetState()
this.listenForAtomEvents()
}
listenForAtomEvents () {
this.subscriptions = new CompositeDisposable()
this.subscriptions.add(
atom.autoUpdater.onDidBeginCheckingForUpdate(() => {
this.setState(CheckingForUpdate)
}),
atom.autoUpdater.onDidBeginDownloadingUpdate(() => {
this.setState(DownloadingUpdate)
}),
atom.autoUpdater.onDidCompleteDownloadingUpdate(({releaseVersion}) => {
this.setAvailableVersion(releaseVersion)
}),
atom.autoUpdater.onUpdateNotAvailable(() => {
this.setState(UpToDate)
}),
atom.autoUpdater.onUpdateError(() => {
this.setState(ErrorState)
}),
atom.config.observe('core.automaticallyUpdate', (value) => {
this.autoUpdatesEnabled = value
this.emitDidChange()
})
)
// TODO: When https://github.com/atom/electron/issues/4587 is closed we can add this support.
// atom.autoUpdater.onUpdateAvailable =>
// @find('.about-updates-item').removeClass('is-shown')
// @updateAvailable.addClass('is-shown')
}
dispose () {
this.subscriptions.dispose()
}
onDidChange (callback) {
return this.emitter.on('did-change', callback)
}
emitDidChange () {
this.emitter.emit('did-change')
}
getAutoUpdatesEnabled () {
return this.autoUpdatesEnabled && this.state !== UpdateManager.State.Unsupported
}
setAutoUpdatesEnabled (enabled) {
return atom.config.set('core.automaticallyUpdate', enabled)
}
getErrorMessage () {
return atom.autoUpdater.getErrorMessage()
}
getState () {
return this.state
}
setState (state) {
this.state = state
this.emitDidChange()
}
resetState () {
this.state = atom.autoUpdater.platformSupportsUpdates() ? atom.autoUpdater.getState() : Unsupported
this.emitDidChange()
}
getAvailableVersion () {
return this.availableVersion
}
setAvailableVersion (version) {
this.availableVersion = version
if (this.availableVersion !== this.currentVersion) {
this.state = UpdateAvailableToInstall
} else {
this.state = UpToDate
}
this.emitDidChange()
}
checkForUpdate () {
atom.autoUpdater.checkForUpdate()
}
restartAndInstallUpdate () {
atom.autoUpdater.restartAndInstallUpdate()
}
getReleaseNotesURLForCurrentVersion () {
return this.getReleaseNotesURLForVersion(this.currentVersion)
}
getReleaseNotesURLForAvailableVersion () {
return this.getReleaseNotesURLForVersion(this.availableVersion)
}
getReleaseNotesURLForVersion (appVersion) {
// Dev versions will not have a releases page
if (appVersion.indexOf('dev') > -1) {
return 'https://atom.io/releases'
}
if (!appVersion.startsWith('v')) {
appVersion = `v${appVersion}`
}
const releaseRepo = appVersion.indexOf('nightly') > -1 ? 'atom-nightly-releases' : 'atom'
return `https://github.com/atom/${releaseRepo}/releases/tag/${appVersion}`
}
}
UpdateManager.State = {
Unsupported: Unsupported,
Idle: Idle,
CheckingForUpdate: CheckingForUpdate,
DownloadingUpdate: DownloadingUpdate,
UpdateAvailableToInstall: UpdateAvailableToInstall,
UpToDate: UpToDate,
Error: ErrorState
}
module.exports = UpdateManager

1806
packages/about/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
{
"name": "about",
"author": "Machisté N. Quintana <mnquintana@users.noreply.github.com>",
"main": "./lib/main",
"version": "1.9.1",
"description": "View useful information about your Atom installation.",
"keywords": [],
"repository": "https://github.com/atom/about",
"license": "MIT",
"scripts": {
"lint": "standard"
},
"engines": {
"atom": ">=1.7 <2.0.0"
},
"dependencies": {
"etch": "0.9.0",
"semver": "^5.5.0"
},
"devDependencies": {
"standard": "^11.0.0"
},
"consumedServices": {
"status-bar": {
"versions": {
"^1.0.0": "consumeStatusBar"
}
}
},
"deserializers": {
"AboutView": "deserializeAboutView"
},
"standard": {
"env": [
"browser",
"node",
"atomtest",
"jasmine"
],
"globals": [
"atom"
]
}
}

View File

@@ -0,0 +1,101 @@
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./helpers/async-spec-helpers') // eslint-disable-line no-unused-vars
describe('About', () => {
let workspaceElement
beforeEach(async () => {
let storage = {}
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
storage[key] = value
})
spyOn(window.localStorage, 'getItem').andCallFake((key) => {
return storage[key]
})
workspaceElement = atom.views.getView(atom.workspace)
await atom.packages.activatePackage('about')
})
it('deserializes correctly', () => {
let deserializedAboutView = atom.deserializers.deserialize({
deserializer: 'AboutView',
uri: 'atom://about'
})
expect(deserializedAboutView).toBeTruthy()
})
describe('when the about:about-atom command is triggered', () => {
it('shows the About Atom view', async () => {
// Attaching the workspaceElement to the DOM is required to allow the
// `toBeVisible()` matchers to work. Anything testing visibility or focus
// requires that the workspaceElement is on the DOM. Tests that attach the
// workspaceElement to the DOM are generally slower than those off DOM.
jasmine.attachToDOM(workspaceElement)
expect(workspaceElement.querySelector('.about')).not.toExist()
await atom.workspace.open('atom://about')
let aboutElement = workspaceElement.querySelector('.about')
expect(aboutElement).toBeVisible()
})
})
describe('when the Atom version number is clicked', () => {
it('copies the version number to the clipboard', async () => {
await atom.workspace.open('atom://about')
let aboutElement = workspaceElement.querySelector('.about')
let versionContainer = aboutElement.querySelector('.atom')
versionContainer.click()
expect(atom.clipboard.read()).toBe(atom.getVersion())
})
})
describe('when the show more link is clicked', () => {
it('expands to show additional version numbers', async () => {
await atom.workspace.open('atom://about')
jasmine.attachToDOM(workspaceElement)
let aboutElement = workspaceElement.querySelector('.about')
let showMoreElement = aboutElement.querySelector('.show-more-expand')
let moreInfoElement = workspaceElement.querySelector('.show-more')
showMoreElement.click()
expect(moreInfoElement).toBeVisible()
})
})
describe('when the Electron version number is clicked', () => {
it('copies the version number to the clipboard', async () => {
await atom.workspace.open('atom://about')
let aboutElement = workspaceElement.querySelector('.about')
let versionContainer = aboutElement.querySelector('.electron')
versionContainer.click()
expect(atom.clipboard.read()).toBe(process.versions.electron)
})
})
describe('when the Chrome version number is clicked', () => {
it('copies the version number to the clipboard', async () => {
await atom.workspace.open('atom://about')
let aboutElement = workspaceElement.querySelector('.about')
let versionContainer = aboutElement.querySelector('.chrome')
versionContainer.click()
expect(atom.clipboard.read()).toBe(process.versions.chrome)
})
})
describe('when the Node version number is clicked', () => {
it('copies the version number to the clipboard', async () => {
await atom.workspace.open('atom://about')
let aboutElement = workspaceElement.querySelector('.about')
let versionContainer = aboutElement.querySelector('.node')
versionContainer.click()
expect(atom.clipboard.read()).toBe(process.version)
})
})
})

View File

@@ -0,0 +1,179 @@
const {it, fit, ffit, fffit, beforeEach, afterEach, conditionPromise} = require('./helpers/async-spec-helpers') // eslint-disable-line no-unused-vars
const MockUpdater = require('./mocks/updater')
describe('the status bar', () => {
let atomVersion
let workspaceElement
beforeEach(async () => {
let storage = {}
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
storage[key] = value
})
spyOn(window.localStorage, 'getItem').andCallFake((key) => {
return storage[key]
})
spyOn(atom, 'getVersion').andCallFake(() => {
return atomVersion
})
workspaceElement = atom.views.getView(atom.workspace)
await atom.packages.activatePackage('status-bar')
await atom.workspace.open('sample.js')
})
afterEach(async () => {
await atom.packages.deactivatePackage('about')
await atom.packages.deactivatePackage('status-bar')
})
describe('on a stable version', function () {
beforeEach(async () => {
atomVersion = '1.2.3'
await atom.packages.activatePackage('about')
})
describe('with no update', () => {
it('does not show the view', () => {
expect(workspaceElement).not.toContain('.about-release-notes')
})
})
describe('with an update', () => {
it('shows the view when the update finishes downloading', () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
expect(workspaceElement).toContain('.about-release-notes')
})
describe('clicking on the status', () => {
it('opens the about page', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
workspaceElement.querySelector('.about-release-notes').click()
await conditionPromise(() => workspaceElement.querySelector('.about'))
expect(workspaceElement.querySelector('.about')).toExist()
})
})
it('continues to show the squirrel until Atom is updated to the new version', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
expect(workspaceElement).toContain('.about-release-notes')
await atom.packages.deactivatePackage('about')
expect(workspaceElement).not.toContain('.about-release-notes')
await atom.packages.activatePackage('about')
await Promise.resolve() // Service consumption hooks are deferred until the next tick
expect(workspaceElement).toContain('.about-release-notes')
await atom.packages.deactivatePackage('about')
expect(workspaceElement).not.toContain('.about-release-notes')
atomVersion = '42.0.0'
await atom.packages.activatePackage('about')
await Promise.resolve() // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes')
})
it('does not show the view if Atom is updated to a newer version than notified', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
await atom.packages.deactivatePackage('about')
atomVersion = '43.0.0'
await atom.packages.activatePackage('about')
await Promise.resolve() // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes')
})
})
})
describe('on a beta version', function () {
beforeEach(async () => {
atomVersion = '1.2.3-beta4'
await atom.packages.activatePackage('about')
})
describe('with no update', () => {
it('does not show the view', () => {
expect(workspaceElement).not.toContain('.about-release-notes')
})
})
describe('with an update', () => {
it('shows the view when the update finishes downloading', () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
expect(workspaceElement).toContain('.about-release-notes')
})
describe('clicking on the status', () => {
it('opens the about page', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
workspaceElement.querySelector('.about-release-notes').click()
await conditionPromise(() => workspaceElement.querySelector('.about'))
expect(workspaceElement.querySelector('.about')).toExist()
})
})
it('continues to show the squirrel until Atom is updated to the new version', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
expect(workspaceElement).toContain('.about-release-notes')
await atom.packages.deactivatePackage('about')
expect(workspaceElement).not.toContain('.about-release-notes')
await atom.packages.activatePackage('about')
await Promise.resolve() // Service consumption hooks are deferred until the next tick
expect(workspaceElement).toContain('.about-release-notes')
await atom.packages.deactivatePackage('about')
expect(workspaceElement).not.toContain('.about-release-notes')
atomVersion = '42.0.0'
await atom.packages.activatePackage('about')
await Promise.resolve() // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes')
})
it('does not show the view if Atom is updated to a newer version than notified', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
await atom.packages.deactivatePackage('about')
atomVersion = '43.0.0'
await atom.packages.activatePackage('about')
await Promise.resolve() // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes')
})
})
})
describe('on a development version', function () {
beforeEach(async () => {
atomVersion = '1.2.3-dev-0123abcd'
await atom.packages.activatePackage('about')
})
describe('with no update', () => {
it('does not show the view', () => {
expect(workspaceElement).not.toContain('.about-release-notes')
})
})
describe('with a previously downloaded update', () => {
it('does not show the view', () => {
window.localStorage.setItem('about:version-available', '42.0.0')
expect(workspaceElement).not.toContain('.about-release-notes')
})
})
})
})

View File

@@ -0,0 +1,65 @@
/** @babel */
const {now} = Date
const {setTimeout} = global
export function beforeEach (fn) {
global.beforeEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
export function afterEach (fn) {
global.afterEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
module.exports[name] = function (description, fn) {
global[name](description, function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
})
export async function conditionPromise (condition) {
const startTime = now()
while (true) {
await timeoutPromise(100)
if (await condition()) {
return
}
if (now() - startTime > 5000) {
throw new Error('Timed out waiting on condition')
}
}
}
export function timeoutPromise (timeout) {
return new Promise(function (resolve) {
setTimeout(resolve, timeout)
})
}
function waitsForPromise (fn) {
const promise = fn()
global.waitsFor('spec promise to resolve', function (done) {
promise.then(done, function (error) {
jasmine.getEnv().currentSpec.fail(error)
done()
})
})
}

View File

@@ -0,0 +1,21 @@
module.exports = {
updateError () {
atom.autoUpdater.emitter.emit('update-error')
},
checkForUpdate () {
atom.autoUpdater.emitter.emit('did-begin-checking-for-update')
},
updateNotAvailable () {
atom.autoUpdater.emitter.emit('update-not-available')
},
downloadUpdate () {
atom.autoUpdater.emitter.emit('did-begin-downloading-update')
},
finishDownloadingUpdate (releaseVersion) {
atom.autoUpdater.emitter.emit('did-complete-downloading-update', {releaseVersion})
}
}

View File

@@ -0,0 +1,22 @@
const UpdateManager = require('../lib/update-manager')
describe('UpdateManager', () => {
let updateManager
beforeEach(() => {
updateManager = new UpdateManager()
})
describe('::getReleaseNotesURLForVersion', () => {
it('returns atom.io releases when dev version', () => {
expect(updateManager.getReleaseNotesURLForVersion('1.7.0-dev-e44b57d')).toContain('atom.io/releases')
})
it('returns the page for the release when not a dev version', () => {
expect(updateManager.getReleaseNotesURLForVersion('1.7.0')).toContain('atom/atom/releases/tag/v1.7.0')
expect(updateManager.getReleaseNotesURLForVersion('v1.7.0')).toContain('atom/atom/releases/tag/v1.7.0')
expect(updateManager.getReleaseNotesURLForVersion('1.7.0-beta10')).toContain('atom/atom/releases/tag/v1.7.0-beta10')
expect(updateManager.getReleaseNotesURLForVersion('1.7.0-nightly10')).toContain('atom/atom-nightly-releases/releases/tag/v1.7.0-nightly10')
})
})
})

View File

@@ -0,0 +1,280 @@
const {shell} = require('electron')
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./helpers/async-spec-helpers') // eslint-disable-line no-unused-vars
const main = require('../lib/main')
const AboutView = require('../lib/components/about-view')
const UpdateView = require('../lib/components/update-view')
const MockUpdater = require('./mocks/updater')
describe('UpdateView', () => {
let aboutElement
let updateManager
let workspaceElement
let scheduler
beforeEach(async () => {
let storage = {}
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
storage[key] = value
})
spyOn(window.localStorage, 'getItem').andCallFake((key) => {
return storage[key]
})
workspaceElement = atom.views.getView(atom.workspace)
await atom.packages.activatePackage('about')
spyOn(atom.autoUpdater, 'getState').andReturn('idle')
spyOn(atom.autoUpdater, 'checkForUpdate')
spyOn(atom.autoUpdater, 'platformSupportsUpdates').andReturn(true)
})
describe('when the About page is open', () => {
beforeEach(async () => {
jasmine.attachToDOM(workspaceElement)
await atom.workspace.open('atom://about')
aboutElement = workspaceElement.querySelector('.about')
updateManager = main.model.state.updateManager
scheduler = AboutView.getScheduler()
})
describe('when the updates are not supported by the platform', () => {
beforeEach(async () => {
atom.autoUpdater.platformSupportsUpdates.andReturn(false)
updateManager.resetState()
await scheduler.getNextUpdatePromise()
})
it('hides the auto update UI and shows the update instructions link', async () => {
expect(aboutElement.querySelector('.about-update-action-button')).not.toBeVisible()
expect(aboutElement.querySelector('.about-auto-updates')).not.toBeVisible()
})
it('opens the update instructions page when the instructions link is clicked', async () => {
spyOn(shell, 'openExternal')
let link = aboutElement.querySelector('.app-unsupported .about-updates-instructions')
link.click()
let args = shell.openExternal.mostRecentCall.args
expect(shell.openExternal).toHaveBeenCalled()
expect(args[0]).toContain('installing-atom')
})
})
describe('when updates are supported by the platform', () => {
beforeEach(async () => {
atom.autoUpdater.platformSupportsUpdates.andReturn(true)
updateManager.resetState()
await scheduler.getNextUpdatePromise()
})
it('shows the auto update UI', () => {
expect(aboutElement.querySelector('.about-updates')).toBeVisible()
})
it('shows the correct panels when the app checks for updates and there is no update available', async () => {
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
MockUpdater.checkForUpdate()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()
MockUpdater.updateNotAvailable()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-up-to-date')).toBeVisible()
expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
})
it('shows the correct panels when the app checks for updates and encounters an error', async () => {
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
MockUpdater.checkForUpdate()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()
spyOn(atom.autoUpdater, 'getErrorMessage').andReturn('an error message')
MockUpdater.updateError()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-update-error')).toBeVisible()
expect(aboutElement.querySelector('.app-error-message').textContent).toBe('an error message')
expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(false)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
})
it('shows the correct panels and button states when the app checks for updates and an update is downloaded', async () => {
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(false)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
MockUpdater.checkForUpdate()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(true)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
MockUpdater.downloadUpdate()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
expect(aboutElement.querySelector('.app-downloading-update')).toBeVisible()
// TODO: at some point it would be nice to be able to cancel an update download, and then this would be a cancel button
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(true)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
MockUpdater.finishDownloadingUpdate('42.0.0')
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-downloading-update')).not.toBeVisible()
expect(aboutElement.querySelector('.app-update-available-to-install')).toBeVisible()
expect(aboutElement.querySelector('.app-update-available-to-install .about-updates-version').textContent).toBe('42.0.0')
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(false)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Restart and install')
})
it('opens the release notes for the downloaded release when the release notes link are clicked', async () => {
MockUpdater.finishDownloadingUpdate('1.2.3')
await scheduler.getNextUpdatePromise()
spyOn(shell, 'openExternal')
let link = aboutElement.querySelector('.app-update-available-to-install .about-updates-release-notes')
link.click()
let args = shell.openExternal.mostRecentCall.args
expect(shell.openExternal).toHaveBeenCalled()
expect(args[0]).toContain('/v1.2.3')
})
it('executes checkForUpdate() when the check for update button is clicked', () => {
let button = aboutElement.querySelector('.about-update-action-button')
button.click()
expect(atom.autoUpdater.checkForUpdate).toHaveBeenCalled()
})
it('executes restartAndInstallUpdate() when the restart and install button is clicked', async () => {
spyOn(atom.autoUpdater, 'restartAndInstallUpdate')
MockUpdater.finishDownloadingUpdate('42.0.0')
await scheduler.getNextUpdatePromise()
let button = aboutElement.querySelector('.about-update-action-button')
button.click()
expect(atom.autoUpdater.restartAndInstallUpdate).toHaveBeenCalled()
})
it("starts in the same state as atom's AutoUpdateManager", async () => {
atom.autoUpdater.getState.andReturn('downloading')
updateManager.resetState()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
expect(aboutElement.querySelector('.app-downloading-update')).toBeVisible()
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(true)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
})
describe('when core.automaticallyUpdate is toggled', () => {
beforeEach(async () => {
expect(atom.config.get('core.automaticallyUpdate')).toBe(true)
atom.autoUpdater.checkForUpdate.reset()
})
it('shows the auto update UI', async () => {
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(true)
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
expect(aboutElement.querySelector('.about-default-update-message').textContent).toBe('Atom will check for updates automatically')
atom.config.set('core.automaticallyUpdate', false)
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(false)
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
expect(aboutElement.querySelector('.about-default-update-message').textContent).toBe('Automatic updates are disabled please check manually')
})
it('updates config and the UI when the checkbox is used to toggle', async () => {
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(true)
aboutElement.querySelector('.about-auto-updates input').click()
await scheduler.getNextUpdatePromise()
expect(atom.config.get('core.automaticallyUpdate')).toBe(false)
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(false)
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
expect(aboutElement.querySelector('.about-default-update-message').textContent).toBe('Automatic updates are disabled please check manually')
aboutElement.querySelector('.about-auto-updates input').click()
await scheduler.getNextUpdatePromise()
expect(atom.config.get('core.automaticallyUpdate')).toBe(true)
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(true)
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
expect(aboutElement.querySelector('.about-default-update-message').textContent).toBe('Atom will check for updates automatically')
})
describe('checking for updates', function () {
afterEach(() => {
this.updateView = null
})
it('checks for update when the about page is shown', () => {
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled()
this.updateView = new UpdateView({
updateManager: updateManager,
availableVersion: '9999.0.0',
viewUpdateReleaseNotes: () => {}
})
expect(atom.autoUpdater.checkForUpdate).toHaveBeenCalled()
})
it('does not check for update when the about page is shown and the update manager is not in the idle state', () => {
atom.autoUpdater.getState.andReturn('downloading')
updateManager.resetState()
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled()
this.updateView = new UpdateView({
updateManager: updateManager,
availableVersion: '9999.0.0',
viewUpdateReleaseNotes: () => {}
})
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled()
})
it('does not check for update when the about page is shown and auto updates are turned off', () => {
atom.config.set('core.automaticallyUpdate', false)
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled()
this.updateView = new UpdateView({
updateManager: updateManager,
availableVersion: '9999.0.0',
viewUpdateReleaseNotes: () => {}
})
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled()
})
})
})
})
})
describe('when the About page is not open and an update is downloaded', () => {
it('should display the new version when it is opened', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
jasmine.attachToDOM(workspaceElement)
await atom.workspace.open('atom://about')
aboutElement = workspaceElement.querySelector('.about')
updateManager = main.model.state.updateManager
scheduler = AboutView.getScheduler()
expect(aboutElement.querySelector('.app-update-available-to-install')).toBeVisible()
expect(aboutElement.querySelector('.app-update-available-to-install .about-updates-version').textContent).toBe('42.0.0')
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(false)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Restart and install')
})
})
})

View File

@@ -0,0 +1,175 @@
@import "ui-variables";
@import "variables";
.about {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
-webkit-user-select: none;
cursor: default;
overflow: auto;
text-align: center;
font-size: 1.25em;
line-height: 1.4;
padding: 4em;
color: @text-color;
background-color: @base-background-color;
button {
cursor: default;
}
a:focus {
// Don't use Bootstrap default here
color: inherit;
}
img, a {
-webkit-user-drag: none;
}
.input-checkbox {
margin-top: -.2em;
}
// used to group different elements
.group-start {
margin-top: 4em;
}
.group-item {
margin-top: 1.5em;
}
}
.about-container {
width: 100%;
max-width: 500px;
}
// Header --------------------------------
.about-atom-io:hover {
.about-logo {
color: @atom-green;
}
}
.about-logo {
display: block;
width: 100%;
max-width: 280px;
margin: 0 auto 1em auto;
color: @text-color-highlight;
transition: color 0.2s;
}
.about-version-container {
&:hover {
color: lighten(@text-color, 15%);
}
&:active {
color: lighten(@text-color, 30%);
}
}
.about-version {
margin-right: .5em;
font-size: 1.25em;
vertical-align: middle;
}
.about-more-version {
color: @text-color-subtle;
font-size: .9em;
}
.about-header-release-notes {
vertical-align: middle;
margin-left: 1em;
}
// Updates --------------------------------
.about-updates {
width: 100%;
max-width: 500px;
}
.about-updates-box {
display: flex;
align-items: center;
padding: @component-padding;
border: 1px solid @base-border-color;
border-radius: @component-border-radius * 2;
background-color: @background-color-highlight;
}
.about-updates-status {
flex: 1;
margin-left: .5em;
text-align: left;
}
.about-updates-item,
.about-default-update-message .about-updates-label {
display: block;
}
.about-updates-label {
color: @text-color-subtle;
&.is-strong {
color: @text-color;
}
}
.about-updates-version {
margin: 0 .4em;
}
.about-updates-release-notes,
.about-updates-instructions {
margin: 0 1em 0 1.5em;
}
.about-auto-updates {
margin-top: 1em;
input {
margin-right: .5em;
}
}
// Love --------------------------------
.about-love {
.icon::before {
// Make these octicons look good inlined with text
position: relative;
width: auto;
height: auto;
margin-right: 0;
font-size: 1.5em;
vertical-align: text-top;
}
.icon-logo-github::before {
font-size: 3.6em;
height: .36em;
}
}
.about-credits {
color: @text-color-subtle;
}
// the blue squirrel --------------------------------
.about-release-notes {
color: @background-color-info;
&:hover {
color: lighten(@background-color-info, 15%);
}
}

View File

@@ -0,0 +1 @@
@atom-green: #40a977;