create and delete studies
parent
bd91fc8b08
commit
5d68d8d1ae
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
.new_study {
|
||||
float: right;
|
||||
margin: 20px;
|
||||
}
|
||||
.studies {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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'))
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue