Initial Commit
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0"?>
|
||||
<info>
|
||||
<id>kicanvasviewer</id>
|
||||
<name>KiCanvas Viewer</name>
|
||||
<summary>View KiCad files with KiCanvas</summary>
|
||||
<description>Opens KiCad schematic and PCB files using KiCanvas.</description>
|
||||
<version>0.1.0</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Herwig Birke</author>
|
||||
<namespace>KiCanvasViewer</namespace>
|
||||
<category>files</category>
|
||||
<dependencies>
|
||||
<nextcloud min-version="33" max-version="33"/>
|
||||
</dependencies>
|
||||
</info>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'routes' => [
|
||||
[
|
||||
'name' => 'viewer#view',
|
||||
'url' => '/view/{fileId}',
|
||||
'verb' => 'GET',
|
||||
],
|
||||
[
|
||||
'name' => 'viewer#file',
|
||||
'url' => '/file/{fileId}/{name}',
|
||||
'verb' => 'GET',
|
||||
],
|
||||
[
|
||||
'name' => 'viewer#resolve',
|
||||
'url' => '/resolve/{etag}',
|
||||
'verb' => 'GET',
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,60 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;500;600;700&display=swap");
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100vh;
|
||||
font-family:
|
||||
"Nunito", ui-rounded, "Hiragino Maru Gothic ProN", Quicksand, Comfortaa,
|
||||
Manjari, "Arial Rounded MT Bold", Calibri, source-sans-pro, sans-serif;
|
||||
line-height: 1.5;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
img,
|
||||
picture,
|
||||
video,
|
||||
canvas,
|
||||
svg {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1024px;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* Styles for KiCanvas */
|
||||
|
||||
kicanvas-embed {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
aspect-ratio: 1.414;
|
||||
background-color: aqua;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
kc-kicanvas-shell {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
||||
fill="#fff" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 984 B |
Binary file not shown.
|
After Width: | Height: | Size: 303 KiB |
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
/*! For license information please see viewerhandler.js.LICENSE.txt */
|
||||
(()=>{"use strict";var e;(function(e){const{id:i,mimes:r,mimesAliases:a,component:n}=e;if(!i||""===i.trim()||"string"!=typeof i)throw new Error("The handler does not have a valid id");if(!(r&&Array.isArray(r)||a))throw new Error("Handler needs a valid mime array or mimesAliases");if(!n||"object"!=typeof n&&"function"!=typeof n)throw new Error("The handler does not have a valid component")})(e={id:"kicanvasviewer",group:"kicanvasviewer",mimes:["application/x-kicad-schematic","application/x-kicad-pcb"],component:{name:"KiCanvasInlineViewer",data:()=>({src:null,error:null,ready:!1,started:!1}),async mounted(){if(this.started)return;this.started=!0;const e=this.$attrs?.etag;if(e)try{const i=await fetch(OC.generateUrl("/apps/kicanvasviewer/resolve/"+encodeURIComponent(e)));if(!i.ok)return void(this.error="KiCanvas: could not resolve file");const r=await i.json();if(!r.fileId||!r.fileName)return void(this.error="KiCanvas: resolve returned no fileId/fileName");this.src=window.location.origin+OC.generateUrl("/apps/kicanvasviewer/file/"+encodeURIComponent(r.fileId)+"/"+encodeURIComponent(r.fileName)),await new Promise((e,i)=>{if(customElements.get("kc-kicanvas-shell"))return void e();const r=document.createElement("script");r.type="module",r.src=OC.linkTo("kicanvasviewer","js/kicanvas/kicanvas.js"),r.onload=e,r.onerror=i,document.head.appendChild(r)}),this.ready=!0,this.$forceUpdate()}catch(e){console.error(e),this.error="KiCanvas error"}else this.error="KiCanvas: no etag found"},render(e){return this.error?e("div",{style:{padding:"40px",fontSize:"20px"}},this.error):this.ready&&this.src?e("div",{style:{position:"absolute",inset:"0",zIndex:100,background:"var(--color-main-background)"}},[e("kc-kicanvas-shell",{key:this.src,attrs:{src:this.src},style:{display:"block",width:"100%",height:"100%"}})]):e("div",{style:{padding:"40px",fontSize:"20px"}},"Loading KiCanvas...")}}}),window._oca_viewer_handlers??(window._oca_viewer_handlers=new Map),window._oca_viewer_handlers.has(e.id)?console.warn(`Handler with id ${e.id} is already registered.`):window._oca_viewer_handlers.set(e.id,e)})();
|
||||
@@ -0,0 +1,4 @@
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\KiCanvasViewer\AppInfo;
|
||||
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||
use OCA\Files\Event\LoadAdditionalScriptsEvent;
|
||||
use OCP\Util;
|
||||
|
||||
class Application extends App implements IBootstrap {
|
||||
public const APP_ID = 'kicanvasviewer';
|
||||
|
||||
public function __construct(array $urlParams = []) {
|
||||
parent::__construct(self::APP_ID, $urlParams);
|
||||
}
|
||||
|
||||
public function register(IRegistrationContext $context): void {
|
||||
$context->registerEventListener(
|
||||
LoadAdditionalScriptsEvent::class,
|
||||
LoadAdditionalScriptsListener::class
|
||||
);
|
||||
}
|
||||
|
||||
public function boot(IBootContext $context): void {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\KiCanvasViewer\AppInfo;
|
||||
|
||||
use OCA\Files\Event\LoadAdditionalScriptsEvent;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\Util;
|
||||
|
||||
class LoadAdditionalScriptsListener implements IEventListener {
|
||||
public function handle(Event $event): void {
|
||||
if (!$event instanceof LoadAdditionalScriptsEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
Util::addScript('kicanvasviewer', 'viewerhandler');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\KiCanvasViewer\Controller;
|
||||
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Http\DataDisplayResponse;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserSession;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
|
||||
class ViewerController extends Controller {
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
private IRootFolder $rootFolder,
|
||||
private IUserSession $userSession,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
||||
#[NoAdminRequired]
|
||||
#[NoCSRFRequired]
|
||||
public function view(int $fileId): TemplateResponse {
|
||||
$file = $this->getFileById($fileId);
|
||||
$name = $file->getName();
|
||||
|
||||
return new TemplateResponse('kicanvasviewer', 'viewer', [
|
||||
'fileId' => $fileId,
|
||||
'fileName' => $name,
|
||||
]);
|
||||
}
|
||||
|
||||
#[NoAdminRequired]
|
||||
#[NoCSRFRequired]
|
||||
public function file(int $fileId, string $name): DataDisplayResponse {
|
||||
$file = $this->getFileById($fileId);
|
||||
|
||||
if (!preg_match('/\.(kicad_sch|kicad_pcb)$/i', $file->getName())) {
|
||||
return new DataDisplayResponse('Unsupported file type', 400);
|
||||
}
|
||||
|
||||
$response = new DataDisplayResponse($file->getContent());
|
||||
$response->addHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||
$response->addHeader('Content-Disposition', 'inline; filename="' . basename($file->getName()) . '"');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function getFileById(int $fileId) {
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
throw new \Exception('Not logged in');
|
||||
}
|
||||
|
||||
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
|
||||
$nodes = $userFolder->getById($fileId);
|
||||
|
||||
if (empty($nodes)) {
|
||||
throw new \Exception('File not found');
|
||||
}
|
||||
|
||||
return $nodes[0];
|
||||
}
|
||||
|
||||
#[NoAdminRequired]
|
||||
#[NoCSRFRequired]
|
||||
public function resolve(string $etag): JSONResponse {
|
||||
$user = $this->userSession->getUser();
|
||||
|
||||
if ($user === null) {
|
||||
return new JSONResponse(['error' => 'not logged in'], 401);
|
||||
}
|
||||
|
||||
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
|
||||
|
||||
$nodes = array_merge(
|
||||
$userFolder->searchByMime('application/x-kicad-schematic'),
|
||||
$userFolder->searchByMime('application/x-kicad-pcb')
|
||||
);
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
if ($node->getEtag() === $etag) {
|
||||
return new JSONResponse([
|
||||
'fileId' => $node->getId(),
|
||||
'fileName' => $node->getName(),
|
||||
'mime' => $node->getMimeType(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return new JSONResponse(['error' => 'not found'], 404);
|
||||
}
|
||||
}
|
||||
Generated
+2125
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "webpack --mode production"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextcloud/files": "^4.0.0",
|
||||
"@nextcloud/viewer": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import { registerHandler } from '@nextcloud/viewer'
|
||||
|
||||
function loadKiCanvasScript() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (customElements.get('kc-kicanvas-shell')) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
const script = document.createElement('script')
|
||||
script.type = 'module'
|
||||
script.src = OC.linkTo('kicanvasviewer', 'js/kicanvas/kicanvas.js')
|
||||
script.onload = resolve
|
||||
script.onerror = reject
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
}
|
||||
|
||||
const KiCanvasInlineViewer = {
|
||||
name: 'KiCanvasInlineViewer',
|
||||
|
||||
data() {
|
||||
return {
|
||||
src: null,
|
||||
error: null,
|
||||
ready: false,
|
||||
started: false,
|
||||
}
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
if (this.started) {
|
||||
return
|
||||
}
|
||||
this.started = true
|
||||
|
||||
const etag = this.$attrs?.etag
|
||||
if (!etag) {
|
||||
this.error = 'KiCanvas: no etag found'
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
OC.generateUrl('/apps/kicanvasviewer/resolve/' + encodeURIComponent(etag))
|
||||
)
|
||||
|
||||
if (!res.ok) {
|
||||
this.error = 'KiCanvas: could not resolve file'
|
||||
return
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
if (!data.fileId || !data.fileName) {
|
||||
this.error = 'KiCanvas: resolve returned no fileId/fileName'
|
||||
return
|
||||
}
|
||||
|
||||
this.src = window.location.origin + OC.generateUrl(
|
||||
'/apps/kicanvasviewer/file/' +
|
||||
encodeURIComponent(data.fileId) +
|
||||
'/' +
|
||||
encodeURIComponent(data.fileName)
|
||||
)
|
||||
|
||||
await loadKiCanvasScript()
|
||||
|
||||
this.ready = true
|
||||
this.$forceUpdate()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.error = 'KiCanvas error'
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
if (this.error) {
|
||||
return h('div', {
|
||||
style: {
|
||||
padding: '40px',
|
||||
fontSize: '20px',
|
||||
},
|
||||
}, this.error)
|
||||
}
|
||||
|
||||
if (!this.ready || !this.src) {
|
||||
return h('div', {
|
||||
style: {
|
||||
padding: '40px',
|
||||
fontSize: '20px',
|
||||
},
|
||||
}, 'Loading KiCanvas...')
|
||||
}
|
||||
|
||||
return h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
inset: '0',
|
||||
zIndex: 100,
|
||||
background: 'var(--color-main-background)',
|
||||
},
|
||||
}, [
|
||||
h('kc-kicanvas-shell', {
|
||||
key: this.src,
|
||||
attrs: {
|
||||
src: this.src,
|
||||
},
|
||||
style: {
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
}),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
registerHandler({
|
||||
id: 'kicanvasviewer',
|
||||
group: 'kicanvasviewer',
|
||||
mimes: [
|
||||
'application/x-kicad-schematic',
|
||||
'application/x-kicad-pcb',
|
||||
],
|
||||
component: KiCanvasInlineViewer,
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
script('kicanvasviewer', 'kicanvas/kicanvas');
|
||||
style('kicanvasviewer', 'viewer');
|
||||
|
||||
$fileId = $_['fileId'];
|
||||
$fileName = $_['fileName'];
|
||||
|
||||
$src = \OC::$server->getURLGenerator()->linkToRoute(
|
||||
'kicanvasviewer.viewer.file',
|
||||
[
|
||||
'fileId' => $fileId,
|
||||
'name' => $fileName,
|
||||
]
|
||||
);
|
||||
?>
|
||||
|
||||
<div id="kicanvasviewer" style="width:100%; height:calc(100vh - 50px); min-height:800px;">
|
||||
<kc-kicanvas-shell
|
||||
id="kicanvas"
|
||||
style="display:block; width:100%; height:100%; min-height:800px;"
|
||||
src="<?= htmlspecialchars($src, ENT_QUOTES) ?>"></kc-kicanvas-shell>
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
viewerhandler: './src/viewerhandler.js',
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'js'),
|
||||
filename: '[name].js',
|
||||
clean: false,
|
||||
},
|
||||
resolve: {
|
||||
fallback: {
|
||||
stream: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user