Initial commit: OCA Storage packages (17 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:06 +02:00
commit 7a380f05d3
659 changed files with 41828 additions and 0 deletions

View file

@ -0,0 +1,5 @@
.fs_file_download_button {
top: 10% !important;
left: 50% !important;
position: absolute !important;
}

View file

@ -0,0 +1,40 @@
/** @odoo-module */
/**
* Copyright 2023 ACSONE SA/NV
*/
import {Dialog} from "@web/core/dialog/dialog";
const {Component, useRef} = owl;
export class AltTextDialog extends Component {
setup() {
this.altText = useRef("altText");
}
async onClose() {
if (this.props.close) {
this.props.close();
}
}
async onConfirm() {
try {
await this.props.confirm(this.altText.el.value);
} catch (e) {
this.props.close();
throw e;
}
this.onClose();
}
}
AltTextDialog.components = {Dialog};
AltTextDialog.template = "fs_image.AltTextDialog";
AltTextDialog.props = {
title: String,
altText: String,
confirm: Function,
close: {type: Function, optional: true},
};

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="fs_image.AltTextDialog" owl="1">
<Dialog size="'md'" title="props.title">
<div class="form-group row">
<t t-if="props.readonly">
<span t-esc="props.value or ''" />
</t>
<div class="col-sm-12 o_field_widget o_field_text">
<input
type="text"
id="altText"
t-ref="altText"
t-att-value="props.altText"
class="o_input"
/>
</div>
</div>
<t t-set-slot="footer" owl="1">
<button
class="btn btn-primary"
t-ref="btn-confirm"
t-on-click="onConfirm"
>Save changes</button>
<button
class="btn btn-secondary"
t-ref="btn-close"
t-on-click="onClose"
>Cancel</button>
</t>
</Dialog>
</t>
</templates>

View file

@ -0,0 +1,117 @@
/** @odoo-module */
/**
* Copyright 2023 ACSONE SA/NV
*/
import {
ImageField,
fileTypeMagicWordMap,
imageCacheKey,
} from "@web/views/fields/image/image_field";
import {onWillUpdateProps, useState} from "@odoo/owl";
import {AltTextDialog} from "../dialogs/alttext_dialog.esm";
import {download, downloadFile} from "@web/core/network/download";
import {registry} from "@web/core/registry";
import {url} from "@web/core/utils/urls";
import {useService} from "@web/core/utils/hooks";
const placeholder = "/web/static/img/placeholder.png";
export class FSImageField extends ImageField {
setup() {
// Call super.setup() to initialize the state
super.setup();
this.state = useState({
...this.props.value,
...this.state,
});
onWillUpdateProps((nextProps) => {
this.state.isUploading = false;
const {filename, mimetype, alt_text, url} = nextProps.value || {};
this.state.filename = filename;
this.state.mimetype = mimetype;
this.state.url = url;
this.state.alt_text = alt_text;
});
this.dialogService = useService("dialog");
}
getUrl(previewFieldName) {
if (
this.state.isValid &&
this.props.value &&
typeof this.props.value === "object"
) {
// Check if value is a dict
if (this.props.value.content) {
// We use the binary content of the value
// Use magic-word technique for detecting image type
const magic =
fileTypeMagicWordMap[this.props.value.content[0]] || "png";
return `data:image/${magic};base64,${this.props.value.content}`;
}
const model = this.props.record.resModel;
const id = this.props.record.resId;
let base_url = this.props.value.url;
if (id !== undefined && id !== null && id !== false) {
const field = previewFieldName;
const filename = this.props.value.filename;
base_url = `/web/image/${model}/${id}/${field}/${filename}`;
}
return url(base_url, {unique: imageCacheKey(this.rawCacheKey)});
}
return placeholder;
}
get hasTooltip() {
return this.props.enableZoom && !this.props.isDebugMode && this.props.value;
}
onFileUploaded(info) {
this.state.isValid = true;
this.props.update({
filename: info.name,
content: info.data,
});
}
onAltTextEdit() {
const self = this;
const altText = this.props.value.alt_text || "";
const dialogProps = {
title: this.env._t("Alt Text"),
altText: altText,
confirm: (value) => {
self.props.update({
...self.props.value,
alt_text: value,
});
},
};
this.dialogService.add(AltTextDialog, dialogProps);
}
async onFileDownload() {
if (this.props.value.content) {
const magic = fileTypeMagicWordMap[this.props.value.content[0]] || "png";
await downloadFile(
`data:image/${magic};base64,${this.props.value.content}`,
this.state.filename,
`image/${magic}`
);
} else {
await download({
data: {
model: this.props.record.resModel,
id: this.props.record.resId,
field: this.props.name,
filename: this.state.filename || "download",
download: true,
},
url: "/web/image",
});
}
}
}
FSImageField.template = "fs_image.FSImageField";
registry.category("fields").add("fs_image", FSImageField);

View file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="fs_image.FSImageField" owl="1">
<div class="d-inline-block position-relative opacity-trigger-hover">
<div
t-attf-class="position-absolute d-flex justify-content-between w-100 bottom-0 opacity-0 opacity-100-hover {{isMobile ? 'o_mobile_controls' : ''}}"
aria-atomic="true"
t-att-style="sizeStyle"
>
<t t-if="!props.readonly">
<FileUploader
acceptedFileExtensions="props.acceptedFileExtensions"
t-key="props.record.resId"
onUploaded.bind="onFileUploaded"
type="'image'"
>
<t t-set-slot="toggler">
<button
class="o_select_file_button btn btn-light border-0 rounded-circle m-1 p-1"
data-tooltip="Edit"
aria-label="Edit"
>
<i class="fa fa-pencil fa-fw" />
</button>
</t>
<t t-if="props.value and state.isValid">
<button
class="o_alt_text_file_button btn btn-light border-0 rounded-circle m-1 p-1"
data-tooltip="Alt Text"
aria-label="Set Alt Text"
t-on-click="onAltTextEdit"
>
<i class="fa fa-blind fa-fw" />
</button>
<button
class="o_clear_file_button btn btn-light border-0 rounded-circle m-1 p-1"
data-tooltip="Clear"
aria-label="Clear"
t-on-click="onFileRemove"
>
<i class="fa fa-trash-o fa-fw" />
</button>
</t>
</FileUploader>
</t>
</div>
<img
class="img img-fluid w-100"
alt="Binary file"
t-att-src="this.getUrl(props.previewImage or props.name)"
t-att-name="props.name"
t-att-height="props.height"
t-att-width="props.width"
t-att-style="sizeStyle"
t-att-alt="props.alt"
t-on-error.stop="onLoadFailed"
t-att-data-tooltip-template="hasTooltip and tooltipAttributes.template"
t-att-data-tooltip-info="hasTooltip and tooltipAttributes.info"
t-att-data-tooltip-delay="hasTooltip and props.zoomDelay"
/>
<button
t-if="props.value and state.isValid"
class="fs_file_download_button btn btn-light border-0 rounded-circle m-1 p-1 translate-middle opacity-0 opacity-100-hover"
data-tooltip="Download"
aria-label="Download"
t-on-click="onFileDownload"
>
<i class="fa fa-download fa-fw" />
</button>
</div>
</t>
</templates>