diff --git a/.github/workflows/run_data_sync.yml b/.github/workflows/run_data_sync.yml index 52919bcb610..ef40eb8da1f 100644 --- a/.github/workflows/run_data_sync.yml +++ b/.github/workflows/run_data_sync.yml @@ -25,7 +25,7 @@ on: env: # please change to your own config. - RUN_TYPE: pass # support strava/nike/garmin/coros/garmin_cn/garmin_sync_cn_global/keep/only_gpx/only_fit/nike_to_strava/strava_to_garmin/tcx_to_garmin/strava_to_garmin_cn/garmin_to_strava/garmin_to_strava_cn/codoon/oppo, Please change the 'pass' it to your own + RUN_TYPE: pass # support strava/nike/garmin/coros/garmin_cn/garmin_sync_cn_global/keep/only_gpx/only_fit/nike_to_strava/strava_to_garmin/tcx_to_garmin/strava_to_garmin_cn/garmin_to_strava/garmin_to_strava_cn/codoon/oppo/db_updater, Please change the 'pass' it to your own ATHLETE: ben_29 TITLE: Workouts MIN_GRID_DISTANCE: 10 # change min distance here @@ -202,6 +202,11 @@ jobs: # If you want to sync fit activity in gpx format, please consider the following script: # python run_page/oppo_sync.py ${{ secrets.OPPO_ID }} ${{ secrets.OPPO_CLIENT_SECRET }} ${{ secrets.OPPO_CLIENT_REFRESH_TOKEN }} --with-gpx + - name: Run db updater script to add "Elevation Gain" field to db + if: env.RUN_TYPE == 'db_updater' + run: | + python run_page/db_updater.py + - name: Make svg GitHub profile if: env.RUN_TYPE != 'pass' run: | diff --git a/README-CN.md b/README-CN.md index 76939ef5938..a6429aeff83 100644 --- a/README-CN.md +++ b/README-CN.md @@ -2,6 +2,15 @@ ## note2: 2023.09.26 garmin need secret_string(and in Actions) get `python run_page/garmin_sync.py ${secret_string}` if cn `python run_page/garmin_sync.py ${secret_string} --is-cn` +## note3: 2024.08.19: Added `Elevation Gain` field, If you forked the project before this update, please run the following command: + - To resolve errors: `sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: activities.elevation_gain` + - For old data: To include `Elevation Gain` for past activities, perform a full reimport. + - If you don't have a local environment, set `RUN_TYPE` to `db_updater` in the `.github/workflows/run_data_sync.yml` file once then change back. + + ```bash + python run_page/db_updater.py + ``` + # [打造个人户外运动主页](http://workouts.ben29.xyz) ![screenshot](https://user-images.githubusercontent.com/6956444/163125711-24d0ad99-490d-4c04-b89f-5b7fe776eb38.png) diff --git a/README.md b/README.md index 6b1f96808bb..0f81194911e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ ## note2: 2023.09.26 garmin need secret_string(and in Actions) get `python run_page/garmin_sync.py ${secret_string}` if cn `python run_page/garmin_sync.py ${secret_string} --is-cn` +## note3: 2024.08.19: Added `Elevation Gain` field, If you forked the project before this update, please run the following command: + - To resolve errors: `sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: activities.elevation_gain` + - For old data: To include `Elevation Gain` for past activities, perform a full reimport. + - If you don't have a local environment, set `RUN_TYPE` to `db_updater` in the `.github/workflows/run_data_sync.yml` file once then change back. + + ```bash + python run_page/db_updater.py + ``` + # [Create a personal workouts home page](http://workouts.ben29.xyz) ![screenshot](https://user-images.githubusercontent.com/6956444/163125711-24d0ad99-490d-4c04-b89f-5b7fe776eb38.png) diff --git a/run_page/codoon_sync.py b/run_page/codoon_sync.py index e0b1020c2ed..7aad65f7ff1 100755 --- a/run_page/codoon_sync.py +++ b/run_page/codoon_sync.py @@ -477,7 +477,7 @@ def parse_points_to_gpx(self, run_points_data): for p in points_dict_list: point = gpxpy.gpx.GPXTrackPoint(**p) gpx_segment.points.append(point) - return gpx.to_xml() + return gpx def get_single_run_record(self, route_id): print(f"Get single run for codoon id {route_id}") @@ -528,11 +528,14 @@ def parse_raw_data_to_namedtuple( p["latitude"] = latlng_data[i][0] p["longitude"] = latlng_data[i][1] - if with_gpx: - # pass the track no points - if str(log_id) not in old_gpx_ids and run_points_data: - gpx_data = self.parse_points_to_gpx(run_points_data) - download_codoon_gpx(gpx_data, str(log_id)) + elevation_gain = None + if run_points_data: + gpx_data = self.parse_points_to_gpx(run_points_data) + elevation_gain = gpx_data.get_uphill_downhill().uphill + if with_gpx: + # pass the track no points + if str(log_id) not in old_gpx_ids: + download_codoon_gpx(gpx_data.to_xml(), str(log_id)) heart_rate_dict = run_data.get("heart_rate") heart_rate = None if heart_rate_dict: @@ -569,6 +572,7 @@ def parse_raw_data_to_namedtuple( seconds=int((end_date.timestamp() - start_date.timestamp())) ), "average_speed": run_data["total_length"] / run_data["total_time"], + "elevation_gain": elevation_gain, "location_country": location_country, "source": "Codoon", } diff --git a/run_page/db_updater.py b/run_page/db_updater.py new file mode 100644 index 00000000000..9777be486d2 --- /dev/null +++ b/run_page/db_updater.py @@ -0,0 +1,25 @@ +from generator.db import init_db, Activity +from config import SQL_FILE +import sqlalchemy +from sqlalchemy import text +from config import GPX_FOLDER, JSON_FILE, SQL_FILE +from utils import make_activities_file + + +def add_column_elevation_gain(session): + # check if column elevation_gain is already added + # if not add it to the db + try: + session.query(Activity).first() + print("column elevation_gain already added, skipping") + except sqlalchemy.exc.OperationalError: + sql_statement = 'alter TABLE "activities" add column elevation_gain Float after average_heartrate' + session.execute(text(sql_statement)) + print("column elevation_gain added successfully") + + +if __name__ == "__main__": + session = init_db(SQL_FILE) + add_column_elevation_gain(session) + # regenerate activities + make_activities_file(SQL_FILE, GPX_FOLDER, JSON_FILE) diff --git a/run_page/endomondo_sync.py b/run_page/endomondo_sync.py index 785c01535d3..cd5d89dbb45 100644 --- a/run_page/endomondo_sync.py +++ b/run_page/endomondo_sync.py @@ -68,6 +68,7 @@ def parse_run_endomondo_to_nametuple(en_dict): "average_speed": en_dict.get("distance_km", 0) / en_dict.get("duration_s", 1) * 1000, + "elevation_gain": None, "location_country": "", } return namedtuple("x", d.keys())(*d.values()) diff --git a/run_page/generator/__init__.py b/run_page/generator/__init__.py index de66d2e70fd..4366b45fe05 100644 --- a/run_page/generator/__init__.py +++ b/run_page/generator/__init__.py @@ -12,7 +12,7 @@ from .db import Activity, init_db, update_or_create_activity -from synced_data_file_logger import save_synced_data_file_list, load_fit_name_mapping +from synced_data_file_logger import save_synced_data_file_list IGNORE_BEFORE_SAVING = os.getenv("IGNORE_BEFORE_SAVING", False) @@ -67,6 +67,8 @@ def sync(self, force): if IGNORE_BEFORE_SAVING: activity.summary_polyline = filter_out(activity.summary_polyline) activity.source = "strava" + # strava use total_elevation_gain as elevation_gain + activity.elevation_gain = activity.total_elevation_gain created = update_or_create_activity(self.session, activity) if created: sys.stdout.write("+") diff --git a/run_page/generator/db.py b/run_page/generator/db.py index d93b28d9ae6..a11c92b3be1 100644 --- a/run_page/generator/db.py +++ b/run_page/generator/db.py @@ -36,6 +36,7 @@ def randomword(): "summary_polyline", "average_heartrate", "average_speed", + "elevation_gain", "source", ] @@ -55,6 +56,7 @@ class Activity(Base): summary_polyline = Column(String) average_heartrate = Column(Float) average_speed = Column(Float) + elevation_gain = Column(Float) streak = None source = Column(String) @@ -118,6 +120,7 @@ def update_or_create_activity(session, run_activity): location_country=location_country, average_heartrate=run_activity.average_heartrate, average_speed=float(run_activity.average_speed), + elevation_gain=float(run_activity.elevation_gain), summary_polyline=( run_activity.map and run_activity.map.summary_polyline or "" ), @@ -133,6 +136,7 @@ def update_or_create_activity(session, run_activity): activity.type = type activity.average_heartrate = run_activity.average_heartrate activity.average_speed = float(run_activity.average_speed) + activity.elevation_gain = float(run_activity.elevation_gain) activity.summary_polyline = ( run_activity.map and run_activity.map.summary_polyline or "" ) diff --git a/run_page/gpxtrackposter/track.py b/run_page/gpxtrackposter/track.py index 3df75e100e2..7802f3d0966 100644 --- a/run_page/gpxtrackposter/track.py +++ b/run_page/gpxtrackposter/track.py @@ -47,6 +47,7 @@ def __init__(self): self.length = 0 self.special = False self.average_heartrate = None + self.elevation_gain = None self.moving_dict = {} self.run_id = 0 self.start_latlng = [] @@ -166,6 +167,7 @@ def _load_tcx_data(self, tcx, file_name): except: pass self.polyline_str = polyline.encode(polyline_container) + self.elevation_gain = tcx.ascent self.moving_dict = { "distance": self.length, "moving_time": datetime.timedelta(seconds=moving_time), @@ -250,6 +252,7 @@ def _load_gpx_data(self, gpx): sum(heart_rate_list) / len(heart_rate_list) if heart_rate_list else None ) self.moving_dict = self._get_moving_data(gpx) + self.elevation_gain = gpx.get_uphill_downhill().uphill def _load_fit_data(self, fit: dict): _polylines = [] @@ -266,6 +269,9 @@ def _load_fit_data(self, fit: dict): self.average_heartrate = ( message["avg_heart_rate"] if "avg_heart_rate" in message else None ) + self.elevation_gain = ( + message["total_ascent"] if "total_ascent" in message else None + ) self.type = message["sport"].lower() # moving_dict @@ -320,6 +326,10 @@ def append(self, other): ) self.file_names.extend(other.file_names) self.special = self.special or other.special + self.average_heartrate = self.average_heartrate or other.average_heartrate + self.elevation_gain = ( + self.elevation_gain if self.elevation_gain else 0 + ) + (other.elevation_gain if other.elevation_gain else 0) except: print( f"something wrong append this {self.end_time},in files {str(self.file_names)}" @@ -355,6 +365,7 @@ def to_namedtuple(self): "average_heartrate": ( int(self.average_heartrate) if self.average_heartrate else None ), + "elevation_gain": (int(self.elevation_gain) if self.elevation_gain else 0), "map": run_map(self.polyline_str), "start_latlng": self.start_latlng, "source": self.source, diff --git a/run_page/joyrun_sync.py b/run_page/joyrun_sync.py index 6b8093bab13..64ab10809c3 100644 --- a/run_page/joyrun_sync.py +++ b/run_page/joyrun_sync.py @@ -9,6 +9,7 @@ from datetime import datetime, timedelta from hashlib import md5 from urllib.parse import quote +from xml.etree import ElementTree import gpxpy import polyline @@ -189,13 +190,21 @@ def parse_content_to_ponits(content): @staticmethod def parse_points_to_gpx( - run_points_data, start_time, end_time, pause_list, interval=5 + run_points_data, + start_time, + end_time, + heart_rate_list=None, + altitude_list=None, + pause_list=[], + interval=5, ): """ parse run_data content to gpx object TODO for now kind of same as `keep` maybe refactor later :param run_points_data: [[latitude, longitude],...] + :param heart_rate_list: [heart_rate, ...] + :param altitude_list: [altitude, ...] :param pause_list: [[interval_index, pause_seconds],...] :param interval: time interval between each point, in seconds """ @@ -211,6 +220,8 @@ def parse_points_to_gpx( "longitude": point[1], "time": datetime.utcfromtimestamp(current_time), } + if altitude_list and len(altitude_list) > index: + points_dict["elevation"] = altitude_list[index] points_dict_list.append(points_dict) current_time += interval @@ -220,13 +231,14 @@ def parse_points_to_gpx( current_time += int(pause_list[0][1]) pause_list.pop(0) - points_dict_list.append( - { - "latitude": run_points_data[-1][0], - "longitude": run_points_data[-1][1], - "time": datetime.utcfromtimestamp(end_time), - } - ) + last = { + "latitude": run_points_data[-1][0], + "longitude": run_points_data[-1][1], + "time": datetime.utcfromtimestamp(end_time), + } + if altitude_list and len(altitude_list) > len(run_points_data): + last["elevation"] = altitude_list[len(run_points_data) - 1] + points_dict_list.append(last) segment_list.append(points_dict_list) # gpx part @@ -237,14 +249,24 @@ def parse_points_to_gpx( gpx.tracks.append(gpx_track) # add segment list to our GPX track: + i = 0 for point_list in segment_list: gpx_segment = gpxpy.gpx.GPXTrackSegment() gpx_track.segments.append(gpx_segment) for p in point_list: point = gpxpy.gpx.GPXTrackPoint(**p) + if heart_rate_list and len(heart_rate_list) > i: + gpx_extension_hr = ElementTree.fromstring( + f""" + {heart_rate_list[i]} + + """ + ) + i += 1 + point.extensions.append(gpx_extension_hr) gpx_segment.points.append(point) - return gpx.to_xml() + return gpx def get_single_run_record(self, fid): payload = { @@ -267,25 +289,40 @@ def parse_raw_data_to_nametuple(self, run_data, old_gpx_ids, with_gpx=False): end_time = run_data["endtime"] pause_list = run_data["pause"] run_points_data = self.parse_content_to_ponits(run_data["content"]) - if with_gpx: - # pass the track no points - if run_points_data: - gpx_data = self.parse_points_to_gpx( - run_points_data, start_time, end_time, pause_list - ) - download_joyrun_gpx(gpx_data, str(joyrun_id)) + altitude_list = run_data["altitude"] + try: heart_rate_list = ( eval(run_data["heartrate"]) if run_data["heartrate"] else None ) except: - print(f"Heart Rate: can not eval for {str(heart_rate_list)}") + print(f"Heart Rate: can not eval for {str(run_data['heartrate'''])}") + try: + altitude_list = eval(altitude_list) if altitude_list else None + except: + print(f"Altitude: can not eval for {str(altitude_list)}") heart_rate = None if heart_rate_list: heart_rate = int(sum(heart_rate_list) / len(heart_rate_list)) # fix #66 if heart_rate < 0: heart_rate = None + elevation_gain = None + # pass the track no points + if run_points_data: + gpx_data = self.parse_points_to_gpx( + run_points_data, + start_time, + end_time, + heart_rate_list, + altitude_list, + pause_list, + ) + elevation_gain = gpx_data.get_uphill_downhill().uphill + if with_gpx: + # pass the track no points + if str(joyrun_id) not in old_gpx_ids: + download_joyrun_gpx(gpx_data.to_xml(), str(joyrun_id)) polyline_str = polyline.encode(run_points_data) if run_points_data else "" start_latlng = start_point(*run_points_data[0]) if run_points_data else None @@ -319,6 +356,7 @@ def parse_raw_data_to_nametuple(self, run_data, old_gpx_ids, with_gpx=False): seconds=int((run_data["endtime"] - run_data["starttime"])) ), "average_speed": run_data["meter"] / run_data["second"], + "elevation_gain": elevation_gain, "location_country": location_country, "source": "Joyrun", } diff --git a/run_page/keep_sync.py b/run_page/keep_sync.py index 149562d4fc7..d82c527e36f 100644 --- a/run_page/keep_sync.py +++ b/run_page/keep_sync.py @@ -108,6 +108,7 @@ def parse_raw_data_to_nametuple( start_time = run_data["startTime"] avg_heart_rate = None + elevation_gain = None decoded_hr_data = [] if run_data["heartRate"]: avg_heart_rate = run_data["heartRate"].get("averageHeartRate", None) @@ -134,14 +135,13 @@ def parse_raw_data_to_nametuple( p_hr = find_nearest_hr(decoded_hr_data, int(p["timestamp"]), start_time) if p_hr: p["hr"] = p_hr - if with_download_gpx: - if str(keep_id) not in old_gpx_ids and run_data["dataType"].startswith( - "outdoor" - ): - gpx_data = parse_points_to_gpx( - run_points_data_gpx, start_time, KEEP2STRAVA[run_data["dataType"]] - ) - download_keep_gpx(gpx_data, str(keep_id)) + if run_data["dataType"].startswith("outdoor"): + gpx_data = parse_points_to_gpx( + run_points_data_gpx, start_time, KEEP2STRAVA[run_data["dataType"]] + ) + elevation_gain = gpx_data.get_uphill_downhill().uphill + if with_download_gpx and str(keep_id) not in old_gpx_ids: + download_keep_gpx(gpx_data.to_xml(), str(keep_id)) else: print(f"ID {keep_id} no gps data") polyline_str = polyline.encode(run_points_data) if run_points_data else "" @@ -165,6 +165,7 @@ def parse_raw_data_to_nametuple( "end_local": datetime.strftime(end_local, "%Y-%m-%d %H:%M:%S"), "length": run_data["distance"], "average_heartrate": int(avg_heart_rate) if avg_heart_rate else None, + "elevation_gain": run_data["accumulativeUpliftedHeight"], "map": run_map(polyline_str), "start_latlng": start_latlng, "distance": run_data["distance"], @@ -173,6 +174,7 @@ def parse_raw_data_to_nametuple( seconds=int((run_data["endTime"] - run_data["startTime"]) / 1000) ), "average_speed": run_data["distance"] / run_data["duration"], + "elevation_gain": elevation_gain, "location_country": str(run_data.get("region", "")), "source": "Keep", } @@ -232,7 +234,7 @@ def parse_points_to_gpx(run_points_data, start_time, sport_type): (point["timestamp"] * 100 + start_time) / 1000 # note that the timestamp of a point is decisecond(分秒) ), - "elevation": point.get("verticalAccuracy"), + "elevation": point.get("altitude"), "hr": point.get("hr"), } points_dict_list.append(points_dict) @@ -262,7 +264,7 @@ def parse_points_to_gpx(run_points_data, start_time, sport_type): ) point.extensions.append(gpx_extension_hr) gpx_segment.points.append(point) - return gpx.to_xml() + return gpx def find_nearest_hr( diff --git a/run_page/nike_sync.py b/run_page/nike_sync.py index 0f6242cbf81..66144cb7de1 100644 --- a/run_page/nike_sync.py +++ b/run_page/nike_sync.py @@ -374,6 +374,7 @@ def parse_no_gpx_data(activity): "moving_time": moving_time, "elapsed_time": elapsed_time, "average_speed": distance / int(activity["active_duration_ms"] / 1000), + "elevation_gain": 0, "location_country": "", } return namedtuple("x", d.keys())(*d.values()) diff --git a/run_page/oppo_sync.py b/run_page/oppo_sync.py index cf5bf803f6b..1af9169eabf 100644 --- a/run_page/oppo_sync.py +++ b/run_page/oppo_sync.py @@ -189,6 +189,7 @@ def parse_raw_data_to_name_tuple(sport_data, with_gpx, with_tcx): start_time = sport_data["startTime"] other_data = sport_data["otherSportData"] avg_heart_rate = None + elevation_gain = None if other_data: avg_heart_rate = other_data.get("avgHeartRate", None) # fix #66 @@ -206,9 +207,10 @@ def parse_raw_data_to_name_tuple(sport_data, with_gpx, with_tcx): point_dict = prepare_track_points(sport_data, with_gpx) + gpx_data = parse_points_to_gpx(sport_data, point_dict) + elevation_gain = gpx_data.get_uphill_downhill().uphill if with_gpx is True: - gpx_data = parse_points_to_gpx(sport_data, point_dict) - download_keep_gpx(gpx_data, str(oppo_id)) + download_keep_gpx(gpx_data.to_xml(), str(oppo_id)) if with_tcx is True: parse_points_to_tcx(sport_data, point_dict) @@ -247,6 +249,7 @@ def parse_raw_data_to_name_tuple(sport_data, with_gpx, with_tcx): seconds=int((sport_data["endTime"] - sport_data["startTime"]) / 1000) ), "average_speed": other_data["totalDistance"] / other_data["totalTime"] * 1000, + "elevation_gain": elevation_gain, "location_country": location_country, "source": sport_data["deviceName"], } @@ -372,7 +375,7 @@ def parse_points_to_gpx(sport_data, points_dict_list): ) point.extensions.append(gpx_extension) gpx_segment.points.append(point) - return gpx.to_xml() + return gpx def download_keep_gpx(gpx_data, keep_id): diff --git a/run_page/tulipsport_sync.py b/run_page/tulipsport_sync.py index a99c12c91ad..6e1966765eb 100755 --- a/run_page/tulipsport_sync.py +++ b/run_page/tulipsport_sync.py @@ -98,6 +98,7 @@ def merge_summary_and_detail_to_nametuple(summary, detail): # end_date = datetime.strftime(summary["end_date"], "%Y-%m-%d %H:%M:%S") # end_date_local = datetime.strftime(summary["end_date_local"], "%Y-%m-%d %H:%M:%S") average_heartrate = int(detail["avg_hr"]) + elevation_gain = None map = run_map("") start_latlng = None distance = summary["distance"] @@ -121,6 +122,9 @@ def merge_summary_and_detail_to_nametuple(summary, detail): latlng_list = [[float(point[0]), float(point[1])] for point in point_list] map = run_map(polyline.encode(latlng_list)) + altitude_list = [point[2] for point in detail["map_data_list"]] + elevation_gain = compute_elevation_gain(altitude_list) + activity_db_instance = { "id": id, "name": name, @@ -134,6 +138,7 @@ def merge_summary_and_detail_to_nametuple(summary, detail): "moving_time": moving_time, "elapsed_time": elapsed_time, "average_speed": average_speed, + "elevation_gain": elevation_gain, "location_country": location_country, } return namedtuple("activity_db_instance", activity_db_instance.keys())( @@ -141,6 +146,14 @@ def merge_summary_and_detail_to_nametuple(summary, detail): ) +def compute_elevation_gain(altitudes): + total_gain = 0 + for i in range(1, len(altitudes)): + if altitudes[i] > altitudes[i - 1]: + total_gain += altitudes[i] - altitudes[i - 1] + return total_gain + + def find_last_tulipsport_start_time(track_ids): start_time = None tulipsport_ids = [ diff --git a/src/components/RunTable/RunRow.tsx b/src/components/RunTable/RunRow.tsx index 47e12f7bf77..1c8e34b7c74 100644 --- a/src/components/RunTable/RunRow.tsx +++ b/src/components/RunTable/RunRow.tsx @@ -11,6 +11,7 @@ interface IRunRowProperties { const RunRow = ({ elementIndex, locateActivity, run, runIndex, setRunIndex }: IRunRowProperties) => { const distance = (run.distance / 1000.0).toFixed(2); + const elevation_gain = run.elevation_gain?.toFixed(0); const paceParts = run.average_speed ? formatPace(run.average_speed) : null; const heartRate = run.average_heartrate; const type = run.type; @@ -35,6 +36,7 @@ const RunRow = ({ elementIndex, locateActivity, run, runIndex, setRunIndex }: IR {run.name} {type} {distance} + {elevation_gain} {paceParts} {heartRate && heartRate.toFixed(0)} {runTime} diff --git a/src/components/RunTable/index.tsx b/src/components/RunTable/index.tsx index 438c49de430..578675a2bd0 100644 --- a/src/components/RunTable/index.tsx +++ b/src/components/RunTable/index.tsx @@ -32,6 +32,10 @@ const RunTable = ({ sortFuncInfo === 'Type' ? a.type > b.type ? 1:-1 : b.type < a.type ? -1:1; const sortKMFunc: SortFunc = (a, b) => sortFuncInfo === 'KM' ? a.distance - b.distance : b.distance - a.distance; + const sortElevationGainFunc: SortFunc = (a, b) => + sortFuncInfo === 'Elevation Gain' + ? (a.elevation_gain ?? 0) - (b.elevation_gain ?? 0) + : (b.elevation_gain ?? 0) - (a.elevation_gain ?? 0); const sortPaceFunc: SortFunc = (a, b) => sortFuncInfo === 'Pace' ? a.average_speed - b.average_speed @@ -53,6 +57,7 @@ const RunTable = ({ const sortFuncMap = new Map([ ['Type', sortTypeFunc], ['KM', sortKMFunc], + ['Elevation Gain', sortElevationGainFunc], ['Pace', sortPaceFunc], ['BPM', sortBPMFunc], ['Time', sortRunTimeFunc], diff --git a/src/components/RunTable/style.module.css b/src/components/RunTable/style.module.css index 8f1787988ea..eeb0a5c2af2 100644 --- a/src/components/RunTable/style.module.css +++ b/src/components/RunTable/style.module.css @@ -1,3 +1,13 @@ +@media only screen and (max-width: 800px) { + + /* 当屏幕宽度小于 800px 时 */ + .runTable th:nth-child(3), + .runTable td:nth-child(3) { + display: none; + /* 隐藏第3列 */ + } +} + @media only screen and (max-width: 700px) { /* 当屏幕宽度小于 700px 时 */ diff --git a/src/components/YearStat/index.tsx b/src/components/YearStat/index.tsx index 2f781c5632e..5ac854dd2cf 100644 --- a/src/components/YearStat/index.tsx +++ b/src/components/YearStat/index.tsx @@ -20,12 +20,14 @@ const YearStat = ({ year, onClick, onClickTypeInYear }: { year: string, onClick: } let sumDistance = 0; let streak = 0; + let sumElevationGain = 0; let heartRate = 0; let heartRateNullCount = 0; const workoutsCounts = {}; runs.forEach((run) => { sumDistance += run.distance || 0; + sumElevationGain += run.elevation_gain || 0; if (run.average_speed) { if(workoutsCounts[run.type]){ var [oriCount, oriSecondsAvail, oriMetersAvail] = workoutsCounts[run.type] @@ -82,6 +84,13 @@ const YearStat = ({ year, onClick, onClickTypeInYear }: { year: string, onClick: }} /> ))} + { sumElevationGain > 0 && + + }