tangled
alpha
login
or
join now
t1c.dev
/
rocksky
forked from
rocksky.app/rocksky
2
fork
atom
A decentralized music tracking and discovery platform built on AT Protocol 🎵
2
fork
atom
overview
issues
pulls
pipelines
Refactor header, add Embed, prune CSS
tsiry-sandratraina.com
1 month ago
a1c222ed
39cc7bbf
+107
-122
8 changed files
expand all
collapse all
unified
split
apps
embed
public
styles.css
src
components
Header
Header.tsx
embeds
Embed.tsx
RecentScrobblesEmbedPage.tsx
TopAlbumsEmbedPage.tsx
TopArtistsEmbedPage.tsx
TopTracksEmbedPage.tsx
index.tsx
+9
-63
apps/embed/public/styles.css
···
1455
1455
outline: none;
1456
1456
}
1457
1457
}
1458
1458
-
.mt-\[-2px\] {
1459
1459
-
margin-top: -2px;
1460
1460
-
}
1461
1458
.mt-\[-3px\] {
1462
1459
margin-top: -3px;
1463
1463
-
}
1464
1464
-
.mt-\[-5px\] {
1465
1465
-
margin-top: -5px;
1466
1460
}
1467
1461
.mt-\[-6px\] {
1468
1462
margin-top: -6px;
1469
1463
}
1470
1470
-
.mt-\[5px\] {
1471
1471
-
margin-top: 5px;
1464
1464
+
.mt-\[3px\] {
1465
1465
+
margin-top: 3px;
1472
1466
}
1473
1467
.mt-\[10px\] {
1474
1468
margin-top: 10px;
···
1476
1470
.mt-\[20px\] {
1477
1471
margin-top: 20px;
1478
1472
}
1479
1479
-
.mr-\[5px\] {
1480
1480
-
margin-right: 5px;
1481
1481
-
}
1482
1473
.mr-\[8px\] {
1483
1474
margin-right: 8px;
1484
1475
}
···
1494
1485
.mb-\[5px\] {
1495
1486
margin-bottom: 5px;
1496
1487
}
1497
1497
-
.mb-\[10px\] {
1498
1498
-
margin-bottom: 10px;
1499
1499
-
}
1500
1488
.mb-\[15px\] {
1501
1489
margin-bottom: 15px;
1502
1490
}
···
1505
1493
}
1506
1494
.mb-\[25px\] {
1507
1495
margin-bottom: 25px;
1508
1508
-
}
1509
1509
-
.ml-\[5px\] {
1510
1510
-
margin-left: 5px;
1511
1511
-
}
1512
1512
-
.ml-\[10px\] {
1513
1513
-
margin-left: 10px;
1514
1496
}
1515
1497
.status {
1516
1498
display: inline-block;
···
1742
1724
.max-h-\[18px\] {
1743
1725
max-height: 18px;
1744
1726
}
1745
1745
-
.max-h-\[20-px\] {
1746
1746
-
max-height: 20-px;
1747
1747
-
}
1748
1748
-
.max-h-\[20px\] {
1749
1749
-
max-height: 20px;
1750
1750
-
}
1751
1751
-
.max-h-\[22px\] {
1752
1752
-
max-height: 22px;
1753
1753
-
}
1754
1727
.max-h-\[25px\] {
1755
1728
max-height: 25px;
1756
1756
-
}
1757
1757
-
.max-h-\[30px\] {
1758
1758
-
max-height: 30px;
1759
1729
}
1760
1730
.max-h-\[60px\] {
1761
1731
max-height: 60px;
···
1766
1736
.max-h-\[100px\] {
1767
1737
max-height: 100px;
1768
1738
}
1769
1769
-
.max-h-\[200px\] {
1770
1770
-
max-height: 200px;
1771
1771
-
}
1772
1772
-
.max-h-\[250px\] {
1773
1773
-
max-height: 250px;
1774
1774
-
}
1775
1739
.max-h-\[280px\] {
1776
1740
max-height: 280px;
1777
1741
}
···
1806
1770
outline-style: none;
1807
1771
}
1808
1772
}
1809
1809
-
.w-1\/2 {
1810
1810
-
width: calc(1/2 * 100%);
1811
1811
-
}
1812
1773
.w-1\/3 {
1813
1774
width: calc(1/3 * 100%);
1814
1775
}
···
1827
1788
.max-w-\[18px\] {
1828
1789
max-width: 18px;
1829
1790
}
1830
1830
-
.max-w-\[20px\] {
1831
1831
-
max-width: 20px;
1832
1832
-
}
1833
1833
-
.max-w-\[22px\] {
1834
1834
-
max-width: 22px;
1835
1835
-
}
1836
1791
.max-w-\[25px\] {
1837
1792
max-width: 25px;
1838
1838
-
}
1839
1839
-
.max-w-\[30px\] {
1840
1840
-
max-width: 30px;
1841
1793
}
1842
1794
.max-w-\[60px\] {
1843
1795
max-width: 60px;
···
1848
1800
.max-w-\[100px\] {
1849
1801
max-width: 100px;
1850
1802
}
1851
1851
-
.max-w-\[200px\] {
1852
1852
-
max-width: 200px;
1853
1853
-
}
1854
1854
-
.max-w-\[250px\] {
1855
1855
-
max-width: 250px;
1856
1856
-
}
1857
1803
.max-w-\[280px\] {
1858
1804
max-width: 280px;
1859
1805
}
···
1893
1839
.items-end {
1894
1840
align-items: flex-end;
1895
1841
}
1842
1842
+
.items-start {
1843
1843
+
align-items: flex-start;
1844
1844
+
}
1896
1845
.\!justify-between {
1897
1846
justify-content: space-between !important;
1898
1847
}
···
1956
1905
}
1957
1906
.rounded-\[5px\] {
1958
1907
border-radius: 5px;
1959
1959
-
}
1960
1960
-
.rounded-\[8px\] {
1961
1961
-
border-radius: 8px;
1962
1908
}
1963
1909
.rounded-\[10px\] {
1964
1910
border-radius: 10px;
···
2054
2000
.\!border-none {
2055
2001
--tw-border-style: none !important;
2056
2002
border-style: none !important;
2003
2003
+
}
2004
2004
+
.border-none {
2005
2005
+
--tw-border-style: none;
2006
2006
+
border-style: none;
2057
2007
}
2058
2008
.input {
2059
2009
&.is-valid, &:has(.is-valid), .validate &:valid, .validate &:has(:valid) {
···
2406
2356
@supports (color: color-mix(in lab, red, red)) {
2407
2357
background-color: color-mix(in oklab, var(--color-base-300) 60%, transparent);
2408
2358
}
2409
2409
-
}
2410
2410
-
.bg-gradient-to-br {
2411
2411
-
--tw-gradient-position: to bottom right in oklab;
2412
2412
-
background-image: linear-gradient(var(--tw-gradient-stops));
2413
2359
}
2414
2360
.loading-spinner {
2415
2361
mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
+35
-39
apps/embed/src/components/Header/Header.tsx
···
1
1
-
import dayjs from "dayjs";
2
1
import type { Profile } from "../../types/profile";
3
2
4
3
export type HeaderProps = {
5
4
profile: Profile;
6
6
-
withoutRange?: boolean;
7
5
};
8
6
9
7
function Header(props: HeaderProps) {
10
10
-
const end = dayjs();
11
11
-
const start = end.subtract(7, "day");
12
12
-
const range = `${start.format("DD MMM YYYY")} — ${end.format("DD MMM YYYY")}`;
13
13
-
14
8
return (
15
9
<>
16
16
-
<div className="flex flex-row items-center mb-[20px]">
10
10
+
<div className="flex flex-row mb-[20px]">
17
11
<div className="flex flex-1 items-center">
18
18
-
<a
19
19
-
href={`https://rocksky.app/profile/${props.profile.handle}`}
20
20
-
target="_blank"
21
21
-
>
22
22
-
<img
23
23
-
className="max-h-[25px] max-w-[25px] rounded-full mr-[10px]"
24
24
-
src={props.profile.avatar}
25
25
-
/>
26
26
-
</a>
12
12
+
<div>
13
13
+
<div className="flex items-center">
14
14
+
<a
15
15
+
href={`https://rocksky.app/profile/${props.profile.handle}`}
16
16
+
target="_blank"
17
17
+
>
18
18
+
<img
19
19
+
className="max-h-[25px] max-w-[25px] rounded-full mr-[10px]"
20
20
+
src={props.profile.avatar}
21
21
+
/>
22
22
+
</a>
23
23
+
<a
24
24
+
href={`https://rocksky.app/profile/${props.profile.handle}`}
25
25
+
target="_blank"
26
26
+
className="no-underline text-inherit"
27
27
+
>
28
28
+
<div className="text-[14px] mt-[-6px]">
29
29
+
@{props.profile.handle}
30
30
+
</div>
31
31
+
</a>
32
32
+
</div>
33
33
+
</div>
34
34
+
</div>
35
35
+
36
36
+
<div>
27
37
<a
28
28
-
href={`https://rocksky.app/profile/${props.profile.handle}`}
38
38
+
href="https://rocksky.app"
39
39
+
className="text-inherit no-underline"
29
40
target="_blank"
30
30
-
className="no-underline text-inherit"
31
41
>
32
32
-
<div className="text-[14px] mt-[-6px]">@{props.profile.handle}</div>
42
42
+
<div className="flex flex-row items-start ">
43
43
+
<img
44
44
+
className="max-h-[18px] max-w-[18px] mr-[8px] "
45
45
+
src="/public/logo.png"
46
46
+
/>
47
47
+
<span className="text-[15px]">Rocksky</span>
48
48
+
</div>
33
49
</a>
34
34
-
{!props.withoutRange && (
35
35
-
<>
36
36
-
<span className="text-[14px] mt-[-3px] ml-[5px] mr-[5px]">|</span>
37
37
-
<span className="text-[13px] mt-[-3px]">{range}</span>
38
38
-
</>
39
39
-
)}
40
50
</div>
41
41
-
42
42
-
<a
43
43
-
href="https://rocksky.app"
44
44
-
className="text-inherit no-underline"
45
45
-
target="_blank"
46
46
-
>
47
47
-
<div className="flex flex-row items-center ">
48
48
-
<img
49
49
-
className="max-h-[18px] max-w-[18px] mr-[8px] "
50
50
-
src="/public/logo.png"
51
51
-
/>
52
52
-
<span className="text-[15px]">Rocksky</span>
53
53
-
</div>
54
54
-
</a>
55
51
</div>
56
52
</>
57
53
);
+42
apps/embed/src/embeds/Embed.tsx
···
1
1
+
import { useState } from "react";
2
2
+
3
3
+
export function Embed() {
4
4
+
const [url, setUrl] = useState<string>(""); // Initialize with an empty string
5
5
+
6
6
+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
7
7
+
const value = (e.target as any).value;
8
8
+
console.log("Input changed:", value); // Debugging log
9
9
+
setUrl(value);
10
10
+
11
11
+
if (
12
12
+
value.match(
13
13
+
/^https?:\/\/rocksky\.app\/did:plc:[a-z2-7]{24}\/scrobble\/[a-z0-9]{13,}$/,
14
14
+
)
15
15
+
) {
16
16
+
alert("ok");
17
17
+
}
18
18
+
alert("z");
19
19
+
};
20
20
+
21
21
+
return (
22
22
+
<div className="min-h-screen w-1/3 flex items-center justify-center m-auto">
23
23
+
<div className="w-full">
24
24
+
<h1 className="text-4xl font-bold text-gray-800 mb-4 text-center">
25
25
+
Embed a Rocksky Scrobble
26
26
+
</h1>
27
27
+
{/*<iframe
28
28
+
src="https://api.rocksky.app/embed/did:plc:7vdlgi2bflelz7mmuxoqjfcr/scrobble/3mdtacalsqs23"
29
29
+
height={500}
30
30
+
width={500}
31
31
+
className="mt-[20px] border-none rounded-[10px]"
32
32
+
/>*/}
33
33
+
<iframe
34
34
+
src="https://api.rocksky.app/embed/u/tsiry-sandratraina.com/top/artists"
35
35
+
height={500}
36
36
+
width={600}
37
37
+
className="mt-[20px] border-none rounded-[10px]"
38
38
+
/>
39
39
+
</div>
40
40
+
</div>
41
41
+
);
42
42
+
}
+1
-1
apps/embed/src/embeds/RecentScrobblesEmbedPage.tsx
···
15
15
export function RecentScrobblesEmbedPage(props: RecentScrobblesEmbedPageProps) {
16
16
return (
17
17
<div className="p-[15px]">
18
18
-
<Header profile={props.profile} withoutRange />
18
18
+
<Header profile={props.profile} />
19
19
<h2 className="m-[0px]">Recent Listens</h2>
20
20
21
21
<div className="w-full overflow-x-auto">
+6
apps/embed/src/embeds/TopAlbumsEmbedPage.tsx
···
2
2
import Header from "../components/Header";
3
3
import type { Album } from "../types/album";
4
4
import type { Profile } from "../types/profile";
5
5
+
import dayjs from "dayjs";
5
6
6
7
export type TopAlbumEmbedPageProps = {
7
8
profile: Profile;
···
9
10
};
10
11
11
12
export function TopAlbumsEmbedPage(props: TopAlbumEmbedPageProps) {
13
13
+
const end = dayjs();
14
14
+
const start = end.subtract(7, "day");
15
15
+
const range = `${start.format("DD MMM YYYY")} — ${end.format("DD MMM YYYY")}`;
16
16
+
12
17
return (
13
18
<div className="p-[15px]">
14
19
<Header profile={props.profile} />
15
20
<h2 className="m-[0px]">Top Albums</h2>
21
21
+
<div className="text-[14px] mt-[3px] mb-[20px]">{range}</div>
16
22
17
23
<div className="w-full overflow-x-auto">
18
24
<table className="table-borderless table">
+6
-1
apps/embed/src/embeds/TopArtistsEmbedPage.tsx
···
11
11
};
12
12
13
13
export function TopArtistsEmbedPage(props: TopArtistsEmbedPageProps) {
14
14
+
const end = dayjs();
15
15
+
const start = end.subtract(7, "day");
16
16
+
const range = `${start.format("DD MMM YYYY")} — ${end.format("DD MMM YYYY")}`;
17
17
+
14
18
return (
15
19
<div className="p-[15px]">
16
20
<Header profile={props.profile} />
17
17
-
<h2 className="m-[0px] mb-[15px]">Top Artists</h2>
21
21
+
<h2 className="m-[0px]">Top Artists</h2>
22
22
+
<div className="text-[14px] mt-[3px] mb-[20px]">{range}</div>
18
23
19
24
<div className="w-full overflow-x-auto">
20
25
<table className="table-borderless table">
+6
-1
apps/embed/src/embeds/TopTracksEmbedPage.tsx
···
2
2
import Header from "../components/Header";
3
3
import type { Profile } from "../types/profile";
4
4
import type { Track } from "../types/track";
5
5
+
import dayjs from "dayjs";
5
6
6
7
export type TopTracksEmbedPageProps = {
7
8
profile: Profile;
···
9
10
};
10
11
11
12
export function TopTracksEmbedPage(props: TopTracksEmbedPageProps) {
13
13
+
const end = dayjs();
14
14
+
const start = end.subtract(7, "day");
15
15
+
const range = `${start.format("DD MMM YYYY")} — ${end.format("DD MMM YYYY")}`;
16
16
+
12
17
return (
13
18
<div className="p-[15px]">
14
19
<Header profile={props.profile} />
15
20
<h2 className="m-[0px]">Top Tracks</h2>
16
16
-
21
21
+
<div className="text-[14px] mt-[3px] mb-[20px]">{range}</div>
17
22
<div className="w-full overflow-x-auto">
18
23
<table className="table-borderless table">
19
24
<tbody>
+2
-17
apps/embed/src/index.tsx
···
23
23
import { logger } from "hono/logger";
24
24
import { ScrobbleEmbedPage } from "./embeds/ScrobbleEmbedPage";
25
25
import getScrobble from "./xrpc/getScrobble";
26
26
+
import { Embed } from "./embeds/Embed";
26
27
27
28
const app = new Hono();
28
29
···
154
155
});
155
156
156
157
app.get("/", (c) => {
157
157
-
return c.render(
158
158
-
<div className="min-h-screen w-1/3 flex items-center justify-center m-auto">
159
159
-
<div className="w-full">
160
160
-
<h1 className="text-4xl font-bold text-gray-800 mb-4 text-center">
161
161
-
Embed a Rocksky Scrobble
162
162
-
</h1>
163
163
-
<div>
164
164
-
<input
165
165
-
type="text"
166
166
-
className="input w-full"
167
167
-
aria-label="input"
168
168
-
placeholder="https://rocksky.app/did:plc:7vdlgi2bflelz7mmuxoqjfcr/scrobble/3mdt3zncfoc23"
169
169
-
/>
170
170
-
</div>
171
171
-
</div>
172
172
-
</div>,
173
173
-
);
158
158
+
return c.render(<Embed />);
174
159
});
175
160
176
161
console.log(