create and delete studies

pull/1880/head
Thibault Duplessis 2016-05-12 16:21:25 +02:00
parent bd91fc8b08
commit 5d68d8d1ae
12 changed files with 152 additions and 80 deletions

View File

@ -99,6 +99,13 @@ object Study extends LilaController {
}
}
def delete(id: String) = Auth { implicit ctx =>
me =>
env.api.byId(id) flatMap { study =>
study.filter(_ isOwner me.id) ?? env.api.delete
} inject Redirect(routes.Study.byOwner(me.username))
}
def pgn(id: String) = Open { implicit ctx =>
OptionFuResult(env.api byId id) { study =>
CanViewResult(study) {

View File

@ -5,6 +5,11 @@ title = s"Studies created by ${owner.titleName}",
active = "owner",
user = owner) {
<div class="content_box no_padding">
@if(ctx.is(owner)) {
<form class="new_study" action="@routes.Study.create" method="post">
<button type="submit" class="button">New study</button>
</form>
}
<h1>Studies created by @userLink(owner)</h1>
@list(pag, routes.Study.byOwner(owner.username))
</div>

View File

@ -0,0 +1,7 @@
@()(implicit ctx: Context)
@if(ctx.isAuth) {
<form class="new_study" action="@routes.Study.create" method="post">
<button type="submit" class="button">New study</button>
</form>
}

View File

@ -127,6 +127,7 @@ GET /study/$id<\w{8}> controllers.Study.show(id: String)
POST /study controllers.Study.create
GET /study/$id<\w{8}>/socket/v:apiVersion controllers.Study.websocket(id: String, apiVersion: Int)
GET /study/$id<\w{8}>.pgn controllers.Study.pgn(id: String)
POST /study/$id<\w{8}>/delete controllers.Study.delete(id: String)
# Round
GET /$gameId<\w{8}> controllers.Round.watcher(gameId: String, color: String = "white")

View File

@ -14,6 +14,8 @@ final class ChapterRepo(coll: Coll) {
def byId(id: Chapter.ID): Fu[Option[Chapter]] = coll.byId[Chapter](id)
def deleteByStudy(s: Study): Funit = coll.remove($studyId(s.id)).void
def byIdAndStudy(id: Chapter.ID, studyId: Study.ID): Fu[Option[Chapter]] =
coll.byId[Chapter](id).map { _.filter(_.studyId == studyId) }

View File

@ -255,6 +255,10 @@ final class StudyApi(
}
}
def delete(study: Study) = sequenceStudy(study.id) { study =>
studyRepo.delete(study) >> chapterRepo.deleteByStudy(study)
}
private def reloadUid(study: Study, uid: Uid) =
sendTo(study, Socket.ReloadUid(uid))

View File

@ -27,6 +27,8 @@ final class StudyRepo(private[study] val coll: Coll) {
def update(s: Study): Funit = coll.update($id(s.id), s).void
def delete(s: Study): Funit = coll.remove($id(s.id)).void
def membersById(id: Study.ID): Fu[Option[StudyMembers]] =
coll.primitiveOne[StudyMembers]($id(id), "members")

View File

@ -332,3 +332,13 @@ body.dark .glyph_form a:hover {
body.dark .glyph_form a:hover i::before {
background: #303030;
}
form.delete_study {
margin-top: 20px;
}
form.delete_study button {
color: red;
opacity: 0.5;
}
form.delete_study button:hover {
opacity: 1;
}

View File

@ -1,3 +1,7 @@
.new_study {
float: right;
margin: 20px;
}
.studies {
display: flex;
flex-flow: row wrap;

View File

@ -6,6 +6,7 @@ var memberCtrl = require('./studyMembers').ctrl;
var chapterCtrl = require('./studyChapters').ctrl;
var commentFormCtrl = require('./commentForm').ctrl;
var glyphFormCtrl = require('./studyGlyph').ctrl;
var studyFormCtrl = require('./studyForm').ctrl;
var tour = require('./studyTour');
var xhr = require('./studyXhr');
@ -23,10 +24,10 @@ module.exports = {
tab: storedProp('study.tab', 'members'),
behind: false, // false if syncing, else incremental number of missed event
catchingUp: false, // was behind, is syncing back
chapterId: null, // only useful when not synchronized
editing: data.isNew
chapterId: null // only useful when not synchronized
};
var form = studyFormCtrl(send, function() { return data; });
var members = memberCtrl(data.members, ctrl.userId, data.ownerId, send, partial(vm.tab, 'members'));
var chapters = chapterCtrl(data.chapters, send, partial(vm.tab, 'chapters'));
@ -116,6 +117,7 @@ module.exports = {
return {
data: data,
form: form,
members: members,
chapters: chapters,
commentForm: commentForm,
@ -168,10 +170,6 @@ module.exports = {
vm.chapterId = currentChapterId();
}
},
update: function(data) {
send("editStudy", data);
vm.editing = null;
},
anaMoveConfig: function(req) {
if (contributing()) addChapterId(req);
},

View File

@ -0,0 +1,81 @@
var m = require('mithril');
var dialog = require('./dialog');
module.exports = {
ctrl: function(send, getData) {
var initAt = new Date();
function isNew() {
return getData().isNew && new Date() - initAt < 5000;
}
var open = m.prop(isNew());
return {
open: open,
save: function(data) {
send("editStudy", data);
open(false);
},
getData: getData,
isNew: isNew
};
},
view: function(ctrl) {
var data = ctrl.getData();
var isNew = ctrl.isNew();
return dialog.form({
onClose: function() {
ctrl.open(false);
},
content: [
m('h2', (isNew ? 'Create' : 'Edit') + ' study'),
m('form.material.form', {
onsubmit: function(e) {
ctrl.save({
name: e.target.querySelector('#study-name').value,
visibility: e.target.querySelector('#study-visibility').value
});
e.stopPropagation();
return false;
}
}, [
m('div.game.form-group', [
m('input#study-name', {
config: function(el, isUpdate) {
if (!isUpdate && !el.value) {
el.value = data.name;
el.focus();
}
}
}),
m('label.control-label[for=study-name]', 'Name'),
m('i.bar')
]),
m('div.game.form-group', [
m('select#study-visibility', [
['public', 'Public'],
['private', 'Invite only']
].map(function(o) {
return m('option', {
value: o[0],
selected: data.visibility === o[0]
}, o[1]);
})),
m('label.control-label[for=study-visibility]', 'Visibility'),
m('i.bar')
]),
dialog.button(isNew ? 'Start' : 'Save')
]),
m('form.delete_study', {
action: '/study/' + data.id + '/delete',
method: 'post',
onsubmit: function() {
return isNew || confirm('Delete the entire study? There is no going back!');
}
}, m('button.button.frameless', isNew ? 'Cancel' : 'Delete study'))
]
});
}
};

View File

@ -7,55 +7,7 @@ var chapterFormView = require('./chapterForm').view;
var commentFormView = require('./commentForm').view;
var glyphFormView = require('./studyGlyph').view;
var inviteFormView = require('./inviteForm').view;
var dialog = require('./dialog');
function form(ctrl) {
return dialog.form({
onClose: function() {
ctrl.vm.editing = null;
},
content: [
m('h2', 'Edit study'),
m('form.material.form', {
onsubmit: function(e) {
ctrl.update({
name: e.target.querySelector('#study-name').value,
visibility: e.target.querySelector('#study-visibility').value
});
e.stopPropagation();
return false;
}
}, [
m('div.game.form-group', [
m('input#study-name', {
config: function(el, isUpdate) {
if (!isUpdate && !el.value) {
el.value = ctrl.data.name;
el.focus();
}
}
}),
m('label.control-label[for=study-name]', 'Name'),
m('i.bar')
]),
m('div.game.form-group', [
m('select#study-visibility', [
['public', 'Public'],
['private', 'Invite only']
].map(function(o) {
return m('option', {
value: o[0],
selected: ctrl.data.visibility === o[0]
}, o[1]);
})),
m('label.control-label[for=study-visibility]', 'Visibility'),
m('i.bar')
]),
dialog.button(ctrl.data.isNew ? 'Start' : 'Save')
])
]
});
}
var studyFormView = require('./studyForm').view;
function contextAction(icon, text, handler) {
return m('a.action', {
@ -95,61 +47,60 @@ function currentComments(ctrl, includingMine) {
}));
}
function buttons(ctrl) {
var path = ctrl.vm.path;
var node = ctrl.vm.node;
var canContribute = ctrl.study.members.canContribute();
function buttons(root) {
var ctrl = root.study;
var canContribute = ctrl.members.canContribute();
return m('div.study_buttons', [
m('div.member_buttons', [
m('span#study-sync.hint--top', {
'data-hint': ctrl.study.vm.behind !== false ? 'Synchronize with other players' : 'Disconnect to play local moves'
'data-hint': ctrl.vm.behind !== false ? 'Synchronize with other players' : 'Disconnect to play local moves'
}, m('a', (function() {
var attrs = {
onclick: ctrl.study.toggleSync
onclick: ctrl.toggleSync
};
var classes = ['button'];
if (ctrl.study.vm.behind > 0) {
attrs['data-count'] = ctrl.study.vm.behind;
if (ctrl.vm.behind > 0) {
attrs['data-count'] = ctrl.vm.behind;
classes.push('data-count');
}
if (ctrl.study.vm.behind !== false) classes.push('glowed');
if (ctrl.vm.behind !== false) classes.push('glowed');
attrs.class = classes.join(' ');
return attrs;
})(), m('i', {
'data-icon': ctrl.study.vm.behind !== false ? 'G' : 'Z'
'data-icon': ctrl.vm.behind !== false ? 'G' : 'Z'
}))),
m('a.button.hint--top', {
'data-hint': 'Download as PGN',
href: '/study/' + ctrl.study.data.id + '.pgn'
href: '/study/' + ctrl.data.id + '.pgn'
}, m('i[data-icon=x]')),
canContribute ? [
m('a.button.hint--top', {
class: ctrl.study.commentForm.current() ? 'active' : '',
class: ctrl.commentForm.current() ? 'active' : '',
'data-hint': 'Comment this position',
disabled: ctrl.study.vm.behind !== false,
disabled: ctrl.vm.behind !== false,
onclick: function() {
ctrl.study.commentForm.toggle(ctrl.study.currentChapter().id, path, node)
ctrl.commentForm.toggle(ctrl.currentChapter().id, root.vm.path, root.vm.node)
}
}, m('i[data-icon=c]')),
m('a.button.hint--top', {
class: ctrl.study.glyphForm.isOpen() ? 'active' : '',
class: ctrl.glyphForm.isOpen() ? 'active' : '',
'data-hint': 'Annotate with symbols',
disabled: ctrl.study.vm.behind !== false,
onclick: ctrl.study.glyphForm.toggle
disabled: ctrl.vm.behind !== false,
onclick: ctrl.glyphForm.toggle
}, m('i.glyph-icon'))
] : null
]),
m('div', [
ctrl.study.members.isOwner() ?
ctrl.members.isOwner() ?
m('button.button.hint--top', {
class: ctrl.study.members.inviteForm.open() ? 'active' : '',
class: ctrl.members.inviteForm.open() ? 'active' : '',
'data-hint': 'Invite someone',
onclick: ctrl.study.members.inviteForm.toggle
onclick: ctrl.members.inviteForm.toggle
}, m('i[data-icon=r]')) : null,
ctrl.study.members.canContribute() ? m('button.button.hint--top', {
class: ctrl.study.chapters.form.vm.open ? 'active' : '',
ctrl.members.canContribute() ? m('button.button.hint--top', {
class: ctrl.chapters.form.vm.open ? 'active' : '',
'data-hint': 'Add a chapter',
onclick: ctrl.study.chapters.form.toggle
onclick: ctrl.chapters.form.toggle
}, m('i[data-icon=O]')) : null
])
]);
@ -173,7 +124,7 @@ module.exports = {
makeTab('chapters', 'Chapters'),
ctrl.members.isOwner() ? m('a.more', {
onclick: function() {
ctrl.vm.editing = !ctrl.vm.editing;
ctrl.form.open(!ctrl.form.open());
}
}, m('i', {
'data-icon': '['
@ -210,7 +161,7 @@ module.exports = {
overboard: function(ctrl) {
if (ctrl.chapters.form.vm.open) return chapterFormView(ctrl.chapters.form);
if (ctrl.members.inviteForm.open()) return inviteFormView(ctrl.members.inviteForm);
if (ctrl.vm.editing) return form(ctrl);
if (ctrl.form.open()) return studyFormView(ctrl.form);
},
underboard: function(ctrl) {