style: Auto format vue file with prettier
- Ran pre-commit run --all-files
This commit is contained in:
parent
392fee66a2
commit
b4bc871aab
26 changed files with 574 additions and 539 deletions
|
|
@ -6,7 +6,7 @@
|
|||
class="search-input form-control"
|
||||
type="text"
|
||||
:placeholder="__('Search properties...')"
|
||||
@input="event => $emit('update:modelValue', event.target.value)"
|
||||
@input="(event) => $emit('update:modelValue', event.target.value)"
|
||||
/>
|
||||
<span class="search-icon">
|
||||
<div v-html="frappe.utils.icon('search', 'sm')"></div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const store = useStore();
|
|||
const { Backspace } = useMagicKeys();
|
||||
whenever(Backspace, (value) => {
|
||||
if (value && selected.value && store.not_using_input) {
|
||||
remove_tab(store.current_tab, '', true);
|
||||
remove_tab(store.current_tab, "", true);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -45,12 +45,10 @@ function add_new_section() {
|
|||
|
||||
function is_tab_empty(tab) {
|
||||
// check if sections have columns and it contains fields
|
||||
return !tab.sections.some((section) =>
|
||||
section.columns.some((column) => column.fields.length)
|
||||
);
|
||||
return !tab.sections.some((section) => section.columns.some((column) => column.fields.length));
|
||||
}
|
||||
|
||||
function remove_tab(tab, event, force=false) {
|
||||
function remove_tab(tab, event, force = false) {
|
||||
// is remove_tab_btn is not visible then return
|
||||
if (!event?.currentTarget?.offsetParent && !force) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@ const props = defineProps(["df", "value"]);
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="control frappe-control editable"
|
||||
:data-fieldtype="df.fieldtype"
|
||||
>
|
||||
<div class="control frappe-control editable" :data-fieldtype="df.fieldtype">
|
||||
<!-- label -->
|
||||
<div class="field-controls">
|
||||
<h4 v-if="df.fieldtype == 'Heading'">
|
||||
|
|
@ -28,5 +25,4 @@ const props = defineProps(["df", "value"]);
|
|||
h4 {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ let slots = useSlots();
|
|||
type="checkbox"
|
||||
:checked="value"
|
||||
:disabled="read_only"
|
||||
@change="event => $emit('update:modelValue', event.target.checked)"
|
||||
@change="(event) => $emit('update:modelValue', event.target.checked)"
|
||||
/>
|
||||
<span class="label-area" :class="{ reqd: df.reqd }">{{ df.label }}</span>
|
||||
</label>
|
||||
|
|
@ -31,7 +31,8 @@ let slots = useSlots();
|
|||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
label, input {
|
||||
label,
|
||||
input {
|
||||
margin-bottom: 0 !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,7 @@ if (props.df.fieldtype === "Icon") {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="control frappe-control"
|
||||
:class="{ editable: slots.label }"
|
||||
>
|
||||
<div class="control frappe-control" :class="{ editable: slots.label }">
|
||||
<!-- label -->
|
||||
<div v-if="slots.label" class="field-controls">
|
||||
<slot name="label" />
|
||||
|
|
@ -49,7 +46,7 @@ if (props.df.fieldtype === "Icon") {
|
|||
type="text"
|
||||
:value="value"
|
||||
:disabled="read_only || df.read_only"
|
||||
@input="event => $emit('update:modelValue', event.target.value)"
|
||||
@input="(event) => $emit('update:modelValue', event.target.value)"
|
||||
/>
|
||||
<input
|
||||
v-if="slots.label && df.fieldtype === 'Barcode'"
|
||||
|
|
|
|||
|
|
@ -13,20 +13,22 @@ function get_options() {
|
|||
|
||||
if (typeof options == "string") {
|
||||
options = options.split("\n") || "";
|
||||
options = options.map(opt => {
|
||||
options = options.map((opt) => {
|
||||
return { label: __(opt), value: opt };
|
||||
});
|
||||
}
|
||||
|
||||
if (options?.length && typeof options[0] == "string") {
|
||||
options = options.map(opt => {
|
||||
options = options.map((opt) => {
|
||||
return { label: __(opt), value: opt };
|
||||
});
|
||||
}
|
||||
|
||||
if (props.df.fieldname == "fieldtype") {
|
||||
if (!in_list(frappe.model.layout_fields, props.modelValue)) {
|
||||
options = options && options.filter(opt => !in_list(frappe.model.layout_fields, opt.value));
|
||||
options =
|
||||
options &&
|
||||
options.filter((opt) => !in_list(frappe.model.layout_fields, opt.value));
|
||||
} else {
|
||||
options = [{ label: __(props.modelValue), value: props.modelValue }];
|
||||
}
|
||||
|
|
@ -56,17 +58,17 @@ let select_control = computed(() => {
|
|||
content.value = select_control.value.get_value();
|
||||
}
|
||||
update_control.value = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
value: content.value,
|
||||
render_input: true,
|
||||
only_input: Boolean(slots.label) || props.no_label
|
||||
only_input: Boolean(slots.label) || props.no_label,
|
||||
});
|
||||
});
|
||||
|
||||
let content = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => emit("update:modelValue", value)
|
||||
set: (value) => emit("update:modelValue", value),
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
|
|
@ -75,15 +77,18 @@ onMounted(() => {
|
|||
|
||||
watch(
|
||||
() => content.value,
|
||||
value => {
|
||||
(value) => {
|
||||
update_control.value = false;
|
||||
select_control.value?.set_value(value);
|
||||
}
|
||||
);
|
||||
|
||||
watch(() => props.df.options, () => {
|
||||
select_control.value;
|
||||
})
|
||||
watch(
|
||||
() => props.df.options,
|
||||
() => {
|
||||
select_control.value;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ let height = computed(() => {
|
|||
type="text"
|
||||
:value="value"
|
||||
:disabled="read_only || df.read_only"
|
||||
@input="event => $emit('update:modelValue', event.target.value)"
|
||||
@input="(event) => $emit('update:modelValue', event.target.value)"
|
||||
/>
|
||||
|
||||
<!-- description -->
|
||||
|
|
|
|||
|
|
@ -28,9 +28,7 @@ function open_in_editor(location) {
|
|||
}
|
||||
function error_component(error, i) {
|
||||
let location = data.value.error.errors[i].location;
|
||||
let location_string = `${location.file}:${location.line}:${
|
||||
location.column
|
||||
}`;
|
||||
let location_string = `${location.file}:${location.line}:${location.column}`;
|
||||
let template = error.replace(
|
||||
" > " + location_string,
|
||||
` > <a class="file-link" @click="open">${location_string}</a>`
|
||||
|
|
@ -41,11 +39,11 @@ function error_component(error, i) {
|
|||
methods: {
|
||||
open() {
|
||||
frappe.realtime.emit("open_in_editor", location);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
defineExpose({show, hide});
|
||||
defineExpose({ show, hide });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
@ -58,8 +56,7 @@ defineExpose({show, hide});
|
|||
z-index: 9999;
|
||||
margin: 0;
|
||||
background: rgba(0, 0, 0, 0.66);
|
||||
--monospace: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier,
|
||||
monospace;
|
||||
--monospace: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
--dim: var(--gray-400);
|
||||
}
|
||||
.window {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="is_shown"
|
||||
class="flex justify-between build-success-message align-center"
|
||||
>
|
||||
<div v-if="is_shown" class="flex justify-between build-success-message align-center">
|
||||
Compiled successfully
|
||||
<a
|
||||
v-if="!live_reload"
|
||||
class="ml-4 text-white underline" href="/" @click.prevent="reload"
|
||||
>
|
||||
<a v-if="!live_reload" class="ml-4 text-white underline" href="/" @click.prevent="reload">
|
||||
Refresh
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -43,7 +37,7 @@ function reload() {
|
|||
window.location.reload();
|
||||
}
|
||||
|
||||
defineExpose({show, hide});
|
||||
defineExpose({ show, hide });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
<template>
|
||||
<div class="file-browser">
|
||||
<div>
|
||||
<a
|
||||
href=""
|
||||
class="text-muted text-medium"
|
||||
@click.prevent="emit('hide-browser')"
|
||||
>
|
||||
<a href="" class="text-muted text-medium" @click.prevent="emit('hide-browser')">
|
||||
{{ __("← Back to upload files") }}
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -23,8 +19,8 @@
|
|||
class="tree with-skeleton"
|
||||
:node="node"
|
||||
:selected_node="selected_node"
|
||||
@node-click="n => toggle_node(n)"
|
||||
@load-more="n => load_more(n)"
|
||||
@node-click="(n) => toggle_node(n)"
|
||||
@load-more="(n) => load_more(n)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -48,7 +44,7 @@ let node = ref({
|
|||
fetching: false,
|
||||
fetched: false,
|
||||
open: false,
|
||||
filtered: true
|
||||
filtered: true,
|
||||
});
|
||||
let selected_node = ref({});
|
||||
let search_text = ref("");
|
||||
|
|
@ -61,16 +57,14 @@ function toggle_node(node) {
|
|||
node.fetching = true;
|
||||
node.children_start = 0;
|
||||
node.children_loading = false;
|
||||
get_files_in_folder(node.value, 0).then(
|
||||
({ files, has_more }) => {
|
||||
node.open = true;
|
||||
node.children = files;
|
||||
node.fetched = true;
|
||||
node.fetching = false;
|
||||
node.children_start += page_length.value;
|
||||
node.has_more_children = has_more;
|
||||
}
|
||||
);
|
||||
get_files_in_folder(node.value, 0).then(({ files, has_more }) => {
|
||||
node.open = true;
|
||||
node.children = files;
|
||||
node.fetched = true;
|
||||
node.fetching = false;
|
||||
node.children_start += page_length.value;
|
||||
node.has_more_children = has_more;
|
||||
});
|
||||
} else {
|
||||
node.open = !node.open;
|
||||
select_node(node);
|
||||
|
|
@ -80,14 +74,12 @@ function load_more(node) {
|
|||
if (node.has_more_children) {
|
||||
let start = node.children_start;
|
||||
node.children_loading = true;
|
||||
get_files_in_folder(node.value, start).then(
|
||||
({ files, has_more }) => {
|
||||
node.children = node.children.concat(files);
|
||||
node.children_start += page_length.value;
|
||||
node.has_more_children = has_more;
|
||||
node.children_loading = false;
|
||||
}
|
||||
);
|
||||
get_files_in_folder(node.value, start).then(({ files, has_more }) => {
|
||||
node.children = node.children.concat(files);
|
||||
node.children_start += page_length.value;
|
||||
node.has_more_children = has_more;
|
||||
node.children_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
function select_node(node) {
|
||||
|
|
@ -100,9 +92,9 @@ function get_files_in_folder(folder, start) {
|
|||
.call("frappe.core.api.file.get_files_in_folder", {
|
||||
folder,
|
||||
start,
|
||||
page_length: page_length.value
|
||||
page_length: page_length.value,
|
||||
})
|
||||
.then(r => {
|
||||
.then((r) => {
|
||||
let { files = [], has_more = false } = r.message || {};
|
||||
files.sort((a, b) => {
|
||||
if (a.is_folder && b.is_folder) {
|
||||
|
|
@ -116,7 +108,7 @@ function get_files_in_folder(folder, start) {
|
|||
}
|
||||
return 0;
|
||||
});
|
||||
files = files.map(file => make_file_node(file));
|
||||
files = files.map((file) => make_file_node(file));
|
||||
return { files, has_more };
|
||||
});
|
||||
}
|
||||
|
|
@ -127,15 +119,12 @@ function search_by_name() {
|
|||
}
|
||||
if (search_text.value.length < 3) return;
|
||||
frappe
|
||||
.call(
|
||||
"frappe.core.api.file.get_files_by_search_text",
|
||||
{
|
||||
text: search_text.value
|
||||
}
|
||||
)
|
||||
.then(r => {
|
||||
.call("frappe.core.api.file.get_files_by_search_text", {
|
||||
text: search_text.value,
|
||||
})
|
||||
.then((r) => {
|
||||
let files = r.message || [];
|
||||
files = files.map(file => make_file_node(file));
|
||||
files = files.map((file) => make_file_node(file));
|
||||
if (!folder_node.value) {
|
||||
folder_node.value = node.value;
|
||||
}
|
||||
|
|
@ -145,7 +134,7 @@ function search_by_name() {
|
|||
children: files,
|
||||
by_search: true,
|
||||
open: true,
|
||||
filtered: true
|
||||
filtered: true,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -164,7 +153,7 @@ function make_file_node(file) {
|
|||
children_start: 0,
|
||||
open: false,
|
||||
fetching: false,
|
||||
filtered: true
|
||||
filtered: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
<template>
|
||||
<div class="file-preview">
|
||||
<div class="file-icon">
|
||||
<img
|
||||
v-if="is_image"
|
||||
:src="src"
|
||||
:alt="file.name"
|
||||
>
|
||||
<div class="fallback" v-else v-html="frappe.utils.icon('file', 'md')">
|
||||
</div>
|
||||
<img v-if="is_image" :src="src" :alt="file.name" />
|
||||
<div class="fallback" v-else v-html="frappe.utils.icon('file', 'md')"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
|
|
@ -24,8 +19,20 @@
|
|||
</div>
|
||||
|
||||
<div class="flex config-area">
|
||||
<label v-if="is_optimizable" class="frappe-checkbox"><input type="checkbox" :checked="optimize" @change="emit('toggle_optimize')">{{ __('Optimize') }}</label>
|
||||
<label class="frappe-checkbox"><input type="checkbox" :checked="file.private" @change="emit('toggle_private')">{{ __('Private') }}</label>
|
||||
<label v-if="is_optimizable" class="frappe-checkbox"
|
||||
><input
|
||||
type="checkbox"
|
||||
:checked="optimize"
|
||||
@change="emit('toggle_optimize')"
|
||||
/>{{ __("Optimize") }}</label
|
||||
>
|
||||
<label class="frappe-checkbox"
|
||||
><input
|
||||
type="checkbox"
|
||||
:checked="file.private"
|
||||
@change="emit('toggle_private')"
|
||||
/>{{ __("Private") }}</label
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="file.error_message" class="file-error text-danger">
|
||||
|
|
@ -45,8 +52,18 @@
|
|||
<div v-if="uploaded" v-html="frappe.utils.icon('solid-success', 'lg')"></div>
|
||||
<div v-if="file.failed" v-html="frappe.utils.icon('solid-error', 'lg')"></div>
|
||||
<div class="file-action-buttons">
|
||||
<button v-if="is_cropable" class="btn btn-crop muted" @click="emit('toggle_image_cropper')" v-html="frappe.utils.icon('crop', 'md')"></button>
|
||||
<button v-if="!uploaded && !file.uploading && !file.failed" class="btn muted" @click="emit('remove')" v-html="frappe.utils.icon('delete', 'md')"></button>
|
||||
<button
|
||||
v-if="is_cropable"
|
||||
class="btn btn-crop muted"
|
||||
@click="emit('toggle_image_cropper')"
|
||||
v-html="frappe.utils.icon('crop', 'md')"
|
||||
></button>
|
||||
<button
|
||||
v-if="!uploaded && !file.uploading && !file.failed"
|
||||
class="btn muted"
|
||||
@click="emit('remove')"
|
||||
v-html="frappe.utils.icon('delete', 'md')"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -79,15 +96,20 @@ let uploaded = computed(() => {
|
|||
return props.file.request_succeeded;
|
||||
});
|
||||
let is_image = computed(() => {
|
||||
return props.file.file_obj.type.startsWith('image');
|
||||
return props.file.file_obj.type.startsWith("image");
|
||||
});
|
||||
let is_optimizable = computed(() => {
|
||||
let is_svg = props.file.file_obj.type == 'image/svg+xml';
|
||||
let is_svg = props.file.file_obj.type == "image/svg+xml";
|
||||
return is_image.value && !is_svg && !uploaded.value && !props.file.failed;
|
||||
});
|
||||
let is_cropable = computed(() => {
|
||||
let croppable_types = ['image/jpeg', 'image/png'];
|
||||
return !uploaded.value && !props.file.uploading && !props.file.failed && croppable_types.includes(props.file.file_obj.type);
|
||||
let croppable_types = ["image/jpeg", "image/png"];
|
||||
return (
|
||||
!uploaded.value &&
|
||||
!props.file.uploading &&
|
||||
!props.file.failed &&
|
||||
croppable_types.includes(props.file.file_obj.type)
|
||||
);
|
||||
});
|
||||
let progress = computed(() => {
|
||||
let value = Math.round((props.file.progress * 100) / props.file.total);
|
||||
|
|
@ -102,7 +124,7 @@ onMounted(() => {
|
|||
if (is_image.value) {
|
||||
if (window.FileReader) {
|
||||
let fr = new FileReader();
|
||||
fr.onload = () => src.value = fr.result;
|
||||
fr.onload = () => (src.value = fr.result);
|
||||
fr.readAsDataURL(props.file.file_obj);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div class="file-uploader"
|
||||
<div
|
||||
class="file-uploader"
|
||||
@dragover.prevent="dragover"
|
||||
@dragleave.prevent="dragleave"
|
||||
@drop.prevent="dropfiles"
|
||||
|
|
@ -10,19 +11,50 @@
|
|||
>
|
||||
<div v-if="!is_dragging">
|
||||
<div class="text-center">
|
||||
{{ __('Drag and drop files here or upload from') }}
|
||||
{{ __("Drag and drop files here or upload from") }}
|
||||
</div>
|
||||
<div class="mt-2 text-center">
|
||||
<button class="btn btn-file-upload" @click="browse_files">
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)"/>
|
||||
<path d="M13.5 22V19" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16.5 22V19" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.5 22H19.5" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.5 16H22.5" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21 8H9C8.17157 8 7.5 8.67157 7.5 9.5V17.5C7.5 18.3284 8.17157 19 9 19H21C21.8284 19 22.5 18.3284 22.5 17.5V9.5C22.5 8.67157 21.8284 8 21 8Z" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)" />
|
||||
<path
|
||||
d="M13.5 22V19"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M16.5 22V19"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10.5 22H19.5"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M7.5 16H22.5"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M21 8H9C8.17157 8 7.5 8.67157 7.5 9.5V17.5C7.5 18.3284 8.17157 19 9 19H21C21.8284 19 22.5 18.3284 22.5 17.5V9.5C22.5 8.67157 21.8284 8 21 8Z"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div class="mt-1">{{ __('My Device') }}</div>
|
||||
<div class="mt-1">{{ __("My Device") }}</div>
|
||||
</button>
|
||||
<input
|
||||
type="file"
|
||||
|
|
@ -31,37 +63,105 @@
|
|||
@change="on_file_input"
|
||||
:multiple="allow_multiple"
|
||||
:accept="(restrictions.allowed_file_types || []).join(', ')"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-file-upload"
|
||||
v-if="!disable_file_browser"
|
||||
@click="show_file_browser = true"
|
||||
>
|
||||
<button class="btn btn-file-upload" v-if="!disable_file_browser" @click="show_file_browser = true">
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)"/>
|
||||
<path d="M13.0245 11.5H8C7.72386 11.5 7.5 11.7239 7.5 12V20C7.5 21.1046 8.39543 22 9.5 22H20.5C21.6046 22 22.5 21.1046 22.5 20V14.5C22.5 14.2239 22.2761 14 22 14H15.2169C15.0492 14 14.8926 13.9159 14.8 13.776L13.4414 11.724C13.3488 11.5841 13.1922 11.5 13.0245 11.5Z" stroke="var(--text-color)" stroke-miterlimit="10" stroke-linecap="square"/>
|
||||
<path d="M8.87939 9.5V8.5C8.87939 8.22386 9.10325 8 9.37939 8H20.6208C20.8969 8 21.1208 8.22386 21.1208 8.5V12" stroke="var(--text-color)" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)" />
|
||||
<path
|
||||
d="M13.0245 11.5H8C7.72386 11.5 7.5 11.7239 7.5 12V20C7.5 21.1046 8.39543 22 9.5 22H20.5C21.6046 22 22.5 21.1046 22.5 20V14.5C22.5 14.2239 22.2761 14 22 14H15.2169C15.0492 14 14.8926 13.9159 14.8 13.776L13.4414 11.724C13.3488 11.5841 13.1922 11.5 13.0245 11.5Z"
|
||||
stroke="var(--text-color)"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
<path
|
||||
d="M8.87939 9.5V8.5C8.87939 8.22386 9.10325 8 9.37939 8H20.6208C20.8969 8 21.1208 8.22386 21.1208 8.5V12"
|
||||
stroke="var(--text-color)"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div class="mt-1">{{ __('Library') }}</div>
|
||||
<div class="mt-1">{{ __("Library") }}</div>
|
||||
</button>
|
||||
<button class="btn btn-file-upload" v-if="allow_web_link" @click="show_web_link = true">
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)"/>
|
||||
<path d="M12.0469 17.9543L17.9558 12.0454" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.8184 11.4547L15.7943 9.47873C16.4212 8.85205 17.2714 8.5 18.1578 8.5C19.0443 8.5 19.8945 8.85205 20.5214 9.47873V9.47873C21.1481 10.1057 21.5001 10.9558 21.5001 11.8423C21.5001 12.7287 21.1481 13.5789 20.5214 14.2058L18.5455 16.1818" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.4547 13.8184L9.47873 15.7943C8.85205 16.4212 8.5 17.2714 8.5 18.1578C8.5 19.0443 8.85205 19.8945 9.47873 20.5214V20.5214C10.1057 21.1481 10.9558 21.5001 11.8423 21.5001C12.7287 21.5001 13.5789 21.1481 14.2058 20.5214L16.1818 18.5455" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<button
|
||||
class="btn btn-file-upload"
|
||||
v-if="allow_web_link"
|
||||
@click="show_web_link = true"
|
||||
>
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)" />
|
||||
<path
|
||||
d="M12.0469 17.9543L17.9558 12.0454"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M13.8184 11.4547L15.7943 9.47873C16.4212 8.85205 17.2714 8.5 18.1578 8.5C19.0443 8.5 19.8945 8.85205 20.5214 9.47873V9.47873C21.1481 10.1057 21.5001 10.9558 21.5001 11.8423C21.5001 12.7287 21.1481 13.5789 20.5214 14.2058L18.5455 16.1818"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11.4547 13.8184L9.47873 15.7943C8.85205 16.4212 8.5 17.2714 8.5 18.1578C8.5 19.0443 8.85205 19.8945 9.47873 20.5214V20.5214C10.1057 21.1481 10.9558 21.5001 11.8423 21.5001C12.7287 21.5001 13.5789 21.1481 14.2058 20.5214L16.1818 18.5455"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div class="mt-1">{{ __('Link') }}</div>
|
||||
<div class="mt-1">{{ __("Link") }}</div>
|
||||
</button>
|
||||
<button v-if="allow_take_photo" class="btn btn-file-upload" @click="capture_image">
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)"/>
|
||||
<path d="M11.5 10.5H9.5C8.67157 10.5 8 11.1716 8 12V20C8 20.8284 8.67157 21.5 9.5 21.5H20.5C21.3284 21.5 22 20.8284 22 20V12C22 11.1716 21.3284 10.5 20.5 10.5H18.5L17.3 8.9C17.1111 8.64819 16.8148 8.5 16.5 8.5H13.5C13.1852 8.5 12.8889 8.64819 12.7 8.9L11.5 10.5Z" stroke="var(--text-color)" stroke-linejoin="round"/>
|
||||
<circle cx="15" cy="16" r="2.5" stroke="var(--text-color)"/>
|
||||
<button
|
||||
v-if="allow_take_photo"
|
||||
class="btn btn-file-upload"
|
||||
@click="capture_image"
|
||||
>
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)" />
|
||||
<path
|
||||
d="M11.5 10.5H9.5C8.67157 10.5 8 11.1716 8 12V20C8 20.8284 8.67157 21.5 9.5 21.5H20.5C21.3284 21.5 22 20.8284 22 20V12C22 11.1716 21.3284 10.5 20.5 10.5H18.5L17.3 8.9C17.1111 8.64819 16.8148 8.5 16.5 8.5H13.5C13.1852 8.5 12.8889 8.64819 12.7 8.9L11.5 10.5Z"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle cx="15" cy="16" r="2.5" stroke="var(--text-color)" />
|
||||
</svg>
|
||||
<div class="mt-1">{{ __('Camera') }}</div>
|
||||
<div class="mt-1">{{ __("Camera") }}</div>
|
||||
</button>
|
||||
<button v-if="google_drive_settings.enabled" class="btn btn-file-upload" @click="show_google_drive_picker">
|
||||
<button
|
||||
v-if="google_drive_settings.enabled"
|
||||
class="btn btn-file-upload"
|
||||
@click="show_google_drive_picker"
|
||||
>
|
||||
<svg width="30" height="30">
|
||||
<image href="/assets/frappe/icons/social/google_drive.svg" width="30" height="30"/>
|
||||
<image
|
||||
href="/assets/frappe/icons/social/google_drive.svg"
|
||||
width="30"
|
||||
height="30"
|
||||
/>
|
||||
</svg>
|
||||
<div class="mt-1">{{ __('Google Drive') }}</div>
|
||||
<div class="mt-1">{{ __("Google Drive") }}</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-muted text-medium text-center">
|
||||
|
|
@ -69,10 +169,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ __('Drop files here') }}
|
||||
{{ __("Drop files here") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-preview-area" v-show="files.length && !show_file_browser && !show_web_link">
|
||||
<div
|
||||
class="file-preview-area"
|
||||
v-show="files.length && !show_file_browser && !show_web_link"
|
||||
>
|
||||
<div class="file-preview-container" v-if="!show_image_cropper">
|
||||
<FilePreview
|
||||
v-for="(file, i) in files"
|
||||
|
|
@ -85,19 +188,16 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="flex align-center" v-if="show_upload_button && currently_uploading === -1">
|
||||
<button
|
||||
class="btn btn-primary btn-sm margin-right"
|
||||
@click="upload_files"
|
||||
>
|
||||
<button class="btn btn-primary btn-sm margin-right" @click="upload_files">
|
||||
<span v-if="files.length === 1">
|
||||
{{ __('Upload file') }}
|
||||
{{ __("Upload file") }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ __('Upload {0} files', [files.length]) }}
|
||||
{{ __("Upload {0} files", [files.length]) }}
|
||||
</span>
|
||||
</button>
|
||||
<div class="text-muted text-medium">
|
||||
{{ __('Click on the lock icon to toggle public/private') }}
|
||||
{{ __("Click on the lock icon to toggle public/private") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -106,60 +206,56 @@
|
|||
:file="files[crop_image_with_index]"
|
||||
:fixed_aspect_ratio="restrictions.crop_image_aspect_ratio"
|
||||
@toggle_image_cropper="toggle_image_cropper(-1)"
|
||||
@upload_after_crop="trigger_upload=true"
|
||||
@upload_after_crop="trigger_upload = true"
|
||||
/>
|
||||
<FileBrowser
|
||||
ref="file_browser"
|
||||
v-if="show_file_browser && !disable_file_browser"
|
||||
@hide-browser="show_file_browser = false"
|
||||
/>
|
||||
<WebLink
|
||||
ref="web_link"
|
||||
v-if="show_web_link"
|
||||
@hide-web-link="show_web_link = false"
|
||||
/>
|
||||
<WebLink ref="web_link" v-if="show_web_link" @hide-web-link="show_web_link = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import FilePreview from './FilePreview.vue';
|
||||
import FileBrowser from './FileBrowser.vue';
|
||||
import WebLink from './WebLink.vue';
|
||||
import GoogleDrivePicker from '../../integrations/google_drive_picker';
|
||||
import ImageCropper from './ImageCropper.vue';
|
||||
import { computed, ref, watch } from "vue";
|
||||
import FilePreview from "./FilePreview.vue";
|
||||
import FileBrowser from "./FileBrowser.vue";
|
||||
import WebLink from "./WebLink.vue";
|
||||
import GoogleDrivePicker from "../../integrations/google_drive_picker";
|
||||
import ImageCropper from "./ImageCropper.vue";
|
||||
|
||||
// props
|
||||
const props = defineProps({
|
||||
show_upload_button: {
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
disable_file_browser: {
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
allow_multiple: {
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
as_dataurl: {
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
doctype: {
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
docname: {
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
fieldname: {
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
folder: {
|
||||
default: 'Home'
|
||||
default: "Home",
|
||||
},
|
||||
method: {
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
on_success: {
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
make_attachments_public: {
|
||||
default: null,
|
||||
|
|
@ -169,15 +265,15 @@ const props = defineProps({
|
|||
max_file_size: null, // 2048 -> 2KB
|
||||
max_number_of_files: null,
|
||||
allowed_file_types: [], // ['image/*', 'video/*', '.jpg', '.gif', '.pdf'],
|
||||
crop_image_aspect_ratio: null // 1, 16 / 9, 4 / 3, NaN (free)
|
||||
})
|
||||
crop_image_aspect_ratio: null, // 1, 16 / 9, 4 / 3, NaN (free)
|
||||
}),
|
||||
},
|
||||
attach_doc_image: {
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
upload_notes: {
|
||||
default: null // "Images or video, upto 2MB"
|
||||
}
|
||||
default: null, // "Images or video, upto 2MB"
|
||||
},
|
||||
});
|
||||
|
||||
// variables
|
||||
|
|
@ -197,7 +293,7 @@ let hide_dialog_footer = ref(false);
|
|||
let allow_take_photo = ref(false);
|
||||
let allow_web_link = ref(true);
|
||||
let google_drive_settings = ref({
|
||||
enabled: false
|
||||
enabled: false,
|
||||
});
|
||||
let wrapper_ready = ref(false);
|
||||
|
||||
|
|
@ -211,14 +307,13 @@ if (frappe.user_id !== "Guest") {
|
|||
if (!resp.exc) {
|
||||
google_drive_settings.value = resp.message;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
if (props.restrictions.max_file_size == null) {
|
||||
frappe.call('frappe.core.api.file.get_max_file_size')
|
||||
.then(res => {
|
||||
props.restrictions.max_file_size = Number(res.message);
|
||||
});
|
||||
frappe.call("frappe.core.api.file.get_max_file_size").then((res) => {
|
||||
props.restrictions.max_file_size = Number(res.message);
|
||||
});
|
||||
}
|
||||
if (props.restrictions.max_number_of_files == null && props.doctype) {
|
||||
props.restrictions.max_number_of_files = frappe.get_meta(props.doctype)?.max_attachments;
|
||||
|
|
@ -242,7 +337,7 @@ function on_file_input(e) {
|
|||
add_files(file_input.value.files);
|
||||
}
|
||||
function remove_file(file) {
|
||||
files.value = files.value.filter(f => f !== file);
|
||||
files.value = files.value.filter((f) => f !== file);
|
||||
}
|
||||
function toggle_image_cropper(index) {
|
||||
crop_image_with_index.value = show_image_cropper.value ? -1 : index;
|
||||
|
|
@ -251,7 +346,7 @@ function toggle_image_cropper(index) {
|
|||
}
|
||||
function toggle_all_private() {
|
||||
let flag;
|
||||
let private_values = files.value.filter(file => file.private);
|
||||
let private_values = files.value.filter((file) => file.private);
|
||||
if (private_values.length < files.value.length) {
|
||||
// there are some private and some public
|
||||
// set all to private
|
||||
|
|
@ -260,7 +355,7 @@ function toggle_all_private() {
|
|||
// all are private, set all to public
|
||||
flag = false;
|
||||
}
|
||||
files.value = files.value.map(file => {
|
||||
files.value = files.value.map((file) => {
|
||||
file.private = flag;
|
||||
return file;
|
||||
});
|
||||
|
|
@ -268,12 +363,19 @@ function toggle_all_private() {
|
|||
function show_max_files_number_warning(file) {
|
||||
console.warn(
|
||||
`File skipped because it exceeds the allowed specified limit of ${max_number_of_files} uploads`,
|
||||
file,
|
||||
file
|
||||
);
|
||||
if (props.doctype) {
|
||||
MSG = __('File "{0}" was skipped because only {1} uploads are allowed for DocType "{2}"', [file.name, max_number_of_files, props.doctype])
|
||||
MSG = __('File "{0}" was skipped because only {1} uploads are allowed for DocType "{2}"', [
|
||||
file.name,
|
||||
max_number_of_files,
|
||||
props.doctype,
|
||||
]);
|
||||
} else {
|
||||
MSG = __('File "{0}" was skipped because only {1} uploads are allowed', [file.name, max_number_of_files])
|
||||
MSG = __('File "{0}" was skipped because only {1} uploads are allowed', [
|
||||
file.name,
|
||||
max_number_of_files,
|
||||
]);
|
||||
}
|
||||
frappe.show_alert({
|
||||
message: MSG,
|
||||
|
|
@ -283,14 +385,14 @@ function show_max_files_number_warning(file) {
|
|||
function add_files(file_array) {
|
||||
let _files = Array.from(file_array)
|
||||
.filter(check_restrictions)
|
||||
.map(file => {
|
||||
let is_image = file.type.startsWith('image');
|
||||
.map((file) => {
|
||||
let is_image = file.type.startsWith("image");
|
||||
let size_kb = file.size / 1024;
|
||||
return {
|
||||
file_obj: file,
|
||||
cropper_file: file,
|
||||
crop_box_data: null,
|
||||
optimize: size_kb > 200 && is_image && !file.type.includes('svg'),
|
||||
optimize: size_kb > 200 && is_image && !file.type.includes("svg"),
|
||||
name: file.name,
|
||||
doc: null,
|
||||
progress: 0,
|
||||
|
|
@ -306,7 +408,7 @@ function add_files(file_array) {
|
|||
// pop extra files as per FileUploader.restrictions.max_number_of_files
|
||||
max_number_of_files = props.restrictions.max_number_of_files;
|
||||
if (max_number_of_files && _files.length > max_number_of_files) {
|
||||
_files.slice(max_number_of_files).forEach(file => {
|
||||
_files.slice(max_number_of_files).forEach((file) => {
|
||||
show_max_files_number_warning(file, props.doctype);
|
||||
});
|
||||
|
||||
|
|
@ -315,8 +417,12 @@ function add_files(file_array) {
|
|||
|
||||
files.value = files.value.concat(_files);
|
||||
// if only one file is allowed and crop_image_aspect_ratio is set, open cropper immediately
|
||||
if (files.value.length === 1 && !props.allow_multiple && props.restrictions.crop_image_aspect_ratio != null) {
|
||||
if (!files.value[0].file_obj.type.includes('svg')) {
|
||||
if (
|
||||
files.value.length === 1 &&
|
||||
!props.allow_multiple &&
|
||||
props.restrictions.crop_image_aspect_ratio != null
|
||||
) {
|
||||
if (!files.value[0].file_obj.type.includes("svg")) {
|
||||
toggle_image_cropper(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -330,13 +436,13 @@ function check_restrictions(file) {
|
|||
if (allowed_file_types && allowed_file_types.length) {
|
||||
is_correct_type = allowed_file_types.some((type) => {
|
||||
// is this is a mime-type
|
||||
if (type.includes('/')) {
|
||||
if (type.includes("/")) {
|
||||
if (!file.type) return false;
|
||||
return file.type.match(type);
|
||||
}
|
||||
|
||||
// otherwise this is likely an extension
|
||||
if (type[0] === '.') {
|
||||
if (type[0] === ".") {
|
||||
return file.name.toLowerCase().endsWith(type.toLowerCase());
|
||||
}
|
||||
return false;
|
||||
|
|
@ -348,17 +454,20 @@ function check_restrictions(file) {
|
|||
}
|
||||
|
||||
if (!is_correct_type) {
|
||||
console.warn('File skipped because of invalid file type', file);
|
||||
console.warn("File skipped because of invalid file type", file);
|
||||
frappe.show_alert({
|
||||
message: __('File "{0}" was skipped because of invalid file type', [file.name]),
|
||||
indicator: 'orange'
|
||||
indicator: "orange",
|
||||
});
|
||||
}
|
||||
if (!valid_file_size) {
|
||||
console.warn('File skipped because of invalid file size', file.size, file);
|
||||
console.warn("File skipped because of invalid file size", file.size, file);
|
||||
frappe.show_alert({
|
||||
message: __('File "{0}" was skipped because size exceeds {1} MB', [file.name, max_file_size / (1024 * 1024)]),
|
||||
indicator: 'orange'
|
||||
message: __('File "{0}" was skipped because size exceeds {1} MB', [
|
||||
file.name,
|
||||
max_file_size / (1024 * 1024),
|
||||
]),
|
||||
indicator: "orange",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -374,17 +483,12 @@ function upload_files() {
|
|||
if (props.as_dataurl) {
|
||||
return return_as_dataurl();
|
||||
}
|
||||
return frappe.run_serially(
|
||||
files.value.map(
|
||||
(file, i) =>
|
||||
() => upload_file(file, i)
|
||||
)
|
||||
);
|
||||
return frappe.run_serially(files.value.map((file, i) => () => upload_file(file, i)));
|
||||
}
|
||||
function upload_via_file_browser() {
|
||||
let selected_file = file_browser.value.selected_node;
|
||||
if (!selected_file.value) {
|
||||
frappe.msgprint(__('Click on a file to select it.'));
|
||||
frappe.msgprint(__("Click on a file to select it."));
|
||||
close_dialog.value = true;
|
||||
return Promise.reject();
|
||||
}
|
||||
|
|
@ -396,23 +500,22 @@ function upload_via_file_browser() {
|
|||
function upload_via_web_link() {
|
||||
let file_url = web_link.value.url;
|
||||
if (!file_url) {
|
||||
frappe.msgprint(__('Invalid URL'));
|
||||
frappe.msgprint(__("Invalid URL"));
|
||||
close_dialog.value = true;
|
||||
return Promise.reject();
|
||||
}
|
||||
file_url = decodeURI(file_url)
|
||||
file_url = decodeURI(file_url);
|
||||
close_dialog.value = true;
|
||||
return upload_file({
|
||||
file_url
|
||||
file_url,
|
||||
});
|
||||
}
|
||||
function return_as_dataurl() {
|
||||
let promises = files.value.map(file =>
|
||||
frappe.dom.file_to_base64(file.file_obj)
|
||||
.then(dataurl => {
|
||||
file.dataurl = dataurl;
|
||||
props.on_success && props.on_success(file);
|
||||
})
|
||||
let promises = files.value.map((file) =>
|
||||
frappe.dom.file_to_base64(file.file_obj).then((dataurl) => {
|
||||
file.dataurl = dataurl;
|
||||
props.on_success && props.on_success(file);
|
||||
})
|
||||
);
|
||||
close_dialog.value = true;
|
||||
return Promise.all(promises);
|
||||
|
|
@ -422,23 +525,23 @@ function upload_file(file, i) {
|
|||
|
||||
return new Promise((resolve, reject) => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.upload.addEventListener('loadstart', (e) => {
|
||||
xhr.upload.addEventListener("loadstart", (e) => {
|
||||
file.uploading = true;
|
||||
})
|
||||
xhr.upload.addEventListener('progress', (e) => {
|
||||
});
|
||||
xhr.upload.addEventListener("progress", (e) => {
|
||||
if (e.lengthComputable) {
|
||||
file.progress = e.loaded;
|
||||
file.total = e.total;
|
||||
}
|
||||
})
|
||||
xhr.upload.addEventListener('load', (e) => {
|
||||
});
|
||||
xhr.upload.addEventListener("load", (e) => {
|
||||
file.uploading = false;
|
||||
resolve();
|
||||
})
|
||||
xhr.addEventListener('error', (e) => {
|
||||
});
|
||||
xhr.addEventListener("error", (e) => {
|
||||
file.failed = true;
|
||||
reject();
|
||||
})
|
||||
});
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState == XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
|
|
@ -447,10 +550,10 @@ function upload_file(file, i) {
|
|||
let file_doc = null;
|
||||
try {
|
||||
r = JSON.parse(xhr.responseText);
|
||||
if (r.message.doctype === 'File') {
|
||||
if (r.message.doctype === "File") {
|
||||
file_doc = r.message;
|
||||
}
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
r = xhr.responseText;
|
||||
}
|
||||
|
||||
|
|
@ -460,14 +563,16 @@ function upload_file(file, i) {
|
|||
props.on_success(file_doc, r);
|
||||
}
|
||||
|
||||
if (i == files.value.length - 1 && files.value.every(file => file.request_succeeded)) {
|
||||
if (
|
||||
i == files.value.length - 1 &&
|
||||
files.value.every((file) => file.request_succeeded)
|
||||
) {
|
||||
close_dialog.value = true;
|
||||
}
|
||||
|
||||
} else if (xhr.status === 403) {
|
||||
file.failed = true;
|
||||
let response = JSON.parse(xhr.responseText);
|
||||
file.error_message = `Not permitted. ${response._error_message || ''}.`;
|
||||
file.error_message = `Not permitted. ${response._error_message || ""}.`;
|
||||
|
||||
try {
|
||||
// Append server messages which are useful hint for perm issues
|
||||
|
|
@ -475,73 +580,73 @@ function upload_file(file, i) {
|
|||
|
||||
server_messages.forEach((m) => {
|
||||
m = JSON.parse(m);
|
||||
file.error_message += `\n ${m.message} `
|
||||
})
|
||||
file.error_message += `\n ${m.message} `;
|
||||
});
|
||||
} catch (e) {
|
||||
console.warning("Failed to parse server message", e)
|
||||
console.warning("Failed to parse server message", e);
|
||||
}
|
||||
|
||||
|
||||
} else if (xhr.status === 413) {
|
||||
file.failed = true;
|
||||
file.error_message = 'Size exceeds the maximum allowed file size.';
|
||||
|
||||
file.error_message = "Size exceeds the maximum allowed file size.";
|
||||
} else {
|
||||
file.failed = true;
|
||||
file.error_message = xhr.status === 0 ? 'XMLHttpRequest Error' : `${xhr.status} : ${xhr.statusText}`;
|
||||
file.error_message =
|
||||
xhr.status === 0
|
||||
? "XMLHttpRequest Error"
|
||||
: `${xhr.status} : ${xhr.statusText}`;
|
||||
|
||||
let error = null;
|
||||
try {
|
||||
error = JSON.parse(xhr.responseText);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
// pass
|
||||
}
|
||||
frappe.request.cleanup({}, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
xhr.open('POST', '/api/method/upload_file', true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('X-Frappe-CSRF-Token', frappe.csrf_token);
|
||||
};
|
||||
xhr.open("POST", "/api/method/upload_file", true);
|
||||
xhr.setRequestHeader("Accept", "application/json");
|
||||
xhr.setRequestHeader("X-Frappe-CSRF-Token", frappe.csrf_token);
|
||||
|
||||
let form_data = new FormData();
|
||||
if (file.file_obj) {
|
||||
form_data.append('file', file.file_obj, file.name);
|
||||
form_data.append("file", file.file_obj, file.name);
|
||||
}
|
||||
form_data.append('is_private', +file.private);
|
||||
form_data.append('folder', props.folder);
|
||||
form_data.append("is_private", +file.private);
|
||||
form_data.append("folder", props.folder);
|
||||
|
||||
if (file.file_url) {
|
||||
form_data.append('file_url', file.file_url);
|
||||
form_data.append("file_url", file.file_url);
|
||||
}
|
||||
|
||||
if (file.file_name) {
|
||||
form_data.append('file_name', file.file_name);
|
||||
form_data.append("file_name", file.file_name);
|
||||
}
|
||||
if (file.library_file_name) {
|
||||
form_data.append('library_file_name', file.library_file_name);
|
||||
form_data.append("library_file_name", file.library_file_name);
|
||||
}
|
||||
|
||||
if (props.doctype && props.docname) {
|
||||
form_data.append('doctype', props.doctype);
|
||||
form_data.append('docname', props.docname);
|
||||
form_data.append("doctype", props.doctype);
|
||||
form_data.append("docname", props.docname);
|
||||
}
|
||||
|
||||
if (props.fieldname) {
|
||||
form_data.append('fieldname', props.fieldname);
|
||||
form_data.append("fieldname", props.fieldname);
|
||||
}
|
||||
|
||||
if (props.method) {
|
||||
form_data.append('method', props.method);
|
||||
form_data.append("method", props.method);
|
||||
}
|
||||
|
||||
if (file.optimize) {
|
||||
form_data.append('optimize', true);
|
||||
form_data.append("optimize", true);
|
||||
}
|
||||
|
||||
if (props.attach_doc_image) {
|
||||
form_data.append('max_width', 200);
|
||||
form_data.append('max_height', 200);
|
||||
form_data.append("max_width", 200);
|
||||
form_data.append("max_height", 200);
|
||||
}
|
||||
|
||||
xhr.send(form_data);
|
||||
|
|
@ -550,23 +655,23 @@ function upload_file(file, i) {
|
|||
function capture_image() {
|
||||
const capture = new frappe.ui.Capture({
|
||||
animate: false,
|
||||
error: true
|
||||
error: true,
|
||||
});
|
||||
capture.show();
|
||||
capture.submit(data_urls => {
|
||||
data_urls.forEach(data_url => {
|
||||
let filename = `capture_${frappe.datetime.now_datetime().replaceAll(/[: -]/g, '_')}.png`;
|
||||
url_to_file(data_url, filename, 'image/png').then((file) =>
|
||||
add_files([file])
|
||||
);
|
||||
capture.submit((data_urls) => {
|
||||
data_urls.forEach((data_url) => {
|
||||
let filename = `capture_${frappe.datetime
|
||||
.now_datetime()
|
||||
.replaceAll(/[: -]/g, "_")}.png`;
|
||||
url_to_file(data_url, filename, "image/png").then((file) => add_files([file]));
|
||||
});
|
||||
});
|
||||
}
|
||||
function show_google_drive_picker() {
|
||||
close_dialog.value = true;
|
||||
let google_drive = new GoogleDrivePicker({
|
||||
pickerCallback: data => google_drive_callback(data),
|
||||
...google_drive_settings.value
|
||||
pickerCallback: (data) => google_drive_callback(data),
|
||||
...google_drive_settings.value,
|
||||
});
|
||||
google_drive.loadPicker();
|
||||
}
|
||||
|
|
@ -574,31 +679,36 @@ function google_drive_callback(data) {
|
|||
if (data.action == google.picker.Action.PICKED) {
|
||||
upload_file({
|
||||
file_url: data.docs[0].url,
|
||||
file_name: data.docs[0].name
|
||||
file_name: data.docs[0].name,
|
||||
});
|
||||
} else if (data.action == google.picker.Action.CANCEL) {
|
||||
cur_frm.attachments.new_attachment()
|
||||
cur_frm.attachments.new_attachment();
|
||||
}
|
||||
}
|
||||
function url_to_file(url, filename, mime_type) {
|
||||
return fetch(url)
|
||||
.then(res => res.arrayBuffer())
|
||||
.then(buffer => new File([buffer], filename, { type: mime_type }));
|
||||
.then((res) => res.arrayBuffer())
|
||||
.then((buffer) => new File([buffer], filename, { type: mime_type }));
|
||||
}
|
||||
|
||||
// computed
|
||||
let upload_complete = computed(() => {
|
||||
return files.value.length > 0
|
||||
&& files.value.every(
|
||||
file => file.total !== 0 && file.progress === file.total);
|
||||
return (
|
||||
files.value.length > 0 &&
|
||||
files.value.every((file) => file.total !== 0 && file.progress === file.total)
|
||||
);
|
||||
});
|
||||
|
||||
// watcher
|
||||
watch(files, (newvalue, oldvalue) => {
|
||||
if (!props.allow_multiple && newvalue.length > 1) {
|
||||
files.value = [newvalue[newvalue.length - 1]];
|
||||
}
|
||||
}, { deep: true });
|
||||
watch(
|
||||
files,
|
||||
(newvalue, oldvalue) => {
|
||||
if (!props.allow_multiple && newvalue.length > 1) {
|
||||
files.value = [newvalue[newvalue.length - 1]];
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
files,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
:class="{
|
||||
active: isNaN(aspect_ratio)
|
||||
? isNaN(button.value)
|
||||
: button.value === aspect_ratio
|
||||
: button.value === aspect_ratio,
|
||||
}"
|
||||
:key="button.label"
|
||||
@click="aspect_ratio = button.value"
|
||||
|
|
@ -63,9 +63,9 @@ function crop_image() {
|
|||
props.file.crop_box_data = cropper.value.getData();
|
||||
const canvas = cropper.value.getCroppedCanvas();
|
||||
const file_type = props.file.file_obj.type;
|
||||
canvas.toBlob(blob => {
|
||||
canvas.toBlob((blob) => {
|
||||
var cropped_file_obj = new File([blob], props.file.name, {
|
||||
type: blob.type
|
||||
type: blob.type,
|
||||
});
|
||||
props.file.file_obj = cropped_file_obj;
|
||||
emit("toggle_image_cropper");
|
||||
|
|
@ -87,7 +87,7 @@ onMounted(() => {
|
|||
scalable: false,
|
||||
viewMode: 1,
|
||||
data: crop_box,
|
||||
aspectRatio: aspect_ratio.value
|
||||
aspectRatio: aspect_ratio.value,
|
||||
});
|
||||
window.cropper = cropper.value;
|
||||
};
|
||||
|
|
@ -98,30 +98,33 @@ let aspect_ratio_buttons = computed(() => {
|
|||
return [
|
||||
{
|
||||
label: __("1:1"),
|
||||
value: 1
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: __("4:3"),
|
||||
value: 4 / 3
|
||||
value: 4 / 3,
|
||||
},
|
||||
{
|
||||
label: __("16:9"),
|
||||
value: 16 / 9
|
||||
value: 16 / 9,
|
||||
},
|
||||
{
|
||||
label: __("Free"),
|
||||
value: NaN
|
||||
}
|
||||
value: NaN,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
// watcher
|
||||
watch(aspect_ratio, (value) => {
|
||||
if (cropper.value) {
|
||||
cropper.value.setAspectRatio(value);
|
||||
}
|
||||
}, { deep: true });
|
||||
|
||||
watch(
|
||||
aspect_ratio,
|
||||
(value) => {
|
||||
if (cropper.value) {
|
||||
cropper.value.setAspectRatio(value);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
:stroke-dasharray="circumference + ' ' + circumference"
|
||||
:style="{
|
||||
stroke: secondary,
|
||||
strokeDashoffset: 0
|
||||
strokeDashoffset: 0,
|
||||
}"
|
||||
:stroke-width="stroke"
|
||||
fill="transparent"
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
:stroke-dasharray="circumference + ' ' + circumference"
|
||||
:style="{
|
||||
stroke: primary,
|
||||
strokeDashoffset: strokeDashoffset
|
||||
strokeDashoffset: strokeDashoffset,
|
||||
}"
|
||||
:stroke-width="stroke"
|
||||
fill="transparent"
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
:style="{
|
||||
color: 'var(--text-color)',
|
||||
fontSize: 'var(--text-xs)',
|
||||
fontWeight: 'var(--text-bold)'
|
||||
fontWeight: 'var(--text-bold)',
|
||||
}"
|
||||
>
|
||||
{{ progress }}%
|
||||
|
|
@ -49,7 +49,7 @@ const props = defineProps({
|
|||
secondary: String,
|
||||
radius: Number,
|
||||
progress: Number,
|
||||
stroke: Number
|
||||
stroke: Number,
|
||||
});
|
||||
|
||||
// variables
|
||||
|
|
@ -60,7 +60,6 @@ let circumference = ref(normalizedRadius.value * 2 * Math.PI);
|
|||
let strokeDashoffset = computed(() => {
|
||||
return circumference.value - (props.progress / 100) * circumference.value;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<template>
|
||||
<div class="file-web-link margin-bottom">
|
||||
<a href class="text-muted text-medium"
|
||||
@click.prevent="emit('hide-web-link')"
|
||||
>
|
||||
{{ __('← Back to upload files') }}
|
||||
<a href class="text-muted text-medium" @click.prevent="emit('hide-web-link')">
|
||||
{{ __("← Back to upload files") }}
|
||||
</a>
|
||||
<div class="input-group">
|
||||
<input
|
||||
|
|
@ -11,7 +9,7 @@
|
|||
class="form-control"
|
||||
:placeholder="__('Attach a web link')"
|
||||
v-model="url"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@
|
|||
handle=".icon-drag"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div
|
||||
class="mt-2 row align-center column-row"
|
||||
v-for="column in df.table_columns"
|
||||
>
|
||||
<div class="mt-2 row align-center column-row" v-for="column in df.table_columns">
|
||||
<div class="col-8">
|
||||
<div class="column-label d-flex align-center">
|
||||
<div class="px-2 icon-drag ml-n2">
|
||||
|
|
@ -50,10 +47,7 @@
|
|||
max="100"
|
||||
step="5"
|
||||
/>
|
||||
<button
|
||||
class="ml-2 btn btn-xs btn-icon"
|
||||
@click="remove_column(column)"
|
||||
>
|
||||
<button class="ml-2 btn btn-xs btn-icon" @click="remove_column(column)">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-close"></use>
|
||||
</svg>
|
||||
|
|
@ -74,7 +68,7 @@ const props = defineProps(["df"]);
|
|||
|
||||
// methods
|
||||
function remove_column(column) {
|
||||
props.df["table_columns"] = props.df.table_columns.filter(_column => _column !== column)
|
||||
props.df["table_columns"] = props.df.table_columns.filter((_column) => _column !== column);
|
||||
}
|
||||
// computed
|
||||
let help_message = computed(() => {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,7 @@
|
|||
v-if="df.fieldtype == 'HTML' && df.html"
|
||||
v-html="df.html"
|
||||
></div>
|
||||
<div
|
||||
class="custom-html"
|
||||
v-if="df.fieldtype == 'Field Template'"
|
||||
>
|
||||
<div class="custom-html" v-if="df.fieldtype == 'Field Template'">
|
||||
{{ df.label }}
|
||||
</div>
|
||||
<input
|
||||
|
|
@ -24,9 +21,7 @@
|
|||
@blur="editing = false"
|
||||
/>
|
||||
<span v-else-if="df.label">{{ df.label }}</span>
|
||||
<i class="text-muted" v-else>
|
||||
{{ __("No Label") }} ({{ df.fieldname }})
|
||||
</i>
|
||||
<i class="text-muted" v-else> {{ __("No Label") }} ({{ df.fieldname }}) </i>
|
||||
</div>
|
||||
<div class="field-actions">
|
||||
<button
|
||||
|
|
@ -45,10 +40,7 @@
|
|||
>
|
||||
Configure columns
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-xs btn-icon"
|
||||
@click="df['remove'] = true"
|
||||
>
|
||||
<button class="btn btn-xs btn-icon" @click="df['remove'] = true">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-close"></use>
|
||||
</svg>
|
||||
|
|
@ -94,14 +86,14 @@ function edit_html() {
|
|||
label: __("HTML"),
|
||||
fieldname: "html",
|
||||
fieldtype: "Code",
|
||||
options: "HTML"
|
||||
}
|
||||
options: "HTML",
|
||||
},
|
||||
],
|
||||
primary_action: ({ html }) => {
|
||||
html = frappe.dom.remove_script_and_style(html);
|
||||
props.df["html"] = html;
|
||||
d.hide();
|
||||
}
|
||||
},
|
||||
});
|
||||
d.set_value("html", props.df.html);
|
||||
d.show();
|
||||
|
|
@ -112,7 +104,7 @@ function configure_columns() {
|
|||
fields: [
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "columns_area"
|
||||
fieldname: "columns_area",
|
||||
},
|
||||
{
|
||||
label: "",
|
||||
|
|
@ -130,8 +122,8 @@ function configure_columns() {
|
|||
dialog.set_value("add_column", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
on_page_show: () => {
|
||||
createApp(ConfigureColumnsVue, { df: props.df }).mount(
|
||||
|
|
@ -139,8 +131,8 @@ function configure_columns() {
|
|||
);
|
||||
},
|
||||
on_hide: () => {
|
||||
props.df["table_columns"] = props.df.table_columns.filter(col => !col.invalid_width);
|
||||
}
|
||||
props.df["table_columns"] = props.df.table_columns.filter((col) => !col.invalid_width);
|
||||
},
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
|
@ -149,18 +141,18 @@ function get_all_columns() {
|
|||
let more_columns = [
|
||||
{
|
||||
label: __("Sr No."),
|
||||
value: "idx"
|
||||
}
|
||||
value: "idx",
|
||||
},
|
||||
];
|
||||
return more_columns.concat(
|
||||
meta.fields
|
||||
.map(tf => {
|
||||
.map((tf) => {
|
||||
if (frappe.model.no_value_type.includes(tf.fieldtype)) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
label: tf.label,
|
||||
value: tf.fieldname
|
||||
value: tf.fieldname,
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
|
@ -172,8 +164,8 @@ function get_column_to_add(fieldname) {
|
|||
label: __("Sr No."),
|
||||
fieldtype: "Data",
|
||||
fieldname: "idx",
|
||||
width: 10
|
||||
}
|
||||
width: 10,
|
||||
},
|
||||
};
|
||||
|
||||
if (fieldname in standard_columns) {
|
||||
|
|
@ -182,7 +174,7 @@ function get_column_to_add(fieldname) {
|
|||
|
||||
return {
|
||||
...frappe.meta.get_docfield(props.df.options, fieldname),
|
||||
width: 10
|
||||
width: 10,
|
||||
};
|
||||
}
|
||||
function validate_table_columns() {
|
||||
|
|
@ -214,7 +206,6 @@ watch(
|
|||
() => validate_table_columns(),
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
<template>
|
||||
<div class="html-editor">
|
||||
<div class="d-flex justify-content-end">
|
||||
<button
|
||||
class="btn btn-default btn-xs btn-edit"
|
||||
@click="toggle_edit"
|
||||
>
|
||||
<button class="btn btn-default btn-xs btn-edit" @click="toggle_edit">
|
||||
{{ !editing ? buttonLabel : __("Done") }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -46,9 +43,9 @@ function toggle_edit() {
|
|||
max_lines: 30,
|
||||
change: () => {
|
||||
emit("change", get_value());
|
||||
}
|
||||
},
|
||||
},
|
||||
render_input: true
|
||||
render_input: true,
|
||||
});
|
||||
}
|
||||
control.value.set_value(props.value);
|
||||
|
|
|
|||
|
|
@ -13,11 +13,7 @@
|
|||
type="button"
|
||||
class="btn btn-xs"
|
||||
@click="letterhead.align = direction"
|
||||
:class="
|
||||
letterhead.align == direction
|
||||
? 'btn-secondary'
|
||||
: 'btn-default'
|
||||
"
|
||||
:class="letterhead.align == direction ? 'btn-secondary' : 'btn-default'"
|
||||
>
|
||||
{{ direction }}
|
||||
</button>
|
||||
|
|
@ -30,12 +26,7 @@
|
|||
min="20"
|
||||
:max="range_input_field === 'image_width' ? 700 : 500"
|
||||
:value="letterhead[range_input_field]"
|
||||
@input="
|
||||
e =>
|
||||
(letterhead[range_input_field] = parseFloat(
|
||||
e.target.value
|
||||
))
|
||||
"
|
||||
@input="(e) => (letterhead[range_input_field] = parseFloat(e.target.value))"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -58,11 +49,7 @@
|
|||
class="ml-2 btn btn-default btn-xs btn-edit"
|
||||
@click="toggle_edit_letterhead"
|
||||
>
|
||||
{{
|
||||
!store.edit_letterhead
|
||||
? __("Edit Letter Head")
|
||||
: __("Done")
|
||||
}}
|
||||
{{ !store.edit_letterhead ? __("Edit Letter Head") : __("Done") }}
|
||||
</button>
|
||||
<button
|
||||
v-if="!letterhead"
|
||||
|
|
@ -73,10 +60,7 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="letterhead && !store.edit_letterhead"
|
||||
v-html="letterhead.content"
|
||||
></div>
|
||||
<div v-if="letterhead && !store.edit_letterhead" v-html="letterhead.content"></div>
|
||||
<!-- <div v-show="letterhead && store.edit_letterhead" ref="editor"></div> -->
|
||||
<div
|
||||
class="edit-letterhead"
|
||||
|
|
@ -85,8 +69,8 @@
|
|||
justifyContent: {
|
||||
Left: 'flex-start',
|
||||
Center: 'center',
|
||||
Right: 'flex-end'
|
||||
}[letterhead.align]
|
||||
Right: 'flex-end',
|
||||
}[letterhead.align],
|
||||
}"
|
||||
>
|
||||
<div class="edit-image">
|
||||
|
|
@ -101,7 +85,7 @@
|
|||
height:
|
||||
range_input_field === 'image_height'
|
||||
? letterhead.image_height + 'px'
|
||||
: null
|
||||
: null,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -143,15 +127,15 @@ function toggle_edit_letterhead() {
|
|||
change: () => {
|
||||
letterhead.value._dirty = true;
|
||||
letterhead.value.content = control.value.get_value();
|
||||
}
|
||||
},
|
||||
},
|
||||
render_input: true,
|
||||
only_input: true,
|
||||
no_wrapper: true
|
||||
no_wrapper: true,
|
||||
});
|
||||
}
|
||||
control.value.set_value(letterhead.value.content);
|
||||
};
|
||||
}
|
||||
function change_letterhead() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Change Letter Head"),
|
||||
|
|
@ -160,62 +144,52 @@ function change_letterhead() {
|
|||
label: __("Letter Head"),
|
||||
fieldname: "letterhead",
|
||||
fieldtype: "Link",
|
||||
options: "Letter Head"
|
||||
}
|
||||
options: "Letter Head",
|
||||
},
|
||||
],
|
||||
primary_action: ({ letterhead }) => {
|
||||
if (letterhead) {
|
||||
set_letterhead(letterhead);
|
||||
}
|
||||
d.hide();
|
||||
}
|
||||
},
|
||||
});
|
||||
d.show();
|
||||
};
|
||||
}
|
||||
function upload_image() {
|
||||
new frappe.ui.FileUploader({
|
||||
folder: "Home/Attachments",
|
||||
on_success: file_doc => {
|
||||
get_image_dimensions(file_doc.file_url).then(
|
||||
({ width, height }) => {
|
||||
letterhead.value["image"] = file_doc.file_url;
|
||||
let new_width = width;
|
||||
let new_height = height;
|
||||
aspect_ratio.value = width / height;
|
||||
range_input_field.value =
|
||||
aspect_ratio.value > 1
|
||||
? "image_width"
|
||||
: "image_height";
|
||||
on_success: (file_doc) => {
|
||||
get_image_dimensions(file_doc.file_url).then(({ width, height }) => {
|
||||
letterhead.value["image"] = file_doc.file_url;
|
||||
let new_width = width;
|
||||
let new_height = height;
|
||||
aspect_ratio.value = width / height;
|
||||
range_input_field.value = aspect_ratio.value > 1 ? "image_width" : "image_height";
|
||||
|
||||
if (width > 200) {
|
||||
new_width = 200;
|
||||
new_height = new_width / aspect_ratio.value;
|
||||
}
|
||||
if (height > 80) {
|
||||
new_height = 80;
|
||||
new_width = aspect_ratio.value * new_height;
|
||||
}
|
||||
|
||||
letterhead.value["image_height"] = new_height;
|
||||
letterhead.value["image_width"] = new_width;
|
||||
if (width > 200) {
|
||||
new_width = 200;
|
||||
new_height = new_width / aspect_ratio.value;
|
||||
}
|
||||
);
|
||||
}
|
||||
if (height > 80) {
|
||||
new_height = 80;
|
||||
new_width = aspect_ratio.value * new_height;
|
||||
}
|
||||
|
||||
letterhead.value["image_height"] = new_height;
|
||||
letterhead.value["image_width"] = new_width;
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
function set_letterhead(_letterhead) {
|
||||
store.value.change_letterhead(_letterhead).then(() => {
|
||||
get_image_dimensions(letterhead.value.image).then(
|
||||
({ width, height }) => {
|
||||
aspect_ratio.value = width / height;
|
||||
range_input_field.value =
|
||||
aspect_ratio.value > 1
|
||||
? "image_width"
|
||||
: "image_height";
|
||||
}
|
||||
);
|
||||
get_image_dimensions(letterhead.value.image).then(({ width, height }) => {
|
||||
aspect_ratio.value = width / height;
|
||||
range_input_field.value = aspect_ratio.value > 1 ? "image_width" : "image_height";
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
function create_letterhead() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Create Letter Head"),
|
||||
|
|
@ -223,23 +197,23 @@ function create_letterhead() {
|
|||
{
|
||||
label: __("Letter Head Name"),
|
||||
fieldname: "name",
|
||||
fieldtype: "Data"
|
||||
}
|
||||
fieldtype: "Data",
|
||||
},
|
||||
],
|
||||
primary_action: ({ name }) => {
|
||||
return frappe.db
|
||||
.insert({
|
||||
doctype: "Letter Head",
|
||||
letter_head_name: name,
|
||||
source: "Image"
|
||||
source: "Image",
|
||||
})
|
||||
.then(doc => {
|
||||
.then((doc) => {
|
||||
d.hide();
|
||||
store.value.change_letterhead(doc.name).then(() => {
|
||||
toggle_edit_letterhead();
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
d.show();
|
||||
}
|
||||
|
|
@ -249,34 +223,33 @@ onMounted(() => {
|
|||
set_letterhead(frappe.boot.sysdefaults.letter_head);
|
||||
}
|
||||
|
||||
watch(() => {
|
||||
return letterhead.value
|
||||
? letterhead.value[range_input_field.value]
|
||||
: null;
|
||||
}, () => {
|
||||
if (aspect_ratio.value === null) return;
|
||||
watch(
|
||||
() => {
|
||||
return letterhead.value ? letterhead.value[range_input_field.value] : null;
|
||||
},
|
||||
() => {
|
||||
if (aspect_ratio.value === null) return;
|
||||
|
||||
let update_field =
|
||||
range_input_field.value == "image_width"
|
||||
? "image_height"
|
||||
: "image_width";
|
||||
letterhead.value[update_field] =
|
||||
update_field == "image_width"
|
||||
? aspect_ratio.value * letterhead.value.image_height
|
||||
: letterhead.value.image_width / aspect_ratio.value;
|
||||
});
|
||||
let update_field =
|
||||
range_input_field.value == "image_width" ? "image_height" : "image_width";
|
||||
letterhead.value[update_field] =
|
||||
update_field == "image_width"
|
||||
? aspect_ratio.value * letterhead.value.image_height
|
||||
: letterhead.value.image_width / aspect_ratio.value;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// watch
|
||||
watch(letterhead, () => {
|
||||
if (!letterhead.value) return;
|
||||
if (letterhead.value.image_width && letterhead.value.image_height) {
|
||||
let dimension =
|
||||
letterhead.value.image_width > letterhead.value.image_height
|
||||
? "width"
|
||||
: "height";
|
||||
let dimension_value = letterhead.value["image_" + dimension];
|
||||
letterhead.value.content = `
|
||||
watch(
|
||||
letterhead,
|
||||
() => {
|
||||
if (!letterhead.value) return;
|
||||
if (letterhead.value.image_width && letterhead.value.image_height) {
|
||||
let dimension =
|
||||
letterhead.value.image_width > letterhead.value.image_height ? "width" : "height";
|
||||
let dimension_value = letterhead.value["image_" + dimension];
|
||||
letterhead.value.content = `
|
||||
<div style="text-align: ${letterhead.value.align.toLowerCase()};">
|
||||
<img
|
||||
src="${letterhead.value.image}"
|
||||
|
|
@ -285,8 +258,11 @@ watch(letterhead, () => {
|
|||
style="${dimension}: ${dimension_value}px;">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}, { deep: true }, { immediate: true });
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ function refresh() {
|
|||
iframe.value?.contentWindow.location.reload();
|
||||
}
|
||||
function get_default_docname() {
|
||||
return frappe.db.get_list(doctype.value, { limit: 1 }).then(doc => {
|
||||
return frappe.db.get_list(doctype.value, { limit: 1 }).then((doc) => {
|
||||
return doc.length > 0 ? doc[0].name : null;
|
||||
});
|
||||
}
|
||||
|
|
@ -78,9 +78,7 @@ let url = computed(() => {
|
|||
params.append("letterhead", store.value.letterhead.name);
|
||||
}
|
||||
let _url =
|
||||
type.value == "PDF"
|
||||
? `/api/method/frappe.utils.weasyprint.download_pdf`
|
||||
: "/printpreview";
|
||||
type.value == "PDF" ? `/api/method/frappe.utils.weasyprint.download_pdf` : "/printpreview";
|
||||
return `${_url}?${params.toString()}`;
|
||||
});
|
||||
|
||||
|
|
@ -95,9 +93,9 @@ onMounted(() => {
|
|||
options: doctype.value,
|
||||
change: () => {
|
||||
docname.value = doc_select.value.get_value();
|
||||
}
|
||||
},
|
||||
},
|
||||
render_input: true
|
||||
render_input: true,
|
||||
});
|
||||
preview_type.value = frappe.ui.form.make_control({
|
||||
parent: preview_type_ref.value,
|
||||
|
|
@ -108,12 +106,12 @@ onMounted(() => {
|
|||
options: ["PDF", "HTML"],
|
||||
change: () => {
|
||||
type.value = preview_type.value.get_value();
|
||||
}
|
||||
},
|
||||
},
|
||||
render_input: true
|
||||
render_input: true,
|
||||
});
|
||||
preview_type.value.set_value(type.value);
|
||||
get_default_docname().then(doc_name => {
|
||||
get_default_docname().then((doc_name) => {
|
||||
doc_name && doc_select.value.set_value(doc_name);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ function add_section_above(section) {
|
|||
label: "",
|
||||
columns: [
|
||||
{ label: "", fields: [] },
|
||||
{ label: "", fields: [] }
|
||||
]
|
||||
{ label: "", fields: [] },
|
||||
],
|
||||
});
|
||||
}
|
||||
sections.push(_section);
|
||||
|
|
@ -75,12 +75,12 @@ let rootStyles = computed(() => {
|
|||
margin_top = 0,
|
||||
margin_bottom = 0,
|
||||
margin_left = 0,
|
||||
margin_right = 0
|
||||
margin_right = 0,
|
||||
} = print_format.value;
|
||||
return {
|
||||
padding: `${margin_top}mm ${margin_right}mm ${margin_bottom}mm ${margin_left}mm`,
|
||||
width: "210mm",
|
||||
minHeight: "297mm"
|
||||
minHeight: "297mm",
|
||||
};
|
||||
});
|
||||
let page_number_style = computed(() => {
|
||||
|
|
@ -89,7 +89,7 @@ let page_number_style = computed(() => {
|
|||
background: "white",
|
||||
padding: "4px",
|
||||
borderRadius: "var(--border-radius)",
|
||||
border: "1px solid var(--border-color)"
|
||||
border: "1px solid var(--border-color)",
|
||||
};
|
||||
if (print_format.value.page_number.includes("Top")) {
|
||||
style.top = print_format.value.margin_top / 2 + "mm";
|
||||
|
|
|
|||
|
|
@ -27,14 +27,12 @@ let show_preview = ref(false);
|
|||
|
||||
// computed
|
||||
let $store = computed(() => {
|
||||
return getStore(props.print_format_name)
|
||||
return getStore(props.print_format_name);
|
||||
});
|
||||
|
||||
let shouldRender = computed(() => {
|
||||
return Boolean(
|
||||
$store.value.print_format.value &&
|
||||
$store.value.meta.value &&
|
||||
$store.value.layout.value
|
||||
$store.value.print_format.value && $store.value.meta.value && $store.value.layout.value
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,7 @@
|
|||
<div class="sidebar-menu">
|
||||
<div class="sidebar-label">{{ __("Page Margins") }}</div>
|
||||
<div class="margin-controls">
|
||||
<div
|
||||
class="form-group"
|
||||
v-for="df in margins"
|
||||
:key="df.fieldname"
|
||||
>
|
||||
<div class="form-group" v-for="df in margins" :key="df.fieldname">
|
||||
<div class="clearfix">
|
||||
<label class="control-label">
|
||||
{{ df.label }}
|
||||
|
|
@ -21,13 +17,7 @@
|
|||
class="form-control form-control-sm"
|
||||
:value="print_format[df.fieldname]"
|
||||
min="0"
|
||||
@change="
|
||||
e =>
|
||||
update_margin(
|
||||
df.fieldname,
|
||||
e.target.value
|
||||
)
|
||||
"
|
||||
@change="(e) => update_margin(df.fieldname, e.target.value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -43,10 +33,7 @@
|
|||
class="form-control form-control-sm"
|
||||
v-model="print_format.font"
|
||||
>
|
||||
<option
|
||||
v-for="font in google_fonts"
|
||||
:value="font"
|
||||
>
|
||||
<option v-for="font in google_fonts" :value="font">
|
||||
{{ font }}
|
||||
</option>
|
||||
</select>
|
||||
|
|
@ -65,10 +52,7 @@
|
|||
placeholder="12, 13, 14"
|
||||
:value="print_format.font_size"
|
||||
@change="
|
||||
e =>
|
||||
(print_format.font_size = parseFloat(
|
||||
e.target.value
|
||||
))
|
||||
(e) => (print_format.font_size = parseFloat(e.target.value))
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -112,10 +96,7 @@
|
|||
item-key="id"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div
|
||||
class="field"
|
||||
:title="element.fieldname"
|
||||
>
|
||||
<div class="field" :title="element.fieldname">
|
||||
{{ element.label }}
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -157,7 +138,7 @@ function clone_field(df) {
|
|||
"options",
|
||||
"table_columns",
|
||||
"html",
|
||||
"field_template"
|
||||
"field_template",
|
||||
]);
|
||||
if (cloned.custom) {
|
||||
// generate unique fieldnames for custom blocks
|
||||
|
|
@ -171,16 +152,14 @@ let margins = computed(() => {
|
|||
return [
|
||||
{ label: __("Top"), fieldname: "margin_top" },
|
||||
{ label: __("Bottom"), fieldname: "margin_bottom" },
|
||||
{ label: __("Left", null, 'alignment'), fieldname: "margin_left" },
|
||||
{ label: __("Right", null, 'alignment'), fieldname: "margin_right" }
|
||||
{ label: __("Left", null, "alignment"), fieldname: "margin_left" },
|
||||
{ label: __("Right", null, "alignment"), fieldname: "margin_right" },
|
||||
];
|
||||
});
|
||||
let fields = computed(() => {
|
||||
let fields = meta.value.fields
|
||||
.filter(df => {
|
||||
if (
|
||||
["Section Break", "Column Break"].includes(df.fieldtype)
|
||||
) {
|
||||
.filter((df) => {
|
||||
if (["Section Break", "Column Break"].includes(df.fieldtype)) {
|
||||
return false;
|
||||
}
|
||||
if (search_text.value) {
|
||||
|
|
@ -195,12 +174,12 @@ let fields = computed(() => {
|
|||
return true;
|
||||
}
|
||||
})
|
||||
.map(df => {
|
||||
.map((df) => {
|
||||
let out = {
|
||||
label: df.label,
|
||||
fieldname: df.fieldname,
|
||||
fieldtype: df.fieldtype,
|
||||
options: df.options
|
||||
options: df.options,
|
||||
};
|
||||
if (df.fieldtype == "Table") {
|
||||
out.table_columns = get_table_columns(df);
|
||||
|
|
@ -214,27 +193,27 @@ let fields = computed(() => {
|
|||
fieldname: "custom_html",
|
||||
fieldtype: "HTML",
|
||||
html: "",
|
||||
custom: 1
|
||||
custom: 1,
|
||||
},
|
||||
{
|
||||
label: __("ID (name)"),
|
||||
fieldname: "name",
|
||||
fieldtype: "Data"
|
||||
fieldtype: "Data",
|
||||
},
|
||||
{
|
||||
label: __("Spacer"),
|
||||
fieldname: "spacer",
|
||||
fieldtype: "Spacer",
|
||||
custom: 1
|
||||
custom: 1,
|
||||
},
|
||||
{
|
||||
label: __("Divider"),
|
||||
fieldname: "divider",
|
||||
fieldtype: "Divider",
|
||||
custom: 1
|
||||
custom: 1,
|
||||
},
|
||||
...print_templates.value,
|
||||
...fields
|
||||
...fields,
|
||||
];
|
||||
});
|
||||
let print_templates = computed(() => {
|
||||
|
|
@ -243,21 +222,18 @@ let print_templates = computed(() => {
|
|||
for (let template of templates) {
|
||||
let df;
|
||||
if (template.field) {
|
||||
df = frappe.meta.get_docfield(
|
||||
meta.value.name,
|
||||
template.field
|
||||
);
|
||||
df = frappe.meta.get_docfield(meta.value.name, template.field);
|
||||
} else {
|
||||
df = {
|
||||
label: template.name,
|
||||
fieldname: frappe.scrub(template.name)
|
||||
fieldname: frappe.scrub(template.name),
|
||||
};
|
||||
}
|
||||
out.push({
|
||||
label: `${__(df.label)} (${__("Field Template")})`,
|
||||
fieldname: df.fieldname + "_template",
|
||||
fieldtype: "Field Template",
|
||||
field_template: template.name
|
||||
field_template: template.name,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
|
|
@ -270,7 +246,7 @@ let page_number_positions = computed(() => {
|
|||
{ label: __("Top Right"), value: "Top Right" },
|
||||
{ label: __("Bottom Left"), value: "Bottom Left" },
|
||||
{ label: __("Bottom Center"), value: "Bottom Center" },
|
||||
{ label: __("Bottom Right"), value: "Bottom Right" }
|
||||
{ label: __("Bottom Right"), value: "Bottom Right" },
|
||||
];
|
||||
});
|
||||
|
||||
|
|
@ -278,7 +254,7 @@ let page_number_positions = computed(() => {
|
|||
onMounted(() => {
|
||||
let method =
|
||||
"frappe.printing.page.print_format_builder_beta.print_format_builder_beta.get_google_fonts";
|
||||
frappe.call(method).then(r => {
|
||||
frappe.call(method).then((r) => {
|
||||
google_fonts.value = r.message || [];
|
||||
if (!google_fonts.value.includes(print_format.value.font)) {
|
||||
google_fonts.value.push(print_format.value.font);
|
||||
|
|
|
|||
|
|
@ -28,10 +28,7 @@
|
|||
<use href="#icon-dot-horizontal"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class="dropdown-menu dropdown-menu-right"
|
||||
role="menu"
|
||||
>
|
||||
<div class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<button
|
||||
v-for="option in section_options"
|
||||
class="dropdown-item"
|
||||
|
|
@ -44,17 +41,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row section-columns">
|
||||
<div
|
||||
class="column col"
|
||||
v-for="(column, i) in section.columns"
|
||||
:key="i"
|
||||
>
|
||||
<div class="column col" v-for="(column, i) in section.columns" :key="i">
|
||||
<draggable
|
||||
class="drag-container"
|
||||
:style="{
|
||||
backgroundColor: column.fields.length
|
||||
? null
|
||||
: 'var(--gray-50)'
|
||||
backgroundColor: column.fields.length ? null : 'var(--gray-50)',
|
||||
}"
|
||||
v-model="column.fields"
|
||||
group="fields"
|
||||
|
|
@ -68,10 +59,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="my-4 text-center text-muted font-italic"
|
||||
v-if="section.page_break"
|
||||
>
|
||||
<div class="my-4 text-center text-muted font-italic" v-if="section.page_break">
|
||||
{{ __("Page Break") }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -93,7 +81,7 @@ function add_column() {
|
|||
if (props.section.columns.length < 4) {
|
||||
props.section.columns.push({
|
||||
label: "",
|
||||
fields: []
|
||||
fields: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -121,46 +109,50 @@ let section_options = computed(() => {
|
|||
return [
|
||||
{
|
||||
label: __("Add section above"),
|
||||
action: () => emit("add_section_above")
|
||||
action: () => emit("add_section_above"),
|
||||
},
|
||||
{
|
||||
label: __("Add column"),
|
||||
action: add_column,
|
||||
condition: () => props.section.columns.length < 4
|
||||
condition: () => props.section.columns.length < 4,
|
||||
},
|
||||
{
|
||||
label: __("Remove column"),
|
||||
action: remove_column,
|
||||
condition: () => props.section.columns.length > 1
|
||||
condition: () => props.section.columns.length > 1,
|
||||
},
|
||||
{
|
||||
label: __("Add page break"),
|
||||
action: add_page_break,
|
||||
condition: () => !props.section.page_break
|
||||
condition: () => !props.section.page_break,
|
||||
},
|
||||
{
|
||||
label: __("Remove page break"),
|
||||
action: remove_page_break,
|
||||
condition: () => props.section.page_break
|
||||
condition: () => props.section.page_break,
|
||||
},
|
||||
{
|
||||
label: __("Remove section"),
|
||||
action: () => { props.section["remove"] = true }
|
||||
action: () => {
|
||||
props.section["remove"] = true;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: __("Field Orientation (Left-Right)"),
|
||||
condition: () => !props.section.field_orientation,
|
||||
action: () => { props.section["field_orientation"] = "left-right" }
|
||||
action: () => {
|
||||
props.section["field_orientation"] = "left-right";
|
||||
},
|
||||
},
|
||||
{
|
||||
label: __("Field Orientation (Top-Down)"),
|
||||
condition: () =>
|
||||
props.section.field_orientation == "left-right",
|
||||
action: () => { props.section["field_orientation"] = "" }
|
||||
}
|
||||
].filter(option => (option.condition ? option.condition() : true));
|
||||
})
|
||||
|
||||
condition: () => props.section.field_orientation == "left-right",
|
||||
action: () => {
|
||||
props.section["field_orientation"] = "";
|
||||
},
|
||||
},
|
||||
].filter((option) => (option.condition ? option.condition() : true));
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { useStore } from "../store";
|
|||
const props = defineProps({
|
||||
node: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const isValidConnection = ({ source, target }) => {
|
||||
|
|
@ -26,21 +26,25 @@ let store = useStore();
|
|||
const { edges, findNode } = useVueFlow();
|
||||
watch(
|
||||
() => findNode(props.node.id)?.selected,
|
||||
val => {
|
||||
(val) => {
|
||||
if (val) store.workflow.selected = props.node;
|
||||
|
||||
let connected_edges = edges.value.filter(
|
||||
edge => edge.source === props.node.id || edge.target === props.node.id
|
||||
(edge) => edge.source === props.node.id || edge.target === props.node.id
|
||||
);
|
||||
connected_edges.forEach(edge => edge.selected = val);
|
||||
connected_edges.forEach((edge) => (edge.selected = val));
|
||||
}
|
||||
);
|
||||
|
||||
let label = computed(() => findNode(props.node.id)?.data?.action);
|
||||
|
||||
watch(() => props.node.data, () => {
|
||||
store.ref_history.commit();
|
||||
}, { deep: true });
|
||||
watch(
|
||||
() => props.node.data,
|
||||
() => {
|
||||
store.ref_history.commit();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -11,21 +11,21 @@ const props = defineProps({
|
|||
targetPosition: { type: String, required: false },
|
||||
sourceHandle: { type: Object, required: false },
|
||||
targetHandle: { type: Object, required: false },
|
||||
markerEnd: { type: String, required: false }
|
||||
markerEnd: { type: String, required: false },
|
||||
});
|
||||
|
||||
let opposite = {
|
||||
left: "left",
|
||||
right: "right",
|
||||
top: "bottom",
|
||||
bottom: "top"
|
||||
bottom: "top",
|
||||
};
|
||||
|
||||
const d = computed(() =>
|
||||
getSmoothStepPath({
|
||||
...props,
|
||||
borderRadius: 30,
|
||||
targetPosition: opposite[props.targetPosition]
|
||||
targetPosition: opposite[props.targetPosition],
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue