Skip to content

Commit

Permalink
Add a tag to the Changelog menu item and a sparkle icon to the user a…
Browse files Browse the repository at this point in the history
…vatar when a new version of TrackBear is deployed
  • Loading branch information
dispatchrabbi committed Sep 6, 2024
1 parent 68095bc commit 6e6a85b
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 60 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Types of changes include:
## Upcoming/Unreleased

- NEW: You can now enter progress in scenes and lines. Screenwriters and poets rejoice! (h/t Penny Gotch)
- NEW: The Changelog entry in the user menu will show when TrackBear has been updated (and the avatar will get a little sparkle icon too).
- FIXED: Fixed a bug where it would take two clicks on the sidebar to get from one starred project, goal, or leaderboard to another. (h/t Binna)

## 0.12.3
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"pinia": "^2.1.7",
"postcss": "^8.4.32",
"primeicons": "^7.0.0",
"primevue": "^3.50.0",
"primevue": "^3.53.0",
"prisma": "^5.11.0",
"tailwindcss": "^3.4.1",
"tsx": "^4.11.0",
Expand Down
30 changes: 21 additions & 9 deletions src/components/UserAvatar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,36 @@ import { defineProps } from 'vue';
import Avatar from 'primevue/avatar';
import CLASSES from 'src/components/classes.ts'
export type UserWithAvatar = {
avatar?: string;
displayName: string;
};
const props = defineProps<{
user: UserWithAvatar;
size?: 'normal' | 'large' | 'xlarge';
icon?: string;
iconClass?: 'primary' | 'accent';
}>();
</script>

<template>
<Avatar
:label="props.user.avatar ? null : '🐻'"
:image="'/uploads/avatars/' + props.user.avatar"
:size="props.size ?? 'normal'"
shape="circle"
:pt="{ image: { class: [ 'object-cover' ] } }"
:pt-options="{ mergeSections: true, mergeProps: true }"
:title="props.user.displayName"
/>
<div class="relative">
<Avatar
:label="props.user.avatar ? null : '🐻'"
:image="'/uploads/avatars/' + props.user.avatar"
:size="props.size ?? 'normal'"
shape="circle"
:pt="{ image: { class: [ 'object-cover' ] } }"
:pt-options="{ mergeSections: true, mergeProps: true }"
:title="props.user.displayName"
/>
<div
v-if="props.icon"
:class="['absolute -top-2 -left-2', CLASSES.TEXT[props.iconClass] ]"
>
<i :class="props.icon" />
</div>
</div>
</template>
18 changes: 18 additions & 0 deletions src/components/classes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// These need to be written out here because otherwise Tailwind will exclude them when building

export const TEXT = {
primary: 'text-primary-500 dark:text-primary-400',
accent: 'text-accent-500 dark:text-accent-400',
// TODO: secondary, danger, warning, info, success, error
};

export const BACKGROUND = {
primary: 'bg-primary-500 dark:bg-primary-400',
accent: 'bg-accent-500 dark:bg-accent-400',
// TODO: secondary, danger, warning, info, success, error
};

