Skip to content
Open

Next #14

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
341 changes: 227 additions & 114 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"nanoanimation": "^2.0.1",
"nanocomponent": "^6.4.2",
"nanostate": "^1.0.3",
"vibedrive-sdk": "file:../../modules/vibedrive-sdk",
"vibedrive-sdk": "file:../sdk",
"xhr": "^2.4.0"
}
}
107 changes: 107 additions & 0 deletions src/components/dropdown.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
var html = require('choo/html')
var Component = require('Nanocomponent')
var caret = require('../elements/caret.el')
var nanobus = require('nanobus')

function Dropdown () {
if (!(this instanceof Dropdown)) return new Dropdown()
Component.call(this)
this.bus = nanobus()
this.on = this.bus.on.bind(this.bus)
this.visible = false
this.selected = null
this.dropdownEl = null

this.open = this.open.bind(this)
this.close = this.close.bind(this)
this._handleClick = this._handleClick.bind(this)
}

Dropdown.prototype = Object.create(Component.prototype)

Dropdown.prototype.createElement = function dropdownElement (state, emit) {
this.emit = emit

var { options, selected } = state

this.options = options
this.selected = this.selected || selected

if (!this.dropdownEl) {
this.dropdownEl = dropdown.call(this, this.options)
}

return html`
<div
onclick=${e => this.visible ? this.close(e) : this.open(e.currentTarget)}
class="us-none hover-b--black h2 fw6 bg-black white pv1 ph3 f7 br2 mh2 flex flex-row justify-start items-center">
<span>${this.selected.label}</span>
${caret()}
${this.dropdownEl}
</div>`
}

function dropdown (options) {
return html`
<div
id="dropdown"
class="dropdown mt4 z-2 fixed top-0 left-0 pa1 black flex items-start flex-column">
<div class="flex flex-column w-100">
${options.map(option => html`
<div class="dropdown-item" onclick=${e => this.selectItem(option)}>
${option.label}
</div>
`)}
</div>
</div>
`
}

Dropdown.prototype.selectItem = function (option) {
this.selected = option
this.rerender()
}

Dropdown.prototype.open = function (selectEl) {
var rect = selectEl.getBoundingClientRect()

this.width = selectEl.offsetWidth
this.left = rect.left
this.top = rect.top
this.visible = true
this.dropdownEl.style = `left: ${this.left}px; top: ${this.top}px; width: ${this.width}px;`

if (this.dropdownEl.classList.contains('hidden')) {
this.dropdownEl.classList.remove('hidden')
}

document.body.addEventListener('mousedown', this._handleClick)

this.rerender()
}

Dropdown.prototype.close = function (e) {
e.preventDefault()

this.visible = false

if (!this.dropdownEl.classList.contains('hidden')) {
this.dropdownEl.classList.add('hidden')
}

document.body.removeEventListener('mousedown', this._handleClick)
this.rerender()
}

Dropdown.prototype._handleClick = function (e) {
if (!this.dropdownEl.contains(e.target)) {
this.close(e)
document.body.removeEventListener('click', this.handleClick)
}
}

Dropdown.prototype.update = function () {
return false
}

