Skip to content
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

feat: added link component #266

Merged
merged 8 commits into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/docs/public/components/link.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions ui/src/builder/BuilderApp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ function handleRendererClick(ev: PointerEvent): void {
const targetId = targetEl.dataset.streamsyncId;
const targetInstancePath = targetEl.dataset.streamsyncInstancePath;
if (targetId !== ssbm.getSelectedId()) {
ev.preventDefault();
ev.stopPropagation();
ssbm.setSelection(targetId, targetInstancePath);
}
Expand Down
12 changes: 11 additions & 1 deletion ui/src/builder/BuilderFieldsText.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
:id="`list-${componentId}-${fieldKey}`"
>
<option
v-for="(option, optionKey) in templateField.options"
v-for="(option, optionKey) in options"
:key="optionKey"
:value="optionKey"
>
Expand Down Expand Up @@ -76,6 +76,16 @@ const templateField = computed(() => {
return definition.fields[fieldKey.value];
});

const options = computed(() => {
const field = templateField.value;
if (field.options) {
return typeof field.options === "function"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @FabienArcellier check this out

@raaymax worked on a dynamic list of options. You can pass it hardcoded options (as we do now), but you can also pass it a function to generate the options.

This was one of the challenges I saw for the Reuse Component component. So with this we're halfway there. I've asked him to work on Reuse Component.

@raaymax for your reference #215

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way I'm talking specifically about selecting the id of the component that we want to reuse.

? field.options(ss, componentId.value)
: field.options;
}
return [];
});

const handleInput = (ev: Event) => {
setContentValue(
component.value.id,
Expand Down
2 changes: 2 additions & 0 deletions ui/src/core/templateMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import CorePlotlyGraph from "../core_components/content/CorePlotlyGraph.vue";
import CoreText from "../core_components/content/CoreText.vue";
import CoreVegaLiteChart from "../core_components/content/CoreVegaLiteChart.vue";
import CoreVideoPlayer from "../core_components/content/CoreVideoPlayer.vue";
import CoreLink from "../core_components/content/CoreLink.vue";
// input
import CoreCheckboxInput from "../core_components/input/CoreCheckboxInput.vue";
import CoreDateInput from "../core_components/input/CoreDateInput.vue";
Expand Down Expand Up @@ -69,6 +70,7 @@ const templateMap = {
columns: CoreColumns,
tab: CoreTab,
tabs: CoreTabs,
link: CoreLink,
horizontalstack: CoreHorizontalStack,
separator: CoreSeparator,
image: CoreImage,
Expand Down
81 changes: 81 additions & 0 deletions ui/src/core_components/content/CoreLink.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<template>
<div class="CoreLink">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're working on links, can you please check how the Markdown links are working, if everything is alright with them?

I assume that with a Text component, markdown mode, I can [hello](https://streamsync.cloud).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked and yes - you can. You can also [Next page](#next-page) to navigate between pages.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it's basically the same, only difference is that you can select page in component and define rel and target.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking!

Yes they're quite similar, that's what kept me from creating this component. However, now that I think more about it:

  • Agreed, there's the rel and target
  • You can change the color
  • It's more semantic. If you see a component tree with 3 Link, you it's self-explanatory. You see 3 Text, could be anything.
  • It's more easily discoverable. "I need to create a link, so I'll create a Text component which probably supports markdown and I'll []() my way into it." -Probably no one ever, at least not on their first try.

By the way there's the possibility of opening URLs from the backend via backend actions too.

<a
:href="fields.url.value"
:target="fields.target.value"
:rel="fields.rel.value"
>
{{ displayText }}
</a>
</div>
</template>

<script lang="ts">
import { FieldType, Core } from "../../streamsyncTypes";
import { cssClasses, primaryTextColor } from "../../renderer/sharedStyleFields";
import injectionKeys from "../../injectionKeys";
export default {
streamsync: {
name: "Link",
description: "A component to create a hyperlink.",
category: "Content",
fields: {
url: {
name: "URL",
type: FieldType.Text,
default: "https://streamsync.cloud",
desc: "Specify a URL or choose a page. Keep in mind that you can only link to pages for which a key has been specified.",
options: (ss: Core) => {
return Object.fromEntries(
ss
.getComponents("root", true)
.map((page) => page.content.key)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Careful with this one. Like I was saying earlier not all pages will have keys. In fact I expect most pages to not have keys.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah sorry didn't see the next line

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats why there is filter in the next line

.filter((key) => Boolean(key))
.map((key) => [`#${key}`, `Page with key: ${key}`]),
);
},
},
target: {
name: "Target",
type: FieldType.Text,
options: {
_self: "Self",
_blank: "Blank",
_parent: "Parent",
_top: "Top",
},
desc: "Specifies where to open the linked document.",
default: "_self",
},
rel: {
name: "Rel",
type: FieldType.Text,
desc: "Specifies the relationship between the current document and the linked document.",
},
text: {
name: "Text",
default: "",
type: FieldType.Text,
desc: "The text to display in the link.",
},
primaryTextColor,
cssClasses,
},
},
};
</script>

<script setup lang="ts">
import { inject, computed } from "vue";
const fields = inject(injectionKeys.evaluatedFields);

const displayText = computed(() => {
return fields.text.value || fields.url.value;
});
</script>

<style scoped>
.CoreLink a {
color: var(--primaryTextColor);
}
</style>
14 changes: 11 additions & 3 deletions ui/src/streamsyncTypes.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { generateCore } from "./core";
import { generateBuilderManager } from "./builder/builderManager";

export type Core = ReturnType<typeof generateCore>;

type ComponentId = string;

/**
* Basic building block of applications.
* Multiple instances of a single component can exists. For example, via Repeater.
*/

export type Component = {
id: string;
id: ComponentId;
parentId: string;
type: string;
position: number;
Expand Down Expand Up @@ -54,7 +58,12 @@ export type StreamsyncComponentDefinition = {
desc?: string; // Description
default?: string; // Value used if the field is empty, e.g. "(No text)"
control?: FieldControl; // Which control (text, textarea, etc) to use if not the default for the type
options?: Record<string, string>; // List of values to be provided as autocomplete options
options?:
| Record<string, string>
| ((
ss?: Core,
componentId?: ComponentId,
) => Record<string, string>); // List of values to be provided as autocomplete options
type: FieldType; // Data type for the field
category?: FieldCategory; // Category (Layout, Content, etc)
applyStyleVariable?: boolean; // Use the value of this field as a CSS variable
Expand All @@ -72,7 +81,6 @@ export type StreamsyncComponentDefinition = {
positionless?: boolean; // Whether this type of component is positionless (like Sidebar)
};

export type Core = ReturnType<typeof generateCore>;
export type BuilderManager = ReturnType<typeof generateBuilderManager>;

export const enum ClipboardOperation {
Expand Down
Loading