History

Add ability export/import preferences

Closes #671.

Thibaut Courouble committed 9 years ago

Commit 44e63161 parent a16059a8 files · +126 −19Browse files

Unified · Split

8 files
  1. assets/javascripts/app/settings.coffee +41 −15
  2. assets/javascripts/lib/cookie_store.coffee +1 −0
  3. assets/javascripts/news.json +3 −0
  4. assets/javascripts/templates/notif_tmpl.coffee +4 −0
  5. assets/javascripts/templates/pages/settings_tmpl.coffee +6 −1
  6. assets/javascripts/views/content/settings_page.coffee +39 −0
  7. assets/javascripts/views/layout/settings.coffee +14 −3
  8. assets/stylesheets/components/_content.scss +18 −0
assets/javascripts/app/settings.coffee+41−15
@@ -1,9 +1,23 @@
11 class app.Settings
2− DOCS_KEY = 'docs'
3− DARK_KEY = 'dark'
4− LAYOUT_KEY = 'layout'
5− SIZE_KEY = 'size'
6− TIPS_KEY = 'tips'
2+ PREFERENCE_KEYS = [
3+ 'hideDisabled'
4+ 'hideIntro'
5+ 'manualUpdate'
6+ 'fastScroll'
7+ 'arrowScroll'
8+ 'docs'
9+ 'dark'
10+ 'layout'
11+ 'size'
12+ 'tips'
13+ ]
14+
15+ INTERNAL_KEYS = [
16+ 'count'
17+ 'schema'
18+ 'version'
19+ 'news'
20+ ]
721
822 @defaults:
923 count: 0
@@ -32,24 +46,24 @@ class app.Settings · 22 unmodified lines
3246 return
3347
3448 hasDocs: ->
35− try !!@store.get(DOCS_KEY)
49+ try !!@store.get('docs')
3650
3751 getDocs: ->
38− @store.get(DOCS_KEY)?.split('/') or app.config.default_docs
52+ @store.get('docs')?.split('/') or app.config.default_docs
3953
4054 setDocs: (docs) ->
41− @set DOCS_KEY, docs.join('/')
55+ @set 'docs', docs.join('/')
4256 return
4357
4458 getTips: ->
45− @store.get(TIPS_KEY)?.split('/') or []
59+ @store.get('tips')?.split('/') or []
4660
4761 setTips: (tips) ->
48− @set TIPS_KEY, tips.join('/')
62+ @set 'tips', tips.join('/')
4963 return
5064
5165 setLayout: (name, enable) ->
52− layout = (@store.get(LAYOUT_KEY) || '').split(' ')
66+ layout = (@store.get('layout') || '').split(' ')
5367 $.arrayDelete(layout, '')
5468
5569 if enable
@@ -58,22 +72,34 @@ class app.Settings · 2 unmodified lines
5872 $.arrayDelete(layout, name)
5973
6074 if layout.length > 0
61− @set LAYOUT_KEY, layout.join(' ')
75+ @set 'layout', layout.join(' ')
6276 else
63− @del LAYOUT_KEY
77+ @del 'layout'
6478 return
6579
6680 hasLayout: (name) ->
67− layout = (@store.get(LAYOUT_KEY) || '').split(' ')
81+ layout = (@store.get('layout') || '').split(' ')
6882 layout.indexOf(name) isnt -1
6983
7084 setSize: (value) ->
71− @set SIZE_KEY, value
85+ @set 'size', value
7286 return
7387
7488 dump: ->
7589 @store.dump()
7690
91+ export: ->
92+ data = @dump()
93+ delete data[key] for key in INTERNAL_KEYS
94+ data
95+
96+ import: (data) ->
97+ for key, value of @export()
98+ @del key unless data.hasOwnProperty(key)
99+ for key, value of data
100+ @set key, value if PREFERENCE_KEYS.indexOf(key) isnt -1
101+ return
102+
77103 reset: ->
78104 @store.reset()
79105 @cache = {}
assets/javascripts/lib/cookie_store.coffee+1−0
@@ -14,6 +14,7 @@ class @CookieStore · 13 unmodified lines
1414 return
1515
1616 value = 1 if value == true
17+ value = parseInt(value, 10) if value and INT.test?(value)
1718 Cookies.set(key, '' + value, path: '/', expires: 1e8)
1819 @constructor.onBlocked(key, value, @get(key)) if @get(key) != value
1920 return
assets/javascripts/news.json+3−0
@@ -1,5 +1,8 @@
11 [
22 [
3+ "2017-09-10",
4+ "<a href=\"/settings\">Preferences</a> can now be exported and imported."
5+ ], [
36 "2017-09-03",
47 "New documentations: <a href=\"/d/\">D</a>, <a href=\"/nim/\">Nim</a> and <a href=\"/vulkan/\">Vulkan</a>"
58 ], [
assets/javascripts/templates/notif_tmpl.coffee+4−0
@@ -30,6 +30,10 @@ app.templates.notifInvalidLocation = -> · 29 unmodified lines
3030 textNotif """ DevDocs must be loaded from #{app.config.production_host} """,
3131 """ Otherwise things are likely to break. """
3232
33+app.templates.notifImportInvalid = ->
34+ textNotif """ Oops, an error occured. """,
35+ """ The file you selected is invalid. """
36+
3337 app.templates.notifNews = (news) ->
3438 notif 'Changelog', """<div class="_notif-content _notif-news">#{app.templates.newsList(news, years: false)}</div>"""
3539
assets/javascripts/templates/pages/settings_tmpl.coffee+6−1
@@ -35,5 +35,10 @@ app.templates.settingsPage = (settings) -> """ · 34 unmodified lines
3535 </div>
3636 </div>
3737
38− <button type="button" class="_btn-link _reset-btn" data-behavior="reset">Reset all preferences and data</button>
38+ <p class="_hide-on-mobile">
39+ <button type="button" class="_btn" data-action="export">Export</button>
40+ <label class="_btn _file-btn"><input type="file" form="settings" name="import" accept=".json">Import</label>
41+
42+ <p>
43+ <button type="button" class="_btn-link _reset-btn" data-behavior="reset">Reset all preferences and data</button>
3944 """
assets/javascripts/views/content/settings_page.coffee+39−0
@@ -5,6 +5,7 @@ class app.views.SettingsPage extends app.View · 4 unmodified lines
55 @className: '_static'
66
77 @events:
8+ click: 'onClick'
89 change: 'onChange'
910
1011 render: ->
@@ -46,6 +47,34 @@ class app.views.SettingsPage extends app.View · 35 unmodified lines
4647 app.settings.set(name, enable)
4748 return
4849
50+ export: ->
51+ data = new Blob([JSON.stringify(app.settings.export())], type: 'application/json')
52+ link = document.createElement('a')
53+ link.href = URL.createObjectURL(data)
54+ link.download = 'devdocs.json'
55+ link.style.display = 'none'
56+ document.body.appendChild(link)
57+ link.click()
58+ document.body.removeChild(link)
59+ return
60+
61+ import: (file, input) ->
62+ unless file and file.type is 'application/json'
63+ new app.views.Notif 'ImportInvalid', autoHide: false
64+ return
65+
66+ reader = new FileReader()
67+ reader.onloadend = ->
68+ data = try JSON.parse(reader.result)
69+ unless data and data.constructor is Object
70+ new app.views.Notif 'ImportInvalid', autoHide: false
71+ return
72+ app.settings.import(data)
73+ $.trigger input.form, 'import'
74+ return
75+ reader.readAsText(file)
76+ return
77+
4978 onChange: (event) =>
5079 input = event.target
5180 switch input.name
@@ -55,10 +84,20 @@ class app.views.SettingsPage extends app.View · 3 unmodified lines
5584 @toggleLayout input.value, input.checked
5685 when 'smoothScroll'
5786 @toggleSmoothScroll input.checked
87+ when 'import'
88+ @import input.files[0], input
5889 else
5990 @toggle input.name, input.checked
6091 return
6192
93+ onClick: (event) =>
94+ target = $.eventTarget(event)
95+ switch target.getAttribute('data-action')
96+ when 'export'
97+ $.stopEvent(event)
98+ @export()
99+ return
100+
62101 onRoute: (context) ->
63102 @render()
64103 return
assets/javascripts/views/layout/settings.coffee+14−3
@@ -9,6 +9,7 @@ class app.views.Settings extends app.View · 8 unmodified lines
99 backBtn: 'button[data-back]'
1010
1111 @events:
12+ import: 'onImport'
1213 change: 'onChange'
1314 submit: 'onSubmit'
1415 click: 'onClick'
@@ -41,11 +42,16 @@ class app.views.Settings extends app.View · 26 unmodified lines
4142 @addClass '_in'
4243 return
4344
44− save: ->
45+ save: (options = {}) ->
4546 unless @saving
4647 @saving = true
47− docs = @docPicker.getSelectedDocs()
48− app.settings.setDocs(docs)
48+
49+ if options.import
50+ docs = app.settings.getDocs()
51+ else
52+ docs = @docPicker.getSelectedDocs()
53+ app.settings.setDocs(docs)
54+
4955 @saveBtn.textContent = if app.appCache then 'Downloading\u2026' else 'Saving\u2026'
5056 disabledDocs = new app.collections.Docs(doc for doc in app.docs.all() when docs.indexOf(doc.slug) is -1)
5157 disabledDocs.uninstall ->
@@ -66,6 +72,11 @@ class app.views.Settings extends app.View · 14 unmodified lines
6672 @save()
6773 return
6874
75+ onImport: =>
76+ @addClass('_dirty')
77+ @save(import: true)
78+ return
79+
6980 onClick: (event) =>
7081 return if event.which isnt 1
7182 if event.target is @backBtn
assets/stylesheets/components/_content.scss+18−0
@@ -398,12 +398,16 @@ · 397 unmodified lines
398398 }
399399
400400 ._btn {
401+ display: inline-block;
402+ vertical-align: top;
403+ line-height: normal;
401404 white-space: nowrap;
402405 padding: .375rem .675rem;
403406 background-image: linear-gradient(lighten($boxBackground, 4%), darken($boxBackground, 2%));
404407 border: 1px solid $boxBorder;
405408 border-radius: 3px;
406409 box-shadow: 0 1px rgba($boxBorder, .08);
410+ cursor: pointer;
407411
408412 &:active {
409413 background-color: $boxBackground;
@@ -411,6 +415,20 @@ · 1 unmodified line
411415 }
412416 }
413417
418+._file-btn {
419+ position: relative;
420+ overflow: hidden;
421+
422+ > input {
423+ position: absolute;
424+ top: 0;
425+ left: 0;
426+ width: 100%;
427+ height: 100%;
428+ visibility: hidden;
429+ }
430+}
431+
414432 ._btn-link {
415433 line-height: inherit;
416434 color: $linkColor;