feat: add stable macOS app download to website
CI/CD / preconditions (pull_request) Successful in 1s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m3s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m41s
CI/CD / test_and_check_app (pull_request) Successful in 6m53s
CI/CD / Publish immutable image (pull_request) Skipped

This commit is contained in:
2026-09-28 09:12:37 +02:00
parent aece653970
commit d18fffe665
12 changed files with 310 additions and 31 deletions
+18 -1
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { Link, usePage } from '@inertiajs/vue3';
import { Monitor, Moon, Plus, Sun } from 'lucide-vue-next';
import { Download, Monitor, Moon, Plus, Sun } from 'lucide-vue-next';
import { computed } from 'vue';
import Breadcrumbs from '@/components/Breadcrumbs.vue';
import UserMenuContent from '@/components/UserMenuContent.vue';
@@ -23,6 +23,7 @@ import {
} from '@/composables/useAppearance';
import { useCurrentUrl } from '@/composables/useCurrentUrl';
import { useLocalization } from '@/composables/useLocalization';
import { useMacDownload } from '@/composables/useMacDownload';
import { create as createProject } from '@/routes/projects';
import { index as board } from '@/routes/projects/board';
import { create as createTicket } from '@/routes/projects/tickets';
@@ -35,6 +36,7 @@ const page = usePage();
const { project } = useAppNavigation();
const { isCurrentUrl } = useCurrentUrl();
const { t } = useLocalization();
const { macDownloadUrl, showMacDownload } = useMacDownload();
const { appearance, updateAppearance } = useAppearance();
const themeIcon = computed(
() => ({ light: Sun, dark: Moon, system: Monitor })[appearance.value],
@@ -69,6 +71,21 @@ const createAction = computed(() => {
<span v-else class="truncate font-semibold">KanbanGit</span>
</div>
<div class="flex shrink-0 items-center gap-1 sm:gap-2">
<Button
v-if="showMacDownload"
as-child
variant="outline"
:aria-label="t('navigation.downloadAppAppleSilicon')"
:title="t('navigation.downloadAppAppleSilicon')"
class="px-3"
>
<a :href="macDownloadUrl"
><Download aria-hidden="true" /><span
class="hidden sm:inline"
>{{ t('navigation.downloadApp') }}</span
></a
>
</Button>
<Button
v-if="createAction"
as-child
@@ -0,0 +1,30 @@
import { onMounted, ref } from 'vue';
import updateSource from '../../../native/update-source.json';
export const macDownloadUrl = `${updateSource.baseUrl}/aarch64-apple-darwin/KanbanGit.dmg`;
export function isMacDownloadAvailable(
userAgent: string,
maxTouchPoints: number,
isTauri: unknown,
): boolean {
return (
userAgent.includes('Macintosh') &&
maxTouchPoints < 2 &&
isTauri !== true
);
}
export function useMacDownload() {
const showMacDownload = ref(false);
onMounted(() => {
showMacDownload.value = isMacDownloadAvailable(
navigator.userAgent,
navigator.maxTouchPoints,
Reflect.get(window, 'isTauri'),
);
});
return { macDownloadUrl, showMacDownload };
}
+20 -2
View File
@@ -1,20 +1,23 @@
<script setup lang="ts">
import { Link } from '@inertiajs/vue3';
import { ShieldCheck } from 'lucide-vue-next';
import { Download, ShieldCheck } from 'lucide-vue-next';
import type { Component } from 'vue';
import AppLogo from '@/components/AppLogo.vue';
import AppearanceTabs from '@/components/AppearanceTabs.vue';
import { Button } from '@/components/ui/button';
import { useLocalization } from '@/composables/useLocalization';
import { useMacDownload } from '@/composables/useMacDownload';
import { home } from '@/routes/index';
defineProps<{ title: string; description: string; icon?: Component }>();
const { t } = useLocalization();
const { macDownloadUrl, showMacDownload } = useMacDownload();
</script>
<template>
<div class="bg-background text-foreground flex min-h-dvh flex-col">
<header
class="bg-card px-page flex justify-center border-b py-4 sm:justify-start"
class="bg-card px-page flex items-center justify-between gap-3 border-b py-4"
>
<Link
:href="home()"
@@ -22,6 +25,21 @@ const { t } = useLocalization();
class="focus-visible:ring-ring/35 rounded-md outline-none focus-visible:ring-2"
><AppLogo
/></Link>
<Button
v-if="showMacDownload"
as-child
variant="outline"
size="sm"
:aria-label="t('navigation.downloadAppAppleSilicon')"
:title="t('navigation.downloadAppAppleSilicon')"
>
<a :href="macDownloadUrl"
><Download aria-hidden="true" /><span
class="hidden sm:inline"
>{{ t('navigation.downloadApp') }}</span
></a
>
</Button>
</header>
<main
class="gap-column sm:px-page mx-auto flex w-full max-w-md flex-1 flex-col justify-center px-4 py-8 sm:py-12"
+4
View File
@@ -718,6 +718,7 @@ export const en = {
'welcome.action.dashboard': 'Open dashboard',
'welcome.action.dashboardShort': 'Dashboard',
'welcome.actionHint': 'One workspace for boards, tickets, and Git context.',
'welcome.downloadAppHint': 'For Macs with Apple Silicon.',
'welcome.preview.label': 'KanbanGit board preview',
'welcome.preview.project': 'Product delivery board',
'welcome.preview.connected': 'Git connected',
@@ -899,6 +900,9 @@ export const en = {
'settings.eyebrow': 'Account settings',
'navigation.createTicket': 'New ticket',
'navigation.createProject': 'New project',
'navigation.downloadApp': 'Download App',
'navigation.downloadAppAppleSilicon':
'Download App for Macs with Apple Silicon',
'navigation.open': 'Open navigation',
'navigation.close': 'Close navigation',
'navigation.expand': 'Expand sidebar',
+41 -11
View File
@@ -1,13 +1,21 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowRight, Columns3, GitBranch, ListChecks } from 'lucide-vue-next';
import {
ArrowRight,
Columns3,
Download,
GitBranch,
ListChecks,
} from 'lucide-vue-next';
import { computed } from 'vue';
import AppLogo from '@/components/AppLogo.vue';
import { Button } from '@/components/ui/button';
import { useLocalization } from '@/composables/useLocalization';
import { useMacDownload } from '@/composables/useMacDownload';
import { dashboard, home, login } from '@/routes/index';
const { t } = useLocalization();
const { macDownloadUrl, showMacDownload } = useMacDownload();
const features = computed(() => [
{
@@ -112,16 +120,38 @@ const previewColumns = computed(() => [
{{ t('welcome.description') }}
</p>
</div>
<Button as-child size="lg">
<Link v-if="$page.props.auth.user" :href="dashboard()">
{{ t('welcome.action.dashboard') }}
<ArrowRight aria-hidden="true" />
</Link>
<Link v-else :href="login()">
{{ t('welcome.action.signIn') }}
<ArrowRight aria-hidden="true" />
</Link>
</Button>
<div
class="flex flex-wrap items-start justify-center gap-3"
>
<Button as-child size="lg">
<Link
v-if="$page.props.auth.user"
:href="dashboard()"
>
{{ t('welcome.action.dashboard') }}
<ArrowRight aria-hidden="true" />
</Link>
<Link v-else :href="login()">
{{ t('welcome.action.signIn') }}
<ArrowRight aria-hidden="true" />
</Link>
</Button>
<div
v-if="showMacDownload"
class="grid justify-items-center gap-1"
>
<Button as-child size="lg" variant="outline">
<a :href="macDownloadUrl"
><Download aria-hidden="true" />{{
t('navigation.downloadApp')
}}</a
>
</Button>
<p class="text-caption text-muted-foreground">
{{ t('welcome.downloadAppHint') }}
</p>
</div>
</div>
<p class="text-caption text-muted-foreground">
{{ t('welcome.actionHint') }}
</p>