Skip to content

Commit

Permalink
Re-enable tags management based on the taxonomy instead of IndexedDB (f…
Browse files Browse the repository at this point in the history
…ixes #90, fixes #98). r=gmarty (#150)
  • Loading branch information
azasypkin committed May 25, 2016
1 parent 5f075ff commit 5043faf
Show file tree
Hide file tree
Showing 27 changed files with 465 additions and 238 deletions.
16 changes: 7 additions & 9 deletions app/css/components/tag-list.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@
}

.tag-list__item {
display: flex;
position: relative;

padding-left: 4rem;
justify-content: space-between;
}

.tag-list__item-checkbox {
position: absolute;
right: 0;
top: 1.5rem;
width: 2rem;
height: 2rem;
margin: 0;
.tag-list__item-remove {
width: 1rem;

border-radius: .2rem;
border: 1px solid black;
border: none;
background: url(../icons/remove.svg) center center no-repeat;
}
1 change: 1 addition & 0 deletions app/css/icons/remove.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion app/css/icons/rename.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/css/icons/tag.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions app/css/views/service-tags.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.add-tag-button {
padding: 0 7rem;

color: #000;
text-align: left;

background: url(../icons/plus.svg) 3.5rem no-repeat;
background-size: 2rem;
border: none;
filter: brightness(0);
-webkit-filter: brightness(0);
}
5 changes: 5 additions & 0 deletions app/css/views/service.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.service__edit-tags-link {
width: 3rem;
height: 3rem;
padding-left: 1rem;
}
22 changes: 6 additions & 16 deletions app/css/views/services/default.css
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
/*
img.rename {
width: 3rem;
height: 4.2rem;
.default-service__body {
box-sizing: border-box;
padding: 2rem;
text-align: center;
}
*/

.add-tag-button {
padding: 0 7rem;

color: #000;
text-align: left;

background: url(../../icons/plus.svg) 3.5rem no-repeat;
background-size: 2rem;
border: none;
filter: brightness(0);
-webkit-filter: brightness(0);
.default-service__notice {
margin: 0;
}
2 changes: 2 additions & 0 deletions app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
<link rel="stylesheet" href="css/components/service-list.css">
<link rel="stylesheet" href="css/components/tag-list.css">
<link rel="stylesheet" href="css/views/services-list.css">
<link rel="stylesheet" href="css/views/service-tags.css">
<link rel="stylesheet" href="css/views/service.css">
<link rel="stylesheet" href="css/views/services/default.css">
<link rel="stylesheet" href="css/views/services/ip-camera.css">
<link rel="stylesheet" href="css/views/user-login.css">
Expand Down
2 changes: 2 additions & 0 deletions app/js/controllers/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RoutingController } from 'components/mvc';
import UsersController from 'js/controllers/users';
import ServicesController from 'js/controllers/services';
import ServiceController from 'js/controllers/service';
import ServiceTagsController from 'js/controllers/service-tags';
import ThemesController from 'js/controllers/themes';
import DevController from 'js/controllers/dev';

Expand All @@ -21,6 +22,7 @@ export default class MainController extends RoutingController {
'': usersController,
'users/(.+)': usersController,
'services': new ServicesController(options),
'services/(.+)/tags': new ServiceTagsController(options),
'services/(.+)': new ServiceController(options),
'themes': themesController,
'themes/(.+)': themesController,
Expand Down
14 changes: 14 additions & 0 deletions app/js/controllers/service-tags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'components/react';
import ReactDOM from 'components/react-dom';
import { Controller } from 'components/mvc';

import ServiceTagsView from 'js/views/service-tags';

export default class ServiceTagsController extends Controller {
main(id) {
ReactDOM.render(React.createElement(ServiceTagsView, {
id,
foxbox: this.foxbox,
}), this.mountNode);
}
}
14 changes: 14 additions & 0 deletions app/js/lib/foxbox/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ export default class API {
.then(() => this[p.net].fetchJSON(this[p.getURL](path), 'PUT', body));
}

/**
* Performs HTTP 'DELETE' API request and accepts JSON as response.
*
* @param {string} path Specific API resource path to be used in conjunction
* with the base API path.
* @param {Object=} body Optional object that will be serialized to JSON
* string and sent as 'DELETE' body.
* @return {Promise}
*/
delete(path, body) {
return this[p.onceReady]()
.then(() => this[p.net].fetchJSON(this[p.getURL](path), 'DELETE', body));
}

/**
* Performs either HTTP 'GET' or 'PUT' (if body parameter is specified) API
* request and accepts Blob as response.
Expand Down
43 changes: 2 additions & 41 deletions app/js/lib/foxbox/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const DB_NAME = 'foxbox-db';
const DB_VERSION = 1;

const DB_SERVICE_STORE = 'services';
const DB_TAG_STORE = 'tags';

export default class Db {
constructor() {
Expand All @@ -46,19 +45,7 @@ export default class Db {
const req = indexedDB.open(DB_NAME, DB_VERSION);

req.onupgradeneeded = this[p.upgradeSchema];
req.onsuccess = (evt) => {
this[p.db].resolve(evt.target.result);

// Prepopulate the tags with common values.
this.getTags()
.then((tags) => {
if (!tags || !tags.length) {
this.setTag({ name: 'Kitchen' });
this.setTag({ name: 'Bedroom' });
this.setTag({ name: 'Living room' });
}
});
};
req.onsuccess = (evt) => this[p.db].resolve(evt.target.result);
req.onerror = (error) => this[p.db].reject(error);
} catch(error) {
this[p.db].reject(error);
Expand Down Expand Up @@ -93,54 +80,28 @@ export default class Db {
return this[p.getAll](DB_SERVICE_STORE);
}

getTags() {
return this[p.getAll](DB_TAG_STORE);
}

getService(id) {
return this[p.getById](DB_SERVICE_STORE, id);
}

getTag(id) {
return this[p.getById](DB_TAG_STORE, id);
}

setService(data) {
return this[p.set](DB_SERVICE_STORE, data);
}

setTag(data) {
return this[p.set](DB_TAG_STORE, data);
}

deleteService(data) {
return this[p.remove](DB_SERVICE_STORE, data.id);
}

deleteTag(data) {
return this[p.remove](DB_TAG_STORE, data);
}

clearServices() {
return this[p.clearDb](DB_SERVICE_STORE);
}

clearTags() {
return this[p.clearDb](DB_TAG_STORE);
}

[p.upgradeSchema](evt) {
const db = evt.target.result;
const fromVersion = evt.oldVersion;
if (fromVersion < 1) {
let store = db.createObjectStore(DB_SERVICE_STORE, { keyPath: 'id' });
const store = db.createObjectStore(DB_SERVICE_STORE, { keyPath: 'id' });
store.createIndex('id', 'id', { unique: true });

store = db.createObjectStore(DB_TAG_STORE, {
keyPath: 'id',
autoIncrement: true,
});
store.createIndex('name', 'name', { unique: true });
}
}

Expand Down
8 changes: 0 additions & 8 deletions app/js/lib/foxbox/foxbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,14 +259,6 @@ export default class Foxbox extends Service {
this[p.settings].session = null;
}

getTags() {
return this[p.db].getTags.apply(this[p.db], arguments);
}

setTag() {
return this[p.db].setTag.apply(this[p.db], arguments);
}

/**
* Ask the user for accepting push notifications from the box.
* This method will be call each time that we log in, but will
Expand Down
2 changes: 1 addition & 1 deletion app/js/lib/foxbox/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export default class Network extends EventDispatcher {
cache: 'no-store',
};

if (method === 'POST' || method === 'PUT') {
if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
req.headers['Content-Type'] = 'application/json;charset=UTF-8';
}

Expand Down
2 changes: 2 additions & 0 deletions app/js/lib/foxbox/recipes.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export default class Recipes {
id: getter.id,
kind: getter.kind,
name: name || getter.adapter,
tags: getter.tags,
options,
};
});
Expand Down Expand Up @@ -304,6 +305,7 @@ export default class Recipes {
id: setter.id,
kind: setter.kind,
name: name || setter.adapter,
tags: setter.tags,
options,
};
});
Expand Down
7 changes: 7 additions & 0 deletions app/js/lib/foxbox/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ const isSimilar = (objectA, objectB) => {
return valueA !== valueB;
}

if (Array.isArray(valueA)) {
// We don't sort arrays here since changed order likely means that array
// has changed.
return valueA.length !== valueB.length ||
valueA.some((itemA, index) => itemA !== valueB[index]);
}

return !isSimilar(valueA, valueB);
});
};
Expand Down
76 changes: 76 additions & 0 deletions app/js/lib/foxbox/services/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const p = Object.freeze({
model: Symbol('model'),
name: Symbol('name'),
watchers: Symbol('watchers'),
tags: Symbol('tags'),
channels: Symbol('channels'),

// Private methods.
Expand All @@ -38,6 +39,7 @@ export default class BaseService extends EventDispatcher {
// Some service don't have name, but can have product_name instead.
this[p.name] = props.properties && props.properties.name ||
props.properties.product_name || '';
this[p.tags] = new Set(props.tags);
this[p.channels] = props.channels;
this[p.watchers] = watchers || new Map();
}
Expand Down Expand Up @@ -150,6 +152,80 @@ export default class BaseService extends EventDispatcher {
this[p.api].unwatch(getterId, wrappedHandler);
}

/**
* Returns list of service tags.
*
* @return {Array<string>}
*/
getTags() {
// Return a copy of the set in the form of plain array, to avoid side
// modifications.
return Array.from(this[p.tags]);
}

/**
* Adds specified tag to the service/all its channels tag list.
*
* @param {string} tag Tag to add.
* @return {Promise}
*/
addTag(tag) {
if (!tag || typeof tag !== 'string') {
throw new Error('Tag should be valid non-empty string.');
}

if (this[p.tags].has(tag)) {
return Promise.resolve();
}

this[p.tags].add(tag);

// For now we mark channels with the specified tag as well, so that tag can
// be picked up from the places that don't have access to the service
// instance (eg. recipes view).
const servicesSelector = { services: { id: this[p.id] }, tags: tag };
const channelsSelector = { channels: { service: this[p.id] }, tags: tag };

return Promise.all([
this[p.api].post('services/tags', servicesSelector),
this[p.api].post('channels/tags', channelsSelector),
])
.catch((error) => {
this[p.tags].delete(tag);
throw error;
});
}

/**
* Removes specified tag from the service/all its channels tag list.
*
* @param {string} tag Tag to remove.
* @return {Promise}
*/
removeTag(tag) {
if (!tag || typeof tag !== 'string') {
throw new Error('Tag should be valid non-empty string.');
}

if (!this[p.tags].has(tag)) {
return Promise.resolve();
}

this[p.tags].delete(tag);

const servicesSelector = { services: { id: this[p.id] }, tags: tag };
const channelsSelector = { channels: { service: this[p.id] }, tags: tag };

return Promise.all([
this[p.api].delete('services/tags', servicesSelector),
this[p.api].delete('channels/tags', channelsSelector),
])
.catch((error) => {
this[p.tags].add(tag);
throw error;
});
}

/**
* Method that should be called when service instance is not needed anymore.
* Classes that extend BaseService and override this method should always call
Expand Down
Loading

0 comments on commit 5043faf

Please sign in to comment.