Merge pull request #3298 from mayfieldiv/add-userSettings-tabSize-support

feat: make code tab size configurable
This commit is contained in:
Mattias Granlund 2024-03-24 20:41:09 +01:00 committed by GitHub
commit bec9224ee0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 179 additions and 14 deletions

View File

@ -12,6 +12,7 @@
export let selected: boolean = true;
export let readonly: boolean = false;
export let draggingDisabled: boolean = false;
export let tabSize = 4;
const dispatch = createEventDispatcher<{ selected: boolean }>();
@ -40,7 +41,12 @@
: 'bg-light-50 border-light-300 dark:bg-dark-700 dark:border-dark-400';
</script>
<div class="code-line text-sm" role="group" on:contextmenu|preventDefault>
<div
class="code-line text-sm"
role="group"
style="--tab-size: {tabSize}"
on:contextmenu|preventDefault
>
<div class="code-line__numbers-line">
<button
on:click={() => selectable && dispatch('selected', !selected)}
@ -78,8 +84,9 @@
width: 100%;
min-width: max-content;
font-family: monospace;
background-color: var(----clr-theme-container-light);
background-color: var(--clr-theme-container-light);
white-space: pre;
tab-size: var(--tab-size);
}
.line {

View File

@ -6,8 +6,9 @@
import { Project } from '$lib/backend/projects';
import { draggable } from '$lib/dragging/draggable';
import { draggableHunk } from '$lib/dragging/draggables';
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/settings/userSettings';
import { getContextByClass } from '$lib/utils/context';
import { onDestroy } from 'svelte';
import { getContext, onDestroy } from 'svelte';
import type { HunkSection } from '$lib/utils/fileSections';
import type { Ownership } from '$lib/vbranches/ownership';
import type { Hunk } from '$lib/vbranches/types';
@ -26,6 +27,7 @@
export let selectedOwnership: Writable<Ownership> | undefined = undefined;
export let linesModified: number;
const userSettings = getContext(SETTINGS_CONTEXT) as SettingsStore;
const project = getContextByClass(Project);
function onHunkSelected(hunk: Hunk, isSelected: boolean) {
@ -91,6 +93,7 @@
{minWidth}
{selectable}
{draggingDisabled}
tabSize={$userSettings.tabSize}
selected={$selectedOwnership?.containsHunk(hunk.filePath, hunk.id)}
on:selected={(e) => onHunkSelected(hunk, e.detail)}
sectionType={subsection.sectionType}

View File

@ -6,6 +6,7 @@
import { createEventDispatcher } from 'svelte';
export let orientation: 'row' | 'column' = 'column';
export let centerAlign = false;
export let extraPadding = false;
export let roundedTop = true;
export let roundedBottom = true;
@ -25,6 +26,7 @@
for={labelFor}
class="section-card"
style:flex-direction={orientation}
class:center-align={centerAlign && orientation === 'row'}
class:extra-padding={extraPadding}
class:rounded-top={roundedTop}
class:rounded-bottom={roundedBottom}
@ -167,4 +169,8 @@
pointer-events: none;
opacity: 0.5;
}
.center-align {
align-items: center;
}
</style>

View File

@ -1,5 +1,6 @@
<script lang="ts">
import Icon from '$lib/components/Icon.svelte';
import { pxToRem } from '$lib/utils/pxToRem';
import { createEventDispatcher, onMount } from 'svelte';
import type iconsJson from '$lib/icons/icons.json';
@ -7,10 +8,14 @@
export let id: string | undefined = undefined; // Required to make label clickable
export let icon: keyof typeof iconsJson | undefined = undefined;
export let value: string | undefined = undefined;
export let width: number | undefined = undefined;
export let textAlign: 'left' | 'center' | 'right' = 'left';
export let placeholder: string | undefined = undefined;
export let label: string | undefined = undefined;
export let reversedDirection: boolean = false;
export let wide: boolean = false;
export let minVal: number | undefined = undefined;
export let maxVal: number | undefined = undefined;
export let disabled = false;
export let readonly = false;
export let required = false;
@ -19,7 +24,7 @@
export let spellcheck = false;
export let focus = false;
export let type: 'text' | 'password' | 'select' = 'text';
export let type: 'text' | 'password' | 'select' | 'number' = 'text';
const dispatch = createEventDispatcher<{
input: string;
@ -28,6 +33,7 @@
}>();
let showPassword = false;
let isInputValid = true;
let htmlInput: HTMLInputElement;
onMount(() => {
@ -35,7 +41,13 @@
});
</script>
<div class="textbox" bind:this={element} class:wide>
<div
class="textbox"
bind:this={element}
class:wide
style:width={pxToRem(width)}
class:shaky={!isInputValid}
>
{#if label}
<label class="textbox__label text-base-13 text-semibold" for={id}>
{label}
@ -60,20 +72,56 @@
{placeholder}
{spellcheck}
{disabled}
min={minVal}
max={maxVal}
{...type == 'password' && showPassword ? { type: 'text' } : { type }}
class="text-input textbox__input text-base-13"
class:textbox__readonly={type != 'select' && readonly}
class:select-none={noselect}
class:select-all={selectall}
style:text-align={textAlign}
bind:value
bind:this={htmlInput}
on:click
on:mousedown
on:input={(e) => dispatch('input', e.currentTarget.value)}
on:input={(e) => {
dispatch('input', e.currentTarget.value);
isInputValid = e.currentTarget.checkValidity();
}}
on:change={(e) => dispatch('change', e.currentTarget.value)}
on:keydown={(e) => dispatch('keydown', e)}
/>
{#if type == 'number'}
<div class="textbox__count-actions">
<button
class="textbox__count-btn"
on:click={() => {
htmlInput.stepDown();
dispatch('input', htmlInput.value);
dispatch('change', htmlInput.value);
isInputValid = htmlInput.checkValidity();
}}
>
<Icon name="minus-small" />
</button>
<button
class="textbox__count-btn"
on:click={() => {
htmlInput.stepUp();
dispatch('input', htmlInput.value);
dispatch('change', htmlInput.value);
isInputValid = htmlInput.checkValidity();
}}
>
<Icon name="plus-small" />
</button>
</div>
{/if}
{#if type == 'password'}
<button
class="textbox__show-hide-icon"
@ -114,10 +162,6 @@
}
}
.textbox__input[type='select'] {
cursor: pointer;
}
.textbox__label {
color: var(--clr-theme-scale-ntrl-50);
}
@ -140,7 +184,7 @@
transform: translateY(-50%);
display: flex;
padding: var(--size-2) var(--size-4);
border-radius: var(--radius-m);
border-radius: var(--radius-s);
transition: background-color var(--transition-fast);
&:hover,
@ -151,6 +195,61 @@
}
}
/* select */
.textbox__input[type='select'] {
cursor: pointer;
}
/* number */
.textbox__input[type='number'] {
appearance: textfield;
-moz-appearance: textfield;
padding-right: 4.25rem;
}
.textbox__input[type='number']::-webkit-inner-spin-button,
.textbox__input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.textbox__count-actions {
z-index: 2;
position: absolute;
top: 50%;
right: var(--size-6);
transform: translateY(-50%);
display: flex;
}
.textbox__count-actions::before {
content: '';
position: absolute;
top: 50%;
left: -0.375rem;
transform: translateY(-50%);
width: 1px;
height: 100%;
background-color: var(--clr-theme-container-outline-light);
}
.textbox__count-btn {
display: flex;
align-items: center;
justify-content: center;
padding: var(--size-2) var(--size-4);
border-radius: var(--radius-s);
color: var(--clr-theme-scale-ntrl-50);
transition: background-color var(--transition-fast);
&:hover,
&:focus {
outline: none;
background-color: color-mix(in srgb, transparent, var(--darken-tint-light));
color: var(--clr-theme-scale-ntrl-40);
}
}
/* Modifiers */
.textbox__left-orient {

View File

@ -54,6 +54,7 @@
"logs": "M3 11.75H13V10.25H3V11.75Z M13 8.75L9 8.75V7.25L13 7.25V8.75Z M8.17154 6.5L4.48014 3.42384L3.51987 4.57617L5.82847 6.5L3.51987 8.42384L4.48014 9.57617L8.17154 6.5Z M0 5.83C0 4.12153 0 3.26729 0.33776 2.61708C0.622386 2.06915 1.06915 1.62239 1.61708 1.33776C2.26729 1 3.12153 1 4.83 1H11.17C12.8785 1 13.7327 1 14.3829 1.33776C14.9309 1.62239 15.3776 2.06915 15.6622 2.61708C16 3.26729 16 4.12153 16 5.83V10.17C16 11.8785 16 12.7327 15.6622 13.3829C15.3776 13.9309 14.9309 14.3776 14.3829 14.6622C13.7327 15 12.8785 15 11.17 15H4.83C3.12153 15 2.26729 15 1.61708 14.6622C1.06915 14.3776 0.622386 13.9309 0.33776 13.3829C0 12.7327 0 11.8785 0 10.17V5.83ZM4.83 2.5H11.17C12.0494 2.5 12.6173 2.50121 13.0492 2.53707C13.4631 2.57145 13.6161 2.62976 13.6915 2.66888C13.9654 2.81119 14.1888 3.03457 14.3311 3.30854C14.3702 3.38385 14.4285 3.53687 14.4629 3.95083C14.4988 4.38275 14.5 4.95059 14.5 5.83V10.17C14.5 11.0494 14.4988 11.6173 14.4629 12.0492C14.4285 12.4631 14.3702 12.6161 14.3311 12.6915C14.1888 12.9654 13.9654 13.1888 13.6915 13.3311C13.6161 13.3702 13.4631 13.4285 13.0492 13.4629C12.6173 13.4988 12.0494 13.5 11.17 13.5H4.83C3.95059 13.5 3.38275 13.4988 2.95083 13.4629C2.53687 13.4285 2.38385 13.3702 2.30854 13.3311C2.03457 13.1888 1.81119 12.9654 1.66888 12.6915C1.62976 12.6161 1.57145 12.4631 1.53707 12.0492C1.50121 11.6173 1.5 11.0494 1.5 10.17V5.83C1.5 4.95059 1.50121 4.38275 1.53707 3.95083C1.57145 3.53687 1.62976 3.38385 1.66888 3.30854C1.81119 3.03457 2.03457 2.81119 2.30854 2.66888C2.38385 2.62976 2.53687 2.57145 2.95083 2.53707C3.38275 2.50121 3.95059 2.5 4.83 2.5Z",
"mail": "M0 5.83C0 4.12153 0 3.26729 0.33776 2.61708C0.622386 2.06915 1.06915 1.62239 1.61708 1.33776C2.26729 1 3.12153 1 4.83 1H11.17C12.8785 1 13.7327 1 14.3829 1.33776C14.9309 1.62239 15.3776 2.06915 15.6622 2.61708C16 3.26729 16 4.12153 16 5.83V10.17C16 11.8785 16 12.7327 15.6622 13.3829C15.3776 13.9309 14.9309 14.3776 14.3829 14.6622C13.7327 15 12.8785 15 11.17 15H4.83C3.12153 15 2.26729 15 1.61708 14.6622C1.06915 14.3776 0.622386 13.9309 0.33776 13.3829C0 12.7327 0 11.8785 0 10.17V5.83ZM4.83 2.5H11.17C12.0494 2.5 12.6173 2.50121 13.0492 2.53707C13.4631 2.57145 13.6161 2.62976 13.6915 2.66888C13.9654 2.81119 14.1888 3.03457 14.3311 3.30854C14.352 3.34874 14.3783 3.41107 14.4036 3.5234L8.81349 8.31493C8.34537 8.71617 7.65462 8.71617 7.18651 8.31493L1.5964 3.52341C1.62165 3.41107 1.648 3.34874 1.66888 3.30854C1.81119 3.03457 2.03457 2.81119 2.30854 2.66888C2.38385 2.62976 2.53687 2.57145 2.95083 2.53707C3.38275 2.50121 3.95059 2.5 4.83 2.5ZM1.50025 5.41662C1.50003 5.54634 1.5 5.68387 1.5 5.83V10.17C1.5 11.0494 1.50121 11.6173 1.53707 12.0492C1.57145 12.4631 1.62976 12.6161 1.66888 12.6915C1.81119 12.9654 2.03457 13.1888 2.30854 13.3311C2.38385 13.3702 2.53687 13.4285 2.95083 13.4629C3.38275 13.4988 3.95059 13.5 4.83 13.5H11.17C12.0494 13.5 12.6173 13.4988 13.0492 13.4629C13.4631 13.4285 13.6161 13.3702 13.6915 13.3311C13.9654 13.1888 14.1888 12.9654 14.3311 12.6915C14.3702 12.6161 14.4285 12.4631 14.4629 12.0492C14.4988 11.6173 14.5 11.0494 14.5 10.17V5.83C14.5 5.68387 14.5 5.54634 14.4997 5.41661L9.78967 9.45382C8.75982 10.3365 7.24017 10.3365 6.21032 9.45382L1.50025 5.41662Z",
"merged-pr-small": "M6.32237 6.86159C7.29909 6.52151 8 5.59261 8 4.5C8 3.11929 6.88071 2 5.5 2C4.11929 2 3 3.11929 3 4.5C3 5.61941 3.73572 6.56698 4.75 6.88555V13H6.25V8.89185C6.48404 9.01126 6.73846 9.09858 7.00806 9.1476L9.22361 9.55043C9.81797 9.65849 10.25 10.1762 10.25 10.7803V13H11.75V10.7803C11.75 9.45123 10.7995 8.31237 9.49193 8.07462L7.27639 7.6718C6.82616 7.58994 6.46908 7.27305 6.32237 6.86159Z",
"minus-small": "M13 7.25V8.75H8.75L3 8.75V7.25L13 7.25Z",
"new-file": "M4 14.5H12C12.8284 14.5 13.5 13.8284 13.5 13V6.24264C13.5 5.84482 13.342 5.46329 13.0607 5.18198L9.81802 1.93934C9.53671 1.65804 9.15518 1.5 8.75736 1.5H4C3.17157 1.5 2.5 2.17157 2.5 3V13C2.5 13.8284 3.17157 14.5 4 14.5ZM4 0C2.34315 0 1 1.34315 1 3V13C1 14.6569 2.34315 16 4 16H12C13.6569 16 15 14.6569 15 13V6.24264C15 5.44699 14.6839 4.68393 14.1213 4.12132L10.8787 0.87868C10.3161 0.316071 9.55301 0 8.75736 0H4Z M7.25 7.25V4H8.75V7.25H12V8.75H8.75V12H7.25V8.75H4V7.25H7.25Z",
"new-file-small-filled": "M4.5 1C2.84315 1 1.5 2.34315 1.5 4V12C1.5 13.6569 2.84315 15 4.5 15H11C12.6569 15 14.5 13.6569 14.5 12V5.59415C14.5 4.81243 14.1949 4.06158 13.6496 3.50145L12.0976 1.9073C11.5328 1.32721 10.7576 1 9.94802 1H4.5ZM7.25 4V7.25H4V8.75L7.25 8.75V12H8.75V8.75H12V7.25H8.75V4H7.25Z",
"open-link": "M5.5 4.75C5.08579 4.75 4.75 5.08579 4.75 5.5V10.5C4.75 10.9142 5.08579 11.25 5.5 11.25H10.5C10.9142 11.25 11.25 10.9142 11.25 10.5V10H12.75V10.5C12.75 11.7426 11.7426 12.75 10.5 12.75H5.5C4.25736 12.75 3.25 11.7426 3.25 10.5V5.5C3.25 4.25736 4.25736 3.25 5.5 3.25H6V4.75H5.5Z M10.1893 4.75H8V3.25H12.75V8H11.25V5.81066L7.53033 9.53033L6.46967 8.46967L10.1893 4.75Z",

View File

@ -16,6 +16,7 @@ export interface Settings {
defaultTreeHeight: number;
zoom: number;
scrollbarVisabilityOnHover: boolean;
tabSize: number;
}
const defaults: Settings = {
@ -29,7 +30,8 @@ const defaults: Settings = {
defaultTreeHeight: 100,
stashedBranchesHeight: 150,
zoom: 1,
scrollbarVisabilityOnHover: false
scrollbarVisabilityOnHover: false,
tabSize: 4
};
export type SettingsStore = Writable<Settings>;

View File

@ -1,3 +1,4 @@
export function pxToRem(px: number, base: number = 16) {
export function pxToRem(px: number | undefined, base: number = 16) {
if (!px) return;
return `${px / base}rem`;
}

View File

@ -176,10 +176,32 @@
{/if}
<SectionCard>
<svelte:fragment slot="title">Appearance</svelte:fragment>
<svelte:fragment slot="title">Theme</svelte:fragment>
<ThemeSelector {userSettings} />
</SectionCard>
<SectionCard orientation="row" centerAlign>
<svelte:fragment slot="title">Hunks tab size</svelte:fragment>
<svelte:fragment slot="actions">
<TextBox
type="number"
width={100}
textAlign="center"
value={$userSettings.tabSize.toString()}
minVal={1}
maxVal={8}
on:change={(e) => {
userSettings.update((s) => ({
...s,
tabSize: parseInt(e.detail) || $userSettings.tabSize
}));
}}
placeholder={$userSettings.tabSize.toString()}
/>
</svelte:fragment>
</SectionCard>
<SectionCard labelFor="hoverScrollbarVisability" orientation="row">
<svelte:fragment slot="title">Dynamic scrollbar visibility on hover</svelte:fragment>
<svelte:fragment slot="caption">

View File

@ -136,3 +136,27 @@ dialog::backdrop {
opacity: 1;
}
}
/* STATES */
.shaky {
animation: shaky 0.35s forwards;
}
@keyframes shaky {
0% {
transform: translateX(-3px);
}
25% {
transform: translateX(3px);
}
50% {
transform: translateX(-2px);
}
75% {
transform: translateX(2px);
}
100% {
transform: translateX(0);
}
}