mirror of
https://gitlab.com/biskuteri-cafe/JKomasto2.git
synced 2024-11-20 04:54:50 +01:00
e6fea4c061
(Before this, JKomasto and sometimes the Mastodon web client would get '411 Record Not Found' when submitting the same text after deleting and redrafting. Presumably the Mastodon server caches both whether an idempotency key was fulfilled and which post it leads to, and for some reason it looks up the second and fails.)
314 lines
7.4 KiB
Java
314 lines
7.4 KiB
Java
|
|
import javax.swing.JFrame;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JTree;
|
|
import javax.swing.JOptionPane;
|
|
import javax.swing.BorderFactory;
|
|
import javax.swing.tree.DefaultTreeModel;
|
|
import javax.swing.tree.TreeNode;
|
|
import javax.swing.tree.MutableTreeNode;
|
|
import javax.swing.tree.DefaultMutableTreeNode;
|
|
import javax.swing.tree.DefaultTreeCellRenderer;
|
|
import javax.swing.tree.TreeSelectionModel;
|
|
import javax.swing.event.TreeSelectionListener;
|
|
import javax.swing.event.TreeSelectionEvent;
|
|
import java.awt.Dimension;
|
|
import java.awt.Cursor;
|
|
import java.awt.BorderLayout;
|
|
import java.util.Enumeration;
|
|
import cafe.biskuteri.hinoki.Tree;
|
|
import java.io.IOException;
|
|
|
|
class
|
|
RepliesWindow extends JFrame {
|
|
|
|
private JKomasto
|
|
primaire;
|
|
|
|
private MastodonApi
|
|
api;
|
|
|
|
private PostWindow
|
|
postWindow;
|
|
|
|
// - -%- -
|
|
|
|
private RepliesComponent
|
|
display;
|
|
|
|
// ---%-@-%---
|
|
|
|
public synchronized void
|
|
showFor(String postId)
|
|
{
|
|
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
|
Tree<String> thread = getThread(postId);
|
|
if (thread != null) display.showThread(thread);
|
|
display.setCursor(null);
|
|
if (thread == null) dispose();
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
public synchronized void
|
|
postSelected(Tree<String> post)
|
|
{
|
|
postWindow.readEntity(post);
|
|
postWindow.setVisible(true);
|
|
}
|
|
|
|
private Tree<String>
|
|
getThread(String postId)
|
|
{
|
|
abstract class Handler implements RequestListener {
|
|
|
|
boolean
|
|
failed = false;
|
|
|
|
// -=%=-
|
|
|
|
public void
|
|
connectionFailed(IOException eIo)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
RepliesWindow.this,
|
|
"Failed to fetch post context...."
|
|
+ "\n" + eIo.getMessage()
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
public void
|
|
requestFailed(int httpCode, Tree<String> json)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
RepliesWindow.this,
|
|
"Failed to fetch post context...."
|
|
+ "\n" + json.get("error").value
|
|
+ "\n(HTTP code: " + httpCode + ")"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
}
|
|
|
|
class TopPostIdGetter extends Handler {
|
|
|
|
String
|
|
topPostId;
|
|
|
|
// -=%=-
|
|
|
|
public void
|
|
requestSucceeded(Tree<String> json)
|
|
{
|
|
Tree<String> ancestors = json.get("ancestors");
|
|
if (ancestors.size() == 0) topPostId = postId;
|
|
else topPostId = ancestors.get(0).get("id").value;
|
|
}
|
|
|
|
};
|
|
|
|
class DescendantsGetter extends Handler {
|
|
|
|
Tree<String>
|
|
descendants;
|
|
|
|
// -=%=-
|
|
|
|
public void
|
|
requestSucceeded(Tree<String> json)
|
|
{
|
|
descendants = json.get("descendants");
|
|
}
|
|
|
|
};
|
|
|
|
class PostGetter extends Handler {
|
|
|
|
Tree<String>
|
|
post;
|
|
|
|
// -=%=-
|
|
|
|
public void
|
|
requestSucceeded(Tree<String> json)
|
|
{
|
|
post = json;
|
|
}
|
|
|
|
}
|
|
|
|
TopPostIdGetter phase1 = new TopPostIdGetter();
|
|
api.getPostContext(postId, phase1);
|
|
if (phase1.failed) return null;
|
|
DescendantsGetter phase2 = new DescendantsGetter();
|
|
api.getPostContext(phase1.topPostId, phase2);
|
|
if (phase2.failed) return null;
|
|
PostGetter phase3 = new PostGetter();
|
|
api.getSpecificPost(phase1.topPostId, phase3);
|
|
if (phase3.failed) return null;
|
|
|
|
Tree<String> thread = new Tree<String>();
|
|
phase3.post.key = "top";
|
|
thread.add(phase3.post);
|
|
thread.add(phase2.descendants);
|
|
return thread;
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
RepliesWindow(JKomasto primaire, PostWindow postWindow)
|
|
{
|
|
super("Thread");
|
|
|
|
this.primaire = primaire;
|
|
this.api = primaire.getMastodonApi();
|
|
this.postWindow = postWindow;
|
|
|
|
display = new RepliesComponent(this);
|
|
setContentPane(display);
|
|
setSize(384, 224);
|
|
|
|
setIconImage(primaire.getProgramIcon());
|
|
}
|
|
|
|
}
|
|
|
|
class
|
|
RepliesComponent extends JPanel
|
|
implements TreeSelectionListener {
|
|
|
|
private RepliesWindow
|
|
primaire;
|
|
|
|
private Tree<String>
|
|
thread;
|
|
|
|
// - -%- -
|
|
|
|
private JTree
|
|
tree;
|
|
|
|
// ---%-@-%---
|
|
|
|
public void
|
|
showThread(Tree<String> thread)
|
|
{
|
|
Enumeration<TreeNode> e;
|
|
DefaultMutableTreeNode root;
|
|
TreeItem item;
|
|
item = new TreeItem(thread.get("top"));
|
|
root = new DefaultMutableTreeNode(item);
|
|
for (Tree<String> desc: thread.get("descendants"))
|
|
{
|
|
String target = desc.get("in_reply_to_id").value;
|
|
assert target != null;
|
|
|
|
DefaultMutableTreeNode p = null;
|
|
e = root.breadthFirstEnumeration();
|
|
while (e.hasMoreElements())
|
|
{
|
|
DefaultMutableTreeNode node;
|
|
node = (DefaultMutableTreeNode)e.nextElement();
|
|
item = (TreeItem)node.getUserObject();
|
|
|
|
String postId = item.post.get("id").value;
|
|
if (postId.equals(target))
|
|
{
|
|
p = node;
|
|
break;
|
|
}
|
|
}
|
|
if (p == null)
|
|
{
|
|
assert false;
|
|
/*
|
|
* Besides descendants possibly not being in order,
|
|
* the top of the thread might be deleted and so
|
|
* thread.top gets set to the given post. Which
|
|
* sibling replies aren't replying to, resulting
|
|
* in assertion failure.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
item = new TreeItem(desc);
|
|
p.add(new DefaultMutableTreeNode(item));
|
|
}
|
|
|
|
tree.setModel(new DefaultTreeModel(root));
|
|
for (int o = 0; o < tree.getRowCount(); ++o)
|
|
tree.expandRow(o);
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
public void
|
|
valueChanged(TreeSelectionEvent eT)
|
|
{
|
|
Object selected = eT.getPath().getLastPathComponent();
|
|
assert selected instanceof DefaultMutableTreeNode;
|
|
|
|
TreeItem item = (TreeItem)
|
|
((DefaultMutableTreeNode)selected)
|
|
.getUserObject();
|
|
|
|
primaire.postSelected(item.post);
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
private static class
|
|
TreeItem {
|
|
|
|
public Tree<String>
|
|
post;
|
|
|
|
// -=%=-
|
|
|
|
public String
|
|
toString()
|
|
{
|
|
Post post = new Post(this.post);
|
|
post.resolveApproximateText();
|
|
return post.approximateText;
|
|
}
|
|
|
|
// -=%=-
|
|
|
|
TreeItem(Tree<String> post)
|
|
{
|
|
this.post = post;
|
|
}
|
|
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
RepliesComponent(RepliesWindow primaire)
|
|
{
|
|
this.primaire = primaire;
|
|
|
|
tree = new JTree();
|
|
tree.setBackground(null);
|
|
DefaultTreeCellRenderer renderer;
|
|
renderer = new DefaultTreeCellRenderer();
|
|
renderer.setBackgroundNonSelectionColor(null);
|
|
renderer.setOpenIcon(null);
|
|
renderer.setClosedIcon(null);
|
|
renderer.setLeafIcon(null);
|
|
tree.setCellRenderer(renderer);
|
|
int mode = TreeSelectionModel.SINGLE_TREE_SELECTION;
|
|
tree.getSelectionModel().setSelectionMode(mode);
|
|
tree.addTreeSelectionListener(this);
|
|
tree.setFont(tree.getFont().deriveFont(16f));
|
|
|
|
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
|
|
|
setLayout(new BorderLayout());
|
|
add(tree);
|
|
}
|
|
|
|
}
|