export default {
TEXT,
BACKGROUND,
};
110 changes: 72 additions & 38 deletions src/components/layout/AppBar.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
<script setup lang="ts">
import { ref, defineProps, defineEmits } from "vue";
import { ref, computed, defineProps, defineEmits, onMounted } from "vue";
import { RouterLink } from "vue-router";
import { useUserStore } from "src/stores/user.ts";
const userStore = useUserStore();
import { getChangelog } from 'src/lib/api/info.ts';
import { useLastChangelogViewed, findLatestChangelogVersion, cmpVersion } from 'src/lib/changelog.ts';
const lastChangelogViewed = useLastChangelogViewed();
const flagUpdates = ref(false);
import { PrimeIcons } from 'primevue/api';
import Button from "primevue/button";
import Menu from "primevue/menu";
import Breadcrumb from "primevue/breadcrumb";
import type { MenuItem } from 'primevue/menuitem';
import TrackbearMasthead from "./TrackbearMasthead.vue";
import UserAvatar from "../UserAvatar.vue";
import Tag from 'primevue/tag';
const props = defineProps<{
breadcrumbs: MenuItem[];
Expand All @@ -26,28 +32,46 @@ const toggleSidebar = function() {
}
const userMenu = ref(null);
const userMenuItems = ref<MenuItem[]>([
{
label: 'Settings',
items: [
{ icon: PrimeIcons.USER, label: 'Account', to: { name: 'account' } },
{ icon: PrimeIcons.TAG, label: 'Manage Tags', to: { name: 'tags' } },
] ,
},
{
label: 'TrackBear',
items: [
{ icon: PrimeIcons.INFO_CIRCLE, label: 'About', to: { name: 'about' } },
{ icon: PrimeIcons.WRENCH, label: 'Changelog', to: { name: 'changelog' } },
{ icon: PrimeIcons.SHIELD, label: 'Privacy', to: { name: 'privacy' } },
{ icon: PrimeIcons.HEART_FILL, label: 'Support the Dev', href: '/ko-fi', target: '_blank', iconColor: 'text-primary-500 dark:text-primary-400' },
]
},
{ separator: true },
{ icon: PrimeIcons.SIGN_OUT, label: 'Log Out', to: { name: 'logout' } },
]);
const userMenuItems = computed(() => {
return [
{
label: 'Settings',
items: [
{ icon: PrimeIcons.USER, label: 'Account', to: { name: 'account' } },
{ icon: PrimeIcons.TAG, label: 'Manage Tags', to: { name: 'tags' } },
] ,
},
{
label: 'TrackBear',
items: [
{ icon: PrimeIcons.INFO_CIRCLE, label: 'About', to: { name: 'about' } },
{ icon: PrimeIcons.WRENCH, label: 'Changelog', to: { name: 'changelog' }, tag: flagUpdates.value ? { text: 'Updates!', icon: PrimeIcons.SPARKLES } : undefined },
{ icon: PrimeIcons.SHIELD, label: 'Privacy', to: { name: 'privacy' } },
{ icon: PrimeIcons.HEART_FILL, label: 'Support the Dev', href: '/ko-fi', target: '_blank', iconColor: 'text-primary-500 dark:text-primary-400' },
]
},
{ separator: true },
{ icon: PrimeIcons.SIGN_OUT, label: 'Log Out', to: { name: 'logout' } },
] as MenuItem[];
});
const toggleUserMenu = ev => userMenu.value.toggle(ev);
async function checkForUpdates() {
try {
const result = await getChangelog();
const latestVersion = findLatestChangelogVersion(result);
if(cmpVersion(latestVersion, lastChangelogViewed.value) > 0) {
flagUpdates.value = true;
}
} catch(err) {
// swallow any error
}
}
onMounted(() => {
checkForUpdates();
});
</script>

<template>
Expand Down Expand Up @@ -99,6 +123,8 @@ const toggleUserMenu = ev => userMenu.value.toggle(ev);
>
<UserAvatar
:user="userStore.user"
:icon="flagUpdates ? PrimeIcons.SPARKLES : null"
icon-class="primary"
/>
<div class="font-light">
{{ userStore.user.displayName }}
Expand All @@ -113,23 +139,31 @@ const toggleUserMenu = ev => userMenu.value.toggle(ev);
:pt-options="{ mergeSections: true, mergeProps: true, }"
>
<template #item="{ item, props: itemProps }">
<RouterLink
v-if="!item.target"
:to="item.to"
v-bind="itemProps.action"
>
<span :class="[item.icon, item.iconColor]" />
<span class="leading-6 text-sm font-medium ml-2">{{ item.label }}</span>
</RouterLink>
<a
v-else
:href="item.href"
:target="item.target"
v-bind="itemProps.action"
>
<span :class="[item.icon, item.iconColor]" />
<span class="leading-6 text-sm font-medium ml-2">{{ item.label }}</span>
</a>
<div class="flex items-center">
<RouterLink
v-if="!item.target"
:to="item.to"
v-bind="itemProps.action"
>
<span :class="[item.icon, item.iconColor]" />
<span class="leading-6 text-sm font-medium ml-2">{{ item.label }}</span>
</RouterLink>
<a
v-else
:href="item.href"
:target="item.target"
v-bind="itemProps.action"
>
<span :class="[item.icon, item.iconColor]" />
<span class="leading-6 text-sm font-medium ml-2">{{ item.label }}</span>
</a>
<div class="spacer flex-auto" />
<Tag
v-if="item.tag"
:icon="item.tag.icon"
:value="item.tag.text"
/>
</div>
</template>
</Menu>
</div>
Expand Down
28 changes: 20 additions & 8 deletions src/components/pages/ChangelogPage.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
<script setup lang="ts">
import { ref } from 'vue';
import { ref, onMounted } from 'vue';
import { Changelog } from 'server/lib/parse-changelog.ts';
import { getChangelog } from 'src/lib/api/info.ts';
import { useLastChangelogViewed, findLatestChangelogVersion, cmpVersion } from 'src/lib/changelog.ts';
const lastChangelogViewed = useLastChangelogViewed();
import PorchLayout from 'src/layouts/PorchLayout.vue';
import TextBlurb from 'src/components/layout/TextBlurb.vue';
import ChangelogVersion from 'src/components/changelog/ChangelogVersion.vue';
const changelog = ref<Changelog>(null);
const errorMessage = ref<string>('');
function loadChangelog() {
getChangelog()
.then(c => changelog.value = c)
.catch(err => {
errorMessage.value = err.message;
});
async function loadChangelog() {
try {
const result = await getChangelog();
changelog.value = result;
const latestVersion = findLatestChangelogVersion(changelog.value);
if(cmpVersion(latestVersion, lastChangelogViewed.value) > 0) {
lastChangelogViewed.value = latestVersion;
}
} catch(err) {
errorMessage.value = err.message;
}
}
loadChangelog();
onMounted(() => {
loadChangelog();
});
// const CHANGELOG_FIXTURE = [
// {
Expand Down
40 changes: 40 additions & 0 deletions src/lib/changelog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import packageJson from '../../package.json' assert { type: 'json' };
import { useLocalStorage } from '@vueuse/core';

import { Changelog } from 'server/lib/parse-changelog.ts';

export function useLastChangelogViewed() {
return useLocalStorage('last-changelog-viewed', '0.0.0');
}

export function getCurrentVersion() {
return packageJson.version;
}

export function findLatestChangelogVersion(changelog: Changelog) {
const versions = changelog
.map(c => c.version)
.filter(v => /\d+\.\d+\.\d+/.test(v))
.sort(cmpVersion)
.reverse();

return versions.length >= 1 ? versions[0] : '0.0.0';
}

export function cmpVersion(a: string, b: string) {
const aParts = a.split('.').map(x => +x);
const bParts = b.split('.').map(x => +x);
if(aParts.length !== bParts.length) {
throw new Error(`Cannot compare ${a} and ${b}; they do not have the same number of parts`);
}

for(let i = 0; i < aParts.length; ++i) {
if(aParts[i] < bParts[i]) {
return -1;
} else if(aParts[i] > bParts[i]) {
return 1;
} // else, on to the next dot down
}

return 0;
}
1 change: 1 addition & 0 deletions src/themes/primevue-presets/wind/tag/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default {
'text-white dark:text-surface-900',
{
'bg-primary-500 dark:bg-primary-400': props.severity == null || props.severity == 'primary',
'bg-accent-500 dark:bg-accent-400': props.severity == 'accent',
'bg-green-500 dark:bg-green-400': props.severity == 'success',
'bg-blue-500 dark:bg-blue-400': props.severity == 'info',
'bg-orange-500 dark:bg-orange-400': props.severity == 'warning',
Expand Down

0 comments on commit 6e6a85b

Please sign in to comment.