diff --git a/app.js b/app.js
index 7ef7536c..767ecd03 100644
--- a/app.js
+++ b/app.js
@@ -1,56 +1,34 @@
const persist = require('choo-persist')
-const mount = require('choo/mount')
const log = require('choo-log')
const css = require('sheetify')
-const xtend = require('xtend')
const choo = require('choo')
+const xtend = Object.assign
-const params = require('./lib/param-router')
+require('./lib/monkeypatch')
css('dat-colors')
css('tachyons')
css('./public/css/base.css')
css('./public/css/colors.css')
-const opts = {
- filter: (state) => {
- state = xtend(state)
- delete state.repos
- return state
+const app = choo()
+
+app.use(persist({
+ filter: function (state) {
+ return xtend({}, state, { repos: {} })
}
+}))
+
+if (process.env.NODE_ENV === 'development') {
+ app.use(log())
}
-persist(opts, (p) => {
- const app = choo()
- app.use(onError('error:display'))
- app.use(p)
+app.use(require('./models/welcome'))
+app.use(require('./models/drag-drop'))
+app.use(require('./models/repos'))
+app.use(require('./models/error'))
- if (process.env.NODE_ENV === 'development') {
- app.use(log())
- }
+app.route('/', require('./pages/main'))
+
+app.mount('body div')
- app.model(require('./models/main-view')())
- app.model(require('./models/window')())
- app.model(require('./models/repos')())
- app.model(require('./models/error')())
-
- app.router([
- ['/', params({
- default: require('./pages/main')
- })]
- ])
-
- mount('body div', app.start())
-})
-
-function onError (action) {
- return {
- onError: function (err, state, createSend) {
- var send = createSend('handleError')
- send(action, err.message, function (err) {
- // if we hit this point the error handler has failed and we should crash
- if (err) throw err
- })
- }
- }
-}
diff --git a/elements/empty.js b/elements/empty.js
new file mode 100644
index 00000000..c62323dd
--- /dev/null
+++ b/elements/empty.js
@@ -0,0 +1,82 @@
+var html = require('choo/html')
+var css = require('sheetify')
+
+var icon = require('../elements/icon')
+
+module.exports = EmptyState
+
+const skeleton = css`
+ :host {
+ position: relative;
+ .skeleton {
+ position: fixed;
+ top: 3.5rem;
+ left: 1.25rem;
+ width: 232px;
+ max-width: 100vw;
+ }
+ .dotted-lines {
+ position: absolute;
+ top: .25rem;
+ right: 5.5rem;
+ width: 17rem;
+ z-index: 3;
+ }
+ .create-new-dat,
+ .link {
+ position: absolute;
+ width: 15rem;
+ }
+ .create-new-dat {
+ top: 14.5rem;
+ right: 4rem;
+ svg {
+ display: inline-block;
+ width: 2rem;
+ height: 2rem;
+ }
+ }
+ .link {
+ top: 6rem;
+ right: 8.5rem;
+ color: red;
+ svg {
+ display: inline-block;
+ width: 2rem;
+ height: 2rem;
+ margin-bottom: -.75rem;
+ }
+ }
+ }
+`
+
+function EmptyState () {
+ return html`
+
+
+
+
+
+ ${icon('link', { class: 'color-blue-disabled' })}
+
+ Import Dat
+
+
+ Download an existing dataset
+
+ by entering its dat link…
+
+
+
+ ${icon('create-new-dat', { class: 'color-green-disabled' })}
+
Create New Dat
+
+ … or select one of your local
+
+ datasets and start sharing it.
+
+
+
+
+ `
+}
diff --git a/elements/status.js b/elements/status.js
index 27f1e6e2..8c908509 100644
--- a/elements/status.js
+++ b/elements/status.js
@@ -79,7 +79,7 @@ const progressSubline = css`
}
`
-module.exports = function (dat, stats, send) {
+module.exports = function (dat, stats) {
if (dat.owner && dat.importer) {
return html`
@@ -128,7 +128,7 @@ function tableElement (dats, send) {
${dats.map(function (dat) {
- return row(dat, send)
+ return row(dat, emit)
})}
@@ -136,7 +136,7 @@ function tableElement (dats, send) {
`
}
-function row (dat, send) {
+function row (dat, emit) {
const stats = dat.stats && dat.stats.get()
var peers = (dat.network)
? dat.network.connected
@@ -153,7 +153,7 @@ function row (dat, send) {
: 'stale'
: 'paused'
- const togglePause = () => send('repos:togglePause', dat)
+ const togglePause = () => emit('toggle pause', dat)
const hexContent = {
loading: button.icon('loading', {
@@ -181,13 +181,13 @@ function row (dat, send) {
var finderButton = button.icon('Open in Finder', {
icon: icon('open-in-finder'),
class: 'row-action',
- onclick: () => send('repos:open', dat)
+ onclick: () => emit('open dat', dat)
})
var linkButton = button.icon('Share Dat', {
icon: icon('link'),
class: 'row-action',
- onclick: () => send('repos:share', dat)
+ onclick: () => emit('share dat', dat)
})
var deleteButton = button.icon('Remove Dat', {
@@ -203,7 +203,7 @@ function row (dat, send) {
target = target.parentNode
}
assert.equal(typeof id, 'string', 'elements/table.deleteButton: id should be type string')
- send('repos:remove', { key: id })
+ emit('remove dat', { key: id })
}
})
@@ -236,7 +236,7 @@ function row (dat, send) {
- ${status(dat, stats, send)}
+ ${status(dat, stats)}
|
${stats.size}
diff --git a/elements/welcome.js b/elements/welcome.js
new file mode 100644
index 00000000..a9d96307
--- /dev/null
+++ b/elements/welcome.js
@@ -0,0 +1,36 @@
+var html = require('choo/html')
+var css = require('sheetify')
+
+var button = require('./button')
+
+module.exports = WelcomeScreen
+
+const welcome = css`
+ :host {
+ height: 100vh;
+ background-color: var(--color-neutral);
+ color: var(--color-white);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ -webkit-app-region: drag;
+ }
+`
+
+function WelcomeScreen (methods) {
+ const onexit = methods.onexit
+ const onLoad = methods.onload
+
+ return html`
+
+
+
+ Share data on the distributed web.
+
+ ${button.green('Get Started', { onclick: onexit })}
+
+ `
+}
+
diff --git a/index.js b/index.js
index 4755088c..729f4497 100644
--- a/index.js
+++ b/index.js
@@ -4,7 +4,7 @@ const window = require('electron-window')
const Env = require('envobj')
const path = require('path')
const doctor = require('dat-doctor')
-const { Writable } = require('stream')
+const Writable = require('stream').Writable
const autoUpdater = require('./lib/auto-updater')
const colors = require('dat-colors')
diff --git a/lib/dat-manager.js b/lib/dat-manager.js
new file mode 100644
index 00000000..67bdb4f4
--- /dev/null
+++ b/lib/dat-manager.js
@@ -0,0 +1,145 @@
+const ipc = require('electron').ipcRenderer
+const encoding = require('dat-encoding')
+const assert = require('assert')
+const mkdirp = require('mkdirp')
+const waterfall = require('run-waterfall')
+
+module.exports = createManager
+
+// creates a wrapper for all dats. Handles stats, and updates choo's internal
+// state whenever a mutation happens
+function createManager ({ multidat, dbPaused, downloadsDir }, onupdate) {
+ assert.ok(multidat, 'models/repos: multidat should exist')
+ assert.ok(onupdate, 'models/repos: onupdate should exist')
+
+ // add stats to all recreated dats
+ var dats = multidat.list()
+ dats.forEach(initDat)
+ onupdate(null, dats)
+
+ return {
+ create: create,
+ close: close,
+ closeAll: closeAll,
+ pause: pause,
+ resume: resume,
+ togglePause: togglePause
+ }
+
+ function create (dir, opts, cb) {
+ if (!cb) {
+ cb = opts
+ opts = {}
+ }
+
+ assert.equal(typeof dir, 'string', 'dat-manager: dir should be a string')
+ assert.equal(typeof opts, 'object', 'dat-manager: opts should be a object')
+ assert.equal(typeof cb, 'function', 'dat-manager: cb should be a function')
+
+ mkdirp(dir, function (err) {
+ if (err) return cb(err)
+
+ opts = Object.assign(opts, {
+ watch: true,
+ resume: true,
+ ignoreHidden: true,
+ compareFileContent: true
+ })
+
+ multidat.create(dir, opts, function (err, dat) {
+ if (err) return cb(err)
+ initDat(dat)
+ update()
+ cb(null, dat)
+ })
+ })
+ }
+
+ function close (key, cb) {
+ dbPaused.write(key, false, function (err) {
+ if (err) return cb(err)
+ multidat.close(key, function (err) {
+ if (err) return cb(err)
+ update()
+ cb()
+ })
+ })
+ }
+
+ function closeAll (cb) {
+ var tasks = multidat.list().map(function (dat, i) {
+ return function () {
+ var done = arguments[arguments.length - 1]
+ dat.close(done)
+ }
+ })
+ waterfall(tasks, cb)
+ }
+
+ function pause (dat, cb) {
+ var key = encoding.toStr(dat.key)
+ dat.leaveNetwork()
+ dbPaused.write(key, true, cb)
+ }
+
+ function resume (dat, cb) {
+ var key = encoding.toStr(dat.key)
+ dat.joinNetwork()
+ dbPaused.write(key, false, cb)
+ }
+
+ function togglePause (dat, cb) {
+ var key = encoding.toStr(dat.key)
+ dbPaused.read(function (err, paused) {
+ if (err) return cb(err)
+ if (paused[key]) resume(dat, cb)
+ else pause(dat, cb)
+ })
+ }
+
+ function update () {
+ var dats = multidat.list().slice()
+ dats.forEach(function (dat) {
+ var stats = dat.stats && dat.stats.get()
+ dat.progress = (!stats)
+ ? 0
+ : (stats.blocksTotal)
+ ? Math.min(1, stats.blocksProgress / stats.blocksTotal)
+ : 0
+ })
+
+ var incomplete = dats.filter(function (dat) {
+ return dat.network && dat.progress < 1
+ })
+ var progress = incomplete.length
+ ? incomplete.reduce(function (acc, dat) {
+ return acc + dat.progress
+ }, 0) / incomplete.length
+ : 1
+ if (progress === 1) progress = -1 // deactivate
+
+ ipc.send('progress', progress)
+ onupdate(null, dats)
+ }
+
+ function initDat (dat) {
+ const key = encoding.toStr(dat.key)
+ dbPaused.read((err, paused) => {
+ if (err) throw err
+ if (!paused[key]) {
+ dat.joinNetwork()
+ }
+ })
+
+ dat.metadata = {}
+
+ const updates = multidat.readManifest(dat)
+ updates.on('manifest', function (manifest) {
+ dat.metadata.title = manifest.title
+ dat.metadata.author = manifest.author
+ update()
+ })
+
+ dat.on('update', update)
+ }
+}
diff --git a/lib/monkeypatch.js b/lib/monkeypatch.js
new file mode 100644
index 00000000..75753d92
--- /dev/null
+++ b/lib/monkeypatch.js
@@ -0,0 +1,9 @@
+// required for electron integration tests
+if (process.env.RUNNING_IN_SPECTRON) {
+ const dialog = require('electron').remote.dialog
+ const path = require('path')
+
+ dialog.showOpenDialog = (opts, cb) => {
+ return [path.join(__dirname, '..', 'tests', 'fixtures')]
+ }
+}
diff --git a/lib/param-router.js b/lib/param-router.js
deleted file mode 100644
index 6e0846b1..00000000
--- a/lib/param-router.js
+++ /dev/null
@@ -1,20 +0,0 @@
-var assert = require('assert')
-
-module.exports = router
-
-function router (routes) {
- assert.equal(typeof routes, 'object', 'param-router: routes should be type object')
- assert.equal(typeof routes.default, 'function', 'param-router: routes.default should be type function')
-
- return function (state, prev, send) {
- var searchKeys = Object.keys(state.location.search)
- var len = searchKeys.length
- for (var i = 0; i < len; i++) {
- var key = searchKeys[i]
- var route = routes[key]
- if (route) return route.call(routes, state, prev, send)
- }
-
- return routes.default(state, prev, send)
- }
-}
diff --git a/models/drag-drop.js b/models/drag-drop.js
new file mode 100644
index 00000000..fbd094a3
--- /dev/null
+++ b/models/drag-drop.js
@@ -0,0 +1,24 @@
+const explain = require('explain-error')
+const fs = require('fs')
+
+module.exports = dragDropModel
+
+function dragDropModel (state, bus) {
+ window.ondragover = function (e) {
+ e.preventDefault()
+ }
+ window.ondrop = function (e) {
+ e.preventDefault()
+ var dirname = e.dataTransfer &&
+ e.dataTransfer.files &&
+ e.dataTransfer.files[0] &&
+ e.dataTransfer.files[0].path
+ if (!dirname) return
+ fs.stat(dirname, (err, stat) => {
+ if (err) return bus.emit('error', explain(err, 'models/window: fs.stat error on dirname'))
+ if (!stat.isDirectory()) return
+ bus.emit('create dat', dirname)
+ })
+ }
+}
+
diff --git a/models/error.js b/models/error.js
index e056942f..770e350f 100644
--- a/models/error.js
+++ b/models/error.js
@@ -3,31 +3,15 @@ const xhr = require('xhr')
const version = require('../package.json').version
const Modal = require('../elements/modal')
-module.exports = model
+module.exports = errorModel
-function model () {
- return {
- namespace: 'error',
- state: {
- message: null
- },
- subscriptions: {
- onUncaughtException: onUncaughtException
- },
- effects: {
- display: display,
- quit: quit
- }
- }
-}
-
-function display (state, error, send, done) {
- const message = error.message || error
- const modal = Modal.error()(message)
- document.body.appendChild(modal)
-}
+function errorModel (state, bus) {
+ bus.on('error', function (err) {
+ const message = err.message || err
+ const modal = Modal.error()(message)
+ document.body.appendChild(modal)
+ })
-function onUncaughtException (send, done) {
process.on('uncaughtException', function (err) {
if (err._thrown) return
@@ -48,8 +32,8 @@ function onUncaughtException (send, done) {
if (err) console.error(err)
})
- const modal = Modal.crash()(() => {
- send('error:quit', done)
+ const modal = Modal.crash()(function () {
+ ipc.sendSync('quit')
})
document.body.appendChild(modal)
@@ -58,6 +42,3 @@ function onUncaughtException (send, done) {
})
}
-function quit (state, data, send, done) {
- ipc.sendSync('quit')
-}
diff --git a/models/main-view.js b/models/main-view.js
deleted file mode 100644
index 6c7be88e..00000000
--- a/models/main-view.js
+++ /dev/null
@@ -1,31 +0,0 @@
-var assert = require('assert')
-
-module.exports = createModel
-
-function createModel (cb) {
- return {
- namespace: 'mainView',
- effects: {
- loadWelcomeScreenPerhaps: loadWelcomeScreenPerhaps
- },
- state: {
- welcome: true
- },
- reducers: {
- toggleWelcomeScreen: toggleWelcomeScreen
- }
- }
-}
-
-function loadWelcomeScreenPerhaps (state, action, send, done) {
- send('repos:shareState', function (err, reposState) {
- if (err) return done(err)
- if (reposState.values.length) return done()
- send('mainView:toggleWelcomeScreen', { toggle: true }, done)
- })
-}
-
-function toggleWelcomeScreen (state, data, action) {
- assert.equal(typeof data.toggle, 'boolean', 'models/main-view: toggle should be a boolean')
- return { welcome: data.toggle }
-}
diff --git a/models/repos.js b/models/repos.js
index 45683b58..bbbbdf99 100644
--- a/models/repos.js
+++ b/models/repos.js
@@ -10,318 +10,141 @@ const Multidat = require('multidat')
const minimist = require('minimist')
const Worker = require('dat-worker')
const toilet = require('toiletdb')
-const assert = require('assert')
const mkdirp = require('mkdirp')
+const assert = require('assert')
+const xtend = require('xtend')
const path = require('path')
const Modal = require('../elements/modal')
+const createManager = require('../lib/dat-manager')
-if (process.env.RUNNING_IN_SPECTRON) {
- dialog.showOpenDialog = (opts, cb) => {
- return [path.join(__dirname, '..', 'tests', 'fixtures')]
- }
-}
+var argv = minimist(remoteProcess.argv.slice(2))
+var downloadsDir = (argv.data)
+ ? argv.data
+ : path.join(app.getPath('downloads'), '/dat')
-module.exports = createModel
+module.exports = reposModel
-function createModel () {
- let manager = null
- let dbPaused = null
+function reposModel (state, bus) {
+ state.repos = xtend({
+ downloadsDir: downloadsDir,
+ removalKey: null,
+ ready: false,
+ values: []
+ }, state.repos)
- const argv = minimist(remoteProcess.argv.slice(2))
- const downloadsDir = (argv.data)
- ? argv.data
- : path.join(app.getPath('downloads'), '/dat')
+ var manager = null
- return {
- namespace: 'repos',
- state: {
- downloadsDir: downloadsDir,
- removalKey: null,
- ready: false,
- values: []
- },
- subscriptions: {
- start: startMultidat,
- onIpc: handleIpc
- },
- reducers: {
- ready: multidatReady,
- update: updateDats
- },
- effects: {
- create: createDat,
- remove: removeDat,
- open: openDirectory,
- share: shareDat,
- clone: cloneDat,
- shareState: shareState,
- togglePause: togglePause
- }
+ function onerror (err) {
+ if (err) bus.emit('error', err)
}
// boot multidat, create the ~/Downloads/dat directory
- function startMultidat (send, done) {
- const dbLocation = argv.db || path.join(process.env.HOME, '.dat-desktop')
- const dbMultidriveFile = path.join(dbLocation, 'dats.json')
- const dbPausedFile = path.join(dbLocation, 'paused.json')
+ var dbLocation = argv.db || path.join(process.env.HOME, '.dat-desktop')
+ var dbMultidriveFile = path.join(dbLocation, 'dats.json')
+ var dbPausedFile = path.join(dbLocation, 'paused.json')
- const tasks = [
- function (next) {
- mkdirp(dbLocation, next)
- },
- function (_, next) {
- mkdirp(downloadsDir, next)
- },
- function (_, next) {
- const dbMultidrive = toilet(dbMultidriveFile)
- dbPaused = toilet(dbPausedFile)
- Multidat(dbMultidrive, {
- dat: Worker,
- stdout: ConsoleStream(),
- stderr: ConsoleStream()
- }, next)
- },
- function (multidat, next) {
- send('repos:ready', function (err) {
- if (err) return next(err)
- next(null, multidat)
- })
- },
- function (multidat, next) {
- manager = createManager(multidat, function (err, dats) {
- if (err) return done(err)
- send('repos:update', dats, next)
+ var tasks = [
+ function (next) {
+ mkdirp(dbLocation, next)
+ },
+ function (_, next) {
+ mkdirp(downloadsDir, next)
+ },
+ function (_, next) {
+ var dbMultidrive = toilet(dbMultidriveFile)
+ Multidat(dbMultidrive, {
+ dat: Worker,
+ stdout: ConsoleStream(),
+ stderr: ConsoleStream()
+ }, next)
+ },
+ function (multidat, done) {
+ var dbPaused = toilet(dbPausedFile)
+ manager = createManager({
+ multidat,
+ dbPaused
+ }, function (err, dats) {
+ if (err) return bus.emit('error', err)
+ state.repos.values = dats
+ state.repos.ready = true
+ bus.emit('render')
+ })
+ window.addEventListener('beforeunload', onBeforeUnload)
+ function onBeforeUnload (ev) {
+ ev.returnValue = false
+ window.removeEventListener('beforeunload', onBeforeUnload)
+ manager.closeAll(function () {
+ app.quit()
})
- window.addEventListener('beforeunload', onBeforeUnload)
- function onBeforeUnload (ev) {
- ev.returnValue = false
- window.removeEventListener('beforeunload', onBeforeUnload)
- manager.closeAll(function () {
- app.quit()
- })
- }
- },
- function (_, next) {
- // show the welcome screen if you start without any dats
- send('mainView:loadWelcomeScreenPerhaps', done)
}
- ]
-
- waterfall(tasks, done)
- }
-
- // share state with external model
- function shareState (state, data, send, done) {
- done(null, state)
- }
-
- // signal multidat is ready to accept values
- function multidatReady (state, data) {
- return { ready: true }
- }
-
- // update the values with then new values from the manager
- function updateDats (state, data) {
- return { values: data }
- }
+ bus.emit('repos loaded')
+ done()
+ }
+ ]
- // handle IPC events from the server
- function handleIpc (send, done) {
- ipc.on('link', (event, url) => {
- const key = encoding.decode(url)
- send('repos:clone', key, done)
- })
- ipc.on('log', (ev, str) => console.log(str))
- ipc.send('ready')
- }
+ waterfall(tasks, onerror)
// open the dat archive in the native filesystem explorer
- function openDirectory (state, data, send, done) {
- assert.ok(data.path, 'repos-model.openDirectory: data.path should exist')
- var pathname = 'file://' + path.resolve(data.path)
- shell.openExternal(pathname, done)
- }
+ bus.on('open dat', function (dat) {
+ var pathname = 'file://' + path.resolve(dat.path)
+ shell.openExternal(pathname, onerror)
+ })
// choose a directory and convert it to a dat archive
- function createDat (state, data, send, done) {
- var pathname = data
+ bus.on('create dat', function (pathname) {
if (!pathname) {
var files = dialog.showOpenDialog({
properties: ['openDirectory']
})
- if (!files || !files.length) return done()
+ if (!files || !files.length) return
pathname = files[0]
}
- manager.create(pathname, done)
- }
+ manager.create(pathname, onerror)
+ })
- function removeDat (state, data, send, done) {
- const key = data.key
- const modal = Modal.confirm()(function () {
- dbPaused.write(key, false, function (err) {
- if (err) return done(err)
- manager.close(key, done)
- })
- })
- document.body.appendChild(modal)
- }
-
- // copy a dat share link to clipboard and open a modal
- function shareDat (state, data, send, done) {
- assert.ok(data.key, 'repos-model.shareDat: data.key should exist')
- const encodedKey = encoding.encode(data.key)
- const modal = Modal.link()(encodedKey)
- document.body.appendChild(modal)
- }
-
- function cloneDat (state, data, send, done) {
- assert.ok(Buffer.isBuffer(data) || typeof data === 'string', 'repos-model.cloneDat: data should be a buffer or a string')
+ bus.on('clone dat', function (key) {
+ cloneDat(key)
+ })
+ ipc.on('link', function (event, url) {
+ cloneDat(url)
+ })
+ function cloneDat (_key) {
try {
- var key = encoding.decode(data).toString('hex')
+ var key = encoding.toStr(_key)
} catch (e) {
- return done(new Error("The value you entered doesn't appear to be a valid Dat link"))
- }
-
- mkdirp(state.downloadsDir, function (err) {
- if (err) return done(err)
- var dir = path.join(state.downloadsDir, key)
-
- mkdirp(dir, function (err) {
- if (err) return done(err)
-
- var opts = { key: key }
- manager.create(dir, opts, done)
- })
- })
- }
-
- function togglePause (state, data, send, done) {
- const dat = data
- const key = encoding.toStr(dat.key)
-
- dbPaused.read((err, paused) => {
- if (err) return done(err)
- if (paused[key]) resume()
- else pause()
- })
-
- function resume () {
- dat.joinNetwork()
- dbPaused.write(key, false, done)
+ return onerror(new Error("The value you entered doesn't appear to be a valid Dat link"))
}
- function pause () {
- dat.leaveNetwork()
- dbPaused.write(key, true, done)
- }
+ var dir = path.join(state.repos.downloadsDir, key)
+ var opts = { key: key }
+ manager.create(dir, opts, onerror)
}
- // creates a wrapper for all dats. Handles stats, and updates choo's internal
- // state whenever a mutation happens
- function createManager (multidat, onupdate) {
- assert.ok(multidat, 'models/repos: multidat should exist')
- assert.ok(onupdate, 'models/repos: onupdate should exist')
-
- // add stats to all recreated dats
- var dats = multidat.list()
- dats.forEach(initDat)
- onupdate(null, dats)
-
- return {
- create: create,
- close: close,
- closeAll: closeAll
- }
-
- function create (dir, opts, cb) {
- if (!cb) {
- cb = opts
- opts = {}
- }
-
- assert.equal(typeof dir, 'string', 'models/repos: dat-manager: dir should be a string')
- assert.equal(typeof opts, 'object', 'models/repos: dat-manager: opts should be a object')
- assert.equal(typeof cb, 'function', 'models/repos: dat-manager: cb should be a function')
-
- opts = Object.assign(opts, {
- watch: true,
- resume: true,
- ignoreHidden: true,
- compareFileContent: true
- })
-
- multidat.create(dir, opts, function (err, dat) {
- if (err) return cb(err)
- initDat(dat)
- update()
- cb(null, dat)
- })
- }
-
- function close (key, cb) {
- multidat.close(key, function (err) {
- if (err) return cb(err)
- update()
- cb()
- })
- }
-
- function closeAll (cb) {
- var tasks = multidat.list().map(function (dat, i) {
- return function () {
- var done = arguments[arguments.length - 1]
- dat.close(done)
- }
- })
- waterfall(tasks, cb)
- }
-
- function update () {
- var dats = multidat.list().slice()
- dats.forEach(function (dat) {
- var stats = dat.stats && dat.stats.get()
- dat.progress = (!stats)
- ? 0
- : (stats.blocksTotal)
- ? Math.min(1, stats.blocksProgress / stats.blocksTotal)
- : 0
- })
-
- var incomplete = dats.filter(function (dat) {
- return dat.network && dat.progress < 1
- })
- var progress = incomplete.length
- ? incomplete.reduce(function (acc, dat) {
- return acc + dat.progress
- }, 0) / incomplete.length
- : 1
- if (progress === 1) progress = -1 // deactivate
-
- ipc.send('progress', progress)
- onupdate(null, dats)
- }
-
- function initDat (dat) {
- const key = encoding.toStr(dat.key)
- dbPaused.read((err, paused) => {
- if (err) throw err
- if (!paused[key]) {
- dat.joinNetwork()
- }
- })
+ // copy a dat share link to clipboard and open a modal
+ bus.on('share dat', function (dat) {
+ assert.ok(dat.key, 'repos-model.shareDat: data.key should exist')
+ const encodedKey = encoding.toStr(dat.key)
+ const modal = Modal.link()(encodedKey)
+ document.body.appendChild(modal)
+ })
- dat.metadata = {}
+ bus.on('toggle pause', function (dat) {
+ manager.togglePause(dat, onerror)
+ })
- const updates = multidat.readManifest(dat)
- updates.on('manifest', function (manifest) {
- dat.metadata.title = manifest.title
- dat.metadata.author = manifest.author
- update()
+ bus.on('remove dat', function (dat) {
+ const modal = Modal.confirm()(function () {
+ manager.close(dat.key, function (err) {
+ if (err) return onerror(err)
+ bus.emit('render')
})
+ })
+ document.body.appendChild(modal)
+ })
- dat.on('update', update)
- }
- }
+ // handle IPC events from the server
+ ipc.on('log', (ev, str) => console.log(str))
+ ipc.send('ready')
}
diff --git a/models/welcome.js b/models/welcome.js
new file mode 100644
index 00000000..2a2c9e96
--- /dev/null
+++ b/models/welcome.js
@@ -0,0 +1,20 @@
+var xtend = Object.assign
+
+module.exports = welcomeModel
+
+function welcomeModel (state, bus) {
+ state.welcome = xtend({
+ show: false
+ }, state.welcome)
+
+ bus.on('repos loaded', function () {
+ if (state.repos.values.length) return
+ state.welcome.show = true
+ bus.emit('render')
+ })
+
+ bus.on('hide welcome screen', function () {
+ state.welcome.show = false
+ bus.emit('render')
+ })
+}
diff --git a/models/window.js b/models/window.js
deleted file mode 100644
index 5153d199..00000000
--- a/models/window.js
+++ /dev/null
@@ -1,30 +0,0 @@
-const explain = require('explain-error')
-const fs = require('fs')
-
-module.exports = createModel
-
-function createModel () {
- return {
- namespace: 'window',
- subscriptions: {
- 'window-file-drop': dropFile
- }
- }
-}
-
-function dropFile (send, done) {
- window.ondragover = (e) => e.preventDefault()
- window.ondrop = (e) => {
- e.preventDefault()
- const dirname = e.dataTransfer &&
- e.dataTransfer.files &&
- e.dataTransfer.files[0] &&
- e.dataTransfer.files[0].path
- if (!dirname) return
- fs.stat(dirname, (err, stat) => {
- if (err) return done(explain(err, 'models/window: fs.stat error on dirname'))
- if (!stat.isDirectory()) return
- send('repos:create', dirname, done)
- })
- }
-}
diff --git a/package.json b/package.json
index 257db60a..ea3f4289 100644
--- a/package.json
+++ b/package.json
@@ -8,9 +8,9 @@
"author": "Dat Team",
"dependencies": {
"base-elements": "^2.2.1",
- "choo": "^4.0.0-6",
- "choo-log": "^3.0.1",
- "choo-persist": "^2.0.0",
+ "choo": "^5.0.1",
+ "choo-log": "^5.0.1",
+ "choo-persist": "^3.0.0",
"console-stream": "juliangruber/console-stream#fix/console",
"dat-colors": "^3.3.0",
"dat-doctor": "^1.2.2",
diff --git a/pages/main.js b/pages/main.js
index dd6404ac..e9267642 100644
--- a/pages/main.js
+++ b/pages/main.js
@@ -1,86 +1,26 @@
'use strict'
const html = require('choo/html')
-const css = require('sheetify')
const Header = require('../elements/header')
-const button = require('../elements/button')
const sprite = require('../elements/sprite')
const Table = require('../elements/table')
-const icon = require('../elements/icon')
-
-const skeleton = css`
- :host {
- position: relative;
- .skeleton {
- position: fixed;
- top: 3.5rem;
- left: 1.25rem;
- width: 232px;
- max-width: 100vw;
- }
- .dotted-lines {
- position: absolute;
- top: .25rem;
- right: 5.5rem;
- width: 17rem;
- z-index: 3;
- }
- .create-new-dat,
- .link {
- position: absolute;
- width: 15rem;
- }
- .create-new-dat {
- top: 14.5rem;
- right: 4rem;
- svg {
- display: inline-block;
- width: 2rem;
- height: 2rem;
- }
- }
- .link {
- top: 6rem;
- right: 8.5rem;
- color: red;
- svg {
- display: inline-block;
- width: 2rem;
- height: 2rem;
- margin-bottom: -.75rem;
- }
- }
- }
-`
-
-const welcome = css`
- :host {
- height: 100vh;
- background-color: var(--color-neutral);
- color: var(--color-white);
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- text-align: center;
- -webkit-app-region: drag;
- }
-`
+const Welcome = require('../elements/welcome')
+const Empty = require('../elements/empty')
module.exports = mainView
// render the main view
// (obj, obj, fn) -> html
-function mainView (state, prev, send) {
- const showWelcomeScreen = state.mainView.welcome
+function mainView (state, emit) {
+ const showWelcomeScreen = state.welcome.show
const dats = state.repos.values
const isReady = state.repos.ready
const header = Header({
isReady: isReady,
- oncreate: () => send('repos:create'),
- onimport: (link) => send('repos:clone', link)
+ oncreate: () => emit('create dat'),
+ onimport: (link) => emit('clone dat', link)
})
document.title = 'Dat Desktop'
@@ -90,10 +30,10 @@ function mainView (state, prev, send) {
return html`
${sprite()}
- ${WelcomeScreen({
+ ${Welcome({
onexit: () => {
window.removeEventListener('keydown', captureKeyEvent)
- send('mainView:toggleWelcomeScreen', { toggle: false })
+ emit('hide welcome screen')
},
onload: () => {
window.addEventListener('keydown', captureKeyEvent)
@@ -108,7 +48,7 @@ function mainView (state, prev, send) {
${sprite()}
${header}
- ${EmptyState()}
+ ${Empty()}
`
}
@@ -117,7 +57,7 @@ function mainView (state, prev, send) {
${sprite()}
${header}
- ${Table(dats, send)}
+ ${Table(dats, emit)}
`
@@ -125,53 +65,7 @@ function mainView (state, prev, send) {
const key = e.code
if (key === 'Enter' || key === 'Space') {
window.removeEventListener('keydown', captureKeyEvent)
- send('mainView:toggleWelcomeScreen', { toggle: false })
+ emit('hide welcome screen')
}
}
}
-
-function WelcomeScreen (methods) {
- const onexit = methods.onexit
- const onLoad = methods.onload
-
- return html`
-
-
-
- Share data on the distributed web.
-
- ${button.green('Get Started', { onclick: onexit })}
-
- `
-}
-
-function EmptyState () {
- return html`
-
-
-
-
-
- ${icon('link', { class: 'color-blue-disabled' })}
-
- Import Dat
-
-
- Download an existing dataset
-
- by entering its dat link…
-
-
-
- ${icon('create-new-dat', { class: 'color-green-disabled' })}
- Create New Dat
-
- … or select one of your local
-
- datasets and start sharing it.
-
-
-
-
- `
-}
|