mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-07 07:21:17 +08:00
237 lines
5.8 KiB
JavaScript
237 lines
5.8 KiB
JavaScript
import deepEquals from 'deep-equal';
|
|
import { EventEmitter } from 'events';
|
|
|
|
export default class DataStore {
|
|
constructor(db) {
|
|
this.db = db;
|
|
this.currentStory = null;
|
|
this.callbacks = [];
|
|
this.cache = {};
|
|
this.users = {};
|
|
this.user = null;
|
|
|
|
this.eventStore = new EventEmitter();
|
|
}
|
|
|
|
addToCache(currentStory, comments) {
|
|
const key = this.getStoryKey(currentStory);
|
|
this.cache[key] = {
|
|
comments,
|
|
addedAt: Date.now(),
|
|
};
|
|
}
|
|
|
|
getFromCache(currentStory) {
|
|
const key = this.getStoryKey(currentStory);
|
|
const item = this.cache[key];
|
|
|
|
if (!item) {
|
|
return {
|
|
comments: [],
|
|
invalidated: false,
|
|
};
|
|
}
|
|
|
|
const comments = item.comments;
|
|
let invalidated = false;
|
|
|
|
// invalid caches created 60 minutes ago.
|
|
if (Date.now() - item.addedAt > 1000 * 60) {
|
|
delete this.cache[key];
|
|
invalidated = true;
|
|
}
|
|
|
|
return { comments, invalidated };
|
|
}
|
|
|
|
reloadCurrentComments() {
|
|
if (this.stopReloading) {
|
|
clearInterval(this.stopReloading);
|
|
}
|
|
|
|
this.stopReloading = setInterval(
|
|
() => {
|
|
this.loadUsers().then(() => this.loadComments());
|
|
},
|
|
1000 * 60 // Reload for every minute
|
|
);
|
|
}
|
|
|
|
setCurrentStory(sbKind, sbStory) {
|
|
this.currentStory = { sbKind, sbStory };
|
|
|
|
// We don't need to do anything if the there's no loggedIn user.
|
|
// if (!this.user) return;
|
|
|
|
this.reloadCurrentComments();
|
|
const item = this.getFromCache(this.currentStory);
|
|
|
|
if (item) {
|
|
this.fireComments(item.comments);
|
|
// if the cache invalidated we need to load comments again.
|
|
if (item.invalidated) {
|
|
return this.loadUsers().then(() => this.loadComments());
|
|
}
|
|
return Promise.resolve(null);
|
|
}
|
|
|
|
// load comments for the first time.
|
|
// TODO: send a null and handle the loading part in the UI side.
|
|
this.eventStore.emit('loading', true);
|
|
this.fireComments([]);
|
|
this.loadUsers()
|
|
.then(() => this.loadComments())
|
|
.then(() => {
|
|
this.eventStore.emit('loading', false);
|
|
return Promise.resolve(null);
|
|
});
|
|
|
|
return this.currentStory;
|
|
}
|
|
|
|
setCurrentUser(user) {
|
|
this.user = user;
|
|
}
|
|
|
|
loadUsers() {
|
|
const query = {};
|
|
const options = { limit: 1e6 };
|
|
return this.db.persister.getAppInfo().then(info => {
|
|
if (!info) {
|
|
return null;
|
|
}
|
|
return this.db
|
|
.getCollection('users')
|
|
.get(query, options)
|
|
.then(users => {
|
|
this.users = users.reduce((newUsers, user) => {
|
|
const usersObj = {
|
|
...newUsers,
|
|
};
|
|
usersObj[user.id] = user;
|
|
return usersObj;
|
|
}, {});
|
|
});
|
|
});
|
|
}
|
|
|
|
loadComments() {
|
|
const currentStory = { ...this.currentStory };
|
|
const query = currentStory;
|
|
const options = { limit: 1e6 };
|
|
return this.db.persister.getAppInfo().then(info => {
|
|
if (!info) {
|
|
return null;
|
|
}
|
|
return this.db
|
|
.getCollection('comments')
|
|
.get(query, options)
|
|
.then(comments => {
|
|
// add to cache
|
|
this.addToCache(currentStory, comments);
|
|
|
|
// set comments only if we are on the relavant story
|
|
if (deepEquals(currentStory, this.currentStory)) {
|
|
this.fireComments(comments);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
getStoryKey(currentStory) {
|
|
return `${currentStory.sbKind}:::${currentStory.sbStory}`;
|
|
}
|
|
|
|
fireComments(comments) {
|
|
this.callbacks.forEach(callback => {
|
|
// link user to the comment directly
|
|
const commentsWithUser = comments.map(comment =>
|
|
Object.assign({}, comment, { user: this.users[comment.userId] })
|
|
);
|
|
callback(commentsWithUser);
|
|
});
|
|
}
|
|
|
|
onComments(cb) {
|
|
this.callbacks.push(cb);
|
|
const stop = () => {
|
|
const index = this.callbacks.indexOf(cb);
|
|
this.callbacks.splice(index, 1);
|
|
};
|
|
|
|
return stop;
|
|
}
|
|
|
|
addPendingComment(comment) {
|
|
// Add the pending comment.
|
|
const pendingComment = { ...comment, loading: true };
|
|
const { comments: existingComments } = this.getFromCache(this.currentStory);
|
|
const updatedComments = existingComments.concat(pendingComment);
|
|
|
|
this.fireComments(updatedComments);
|
|
return Promise.resolve(null);
|
|
}
|
|
|
|
setDeletedComment(commentId) {
|
|
const { comments } = this.getFromCache(this.currentStory);
|
|
const deleted = comments.find(c => c.id === commentId);
|
|
if (deleted) {
|
|
deleted.loading = true;
|
|
}
|
|
this.fireComments(comments);
|
|
return Promise.resolve(null);
|
|
}
|
|
|
|
addAuthorToTheDatabase() {
|
|
if (this.users[this.user.id]) {
|
|
// user exists in the DB.
|
|
return Promise.resolve(null);
|
|
}
|
|
|
|
// add user to the local cache
|
|
this.users[this.user.id] = this.user;
|
|
|
|
// add user to the actual collection
|
|
return this.db.getCollection('users').set(this.user);
|
|
}
|
|
|
|
// NOTE the "sbProtected" makes sure only the author can modify
|
|
// or delete a comment after its saved on the cloud database.
|
|
addCommentToDatabase(comment) {
|
|
const doc = {
|
|
...comment,
|
|
...this.currentStory,
|
|
...this.currentStory,
|
|
sbProtected: true,
|
|
};
|
|
|
|
return this.db.getCollection('comments').set(doc);
|
|
}
|
|
|
|
deleteCommentOnDatabase(commentId) {
|
|
const query = { id: commentId };
|
|
return this.db.getCollection('comments').del(query);
|
|
}
|
|
|
|
addComment(comment) {
|
|
return this.addAuthorToTheDatabase()
|
|
.then(() => this.addPendingComment(comment))
|
|
.then(() => this.addCommentToDatabase(comment))
|
|
.then(() => this.loadUsers())
|
|
.then(() => this.loadComments());
|
|
}
|
|
|
|
deleteComment(commentId) {
|
|
return this.setDeletedComment(commentId)
|
|
.then(() => this.deleteCommentOnDatabase(commentId))
|
|
.then(() => this.loadComments());
|
|
}
|
|
|
|
onLoading(cb) {
|
|
this.eventStore.on('loading', cb);
|
|
return () => {
|
|
this.eventStore.removeListener('loading', cb);
|
|
};
|
|
}
|
|
}
|