Skip to content

Commit

Permalink
Merge pull request #16657 from ElectronicBlueberry/workflow-embed
Browse files Browse the repository at this point in the history
Workflow Embed
  • Loading branch information
dannon authored Nov 8, 2023
2 parents 9741c3b + 2ba1e32 commit 307938e
Show file tree
Hide file tree
Showing 28 changed files with 1,301 additions and 724 deletions.
1 change: 1 addition & 0 deletions client/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const baseRules = {
required: {
some: ["nesting", "id"],
},
allowChildren: true,
},
],
"vuejs-accessibility/mouse-events-have-key-events": "warn",
Expand Down
44 changes: 0 additions & 44 deletions client/src/components/Common/SlugInput.vue

This file was deleted.

107 changes: 107 additions & 0 deletions client/src/components/Sharing/EditableUrl.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import { faCheck, faCopy, faEdit } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BButton } from "bootstrap-vue";
import { computed, nextTick, ref } from "vue";
import { copy } from "@/utils/clipboard";
import SlugInput from "./SlugInput.vue";
library.add(faCopy, faEdit, faCheck);
const props = defineProps<{
prefix: string;
slug: string;
}>();
const emit = defineEmits<{
(e: "change", value: string): void;
(e: "submit", value: string): void;
}>();
const editing = ref(false);
const slugInput = ref<InstanceType<typeof SlugInput>>();
const url = computed(() => props.prefix + props.slug);
async function onEdit() {
editing.value = true;
await nextTick();
(slugInput.value?.$el as HTMLInputElement).focus();
}
function onChange(value: string) {
emit("change", value);
}
function onSubmit() {
editing.value = false;
emit("submit", props.slug);
}
const copied = ref(false);
const clipboardTitle = computed(() => (copied.value ? "Copied!" : "Copy URL"));
function onCopy() {
copy(url.value);
copied.value = true;
}
function onCopyOut() {
copied.value = false;
}
</script>

<template>
<div class="editable-url">
url:
<a v-if="!editing" id="item-url" :href="url" target="_top">{{ url }}</a>
<span v-else id="item-url-text">
{{ prefix }}
<SlugInput
ref="slugInput"
class="ml-1"
:slug="props.slug"
@change="onChange"
@cancel="onChange"
@keyup.enter="onSubmit" />
</span>

<BButton
v-if="!editing"
v-b-tooltip.hover
class="inline-icon-button"
title="Edit URL"
size="md"
@click="onEdit">
<FontAwesomeIcon icon="edit" fixed-width />
</BButton>
<BButton v-else v-b-tooltip.hover class="inline-icon-button" title="Done" size="md" @click="onSubmit">
<FontAwesomeIcon icon="check" fixed-width />
</BButton>

<BButton
id="tooltip-clipboard"
v-b-tooltip.hover
size="md"
class="inline-icon-button"
:title="clipboardTitle"
@click="onCopy"
@mouseout="onCopyOut"
@blur="onCopyOut">
<FontAwesomeIcon :icon="['far', 'copy']" fixed-width />
</BButton>
</div>
</template>

