Enhance Form Builder Components with Readonly State Support and New Modal Functionality
- Updated ComponentPreview.vue to include support for the 'searchSelect' and 'switch' components in readonly states, ensuring consistent behavior across form fields. - Modified FormBuilderComponents.vue to set default readonly properties for various components, enhancing usability and preventing unintended edits. - Enhanced FormBuilderFieldSettingsModal.vue to reflect the updated readonly logic for component types, improving user awareness of field capabilities. - Introduced a new modal in manage.vue for copying workflow links, allowing users to select between direct and iframe link types with customizable options. - Improved link generation logic to support iframe parameters, enhancing the flexibility of sharing workflows. - Updated styles in SearchSelect.vue and Switch.vue to visually indicate readonly states, ensuring a consistent user experience across components.
This commit is contained in:
parent
f024cc91dd
commit
f86fe87fc5
@ -27,7 +27,7 @@
|
||||
:label="component.props.label" :help="component.props.help" :placeholder="component.props.placeholder"
|
||||
:validation="component.props.validation" :validation-visibility="isPreview ? 'live' : 'blur'"
|
||||
:readonly="component.props.readonly || !isPreview"
|
||||
:disabled="!isPreview || (component.props.readonly && ['select', 'checkbox', 'radio'].includes(component.type))"
|
||||
:disabled="!isPreview || (component.props.readonly && ['select', 'searchSelect', 'checkbox', 'radio', 'switch'].includes(component.type))"
|
||||
:options="component.props.options || undefined" :value="component.props.value || undefined"
|
||||
:accept="component.props.accept || undefined" :max="component.props.max || undefined"
|
||||
:mask="component.props.mask || undefined" :digits="component.props.digits || undefined"
|
||||
@ -36,11 +36,12 @@
|
||||
:classes="component.type === 'checkbox' ? {
|
||||
wrapper: 'mb-1',
|
||||
options: 'space-y-0.5'
|
||||
} : {}" :class="{
|
||||
} : { }" :class="{
|
||||
'canvas-component': isPreview,
|
||||
'readonly-select': component.props.readonly && component.type === 'select',
|
||||
'readonly-select': component.props.readonly && (component.type === 'select' || component.type === 'searchSelect'),
|
||||
'readonly-checkbox': component.props.readonly && component.type === 'checkbox',
|
||||
'readonly-radio': component.props.readonly && component.type === 'radio'
|
||||
'readonly-radio': component.props.readonly && component.type === 'radio',
|
||||
'readonly-switch': component.props.readonly && component.type === 'switch'
|
||||
}" />
|
||||
|
||||
<!-- Heading -->
|
||||
@ -1590,29 +1591,33 @@ const getButtonSizeClass = (size) => {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Readonly styles for select, checkbox, and radio components */
|
||||
/* Readonly styles for select, checkbox, radio, and switch components */
|
||||
:deep(.readonly-select),
|
||||
:deep(.readonly-checkbox),
|
||||
:deep(.readonly-radio) {
|
||||
:deep(.readonly-radio),
|
||||
:deep(.readonly-switch) {
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
:deep(.readonly-select select),
|
||||
:deep(.readonly-select .formkit-inner),
|
||||
:deep(.readonly-checkbox input[type="checkbox"]),
|
||||
:deep(.readonly-radio input[type="radio"]) {
|
||||
:deep(.readonly-radio input[type="radio"]),
|
||||
:deep(.readonly-switch input[type="checkbox"]) {
|
||||
pointer-events: none !important;
|
||||
opacity: 0.8 !important;
|
||||
background-color: #f3f4f6 !important;
|
||||
}
|
||||
|
||||
:deep(.readonly-checkbox) .formkit-options,
|
||||
:deep(.readonly-radio) .formkit-options {
|
||||
:deep(.readonly-radio) .formkit-options,
|
||||
:deep(.readonly-switch) .formkit-wrapper {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
:deep(.readonly-checkbox) .formkit-wrapper,
|
||||
:deep(.readonly-radio) .formkit-wrapper {
|
||||
:deep(.readonly-radio) .formkit-wrapper,
|
||||
:deep(.readonly-switch) .formkit-wrapper {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
|
@ -136,6 +136,7 @@ const availableComponents = [
|
||||
placeholder: 'Enter text...',
|
||||
help: '',
|
||||
validation: '',
|
||||
readonly: false,
|
||||
// Conditional Logic Properties
|
||||
conditionalLogic: {
|
||||
enabled: false,
|
||||
@ -156,6 +157,7 @@ const availableComponents = [
|
||||
placeholder: 'Enter text...',
|
||||
help: '',
|
||||
validation: '',
|
||||
readonly: false,
|
||||
// Conditional Logic Properties
|
||||
conditionalLogic: {
|
||||
enabled: false,
|
||||
@ -176,6 +178,7 @@ const availableComponents = [
|
||||
placeholder: '0',
|
||||
help: '',
|
||||
validation: '',
|
||||
readonly: false,
|
||||
// Conditional Logic Properties
|
||||
conditionalLogic: {
|
||||
enabled: false,
|
||||
@ -196,6 +199,7 @@ const availableComponents = [
|
||||
placeholder: 'email@example.com',
|
||||
help: '',
|
||||
validation: 'email',
|
||||
readonly: false,
|
||||
// Conditional Logic Properties
|
||||
conditionalLogic: {
|
||||
enabled: false,
|
||||
@ -216,6 +220,7 @@ const availableComponents = [
|
||||
placeholder: 'Enter password...',
|
||||
help: '',
|
||||
validation: '',
|
||||
readonly: false,
|
||||
// Conditional Logic Properties
|
||||
conditionalLogic: {
|
||||
enabled: false,
|
||||
@ -235,7 +240,8 @@ const availableComponents = [
|
||||
type: 'url',
|
||||
placeholder: 'https://example.com',
|
||||
help: '',
|
||||
validation: 'url'
|
||||
validation: 'url',
|
||||
readonly: false
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -248,7 +254,8 @@ const availableComponents = [
|
||||
type: 'tel',
|
||||
placeholder: '+1 (555) 123-4567',
|
||||
help: '',
|
||||
validation: ''
|
||||
validation: '',
|
||||
readonly: false
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -262,7 +269,8 @@ const availableComponents = [
|
||||
placeholder: 'Enter value...',
|
||||
help: 'Input will be formatted according to the mask',
|
||||
mask: '###-###-####',
|
||||
validation: ''
|
||||
validation: '',
|
||||
readonly: false
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -295,6 +303,7 @@ const availableComponents = [
|
||||
{ label: 'Option 3', value: 'option_3' }
|
||||
],
|
||||
validation: '',
|
||||
readonly: false,
|
||||
// Conditional Logic Properties
|
||||
conditionalLogic: {
|
||||
enabled: false,
|
||||
@ -320,6 +329,7 @@ const availableComponents = [
|
||||
{ label: 'Option 3', value: 'option_3' }
|
||||
],
|
||||
validation: '',
|
||||
readonly: false,
|
||||
// Conditional Logic Properties
|
||||
conditionalLogic: {
|
||||
enabled: false,
|
||||
@ -345,7 +355,8 @@ const availableComponents = [
|
||||
{ label: 'Option 2', value: 'option_2' },
|
||||
{ label: 'Option 3', value: 'option_3' }
|
||||
],
|
||||
validation: ''
|
||||
validation: '',
|
||||
readonly: false
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -362,7 +373,8 @@ const availableComponents = [
|
||||
{ label: 'Option 2', value: 'option_2' },
|
||||
{ label: 'Option 3', value: 'option_3' }
|
||||
],
|
||||
validation: ''
|
||||
validation: '',
|
||||
readonly: false
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -377,7 +389,8 @@ const availableComponents = [
|
||||
name: 'switch_field',
|
||||
help: 'Toggle this option on or off',
|
||||
value: false,
|
||||
validation: ''
|
||||
validation: '',
|
||||
readonly: false
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -2223,7 +2223,7 @@ const showField = (fieldName) => {
|
||||
rows: ['textarea'],
|
||||
options: ['select', 'searchSelect', 'checkbox', 'radio'],
|
||||
conditionalLogic: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group'],
|
||||
readonly: ['text', 'number', 'email', 'textarea', 'mask', 'url', 'tel']
|
||||
readonly: ['text', 'number', 'email', 'password', 'textarea', 'mask', 'url', 'tel', 'select', 'searchSelect', 'checkbox', 'radio', 'switch']
|
||||
}
|
||||
|
||||
return fieldConfig[fieldName]?.includes(props.component.type) || false
|
||||
|
@ -5,14 +5,14 @@
|
||||
@input="handleChange"
|
||||
:options="context.options || []"
|
||||
:placeholder="context.placeholder || 'Search and select an option'"
|
||||
:disabled="context.disabled"
|
||||
:searchable="true"
|
||||
:disabled="context.disabled || context.readonly"
|
||||
:searchable="!context.readonly"
|
||||
:clearable="false"
|
||||
label="label"
|
||||
:reduce="option => option.value"
|
||||
:class="[
|
||||
'vue-select-wrapper',
|
||||
{ 'vue-select-disabled': context.disabled }
|
||||
{ 'vue-select-disabled': context.disabled || context.readonly }
|
||||
]"
|
||||
/>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
:value="_value"
|
||||
:name="context.node.name"
|
||||
:id="context.id"
|
||||
:disabled="context.disabled"
|
||||
:disabled="context.disabled || context.readonly"
|
||||
:required="context.attrs.required"
|
||||
class="hidden-select"
|
||||
tabindex="-1"
|
||||
@ -60,6 +60,10 @@ const _value = computed({
|
||||
|
||||
// Handle value changes from vue3-select-component
|
||||
const handleChange = (value) => {
|
||||
// Don't allow changes if readonly
|
||||
if (props.context.readonly) {
|
||||
return
|
||||
}
|
||||
props.context.node.input(value)
|
||||
}
|
||||
</script>
|
||||
@ -81,4 +85,20 @@ const handleChange = (value) => {
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
/* Readonly styles for SearchSelect */
|
||||
.vue-select-disabled {
|
||||
opacity: 0.8;
|
||||
background-color: #f3f4f6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.vue-select-disabled .vs__dropdown-toggle {
|
||||
background-color: #f3f4f6 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.vue-select-disabled .vs__actions {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
</style>
|
@ -4,6 +4,10 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
function handleChange(event) {
|
||||
// Don't allow changes if readonly
|
||||
if (props.context.readonly) {
|
||||
return;
|
||||
}
|
||||
props.context.node.input(event.target.checked);
|
||||
}
|
||||
</script>
|
||||
@ -16,11 +20,11 @@ function handleChange(event) {
|
||||
:name="context.name"
|
||||
type="checkbox"
|
||||
:checked="context.value"
|
||||
:disabled="context.disabled"
|
||||
:disabled="context.disabled || context.readonly"
|
||||
@change="handleChange"
|
||||
class="switch-input"
|
||||
/>
|
||||
<label :for="context.id" class="switch-label">
|
||||
<label :for="context.id" class="switch-label" :class="{ 'cursor-not-allowed': context.readonly }">
|
||||
<span class="switch-track">
|
||||
<span class="switch-thumb"></span>
|
||||
</span>
|
||||
@ -91,4 +95,23 @@ function handleChange(event) {
|
||||
.switch-input:checked + .switch-label .switch-thumb {
|
||||
transform: translateX(1.25rem) translateZ(0);
|
||||
}
|
||||
|
||||
/* Readonly styles for Switch */
|
||||
.switch-input:disabled + .switch-label .switch-track {
|
||||
@apply opacity-50 cursor-not-allowed;
|
||||
}
|
||||
|
||||
.switch-input:disabled + .switch-label {
|
||||
@apply cursor-not-allowed;
|
||||
}
|
||||
|
||||
/* Additional readonly styling */
|
||||
.switch-label.cursor-not-allowed {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.switch-label.cursor-not-allowed .switch-track {
|
||||
opacity: 0.8;
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
</style>
|
@ -29,6 +29,16 @@ const sortBy = ref('processCreatedDate');
|
||||
const sortOrder = ref('desc');
|
||||
const currentView = ref('dashboard'); // 'dashboard', 'list', 'analytics'
|
||||
|
||||
// Copy link modal state
|
||||
const showCopyLinkModal = ref(false);
|
||||
const selectedProcess = ref(null);
|
||||
const copyLinkType = ref('direct'); // 'direct' or 'iframe'
|
||||
const iframeOptions = ref({
|
||||
debug: false,
|
||||
hideComplete: true,
|
||||
theme: 'light'
|
||||
});
|
||||
|
||||
// Pagination state
|
||||
const currentPage = ref(1);
|
||||
const itemsPerPage = ref(20); // Default to 20 items per page
|
||||
@ -451,16 +461,119 @@ onUnmounted(() => {
|
||||
clearTimeout(searchTimeout);
|
||||
});
|
||||
|
||||
// Copy workflow run link to clipboard
|
||||
const copyWorkflowLink = async (processId) => {
|
||||
// Enhanced copy workflow link functionality
|
||||
const openCopyLinkModal = (process) => {
|
||||
selectedProcess.value = process;
|
||||
showCopyLinkModal.value = true;
|
||||
};
|
||||
|
||||
const copyWorkflowLink = async (processId, linkType = 'direct', options = {}) => {
|
||||
try {
|
||||
const link = `${window.location.origin}/workflow/${processId}`;
|
||||
await navigator.clipboard.writeText(link);
|
||||
toast.success('Run link copied to clipboard!');
|
||||
// Check if we're on the client side
|
||||
if (process.client) {
|
||||
let link = `${baseUrl.value}/workflow/${processId}`;
|
||||
|
||||
if (linkType === 'iframe') {
|
||||
const params = new URLSearchParams();
|
||||
if (options.debug !== undefined) params.append('debug', options.debug);
|
||||
if (options.hideComplete !== undefined) params.append('hideComplete', options.hideComplete);
|
||||
if (options.theme) params.append('theme', options.theme);
|
||||
|
||||
if (params.toString()) {
|
||||
link += `?${params.toString()}`;
|
||||
}
|
||||
}
|
||||
|
||||
await navigator.clipboard.writeText(link);
|
||||
toast.success(`${linkType === 'iframe' ? 'Iframe link' : 'Direct link'} copied to clipboard!`);
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error('Failed to copy link');
|
||||
}
|
||||
};
|
||||
|
||||
const openWorkflowLink = (processId, linkType = 'direct', options = {}) => {
|
||||
// Check if we're on the client side
|
||||
if (process.client) {
|
||||
let link = `${baseUrl.value}/workflow/${processId}`;
|
||||
|
||||
if (linkType === 'iframe') {
|
||||
const params = new URLSearchParams();
|
||||
if (options.debug !== undefined) params.append('debug', options.debug);
|
||||
if (options.hideComplete !== undefined) params.append('hideComplete', options.hideComplete);
|
||||
if (options.theme) params.append('theme', options.theme);
|
||||
|
||||
if (params.toString()) {
|
||||
link += `?${params.toString()}`;
|
||||
}
|
||||
}
|
||||
|
||||
window.open(link, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
const closeCopyLinkModal = () => {
|
||||
showCopyLinkModal.value = false;
|
||||
selectedProcess.value = null;
|
||||
copyLinkType.value = 'direct';
|
||||
iframeOptions.value = {
|
||||
debug: false,
|
||||
hideComplete: true,
|
||||
theme: 'light'
|
||||
};
|
||||
};
|
||||
|
||||
const handleCopyLink = async () => {
|
||||
if (!selectedProcess.value) return;
|
||||
|
||||
await copyWorkflowLink(selectedProcess.value.id, copyLinkType.value, copyLinkType.value === 'iframe' ? iframeOptions.value : {});
|
||||
closeCopyLinkModal();
|
||||
};
|
||||
|
||||
const handleOpenLink = () => {
|
||||
if (!selectedProcess.value) return;
|
||||
|
||||
openWorkflowLink(selectedProcess.value.id, copyLinkType.value, copyLinkType.value === 'iframe' ? iframeOptions.value : {});
|
||||
closeCopyLinkModal();
|
||||
};
|
||||
|
||||
// Computed properties for link generation
|
||||
const baseUrl = computed(() => {
|
||||
return process.client ? window.location.origin : '';
|
||||
});
|
||||
|
||||
const generatedLink = computed(() => {
|
||||
if (!selectedProcess.value || !baseUrl.value) return '';
|
||||
|
||||
if (copyLinkType.value === 'iframe') {
|
||||
const params = new URLSearchParams();
|
||||
if (iframeOptions.value.debug !== undefined) params.append('debug', iframeOptions.value.debug);
|
||||
if (iframeOptions.value.hideComplete !== undefined) params.append('hideComplete', iframeOptions.value.hideComplete);
|
||||
if (iframeOptions.value.theme) params.append('theme', iframeOptions.value.theme);
|
||||
|
||||
return `${baseUrl.value}/workflow/${selectedProcess.value.id}${params.toString() ? `?${params.toString()}` : ''}`;
|
||||
}
|
||||
|
||||
return `${baseUrl.value}/workflow/${selectedProcess.value.id}`;
|
||||
});
|
||||
|
||||
const generateIframeCode = () => {
|
||||
if (!selectedProcess.value || !baseUrl.value) return '';
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (iframeOptions.value.debug !== undefined) params.append('debug', iframeOptions.value.debug);
|
||||
if (iframeOptions.value.hideComplete !== undefined) params.append('hideComplete', iframeOptions.value.hideComplete);
|
||||
if (iframeOptions.value.theme) params.append('theme', iframeOptions.value.theme);
|
||||
|
||||
const url = `${baseUrl.value}/workflow/${selectedProcess.value.id}${params.toString() ? `?${params.toString()}` : ''}`;
|
||||
|
||||
return `<iframe
|
||||
src="${url}"
|
||||
width="100%"
|
||||
height="600px"
|
||||
style="border: none; border-radius: 8px;">
|
||||
</iframe>`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -782,9 +895,9 @@ const copyWorkflowLink = async (processId) => {
|
||||
<!-- Copy Run Link Button (for published processes) -->
|
||||
<button
|
||||
v-if="process.status === 'published'"
|
||||
@click="copyWorkflowLink(process.id)"
|
||||
@click="openCopyLinkModal(process)"
|
||||
class="p-2 text-green-600 hover:text-green-800 hover:bg-green-50 rounded-lg transition-colors"
|
||||
title="Copy Run Link"
|
||||
title="Copy or Open Run Link"
|
||||
:disabled="loading"
|
||||
>
|
||||
<Icon name="material-symbols:link" class="text-lg" />
|
||||
@ -1051,6 +1164,163 @@ const copyWorkflowLink = async (processId) => {
|
||||
</div>
|
||||
</template>
|
||||
</RsModal>
|
||||
|
||||
<!-- Copy Link Modal -->
|
||||
<RsModal v-model="showCopyLinkModal" title="Share Workflow Link" size="lg" position="center">
|
||||
<div class="p-6">
|
||||
<!-- Process Info -->
|
||||
<div class="mb-6 p-4 bg-gray-50 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<Icon name="material-symbols:link" class="text-blue-600 w-6 h-6" />
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900">{{ selectedProcess?.name }}</h3>
|
||||
<p class="text-sm text-gray-600">{{ selectedProcess?.description || 'No description available' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Link Type Selection -->
|
||||
<div class="mb-6">
|
||||
<h4 class="font-medium text-gray-900 mb-3">Link Type</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div
|
||||
@click="copyLinkType = 'direct'"
|
||||
:class="[
|
||||
'p-4 border-2 rounded-lg cursor-pointer transition-colors',
|
||||
copyLinkType === 'direct'
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
: 'border-gray-200 hover:border-gray-300'
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div :class="[
|
||||
'w-4 h-4 rounded-full border-2',
|
||||
copyLinkType === 'direct'
|
||||
? 'border-blue-500 bg-blue-500'
|
||||
: 'border-gray-300'
|
||||
]">
|
||||
<Icon v-if="copyLinkType === 'direct'" name="material-symbols:check" class="w-3 h-3 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="font-medium text-gray-900">Direct Link</h5>
|
||||
<p class="text-sm text-gray-600">Direct URL to the workflow</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@click="copyLinkType = 'iframe'"
|
||||
:class="[
|
||||
'p-4 border-2 rounded-lg cursor-pointer transition-colors',
|
||||
copyLinkType === 'iframe'
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
: 'border-gray-200 hover:border-gray-300'
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div :class="[
|
||||
'w-4 h-4 rounded-full border-2',
|
||||
copyLinkType === 'iframe'
|
||||
? 'border-blue-500 bg-blue-500'
|
||||
: 'border-gray-300'
|
||||
]">
|
||||
<Icon v-if="copyLinkType === 'iframe'" name="material-symbols:check" class="w-3 h-3 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="font-medium text-gray-900">Iframe Integration</h5>
|
||||
<p class="text-sm text-gray-600">Embed in external applications</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Iframe Options -->
|
||||
<div v-if="copyLinkType === 'iframe'" class="mb-6">
|
||||
<h4 class="font-medium text-gray-900 mb-3">Iframe Options</h4>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700">Hide Debug Information</label>
|
||||
<p class="text-xs text-gray-500">Remove debug UI elements</p>
|
||||
</div>
|
||||
<FormKit
|
||||
v-model="iframeOptions.debug"
|
||||
type="checkbox"
|
||||
:classes="{
|
||||
wrapper: 'flex items-center',
|
||||
input: 'w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700">Hide Completion Message</label>
|
||||
<p class="text-xs text-gray-500">Auto-advance after completion</p>
|
||||
</div>
|
||||
<FormKit
|
||||
v-model="iframeOptions.hideComplete"
|
||||
type="checkbox"
|
||||
:classes="{
|
||||
wrapper: 'flex items-center',
|
||||
input: 'w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700">Theme</label>
|
||||
<FormKit
|
||||
v-model="iframeOptions.theme"
|
||||
type="select"
|
||||
:options="[
|
||||
{ label: 'Light', value: 'light' },
|
||||
{ label: 'Dark', value: 'dark' }
|
||||
]"
|
||||
:classes="{
|
||||
outer: 'mt-1',
|
||||
input: 'w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Generated Link Preview -->
|
||||
<div class="mb-6">
|
||||
<h4 class="font-medium text-gray-900 mb-3">Generated Link</h4>
|
||||
<div class="bg-gray-50 p-3 rounded-lg">
|
||||
<code class="text-sm text-gray-800 break-all">
|
||||
{{ generatedLink }}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Iframe Code Preview -->
|
||||
<div v-if="copyLinkType === 'iframe'" class="mb-6">
|
||||
<h4 class="font-medium text-gray-900 mb-3">Iframe Code</h4>
|
||||
<div class="bg-gray-50 p-3 rounded-lg">
|
||||
<pre class="text-sm text-gray-800 overflow-x-auto"><code>{{ generateIframeCode() }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex gap-2">
|
||||
<RsButton @click="handleOpenLink" variant="secondary" :disabled="!selectedProcess">
|
||||
<Icon name="material-symbols:open-in-new" class="mr-2" />
|
||||
Open Link
|
||||
</RsButton>
|
||||
<RsButton @click="handleCopyLink" variant="primary" :disabled="!selectedProcess">
|
||||
<Icon name="material-symbols:content-copy" class="mr-2" />
|
||||
Copy Link
|
||||
</RsButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</RsModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user