module.exports = Dropdown
2 changes: 1 addition & 1 deletion src/components/player.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class PlayerComponent extends Component {
if (!this.track) return html`<nav></nav>`
var { metadata } = this.track
return html`
<nav class="z-1 flex items-center w-100 h3 justify-between fixed bottom-0 bg-black ph3">
<nav class="bw1 ba b--pure-black z-1 flex items-center w-100 h3 justify-between fixed bottom-0 bg-black ph3">

<div class="flex items-center w-30 ">

Expand Down
168 changes: 168 additions & 0 deletions src/components/playlist-filter-popover.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
var html = require('choo/html')
var assert = require('assert')
var Component = require('Nanocomponent')
var Dropdown = require('./dropdown.component')
var nanoclass = require('../lib/nanoclass')

var fieldTypes = [{
name: 'text',
operators: [ 'is empty', 'is not empty' ]
}]

var fields = [{
label: 'Genre',
name: 'genre',
type: 'text'
}, {
label: 'Artist',
name: 'artist',
type: 'text'
}, {
label: 'Title',
name: 'title',
type: 'text'
}, {
label: 'BPM',
name: 'bpm',
type: 'number'
}]

var operators = [{
label: 'contains',
name: 'contains'
}, {
label: 'does not contain',
name: 'doesNotContain'
}, {
label: 'is empty',
name: 'isEmpty'
}, {
label: 'is not empty',
name: 'isNotEmpty'
}]

function PlaylistFiltersPopover () {
if (!(this instanceof PlaylistFiltersPopover)) return new PlaylistFiltersPopover()
Component.call(this)

this.visible = false
this.left = 0
this.top = 0
this.filters = [PlaylistFilterItem()]
this.toggleVisibility = this.toggleVisibility.bind(this)
this.addFilter = this.addFilter.bind(this)
this.removeFilter = this.removeFilter.bind(this)
this.close = this.close.bind(this)
}

PlaylistFiltersPopover.prototype = Object.create(Component.prototype)

PlaylistFiltersPopover.prototype.createElement = function (state, emit) {
this.emit = emit

var nc = nanoclass({
'hidden': !this.visible
})

var style = `left: ${this.left}px; top: ${this.top}px;`

return html`
<div style=${style} class="${nc} shadow1 bw1 ba b--black mt4 z-2 fixed top-0 left-0 pa1 bg-near-black flex items-start flex-column"
style="min-width: 25rem">
<div class="mb2 flex flex-column">
${this.filters.map(playlistFilterItem => playlistFilterItem.render())}
</div>
<button class="hover-b--blue bg-none blue f6 fw6 mv2 mh1 ph3 pv2"
onclick=${this.addFilter}>
+ Add Filter
</button>
</div>`
}

PlaylistFiltersPopover.prototype.update = function () {
return false
}

PlaylistFiltersPopover.prototype.toggleVisibility = function (e) {
assert.ok(e && e instanceof window.Event, 'toggleVisibility expects an Event as an argument')
this.visible = !this.visible

if (this.visible) {
var rect = e.currentTarget.getBoundingClientRect()

this.left = rect.left
this.top = rect.top
}

this.rerender()
}

PlaylistFiltersPopover.prototype.addFilter = function () {
var playlistFilter = PlaylistFilterItem()

playlistFilter.remove = () => this.removeFilter(playlistFilter)

this.filters.push(playlistFilter)
this.rerender()

// REPLACE emit render with an emit that changes the current playlist, which
// will then emit a render
this.emit('render')
}

PlaylistFiltersPopover.prototype.removeFilter = function (filter) {
var idx = this.filters.findIndex(f => f === filter)
this.filters.splice(idx, 1)
this.rerender()

// REPLACE emit render with an emit that changes the current playlist, which
// will then emit a render
this.emit('render')
}

PlaylistFiltersPopover.prototype.close = function () {
this.visible = false
this.rerender()
}

module.exports = PlaylistFiltersPopover

function PlaylistFilterItem () {
if (!(this instanceof PlaylistFilterItem)) return new PlaylistFilterItem()
Component.call(this)

this.remove = function () {}

this.field = {
label: 'Genre',
name: 'genre',
type: 'text'
}
this.operator = 'contains'
this.value = ''
}

PlaylistFilterItem.prototype = Object.create(Component.prototype)

PlaylistFilterItem.prototype.createElement = function playlistFilterItemElement () {
this.fieldEl = Dropdown()
this.operatorEl = Dropdown()
this.valueEl = Dropdown()

return html`
<div class="flex flex-row mv2">
<button class="scale bg-none white fw6" onclick=${e => this.remove()}>
×
</button>
<div class="flex flex-row">
${this.fieldEl.render({ options: fields, selected: this.field })}
${this.operatorEl.render({ options: operators, selected: this.operator })}
<input class="h2 fw6 bg-black white pv1 ph3 f7 br2 mh2" type="text"
value=${this.value}>
</div>
</div>`
}

PlaylistFilterItem.prototype.update = function () {
return false
}
77 changes: 77 additions & 0 deletions src/components/tree-leaf.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
var html = require('choo/html')
var Component = require('nanocomponent')

function TreeLeaf (parent) {
if (!(this instanceof TreeLeaf)) return new TreeLeaf(parent)

Component.call(this)
}

TreeLeaf.prototype = Object.create(Component.prototype)

TreeLeaf.prototype.createElement = function (state, emit) {
return html`
<div class="playlist-group w-100">
${html`
<div tabindex="0" class="${!this.parent ? 'hidden' : ''} playlist-group-item flex flex-row ph1 pv2 w-100">
${paddingBlocks(this.parent)}
${toggleIcon.call(this, this.open)}
<div class="w-100 tl mh1">${this.name}</div>
</div>`
}
<div class="playlist-group-children ${this.open ? '' : 'hidden'}">
${this.subgroups.map((group, i) => group.render(this._subgroups[i], emit))}
${this.playlists.map(p => html`
<div tabindex=0 class="playlist-group-item playlist-item flex flex-row pa1 w-100">
${paddingBlocks(this.parent, this.parent ? 1 : 0)}
${playlistIcon()}
${p.name}
</div>
`)}
</div>
</div>`

function paddingBlocks (parent, extra = 0) {
var n = numberOfParentsRecursive(parent, extra)

return html`
<div class="flex flex-row">
${Array(n).fill(0).map(x => html`<div class="w1 h1 mh1"></div>`)}
</div>`
}

function numberOfParentsRecursive (parent = {}, i = 0) {
return parent.parent
? numberOfParentsRecursive(parent.parent, i + 1)
: i
}

function toggleIcon (toggled) {
return html`
<button class="w1 h1 mr2 bg-none scale" onclick=${e => this.toggle()}>
${toggled
? html`<svg class="ic-white w1 h1" viewBox="0 0 8 8">
<use xlink:href="icons/openiconic.svg#si-open-chevron-bottom"></use>
</svg>`
: html`<svg class="ic-white w1 h1" viewBox="0 0 8 8">
<use xlink:href="icons/openiconic.svg#si-open-chevron-right"></use>
</svg>`
}
</button>`
}

function playlistIcon () {
return html`
<div class="playlist-group-icon w1 h1 mh2">
<svg class="ic-white w1 h1" viewBox="0 0 8 8">
<use xlink:href="icons/openiconic.svg#si-open-excerpt"></use>
</svg>
</div>`
}
}

TreeLeaf.prototype.update = function (state, emit) {
return state.subgroups !== this._subgroups
}

module.exports = TreeLeaf
Loading