Skip to content

Instantly share code, notes, and snippets.

@Reinachan
Last active August 14, 2021 00:42
Show Gist options
  • Select an option

  • Save Reinachan/40eb41bf6ccb0972ef242f608c8eab59 to your computer and use it in GitHub Desktop.

Select an option

Save Reinachan/40eb41bf6ccb0972ef242f608c8eab59 to your computer and use it in GitHub Desktop.
AniList Latest Updated Anime iOS Widget
const html =
"<html> \
<head> \
<style> \
:root { \
--main-color: #B368F6; \
} \
body { \
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; \
color: white; \
} \
#anime { \
width: 500px; \
height: 500px; \
background-color: black; \
display: grid; \
gap: 4px; \
} \
.anime-container { \
--poster-width: 65px; \
--poster-height: 96px; \
display: grid; \
max-height: var(--poster-height); \
gap: 10px; \
grid-template-columns: var(--poster-width); \
} \
.progress-info { \
grid-column: 2; \
grid-row: 3; \
display: grid; \
margin-bottom: 5px; \
} \
.progress-bar { \
grid-row: 2; \
display: grid; \
height: 5px; \
width: 95%; \
background-color: #333; \
border-radius: 2px; \
} \
.progress-detail { \
grid-row: 1; \
display: grid; \
} \
.episode-text { \
grid-area: 1 / 1; \
} \
.next-episode { \
grid-area: 2 / 1; \
} \
.progress { \
grid-row: 1; \
grid-column: 1; \
background-color: var(--main-color); \
height: 100%; \
position: relative; \
z-index: 5; \
border-radius: 2px; \
} \
.release-progress { \
grid-row: 1; \
grid-column: 1; \
background-color: var(--main-color); \
opacity: 50%; \
height: 100%; \
position: relative; \
z-index: 4; \
border-radius: 2px; \
} \
.progress-detail span { \
font-size: 10px; \
} \
.title { \
grid-column: 2; \
grid-row: 1; \
margin-bottom: 0; \
font-size: 15px; \
color: white; \
text-align: left; \
} \
.poster { \
grid-column: 1; \
grid-row: span 3; \
height: var(--poster-height); \
width: var(--poster-width); \
object-fit: cover; \
border-radius: 4px; \
} \
</style> \
</head> \
<body> \
<div id='anime'> \
</div> \
</body> \
<script> \
const wrapper = async () => { \
const USERNAME = 'Reina'; \
const TYPE = 'ANIME'; \
console.log('test'); \
const ANIME_WRAPPER_DIV = document.getElementById('anime'); \
const query = ` \
query ($userName: String, $type: MediaType) { \
Page (page: 1, perPage: 5) { \
mediaList (userName: $userName, type: $type, status_in: CURRENT, sort: UPDATED_TIME_DESC) { \
media { \
title { \
english \
romaji \
} \
coverImage { \
medium: large \
} \
episodes \
chapters \
nextAiringEpisode { \
timeUntilAiring \
episode \
} \
} \
progress \
user { \
options { \
profileColor \
} \
} \
} \
} \
} \
`; \
const variables = { \
userName: USERNAME, \
type: TYPE, \
}; \
const getAnime = async () => { \
const url = 'https://graphql.anilist.co/'; \
const anime = await fetch(url, { \
method: 'POST', \
headers: { \
'Content-Type': 'application/json', \
Accept: 'application/json', \
}, \
body: JSON.stringify({ \
query: query, \
variables: variables, \
}), \
}); \
return await anime.json(); \
}; \
const anime = await getAnime(); \
console.log(anime); \
const createDom = (type, className, parent) => { \
const node = document.createElement(type); \
node.className = className; \
parent.appendChild(node); \
return node; \
}; \
const epiChapter = (media) => { \
if (media === 'ANIME') { \
return 'episodes'; \
} \
if (media === 'MANGA') { \
return 'chapters'; \
} \
}; \
const newEpisodeWhen = (next) => { \
if (next < 60) { \
return 'Next episode in ' + next + ' seconds'; \
} \
if (next < 3600) { \
return 'Next episode in ' + Math.round(next / 60) + ' minutes'; \
} \
if (next < 86400) { \
return 'Next episode in ' + Math.round(next / 3600) + ' hours'; \
} \
if (next < 518400) { \
return 'Next episode in ' + Math.round(next / 86400) + ' days'; \
} \
if (next < 604800) { \
return 'Next episode in a week'; \
} \
if (next < 259200) { \
return 'Next episode in ' + Math.round(next / 86400) + ' days'; \
} \
if (next < 31536034.56) { \
return 'Next episode in ' + Math.round(next / 2628002.88) + ' months'; \
} \
return ''; \
}; \
const colorPicker = (c) => { \
const colorTable = { \
blue: '61B1EC', \
purple: 'B368F6', \
green: '70C661', \
orange: 'E28D3A', \
red: 'D0433C', \
pink: 'EEA2D3', \
grey: '6B7A91', \
}; \
return '#' + colorTable[c]; \
}; \
const progressBar = (anime) => { \
const progress = anime.progress; \
const total = anime.media.episodes ?? anime.media.chapters; \
const nextDate = anime?.media?.nextAiringEpisode?.timeUntilAiring; \
const nextNumber = anime?.media?.nextAiringEpisode?.episode; \
const format = epiChapter(TYPE); \
const progressWidth = ((progress ?? 1) / (total ?? 1000)) * 100; \
const releaseProgressWidth = \
((nextNumber ? nextNumber - 1 : 1) / (total ?? 1000)) * 100; \
const progressText = \
(progress ?? '??') + ' / ' + (total ?? '??') + ' ' + format; \
const nextEpisode = nextDate ? newEpisodeWhen(nextDate) : ''; \
const bar = ` \
<div class='progress-info'> \
<div class='progress-detail'> \
<span class='episode-text'>${progressText}</span> \
<span class='next-episode'>${nextEpisode}</span> \
</div> \
<div class='progress-bar'> \
<div class='progress' style='width: ${progressWidth}%;'></div> \
<div class='release-progress' style='width: ${releaseProgressWidth}%;'></div> \
</div> \
</div> \
`; \
return bar; \
}; \
const createAnimeDom = (anime) => { \
const containerDiv = createDom('div', 'anime-container', ANIME_WRAPPER_DIV); \
const progress = progressBar(anime); \
containerDiv.innerHTML = ` \
<h2 class='title'>${anime.media.title.english}</h2> \
<img class='poster' src='${anime.media.coverImage.medium}'> \
${progress} \
`; \
}; \
const mediaList = anime.data.Page.mediaList; \
const profileColor = mediaList[0].user.options.profileColor; \
const color = colorPicker(profileColor); \
document.documentElement.style.setProperty('--main-color', color); \
mediaList.forEach((anime) => { \
console.log(anime); \
createAnimeDom(anime); \
}); \
}; \
wrapper() \
</script> \
</html>";
<html>
<head>
<style>
:root {
--main-color: #B368F6;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
color: white;
}
#anime {
width: 500px;
height: 500px;
background-color: black;
display: grid;
gap: 4px;
}
.anime-container {
--poster-width: 65px;
--poster-height: 96px;
display: grid;
max-height: var(--poster-height);
gap: 10px;
grid-template-columns: var(--poster-width);
}
.progress-info {
grid-column: 2;
grid-row: 3;
display: grid;
margin-bottom: 5px;
}
.progress-bar {
grid-row: 2;
display: grid;
height: 5px;
width: 95%;
background-color: #333;
border-radius: 2px;
}
.progress-detail {
grid-row: 1;
display: grid;
}
.episode-text {
grid-area: 1 / 1;
}
.next-episode {
grid-area: 2 / 1;
}
.progress {
grid-row: 1;
grid-column: 1;
background-color: var(--main-color);
height: 100%;
position: relative;
z-index: 5;
border-radius: 2px;
}
.release-progress {
grid-row: 1;
grid-column: 1;
background-color: var(--main-color);
opacity: 50%;
height: 100%;
position: relative;
z-index: 4;
border-radius: 2px;
}
.progress-detail span {
font-size: 10px;
}
.title {
grid-column: 2;
grid-row: 1;
margin-bottom: 0;
font-size: 15px;
color: white;
text-align: left;
}
.poster {
grid-column: 1;
grid-row: span 3;
height: var(--poster-height);
width: var(--poster-width);
object-fit: cover;
border-radius: 4px;
}
</style>
</head>
<body>
<div id="anime">
</div>
</body>
</html>
const wrapper = async () => {
const USERNAME = 'Reina';
const TYPE = 'ANIME';
console.log('test');
const ANIME_WRAPPER_DIV = document.getElementById('anime');
const query = `
query ($userName: String, $type: MediaType) {
Page (page: 1, perPage: 5) {
mediaList (userName: $userName, type: $type, status_in: CURRENT, sort: UPDATED_TIME_DESC) {
media {
title {
english
romaji
}
coverImage {
medium: large
}
episodes
chapters
nextAiringEpisode {
timeUntilAiring
episode
}
}
progress
user {
options {
profileColor
}
}
}
}
}
`;
const variables = {
userName: USERNAME,
type: TYPE,
};
const getAnime = async () => {
const url = 'https://graphql.anilist.co/';
const anime = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({
query: query,
variables: variables,
}),
});
return await anime.json();
};
const anime = await getAnime();
console.log(anime);
const createDom = (type, className, parent) => {
const node = document.createElement(type);
node.className = className;
parent.appendChild(node);
return node;
};
const epiChapter = (media) => {
if (media === 'ANIME') {
return 'episodes';
}
if (media === 'MANGA') {
return 'chapters';
}
};
const newEpisodeWhen = (next) => {
if (next < 60) {
return 'Next episode in ' + next + ' seconds';
}
if (next < 3600) {
return 'Next episode in ' + Math.round(next / 60) + ' minutes';
}
if (next < 86400) {
return 'Next episode in ' + Math.round(next / 3600) + ' hours';
}
if (next < 518400) {
return 'Next episode in ' + Math.round(next / 86400) + ' days';
}
if (next < 604800) {
return 'Next episode in a week';
}
if (next < 259200) {
return 'Next episode in ' + Math.round(next / 86400) + ' days';
}
if (next < 31536034.56) {
return 'Next episode in ' + Math.round(next / 2628002.88) + ' months';
}
return '';
};
const colorPicker = (c) => {
const colorTable = {
blue: '61B1EC',
purple: 'B368F6',
green: '70C661',
orange: 'E28D3A',
red: 'D0433C',
pink: 'EEA2D3',
grey: '6B7A91',
};
return '#' + colorTable[c];
};
const progressBar = (anime) => {
const progress = anime.progress;
const total = anime.media.episodes ?? anime.media.chapters;
const nextDate = anime?.media?.nextAiringEpisode?.timeUntilAiring;
const nextNumber = anime?.media?.nextAiringEpisode?.episode;
const format = epiChapter(TYPE);
const progressWidth = ((progress ?? 1) / (total ?? 1000)) * 100;
const releaseProgressWidth =
((nextNumber ? nextNumber - 1 : 1) / (total ?? 1000)) * 100;
const progressText =
(progress ?? '??') + ' / ' + (total ?? '??') + ' ' + format;
const nextEpisode = nextDate ? newEpisodeWhen(nextDate) : '';
const bar = `
<div class="progress-info">
<div class="progress-detail">
<span class="episode-text">${progressText}</span>
<span class="next-episode">${nextEpisode}</span>
</div>
<div class="progress-bar">
<div class="progress" style="width: ${progressWidth}%;"></div>
<div class="release-progress" style="width: ${releaseProgressWidth}%;"></div>
</div>
</div>
`;
return bar;
};
const createAnimeDom = (anime) => {
const containerDiv = createDom('div', 'anime-container', ANIME_WRAPPER_DIV);
const progress = progressBar(anime);
containerDiv.innerHTML = `
<h2 class="title">${anime.media.title.english}</h2>
<img class="poster" src="${anime.media.coverImage.medium}">
${progress}
`;
};
const mediaList = anime.data.Page.mediaList;
const profileColor = mediaList[0].user.options.profileColor;
const color = colorPicker(profileColor);
document.documentElement.style.setProperty('--main-color', color);
mediaList.forEach((anime) => {
console.log(anime);
createAnimeDom(anime);
});
};
wrapper()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment