style: Auto format vue file with prettier

- Ran pre-commit run --all-files
This commit is contained in:
Suraj Shetty 2023-11-13 11:26:00 +05:30
parent 392fee66a2
commit b4bc871aab
26 changed files with 574 additions and 539 deletions

View file

@ -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>

View file

@ -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;

View file

@ -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>

View file

@ -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;
}

View file

@ -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'"

View file

@ -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>

View file

@ -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 -->

View file

@ -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,
` &gt; <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 {

View file

@ -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>

View file

@ -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,
};
}

View file

@ -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);
}
}

View file

@ -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,

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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(() => {

View file

@ -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>

View file

@ -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);

View file

@ -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>

View file

@ -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);
});
});

View file

@ -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";

View file

@ -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
);
});

View file

@ -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);

View file

@ -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>

View file

@ -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>

View file

@ -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>