Compare commits
4 Commits
a4514ad98d
...
5999eccc21
| Author | SHA1 | Date | |
|---|---|---|---|
| 5999eccc21 | |||
| 7155255733 | |||
| 6ff30a37f7 | |||
| 84e18dddfa |
@@ -1,6 +1,7 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { gql } from "@/graphql";
|
import { gql } from "@/graphql";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
export const useHomeDataStore = defineStore("homeData", () => {
|
export const useHomeDataStore = defineStore("homeData", () => {
|
||||||
const loaded = ref(false);
|
const loaded = ref(false);
|
||||||
@@ -11,22 +12,31 @@ export const useHomeDataStore = defineStore("homeData", () => {
|
|||||||
const favorites = ref([]);
|
const favorites = ref([]);
|
||||||
const activities = ref([]);
|
const activities = ref([]);
|
||||||
const spotifyRecent = ref([]);
|
const spotifyRecent = ref([]);
|
||||||
|
const rowingSessions = ref([]);
|
||||||
|
const gitFeed = ref(null);
|
||||||
|
const radioLive = ref(false);
|
||||||
|
|
||||||
async function fetchAll() {
|
async function fetchAll() {
|
||||||
try {
|
try {
|
||||||
const data = await gql(`
|
const [data] = await Promise.all([
|
||||||
|
gql(`
|
||||||
query HomeData {
|
query HomeData {
|
||||||
posts { id title content createdAt updatedAt author { id username } }
|
posts { id title content createdAt updatedAt author { id username } }
|
||||||
favorites { id type name link createdAt }
|
favorites { id type name link createdAt }
|
||||||
activities { id type name link createdAt }
|
activities { id type name link createdAt }
|
||||||
spotifyRecent { track { name album { name images { url } } artists { name } } playedAt }
|
spotifyRecent { track { name album { name images { url } } artists { name } } playedAt }
|
||||||
|
rowingSessions { id date time distance timePer500m calories }
|
||||||
me { id username admin }
|
me { id username admin }
|
||||||
}
|
}
|
||||||
`);
|
`),
|
||||||
|
fetchGitFeed(),
|
||||||
|
fetchRadioStatus(),
|
||||||
|
]);
|
||||||
posts.value = data.posts;
|
posts.value = data.posts;
|
||||||
favorites.value = data.favorites;
|
favorites.value = data.favorites;
|
||||||
activities.value = data.activities;
|
activities.value = data.activities;
|
||||||
spotifyRecent.value = data.spotifyRecent;
|
spotifyRecent.value = data.spotifyRecent;
|
||||||
|
rowingSessions.value = data.rowingSessions;
|
||||||
me.value = data.me || null;
|
me.value = data.me || null;
|
||||||
loaded.value = true;
|
loaded.value = true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -35,6 +45,24 @@ export const useHomeDataStore = defineStore("homeData", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchGitFeed() {
|
||||||
|
try {
|
||||||
|
const res = await axios.get("/gitea/api/v1/users/adamf/activities/feeds?limit=1");
|
||||||
|
gitFeed.value = res.data[0] || null;
|
||||||
|
} catch {
|
||||||
|
gitFeed.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchRadioStatus() {
|
||||||
|
try {
|
||||||
|
await axios.head("/radio/stream");
|
||||||
|
radioLive.value = true;
|
||||||
|
} catch {
|
||||||
|
radioLive.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fetchAll();
|
fetchAll();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -45,6 +73,10 @@ export const useHomeDataStore = defineStore("homeData", () => {
|
|||||||
favorites,
|
favorites,
|
||||||
activities,
|
activities,
|
||||||
spotifyRecent,
|
spotifyRecent,
|
||||||
|
rowingSessions,
|
||||||
|
gitFeed,
|
||||||
|
radioLive,
|
||||||
fetchAll,
|
fetchAll,
|
||||||
|
fetchRadioStatus,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import axios from "axios";
|
|
||||||
import Header from "@/components/text/Header.vue";
|
import Header from "@/components/text/Header.vue";
|
||||||
|
import { useHomeDataStore } from "@/stores/homeData";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
|
||||||
|
const store = useHomeDataStore();
|
||||||
|
const { loaded, error, rowingSessions } = storeToRefs(store);
|
||||||
|
|
||||||
|
const rows = computed(() => rowingSessions.value.slice().reverse());
|
||||||
|
const loading = computed(() => !loaded.value);
|
||||||
|
|
||||||
const rows = ref([]);
|
|
||||||
const loading = ref(true);
|
|
||||||
const error = ref(null);
|
|
||||||
const metric = ref("distance");
|
const metric = ref("distance");
|
||||||
const hovered = ref(null);
|
const hovered = ref(null);
|
||||||
|
|
||||||
@@ -15,18 +19,9 @@ const METRICS = [
|
|||||||
{ key: "calories", label: "Calories", color: "#62ff57" },
|
{ key: "calories", label: "Calories", color: "#62ff57" },
|
||||||
];
|
];
|
||||||
|
|
||||||
onMounted(async () => {
|
const activeMetric = computed(() =>
|
||||||
try {
|
METRICS.find((m) => m.key === metric.value),
|
||||||
const res = await axios.get("/api/rowing");
|
);
|
||||||
rows.value = res.data.slice().reverse(); // API returns DESC, reverse to chronological
|
|
||||||
} catch (e) {
|
|
||||||
error.value = e.message;
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const activeMetric = computed(() => METRICS.find((m) => m.key === metric.value));
|
|
||||||
|
|
||||||
// SVG layout constants
|
// SVG layout constants
|
||||||
const W = 290;
|
const W = 290;
|
||||||
@@ -43,7 +38,7 @@ const chartData = computed(() =>
|
|||||||
date: new Date(r.date),
|
date: new Date(r.date),
|
||||||
value: r[metric.value],
|
value: r[metric.value],
|
||||||
raw: r,
|
raw: r,
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
const minVal = computed(() => Math.min(...chartData.value.map((d) => d.value)));
|
const minVal = computed(() => Math.min(...chartData.value.map((d) => d.value)));
|
||||||
@@ -64,16 +59,25 @@ const points = computed(() => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
const polyline = computed(() => points.value.map((p) => `${p.x},${p.y}`).join(" "));
|
const polyline = computed(() =>
|
||||||
|
points.value.map((p) => `${p.x},${p.y}`).join(" "),
|
||||||
|
);
|
||||||
|
|
||||||
const xLabels = computed(() => {
|
const xLabels = computed(() => {
|
||||||
const data = chartData.value;
|
const data = chartData.value;
|
||||||
const pts = points.value;
|
const pts = points.value;
|
||||||
if (!data.length) return [];
|
if (!data.length) return [];
|
||||||
const indices = new Set([0, Math.floor((data.length - 1) / 2), data.length - 1]);
|
const indices = new Set([
|
||||||
|
0,
|
||||||
|
Math.floor((data.length - 1) / 2),
|
||||||
|
data.length - 1,
|
||||||
|
]);
|
||||||
return [...indices].map((i) => ({
|
return [...indices].map((i) => ({
|
||||||
x: pts[i].x,
|
x: pts[i].x,
|
||||||
label: data[i].date.toLocaleDateString("en-GB", { month: "short", day: "numeric" }),
|
label: data[i].date.toLocaleDateString("en-GB", {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -194,7 +198,9 @@ function formatValue(key, val) {
|
|||||||
font-size="10"
|
font-size="10"
|
||||||
fill="var(--primary)"
|
fill="var(--primary)"
|
||||||
font-family="var(--font_heading)"
|
font-family="var(--font_heading)"
|
||||||
>{{ yl.label }}</text>
|
>
|
||||||
|
{{ yl.label }}
|
||||||
|
</text>
|
||||||
|
|
||||||
<!-- X axis labels -->
|
<!-- X axis labels -->
|
||||||
<text
|
<text
|
||||||
@@ -206,11 +212,27 @@ function formatValue(key, val) {
|
|||||||
font-size="10"
|
font-size="10"
|
||||||
fill="var(--primary)"
|
fill="var(--primary)"
|
||||||
font-family="var(--font_heading)"
|
font-family="var(--font_heading)"
|
||||||
>{{ xl.label }}</text>
|
>
|
||||||
|
{{ xl.label }}
|
||||||
|
</text>
|
||||||
|
|
||||||
<!-- Axes -->
|
<!-- Axes -->
|
||||||
<line :x1="PL" :y1="PT" :x2="PL" :y2="PT + PLOT_H" stroke="var(--primary)" stroke-width="0.5" />
|
<line
|
||||||
<line :x1="PL" :y1="PT + PLOT_H" :x2="W - PR" :y2="PT + PLOT_H" stroke="var(--primary)" stroke-width="0.5" />
|
:x1="PL"
|
||||||
|
:y1="PT"
|
||||||
|
:x2="PL"
|
||||||
|
:y2="PT + PLOT_H"
|
||||||
|
stroke="var(--primary)"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
:x1="PL"
|
||||||
|
:y1="PT + PLOT_H"
|
||||||
|
:x2="W - PR"
|
||||||
|
:y2="PT + PLOT_H"
|
||||||
|
stroke="var(--primary)"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Tooltip -->
|
<!-- Tooltip -->
|
||||||
<g v-if="hovered !== null && points[hovered]">
|
<g v-if="hovered !== null && points[hovered]">
|
||||||
@@ -230,14 +252,24 @@ function formatValue(key, val) {
|
|||||||
font-size="12"
|
font-size="12"
|
||||||
fill="var(--secondary)"
|
fill="var(--secondary)"
|
||||||
font-family="var(--font_heading)"
|
font-family="var(--font_heading)"
|
||||||
>{{ points[hovered].date.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "2-digit" }) }}</text>
|
>
|
||||||
|
{{
|
||||||
|
points[hovered].date.toLocaleDateString("en-GB", {
|
||||||
|
day: "numeric",
|
||||||
|
month: "short",
|
||||||
|
year: "2-digit",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</text>
|
||||||
<text
|
<text
|
||||||
:x="Math.min(points[hovered].x + 7, W - 82)"
|
:x="Math.min(points[hovered].x + 7, W - 82)"
|
||||||
:y="points[hovered].y + 8"
|
:y="points[hovered].y + 8"
|
||||||
font-size="14"
|
font-size="14"
|
||||||
:fill="activeMetric.color"
|
:fill="activeMetric.color"
|
||||||
font-family="var(--font_heading)"
|
font-family="var(--font_heading)"
|
||||||
>{{ formatValue(metric, points[hovered].value) }}</text>
|
>
|
||||||
|
{{ formatValue(metric, points[hovered].value) }}
|
||||||
|
</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -246,15 +278,29 @@ function formatValue(key, val) {
|
|||||||
<div class="flex justify-between text-xs border-t border-quaternary pt-1">
|
<div class="flex justify-between text-xs border-t border-quaternary pt-1">
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<span class="text-primary font-heading">{{ rows.length }}</span>
|
<span class="text-primary font-heading">{{ rows.length }}</span>
|
||||||
<span class="text-quaternary" style="font-size: 0.6rem">sessions</span>
|
<span class="text-quaternary" style="font-size: 0.6rem"
|
||||||
|
>sessions</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<span class="text-primary font-heading">{{ rows.reduce((s, r) => s + r.distance, 0).toLocaleString() }}m</span>
|
<span class="text-primary font-heading"
|
||||||
<span class="text-quaternary" style="font-size: 0.6rem">total dist</span>
|
>{{
|
||||||
|
rows.reduce((s, r) => s + r.distance, 0).toLocaleString()
|
||||||
|
}}m</span
|
||||||
|
>
|
||||||
|
<span class="text-quaternary" style="font-size: 0.6rem"
|
||||||
|
>total dist</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<span class="text-primary font-heading">{{ formatTime(rows.reduce((s, r) => s + r.timePer500m, 0) / (rows.length || 1)) }}</span>
|
<span class="text-primary font-heading">{{
|
||||||
<span class="text-quaternary" style="font-size: 0.6rem">avg pace</span>
|
formatTime(
|
||||||
|
rows.reduce((s, r) => s + r.timePer500m, 0) / (rows.length || 1),
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
<span class="text-quaternary" style="font-size: 0.6rem"
|
||||||
|
>avg pace</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -264,7 +310,9 @@ function formatValue(key, val) {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.metric-btn {
|
.metric-btn {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.15s, color 0.15s;
|
transition:
|
||||||
|
background-color 0.15s,
|
||||||
|
color 0.15s;
|
||||||
letter-spacing: 0.03em;
|
letter-spacing: 0.03em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user