Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moved Manga chapters over to Decimals for fractional releases #1002

Merged
merged 9 commits into from
Sep 4, 2024
2 changes: 1 addition & 1 deletion apps/frontend/app/lib/state/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type UpdateProgressData = {
showEpisodeNumber?: number | null;
podcastEpisodeNumber?: number | null;
animeEpisodeNumber?: number | null;
mangaChapterNumber?: number | null;
mangaChapterNumber?: string | null;
mangaVolumeNumber?: number | null;
};

Expand Down
7 changes: 6 additions & 1 deletion apps/frontend/app/lib/utilities.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,19 @@ const emptyNumberString = z
.transform((v) => (!v ? undefined : Number.parseInt(v)))
.nullable();

const emptyDecimalString = z
.any()
.transform((v) => (!v ? undefined : Number.parseFloat(v).toString()))
.nullable();

export const MetadataIdSchema = z.object({ metadataId: z.string() });

export const MetadataSpecificsSchema = z.object({
showSeasonNumber: emptyNumberString,
showEpisodeNumber: emptyNumberString,
podcastEpisodeNumber: emptyNumberString,
animeEpisodeNumber: emptyNumberString,
mangaChapterNumber: emptyNumberString,
mangaChapterNumber: emptyDecimalString,
mangaVolumeNumber: emptyNumberString,
});

