Skip to content

Commit

Permalink
add: add professor, classroom in Tile
Browse files Browse the repository at this point in the history
  • Loading branch information
pbc1017 committed Mar 13, 2024
1 parent 956c23c commit 2c1a68b
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 44 deletions.
6 changes: 6 additions & 0 deletions src/common/interfaces/ITimetable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ export namespace ITimetable {
common_title: string | null;
common_title_en: string | null;
subject_classtime: IClasstime[];
// professor_names: string[] | null; // 교수 이름 목록
// professor_names_en: string[] | null; // 교수 영문 이름 목록
// classroom_str: string | null; // 강의실 문자열
// classroom_str_en: string | null; // 강의실 영문 문자열
// classroom_short: string | null; // 강의실 축약 문자열
// classroom_short_en: string | null; // 강의실 영문 축약 문자열
// Additional properties as needed
}

Expand Down
86 changes: 86 additions & 0 deletions src/modules/lectures/lectures.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import { toJsonReview } from 'src/common/interfaces/serializer/review.serializer
import { ReviewsRepository } from 'src/prisma/repositories/review.repository';
import { LectureDetails } from '../../common/schemaTypes/types';
import { LectureRepository } from './../../prisma/repositories/lecture.repository';
import { PrismaService } from 'src/prisma/prisma.service';

@Injectable()
export class LecturesService {
constructor(
private LectureRepository: LectureRepository,
private reviewsRepository: ReviewsRepository,
private prisma: PrismaService,
) {}

public async getLectureByFilter(
Expand Down Expand Up @@ -104,4 +106,88 @@ export class LecturesService {
return professor.professor.professor_name_en;
}
}

async getProfessorShortStr(
lectureId: number,
isEnglish: boolean = false,
): Promise<string> {
const lectureWithProfessors = await this.prisma.subject_lecture.findUnique({
where: { id: lectureId },
include: {
subject_lecture_professors: {
include: {
professor: true,
},
},
},
});

if (!lectureWithProfessors) {
throw new Error(`Lecture with ID ${lectureId} not found.`);
}

const profNameList = lectureWithProfessors.subject_lecture_professors.map(
(lp) =>
isEnglish
? lp.professor.professor_name_en
: lp.professor.professor_name,
);

if (profNameList.length <= 2) {
return profNameList.join(', ');
}
return `${profNameList[0]}${profNameList.length - 1} 명`;
}

async getClassroomShortStr(
lectureId: number,
isEnglish: boolean = false,
): Promise<string> {
const lectureWithClassTimes = await this.prisma.subject_lecture.findUnique({
where: { id: lectureId },
include: {
subject_classtime: true, // 모든 classtime을 포함하도록 설정합니다.
},
});

if (
!lectureWithClassTimes ||
!lectureWithClassTimes.subject_classtime ||
lectureWithClassTimes.subject_classtime.length === 0
) {
throw new Error(`Lecture with ID ${lectureId} not found.`);
}

// 첫 번째 classtime 요소에서 정보를 가져옵니다.
const classtime = lectureWithClassTimes.subject_classtime[0];
const { building_full_name, building_full_name_en, room_name } = classtime;

if (!building_full_name) {
return isEnglish ? 'Unknown' : '정보 없음';
}

let classroomShort = '';
if (building_full_name.startsWith('(')) {
const buildingCode = building_full_name.substring(
1,
building_full_name.indexOf(')'),
);
classroomShort = `(${buildingCode}) ${room_name || ''}`;
} else {
classroomShort = `${building_full_name} ${room_name || ''}`;
}

let classroomShortEn = '';
if (building_full_name_en && building_full_name_en.startsWith('(')) {
const buildingCodeEn = building_full_name_en.substring(
1,
building_full_name_en.indexOf(')'),
);
classroomShortEn = `(${buildingCodeEn}) ${room_name || ''}`;
} else {
classroomShortEn = `${building_full_name_en} ${room_name || ''}`;
}

return isEnglish ? classroomShortEn : classroomShort;
}
}
3 changes: 2 additions & 1 deletion src/modules/share/share.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { ShareService } from './share.service';
import { TimetablesModule } from '../timetables/timetables.module';
import { PrismaModule } from 'src/prisma/prisma.module';
import { SemestersModule } from '../semesters/semesters.module';
import { LecturesModule } from '../lectures/lectures.module';

@Module({
imports: [PrismaModule, TimetablesModule, SemestersModule],
imports: [PrismaModule, TimetablesModule, SemestersModule, LecturesModule],
controllers: [ShareController],
providers: [ShareService],
})
Expand Down
158 changes: 115 additions & 43 deletions src/modules/share/share.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ITimetable } from 'src/common/interfaces';
import { TimetableRepository } from 'src/prisma/repositories/timetable.repository';
import { SemesterRepository } from 'src/prisma/repositories/semester.repository';
import { SemestersService } from '../semesters/semesters.service';
import { LecturesService } from '../lectures/lectures.service';