<style scoped lang="scss">
.editable-url {
height: 1.5rem;
display: flex;
align-items: center;
gap: 0.25rem;
}
</style>
229 changes: 229 additions & 0 deletions client/src/components/Sharing/Embeds/WorkflowEmbed.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import { faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { useDebounce } from "@vueuse/core";
import { BButton, BFormCheckbox, BFormInput, BInputGroup, BInputGroupAppend } from "bootstrap-vue";
import { computed, reactive, ref } from "vue";
import { getAppRoot } from "@/onload/loadConfig";
import { copy } from "@/utils/clipboard";
import ZoomControl from "@/components/Workflow/Editor/ZoomControl.vue";
import WorkflowPublished from "@/components/Workflow/Published/WorkflowPublished.vue";
library.add(faCopy);
const props = defineProps<{
id: string;
}>();
const settings = reactive({
buttons: true,
about: false,
heading: false,
minimap: true,
zoomControls: true,
initialX: -20,
initialY: -20,
zoom: 1,
});
function onChangePosition(event: Event, xy: "x" | "y") {
const value = parseInt((event.target as HTMLInputElement).value);
if (xy === "x") {
settings.initialX = value;
} else {
settings.initialY = value;
}
}
const root = computed(() => {
const port = window.location.port ? `:${window.location.port}` : "";
return `${window.location.protocol}//${window.location.hostname}${port}${getAppRoot()}`;
});
const embedUrl = computed(() => {
let url = `${root.value}published/workflow?id=${props.id}&embed=true`;
url += `&buttons=${settings.buttons}`;
url += `&about=${settings.about}`;
url += `&heading=${settings.heading}`;
url += `&minimap=${settings.minimap}`;
url += `&zoom_controls=${settings.zoomControls}`;
url += `&initialX=${settings.initialX}&initialY=${settings.initialY}`;
url += `&zoom=${settings.zoom}`;
return url;
});
const embed = computed(() => `<iframe title="Galaxy Workflow Embed" src="${embedUrl.value}" />`);
// These Embed settings are not reactive, to we have to key them
const embedKey = computed(() => `zoom: ${settings.zoom}, x: ${settings.initialX}, y: ${settings.initialY}`);
const debouncedEmbedKey = useDebounce(embedKey, 500);
const showEmbed = ref(false);
const showEmbedDebounced = useDebounce(showEmbed, 100);
const copied = ref(false);
function onCopy() {
copy(embed.value);
copied.value = true;
}
function onCopyOut() {
copied.value = false;
}
const clipboardTitle = computed(() => (copied.value ? "Copied!" : "Copy URL"));
</script>

<template>
<div class="workflow-embed">
<div class="settings">
<BFormCheckbox v-model="settings.heading"> Show heading </BFormCheckbox>
<BFormCheckbox v-model="settings.about"> Show about </BFormCheckbox>
<BFormCheckbox v-model="settings.buttons"> Show buttons </BFormCheckbox>
<BFormCheckbox v-model="settings.minimap"> Show minimap </BFormCheckbox>
<BFormCheckbox v-model="settings.zoomControls"> Show zoom controls </BFormCheckbox>

<label for="embed-position-control" class="control-label">
Initial position
<div id="embed-position-control" class="position-control">
<label>
x:
<input
:value="settings.initialX"
type="number"
@change="(event) => onChangePosition(event, 'x')" />
</label>

<label>
y:
<input
:value="settings.initialY"
type="number"
@change="(event) => onChangePosition(event, 'y')" />
</label>
</div>
</label>

<label for="embed-zoom-control" class="control-label">
Initial zoom
<ZoomControl
id="embed-zoom-control"
:zoom-level="settings.zoom"
class="zoom-control"
@onZoom="(level) => (settings.zoom = level)" />
</label>
</div>
<div class="preview">
<label for="embed-code" class="w-100">
Embed code
<BInputGroup id="embed-code">
<BFormInput class="embed-code-input" :value="embed" readonly />
<BInputGroupAppend>
<BButton
v-b-tooltip.hover
:title="clipboardTitle"
variant="primary"
@click="onCopy"
@blur="onCopyOut">
<FontAwesomeIcon icon="copy" />
</BButton>
</BInputGroupAppend>
</BInputGroup>
</label>

<BFormCheckbox v-model="showEmbed" switch>Show embed Preview</BFormCheckbox>
<WorkflowPublished
v-if="showEmbedDebounced"
:id="props.id"
:key="debouncedEmbedKey"
class="published-preview"
:zoom="settings.zoom"
embed
:show-about="settings.about"
:show-buttons="settings.buttons"
:show-heading="settings.heading"
:show-minimap="settings.minimap"
:show-zoom-controls="settings.zoomControls"
:initial-x="settings.initialX"
:initial-y="settings.initialY" />
</div>
</div>
</template>

<style scoped lang="scss">
@import "theme/blue.scss";
.workflow-embed {
display: flex;
gap: 0.5rem;
}
@container (max-width: 1200px) {
.workflow-embed {
flex-direction: column;
}
}
.workflow-embed {
.settings {
flex: 1;
display: flex;
align-items: start;
justify-content: start;
flex-direction: column;
}
.preview {
flex: 1;
.published-preview {
border: 2px solid $brand-primary;
border-radius: 4px;
width: 100%;
height: 550px;
}
.embed-code-input {
background-color: transparent;
}
}
}
.control-label {
display: flex;
flex-direction: column;
margin-top: 0.25rem;
}
.zoom-control,
.position-control {
position: unset;
padding: 2px 4px;
border-color: $brand-primary;
border-radius: 4px;
border-width: 1px;
border-style: solid;
}
.position-control {
display: flex;
flex-direction: column;
width: 9rem;
input {
width: 100%;
}
label {
margin-bottom: 0;
display: flex;
gap: 0.25rem;
align-items: center;
}
}
</style>
Loading

0 comments on commit 307938e

Please sign in to comment.