Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve repo list #2552

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web/src/components/repo/pipeline/PipelineLog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ const hasLogs = computed(
// we do not have logs for skipped steps
repo?.value && pipeline.value && step.value && step.value.state !== 'skipped' && step.value.state !== 'killed',
);
const autoScroll = useStorage('log-auto-scroll', false);
const autoScroll = useStorage('woodpecker:log-auto-scroll', false);
const showActions = ref(false);
const downloadInProgress = ref(false);
const ansiUp = ref(new AnsiUp());
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/repo/settings/BadgeTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import { Repo } from '~/lib/api/types';
const apiClient = useApiClient();
const repo = inject<Ref<Repo>>('repo');

const badgeType = useStorage('last-badge-type', 'markdown');
const badgeType = useStorage('woodpecker:last-badge-type', 'markdown');

if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/user/UserGeneralTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</template>

<script lang="ts" setup>
import { useLocalStorage } from '@vueuse/core';
import { useStorage } from '@vueuse/core';
import dayjs from 'dayjs';
import TimeAgo from 'javascript-time-ago';
import { SUPPORTED_LOCALES } from 'virtual:vue-i18n-supported-locales';
Expand All @@ -27,7 +27,7 @@ const localeOptions = computed(() =>
})),
);

const storedLocale = useLocalStorage('woodpecker:locale', locale.value);
const storedLocale = useStorage('woodpecker:locale', locale.value);
const selectedLocale = computed<string>({
async set(_selectedLocale) {
await setI18nLanguage(_selectedLocale);
Expand Down
61 changes: 21 additions & 40 deletions web/src/compositions/useDarkMode.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,28 @@
import { computed, ref, watch } from 'vue';
import { useStorage } from '@vueuse/core';
import { watch } from 'vue';

const LS_DARK_MODE = 'woodpecker:dark-mode';
const isDarkModeActive = ref(false);
const isDarkModeActive = useStorage('woodpecker:dark-mode', window.matchMedia('(prefers-color-scheme: dark)').matches);

watch(isDarkModeActive, (isActive) => {
if (isActive) {
document.documentElement.classList.remove('light');
document.documentElement.classList.add('dark');
document.documentElement.setAttribute('data-theme', 'dark');
document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#2A2E3A'); // internal-wp-secondary-600 (see windi.config.ts)
} else {
document.documentElement.classList.remove('dark');
document.documentElement.classList.add('light');
document.documentElement.setAttribute('data-theme', 'light');
document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#369943'); // internal-wp-primary-400
}
});

function setDarkMode(isActive: boolean) {
isDarkModeActive.value = isActive;
localStorage.setItem(LS_DARK_MODE, isActive ? 'dark' : 'light');
}

function load() {
const isActive = localStorage.getItem(LS_DARK_MODE) as 'dark' | 'light' | null;
if (isActive === null) {
setDarkMode(window.matchMedia('(prefers-color-scheme: dark)').matches);
} else {
setDarkMode(isActive === 'dark');
}
}

load();
watch(
isDarkModeActive,
(isActive) => {
if (isActive) {
document.documentElement.classList.remove('light');
document.documentElement.classList.add('dark');
document.documentElement.setAttribute('data-theme', 'dark');
document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#2A2E3A'); // internal-wp-secondary-600 (see windi.config.ts)
} else {
document.documentElement.classList.remove('dark');
document.documentElement.classList.add('light');
document.documentElement.setAttribute('data-theme', 'light');
document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#369943'); // internal-wp-primary-400
}
},
{ immediate: true },
);

export function useDarkMode() {
return {
darkMode: computed({
get() {
return isDarkModeActive.value;
},
set(isActive: boolean) {
setDarkMode(isActive);
},
}),
darkMode: isDarkModeActive,
};
}
25 changes: 25 additions & 0 deletions web/src/compositions/useRepos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useStorage } from '@vueuse/core';

import { Repo } from '~/lib/api/types';

export default function useRepos() {
const lastAccess = useStorage('woodpecker:repo-last-access', new Map<number, number>());

function sortReposByLastAccess(repos: Repo[]): Repo[] {
return repos.sort((a, b) => {
const aLastAccess = lastAccess.value.get(a.id) || 0;
const bLastAccess = lastAccess.value.get(b.id) || 0;

return bLastAccess - aLastAccess;
});
}

function updateLastAccess(repoId: number) {
lastAccess.value.set(repoId, Date.now());
}

return {
sortReposByLastAccess,
updateLastAccess,
};
}
21 changes: 4 additions & 17 deletions web/src/compositions/useUserConfig.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,19 @@
import { computed, ref } from 'vue';

const USER_CONFIG_KEY = 'woodpecker-user-config';
import { useStorage } from '@vueuse/core';
import { computed } from 'vue';

type UserConfig = {
isPipelineFeedOpen: boolean;
redirectUrl: string;
};

const defaultUserConfig: UserConfig = {
const config = useStorage<UserConfig>('woodpecker:user-config', {
isPipelineFeedOpen: false,
redirectUrl: '',
};

function loadUserConfig(): UserConfig {
const lsData = localStorage.getItem(USER_CONFIG_KEY);
if (!lsData) {
return defaultUserConfig;
}

return JSON.parse(lsData);
}

const config = ref<UserConfig>(loadUserConfig());
});

export default () => ({
setUserConfig<T extends keyof UserConfig>(key: T, value: UserConfig[T]): void {
config.value = { ...config.value, [key]: value };
localStorage.setItem(USER_CONFIG_KEY, JSON.stringify(config.value));
},
userConfig: computed(() => config.value),
});
1 change: 1 addition & 0 deletions web/src/lib/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default class ApiClient {
headers: {
...(method !== 'GET' && this.csrf ? { 'X-CSRF-TOKEN': this.csrf } : {}),
...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
...(data ? { 'Content-Type': 'application/json' } : {}),
},
body: data ? JSON.stringify(data) : undefined,
});
Expand Down
4 changes: 2 additions & 2 deletions web/src/utils/locale.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useLocalStorage } from '@vueuse/core';
import { useStorage } from '@vueuse/core';

