Merge origin/master into origin/always-await-promises

This commit is contained in:
Caleb Owens 2024-04-14 20:25:44 +01:00
commit 78a7d44b45
20 changed files with 213 additions and 278 deletions

View File

@ -1,6 +1,5 @@
<script lang="ts">
import Button from './Button.svelte';
import IconButton from './IconButton.svelte';
import { UpdaterService } from '$lib/backend/updater';
import { showToast } from '$lib/notifications/toasts';
import { getContext } from '$lib/utils/context';
@ -20,7 +19,7 @@
{#if $update$?.version && $update$.status != 'UPTODATE' && !dismissed}
<div class="update-banner" class:busy={$update$?.status == 'PENDING'}>
<div class="floating-button">
<IconButton icon="cross-small" on:click={() => (dismissed = true)} />
<Button icon="cross-small" style="ghost" on:click={() => (dismissed = true)} />
</div>
<div class="img">
<div class="circle-img">

View File

@ -31,6 +31,8 @@
let branchName = branch?.upstreamName || normalizeBranchName($branchStore.name);
function handleBranchNameChange(title: string) {
if (title == '') return;
branchName = normalizeBranchName(title);
branchController.updateBranchName(branch.id, title);
}
@ -87,40 +89,44 @@
</div>
{:else}
<div class="header__wrapper">
<div class="header card" class:isUnapplied>
<div class="header__info">
<div class="header__label">
<div class="header card">
<div class="header__info-wrapper">
{#if !isUnapplied}
<div class="draggable" data-drag-handle>
<Icon name="draggable" />
</div>
{/if}
<div class="header__info">
<BranchLabel
name={branch.name || 'hello'}
name={branch.name}
on:change={(e) => handleBranchNameChange(e.detail.name)}
disabled={isUnapplied}
/>
</div>
<div class="header__remote-branch">
<ActiveBranchStatus
branchName={branch.upstreamName ?? branchName}
{isUnapplied}
{hasIntegratedCommits}
remoteExists={!!branch.upstreamName}
isLaneCollapsed={$isLaneCollapsed}
/>
<div class="header__remote-branch">
<ActiveBranchStatus
branchName={branch.upstreamName ?? branchName}
{isUnapplied}
{hasIntegratedCommits}
remoteExists={!!branch.upstreamName}
isLaneCollapsed={$isLaneCollapsed}
/>
{#await branch.isMergeable then isMergeable}
{#if !isMergeable}
<Tag
icon="locked-small"
style="warning"
help="Applying this branch will add merge conflict markers that you will have to resolve"
>
Conflict
</Tag>
{/if}
{/await}
</div>
<div class="draggable" data-drag-handle>
<Icon name="draggable" />
{#await branch.isMergeable then isMergeable}
{#if !isMergeable}
<Tag
icon="locked-small"
style="warning"
help="Applying this branch will add merge conflict markers that you will have to resolve"
>
Conflict
</Tag>
{/if}
{/await}
</div>
</div>
</div>
<div class="header__actions">
<div class="header__buttons">
{#if branch.active}
@ -229,7 +235,7 @@
</div>
{/if}
<style lang="postcss">
<style>
.header__wrapper {
z-index: var(--z-lifted);
position: sticky;
@ -240,15 +246,6 @@
position: relative;
flex-direction: column;
gap: var(--size-2);
&:hover {
& .draggable {
opacity: 1;
}
}
&.isUnapplied {
background: var(--clr-bg-alt);
}
}
.header__top-overlay {
z-index: var(--z-ground);
@ -258,13 +255,17 @@
width: 100%;
height: var(--size-20);
background: var(--target-branch-background);
/* background-color: red; */
}
.header__info-wrapper {
display: flex;
gap: var(--size-2);
padding: var(--size-10);
}
.header__info {
flex: 1;
display: flex;
flex-direction: column;
transition: margin var(--transition-slow);
padding: var(--size-12);
overflow: hidden;
gap: var(--size-10);
}
.header__actions {
@ -276,31 +277,19 @@
border-radius: 0 0 var(--radius-m) var(--radius-m);
user-select: none;
}
.isUnapplied .header__actions {
border-top: 1px solid var(--clr-border-main);
}
.header__buttons {
display: flex;
position: relative;
gap: var(--size-4);
}
.header__label {
display: flex;
flex-grow: 1;
align-items: center;
gap: var(--size-4);
}
.draggable {
display: flex;
height: fit-content;
cursor: grab;
position: absolute;
right: var(--size-4);
top: var(--size-6);
opacity: 0;
padding: var(--size-2) var(--size-2) 0 0;
color: var(--clr-scale-ntrl-50);
transition:
opacity var(--transition-slow),
color var(--transition-slow);
transition: color var(--transition-slow);
&:hover {
color: var(--clr-scale-ntrl-40);
@ -326,7 +315,7 @@
align-items: center;
}
/* COLLAPSABLE LANE */
/* COLLAPSIBLE LANE */
.collapsed-lane {
cursor: default;

View File

@ -1,119 +1,97 @@
<script lang="ts">
import { useResize } from '$lib/utils/useResize';
import { createEventDispatcher } from 'svelte';
export let name: string;
export let disabled = false;
let inputActive = false;
let label: HTMLDivElement;
let input: HTMLInputElement;
function activateInput() {
if (disabled) return;
inputActive = true;
setTimeout(() => input.select(), 0);
}
let inputEl: HTMLInputElement;
let initialName = name;
let mesureEl: HTMLSpanElement;
let inputWidth = 0;
let inputWidth: string | undefined;
const dispatch = createEventDispatcher<{
change: { name: string };
}>();
$: {
if (mesureEl) {
inputWidth = mesureEl.getBoundingClientRect().width;
}
}
</script>
{#if inputActive}
<span class="branch-name-mesure-el text-base-13 text-bold" bind:this={mesureEl}>{name}</span>
<input
type="text"
{disabled}
bind:this={input}
bind:value={name}
on:change={(e) => dispatch('change', { name: e.currentTarget.value })}
on:input={() => {
if (input.value.length > 0) {
inputWidth = mesureEl.getBoundingClientRect().width;
} else {
inputWidth = 0;
}
}}
title={name}
class="branch-name-input text-base-13 text-bold"
on:dblclick|stopPropagation
on:blur={() => (inputActive = false)}
on:keydown={(e) => {
if (e.key == 'Enter') {
// Unmount input field asynchronously to ensure on:change gets executed.
setTimeout(() => (inputActive = false), 0);
setTimeout(() => label.focus(), 0);
}
if (e.key == 'Escape') {
inputActive = false;
name = initialName;
setTimeout(() => label.focus(), 0);
}
}}
autocomplete="off"
autocorrect="off"
spellcheck="false"
style={`width: calc(${inputWidth}px + var(--size-12))`}
/>
{:else}
<div
bind:this={label}
role="textbox"
tabindex="0"
class="branch-name text-base-13 text-bold truncate"
on:keydown={(e) => e.key == 'Enter' && activateInput()}
on:mousedown={activateInput}
>
{name}
</div>
{/if}
<span
use:useResize={(frame) => {
inputWidth = `${Math.round(frame.width)}px`;
}}
class="branch-name-mesure-el text-base-14 text-bold"
bind:this={mesureEl}>{name}</span
>
<input
type="text"
{disabled}
bind:this={inputEl}
bind:value={name}
on:change={(e) => dispatch('change', { name: e.currentTarget.value.trim() })}
title={name}
class="branch-name-input text-base-14 text-bold"
on:dblclick|stopPropagation
on:click|stopPropagation={() => {
inputEl.focus();
}}
on:blur={() => {
if (name == '') name = initialName;
}}
on:focus={() => {
initialName = name;
}}
on:keydown={(e) => {
if (e.key == 'Enter' || e.key == 'Escape') {
inputEl.blur();
}
}}
autocomplete="off"
autocorrect="off"
spellcheck="false"
style:width={inputWidth}
/>
<style lang="postcss">
.branch-name,
.branch-name-mesure-el,
.branch-name-input {
min-width: 2.8rem;
height: var(--size-20);
pointer-events: auto;
color: var(--clr-scale-ntrl-0);
padding: var(--size-2) var(--size-4);
border-radius: var(--radius-s);
border: 1px solid transparent;
}
.branch-name {
cursor: text;
display: inline-block;
transition: background-color var(--transition-fast);
&:hover,
&:focus {
background-color: var(--clr-bg-muted);
outline: none;
}
}
.branch-name-mesure-el {
pointer-events: auto;
visibility: hidden;
border: 2px solid transparent;
top: 30px;
color: black;
position: absolute;
display: inline-block;
visibility: hidden;
white-space: pre;
}
.branch-name-input {
min-width: 1rem;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
max-width: 100%;
width: 100%;
border-radius: var(--radius-s);
color: var(--clr-scale-ntrl-0);
background-color: var(--clr-bg-main);
outline: none;
&:focus {
/* not readonly */
&:not([disabled]):hover {
background-color: var(--clr-bg-muted);
}
&:not([disabled]):focus {
outline: none;
background-color: var(--clr-bg-muted);
border-color: var(--clr-border-main);
}
}
</style>

View File

@ -22,8 +22,6 @@
const branchController = getContext(BranchController);
const project = getContext(Project);
let meatballButton: HTMLDivElement;
let container: HTMLDivElement;
let isApplying = false;
function updateContextMenu(copyablePrUrl: string) {
@ -44,11 +42,9 @@
</script>
<div class="header__wrapper">
<div class="header card" bind:this={container}>
<div class="header card">
<div class="header__info">
<div class="header__label">
<BranchLabel bind:name={branch.name} />
</div>
<BranchLabel disabled bind:name={branch.name} />
<div class="header__remote-branch">
<div
class="status-tag text-base-11 text-semibold remote"
@ -90,8 +86,7 @@
</div>
</div>
<div class="header__actions">
<div class="header__buttons"></div>
<div class="relative" bind:this={meatballButton}>
<div class="header__buttons">
<Button
style="ghost"
kind="solid"
@ -145,15 +140,16 @@
display: flex;
flex-direction: column;
transition: margin var(--transition-slow);
padding: var(--size-12);
padding: var(--size-10);
gap: var(--size-10);
overflow: hidden;
}
.header__actions {
display: flex;
gap: var(--size-4);
background: var(--clr-bg-alt);
padding: var(--size-14);
justify-content: space-between;
justify-content: flex-end;
border-radius: 0 0 var(--radius-m) var(--radius-m);
user-select: none;
}
@ -162,12 +158,6 @@
position: relative;
gap: var(--size-4);
}
.header__label {
display: flex;
flex-grow: 1;
align-items: center;
gap: var(--size-4);
}
.header__remote-branch {
color: var(--clr-scale-ntrl-50);

View File

@ -2,7 +2,6 @@
import Icon from '$lib/components/Icon.svelte';
import { pxToRem } from '$lib/utils/pxToRem';
import { tooltip } from '$lib/utils/tooltip';
import { onMount } from 'svelte';
import type iconsJson from '$lib/icons/icons.json';
import type { ComponentColor, ComponentStyleKind } from '$lib/vbranches/types';
@ -15,9 +14,9 @@
export let tabindex = 0;
export let type: 'submit' | 'reset' | undefined = undefined;
// Layout props
export let width: number | undefined = undefined;
export let size: 'medium' | 'large' = 'medium';
export let reversedDirection: boolean = false;
export let width: number | undefined = undefined;
export let size: 'tag' | 'button' | 'cta' = 'button';
export let wide = false;
export let grow = false;
export let align: 'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline' | 'auto' = 'auto';
@ -32,11 +31,6 @@
export let badgeIcon: keyof typeof iconsJson | undefined = undefined;
const SLOTS = $$props.$$slots;
onMount(() => {
if (!element) return;
element.ariaLabel = element.innerText?.trim();
});
</script>
<button
@ -45,6 +39,7 @@
class:wide
class:grow
class:not-button={clickable}
class:fixed-width={!SLOTS}
class:is-dropdown={isDropdownChild}
style:align-self={align}
style:width={width ? pxToRem(width) : undefined}
@ -153,9 +148,9 @@
pointer-events: none;
}
}
.label {
display: inline-flex;
padding: 0 var(--size-2);
}
/* BADGE */
@ -221,7 +216,7 @@
&:not(.not-button, &:disabled):hover {
--btn-clr: var(--clr-scale-ntrl-20);
--btn-bg: oklch(from var(--clr-core-ntrl-60) l c h / 0.15);
--btn-bg: var(--clr-bg-muted);
}
& .badge {
@ -355,18 +350,43 @@
/* SIZE MODIFIERS */
.btn.medium {
height: var(--size-control-button);
min-width: var(--size-control-button);
padding: var(--size-4) var(--size-6);
.btn.tag {
height: var(--size-control-tag);
min-width: var(--size-control-tag);
padding: var(--size-2) var(--size-4);
}
.btn.large {
.btn.button {
height: var(--size-control-button);
min-width: var(--size-control-button);
padding: var(--size-4) var(--size-8);
}
.btn.cta {
height: var(--size-control-cta);
min-width: var(--size-control-cta);
padding: var(--size-6) var(--size-8);
}
/* FIXED WIDTH */
.btn.fixed-width {
&.tag {
width: var(--size-control-tag);
padding: var(--size-2);
}
&.button {
width: var(--size-control-button);
padding: var(--size-4);
}
&.cta {
width: var(--size-control-cta);
padding: var(--size-6);
}
}
/* DROPDOWN */
.is-dropdown {
&:first-of-type {

View File

@ -19,7 +19,7 @@
import { splitMessage } from '$lib/utils/commitMessage';
import { getContext, getContextStore } from '$lib/utils/context';
import { tooltip } from '$lib/utils/tooltip';
import { setAutoHeight } from '$lib/utils/useAutoHeight';
import { useAutoHeight } from '$lib/utils/useAutoHeight';
import { useResize } from '$lib/utils/useResize';
import { BranchController } from '$lib/vbranches/branchController';
import { Ownership } from '$lib/vbranches/ownership';
@ -69,8 +69,8 @@
}
function updateHeights() {
setAutoHeight(titleTextArea);
setAutoHeight(descriptionTextArea);
useAutoHeight(titleTextArea);
useAutoHeight(descriptionTextArea);
}
async function commit() {
@ -148,9 +148,9 @@
bind:this={titleTextArea}
use:focusTextareaOnMount
use:useResize={() => {
setAutoHeight(titleTextArea);
useAutoHeight(titleTextArea);
}}
on:focus={(e) => setAutoHeight(e.currentTarget)}
on:focus={(e) => useAutoHeight(e.currentTarget)}
on:input={(e) => {
$commitMessage = concatMessage(e.currentTarget.value, description);
}}
@ -172,8 +172,8 @@
spellcheck="false"
rows="1"
bind:this={descriptionTextArea}
use:useResize={() => setAutoHeight(descriptionTextArea)}
on:focus={(e) => setAutoHeight(e.currentTarget)}
use:useResize={() => useAutoHeight(descriptionTextArea)}
on:focus={(e) => useAutoHeight(e.currentTarget)}
on:input={(e) => {
$commitMessage = concatMessage(title, e.currentTarget.value);
}}
@ -182,7 +182,7 @@
if (e.key == 'Backspace' && value.length == 0) {
e.preventDefault();
titleTextArea.focus();
setAutoHeight(e.currentTarget);
useAutoHeight(e.currentTarget);
} else if (e.key == 'a' && (e.metaKey || e.ctrlKey) && value.length == 0) {
// select previous textarea on cmd+a if this textarea is empty
e.preventDefault();

View File

@ -1,7 +1,7 @@
<script lang="ts">
import FileStatusTag from './FileStatusTag.svelte';
import Tag from './Tag.svelte';
import IconButton from '$lib/components/IconButton.svelte';
import Button from '$lib/components/Button.svelte';
import { getVSIFileIcon } from '$lib/ext-icons';
import { computeFileStatus } from '$lib/utils/fileStatus';
import { computeAddedRemovedByFiles } from '$lib/utils/metrics';
@ -66,7 +66,7 @@
</div>
</div>
</div>
<IconButton icon="cross" size="m" on:click={() => dispatch('close')} />
<Button icon="cross" style="ghost" on:click={() => dispatch('close')} />
</div>
<style lang="postcss">

View File

@ -1,6 +1,6 @@
<script lang="ts">
import Button from './Button.svelte';
import AccountLink from '$lib/components/AccountLink.svelte';
import IconButton from '$lib/components/IconButton.svelte';
import * as events from '$lib/utils/events';
import { goto } from '$app/navigation';
@ -10,19 +10,19 @@
<div class="footer" class:collapsed={isNavCollapsed}>
<div class="left-btns">
<IconButton
<Button
icon="mail"
help="Send feedback"
size="l"
width={isNavCollapsed ? '100%' : undefined}
style="ghost"
size="cta"
on:mousedown={() => events.emit('openSendIssueModal')}
wide={isNavCollapsed}
/>
<IconButton
<Button
icon="settings"
help="Project settings"
size="l"
width={isNavCollapsed ? '100%' : undefined}
style="ghost"
size="cta"
on:mousedown={async () => goto(`/${projectId}/settings`)}
wide={isNavCollapsed}
/>
</div>
<AccountLink {isNavCollapsed} />

View File

@ -44,7 +44,7 @@
toasts.success('GitHub authenticated');
} catch (err: any) {
console.error(err);
toasts.success('GitHub authentication failed');
toasts.error('GitHub authentication failed');
} finally {
loading = false;
gitHubOauthModal.close();

View File

@ -1,65 +0,0 @@
<script lang="ts">
import Icon from '$lib/components/Icon.svelte';
import { tooltip } from '$lib/utils/tooltip';
import type iconsJson from '$lib/icons/icons.json';
export let icon: keyof typeof iconsJson;
export let size: 's' | 'm' | 'l' = 'l';
export let loading = false;
export let help = '';
export let width: string | undefined = undefined;
let className = '';
let selected = false;
export { className as class };
export let title = '';
</script>
<button
class="icon-btn {className} size-{size}"
class:selected
use:tooltip={help}
{title}
on:click
on:mousedown
style:width
>
<Icon name={loading ? 'spinner' : icon} />
</button>
<style lang="postcss">
.icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-m);
color: var(--clr-scale-ntrl-50);
cursor: pointer;
transition:
background-color var(--transition-fast),
color var(--transition-fast);
&:not(.selected):hover {
background-color: var(--clr-bg-muted);
color: var(--clr-scale-ntrl-40);
}
}
.selected {
background-color: var(--clr-bg-muted);
cursor: default;
}
.size-l {
height: var(--size-control-cta);
width: var(--size-control-cta);
padding: var(--size-8);
}
.size-m {
height: var(--size-control-button);
width: var(--size-control-button);
padding: var(--size-4);
}
.size-s {
height: var(--size-control-tag);
width: var(--size-control-tag);
padding: var(--size-2);
}
</style>

View File

@ -52,7 +52,7 @@
tabindex="0"
use:recordDimensions
use:clickOutside={{ handler: () => onDismiss() }}
style="position: absolute; top:{pos.y}px; left:{pos.x}px"
style="z-index: var(--z-floating); position: absolute; top:{pos.y}px; left:{pos.x}px"
>
<slot {item} dismiss={onDismiss} />
</div>

View File

@ -1,5 +1,5 @@
<script lang="ts">
import IconButton from './IconButton.svelte';
import Button from './Button.svelte';
import InfoMessage from './InfoMessage.svelte';
import MergeButton from './MergeButton.svelte';
import Tag from './Tag.svelte';
@ -245,9 +245,11 @@
{@const pr = $pr$}
<div class="card pr-card">
<div class="floating-button">
<IconButton
<Button
icon="update-small"
size="m"
size="tag"
style="ghost"
kind="soft"
loading={isFetchingDetails || isFetchingChecks}
help={$lastDetailsFetch ? 'Updated ' + $lastDetailsFetch : ''}
on:click={async () => {

View File

@ -1,6 +1,6 @@
<script lang="ts">
import SupportersBanner from './SupportersBanner.svelte';
import IconButton from '../IconButton.svelte';
import Button from '$lib/components/Button.svelte';
import Icon from '$lib/components/Icon.svelte';
import { UserService } from '$lib/stores/user';
import { getContext } from '$lib/utils/context';
@ -31,9 +31,10 @@
<div class="profile-sidebar__menu-wrapper">
<div class="profile-sidebar__header">
<div class="back-btn__icon">
<IconButton
<Button
icon="chevron-left"
size="m"
style="ghost"
kind="soft"
on:mousedown={() => {
if (history.length > 0) {
history.back();

View File

@ -13,6 +13,7 @@ export function computeFileStatus(file: AnyFile): FileStatus {
}
return 'M';
}
if (file.hunks.length == 1) {
const changeType = file.hunks[0].changeType;
if (changeType == 'added') {

View File

@ -39,7 +39,7 @@ export function tooltip(node: HTMLElement, optsOrString: ToolTipOptions | string
function onMouseLeave() {
// If tooltip shown when mouse out then we hide after delay
if (tooltip) hideAfterDelay();
if (tooltip) hide();
// But if we mouse out before tooltip is shown, we cancel the show timer
else if (timeoutId) {
clearTimeout(timeoutId);
@ -63,13 +63,6 @@ export function tooltip(node: HTMLElement, optsOrString: ToolTipOptions | string
timeoutId = undefined;
}
function hideAfterDelay() {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => hide(), 250);
}
function adjustPosition() {
if (!tooltip) return;

View File

@ -1,5 +1,5 @@
export function setAutoHeight(element: HTMLTextAreaElement) {
export function useAutoHeight(element: HTMLTextAreaElement) {
if (!element) return;
element.style.height = 'auto';
element.style.height = `${element.scrollHeight + 2}px`;
element.style.height = `${element.scrollHeight}px`;
}

View File

@ -1,9 +1,15 @@
export function useResize(element: HTMLElement, callback: (width: number, height: number) => void) {
export function useResize(
element: HTMLElement,
callback: (frame: { width: number; height: number }) => void
) {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
const { inlineSize, blockSize } = entry.borderBoxSize[0];
callback(width, height);
callback({
width: Math.round(inlineSize),
height: Math.round(blockSize)
});
}
});

View File

@ -22,6 +22,7 @@
import { onMount, setContext } from 'svelte';
import { Toaster } from 'svelte-french-toast';
import type { LayoutData } from './$types';
import { dev } from '$app/environment';
import { goto } from '$app/navigation';
export let data: LayoutData;
@ -71,7 +72,12 @@
});
</script>
<div data-tauri-drag-region class="app-root">
<div
data-tauri-drag-region
class="app-root"
role="application"
on:contextmenu={(e) => !dev && e.preventDefault()}
>
<slot />
</div>
<Toaster />

View File

@ -28,6 +28,7 @@
--z-ground: 1;
--z-lifted: 2;
--z-floating: 3;
--z-tooltip: 4;
--z-blocker: 10;
/* TODO: add focus color */

View File

@ -6,10 +6,24 @@
color: var(--clr-core-ntrl-60);
display: inline-block;
padding: var(--size-6);
z-index: var(--z-floating);
z-index: var(--z-tooltip);
max-width: 11.25rem;
position: absolute;
left: -9999px;
top: -9999px;
opacity: 0;
animation: showup-tooltip-animation 0.1s ease-out forwards;
}
@keyframes showup-tooltip-animation {
from {
opacity: 0;
transform: translateY(-0.2rem) scale(0.9);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}