Expand Down
27 changes: 20 additions & 7 deletions apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ import {
changeCase,
formatDateToNaiveDate,
humanizeDuration,
isInteger,
isNumber,
isString,
processSubmission,
} from "@ryot/ts-utils";
import {
Expand Down Expand Up @@ -1367,13 +1369,24 @@ const HistoryItem = (props: { history: History; index: number }) => {
)
? `EP-${props.history.animeExtraInformation.episode}`
: null;
const displayMangaExtraInformation = isNumber(
props.history.mangaExtraInformation?.chapter,
)
? `CH-${props.history.mangaExtraInformation.chapter}`
: isNumber(props.history.mangaExtraInformation?.volume)
? `VOL-${props.history.mangaExtraInformation.volume}`
: null;
const displayMangaExtraInformation = (() => {
const { chapter, volume } = props.history.mangaExtraInformation || {};

if (chapter != null) {
const chapterNum = isString(chapter)
? Number.parseFloat(chapter)
: chapter;

if (!Number.isNaN(chapterNum)) {
const isWholeNumber = isInteger(chapterNum);
return `CH-${isWholeNumber ? Math.floor(chapterNum) : chapterNum}`;
}
}

if (isNumber(volume)) return `VOL-${volume}`;

return null;
})();
const watchedOnInformation = props.history.providerWatchedOn;

const filteredDisplayInformation = [
Expand Down
12 changes: 7 additions & 5 deletions apps/frontend/app/routes/_dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -897,12 +897,12 @@ const MetadataInProgressUpdateForm = ({
</Text>
<Flex align="center" gap="xs">
<NumberInput
defaultValue={((total || 1) * (value || 1)) / 100}
defaultValue={((Number(total) || 1) * (value || 1)) / 100}
onChange={(v) => {
const value = (Number(v) / (total || 1)) * 100;
const value = (Number(v) / (Number(total) || 1)) * 100;
setValue(value);
}}
max={total}
max={Number(total)}
min={0}
step={1}
hideControls
Expand Down Expand Up @@ -999,7 +999,8 @@ const NewProgressUpdateForm = ({
onChange={(e) => {
setMetadataToUpdate(
produce(metadataToUpdate, (draft) => {
draft.mangaChapterNumber = Number(e);
draft.mangaChapterNumber =
e === "" ? undefined : Number(e).toString();
}),
);
}}
Expand All @@ -1014,7 +1015,8 @@ const NewProgressUpdateForm = ({
onChange={(e) => {
setMetadataToUpdate(
produce(metadataToUpdate, (draft) => {
draft.mangaVolumeNumber = Number(e);
draft.mangaVolumeNumber =
e === "" ? undefined : Number(e);
}),
);
}}
Expand Down
37 changes: 27 additions & 10 deletions apps/frontend/app/routes/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,15 @@ export const action = unstable_defineAction(async ({ request }) => {
}
}
if (submission.metadataLot === MediaLot.Manga) {
const isValidNumber = (value: unknown): boolean => {
const num = Number(value);
return !Number.isNaN(num) && Number.isFinite(num);
};

if (
(isNumber(submission.mangaChapterNumber) &&
(isValidNumber(submission.mangaChapterNumber) &&
isNumber(submission.mangaVolumeNumber)) ||
(!isNumber(submission.mangaChapterNumber) &&
(!isValidNumber(submission.mangaChapterNumber) &&
!isNumber(submission.mangaVolumeNumber))
)
throw Response.json({
Expand All @@ -293,14 +298,26 @@ export const action = unstable_defineAction(async ({ request }) => {
}
}
if (submission.mangaChapterNumber) {
const lastSeenChapter =
latestHistoryItem?.mangaExtraInformation?.chapter || 0;
for (
let i = lastSeenChapter + 1;
i < submission.mangaChapterNumber;
i++
) {
updates.push({ ...variables, mangaChapterNumber: i });
const targetChapter = Number(submission.mangaChapterNumber);
const markedChapters = new Set();

for (const historyItem of userMetadataDetails?.history ?? []) {
const chapter = Number(
historyItem?.mangaExtraInformation?.chapter,
);

if (!Number.isNaN(chapter) && chapter < targetChapter) {
markedChapters.add(chapter);
}
}

for (let i = 1; i < targetChapter; i++) {
if (!markedChapters.has(i)) {
updates.push({
...variables,
mangaChapterNumber: i.toString(),
});
}
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions crates/models/media/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ pub struct AnimeSpecifics {
)]
#[graphql(input_name = "MangaSpecificsInput")]
pub struct MangaSpecifics {
pub chapters: Option<i32>,
pub chapters: Option<Decimal>,
pub volumes: Option<i32>,
pub url: Option<String>,
}
Expand Down Expand Up @@ -401,7 +401,7 @@ pub struct PostReviewInput {
pub show_episode_number: Option<i32>,
pub podcast_episode_number: Option<i32>,
pub anime_episode_number: Option<i32>,
pub manga_chapter_number: Option<i32>,
pub manga_chapter_number: Option<Decimal>,
pub manga_volume_number: Option<i32>,
}

Expand All @@ -414,7 +414,7 @@ pub struct ProgressUpdateInput {
pub show_episode_number: Option<i32>,
pub podcast_episode_number: Option<i32>,
pub anime_episode_number: Option<i32>,
pub manga_chapter_number: Option<i32>,
pub manga_chapter_number: Option<Decimal>,
pub manga_volume_number: Option<i32>,
pub change_state: Option<SeenState>,
pub provider_watched_on: Option<String>,
Expand Down Expand Up @@ -562,7 +562,7 @@ pub struct ImportOrExportMediaItemSeen {
/// If for an anime, the episode which was seen.
pub anime_episode_number: Option<i32>,
/// If for a manga, the chapter which was seen.
pub manga_chapter_number: Option<i32>,
pub manga_chapter_number: Option<Decimal>,
/// If for a manga, the volume which was seen.
pub manga_volume_number: Option<i32>,
/// The provider this item was watched on.
Expand Down Expand Up @@ -602,7 +602,7 @@ pub struct ImportOrExportItemRating {
/// If for an anime, the episode for which this review was for.
pub anime_episode_number: Option<i32>,
/// If for a manga, the chapter for which this review was for.
pub manga_chapter_number: Option<i32>,
pub manga_chapter_number: Option<Decimal>,
/// The comments attached to this review.
pub comments: Option<Vec<ImportOrExportItemReviewComment>>,
}
Expand Down Expand Up @@ -811,7 +811,7 @@ pub struct SeenAnimeExtraInformation {
Debug, PartialEq, Eq, Serialize, Deserialize, Clone, SimpleObject, FromJsonQueryResult,
)]
pub struct SeenMangaExtraInformation {
pub chapter: Option<i32>,
pub chapter: Option<Decimal>,
pub volume: Option<i32>,
}

Expand Down Expand Up @@ -1054,7 +1054,7 @@ pub struct IntegrationMediaSeen {
pub show_episode_number: Option<i32>,
pub podcast_episode_number: Option<i32>,
pub anime_episode_number: Option<i32>,
pub manga_chapter_number: Option<i32>,
pub manga_chapter_number: Option<Decimal>,
pub manga_volume_number: Option<i32>,
pub provider_watched_on: Option<String>,
}
Expand Down Expand Up @@ -1463,7 +1463,7 @@ pub struct UserMetadataDetailsShowSeasonProgress {
pub struct UserMediaNextEntry {
pub season: Option<i32>,
pub volume: Option<i32>,
pub chapter: Option<i32>,
pub chapter: Option<Decimal>,
pub episode: Option<i32>,
}

Expand Down
2 changes: 1 addition & 1 deletion crates/providers/src/mal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ async fn details(client: &Client, media_type: &str, id: &str) -> Result<MediaDet
.num_volumes
.zip(details.num_chapters)
.map(|(v, c)| MangaSpecifics {
chapters: Some(c),
chapters: Some(Decimal::from(c)),
volumes: Some(v),
url: None,
});
Expand Down
2 changes: 1 addition & 1 deletion crates/providers/src/manga_updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ impl MediaProvider for MangaUpdatesService {
.collect(),
publish_year: data.year.and_then(|y| y.parse().ok()),
manga_specifics: Some(MangaSpecifics {
chapters: data.latest_chapter,
chapters: data.latest_chapter.map(|c| Decimal::from(c)),
url: data.url,
volumes,
}),
Expand Down
2 changes: 1 addition & 1 deletion crates/services/importer/src/mal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ fn convert_to_format(item: Item, lot: MediaLot) -> ImportOrExportMediaItem {
.map(|i| {
let (anime_episode, manga_chapter) = match lot {
MediaLot::Anime => (Some(i), None),
MediaLot::Manga => (None, Some(i)),
MediaLot::Manga => (None, Some(Decimal::new(i as i64, 0))),
_ => unreachable!(),
};
ImportOrExportMediaItemSeen {
Expand Down
11 changes: 8 additions & 3 deletions crates/services/miscellaneous/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ use providers::{
tmdb::{NonMediaTmdbService, TmdbMovieService, TmdbService, TmdbShowService},
vndb::VndbService,
};
use rust_decimal::prelude::{One, ToPrimitive};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use sea_orm::{
Expand Down Expand Up @@ -130,7 +131,7 @@ struct ProgressUpdateCache {
show_episode_number: Option<i32>,
podcast_episode_number: Option<i32>,
anime_episode_number: Option<i32>,
manga_chapter_number: Option<i32>,
manga_chapter_number: Option<Decimal>,
manga_volume_number: Option<i32>,
}

Expand Down Expand Up @@ -545,7 +546,7 @@ impl MiscellaneousService {
h.manga_extra_information.as_ref().and_then(|hist| {
hist.chapter
.map(|e| UserMediaNextEntry {
chapter: Some(e + 1),
chapter: Some(e.floor() + dec!(1)),
..Default::default()
})
.or(hist.volume.map(|e| UserMediaNextEntry {
Expand Down Expand Up @@ -3394,7 +3395,11 @@ impl MiscellaneousService {
} else if let Some(e) = metadata.model.anime_specifics.and_then(|a| a.episodes) {
(1..e + 1).map(|e| format!("{}", e)).collect_vec()
} else if let Some(c) = metadata.model.manga_specifics.and_then(|m| m.chapters) {
(1..c + 1).map(|e| format!("{}", e)).collect_vec()
let one = Decimal::one();
(0..c.to_u32().unwrap_or(0))
.map(|i| Decimal::from(i) + one)
.map(|d| d.to_string())
.collect_vec()
} else {
vec![]
};
Expand Down
3 changes: 2 additions & 1 deletion crates/utils/database/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use media_models::{
};
use migrations::AliasedCollectionToEntity;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use sea_orm::{
prelude::{Date, DateTimeUtc, Expr},
Expand Down Expand Up @@ -489,7 +490,7 @@ pub async fn calculate_user_activities_and_summary(
shows: HashSet<String>,
show_seasons: HashSet<i32>,
anime_episodes: HashSet<i32>,
manga_chapters: HashSet<i32>,
manga_chapters: HashSet<Decimal>,
manga_volumes: HashSet<i32>,
}
type Tracker = HashMap<Date, TrackerItem>;
Expand Down
4 changes: 2 additions & 2 deletions docs/includes/export-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export interface ImportOrExportItemRating {
/** The comments attached to this review. */
comments: ImportOrExportItemReviewComment[] | null;
/** If for a manga, the chapter for which this review was for. */
manga_chapter_number: number | null;
manga_chapter_number: string | null;
/** If for a podcast, the episode for which this review was for. */
podcast_episode_number: number | null;
/** The score of the review. */
Expand Down Expand Up @@ -115,7 +115,7 @@ export interface ImportOrExportMediaItemSeen {
/** The timestamp when finished watching. */
ended_on: string | null;
/** If for a manga, the chapter which was seen. */
manga_chapter_number: number | null;
manga_chapter_number: string | null;
/** If for a manga, the volume which was seen. */
manga_volume_number: number | null;
/** If for a podcast, the episode which was seen. */
Expand Down
Loading