mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-12-22 16:21:52 +00:00
Added map visualizer
This commit is contained in:
parent
07f7782ae4
commit
f25be16d1e
@ -53,5 +53,10 @@ return [
|
||||
'switch' => [
|
||||
'web-analytics' => false,
|
||||
'blog-domain-mode' => 3
|
||||
],
|
||||
'tools' => [
|
||||
// 请仅在https下启用以下功能.
|
||||
// 非https下, chrome无法进行复制.
|
||||
'map-copy-enabled' => false,
|
||||
]
|
||||
];
|
||||
|
132
web/app/controllers/map_visualizer.php
Normal file
132
web/app/controllers/map_visualizer.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
$REQUIRE_LIB['shjs'] = "";
|
||||
$REQUIRE_LIB['dracula'] = "";
|
||||
$REQUIRE_LIB['base64'] = "";
|
||||
$REQUIRE_LIB['raphael'] = "";
|
||||
echoUOJPageHeader("图可视化");
|
||||
|
||||
?>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="directed" name="directed">
|
||||
<label class="form-check-label" for="directed">
|
||||
是否有向
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" id="edges" rows="3" placeholder="格式:
|
||||
第一条边起点 第一条边终点 [第一条边权重]
|
||||
第二条边起点 第二条边终点 [第二条边权重]
|
||||
第三条边起点 第三条边终点 [第三条边权重]
|
||||
如:
|
||||
1 2
|
||||
2 3
|
||||
1 3 2
|
||||
"></textarea>
|
||||
</div>
|
||||
<?php if (UOJConfig::$data['tools']['map-copy-enabled']): ?>
|
||||
<button type="button" class="btn btn-secondary" id="copy">复制图片</button>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<div class="col-md-9" id="paper">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
<?php if (UOJConfig::$data['tools']['map-copy-enabled']): ?>
|
||||
$("#copy").click(function () {
|
||||
if(navigator.permissions) {
|
||||
navigator.permissions.query({name: "clipboard-write"}).then(result => {
|
||||
if (result.state === "granted" || result.state === "prompt") {
|
||||
let paper = $("#paper")
|
||||
// First, we create an canvas and a img which has the same size with the canvas.
|
||||
let canvas = document.createElement('canvas');
|
||||
let s_img = document.createElement('img');
|
||||
s_img.height = paper.height()
|
||||
s_img.width = paper.width()
|
||||
canvas.height = paper.height()
|
||||
canvas.width = paper.width()
|
||||
// After this image is loaded, we draw it on our canvas
|
||||
s_img.onload = function () {
|
||||
// On some browsers, the exported png have a black background.
|
||||
// So we will draw a white background first.
|
||||
ctx = canvas.getContext('2d');
|
||||
ctx.beginPath();
|
||||
ctx.rect(0, 0, paper.width(), paper.height());
|
||||
ctx.fillStyle = "white";
|
||||
ctx.fill();
|
||||
// We put our image created from the svg to the canvas
|
||||
canvas.getContext('2d').drawImage(s_img, 0, 0);
|
||||
// Then we export canvas as a png file, paste it to clipboard.
|
||||
canvas.toBlob(function (blob) {
|
||||
const item = new ClipboardItem({[blob.type]: blob});
|
||||
navigator.clipboard.write([item]).then(function () {
|
||||
alert("图片已经复制到剪切板中");
|
||||
}, function (err) {
|
||||
alert(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
// Load our svg to this image.
|
||||
s_img.src = "data:image/svg+xml;base64," + Base64.encode(new XMLSerializer().serializeToString(document.querySelector("#paper > svg")));
|
||||
} else {
|
||||
alert("获取剪切板权限失败, 请打开网页设置并授予剪切板权限!")
|
||||
}
|
||||
});
|
||||
} else {
|
||||
alert("获取剪切板权限失败, 请使用最新Chrome浏览器, 打开网页设置并授予剪切板权限!")
|
||||
}
|
||||
})
|
||||
<?php endif ?>
|
||||
let repaint = function () {
|
||||
let paper = $("#paper");
|
||||
// Clear existing elements.
|
||||
paper.html("");
|
||||
let directed = $('#directed').is(":checked");
|
||||
let Graph = Dracula.Graph;
|
||||
let Renderer = Dracula.Renderer.Raphael;
|
||||
let Layout = Dracula.Layout.Spring;
|
||||
let graph = new Graph();
|
||||
let render = function(r, n) {
|
||||
let color = Raphael.getColor();
|
||||
return r.set()
|
||||
.push(
|
||||
r.ellipse(0, 0, 30, 20).attr({stroke: color, "stroke-width": 2, fill: color, "fill-opacity": 0})
|
||||
)
|
||||
.push(r.text(0, 0, n.id).attr({ opacity: 1, 'font-size': 20,}));
|
||||
}
|
||||
$("#edges").val().split("\n").forEach(function (edge) {
|
||||
if (edge.split(" ").length >= 2) {
|
||||
if (edge.split(" ")[0].length !== 0 && edge.split(" ")[1].length !== 0 ){
|
||||
graph.addNode(edge.split(" ")[0], {
|
||||
render: render
|
||||
});
|
||||
graph.addNode(edge.split(" ")[1], {
|
||||
render: render
|
||||
});
|
||||
graph.addEdge(edge.split(" ")[0], edge.split(" ")[1], {
|
||||
directed: directed,
|
||||
label:edge.split(" ")[2],
|
||||
'label-style' : {
|
||||
'font-size': 20,
|
||||
"fill-opacity":"1"
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
var layout = new Layout(graph)
|
||||
var renderer = new Renderer('#paper', graph, paper.width(), paper.height())
|
||||
renderer.draw()
|
||||
}
|
||||
$("#edges").on("input", repaint)
|
||||
$("#directed").on("input", repaint)
|
||||
})()
|
||||
</script>
|
||||
|
||||
<?php
|
||||
echoUOJPageFooter();
|
@ -67,6 +67,8 @@ Route::group([
|
||||
|
||||
Route::any('/paste', '/paste_post.php');
|
||||
Route::any('/pastes/{rand_str_id}', '/paste_view.php');
|
||||
|
||||
Route::any('/map_visualizer', '/map_visualizer.php');
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -11,6 +11,16 @@
|
||||
<li class="nav-item"><a class="nav-link" href="<?= HTML::url('/hacks') ?>"><span class="glyphicon glyphicon-flag"></span> <?= UOJLocale::get('hacks') ?></a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="<?= HTML::blog_list_url() ?>"><span class="glyphicon glyphicon-edit"></span> <?= UOJLocale::get('blogs') ?></a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="<?= HTML::url('/faq') ?>"><span class="glyphicon glyphicon-info-sign"></span> <?= UOJLocale::get('help') ?></a></li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="toolsDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-wrench"></span>
|
||||
工具
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="<?= HTML::url('/paste') ?>"><span class="glyphicon glyphicon-paste"></span> 代码分享 </a>
|
||||
<a class="dropdown-item" href="<?= HTML::url('/map_visualizer') ?>"><span class="glyphicon glyphicon-retweet"></span> 图可视化 </a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<form id="form-search-problem" class="form-inline my-2 my-lg-0" method="get">
|
||||
<div class="input-group">
|
||||
|
@ -204,6 +204,16 @@
|
||||
<?= HTML::js_src('/js/ckeditor/ckeditor.js') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (isset($REQUIRE_LIB['dracula'])): ?>
|
||||
<!-- dracula.js -->
|
||||
<?= HTML::js_src('/js/dracula.min.js') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (isset($REQUIRE_LIB['base64'])): ?>
|
||||
<!-- base64.js -->
|
||||
<?= HTML::js_src('/js/base64.min.js') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
||||
|
8
web/js/base64.min.js
vendored
Normal file
8
web/js/base64.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Minified by jsDelivr using Terser v3.14.1.
|
||||
* Original file: /npm/js-base64@3.4.4/base64.js
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):function(){const r=e.Base64,n=t();n.noConflict=(()=>(e.Base64=r,n)),e.Meteor&&(Base64=n),e.Base64=n}()}("undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:this,function(){"use strict";const e="function"==typeof atob,t="function"==typeof btoa,r="function"==typeof Buffer,n=[..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="],o=(e=>{let t={};return n.forEach((e,r)=>t[e]=r),t})(),a=/^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/,f=String.fromCharCode.bind(String),i="function"==typeof Uint8Array.from?Uint8Array.from.bind(Uint8Array):(e,t=(e=>e))=>new Uint8Array(Array.prototype.slice.call(e,0).map(t)),c=e=>e.replace(/[+\/]/g,e=>"+"==e?"-":"_").replace(/=+$/m,""),u=e=>e.replace(/[^A-Za-z0-9\+\/]/g,""),s=e=>{let t,r,o,a,f="";const i=e.length%3;for(let i=0;i<e.length;){if((r=e.charCodeAt(i++))>255||(o=e.charCodeAt(i++))>255||(a=e.charCodeAt(i++))>255)throw new TypeError("invalid character found");f+=n[(t=r<<16|o<<8|a)>>18&63]+n[t>>12&63]+n[t>>6&63]+n[63&t]}return i?f.slice(0,i-3)+"===".substring(i):f},l=t?e=>btoa(e):r?e=>Buffer.from(e,"binary").toString("base64"):s,d=r?e=>Buffer.from(e).toString("base64"):e=>{let t=[];for(let r=0,n=e.length;r<n;r+=4096)t.push(f.apply(null,e.subarray(r,r+4096)));return btoa(t.join(""))},p=(e,t=!1)=>t?c(d(e)):d(e),y=e=>unescape(encodeURIComponent(e)),b=r?e=>Buffer.from(e,"utf8").toString("base64"):e=>l(y(e)),h=(e,t=!1)=>t?c(b(e)):b(e),B=e=>h(e,!0),g=e=>decodeURIComponent(escape(e)),m=e=>{if(e=e.replace(/\s+/g,""),!a.test(e))throw new TypeError("malformed base64.");e+="==".slice(2-(3&e.length));let t,r,n,i="";for(let a=0;a<e.length;)t=o[e.charAt(a++)]<<18|o[e.charAt(a++)]<<12|(r=o[e.charAt(a++)])<<6|(n=o[e.charAt(a++)]),i+=64===r?f(t>>16&255):64===n?f(t>>16&255,t>>8&255):f(t>>16&255,t>>8&255,255&t);return i},A=e?e=>atob(u(e)):r?e=>Buffer.from(e,"base64").toString("binary"):m,U=r?e=>Buffer.from(e,"base64").toString("utf8"):e=>g(A(e)),w=e=>u(e.replace(/[-_]/g,e=>"-"==e?"+":"/")),S=e=>U(w(e)),C=r?e=>i(Buffer.from(w(e),"base64")):e=>i(A(w(e)),e=>e.charCodeAt(0)),R=e=>({value:e,enumerable:!1,writable:!0,configurable:!0}),I=function(){const e=(e,t)=>Object.defineProperty(String.prototype,e,R(t));e("fromBase64",function(){return S(this)}),e("toBase64",function(e){return h(this,e)}),e("toBase64URI",function(){return h(this,!0)}),e("toBase64URL",function(){return h(this,!0)}),e("toUint8Array",function(){return C(this)})},j=function(){const e=(e,t)=>Object.defineProperty(Uint8Array.prototype,e,R(t));e("toBase64",function(e){return p(this,e)}),e("toBase64URI",function(){return p(this,!0)}),e("toBase64URL",function(){return p(this,!0)})},x={version:"3.4.4",VERSION:"3.4.4",atob:A,atobPolyfill:m,btoa:l,btoaPolyfill:s,fromBase64:S,toBase64:h,encode:h,encodeURI:B,encodeURL:B,utob:y,btou:g,decode:S,fromUint8Array:p,toUint8Array:C,extendString:I,extendUint8Array:j,extendBuiltins:()=>{I(),j()},Base64:{}};return Object.keys(x).forEach(e=>x.Base64[e]=x[e]),x});
|
||||
//# sourceMappingURL=/sm/d011865b76fc8428bebb88d3ec34fb9679a1de489566eef3d3a5c2171d590667.map
|
1
web/js/dracula.min.js
vendored
Normal file
1
web/js/dracula.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user