mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-25 02:26:14 +03:00
integrated the thign
This commit is contained in:
parent
09de80f6f0
commit
70422439d7
@ -6,7 +6,7 @@
|
||||
import ContextMenuItem from '$lib/components/contextmenu/ContextMenuItem.svelte';
|
||||
import ContextMenuSection from '$lib/components/contextmenu/ContextMenuSection.svelte';
|
||||
import { projectAiGenEnabled } from '$lib/config/config';
|
||||
import Modal from '$lib/shared/Modal.svelte';
|
||||
import Modal from '@gitbutler/ui/modal/Modal.svelte';
|
||||
import TextBox from '$lib/shared/TextBox.svelte';
|
||||
import Toggle from '$lib/shared/Toggle.svelte';
|
||||
import { User } from '$lib/stores/user';
|
||||
|
@ -94,7 +94,7 @@ export class BranchListing {
|
||||
* This includes any commits, uncommited changes or even updates to the branch metadata (e.g. renaming).
|
||||
*/
|
||||
@Transform((obj) => new Date(obj.value))
|
||||
updatedAt!: number;
|
||||
updatedAt!: Date;
|
||||
/** The person who commited the head commit */
|
||||
@Type(() => Author)
|
||||
lastCommiter!: Author;
|
||||
|
@ -7,10 +7,10 @@
|
||||
import { draggableCommit } from '$lib/dragging/draggable';
|
||||
import { DraggableCommit, nonDraggable } from '$lib/dragging/draggables';
|
||||
import BranchFilesList from '$lib/file/BranchFilesList.svelte';
|
||||
import Modal from '$lib/shared/Modal.svelte';
|
||||
import Modal from '@gitbutler/ui/modal/Modal.svelte';
|
||||
import { copyToClipboard } from '$lib/utils/clipboard';
|
||||
import { getContext, getContextStore } from '$lib/utils/context';
|
||||
import { getTimeAgo } from '$lib/utils/timeAgo';
|
||||
import { getTimeAgo } from '@gitbutler/ui/timeAgo/timeAgo';
|
||||
import { openExternalUrl } from '$lib/utils/url';
|
||||
import { BranchController } from '$lib/vbranches/branchController';
|
||||
import { createCommitStore } from '$lib/vbranches/contexts';
|
||||
|
@ -4,7 +4,7 @@
|
||||
import { projectMergeUpstreamWarningDismissed } from '$lib/config/config';
|
||||
import { getGitHost } from '$lib/gitHost/interface/gitHost';
|
||||
import { showInfo } from '$lib/notifications/toasts';
|
||||
import Modal from '$lib/shared/Modal.svelte';
|
||||
import Modal from '@gitbutler/ui/modal/Modal.svelte';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import { BranchController } from '$lib/vbranches/branchController';
|
||||
import Button from '@gitbutler/ui/inputs/Button.svelte';
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Modal from '../shared/Modal.svelte';
|
||||
import TextBox from '../shared/TextBox.svelte';
|
||||
import { PromptService } from '$lib/backend/prompt';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import Button from '@gitbutler/ui/inputs/Button.svelte';
|
||||
import Modal from '@gitbutler/ui/modal/Modal.svelte';
|
||||
|
||||
const promptService = getContext(PromptService);
|
||||
const [prompt, error] = promptService.reactToPrompt({ timeoutMs: 30000 });
|
||||
|
@ -4,7 +4,7 @@
|
||||
import { BaseBranchService } from '$lib/baseBranch/baseBranchService';
|
||||
import { RemotesService } from '$lib/remotes/service';
|
||||
import Link from '$lib/shared/Link.svelte';
|
||||
import Modal from '$lib/shared/Modal.svelte';
|
||||
import Modal from '@gitbutler/ui/modal/Modal.svelte';
|
||||
import TextBox from '$lib/shared/TextBox.svelte';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import { getMarkdownRenderer } from '$lib/utils/markdown';
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Modal from '$lib/shared/Modal.svelte';
|
||||
import Modal from '@gitbutler/ui/modal/Modal.svelte';
|
||||
import Button from '@gitbutler/ui/inputs/Button.svelte';
|
||||
|
||||
export let projectTitle: string = '#';
|
||||
|
@ -1,138 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Icon from '@gitbutler/ui/icon/Icon.svelte';
|
||||
import { createEventDispatcher, getContext, onMount } from 'svelte';
|
||||
import type { SegmentContext } from './segment';
|
||||
import type iconsJson from '@gitbutler/ui/icon/icons.json';
|
||||
|
||||
export let id: string;
|
||||
export let disabled = false;
|
||||
export let icon: keyof typeof iconsJson | undefined = undefined;
|
||||
export let label: string | undefined = undefined;
|
||||
export let size: 'small' | 'medium' = 'medium';
|
||||
|
||||
let ref: HTMLButtonElement | undefined;
|
||||
const dispatcher = createEventDispatcher<{ select: string }>();
|
||||
|
||||
const context = getContext<SegmentContext>('SegmentedControl');
|
||||
const index = context.setIndex();
|
||||
const focusedSegmentIndex = context.focusedSegmentIndex;
|
||||
const selectedSegmentIndex = context.selectedSegmentIndex;
|
||||
const length = context.length;
|
||||
|
||||
$: isFocused = $focusedSegmentIndex === index;
|
||||
$: if (isFocused) {
|
||||
ref?.focus();
|
||||
}
|
||||
$: isSelected = $selectedSegmentIndex === index;
|
||||
|
||||
onMount(() => {
|
||||
context.addSegment({ id, index, disabled });
|
||||
});
|
||||
</script>
|
||||
|
||||
<button
|
||||
bind:this={ref}
|
||||
class="segment-btn segment-{size}"
|
||||
class:left={index === 0}
|
||||
class:right={index === $length - 1}
|
||||
role="tab"
|
||||
tabindex={isSelected ? -1 : 0}
|
||||
aria-selected={isSelected}
|
||||
aria-disabled={disabled}
|
||||
{...$$restProps}
|
||||
on:mousedown|preventDefault={() => {
|
||||
if (index !== $selectedSegmentIndex && !disabled) {
|
||||
context.setSelected(index);
|
||||
dispatcher('select', id);
|
||||
}
|
||||
}}
|
||||
on:keydown={({ key }) => {
|
||||
if (key === 'Enter' || key === ' ') {
|
||||
if (index !== $selectedSegmentIndex && !disabled) {
|
||||
context.setSelected(index);
|
||||
dispatcher('select', id);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if label}
|
||||
<span class="label text-base-12">
|
||||
{label}
|
||||
</span>
|
||||
{/if}
|
||||
{#if icon}
|
||||
<div class="icon">
|
||||
<Icon name={icon} />
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<style lang="postcss">
|
||||
.segment-btn {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
|
||||
height: var(--size-button);
|
||||
|
||||
border-top-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 1px;
|
||||
|
||||
border-color: var(--clr-border-2);
|
||||
|
||||
transition: background var(--transition-fast);
|
||||
|
||||
&[aria-selected='true'] {
|
||||
background-color: var(--clr-bg-2);
|
||||
|
||||
cursor: default;
|
||||
|
||||
& > .label,
|
||||
& > .icon {
|
||||
color: var(--clr-scale-ntrl-50);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
&.left {
|
||||
border-left-width: 1px;
|
||||
border-top-left-radius: var(--radius-m);
|
||||
border-bottom-left-radius: var(--radius-m);
|
||||
}
|
||||
&.right {
|
||||
border-right-width: 1px;
|
||||
border-top-right-radius: var(--radius-m);
|
||||
border-bottom-right-radius: var(--radius-m);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--clr-scale-ntrl-30);
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--clr-scale-ntrl-30);
|
||||
}
|
||||
|
||||
/* MODIFIERS */
|
||||
.segment-small {
|
||||
height: var(--size-tag);
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.segment-medium {
|
||||
height: var(--size-button);
|
||||
padding: 4px 8px;
|
||||
}
|
||||
</style>
|
@ -1,58 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, setContext } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { SegmentContext, SegmentItem } from './segment';
|
||||
|
||||
export let wide = false;
|
||||
export let selectedIndex = 0;
|
||||
export let selected: string | undefined = undefined;
|
||||
|
||||
let dispatch = createEventDispatcher<{ select: string }>();
|
||||
|
||||
let indexesIterator = -1;
|
||||
let segments: SegmentItem[] = [];
|
||||
|
||||
let focusedSegmentIndex = writable(-1);
|
||||
let selectedSegmentIndex = writable(selectedIndex);
|
||||
let length = writable(0);
|
||||
|
||||
const context: SegmentContext = {
|
||||
focusedSegmentIndex,
|
||||
selectedSegmentIndex,
|
||||
length,
|
||||
setIndex: () => {
|
||||
indexesIterator += 1;
|
||||
return indexesIterator;
|
||||
},
|
||||
addSegment: ({ id, index, disabled }) => {
|
||||
segments = [...segments, { id, index, disabled }];
|
||||
length.set(segments.length);
|
||||
if (index === selectedIndex) selected = id;
|
||||
},
|
||||
setSelected: (segmentIndex) => {
|
||||
if (segmentIndex >= 0 && segmentIndex < segments.length) {
|
||||
$focusedSegmentIndex = segmentIndex;
|
||||
|
||||
if (!segments[segmentIndex].disabled) {
|
||||
$selectedSegmentIndex = $focusedSegmentIndex;
|
||||
selected = segments[segmentIndex].id;
|
||||
dispatch('select', selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
setContext<SegmentContext>('SegmentedControl', context);
|
||||
</script>
|
||||
|
||||
<div class="wrapper" class:wide>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.wrapper {
|
||||
display: inline-flex;
|
||||
&.wide {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,11 +0,0 @@
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export interface SegmentItem {
|
||||
index: number;
|
||||
}
|
||||
export interface SegmentContext {
|
||||
selectedSegmentIndex: Writable<number>;
|
||||
setIndex(): number;
|
||||
addSegment(segment: SegmentItem): void;
|
||||
setSelected({ index, id }: { index: number; id: string }): void;
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
import { HttpClient } from '$lib/backend/httpClient';
|
||||
import { invoke, listen } from '$lib/backend/ipc';
|
||||
import * as zip from '$lib/backend/zip';
|
||||
import Modal from '$lib/shared/Modal.svelte';
|
||||
import Modal from '@gitbutler/ui/modal/Modal.svelte';
|
||||
import { User } from '$lib/stores/user';
|
||||
import { getContext, getContextStore } from '$lib/utils/context';
|
||||
import * as toasts from '$lib/utils/toasts';
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { BaseBranchService } from '$lib/baseBranch/baseBranchService';
|
||||
import { getGitHostListingService } from '$lib/gitHost/interface/gitHostListingService';
|
||||
import TimeAgo from '$lib/shared/TimeAgo.svelte';
|
||||
import Button from '@gitbutler/ui/inputs/Button.svelte';
|
||||
import TimeAgo from '@gitbutler/ui/timeAgo/TimeAgo.svelte';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import { VirtualBranchService } from '$lib/vbranches/virtualBranch';
|
||||
import Button from '@gitbutler/ui/inputs/Button.svelte';
|
||||
|
||||
const baseBranchService = getContext(BaseBranchService);
|
||||
const vbranchService = getContext(VirtualBranchService);
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { clickOutside } from '$lib/clickOutside';
|
||||
import { createKeybind } from '$lib/utils/hotkeys';
|
||||
import { portal } from '$lib/utils/portal';
|
||||
import { portal } from '@gitbutler/ui/utils/portal';
|
||||
import { resizeObserver } from '$lib/utils/resizeObserver';
|
||||
import { type Snippet } from 'svelte';
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
import ContextMenuItem from '$lib/components/contextmenu/ContextMenuItem.svelte';
|
||||
import ContextMenuSection from '$lib/components/contextmenu/ContextMenuSection.svelte';
|
||||
import { editor } from '$lib/editorLink/editorLink';
|
||||
import Modal from '$lib/shared/Modal.svelte';
|
||||
import Modal from '@gitbutler/ui/modal/Modal.svelte';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import { computeFileStatus } from '$lib/utils/fileStatus';
|
||||
import * as toasts from '$lib/utils/toasts';
|
||||
|
@ -1,3 +0,0 @@
|
||||
export function gravatarUrl(id: string | undefined | null): string | undefined {
|
||||
if (id) return `https://www.gravatar.com/avatar/${id}?s=100&r=g&d=retro`;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import BranchIcon from '../branch/BranchIcon.svelte';
|
||||
import TimeAgo from '$lib/shared/TimeAgo.svelte';
|
||||
import TimeAgo from '@gitbutler/ui/timeAgo/TimeAgo.svelte';
|
||||
import type { CombinedBranch } from '$lib/branches/types';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
|
@ -1,41 +1,18 @@
|
||||
<script lang="ts">
|
||||
import BranchItemNew from './BranchItemNew.svelte';
|
||||
// import BranchItemNew from './BranchItemNew.svelte';
|
||||
import BranchesHeaderNew from './BranchesHeaderNew.svelte';
|
||||
import noBranchesSvg from '$lib/assets/empty-state/no-branches.svg?raw';
|
||||
import { getBranchServiceStore } from '$lib/branches/service';
|
||||
// import FilterButton from '$lib/components/FilterBranchesButton.svelte';
|
||||
// import { getGitHost } from '$lib/gitHost/interface/gitHost';
|
||||
// import { persisted } from '$lib/persisted/persisted';
|
||||
import { BranchListing, BranchListingService } from '$lib/branches/branchListing';
|
||||
import SmartSidebarEntry from '$lib/navigation/SmartSidebarEntry.svelte';
|
||||
import ScrollableContainer from '$lib/shared/ScrollableContainer.svelte';
|
||||
import { readable } from 'svelte/store';
|
||||
import type { CombinedBranch } from '$lib/branches/types';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
}
|
||||
const branchListingService = getContext(BranchListingService);
|
||||
// const gitHostListingService = getGitHostListingService();
|
||||
|
||||
const { projectId }: Props = $props();
|
||||
|
||||
const branchService = getBranchServiceStore();
|
||||
|
||||
let searchValue = $state<undefined | string>();
|
||||
let branches = $state($branchService?.branches || readable([]));
|
||||
let searchedBranches = $derived(filterByText($branches, searchValue));
|
||||
let groupedBranches = $derived(groupByDate(searchedBranches));
|
||||
|
||||
let viewport = $state<HTMLDivElement>();
|
||||
let contents = $state<HTMLElement>();
|
||||
|
||||
$effect(() => {
|
||||
branches = $branchService?.branches || readable([]);
|
||||
});
|
||||
|
||||
function filterByText(branches: CombinedBranch[], searchText: string | undefined) {
|
||||
// console.log('filterByText', branches, searchText);
|
||||
if (searchText === undefined || searchText === '') return branches;
|
||||
|
||||
return branches.filter((b) => searchMatchesAnIdentifier(searchText, b.searchableIdentifiers));
|
||||
}
|
||||
const branchesStore = branchListingService.branchListings;
|
||||
const branches = $derived($branchesStore || []);
|
||||
const searchedBranches = $derived(branches);
|
||||
|
||||
function searchMatchesAnIdentifier(search: string, identifiers: string[]) {
|
||||
for (const identifier of identifiers) {
|
||||
@ -45,32 +22,30 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
function groupByDate(branches: CombinedBranch[]) {
|
||||
const grouped: Record<string, CombinedBranch[]> = {
|
||||
const oneDay = 1000 * 60 * 60 * 24;
|
||||
function groupByDate(branches: BranchListing[]) {
|
||||
const grouped: Record<'today' | 'yesterday' | 'lastWeek' | 'older', BranchListing[]> = {
|
||||
today: [],
|
||||
yesterday: [],
|
||||
lastWeek: [],
|
||||
older: []
|
||||
};
|
||||
|
||||
const currentTs = new Date().getTime();
|
||||
const now = Date.now();
|
||||
|
||||
const remoteBranches = branches.filter((b) => b.remoteBranch);
|
||||
|
||||
remoteBranches.forEach((b) => {
|
||||
if (!b.modifiedAt) {
|
||||
branches.forEach((b) => {
|
||||
if (!b.updatedAt) {
|
||||
grouped.older.push(b);
|
||||
return;
|
||||
}
|
||||
|
||||
const modifiedAt = b.modifiedAt?.getTime();
|
||||
const ms = currentTs - modifiedAt;
|
||||
const msSinceLastCommit = now - b.updatedAt.getTime();
|
||||
|
||||
if (ms < 86400 * 1000) {
|
||||
if (msSinceLastCommit < oneDay) {
|
||||
grouped.today.push(b);
|
||||
} else if (ms < 2 * 86400 * 1000) {
|
||||
} else if (msSinceLastCommit < 2 * oneDay) {
|
||||
grouped.yesterday.push(b);
|
||||
} else if (ms < 7 * 86400 * 1000) {
|
||||
} else if (msSinceLastCommit < 7 * oneDay) {
|
||||
grouped.lastWeek.push(b);
|
||||
} else {
|
||||
grouped.older.push(b);
|
||||
@ -79,18 +54,22 @@
|
||||
|
||||
return grouped;
|
||||
}
|
||||
|
||||
const groupedBranches = $derived(groupByDate(searchedBranches));
|
||||
|
||||
let viewport = $state<HTMLDivElement>();
|
||||
let contents = $state<HTMLDivElement>();
|
||||
</script>
|
||||
|
||||
{#snippet branchGroup(props: {
|
||||
title: string,
|
||||
children: CombinedBranch[]
|
||||
|
||||
children: BranchListing[]
|
||||
})}
|
||||
{#if props.children.length > 0}
|
||||
<div class="group">
|
||||
<h3 class="text-base-12 text-semibold group-header">{props.title}</h3>
|
||||
{#each props.children as branch}
|
||||
<BranchItemNew {projectId} {branch} />
|
||||
{#each props.children as branchListing}
|
||||
<SmartSidebarEntry {branchListing} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
@ -98,7 +77,7 @@
|
||||
|
||||
<div class="branches">
|
||||
<BranchesHeaderNew
|
||||
totalBranchCount={$branches.length}
|
||||
totalBranchCount={branches.length}
|
||||
filteredBranchCount={searchedBranches?.length}
|
||||
onSearch={(value) => (searchValue = value)}
|
||||
>
|
||||
@ -114,10 +93,11 @@
|
||||
/>
|
||||
{/snippet} -->
|
||||
</BranchesHeaderNew>
|
||||
{#if $branches.length > 0}
|
||||
{#if branches.length > 0}
|
||||
{#if searchedBranches.length > 0}
|
||||
<ScrollableContainer
|
||||
bind:viewport
|
||||
bind:contents
|
||||
showBorderWhenScrolled
|
||||
fillViewport={searchedBranches.length === 0}
|
||||
>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import ProjectAvatar from './ProjectAvatar.svelte';
|
||||
import ProjectsPopup from './ProjectsPopup.svelte';
|
||||
import { Project } from '$lib/backend/projects';
|
||||
import ProjectAvatar from '$lib/navigation/ProjectAvatar.svelte';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import Icon from '@gitbutler/ui/icon/Icon.svelte';
|
||||
import { tooltip } from '@gitbutler/ui/utils/tooltip';
|
||||
|
@ -2,9 +2,9 @@
|
||||
import { ProjectService } from '$lib/backend/projects';
|
||||
import ScrollableContainer from '$lib/shared/ScrollableContainer.svelte';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import { portal } from '$lib/utils/portal';
|
||||
import { resizeObserver } from '$lib/utils/resizeObserver';
|
||||
import Icon from '@gitbutler/ui/icon/Icon.svelte';
|
||||
import { portal } from '@gitbutler/ui/utils/portal';
|
||||
import type iconsJson from '@gitbutler/ui/icon/icons.json';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
|
69
apps/desktop/src/lib/navigation/SmartSidebarEntry.svelte
Normal file
69
apps/desktop/src/lib/navigation/SmartSidebarEntry.svelte
Normal file
@ -0,0 +1,69 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
BranchListingDetails,
|
||||
BranchListingService,
|
||||
type BranchListing
|
||||
} from '$lib/branches/branchListing';
|
||||
import { getGitHostListingService } from '$lib/gitHost/interface/gitHostListingService';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import Avatar from '@gitbutler/ui/avatar/Avatar.svelte';
|
||||
import AvatarGrouping from '@gitbutler/ui/avatar/AvatarGrouping.svelte';
|
||||
import { gravatarUrlFromEmail } from '@gitbutler/ui/avatar/gravatar';
|
||||
import SidebarEntry from '@gitbutler/ui/sidebarEntry/SidebarEntry.svelte';
|
||||
|
||||
interface Props {
|
||||
branchListing: BranchListing;
|
||||
}
|
||||
|
||||
const { branchListing }: Props = $props();
|
||||
|
||||
const gitHostListingService = getGitHostListingService();
|
||||
const branchListingService = getContext(BranchListingService);
|
||||
|
||||
const prs = $derived($gitHostListingService?.prs);
|
||||
const pr = $derived($prs?.find((pr) => pr.sourceBranch === branchListing.name));
|
||||
|
||||
let branchListingDetails = $state<BranchListingDetails>();
|
||||
|
||||
async function onFirstSeen() {
|
||||
if (!branchListingDetails) {
|
||||
console.log('doing the math');
|
||||
branchListingDetails = await branchListingService.getBranchListingDetails(branchListing.name);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<SidebarEntry
|
||||
title={branchListing.name}
|
||||
remotes={branchListing.remotes}
|
||||
local={false}
|
||||
applied={!!branchListing.virtualBranch}
|
||||
lastCommitDetails={{
|
||||
authorName: branchListing.lastCommiter.name || 'Unknown',
|
||||
lastCommitAt: branchListing.updatedAt
|
||||
}}
|
||||
pullRequestDetails={pr && {
|
||||
title: pr.title
|
||||
}}
|
||||
branchDetails={branchListingDetails && {
|
||||
commitCount: branchListing.numberOfCommits,
|
||||
linesAdded: branchListingDetails.linesAdded,
|
||||
linesRemoved: branchListingDetails.linesRemoved
|
||||
}}
|
||||
{onFirstSeen}
|
||||
>
|
||||
{#snippet authorAvatars()}
|
||||
<AvatarGrouping>
|
||||
{#each branchListing.authors as author}
|
||||
{#await gravatarUrlFromEmail(author.email || 'example@example.com') then gravatarUrl}
|
||||
<Avatar
|
||||
srcUrl={gravatarUrl}
|
||||
size="medium"
|
||||
tooltipText={author.name || 'unknown'}
|
||||
altText={author.name || 'unknown'}
|
||||
/>
|
||||
{/await}
|
||||
{/each}
|
||||
</AvatarGrouping>
|
||||
{/snippet}
|
||||
</SidebarEntry>
|
@ -9,7 +9,7 @@
|
||||
import { getGitHostPrMonitor } from '$lib/gitHost/interface/gitHostPrMonitor';
|
||||
import { getGitHostPrService } from '$lib/gitHost/interface/gitHostPrService';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import { createTimeAgoStore } from '$lib/utils/timeAgo';
|
||||
import { createTimeAgoStore } from '@gitbutler/ui/timeAgo/timeAgo';
|
||||
import * as toasts from '$lib/utils/toasts';
|
||||
import { VirtualBranchService } from '$lib/vbranches/virtualBranch';
|
||||
import Button from '@gitbutler/ui/inputs/Button.svelte';
|
||||
|
@ -12,7 +12,7 @@
|
||||
import ScrollableContainer from '../shared/ScrollableContainer.svelte';
|
||||
import TextBox from '../shared/TextBox.svelte';
|
||||
import { KeyName } from '$lib/utils/hotkeys';
|
||||
import { portal } from '$lib/utils/portal';
|
||||
import { portal } from '@gitbutler/ui/utils/portal';
|
||||
import { resizeObserver } from '$lib/utils/resizeObserver';
|
||||
import { type Snippet } from 'svelte';
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { checkAuthStatus, initDeviceOauth } from '$lib/backend/github';
|
||||
import SectionCard from '$lib/components/SectionCard.svelte';
|
||||
import { getGitHubUserServiceStore } from '$lib/gitHost/github/githubUserService';
|
||||
import Modal from '$lib/shared/Modal.svelte';
|
||||
import Modal from '@gitbutler/ui/modal/Modal.svelte';
|
||||
import { UserService } from '$lib/stores/user';
|
||||
import { copyToClipboard } from '$lib/utils/clipboard';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
|
@ -7,7 +7,7 @@
|
||||
import Icon, { type IconColor } from '@gitbutler/ui/icon/Icon.svelte';
|
||||
import Button from '@gitbutler/ui/inputs/Button.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type iconsJson from '../icons/icons.json';
|
||||
import type iconsJson from '@gitbutler/ui/icon/icons.json';
|
||||
|
||||
export let icon: keyof typeof iconsJson | undefined = undefined;
|
||||
export let style: MessageStyle = 'neutral';
|
||||
|
@ -1,11 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createTimeAgoStore } from '$lib/utils/timeAgo';
|
||||
|
||||
export let date: Date | undefined;
|
||||
export let addSuffix = true;
|
||||
$: store = createTimeAgoStore(date, addSuffix);
|
||||
</script>
|
||||
|
||||
{#if store}
|
||||
{$store}
|
||||
{/if}
|
@ -71,7 +71,7 @@
|
||||
setContext(CommitDragActionsFactory, data.commitDragActionsFactory);
|
||||
setContext(ReorderDropzoneManagerFactory, data.reorderDropzoneManagerFactory);
|
||||
setContext(RemoteBranchService, data.remoteBranchService);
|
||||
setContext(branchListingService, branchListingService);
|
||||
setContext(BranchListingService, branchListingService);
|
||||
});
|
||||
|
||||
let intervalId: any;
|
||||
|
@ -11,7 +11,7 @@
|
||||
type Settings,
|
||||
type ScrollbarVisilitySettings
|
||||
} from '$lib/settings/userSettings';
|
||||
import Modal from '$lib/shared/Modal.svelte';
|
||||
import Modal from '@gitbutler/ui/modal/Modal.svelte';
|
||||
import RadioButton from '$lib/shared/RadioButton.svelte';
|
||||
import Spacer from '$lib/shared/Spacer.svelte';
|
||||
import TextBox from '$lib/shared/TextBox.svelte';
|
||||
|
@ -45,6 +45,7 @@
|
||||
"@sveltejs/vite-plugin-svelte": "catalog:svelte",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"cpy-cli": "^5.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-cli": "^11.0.0",
|
||||
"postcss-minify": "^1.1.0",
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import AvatarImage from './AvatarImage.svelte';
|
||||
import Avatar from '$lib/avatar/Avatar.svelte';
|
||||
import { tooltip } from '$lib/utils/tooltip';
|
||||
import { isDefined } from '$lib/utils/typeguards';
|
||||
import type { CommitNodeData, Color } from '$lib/CommitLines/types';
|
||||
@ -34,7 +34,7 @@
|
||||
>
|
||||
{#if commitNode.type === 'large' && commitNode.commit}
|
||||
<div class="large-node">
|
||||
<AvatarImage
|
||||
<Avatar
|
||||
srcUrl={commitNode.commit?.author.gravatarUrl ?? ''}
|
||||
tooltipText={hoverText}
|
||||
altText={`Gravatar for ${commitNode.commit.author.email}`}
|
||||
|
@ -6,14 +6,15 @@
|
||||
srcUrl: string;
|
||||
tooltipText: string;
|
||||
altText: string;
|
||||
size?: 'small' | 'medium';
|
||||
}
|
||||
|
||||
let isLoaded = $state(false);
|
||||
|
||||
const { srcUrl, tooltipText, altText }: Props = $props();
|
||||
const { srcUrl, tooltipText, altText, size = 'small' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="image-wrapper" style:background-color={stringToColor(altText)}>
|
||||
<div class="image-wrapper {size}" style:background-color={stringToColor(altText)}>
|
||||
<img
|
||||
class="avatar"
|
||||
alt={altText}
|
||||
@ -33,6 +34,18 @@
|
||||
border-radius: 6px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
||||
&.small {
|
||||
border-radius: 6px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
&.medium {
|
||||
border-radius: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.image-wrapper > * {
|
27
packages/ui/src/lib/avatar/AvatarGrouping.svelte
Normal file
27
packages/ui/src/lib/avatar/AvatarGrouping.svelte
Normal file
@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
const { children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="avatar-grouping">
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.avatar-grouping {
|
||||
display: flex;
|
||||
|
||||
:global(& > *) {
|
||||
margin-right: -4px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
14
packages/ui/src/lib/avatar/gravatar.ts
Normal file
14
packages/ui/src/lib/avatar/gravatar.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export function gravatarUrl(id: string | undefined | null): string | undefined {
|
||||
if (id) return `https://www.gravatar.com/avatar/${id}?s=100&r=g&d=retro`;
|
||||
}
|
||||
|
||||
export async function gravatarUrlFromEmail(email: string): Promise<string> {
|
||||
const encoder = new TextEncoder();
|
||||
const strippedEmail = email.toLocaleLowerCase().replaceAll(' ', '');
|
||||
const data = encoder.encode(strippedEmail);
|
||||
const hash = await crypto.subtle.digest('SHA-256', data);
|
||||
const hashArray = Array.from(new Uint8Array(hash));
|
||||
const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
return gravatarUrl(hashHex) as string;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Icon from '$lib/icon/Icon.svelte';
|
||||
import { portal } from '$lib/utils/portal';
|
||||
import Icon from '@gitbutler/ui/icon/Icon.svelte';
|
||||
import type iconsJson from '@gitbutler/ui/icon/icons.json';
|
||||
import type iconsJson from '$lib/icon/icons.json';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
@ -1,106 +1,114 @@
|
||||
<script lang="ts">
|
||||
import TimeAgo from '$lib/shared/TimeAgo.svelte';
|
||||
import Icon from '@gitbutler/ui/icon/Icon.svelte';
|
||||
import { stringToColor } from '@gitbutler/ui/utils/stringToColor';
|
||||
import { tooltip } from '@gitbutler/ui/utils/tooltip';
|
||||
import type { CombinedBranch } from '$lib/branches/types';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import Icon from '$lib/icon/Icon.svelte';
|
||||
import TimeAgo from '$lib/timeAgo/TimeAgo.svelte';
|
||||
import { tooltip } from '$lib/utils/tooltip';
|
||||
import { onMount, type Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
branch: CombinedBranch;
|
||||
onMouseDown?: () => void;
|
||||
onFirstSeen?: () => void;
|
||||
selected?: boolean;
|
||||
title: string;
|
||||
applied?: boolean;
|
||||
pullRequestDetails?: { title: string };
|
||||
lastCommitDetails?: { authorName: string; lastCommitAt: Date };
|
||||
branchDetails?: { commitCount: number; linesAdded: number; linesRemoved: number };
|
||||
remotes?: string[];
|
||||
local?: boolean;
|
||||
|
||||
authorAvatars: Snippet;
|
||||
}
|
||||
|
||||
const { projectId, branch }: Props = $props();
|
||||
const {
|
||||
onMouseDown = () => {},
|
||||
onFirstSeen = () => {},
|
||||
selected = false,
|
||||
applied = false,
|
||||
title,
|
||||
pullRequestDetails,
|
||||
lastCommitDetails,
|
||||
branchDetails,
|
||||
remotes = [],
|
||||
local = false,
|
||||
|
||||
let href = $derived(getBranchLink(branch));
|
||||
let selected = $state(false);
|
||||
authorAvatars
|
||||
}: Props = $props();
|
||||
|
||||
let intersectionTarget = $state<HTMLButtonElement>();
|
||||
|
||||
const observer = new IntersectionObserver(onFirstSeen);
|
||||
|
||||
$effect(() => {
|
||||
selected = href ? $page.url.href.endsWith(href) : false;
|
||||
// console.log(branch);
|
||||
if (intersectionTarget) {
|
||||
observer.observe(intersectionTarget);
|
||||
}
|
||||
});
|
||||
|
||||
function getBranchLink(b: CombinedBranch): string | undefined {
|
||||
if (b.vbranch) return `/${projectId}/board/`;
|
||||
if (b.remoteBranch) return `/${projectId}/remote/${branch?.remoteBranch?.displayName}`;
|
||||
if (b.pr) return `/${projectId}/pull/${b.pr.number}`;
|
||||
}
|
||||
onMount(() => {
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="branch"
|
||||
class:selected
|
||||
onmousedown={() => {
|
||||
if (href) goto(href);
|
||||
}}
|
||||
>
|
||||
<button class="branch" class:selected onmousedown={onMouseDown} bind:this={intersectionTarget}>
|
||||
<h4 class="text-base-13 text-semibold branch-name">
|
||||
{branch.displayName}
|
||||
{title}
|
||||
</h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="row-group">
|
||||
<div class="branch-authors">
|
||||
{#each branch.authors as author}
|
||||
<div
|
||||
use:tooltip={{
|
||||
text: author.name || 'Unknown',
|
||||
delay: 500
|
||||
}}
|
||||
class="author-avatar"
|
||||
style:background-color={stringToColor(author.name)}
|
||||
style:background-image="url({author.gravatarUrl})"
|
||||
></div>
|
||||
{/each}
|
||||
</div>
|
||||
{@render authorAvatars()}
|
||||
<div class="branch-remotes">
|
||||
<!-- NEED API -->
|
||||
{#if branch.remoteBranch}
|
||||
{#each remotes as remote}
|
||||
<div class="branch-tag tag-remote">
|
||||
<span class="text-base-10 text-semibold">origin</span>
|
||||
<span class="text-base-10 text-semibold">{remote}</span>
|
||||
</div>
|
||||
<!-- <div class="branch-tag tag-local">
|
||||
{/each}
|
||||
{#if local}
|
||||
<div class="branch-tag tag-local">
|
||||
<span class="text-base-10 text-semibold">local</span>
|
||||
</div> -->
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-group">
|
||||
{#if branch.pr}
|
||||
<div use:tooltip={{ text: branch.pr.title, delay: 500 }} class="branch-tag tag-pr">
|
||||
{#if pullRequestDetails}
|
||||
<div use:tooltip={{ text: pullRequestDetails.title, delay: 500 }} class="branch-tag tag-pr">
|
||||
<span class="text-base-10 text-semibold">PR</span>
|
||||
<Icon name="pr-small" />
|
||||
</div>
|
||||
{/if}
|
||||
<!-- NEED API -->
|
||||
<!-- <div class="branch-tag tag-applied">
|
||||
<span class="text-base-10 text-semibold">applied</span>
|
||||
</div> -->
|
||||
{#if applied}
|
||||
<div class="branch-tag tag-applied">
|
||||
<span class="text-base-10 text-semibold">applied</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<span class="branch-time text-base-11 details truncate">
|
||||
<TimeAgo date={branch.modifiedAt} />
|
||||
{#if branch.author?.name}
|
||||
by {branch.author?.name}
|
||||
{#if lastCommitDetails}
|
||||
<TimeAgo date={lastCommitDetails.lastCommitAt} />
|
||||
by {lastCommitDetails.authorName}
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<!-- NEED API -->
|
||||
<div class="stats">
|
||||
<div use:tooltip={'Number of commits'} class="branch-tag tag-commits">
|
||||
<span class="text-base-10 text-semibold">34</span>
|
||||
<Icon name="commit" />
|
||||
</div>
|
||||
{#if branchDetails}
|
||||
<div use:tooltip={'Number of commits'} class="branch-tag tag-commits">
|
||||
<span class="text-base-10 text-semibold">{branchDetails.commitCount}</span>
|
||||
<Icon name="commit" />
|
||||
</div>
|
||||
|
||||
<div use:tooltip={'Code changes'} class="code-changes">
|
||||
<span class="text-base-10 text-semibold">+289</span>
|
||||
<span class="text-base-10 text-semibold">-129</span>
|
||||
</div>
|
||||
<div use:tooltip={'Code changes'} class="code-changes">
|
||||
<span class="text-base-10 text-semibold">+{branchDetails.linesAdded}</span>
|
||||
<span class="text-base-10 text-semibold">-{branchDetails.linesRemoved}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
@ -136,26 +144,6 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* AUTHORS */
|
||||
|
||||
.branch-authors {
|
||||
display: flex;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.author-avatar {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
margin-left: -4px;
|
||||
background-color: var(--clr-scale-ntrl-50);
|
||||
background-size: cover;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* TAG */
|
||||
|
||||
.branch-tag {
|
||||
@ -229,8 +217,9 @@
|
||||
}
|
||||
|
||||
.branch-remotes {
|
||||
margin-left: 6px;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.branch-name {
|
15
packages/ui/src/lib/timeAgo/TimeAgo.svelte
Normal file
15
packages/ui/src/lib/timeAgo/TimeAgo.svelte
Normal file
@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { createTimeAgoStore } from '$lib/timeAgo/timeAgo';
|
||||
|
||||
interface Props {
|
||||
date?: Date;
|
||||
addSuffix?: boolean;
|
||||
}
|
||||
|
||||
const { date, addSuffix }: Props = $props();
|
||||
const store = $derived(createTimeAgoStore(date, addSuffix));
|
||||
</script>
|
||||
|
||||
{#if store}
|
||||
{$store}
|
||||
{/if}
|
34
packages/ui/src/stories/avatar/Avatar.stories.ts
Normal file
34
packages/ui/src/stories/avatar/Avatar.stories.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import Avatar from '$lib/avatar/Avatar.svelte';
|
||||
import type { Meta, StoryObj } from '@storybook/svelte';
|
||||
|
||||
const meta = {
|
||||
component: Avatar,
|
||||
|
||||
argTypes: {
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['small', 'medium']
|
||||
}
|
||||
}
|
||||
} satisfies Meta<Avatar>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const AvatarSmall: Story = {
|
||||
args: {
|
||||
srcUrl: 'https://gravatar.com/avatar/f43ef760d895a84ca7bb35ff6f4c6b7c',
|
||||
tooltipText: 'The avatar of bob',
|
||||
altText: 'Bobs avatar',
|
||||
size: 'small'
|
||||
}
|
||||
};
|
||||
|
||||
export const AvatarMedium: Story = {
|
||||
args: {
|
||||
srcUrl: 'https://gravatar.com/avatar/f43ef760d895a84ca7bb35ff6f4c6b7c',
|
||||
tooltipText: 'The avatar of bob',
|
||||
altText: 'Bobs avatar',
|
||||
size: 'medium'
|
||||
}
|
||||
};
|
13
packages/ui/src/stories/avatar/AvatarGrouping.stories.ts
Normal file
13
packages/ui/src/stories/avatar/AvatarGrouping.stories.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import DemoAvatarGrouping from './DemoAvatarGrouping.svelte';
|
||||
import type { Meta, StoryObj } from '@storybook/svelte';
|
||||
|
||||
const meta = {
|
||||
component: DemoAvatarGrouping
|
||||
} satisfies Meta<DemoAvatarGrouping>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const AvatarGrouping: Story = {
|
||||
args: {}
|
||||
};
|
25
packages/ui/src/stories/avatar/DemoAvatarGrouping.svelte
Normal file
25
packages/ui/src/stories/avatar/DemoAvatarGrouping.svelte
Normal file
@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import Avatar from '$lib/avatar/Avatar.svelte';
|
||||
import AvatarGrouping from '$lib/avatar/AvatarGrouping.svelte';
|
||||
</script>
|
||||
|
||||
<AvatarGrouping>
|
||||
<Avatar
|
||||
srcUrl="https://gravatar.com/avatar/f43ef760d895a84ca7bb35ff6f4c6b7c"
|
||||
size="medium"
|
||||
tooltipText="Bestest hamster"
|
||||
altText="Bestest hamster"
|
||||
/>
|
||||
<Avatar
|
||||
srcUrl="https://gravatar.com/avatar/f43ef760d895a84ca7bb35ff6f4c6b7c"
|
||||
size="medium"
|
||||
tooltipText="Bestest hamster"
|
||||
altText="Bestest hamster"
|
||||
/>
|
||||
<Avatar
|
||||
srcUrl="https://gravatar.com/avatar/f43ef760d895a84ca7bb35ff6f4c6b7c"
|
||||
size="medium"
|
||||
tooltipText="Bestest hamster"
|
||||
altText="Bestest hamster"
|
||||
/>
|
||||
</AvatarGrouping>
|
73
packages/ui/src/stories/button/Button.stories.ts
Normal file
73
packages/ui/src/stories/button/Button.stories.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import DemoButton from './ButtonDemo.svelte';
|
||||
import iconsJson from '$lib/icon/icons.json';
|
||||
import type { Meta, StoryObj } from '@storybook/svelte';
|
||||
|
||||
const meta = {
|
||||
component: DemoButton,
|
||||
argTypes: {
|
||||
disabled: {
|
||||
control: 'boolean'
|
||||
},
|
||||
clickable: {
|
||||
control: 'boolean'
|
||||
},
|
||||
loading: {
|
||||
control: 'boolean'
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['tag', 'button', 'cta']
|
||||
},
|
||||
style: {
|
||||
control: 'select',
|
||||
options: ['neutral', 'ghost', 'pop', 'success', 'error', 'warning', 'purple', undefined]
|
||||
},
|
||||
kind: {
|
||||
control: 'select',
|
||||
options: ['solid', 'soft', undefined]
|
||||
},
|
||||
outline: {
|
||||
control: 'boolean'
|
||||
},
|
||||
dashed: {
|
||||
control: 'boolean'
|
||||
},
|
||||
icon: {
|
||||
control: 'select',
|
||||
options: [undefined, ...Object.keys(iconsJson)]
|
||||
}
|
||||
}
|
||||
} satisfies Meta<DemoButton>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const ButtonDefalut: Story = {
|
||||
args: {
|
||||
contents: 'Testeroni',
|
||||
size: 'button',
|
||||
disabled: false,
|
||||
clickable: true,
|
||||
loading: false,
|
||||
style: 'neutral',
|
||||
kind: 'soft',
|
||||
outline: false,
|
||||
dashed: false,
|
||||
icon: undefined
|
||||
}
|
||||
};
|
||||
|
||||
export const ButtonWithIcon: Story = {
|
||||
args: {
|
||||
contents: "Testeroni",
|
||||
size: "button",
|
||||
disabled: false,
|
||||
clickable: true,
|
||||
loading: false,
|
||||
style: "pop",
|
||||
kind: "solid",
|
||||
outline: false,
|
||||
dashed: false,
|
||||
icon: "ai-small"
|
||||
}
|
||||
};
|
12
packages/ui/src/stories/button/ButtonDemo.svelte
Normal file
12
packages/ui/src/stories/button/ButtonDemo.svelte
Normal file
@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/inputs/Button.svelte';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
|
||||
type Props = ComponentProps<Button> & { contents: string };
|
||||
|
||||
const { contents, ...args }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Button {...args}>
|
||||
{contents}
|
||||
</Button>
|
24
packages/ui/src/stories/modal/DemoModal.svelte
Normal file
24
packages/ui/src/stories/modal/DemoModal.svelte
Normal file
@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import iconsJson from '$lib/icon/icons.json';
|
||||
import Button from '$lib/inputs/Button.svelte';
|
||||
import Modal from '$lib/modal/Modal.svelte';
|
||||
|
||||
interface Props {
|
||||
width?: 'default' | 'small' | 'large';
|
||||
title?: string;
|
||||
icon?: keyof typeof iconsJson;
|
||||
}
|
||||
|
||||
const { ...args }: Props = $props();
|
||||
|
||||
let modal = $state<Modal>();
|
||||
</script>
|
||||
|
||||
<Button on:click={() => modal?.show()}>Show</Button>
|
||||
<Modal bind:this={modal} {...args}>
|
||||
<p>Wonderful modal content</p>
|
||||
|
||||
{#snippet controls(close)}
|
||||
<Button on:click={() => close()}>Close</Button>
|
||||
{/snippet}
|
||||
</Modal>
|
28
packages/ui/src/stories/modal/Modal.stories.ts
Normal file
28
packages/ui/src/stories/modal/Modal.stories.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import DemoModal from './DemoModal.svelte';
|
||||
import iconsJson from '$lib/icon/icons.json';
|
||||
import type { Meta, StoryObj } from '@storybook/svelte';
|
||||
|
||||
const meta = {
|
||||
component: DemoModal,
|
||||
argTypes: {
|
||||
width: {
|
||||
control: 'select',
|
||||
options: ['default', 'small', 'large']
|
||||
},
|
||||
title: { control: 'text' },
|
||||
icon: {
|
||||
control: 'select',
|
||||
options: [undefined, ...Object.keys(iconsJson)]
|
||||
}
|
||||
}
|
||||
} satisfies Meta<DemoModal>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const ModalStory: Story = {
|
||||
args: {
|
||||
width: 'small',
|
||||
title: 'This is a fantastic modal :D'
|
||||
}
|
||||
};
|
50
packages/ui/src/stories/sidebarEntry/DemoSidebarEntry.svelte
Normal file
50
packages/ui/src/stories/sidebarEntry/DemoSidebarEntry.svelte
Normal file
@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import Avatar from '$lib/avatar/Avatar.svelte';
|
||||
import AvatarGrouping from '$lib/avatar/AvatarGrouping.svelte';
|
||||
import SidebarEntry from '$lib/sidebarEntry/SidebarEntry.svelte';
|
||||
|
||||
interface Props {
|
||||
selected?: boolean;
|
||||
title: string;
|
||||
applied?: boolean;
|
||||
pullRequestDetails?: { title: string };
|
||||
// Storybook can give us pretty much anything under the sun for a date so we need to handle it
|
||||
lastCommitDetails?: { authorName: string; lastCommitAt: any };
|
||||
branchDetails?: { commitCount: number; linesAdded: number; linesRemoved: number };
|
||||
remotes?: string[];
|
||||
local?: boolean;
|
||||
}
|
||||
|
||||
const { ...args }: Props = $props();
|
||||
|
||||
$effect.pre(() => {
|
||||
if (args.lastCommitDetails) {
|
||||
args.lastCommitDetails.lastCommitAt = new Date(args.lastCommitDetails.lastCommitAt);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<SidebarEntry {...args}>
|
||||
{#snippet authorAvatars()}
|
||||
<AvatarGrouping>
|
||||
<Avatar
|
||||
srcUrl="https://gravatar.com/avatar/f43ef760d895a84ca7bb35ff6f4c6b7c"
|
||||
size="medium"
|
||||
tooltipText="Bestest hamster"
|
||||
altText="Bestest hamster"
|
||||
/>
|
||||
<Avatar
|
||||
srcUrl="https://gravatar.com/avatar/f43ef760d895a84ca7bb35ff6f4c6b7c"
|
||||
size="medium"
|
||||
tooltipText="Bestest hamster"
|
||||
altText="Bestest hamster"
|
||||
/>
|
||||
<Avatar
|
||||
srcUrl="https://gravatar.com/avatar/f43ef760d895a84ca7bb35ff6f4c6b7c"
|
||||
size="medium"
|
||||
tooltipText="Bestest hamster"
|
||||
altText="Bestest hamster"
|
||||
/>
|
||||
</AvatarGrouping>
|
||||
{/snippet}
|
||||
</SidebarEntry>
|
107
packages/ui/src/stories/sidebarEntry/SidebarEntry.stories.ts
Normal file
107
packages/ui/src/stories/sidebarEntry/SidebarEntry.stories.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import DemoSidebarEntry from './DemoSidebarEntry.svelte';
|
||||
import type { Meta, StoryObj } from '@storybook/svelte';
|
||||
|
||||
interface _Props {
|
||||
selected?: boolean;
|
||||
title: string;
|
||||
applied?: boolean;
|
||||
pullRequestDetails?: { title: string };
|
||||
lastCommitDetails?: { authorName: string; lastCommitAt: Date };
|
||||
branchDetails?: { commitCount: number; linesAdded: number; linesRemoved: number };
|
||||
remotes?: string[];
|
||||
local?: boolean;
|
||||
}
|
||||
|
||||
const meta = {
|
||||
component: DemoSidebarEntry,
|
||||
argTypes: {
|
||||
selected: { control: 'boolean' },
|
||||
title: { control: 'text' },
|
||||
applied: { control: 'boolean' },
|
||||
pullRequestDetails: { control: 'object' },
|
||||
lastCommitDetails: { control: 'object' },
|
||||
branchDetails: { control: 'object' },
|
||||
remotes: { control: 'object' },
|
||||
local: { control: 'boolean' }
|
||||
}
|
||||
} satisfies Meta<DemoSidebarEntry>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const SidebarEntry: Story = {
|
||||
args: {
|
||||
title: 'best branch ever',
|
||||
selected: false,
|
||||
applied: false,
|
||||
pullRequestDetails: undefined,
|
||||
lastCommitDetails: {
|
||||
authorName: 'Caleb',
|
||||
lastCommitAt: '2024-07-31T15:39:14.540Z'
|
||||
},
|
||||
branchDetails: {
|
||||
commitCount: 4,
|
||||
linesAdded: 35,
|
||||
linesRemoved: 64
|
||||
},
|
||||
remotes: [],
|
||||
local: true
|
||||
},
|
||||
argTypes: {}
|
||||
};
|
||||
|
||||
export const SidebarEntryPr: Story = {
|
||||
args: {
|
||||
title: 'best branch ever',
|
||||
selected: false,
|
||||
applied: false,
|
||||
|
||||
lastCommitDetails: {
|
||||
authorName: 'Caleb',
|
||||
lastCommitAt: '2024-07-31T15:39:14.540Z'
|
||||
},
|
||||
|
||||
branchDetails: {
|
||||
commitCount: 4,
|
||||
linesAdded: 35,
|
||||
linesRemoved: 64
|
||||
},
|
||||
|
||||
remotes: ['origin'],
|
||||
local: true,
|
||||
|
||||
pullRequestDetails: {
|
||||
title: 'bestest pr'
|
||||
}
|
||||
},
|
||||
|
||||
argTypes: {}
|
||||
};
|
||||
|
||||
export const SidebarEntryInWorkspace: Story = {
|
||||
args: {
|
||||
title: 'best branch ever',
|
||||
selected: false,
|
||||
applied: true,
|
||||
|
||||
lastCommitDetails: {
|
||||
authorName: 'Caleb',
|
||||
lastCommitAt: '2024-07-31T15:39:14.540Z'
|
||||
},
|
||||
|
||||
branchDetails: {
|
||||
commitCount: 4,
|
||||
linesAdded: 35,
|
||||
linesRemoved: 64
|
||||
},
|
||||
|
||||
remotes: ['origin'],
|
||||
local: true,
|
||||
|
||||
pullRequestDetails: {
|
||||
title: 'bestest pr'
|
||||
}
|
||||
},
|
||||
|
||||
argTypes: {}
|
||||
};
|
12
packages/ui/src/stories/timeAgo/DemoTimeAgo.svelte
Normal file
12
packages/ui/src/stories/timeAgo/DemoTimeAgo.svelte
Normal file
@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import TimeAgo from '$lib/timeAgo/TimeAgo.svelte';
|
||||
|
||||
interface Props {
|
||||
date: number;
|
||||
addSuffix?: boolean;
|
||||
}
|
||||
|
||||
const { date, addSuffix }: Props = $props();
|
||||
</script>
|
||||
|
||||
<TimeAgo date={new Date(date)} {addSuffix}></TimeAgo>
|
28
packages/ui/src/stories/timeAgo/TimeAgo.stories.ts
Normal file
28
packages/ui/src/stories/timeAgo/TimeAgo.stories.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import DemoTimeAgo from './DemoTimeAgo.svelte';
|
||||
import type { Meta, StoryObj } from '@storybook/svelte';
|
||||
|
||||
const meta = {
|
||||
component: DemoTimeAgo,
|
||||
argTypes: {
|
||||
date: {
|
||||
control: 'date'
|
||||
}
|
||||
}
|
||||
} satisfies Meta<DemoTimeAgo>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const TimeAgoSuffixless: Story = {
|
||||
args: {
|
||||
date: 1721315627068,
|
||||
addSuffix: false
|
||||
}
|
||||
};
|
||||
|
||||
export const TimeAgoWithSuffix: Story = {
|
||||
args: {
|
||||
date: 1721315627068,
|
||||
addSuffix: true
|
||||
}
|
||||
};
|
@ -373,6 +373,9 @@ importers:
|
||||
cpy-cli:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
date-fns:
|
||||
specifier: ^2.30.0
|
||||
version: 2.30.0
|
||||
postcss:
|
||||
specifier: ^8.4.38
|
||||
version: 8.4.39
|
||||
|
Loading…
Reference in New Issue
Block a user