@Injectable()
export class ShareService {
Expand All @@ -18,6 +19,7 @@ export class ShareService {
private readonly prismaService: PrismaService,
private readonly timetableRepository: TimetableRepository,
private readonly semesterRepository: SemesterRepository,
private readonly lecturesService: LecturesService, // LecturesService 추가
) {
registerFont(join(this.file_path, 'fonts/NotoSansKR-Regular.otf'), {
family: 'NotoSansKR',
Expand All @@ -39,6 +41,26 @@ export class ShareService {
return `${semester.year} ${seasonName}`;
}

private getTimetableType(lectures: ITimetable.ILecture[]): '5days' | '7days' {
return lectures.some((lecture) =>
lecture.subject_classtime.some((classtime) => classtime.day >= 5),
)
? '7days'
: '5days';
}

// Make sure to adjust other methods that use lectures to match the type
private async getTimetableEntries(
timetableId: number,
): Promise<ITimetable.ILecture[]> {
const timetableDetails =
await this.timetableRepository.getLecturesWithClassTimes(timetableId);
if (!timetableDetails) {
throw new HttpException('No such timetable', HttpStatus.NOT_FOUND);
}
return timetableDetails.map((detail) => detail.subject_lecture);
}

private drawRoundedRectangle(
ctx: CanvasRenderingContext2D,
x: number,
Expand Down Expand Up @@ -91,47 +113,83 @@ export class ShareService {
return lines;
}

private drawTextbox(
private drawText(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
text: string,
font: string,
fontSize: number,
color: string,
align?: 'right' | 'left',
align?: 'right' | 'left' | 'center',
) {
const lines = this.sliceTextToFitWidth(text, width, font, fontSize);
ctx.fillStyle = color;
ctx.font = `${fontSize}px '${font}'`;
ctx.textAlign = align ? align : 'left';
if (align == 'right') ctx.fillText(text, x, y);
else {
lines.forEach((line, i) => {
ctx.fillText(line, x, y + fontSize * (i + 1));
});
}
ctx.fillText(text, x, y);
}

private getTimetableType(lectures: ITimetable.ILecture[]): '5days' | '7days' {
return lectures.some((lecture) =>
lecture.subject_classtime.some((classtime) => classtime.day >= 5),
)
? '7days'
: '5days';
}
private drawTile(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
height: number,
title: string,
professor: string,
location: string,
font: string,
fontSize: number,
) {
const slicedTitle = this.sliceTextToFitWidth(title, width, font, fontSize);
const slicedProfessor = this.sliceTextToFitWidth(
professor,
width,
font,
fontSize,
);
const slicedLocation = this.sliceTextToFitWidth(
location,
width,
font,
fontSize,
);

// Make sure to adjust other methods that use lectures to match the type
private async getTimetableEntries(
timetableId: number,
): Promise<ITimetable.ILecture[]> {
const timetableDetails =
await this.timetableRepository.getLecturesWithClassTimes(timetableId);
if (!timetableDetails) {
throw new HttpException('No such timetable', HttpStatus.NOT_FOUND);
}
return timetableDetails.map((detail) => detail.subject_lecture);
let textTotalHeight = 0;
const slices: string[] = [
...slicedTitle,
'',
...slicedProfessor,
'',
...slicedLocation,
];

// Calculate total height for text
textTotalHeight = slices.reduce((total, slice, index) => {
if (slice === '') return total + 2; // space between sections
return total + fontSize;
}, 0);

const topPad = (height - textTotalHeight) / 2;
let offsetY = topPad + fontSize;

slices.forEach((slice, index) => {
if (slice !== '') {
this.drawText(
ctx,
x,
y + offsetY,
slice,
font,
fontSize,
'rgba(0, 0, 0, ' + (index < slicedTitle.length ? 0.8 : 0.5) + ')', // Adjust opacity
'left',
);
offsetY += fontSize;
} else {
offsetY += 2; // Adding space between sections
}
});
}

async createTimetableImage(
Expand Down Expand Up @@ -181,30 +239,31 @@ export class ShareService {
if (!semesterObject) {
throw new HttpException('Semester not found', HttpStatus.NOT_FOUND);
}
const isEnglish = language && language.includes('en');
const isEnglish: boolean = !!language && language.includes('en');
const semesterName = this.getSemesterName(
semesterObject,
isEnglish ? 'en' : 'kr',
);
this.drawTextbox(
this.drawText(
ctx,
timetableType === '5days' ? 952 : 952 + 350,
78,
200,
semesterName,
'NotoSansKR',
semesterFontSize,
'#CCCCCC',
'right',
);

lectures.forEach((lecture) => {
// 강의 정보를 순차적으로 처리
for (const lecture of lectures) {
const color = TIMETABLE_CELL_COLORS[lecture.course_id % 16];
lecture.subject_classtime.forEach((classtime) => {

// 각 강의 시간에 대해 비동기 처리
for (const classtime of lecture.subject_classtime) {
const { day, begin, end } = classtime;
const beginNumber = begin.getUTCHours() * 60 + begin.getUTCMinutes();
const endNumber = end.getUTCHours() * 60 + end.getUTCMinutes();
console.log(day, begin, end, beginNumber, endNumber);

const [x, y, width, height] = [
178 * day + 76,
Expand All @@ -213,21 +272,34 @@ export class ShareService {
((endNumber - beginNumber) * 4) / 3 - 7,
];

// const [x, y, width, height] = [100, 100, 200, 60]; // Placeholder values

this.drawRoundedRectangle(ctx, x, y, width, height, 10, color);
this.drawTextbox(

// 교수님 이름과 강의실 정보를 비동기적으로 가져온 후 타일 그리기
const professorShortStr =
await this.lecturesService.getProfessorShortStr(
lecture.id,
isEnglish,
);
const classroomShortStr =
await this.lecturesService.getClassroomShortStr(
lecture.id,
isEnglish,
);

this.drawTile(
ctx,
x + 10,
y + 10,
width - 20,
lecture.title,
x + 12,
y + 8,
width - 24,
height - 16,
isEnglish ? lecture.title_en : lecture.title,
professorShortStr,
classroomShortStr,
'NotoSansKR',
tileFontSize,
'#000',
);
});
});
}
}

// Return the image as a buffer
return canvas.toBuffer();
Expand Down

0 comments on commit 2c1a68b

Please sign in to comment.