Skip to content

Commit

Permalink
♻️ Enhance the schedules component
Browse files Browse the repository at this point in the history
  • Loading branch information
Asing1001 committed Nov 16, 2023
1 parent be1de34 commit 732cda6
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 98 deletions.
88 changes: 49 additions & 39 deletions src/app/components/schedules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ import TimeList from './timeList';
import Chip from 'material-ui/Chip';
import { grey500 } from 'material-ui/styles/colors';

const theaterInfoStyle: React.CSSProperties = {
marginRight: '0.5em',
fontSize: 'small',
alignItems: 'center',
display: 'inline-flex',
};

interface MovieDetailProps {
schedules: Schedule[];
}
Expand All @@ -27,58 +20,75 @@ class Schedules extends React.PureComponent<MovieDetailProps, any> {
};
}

memoizedAvailableDates: string[] | null = null; // Memoization cache

getAvailableDates() {
return [...new Set(this.props.schedules.map(({ date }) => date))];
if (this.memoizedAvailableDates === null) {
this.memoizedAvailableDates = [...new Set(this.props.schedules.map(({ date }) => date))];
}
return this.memoizedAvailableDates;
}

setSchedulesWithDistance(schedules) {
getClientGeoLocation().then(({ latitude, longitude }) => {
const schedulesWithDistance = schedules
.map((schedule) => {
const { theaterExtension } = schedule;
if (
theaterExtension &&
theaterExtension.location &&
theaterExtension.location.lat &&
theaterExtension.location.lng
) {
const { lat, lng } = theaterExtension.location;
return {
...schedule,
theaterExtension: {
...theaterExtension,
distance: getDistanceInKM(lng, lat, longitude, latitude),
},
};
} else {
return schedule; // If lat and lng are missing, return the original schedule
}
})
.sort(
({ theaterExtension: { distance: distanceA } }, { theaterExtension: { distance: distanceB } }) =>
distanceA - distanceB
);
this.setState({ schedulesWithDistance });
const updatedSchedules = schedules.map((schedule) => {
const { theaterExtension } = schedule;
if (
theaterExtension &&
theaterExtension.location &&
theaterExtension.location.lat &&
theaterExtension.location.lng
) {
const { lat, lng } = theaterExtension.location;
return {
...schedule,
theaterExtension: {
...theaterExtension,
distance: getDistanceInKM(lng, lat, longitude, latitude),
},
};
} else {
return schedule; // If lat and lng are missing, return the original schedule
}
});

// Sort the schedules by distance
updatedSchedules.sort(
({ theaterExtension: { distance: distanceA } }, { theaterExtension: { distance: distanceB } }) =>
distanceA - distanceB
);

// Batch the state update
this.setState({ schedulesWithDistance: updatedSchedules });
});
}

componentDidMount() {
let schedules = this.props.schedules
.slice()
.sort(({ theaterExtension: { regionIndex: a } }, { theaterExtension: { regionIndex: b } }) => a - b);
.sort(
({ theaterExtension: { regionIndex: a } }, { theaterExtension: { regionIndex: b } }) =>
parseInt(a) - parseInt(b)
);
this.setState({ schedulesWithDistance: schedules }, () => this.setSchedulesWithDistance(schedules));
}

handleDateClick = (date) => {
this.setState({ selectedDate: date });
};

