mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2025-01-01 22:12:04 +03:00
commit
5f928c6d44
@ -14,6 +14,7 @@
|
||||
export let iconAlign: 'left' | 'right' = 'right';
|
||||
export let color: ButtonColor = 'primary';
|
||||
export let kind: 'filled' | 'outlined' = 'filled';
|
||||
export let isDropdownChild = false;
|
||||
export let disabled = false;
|
||||
export let notClickable = false;
|
||||
export let id: string | undefined = undefined;
|
||||
@ -27,9 +28,6 @@
|
||||
|
||||
export let element: HTMLAnchorElement | HTMLButtonElement | HTMLElement | null = null;
|
||||
|
||||
let className = '';
|
||||
export { className as class };
|
||||
|
||||
const SLOTS = $$props.$$slots;
|
||||
|
||||
onMount(() => {
|
||||
@ -39,7 +37,7 @@
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={`btn ${className}`}
|
||||
class="btn"
|
||||
class:medium={size == 'medium'}
|
||||
class:large={size == 'large'}
|
||||
class:error-outline={color == 'error' && kind == 'outlined'}
|
||||
@ -55,6 +53,7 @@
|
||||
class:wide
|
||||
class:grow
|
||||
class:not-clickable={notClickable}
|
||||
class:is-dropdown={isDropdownChild}
|
||||
style:align-self={align}
|
||||
style:width={width ? pxToRem(width) : undefined}
|
||||
use:tooltip={help}
|
||||
@ -78,6 +77,7 @@
|
||||
|
||||
<style lang="postcss">
|
||||
.btn {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -198,4 +198,33 @@
|
||||
height: var(--size-btn-l);
|
||||
padding: var(--space-6) var(--space-8);
|
||||
}
|
||||
|
||||
/* DROPDOWN */
|
||||
.is-dropdown {
|
||||
&:first-of-type {
|
||||
flex: 1;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-right: none;
|
||||
|
||||
&.primary-filled {
|
||||
&:after {
|
||||
content: '';
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background: var(--clr-theme-scale-ntrl-100);
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { clickOutside } from '$lib/clickOutside';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import { joinClassNames } from '$lib/utils/joinClassNames';
|
||||
import type iconsJson from '$lib/icons/icons.json';
|
||||
|
||||
export let icon: keyof typeof iconsJson | undefined = undefined;
|
||||
@ -29,24 +28,17 @@
|
||||
<div class="dropdown-wrapper" class:wide>
|
||||
<div class="dropdown" bind:this={container}>
|
||||
<Button
|
||||
class={joinClassNames([
|
||||
'dropdown__text-btn',
|
||||
kind == 'outlined' ? 'dropdown__text-btn_outlined' : 'dropdown__text-btn_filled',
|
||||
wide ? 'wide-text-btn' : ''
|
||||
])}
|
||||
{color}
|
||||
{icon}
|
||||
{kind}
|
||||
{help}
|
||||
iconAlign="left"
|
||||
disabled={disabled || loading}
|
||||
isDropdownChild
|
||||
on:click><slot /></Button
|
||||
>
|
||||
<Button
|
||||
class={joinClassNames([
|
||||
'dropdown__icon-btn',
|
||||
kind == 'outlined' ? 'dropdown__icon-btn_outlined' : ''
|
||||
])}
|
||||
class="dropdown__icon-btn"
|
||||
bind:element={iconElt}
|
||||
{color}
|
||||
{kind}
|
||||
@ -54,6 +46,7 @@
|
||||
icon={visible ? 'chevron-top' : 'chevron-down'}
|
||||
{loading}
|
||||
disabled={disabled || loading}
|
||||
isDropdownChild
|
||||
on:click={() => (visible = !visible)}
|
||||
/>
|
||||
</div>
|
||||
@ -97,18 +90,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-wrapper :global(.dropdown__text-btn_outlined) {
|
||||
transform: translateX(1px);
|
||||
}
|
||||
|
||||
.dropdown-wrapper :global(.dropdown__text-btn_filled) {
|
||||
border-right: 1px solid var(--clr-theme-scale-pop-50);
|
||||
}
|
||||
|
||||
.dropdown-wrapper :global(.dropdown__icon-btn_outlined):disabled {
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
@ -126,8 +107,4 @@
|
||||
.wide {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdown-wrapper :global(.wide-text-btn) {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
@ -26,9 +26,18 @@
|
||||
style:fill-opacity={opacity}
|
||||
style:width={pxToRem(size)}
|
||||
style:height={pxToRem(size)}
|
||||
style="--spinner-radius: {spinnerRadius}"
|
||||
>
|
||||
{#if name == 'spinner'}
|
||||
<circle class="spinner-path" cx="8" cy="8" r={spinnerRadius} fill="none" />
|
||||
<circle
|
||||
class="spinner-back-path"
|
||||
cx="8"
|
||||
cy="8"
|
||||
r="var(--spinner-radius)"
|
||||
fill="none"
|
||||
vector-effect="non-scaling-stroke"
|
||||
/>
|
||||
{:else}
|
||||
<path fill="currentColor" d={iconsJson[name]} />
|
||||
{/if}
|
||||
@ -36,6 +45,7 @@
|
||||
|
||||
<style lang="postcss">
|
||||
.icon-wrapper {
|
||||
--spinner-stroke-width: calc(var(--space-2) / 1.4);
|
||||
flex-shrink: 0;
|
||||
pointer-events: none;
|
||||
display: inline-block;
|
||||
@ -64,22 +74,28 @@
|
||||
}
|
||||
}
|
||||
.spinner-path {
|
||||
stroke-width: calc(var(--space-2) / 1.3);
|
||||
stroke-width: var(--spinner-stroke-width);
|
||||
stroke: currentColor;
|
||||
animation: spinning-path 1.5s infinite ease-in-out;
|
||||
animation: spinning-path 2s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.spinner-back-path {
|
||||
stroke-width: var(--spinner-stroke-width);
|
||||
stroke: currentColor;
|
||||
opacity: 0.3;
|
||||
}
|
||||
@keyframes spinning-path {
|
||||
0% {
|
||||
stroke-dasharray: 1, 200;
|
||||
stroke-dasharray: 1, 120;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dasharray: 60, 200;
|
||||
stroke-dashoffset: -12;
|
||||
60% {
|
||||
stroke-dasharray: 60, 120;
|
||||
stroke-dashoffset: -10;
|
||||
}
|
||||
100% {
|
||||
stroke-dasharray: 60, 200;
|
||||
stroke-dashoffset: -34;
|
||||
stroke-dasharray: 60, 120;
|
||||
stroke-dashoffset: calc(-1 * var(--spinner-radius) * 5.5);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -58,7 +58,6 @@
|
||||
const startedAt = checksStatus.startedAt;
|
||||
if (!startedAt) return;
|
||||
const secondsAgo = (new Date().getTime() - startedAt.getTime()) / 1000;
|
||||
|
||||
let timeUntilUdate: number | undefined = undefined;
|
||||
if (secondsAgo < 600) {
|
||||
timeUntilUdate = 30;
|
||||
@ -67,12 +66,10 @@
|
||||
} else if (secondsAgo < 3600) {
|
||||
timeUntilUdate = 120;
|
||||
}
|
||||
|
||||
if (!timeUntilUdate) {
|
||||
// Stop polling for status.
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => fetchPrStatus(), timeUntilUdate * 1000);
|
||||
}
|
||||
|
||||
@ -80,6 +77,7 @@
|
||||
$: checksColor = getChecksColor(checksStatus);
|
||||
$: statusIcon = getStatusIcon();
|
||||
$: statusColor = getStatusColor();
|
||||
$: statusLabel = getPrStatusLabel();
|
||||
|
||||
$: if ($pr$) fetchPrStatus();
|
||||
|
||||
@ -90,23 +88,27 @@
|
||||
if (status.completed) {
|
||||
return status.success ? 'success' : 'error';
|
||||
}
|
||||
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
function getChecksIcon(status: ChecksStatus): keyof typeof iconsJson | undefined {
|
||||
if (status?.error) return 'error';
|
||||
if (isFetching) return 'spinner';
|
||||
|
||||
if (status?.error) return 'error-small';
|
||||
if (!status) return;
|
||||
if (status && !status.hasChecks) return;
|
||||
if (status.completed) {
|
||||
return status.success ? 'success' : 'error';
|
||||
return status.success ? 'success-small' : 'error-small';
|
||||
}
|
||||
|
||||
return 'spinner';
|
||||
}
|
||||
|
||||
function statusToTagText(status: ChecksStatus | undefined): string | undefined {
|
||||
if (status?.error) return 'error';
|
||||
if (!status) return;
|
||||
if (status && !status.hasChecks) return;
|
||||
if (status && !status.hasChecks) return 'No checks';
|
||||
if (status.completed) {
|
||||
return status.success ? 'Checks passed' : 'Checks failed';
|
||||
}
|
||||
@ -147,27 +149,36 @@
|
||||
{$pr$.title}
|
||||
</div>
|
||||
<div class="pr-tags">
|
||||
<Tag icon={statusIcon} color={statusColor} border verticalOrientation={isLaneCollapsed}>
|
||||
{getPrStatusLabel()}
|
||||
<Tag
|
||||
icon={statusIcon}
|
||||
color={statusColor}
|
||||
filled={statusLabel !== 'open'}
|
||||
verticalOrientation={isLaneCollapsed}
|
||||
>
|
||||
{statusLabel}
|
||||
</Tag>
|
||||
{#if branch.upstream && checksIcon}
|
||||
<Tag
|
||||
icon={checksIcon}
|
||||
color={checksColor}
|
||||
filled={checksIcon == 'success'}
|
||||
filled={checksIcon == 'success-small'}
|
||||
clickable
|
||||
border
|
||||
verticalOrientation={isLaneCollapsed}
|
||||
on:click={fetchPrStatus}
|
||||
help="Refresh checks status"
|
||||
>
|
||||
{statusToTagText(checksStatus)}
|
||||
</Tag>
|
||||
{:else}
|
||||
<Tag color="light" verticalOrientation={isLaneCollapsed} help="Fetching checks status">
|
||||
No checks
|
||||
</Tag>
|
||||
{/if}
|
||||
<Tag
|
||||
icon="open-link"
|
||||
color="ghost"
|
||||
border
|
||||
clickable
|
||||
border
|
||||
verticalOrientation={isLaneCollapsed}
|
||||
on:click={(e) => {
|
||||
const url = $pr$?.htmlUrl;
|
||||
|
@ -55,7 +55,7 @@
|
||||
</span>
|
||||
{#if icon}
|
||||
<div class="icon" class:verticalIcon={verticalOrientation}>
|
||||
<Icon name={icon} />
|
||||
<Icon name={icon} spinnerRadius={3.5} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
.tooltip {
|
||||
pointer-events: none;
|
||||
background-color: var(--clr-core-ntrl-10);
|
||||
border-radius: var(--radius-s);
|
||||
border: 1px solid var(--clr-core-ntrl-30);
|
||||
|
Loading…
Reference in New Issue
Block a user