export function getUserLanguage(): string {
const browserLocale = navigator.language.split('-')[0];
const selectedLocale = useLocalStorage('woodpecker:locale', browserLocale).value;
const selectedLocale = useStorage('woodpecker:locale', browserLocale).value;

return selectedLocale;
}
31 changes: 26 additions & 5 deletions web/src/views/Repos.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,49 @@
<Button :to="{ name: 'repo-add' }" start-icon="plus" :text="$t('repo.add')" />
</template>

<div class="space-y-4">
<ListItem v-for="repo in searchedRepos" :key="repo.id" :to="{ name: 'repo', params: { repoId: repo.id } }">
<span class="text-wp-text-100">{{ `${repo.owner} / ${repo.name}` }}</span>
</ListItem>
<div class="gap-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2">
<router-link v-for="repo in repoList" :key="repo.id" :to="{ name: 'repo', params: { repoId: repo.id } }">
<div
class="flex flex-col border rounded-md bg-wp-background-100 overflow-hidden p-4 border-wp-background-400 dark:bg-wp-background-200 cursor-pointer hover:shadow-md hover:bg-wp-background-300 dark:hover:bg-wp-background-300"
>
<div class="flex items-center gap-2">
<img v-if="repo.avatar_url" :src="repo.avatar_url" class="w-8 h-8" alt="..." />
<Icon v-else name="repo" class="text-wp-text-100" />
<span class="text-wp-text-100 text-xl">{{ `${repo.owner} / ${repo.name}` }}</span>
<Badge v-if="repo.visibility === RepoVisibility.Public" label="public" class="ml-auto" />
</div>

<div class="mt-8 flex gap-2">
<PipelineStatusIcon status="failure" />
<span class="text-wp-text-100">last pipeline was successful 20 mins ago</span>
</div>
</div>
</router-link>
</div>
</Scaffold>
</template>

<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';

import Badge from '~/components/atomic/Badge.vue';
import Button from '~/components/atomic/Button.vue';
import ListItem from '~/components/atomic/ListItem.vue';
import Icon from '~/components/atomic/Icon.vue';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import useRepos from '~/compositions/useRepos';
import { useRepoSearch } from '~/compositions/useRepoSearch';
import { RepoVisibility } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos';

const repoStore = useRepoStore();
const repos = computed(() => Object.values(repoStore.ownedRepos));
const search = ref('');

const { searchedRepos } = useRepoSearch(repos, search);
const { sortReposByLastAccess } = useRepos();

const repoList = computed(() => sortReposByLastAccess(searchedRepos.value || []));

onMounted(async () => {
await repoStore.loadRepos();
Expand Down
3 changes: 3 additions & 0 deletions web/src/views/repo/RepoWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import useApiClient from '~/compositions/useApiClient';
import useAuthentication from '~/compositions/useAuthentication';
import useConfig from '~/compositions/useConfig';
import useNotifications from '~/compositions/useNotifications';
import useRepos from '~/compositions/useRepos';
import { RepoPermissions } from '~/lib/api/types';
import { usePipelineStore } from '~/store/pipelines';
import { useRepoStore } from '~/store/repos';
Expand All @@ -76,6 +77,7 @@ const route = useRoute();
const router = useRouter();
const i18n = useI18n();
const config = useConfig();
const { updateLastAccess } = useRepos();

const { forge } = useConfig();
const repo = repoStore.getRepo(repositoryId);
Expand All @@ -102,6 +104,7 @@ async function loadRepo() {

await repoStore.loadRepo(repositoryId.value);
await pipelineStore.loadRepoPipelines(repositoryId.value);
updateLastAccess(repositoryId.value);
}

onMounted(() => {
Expand Down