render() {
const availableDates = this.getAvailableDates();

return (
<div className="col-xs-12">
<div className="date-wrapper col-xs-12">
{this.getAvailableDates().map((date, index) => (
{availableDates.map((date, index) => (
<Chip
className="datebtn"
backgroundColor={this.state.selectedDate === date ? grey500 : null}
key={index}
onClick={() => this.setState({ selectedDate: date })}
onClick={this.handleDateClick.bind(this, date)} // Avoid inline arrow functions
>
{moment(date).isSame(moment(), 'day') ? '今天' : moment(date).format('MM/DD')}
</Chip>
Expand All @@ -88,10 +98,10 @@ class Schedules extends React.PureComponent<MovieDetailProps, any> {
.filter(({ date }) => date === this.state.selectedDate)
.map(({ timesStrings, theaterName, roomTypes, distance, theaterExtension, date }, index) => {
return (
<p key={index} className="col-xs-12">
<div key={index} className="col-xs-12">
<TheaterCard theater={theaterExtension} roomTypes={roomTypes}></TheaterCard>
<TimeList timesStrings={timesStrings} greyOutExpired={moment(date).isSame(moment(), 'day')}></TimeList>
</p>
</div>
);
})}
</div>
Expand Down
75 changes: 37 additions & 38 deletions src/crawler/theaterCrawler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,48 @@ import { getCheerio$ } from '../helper/util';

const theaterListUrl = 'http://www.atmovies.com.tw/showtime/';
export async function getTheaterList(): Promise<Theater[]> {
console.time('getTheaterList');
const regionList = await getRegionList();
const promises = regionList.map(getTheaterListByRegion);
const theaterList = [].concat(...(await Promise.all(promises)));
console.timeEnd('getTheaterList');
return theaterList;
console.time('getTheaterList');
const regionList = await getRegionList();
const promises = regionList.map(getTheaterListByRegion);
const theaterList = [].concat(...(await Promise.all(promises)));
console.timeEnd('getTheaterList');
return theaterList;
}

export async function getRegionList(): Promise<Region[]> {
const $ = await getCheerio$(theaterListUrl);
const regionList = Array.from($('map > [shape=rect]')).map((area) => {
const $area = $(area);
return {
name: $area.attr('alt'),
regionId: $area.attr('href').substr(theaterListUrl.length, 3),
};
});
const $ = await getCheerio$(theaterListUrl);
const regionList = Array.from($('map > [shape=rect]')).map((area) => {
const $area = $(area);
return {
name: $area.attr('alt'),
regionId: $area.attr('href').substr(theaterListUrl.length, 3),
};
});

return regionList;
return regionList;
}

export async function getTheaterListByRegion({ name: regionName, regionId }, index) {
const $ = await getCheerio$(`${theaterListUrl}${regionId}/`);
let theaterList: Theater[] = [];
let subRegion = regionName;
Array.from($('#theaterList>li')).forEach(li => {
const $li = $(li);
if ($li.hasClass('type0')) {
subRegion = $li.text().trim().slice(0, -1);
}
else {
theaterList.push({
name: $li.find('a').first().text().trim(),
url: $li.find('a[target]').attr('href'),
scheduleUrl: $li.find('a').attr('href'),
address: $li.find('li').first().text().trim(),
phone: $li.find('li:nth-child(2)').text().trim(),
region: regionName,
regionIndex: index,
subRegion
})
}
});
export async function getTheaterListByRegion({ name: regionName, regionId }, index: number) {
const $ = await getCheerio$(`${theaterListUrl}${regionId}/`);
const theaterList: Theater[] = [];
let subRegion = regionName;
Array.from($('#theaterList>li')).forEach((li) => {
const $li = $(li);
if ($li.hasClass('type0')) {
subRegion = $li.text().trim().slice(0, -1);
} else {
theaterList.push({
name: $li.find('a').first().text().trim(),
url: $li.find('a[target]').attr('href'),
scheduleUrl: $li.find('a').attr('href'),
address: $li.find('li').first().text().trim(),
phone: $li.find('li:nth-child(2)').text().trim(),
region: regionName,
regionIndex: index.toString(),
subRegion,
});
}
});

return theaterList;
return theaterList;
}
2 changes: 1 addition & 1 deletion src/data/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ const TheaterType = new GraphQLObjectType({
resolve: (obj) => obj.region,
},
regionIndex: {
type: GraphQLString,
type: GraphQLInt,
resolve: (obj) => obj.regionIndex,
},
subRegion: {
Expand Down
18 changes: 9 additions & 9 deletions src/models/location.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export default class Location {
constructor(lat = "", lng = "", place_id = "") {
this.lat = lat
this.lng = lng
this.place_id = place_id
}
lat: string
lng: string
place_id: string
}
constructor(lat = '', lng = '', place_id = '') {
this.lat = lat;
this.lng = lng;
this.place_id = place_id;
}
lat?: string;
lng?: string;
place_id?: string;
}
9 changes: 2 additions & 7 deletions src/models/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,15 @@ import Movie from '../models/movie';
import theater from '../models/theater';

export default class Schedule {
constructor(
scheduleUrl = '',
theaterName = '',
timesStrings = [],
theaterExtension = new theater()
) {
constructor(scheduleUrl = '', theaterName = '', timesStrings = [], theaterExtension = new theater()) {
this.scheduleUrl = scheduleUrl;
this.theaterName = theaterName;
this.timesStrings = timesStrings;
this.theaterExtension = theaterExtension;
}
scheduleUrl?: string;
movie?: Movie;
movieName: string;
movieName?: string;
theaterName?: string;
level?: string;
timesStrings?: string[];
Expand Down
2 changes: 1 addition & 1 deletion src/models/theater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default class Theater {
phone?: string;
region?: string;
subRegion?: string;
regionIndex?: number;
regionIndex?: string;
location?: Location;
distance?: number;
scheduleUrl?: string;
Expand Down
15 changes: 12 additions & 3 deletions src/test/theaterCrawler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,17 @@ describe('theaterCrawler', () => {
describe('getTheaterListByRegion(a02)', () => {
it('length.should.eq(1)', async function () {
this.timeout(20000);
let theaterList = await getTheaterListByRegion({ regionId: 'a01', name: "基隆" }, 1);
expect(theaterList[0]).contain({"name":"基隆秀泰影城","url":"https://www.showtimes.com.tw/events?corpId=5","scheduleUrl":"/showtime/t02g04/a01/","address":"基隆市中正區信一路177號","phone":"(02)2421-2388","region":"基隆","regionIndex":1,"subRegion":"基隆"})
const theaterList = await getTheaterListByRegion({ regionId: 'a01', name: '基隆' }, 1);
expect(theaterList[0]).contain({
name: '基隆秀泰影城',
url: 'https://www.showtimes.com.tw/events?corpId=5',
scheduleUrl: '/showtime/t02g04/a01/',
address: '基隆市中正區信一路177號',
phone: '(02)2421-2388',
region: '基隆',
regionIndex: '1',
subRegion: '基隆',
});
theaterList.length.should.eq(1);
});
});
Expand All @@ -23,4 +32,4 @@ describe('theaterCrawler', () => {
regionList.length.should.above(0);
});
});
});
});

0 comments on commit 732cda6

Please sign in to comment.