Forum Message UI Improvement (Thesis Board #3470) #61
@ -1,7 +1,8 @@
|
||||
package se.su.dsv.scipro.forum;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumThread;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
@ -20,4 +21,6 @@ public interface BasicForumService extends Serializable {
|
||||
List<ForumPost> getPosts(ForumThread forumThread);
|
||||
|
||||
ForumThread createThread(String subject);
|
||||
|
||||
long countUnreadThreads(List<ForumThread> forumThreadList, User user);
|
||||
}
|
||||
|
@ -87,6 +87,11 @@ public class BasicForumServiceImpl implements BasicForumService {
|
||||
return threadRepository.save(forumThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countUnreadThreads(List<ForumThread> forumThreadList, User user) {
|
||||
return postRepository.countUnreadThreads(forumThreadList, user);
|
||||
}
|
||||
|
||||
private ForumPostReadState getReadState(final User user, final ForumPost post) {
|
||||
ForumPostReadState state = readStateRepository.find(user, post);
|
||||
if (state == null) {
|
||||
|
@ -8,6 +8,7 @@ import se.su.dsv.scipro.forum.dataobjects.ProjectThread;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.system.JpaRepository;
|
||||
import se.su.dsv.scipro.system.QueryDslPredicateExecutor;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.util.Pair;
|
||||
|
||||
@Transactional
|
||||
@ -15,4 +16,6 @@ public interface ForumPostRepository extends JpaRepository<ForumPost, Long>, Que
|
||||
List<ForumPost> findByThread(ForumThread forumThread);
|
||||
|
||||
List<Pair<ProjectThread, ForumPost>> latestPost(Project project, int amount);
|
||||
|
||||
long countUnreadThreads(List<ForumThread> forumThreadList, User user);
|
||||
}
|
||||
|
@ -2,19 +2,22 @@ package se.su.dsv.scipro.forum;
|
||||
|
||||
import static com.querydsl.core.types.dsl.Expressions.allOf;
|
||||
|
||||
import com.querydsl.jpa.JPAExpressions;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumThread;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ProjectThread;
|
||||
import se.su.dsv.scipro.forum.dataobjects.QForumPost;
|
||||
import se.su.dsv.scipro.forum.dataobjects.QForumPostReadState;
|
||||
import se.su.dsv.scipro.forum.dataobjects.QForumThread;
|
||||
import se.su.dsv.scipro.forum.dataobjects.QProjectThread;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.system.GenericRepo;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.util.Pair;
|
||||
|
||||
public class ForumPostRepositoryImpl extends GenericRepo<ForumPost, Long> implements ForumPostRepository {
|
||||
@ -44,4 +47,24 @@ public class ForumPostRepositoryImpl extends GenericRepo<ForumPost, Long> implem
|
||||
.map(tuple -> new Pair<>(tuple.get(QProjectThread.projectThread), tuple.get(QForumPost.forumPost)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countUnreadThreads(List<ForumThread> forumThreadList, User user) {
|
||||
return new JPAQuery<>(em())
|
||||
.select(QForumThread.forumThread.id.countDistinct())
|
||||
.from(QForumThread.forumThread)
|
||||
.leftJoin(QForumThread.forumThread.posts, QForumPost.forumPost)
|
||||
.where(
|
||||
QForumPost.forumPost.notIn(
|
||||
JPAExpressions.select(QForumPostReadState.forumPostReadState.id.post)
|
||||
.from(QForumPostReadState.forumPostReadState)
|
||||
.where(
|
||||
QForumPostReadState.forumPostReadState.read.isTrue(),
|
||||
QForumPostReadState.forumPostReadState.id.user.eq(user)
|
||||
)
|
||||
),
|
||||
QForumThread.forumThread.in(forumThreadList)
|
||||
)
|
||||
.fetchOne();
|
||||
}
|
||||
}
|
||||
|
@ -23,5 +23,5 @@ public interface ProjectForumService {
|
||||
// TODO: Get these away from here
|
||||
List<Pair<ProjectThread, ForumPost>> latestPost(Project a, int amount);
|
||||
|
||||
boolean hasUnreadThreads(Project project, User user);
|
||||
long getUnreadThreadsCount(Project project, User user);
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ package se.su.dsv.scipro.forum;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import se.su.dsv.scipro.file.FileSource;
|
||||
import se.su.dsv.scipro.file.ProjectFileService;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
@ -114,14 +115,10 @@ public class ProjectForumServiceImpl implements ProjectForumService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasUnreadThreads(Project project, User user) {
|
||||
public long getUnreadThreadsCount(Project project, User user) {
|
||||
List<ProjectThread> threads = getThreads(project);
|
||||
for (ProjectThread thread : threads) {
|
||||
if (!basicForumService.isThreadRead(user, thread.getForumThread())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
List<ForumThread> list = threads.stream().map(ProjectThread::getForumThread).toList();
|
||||
return basicForumService.countUnreadThreads(list, user);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -105,9 +105,9 @@ public class ProjectForumServiceImplTest extends ForumModuleTest {
|
||||
final ProjectThread thread = service.createThread(project, supervisor, "subject", "content", Set.of());
|
||||
service.createReply(thread, author, "reply", Set.of());
|
||||
|
||||
boolean hasUnreadThreads = service.hasUnreadThreads(project, supervisor);
|
||||
long count = service.getUnreadThreadsCount(project, supervisor);
|
||||
|
||||
assertTrue(hasUnreadThreads);
|
||||
assertEquals(1, count);
|
||||
}
|
||||
|
||||
private void assertNewForumThread(
|
||||
|
@ -6,9 +6,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<wicket:panel>
|
||||
<a wicket:id="toggle" href="#">
|
||||
<span wicket:id="icon" class="fa fa-flag read-state"></span>
|
||||
</a>
|
||||
<a wicket:id="toggle" href="#"><span wicket:id="icon" class="fa fa-flag read-state"></span></a>
|
||||
</wicket:panel>
|
||||
</body>
|
||||
</html>
|
@ -10,10 +10,14 @@ import org.apache.wicket.markup.html.panel.Panel;
|
||||
|
||||
public abstract class AbstractReadStatePanel extends Panel {
|
||||
|
||||
private final Component icon;
|
||||
public static final String TOGGLE = "toggle";
|
||||
static final String ICON = "icon";
|
||||
|
||||
public AbstractReadStatePanel(final String id) {
|
||||
super(id);
|
||||
Component icon = new UpdatingImage(ICON);
|
||||
icon.setOutputMarkupId(true);
|
||||
|
||||
AjaxFallbackLink<Void> link = new AjaxFallbackLink<>(TOGGLE) {
|
||||
@Override
|
||||
public void onClick(final Optional<AjaxRequestTarget> target) {
|
||||
@ -23,20 +27,15 @@ public abstract class AbstractReadStatePanel extends Panel {
|
||||
});
|
||||
}
|
||||
};
|
||||
add(link);
|
||||
|
||||
icon = new UpdatingImage(ICON);
|
||||
icon.setOutputMarkupId(true);
|
||||
link.add(icon);
|
||||
|
||||
add(link);
|
||||
}
|
||||
|
||||
protected abstract boolean isRead();
|
||||
|
||||
protected abstract void onFlagClick(final AjaxRequestTarget target);
|
||||
|
||||
public static final String TOGGLE = "toggle";
|
||||
static final String ICON = "icon";
|
||||
|
||||
private class UpdatingImage extends WebComponent {
|
||||
|
||||
public UpdatingImage(String id) {
|
||||
|
@ -41,6 +41,13 @@
|
||||
</div>
|
||||
|
||||
<table class="table table-striped table-hover" wicket:id="dp"></table>
|
||||
|
||||
<wicket:fragment wicket:id="readStateColumnMarkupId">
|
||||
<span wicket:id="flag"></span>
|
||||
<wicket:enclosure child="counter">
|
||||
(<wicket:container wicket:id="counter"></wicket:container>)
|
||||
</wicket:enclosure>
|
||||
</wicket:fragment>
|
||||
</wicket:panel>
|
||||
</body>
|
||||
</html>
|
@ -5,6 +5,7 @@ import static java.util.Arrays.asList;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.wicket.AttributeModifier;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
|
||||
import org.apache.wicket.ajax.markup.html.form.AjaxCheckBox;
|
||||
@ -14,16 +15,22 @@ import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColu
|
||||
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
|
||||
import org.apache.wicket.extensions.markup.html.repeater.data.table.LambdaColumn;
|
||||
import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.form.EnumChoiceRenderer;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
import org.apache.wicket.markup.html.form.LambdaChoiceRenderer;
|
||||
import org.apache.wicket.markup.html.panel.Fragment;
|
||||
import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.markup.repeater.Item;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LambdaModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import se.su.dsv.scipro.components.*;
|
||||
import se.su.dsv.scipro.components.AjaxCheckBoxMultipleChoice;
|
||||
import se.su.dsv.scipro.components.BootstrapRadioChoice;
|
||||
import se.su.dsv.scipro.components.ExportableDataPanel;
|
||||
import se.su.dsv.scipro.components.ListAdapterModel;
|
||||
import se.su.dsv.scipro.components.TemporalColumn;
|
||||
import se.su.dsv.scipro.components.datatables.MultipleUsersColumn;
|
||||
import se.su.dsv.scipro.components.datatables.UserColumn;
|
||||
import se.su.dsv.scipro.dataproviders.FilteredDataProvider;
|
||||
@ -250,14 +257,21 @@ public class SupervisorMyProjectsPanel extends Panel {
|
||||
|
||||
@Override
|
||||
public void populateItem(Item<ICellPopulator<Project>> item, String id, IModel<Project> projectModel) {
|
||||
item.add(
|
||||
new AbstractReadStatePanel(id) {
|
||||
@Override
|
||||
protected boolean isRead() {
|
||||
return !projectForumService.hasUnreadThreads(
|
||||
// Since table cell only can contain one item, we use Wicket Fragment here. It will contain two components,
|
||||
// one for flag, one for unread messages counter.
|
||||
|
||||
Fragment fragment = new Fragment(id, "readStateColumnMarkupId", SupervisorMyProjectsPanel.this);
|
||||
|
||||
long msgCount = projectForumService.getUnreadThreadsCount(
|
||||
projectModel.getObject(),
|
||||
SciProSession.get().getUser()
|
||||
);
|
||||
boolean isRead = msgCount == 0;
|
||||
|
||||
AbstractReadStatePanel readStatePanel = new AbstractReadStatePanel("flag") {
|
||||
@Override
|
||||
protected boolean isRead() {
|
||||
return isRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -267,8 +281,25 @@ public class SupervisorMyProjectsPanel extends Panel {
|
||||
SupervisorThreadedForumPage.getPageParameters(projectModel.getObject())
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isRead) {
|
||||
readStatePanel.add(new AttributeModifier("title", getString("unread.msg")));
|
||||
}
|
||||
);
|
||||
|
||||
fragment.add(readStatePanel);
|
||||
|
||||
Label counterLabel = new Label("counter", msgCount) {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(msgCount > 0);
|
||||
}
|
||||
};
|
||||
|
||||
fragment.add(counterLabel);
|
||||
|
||||
item.add(fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,3 +12,5 @@ ProjectStatus.COMPLETED= Completed
|
||||
|
||||
SupervisorProjectNoteDisplay.COMPACT=Compact
|
||||
SupervisorProjectNoteDisplay.FULL=Full
|
||||
|
||||
unread.msg=There are unread messages.
|
Loading…
x
Reference in New Issue
Block a user