Skip to content

Commit 8130dc0

Browse files
committed
updated grid.js
1 parent d648bc3 commit 8130dc0

3 files changed

Lines changed: 195 additions & 1 deletion

File tree

‎README.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,8 @@ Zeeschuimer only works in the Firefox browser, so if you don't have that install
7373

7474
You can install Zeeschuimer by navigating to the latest release on [this Github page](https://github.com/digitalmethodsinitiative/zeeschuimer/releases). When in Firefox, you can click on the **.xpi** file and it will automatically install.
7575

76-
A button with the Zeeschuimer logo (<img alt="Zeeschuimer's browser icon, a yellow 'Z' on a green background" src="/zeeschuimer-16.png">) should appear in your browser toolbar (a stylized Z). When you cli
76+
A button with the Zeeschuimer logo (<img alt="Zeeschuimer's browser icon, a yellow 'Z' on a green background" src="/zeeschuimer-16.png">) should appear in your browser toolbar (a stylized Z). When you click on the icon, you will be shown the Zeeschuimer control panel:
77+
78+
(<img alt="Zeeschuimer's control panel" src="/zs.png">)
79+
80+

‎grid.js‎

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
const useArchivedImages = true;
2+
const ndjsonFile = "posts.ndjson";
3+
4+
let allPosts = [];
5+
let hoveredPost = null;
6+
let selectedPost = null;
7+
8+
fetch(ndjsonFile)
9+
.then(res => res.text())
10+
.then(text => {
11+
const lines = text.trim().split(/\r?\n/);
12+
13+
allPosts = lines.map(line => {
14+
const raw = JSON.parse(line);
15+
const data = raw.data || {};
16+
17+
const imageUrl =
18+
data.image_versions2?.candidates?.[0]?.url ||
19+
data.carousel_media?.[0]?.image_versions2?.candidates?.[0]?.url ||
20+
null;
21+
22+
if (!imageUrl) return null;
23+
24+
return {
25+
id: data.id,
26+
code: data.code,
27+
username: data.user?.username || "unknown",
28+
caption: data.caption?.text || "",
29+
following: data.user?.friendship_status?.following || false,
30+
isPaidPartnership: data.is_paid_partnership || false,
31+
likeCount: data.like_count || 0,
32+
commentCount: data.comment_count || 0,
33+
thumbnailUrl: imageUrl,
34+
displayUrl: imageUrl,
35+
annotation1: "",
36+
annotation2: ""
37+
};
38+
}).filter(Boolean);
39+
40+
renderGrid(allPosts);
41+
});
42+
43+
function renderGrid(posts) {
44+
const grid = document.getElementById("grid");
45+
grid.innerHTML = "";
46+
47+
posts.forEach(post => {
48+
const tile = document.createElement("div");
49+
tile.className = "tile";
50+
tile.dataset.id = post.id;
51+
52+
if (selectedPost && selectedPost.id === post.id) {
53+
tile.classList.add("selected");
54+
}
55+
56+
const img = document.createElement("img");
57+
img.src = useArchivedImages
58+
? `images/${post.id}.jpg`
59+
: post.thumbnailUrl;
60+
61+
tile.appendChild(img);
62+
63+
tile.addEventListener("mouseenter", () => {
64+
hoveredPost = post;
65+
renderDetail(post);
66+
});
67+
68+
tile.addEventListener("mouseleave", () => {
69+
hoveredPost = null;
70+
if (selectedPost) renderDetail(selectedPost);
71+
});
72+
73+
tile.addEventListener("click", () => {
74+
selectedPost = post;
75+
updateSelectionOutline();
76+
renderDetail(post);
77+
});
78+
79+
grid.appendChild(tile);
80+
});
81+
}
82+
83+
function updateSelectionOutline() {
84+
document.querySelectorAll(".tile").forEach(t => {
85+
t.classList.remove("selected");
86+
if (selectedPost && t.dataset.id === selectedPost.id) {
87+
t.classList.add("selected");
88+
}
89+
});
90+
}
91+
92+
document.getElementById("filterSelect").addEventListener("change", e => {
93+
const value = e.target.value;
94+
let filtered = allPosts;
95+
96+
if (value === "following") filtered = allPosts.filter(p => p.following);
97+
if (value === "nonfollowing") filtered = allPosts.filter(p => !p.following);
98+
99+
renderGrid(filtered);
100+
});
101+
102+
function renderDetail(post) {
103+
const detail = document.getElementById("detailView");
104+
detail.innerHTML = "";
105+
106+
const postUrl = `https://www.instagram.com/p/${post.code}/`;
107+
108+
const link = document.createElement("a");
109+
link.href = postUrl;
110+
link.target = "_blank";
111+
112+
const img = document.createElement("img");
113+
img.className = "detailImage";
114+
img.src = useArchivedImages
115+
? `images/${post.id}.jpg`
116+
: post.displayUrl;
117+
118+
link.appendChild(img);
119+
detail.appendChild(link);
120+
121+
const meta = document.createElement("div");
122+
meta.className = "metaBlock";
123+
124+
meta.appendChild(makeField("Username", post.username));
125+
meta.appendChild(makeField("Caption", post.caption));
126+
meta.appendChild(makeField("Likes", post.likeCount));
127+
meta.appendChild(makeField("Comments", post.commentCount));
128+
meta.appendChild(makeField("Following", post.following));
129+
meta.appendChild(makeField("Paid Partnership", post.isPaidPartnership));
130+
131+
meta.appendChild(makeAnnotationField("Annotation 1", post, "annotation1"));
132+
meta.appendChild(makeAnnotationField("Annotation 2", post, "annotation2"));
133+
134+
detail.appendChild(meta);
135+
}
136+
137+
function makeField(label, value) {
138+
const wrapper = document.createElement("p");
139+
140+
const title = document.createElement("strong");
141+
title.textContent = label;
142+
143+
const br = document.createElement("br");
144+
145+
const content = document.createElement("span");
146+
content.textContent = value ?? "—";
147+
148+
wrapper.appendChild(title);
149+
wrapper.appendChild(br);
150+
wrapper.appendChild(content);
151+
152+
return wrapper;
153+
}
154+
155+
function makeAnnotationField(label, post, key) {
156+
const wrapper = document.createElement("p");
157+
158+
const title = document.createElement("strong");
159+
title.textContent = label;
160+
161+
const br = document.createElement("br");
162+
163+
const input = document.createElement("textarea");
164+
input.value = post[key] || "";
165+
input.rows = 2;
166+
input.style.width = "100%";
167+
168+
input.addEventListener("input", () => {
169+
post[key] = input.value;
170+
});
171+
172+
wrapper.appendChild(title);
173+
wrapper.appendChild(br);
174+
wrapper.appendChild(input);
175+
176+
return wrapper;
177+
}
178+
179+
document.getElementById("exportBtn").addEventListener("click", () => {
180+
const dataStr = JSON.stringify(allPosts, null, 2);
181+
const blob = new Blob([dataStr], { type: "application/json" });
182+
const url = URL.createObjectURL(blob);
183+
184+
const a = document.createElement("a");
185+
a.href = url;
186+
a.download = "annotated_posts.json";
187+
a.click();
188+
189+
URL.revokeObjectURL(url);
190+
});

‎zs.png‎

414 KB
Loading

0 commit comments

Comments
 (0)