-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added paper details to Paper View #144
base: develop
Are you sure you want to change the base?
Changes from all commits
fe91193
573ea12
16c49c2
2488cd8
c7c8eab
83f7ceb
3ea2a68
c0b18be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<script lang="ts"> | ||
import type { HTMLInputAttributes } from "svelte/elements"; | ||
import type { WithElementRef } from "bits-ui"; | ||
import { maxHeight } from "./abstract-max-height-action"; | ||
import ToggleableInput from "./ToggleableInput.svelte"; | ||
|
||
type Props = WithElementRef<HTMLInputAttributes> & { | ||
isEditable: boolean; | ||
maxHeightActionProps?: { showButtonBar: boolean; showAdditionalInfos: boolean }; | ||
}; | ||
|
||
let { | ||
isEditable = $bindable(false), | ||
maxHeightActionProps, | ||
value = $bindable(), | ||
}: Props = $props(); | ||
</script> | ||
|
||
<!-- | ||
@component | ||
Same as ToggleableInput, but configured for the abstract textarea on the paper details card. | ||
--> | ||
<ToggleableInput | ||
{isEditable} | ||
inputAction={maxHeight} | ||
inputActionProps={maxHeightActionProps} | ||
{value} | ||
/> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<script lang="ts"> | ||
import type { HTMLInputAttributes } from "svelte/elements"; | ||
import type { WithElementRef } from "bits-ui"; | ||
import { cn } from "$lib/utils/shadcn-helper"; | ||
import autosize from "svelte-autosize"; | ||
import type { Action } from "svelte/action"; | ||
|
||
type Props = WithElementRef<HTMLInputAttributes> & { | ||
isEditable: boolean; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
inputAction?: Action<HTMLTextAreaElement, any>; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
inputActionProps?: any; | ||
}; | ||
|
||
let { | ||
isEditable = $bindable(false), | ||
inputAction = () => {}, | ||
inputActionProps = {}, | ||
value = $bindable(), | ||
class: className, | ||
}: Props = $props(); | ||
</script> | ||
|
||
<!-- | ||
@component | ||
A textarea that can be toggled between read-only and editable mode. | ||
|
||
The `inputAction` and `inputActionProps` props are used to pass an action to the textarea. | ||
This is used in `AbstractToggleableInput` and probably won't be needed elsewhere. | ||
|
||
Usage: | ||
```svelte | ||
<ToggleableInput {isEditable} {value} /> | ||
``` | ||
--> | ||
<textarea | ||
use:autosize | ||
use:inputAction={inputActionProps} | ||
Comment on lines
+38
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think this workaround is necessary? The abstract's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What did you use to produce that? I tried plenty of approaches and none worked when the text overflows. The action was some sort of last resort. |
||
class={cn( | ||
"bg-background px-1.5 py-1 min-h-8 w-full resize-none text-default focus-visible:outline-none border", | ||
isEditable ? "border-input rounded-md" : "border-transparent", | ||
className, | ||
)} | ||
rows="1" | ||
{value} | ||
readonly={!isEditable} | ||
data-testid="toggleable-input" | ||
></textarea> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import type { Action } from "svelte/action"; | ||
|
||
/** | ||
* This action has the sole purpose of preventing the textarea from growing outside of the card it is in. | ||
* This is achieved by setting the max height of the textarea to the remaining space in the card. | ||
* | ||
* Previous attempts to find a dynamic solution to this problem have failed, so this action is a workaround by | ||
* using hard coded padding values that might change in the future. | ||
*/ | ||
export const maxHeight: Action< | ||
HTMLTextAreaElement, | ||
{ showButtonBar: boolean; showAdditionalInfos: boolean } | ||
> = (node, { showButtonBar, showAdditionalInfos }) => { | ||
setMaxHeight(node, showButtonBar, showAdditionalInfos); | ||
|
||
// Autosize action sets overflow X to scroll, so we need to reset it | ||
node.addEventListener("input", () => { | ||
node.style.overflowX = "hidden"; | ||
}); | ||
|
||
window.addEventListener("resize", () => { | ||
setMaxHeight(node, showButtonBar, showAdditionalInfos); | ||
}); | ||
|
||
return { | ||
update({ showButtonBar, showAdditionalInfos }) { | ||
setMaxHeight(node, showButtonBar, showAdditionalInfos); | ||
}, | ||
destroy() { | ||
window.removeEventListener("resize", () => { | ||
setMaxHeight(node, showButtonBar, showAdditionalInfos); | ||
}); | ||
}, | ||
}; | ||
}; | ||
|
||
/** | ||
* Sets the max height of the textarea element based on the padding and position of the element. | ||
* | ||
* @param node - the textarea element | ||
* @param showButtonBar - whether the button bar is shown | ||
* @param showAdditionalInfos - whether the additional infos are shown | ||
*/ | ||
function setMaxHeight( | ||
node: HTMLTextAreaElement, | ||
showButtonBar: boolean, | ||
showAdditionalInfos: boolean, | ||
) { | ||
// Calculate max height based on the window height | ||
// node.getBoundingClientRect().top gives the top y position of the element i.e. the start of the element | ||
// 113/53 is the sum of all the paddings the button bar and a border | ||
const padding = | ||
(showButtonBar ? 113 : 53) + // 40 Pixels of button bar + 20 pixels of gap | ||
(showAdditionalInfos ? 5 : 0); // somehow there's a 5px change when the additional infos are shown | ||
const maxHeight = window.innerHeight - node.getBoundingClientRect().top - padding; | ||
node.style.maxHeight = `${maxHeight}px`; | ||
node.style.overflowY = "auto"; | ||
node.style.overflowX = "hidden"; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,36 @@ | ||
<script lang="ts"> | ||
import { Skeleton } from "$lib/components/primitives/skeleton"; | ||
import type { Paper, PaperSpec } from "$lib/model/backend"; | ||
import { getNames } from "$lib/utils/common-helper"; | ||
|
||
interface Props { | ||
paper: Paper | PaperSpec; | ||
loadingPaper: Promise<Paper | PaperSpec>; | ||
} | ||
|
||
const { paper }: Props = $props(); | ||
const { loadingPaper }: Props = $props(); | ||
</script> | ||
|
||
<div class="grid grid-flow-row gap-0"> | ||
<div class="flex flex-row gap-1 items-center truncate"> | ||
{#if "id" in paper} | ||
<div class="w-fit text-default-sb-nc text-neutral-500">#{paper.id}</div> | ||
{/if} | ||
<h2 class="place-content-center truncate">{paper.title}</h2> | ||
{#await loadingPaper} | ||
<div class="grid grid-flow-row gap-1.5"> | ||
<Skeleton class="h-6 w-56 sm:w-80 md:w-[30rem] lg:w-[44rem] rounded-full" /> | ||
<Skeleton class="h-[1.125rem] w-28 sm:w-40 md:w-[15rem] lg:w-[22rem] rounded-full" /> | ||
</div> | ||
<div class="flex flex-row items-center text-hint truncate"> | ||
{#if paper.authors.length > 0} | ||
<span class="place-content-start truncate" | ||
>{paper.authors.map((a) => `${a.firstName} ${a.lastName}`).join(", ")}</span | ||
> | ||
{:else} | ||
<span class="italic">unknown authors</span> | ||
{/if} | ||
{:then paper} | ||
<div class="grid grid-flow-row gap-0"> | ||
<div class="flex flex-row gap-1 items-center truncate"> | ||
{#if "id" in paper} | ||
<div class="w-fit text-default-sb-nc text-neutral-500">#{paper.id}</div> | ||
{/if} | ||
Comment on lines
+21
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is used when the data is only a |
||
<h2 class="place-content-center truncate">{paper.title}</h2> | ||
</div> | ||
<div class="flex flex-row items-center text-hint truncate"> | ||
{#if paper.authors.length > 0} | ||
<span class="place-content-start truncate">{getNames(paper.authors)}</span> | ||
{:else} | ||
<span class="italic">unknown authors</span> | ||
{/if} | ||
</div> | ||
</div> | ||
</div> | ||
{:catch} | ||
<span class="text-error">Failed to load paper</span> | ||
{/await} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's your reasoning for pulling that promise inside this component?
Couldn't this component be used as is when wrapped in svelte's
{#await ...}
?This question arises multiple times throughout the code, one comment thread is enough though imo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used this because the component does not heavily rely on the asynchronous loading, i.e., we can just display the component and as soon as the paper is loaded the functionality works as well.
Therefore, wrapping the component in an await block seemed too much to me.
Where else did you notice this?