folder rename
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
/** @odoo-module **/
|
||||
// Fusion Plating -- 3D CAD Viewer (iframe wrapper)
|
||||
// Copyright 2026 Nexa Systems Inc.
|
||||
// License OPL-1 (Odoo Proprietary License v1.0)
|
||||
//
|
||||
// Simple OWL field widget that embeds the standalone 3D viewer page
|
||||
// in an iframe. The viewer page uses Online3DViewer (o3dv) which
|
||||
// supports STEP, IGES, BREP, STL, OBJ, glTF, and 20+ more formats.
|
||||
|
||||
import { Component, useState } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
|
||||
export class Fp3dViewer extends Component {
|
||||
static template = "fusion_plating_configurator.Fp3dViewer";
|
||||
static props = { ...standardFieldProps };
|
||||
|
||||
setup() {
|
||||
this.state = useState({ hasAttachment: false, iframeSrc: "" });
|
||||
this._updateState();
|
||||
}
|
||||
|
||||
get rawValue() {
|
||||
return this.props.record.data[this.props.name];
|
||||
}
|
||||
|
||||
get attachmentId() {
|
||||
const v = this.rawValue;
|
||||
if (!v) return 0;
|
||||
if (Array.isArray(v)) return v[0] || 0;
|
||||
if (typeof v === "object" && v.id) return v.id;
|
||||
return typeof v === "number" ? v : 0;
|
||||
}
|
||||
|
||||
get attachmentName() {
|
||||
const v = this.rawValue;
|
||||
if (!v) return "";
|
||||
if (Array.isArray(v)) return v[1] || "";
|
||||
if (typeof v === "object" && v.display_name) return v.display_name;
|
||||
return "";
|
||||
}
|
||||
|
||||
_updateState() {
|
||||
const aid = this.attachmentId;
|
||||
this.state.hasAttachment = !!aid;
|
||||
if (aid) {
|
||||
const name = encodeURIComponent(this.attachmentName);
|
||||
this.state.iframeSrc = `/fp/3d-viewer?id=${aid}&name=${name}`;
|
||||
}
|
||||
}
|
||||
|
||||
onPatched() {
|
||||
this._updateState();
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("fields").add("fp_3d_preview", {
|
||||
component: Fp3dViewer,
|
||||
supportedTypes: ["many2one"],
|
||||
});
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// 3D Viewer Dialog component (full-screen embedded viewer)
|
||||
// =============================================================================
|
||||
export class Fp3dViewerDialog extends Component {
|
||||
static template = "fusion_plating_configurator.Fp3dViewerDialog";
|
||||
static components = { Dialog };
|
||||
static props = {
|
||||
attachmentId: Number,
|
||||
name: { type: String, optional: true },
|
||||
close: { type: Function, optional: true },
|
||||
};
|
||||
|
||||
setup() {
|
||||
this.state = useState({ isMaximized: true });
|
||||
}
|
||||
|
||||
get iframeSrc() {
|
||||
const name = encodeURIComponent(this.props.name || "");
|
||||
return `/fp/3d-viewer?id=${this.props.attachmentId}&name=${name}`;
|
||||
}
|
||||
|
||||
get dialogSize() {
|
||||
return this.state.isMaximized ? "fullscreen" : "xl";
|
||||
}
|
||||
|
||||
get frameStyle() {
|
||||
if (this.state.isMaximized) {
|
||||
return "height: calc(98vh - 100px) !important;";
|
||||
}
|
||||
return "height: calc(85vh - 100px) !important;";
|
||||
}
|
||||
|
||||
toggleSize() {
|
||||
this.state.isMaximized = !this.state.isMaximized;
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("dialog").add("Fp3dViewerDialog", Fp3dViewerDialog);
|
||||
|
||||
|
||||
// Client action handler — opens the 3D viewer in a dialog within the same window.
|
||||
// Triggered by Python returning:
|
||||
// { type: 'ir.actions.client', tag: 'fp_3d_viewer_open',
|
||||
// params: { attachment_id: N, name: "..." } }
|
||||
function fp3dViewerOpenAction(env, action) {
|
||||
const params = action.params || {};
|
||||
if (!params.attachment_id) return Promise.resolve();
|
||||
env.services.dialog.add(Fp3dViewerDialog, {
|
||||
attachmentId: params.attachment_id,
|
||||
name: params.name || "",
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
registry.category("actions").add("fp_3d_viewer_open", fp3dViewerOpenAction);
|
||||
@@ -0,0 +1,81 @@
|
||||
/** @odoo-module **/
|
||||
// Fusion Plating -- PDF Drawing Preview Widget
|
||||
// Copyright 2026 Nexa Systems Inc.
|
||||
// License OPL-1
|
||||
//
|
||||
// Custom many2many_binary widget that opens PDFs in the fusion_pdf_preview
|
||||
// dialog instead of downloading them.
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import {
|
||||
Many2ManyBinaryField,
|
||||
many2ManyBinaryField,
|
||||
} from "@web/views/fields/many2many_binary/many2many_binary_field";
|
||||
|
||||
export class FpPdfPreviewBinary extends Many2ManyBinaryField {
|
||||
static template = "fusion_plating_configurator.FpPdfPreviewBinary";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.dialogService = useService("dialog");
|
||||
}
|
||||
|
||||
onFileClick(ev, file) {
|
||||
const isPdf = (file.mimetype === "application/pdf") ||
|
||||
(file.name || "").toLowerCase().endsWith(".pdf");
|
||||
const dialogs = registry.category("dialog");
|
||||
|
||||
if (isPdf && dialogs.contains("PDFViewerDialog")) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const PDFViewerDialog = dialogs.get("PDFViewerDialog");
|
||||
const url = `/web/content/${file.id}?download=false`;
|
||||
this.dialogService.add(PDFViewerDialog, {
|
||||
url: url,
|
||||
title: file.name || "Drawing",
|
||||
reportName: "",
|
||||
recordIds: "",
|
||||
modelName: "ir.attachment",
|
||||
});
|
||||
}
|
||||
// For non-PDF or when preview not available, default browser behavior
|
||||
// (the <a href> with download attribute) kicks in because we don't
|
||||
// prevent default.
|
||||
}
|
||||
}
|
||||
|
||||
export const fpPdfPreviewBinary = {
|
||||
...many2ManyBinaryField,
|
||||
component: FpPdfPreviewBinary,
|
||||
};
|
||||
|
||||
registry.category("fields").add("fp_pdf_preview_binary", fpPdfPreviewBinary);
|
||||
|
||||
|
||||
// Client action handler: open a PDF attachment in the fusion_pdf_preview dialog.
|
||||
// Triggered by Python methods returning:
|
||||
// { type: 'ir.actions.client', tag: 'fp_pdf_preview_open',
|
||||
// params: { attachment_id: N, title: "..." } }
|
||||
function fpPdfPreviewOpenAction(env, action) {
|
||||
const params = action.params || {};
|
||||
const attId = params.attachment_id;
|
||||
if (!attId) return Promise.resolve();
|
||||
const dialogs = registry.category("dialog");
|
||||
const PDFViewerDialog = dialogs.contains("PDFViewerDialog") ? dialogs.get("PDFViewerDialog") : null;
|
||||
if (!PDFViewerDialog) {
|
||||
window.open(`/web/content/${attId}?download=false`, '_blank');
|
||||
return Promise.resolve();
|
||||
}
|
||||
const url = `/web/content/${attId}?download=false`;
|
||||
env.services.dialog.add(PDFViewerDialog, {
|
||||
url: url,
|
||||
title: params.title || 'Document',
|
||||
reportName: '',
|
||||
recordIds: '',
|
||||
modelName: 'ir.attachment',
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
registry.category("actions").add("fp_pdf_preview_open", fpPdfPreviewOpenAction);
|
||||
@@ -0,0 +1,80 @@
|
||||
/** @odoo-module **/
|
||||
// Fusion Plating -- Inline PDF Preview field widget
|
||||
// Copyright 2026 Nexa Systems Inc.
|
||||
// License OPL-1
|
||||
//
|
||||
// Field widget for Many2one(ir.attachment) fields that embeds the
|
||||
// PDF.js viewer inline at a fixed height (one page at a time).
|
||||
// A "Full Screen" button below opens the fusion_pdf_preview dialog.
|
||||
|
||||
import { Component, useState } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
|
||||
export class FpPdfInlinePreview extends Component {
|
||||
static template = "fusion_plating_configurator.FpPdfInlinePreview";
|
||||
static props = { ...standardFieldProps };
|
||||
|
||||
setup() {
|
||||
this.dialogService = useService("dialog");
|
||||
this.state = useState({ hasAttachment: false, iframeSrc: "", attId: 0, name: "" });
|
||||
this._updateState();
|
||||
}
|
||||
|
||||
get rawValue() {
|
||||
return this.props.record.data[this.props.name];
|
||||
}
|
||||
|
||||
_updateState() {
|
||||
const v = this.rawValue;
|
||||
let attId = 0;
|
||||
let name = "";
|
||||
if (v) {
|
||||
if (Array.isArray(v)) {
|
||||
attId = v[0] || 0;
|
||||
name = v[1] || "";
|
||||
} else if (typeof v === "object" && v.id) {
|
||||
attId = v.id;
|
||||
name = v.display_name || "";
|
||||
} else if (typeof v === "number") {
|
||||
attId = v;
|
||||
}
|
||||
}
|
||||
this.state.hasAttachment = !!attId;
|
||||
this.state.attId = attId;
|
||||
this.state.name = name;
|
||||
if (attId) {
|
||||
const fileUrl = `/web/content/${attId}?download=false`;
|
||||
// PDF.js URL params: zoom=page-fit, no thumbs sidebar, single-page mode
|
||||
this.state.iframeSrc =
|
||||
`/fusion_pdf_preview/static/lib/pdfjs/web/viewer.html` +
|
||||
`?file=${encodeURIComponent(fileUrl)}` +
|
||||
`#zoom=page-fit&pagemode=none&scrollmode=3`;
|
||||
}
|
||||
}
|
||||
|
||||
onPatched() {
|
||||
this._updateState();
|
||||
}
|
||||
|
||||
openFullScreen() {
|
||||
if (!this.state.attId) return;
|
||||
const dialogs = registry.category("dialog");
|
||||
if (!dialogs.contains("PDFViewerDialog")) return;
|
||||
const PDFViewerDialog = dialogs.get("PDFViewerDialog");
|
||||
const url = `/web/content/${this.state.attId}?download=false`;
|
||||
this.dialogService.add(PDFViewerDialog, {
|
||||
url: url,
|
||||
title: this.state.name || "Drawing",
|
||||
reportName: "",
|
||||
recordIds: "",
|
||||
modelName: "ir.attachment",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("fields").add("fp_pdf_inline_preview", {
|
||||
component: FpPdfInlinePreview,
|
||||
supportedTypes: ["many2one"],
|
||||
});
|
||||
Reference in New Issue
Block a user