Files: 54f45b9e3a2114fd3c234a8de046349c9a3dab1c / src / components / AppController.js
9168 bytesRaw
1 | import { VotesManager } from '../VotesManager.js'; |
2 | import { IdentityManager } from '../IdentityManager.js'; |
3 | |
4 | class AppController extends HTMLElement { |
5 | constructor() { |
6 | super(); |
7 | } |
8 | connectedCallback() { |
9 | const appDescription = this.msg.value; |
10 | const votesManager = new VotesManager(this.sbot) |
11 | const controllerArea = this.attachShadow({ mode: 'open' }); |
12 | controllerArea.innerHTML = ` |
13 | <style> |
14 | * { |
15 | box-sizing: border-box; |
16 | } |
17 | |
18 | #app { |
19 | font-size: 16px; |
20 | font-family: Inter, 'Helvetica Neue', Arial, Helvetica, sans-serif; |
21 | border-bottom: 1px solid gray; |
22 | width: 100%; |
23 | margin: 0; |
24 | padding: 8px 6px 0 8px; |
25 | } |
26 | |
27 | .bar { |
28 | display: grid; |
29 | grid-template-columns: minmax(0, 1fr) auto; |
30 | padding-bottom: 8px; |
31 | white-space: nowrap; |
32 | } |
33 | |
34 | .info > * { |
35 | text-overflow: ellipsis; |
36 | overflow-x: hidden; |
37 | } |
38 | |
39 | .details { |
40 | padding-bottom: 0; |
41 | overflow-x: hidden; |
42 | height: 0; |
43 | white-space: normal; |
44 | } |
45 | |
46 | #app.expanded .info * { |
47 | height: unset; |
48 | white-space: normal; |
49 | } |
50 | |
51 | #app.expanded .details { |
52 | padding-bottom: 8px; |
53 | height: auto; |
54 | } |
55 | |
56 | .name { |
57 | font-size: 17px; |
58 | line-height: 20px; |
59 | height: 20px; |
60 | font-weight: 600; |
61 | } |
62 | |
63 | #author, |
64 | .time, |
65 | #author-id { |
66 | font-size: 13px; |
67 | line-height: 16px; |
68 | height: 16px; |
69 | } |
70 | |
71 | .time:not(:empty)::before { |
72 | content: 'Published on '; |
73 | color: gray; |
74 | } |
75 | |
76 | #author-id { |
77 | font-family: monospace; |
78 | color: gray; |
79 | } |
80 | |
81 | .details .actions { |
82 | float: right; |
83 | } |
84 | |
85 | .actions { |
86 | display: flex; |
87 | margin-left: 6px; |
88 | } |
89 | |
90 | .actions button { |
91 | margin: 0 2px; |
92 | padding: 6px; |
93 | border: none; |
94 | border-radius: 50%; |
95 | height: 36px; |
96 | background-color: #f8f8f8; |
97 | } |
98 | |
99 | .actions button:hover { |
100 | background-color: #dddddd; |
101 | } |
102 | |
103 | .actions button svg { |
104 | display: block; |
105 | height: 24px; |
106 | width: 24px; |
107 | } |
108 | |
109 | .svghover .onhover { |
110 | display: none; |
111 | } |
112 | |
113 | .svghover:hover path { |
114 | display: none; |
115 | } |
116 | |
117 | .svghover:hover .onhover { |
118 | display: unset; |
119 | } |
120 | |
121 | .hidden { |
122 | display: none; |
123 | } |
124 | |
125 | .count { |
126 | position: relative; |
127 | } |
128 | |
129 | .count[data-count]::before { |
130 | content: attr(data-count); |
131 | position: absolute; |
132 | bottom: 0; |
133 | left: 0; |
134 | font-size: 13px; |
135 | line-height: 13px; |
136 | font-weight: 600; |
137 | padding: 0 0 4px 4px; |
138 | border-top-right-radius: 4px; |
139 | /* color: #ff2f92; */ |
140 | background-color: #f8f8f8; |
141 | background: linear-gradient(45deg, rgba(255,255,255,0) 0%, #f8f8f8 100%); |
142 | } |
143 | |
144 | .count[data-count]:hover::before { |
145 | background: linear-gradient(45deg, rgba(255, 255, 255, 0) 50%, rgba(221, 221, 221, 1) 100%); |
146 | } |
147 | </style> |
148 | <div id="app"> |
149 | <div class="bar"> |
150 | <div class="info"> |
151 | <div class="name">${appDescription.content.name || appDescription.content.mentions[0].name || appDescription.content.comment || ''}</div> |
152 | <div id="author"></div> |
153 | </div> |
154 | <div class="actions"> |
155 | <button title="Like" id="like" class="svghover hidden count"> |
156 | <svg width="24" viewBox="0 0 24 24"> |
157 | <path fill="currentColor" |
158 | d="M12.1,18.55L12,18.65L11.89,18.55C7.14,14.24 4,11.39 4,8.5C4,6.5 5.5,5 7.5,5C9.04,5 10.54,6 11.07,7.36H12.93C13.46,6 14.96,5 16.5,5C18.5,5 20,6.5 20,8.5C20,11.39 16.86,14.24 12.1,18.55M16.5,3C14.76,3 13.09,3.81 12,5.08C10.91,3.81 9.24,3 7.5,3C4.42,3 2,5.41 2,8.5C2,12.27 5.4,15.36 10.55,20.03L12,21.35L13.45,20.03C18.6,15.36 22,12.27 22,8.5C22,5.41 19.58,3 16.5,3Z" /> |
159 | <path class="onhover" fill="currentColor" |
160 | d="M12.67 20.74L12 21.35L10.55 20.03C5.4 15.36 2 12.27 2 8.5C2 5.41 4.42 3 7.5 3C9.24 3 10.91 3.81 12 5.08C13.09 3.81 14.76 3 16.5 3C19.58 3 22 5.41 22 8.5C22 9.93 21.5 11.26 20.62 12.61C20 12.31 19.31 12.11 18.59 12.04C19.5 10.8 20 9.65 20 8.5C20 6.5 18.5 5 16.5 5C14.96 5 13.46 6 12.93 7.36H11.07C10.54 6 9.04 5 7.5 5C5.5 5 4 6.5 4 8.5C4 11.39 7.14 14.24 11.89 18.55L12 18.65L12.04 18.61C12.12 19.37 12.34 20.09 12.67 20.74M17 14V17H14V19H17V22H19V19H22V17H19V14H17Z" /> |
161 | </svg> |
162 | </button> |
163 | <button title="Unlike" id="unlike" class="svghover hidden count"> |
164 | <svg width="24" viewBox="0 0 24 24"> |
165 | <path fill="currentColor" |
166 | d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z" /> |
167 | <path class="onhover" fill="currentColor" |
168 | d="M12 18C12 19 12.25 19.92 12.67 20.74L12 21.35L10.55 20.03C5.4 15.36 2 12.27 2 8.5C2 5.41 4.42 3 7.5 3C9.24 3 10.91 3.81 12 5.08C13.09 3.81 14.76 3 16.5 3C19.58 3 22 5.41 22 8.5C22 9.93 21.5 11.26 20.62 12.61C19.83 12.23 18.94 12 18 12C14.69 12 12 14.69 12 18M14 17V19H22V17H14Z" /> |
169 | </svg> |
170 | </button> |
171 | <button title="Run" id="run"> |
172 | <svg width="24" viewBox="0 0 24 24"> |
173 | <path fill="currentColor" d="M8,5.14V19.14L19,12.14L8,5.14Z" /> |
174 | </svg> |
175 | </button> |
176 | </div> |
177 | </div> |
178 | <div class="details bar"> |
179 | <div class="info"> |
180 | <div class="actions"> |
181 | <button title="Revoke App" id="revoke" class="hidden"> |
182 | <svg width="24" viewBox="0 0 24 24"> |
183 | <path fill="currentColor" |
184 | d="M8.27,3L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3M8.41,7L12,10.59L15.59,7L17,8.41L13.41,12L17,15.59L15.59,17L12,13.41L8.41,17L7,15.59L10.59,12L7,8.41" /> |
185 | </svg> |
186 | </button> |
187 | <button title="View Source" id="source"> |
188 | <svg width="24" viewBox="0 0 24 24"> |
189 | <path fill="currentColor" |
190 | d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M6.12,15.5L9.86,19.24L11.28,17.83L8.95,15.5L11.28,13.17L9.86,11.76L6.12,15.5M17.28,15.5L13.54,11.76L12.12,13.17L14.45,15.5L12.12,17.83L13.54,19.24L17.28,15.5Z" /> |
191 | </svg> |
192 | </button> |
193 | </div> |
194 | <div class="comment">${appDescription.content.comment || ''}</div> |
195 | <div id="author-id">${appDescription.author}</div> |
196 | <div class="time">${(new Date(appDescription.timestamp)).toLocaleString() || ''}</div> |
197 | </div> |
198 | </div> |
199 | </div>` |
200 | |
201 | const appEl = controllerArea.getElementById('app') |
202 | appEl.addEventListener('click', e => { |
203 | appEl.classList.toggle('expanded') |
204 | }) |
205 | |
206 | const renderLikesStuff = () => { |
207 | votesManager.getVotes(this.msg.key).then(likes => { |
208 | const count = likes.length |
209 | if (count > 0) { |
210 | controllerArea.querySelectorAll('.count').forEach(e => e.setAttribute('data-count', count)) |
211 | } else { |
212 | controllerArea.querySelectorAll('.count').forEach(e => e.removeAttribute('data-count')) |
213 | } |
214 | }) |
215 | |
216 | votesManager.getOwnVote(this.msg.key).then(liked => { |
217 | if (liked) { |
218 | this.classList.add('liked') |
219 | controllerArea.getElementById('like').classList.add('hidden') |
220 | controllerArea.getElementById('unlike').classList.remove('hidden') |
221 | } else { |
222 | this.classList.remove('liked') |
223 | controllerArea.getElementById('like').classList.remove('hidden') |
224 | controllerArea.getElementById('unlike').classList.add('hidden') |
225 | } |
226 | }, e => { |
227 | console.log("error getting own vote:", e) |
228 | }) |
229 | } |
230 | renderLikesStuff() |
231 | |
232 | this.sbot.whoami().then( |
233 | currentUser => this.msg.value.author === currentUser.id) |
234 | .then(own => { |
235 | if (own) { |
236 | controllerArea.getElementById('revoke').classList.remove('hidden') |
237 | } |
238 | }) |
239 | |
240 | |
241 | ;(new IdentityManager(this.sbot)).getSelfAssignedName(appDescription.author).then(name => { |
242 | controllerArea.getElementById('author').innerHTML = name |
243 | }).catch(e => console.log(e)); |
244 | |
245 | controllerArea.getElementById('run').addEventListener('click', e => { |
246 | e.stopPropagation() |
247 | this.dispatchEvent(new Event('run')); |
248 | }) |
249 | controllerArea.getRootNode().getElementById('source').addEventListener('click', e => { |
250 | e.stopPropagation() |
251 | this.dispatchEvent(new Event('view-source')); |
252 | }) |
253 | controllerArea.getRootNode().getElementById('like').addEventListener('click', async e => { |
254 | e.stopPropagation() |
255 | await votesManager.vote(this.msg.key, 1) |
256 | renderLikesStuff() |
257 | this.dispatchEvent(new Event('like')) |
258 | }) |
259 | controllerArea.getRootNode().getElementById('unlike').addEventListener('click', async e => { |
260 | e.stopPropagation() |
261 | await votesManager.vote(this.msg.key, 0) |
262 | renderLikesStuff() |
263 | this.dispatchEvent(new Event('unlike')) |
264 | }) |
265 | controllerArea.getRootNode().getElementById('revoke').addEventListener('click', async e => { |
266 | e.stopPropagation() |
267 | if (!confirm("Revoke App? This action cannot be undone.")) { |
268 | return; |
269 | } |
270 | await this.revoke() |
271 | this.parentElement.removeChild(this) |
272 | this.dispatchEvent(new Event('revoked')) |
273 | }) |
274 | } |
275 | |
276 | revoke() { |
277 | return new Promise((resolve, reject) => { |
278 | this.sbot.publish({ |
279 | type: 'about', |
280 | about: this.msg.key, |
281 | status: 'revoked' |
282 | }, function (err, msg) { |
283 | if (err) { |
284 | reject(err) |
285 | } else { |
286 | resolve(true) |
287 | } |
288 | }) |
289 | }) |
290 | } |
291 | } |
292 | |
293 | customElements.define("app-controller", AppController); |
Built with git-ssb-web