1
0
mirror of https://github.com/ToxicCrack/PrintABrick.git synced 2025-05-16 20:30:09 -07:00

Merged branch enhance/improve-loader into dev

This commit is contained in:
David Hübner 2017-04-16 22:23:07 +02:00
commit 9eb99a32ea
124 changed files with 6247 additions and 1818 deletions

1
.gitignore vendored
View File

@ -18,4 +18,5 @@
/node_modules/
/app/Resources/libs/semantic/dist
/web/resources/
/web/media/
.php_cs.cache

View File

@ -30,6 +30,7 @@ For full requirements see Symfony 3.2 [docs](http://symfony.com/doc/3.2/referenc
#### Database
1. Set application parameters in *app/config/parameters.yml*
2. Generate empty database by running command `$ php bin/console doctrine:database:create`
3. Load LDraw models into database by running commad `$ php bin/console app:load:ldraw [ldraw_dir_path]`
3. Create database tables by running command `$ bin/console doctrine:schema:create`
3. Load LDraw models into database by running commad `$ php bin/console app:load:models <ldraw_dir> [--all] [--file=FILE] [--update]`
4. Load Rebrickable data into database by running command `$ php bin/console app:load:rebrickable`
5. Load relations between LDraw models and Rebrickable parts by running command `$ php bin/console app:load:relation`

View File

@ -20,6 +20,7 @@ class AppKernel extends Kernel
new Oneup\FlysystemBundle\OneupFlysystemBundle(),
new Knp\Bundle\PaginatorBundle\KnpPaginatorBundle(),
new Lexik\Bundle\FormFilterBundle\LexikFormFilterBundle(),
new Liip\ImagineBundle\LiipImagineBundle(),
];
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,85 +1,277 @@
ModelViewer = function() {
var container;
var camera, cameraTarget, scene, renderer, controls, object;
var width, height;
var ModelViewer = function($dom_element, model_url) {
var $this = this;
this.container = document.createElement('div');
this.container.setAttribute('class','model-view');
this.dom_element = $dom_element;
$dom_element.append(this.container);
this.initScene = function($container) {
width = parseFloat($container.width());
height = parseFloat($container.height());
this.camera = null;
this.scene = null;
this.renderer = null;
this.controls = null;
this.width = parseFloat($dom_element.width());
this.height = parseFloat($dom_element.height());
this.scale = 1;
this.wireframe = false;
this.rendering = false;
this.container.style.display = "none";
this.toggleButton = null;
this.background = 0xffffff;
this.model_url = model_url;
this.loaded = false;
camera = new THREE.PerspectiveCamera(45, width/height, 0.1, 1000);
camera.position.set(-2, 2, 0.8);
camera.lookAt(new THREE.Vector3(0, 3, 0));
this.object = null;
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x000000, 0.001);
// var grid = new THREE.GridHelper( 30, 70 );
// // grid.position.set(30/70,-0.5,30/70);
// scene.add( grid );
// Lights
light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
light = new THREE.DirectionalLight( 0x002288 );
light.position.set( -1, -1, -1 );
scene.add( light );
this.initHtml();
this.initScene();
scene.add( new THREE.AmbientLight( 0xf0f0f0 ));
scene.background = new THREE.Color( 0x000000 );
// this.stats = new Stats();
// this.stats.dom.style.position = 'absolute';
// this.container.append(this.stats.dom);
// renderer
renderer = new THREE.WebGLRenderer();
renderer.setClearColor( scene.fog.color );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( width, height );
renderer.gammaInput = true;
renderer.gammaOutput = true;
renderer.shadowMap.enabled = true;
renderer.shadowMap.renderReverseSided = false;
$container.append(renderer.domElement);
function renderLoop() {
requestAnimationFrame(renderLoop);
if($this.rendering) {
if(!$this.loaded) {
$this.loadStl($this.model_url);
$this.loaded = true;
}
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.addEventListener( 'change', this.render ); // add this only if there is no animation loop (requestAnimationFrame)
// controls.enableDamping = true;
// controls.dampingFactor = 0.25;
controls.enableZoom = true;
};
if($this.stats) {
$this.stats.update();
}
$this.render();
}
this.loadStl = function(model) {
var loader = new THREE.STLLoader();
loader.load(model, function (geometry) {
var material = new THREE.MeshPhongMaterial({color: 0xaaaaaa, shininess:200, specular: 0x333333, shading: THREE.FlatShading});
$this.resize(parseInt($this.dom_element.width()), parseInt($this.dom_element.height()));
}
renderLoop();
};
ModelViewer.prototype.initHtml = function () {
$this = this;
var buttons = document.createElement("div");
buttons.setAttribute("class", "modelviewer-buttons");
this.wireframeButton = $('<button/>', {
'class':'model',
'style':'display:none',
'html':'<i class="eye icon"/>Wireframe',
'click': $this.toggleMaterial.bind($this)
}).appendTo(buttons);
this.toggleButton = $('<button/>', {
'class':'toggle',
'html':'<i class="cube icon"/>3D',
'click': $this.toggleRendering.bind($this)
}).appendTo(buttons);
this.dom_element.append(buttons);
};
ModelViewer.prototype.initCamera = function () {
this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, .1, 300);
this.camera.position.z = 80;
this.camera.position.y = -45;
this.camera.position.x = 35;
this.camera.up = new THREE.Vector3(0, 0, 1);
};
ModelViewer.prototype.initScene = function() {
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color( this.background );
this.scene.fog = new THREE.FogExp2(this.background, 0.06);
this.initLights();
this.initCamera();
var groundPlaneMaterial = new THREE.MeshPhongMaterial({
color: 0xFFFFFF,
wireframe: false,
transparent: true,
opacity: 0.25,
fog: false,
specular: 0x999999,
shininess: 100
});
this.plane = new THREE.Mesh(new THREE.PlaneGeometry(80,80), groundPlaneMaterial);
this.plane.receiveShadow = true;
this.scene.add(this.plane);
this.grid = new THREE.GridHelper( 80, 100, 0x000000, 0xAAAAAA);
this.grid.rotation.x = Math.PI/2;
this.scene.add(this.grid);
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize(this.width, this.height);
this.renderer.setPixelRatio(window.devicePixelRatio ? window.devicePixelRatio : 1);
this.container.append(this.renderer.domElement);
this.controls = new THREE.OrbitControls( this.camera, this.renderer.domElement );
this.controls.enableZoom = true;
};
ModelViewer.prototype.initLights = function () {
this.spotLight = new THREE.SpotLight(0xffffff, 0.8, 0);
this.spotLight.position.set(-100, 100, 100);
this.spotLight.castShadow = false;
this.scene.add(this.spotLight);
this.bottomSpotLight = new THREE.SpotLight(0xffffff, 0.8, 0);
this.bottomSpotLight.position.set(0, -10, -100);
this.bottomSpotLight.castShadow = false;
this.scene.add(this.bottomSpotLight);
this.ambientLight = new THREE.AmbientLight(0xffffff, 0.1);
this.scene.add(this.ambientLight);
this.pointLight = new THREE.PointLight(0xfdfdfd, 1, 0);
this.pointLight.position.set(32, -39, 35);
this.pointLight.castShadow = true;
this.scene.add(this.pointLight);
};
ModelViewer.prototype.addModel = function(geometry) {
var material = new THREE.MeshPhongMaterial({
color: 0x136fc3,
specular: 0x0D0D0D,
shading: THREE.SmoothShading,
shininess: 30,
fog: false,
side: THREE.DoubleSide,
wireframe: this.wireframe,
});
geometry.center();
var mesh = new THREE.Mesh(geometry, material);
mesh.scale.set(this.scale, this.scale, this.scale);
mesh.geometry.computeFaceNormals();
mesh.geometry.computeVertexNormals();
mesh.rotation.set(-Math.PI/2,0, 0);
mesh.geometry.computeBoundingBox();
var positionY = (mesh.geometry.boundingBox.max.y + mesh.geometry.boundingBox.min.y)/2;
// mesh.position.set(0, positionY, 0);
mesh.rotation.set(Math.PI, 0, 0);
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
// mesh.receiveShadow = true;
renderer.render(scene, camera);
});
};
var dims = mesh.geometry.boundingBox.max.clone().sub(mesh.geometry.boundingBox.min);
this.animate = function() {
maxDim = Math.max(Math.max(dims.x, dims.y), dims.z);
requestAnimationFrame( this.animate );
this.render();
};
mesh.position.x = -(mesh.geometry.boundingBox.min.x + mesh.geometry.boundingBox.max.x) / 2 * this.scale;
mesh.position.z = -(mesh.geometry.boundingBox.min.y + mesh.geometry.boundingBox.max.y) / 2 * this.scale;
mesh.position.y = -mesh.geometry.boundingBox.min.z * this.scale;
this.render = function() {
var positionY = (mesh.geometry.boundingBox.max.z + mesh.geometry.boundingBox.min.z)/2 * this.scale;
var positionZ = (mesh.geometry.boundingBox.max.y - mesh.geometry.boundingBox.min.y)/2 * this.scale;
renderer.render(scene, camera);
mesh.position.set(0, positionY, positionZ);
};
this.object = mesh;
this.scene.add(mesh);
this.centerCamera(mesh);
};
ModelViewer.prototype.loadStl = function(model) {
var self = this;
var loader = new THREE.STLLoader();
loader.load(model, function (geometry) {
self.addModel(geometry);
}
);
};
ModelViewer.prototype.centerCamera = function(mesh) {
var boxHelper = new THREE.BoxHelper( mesh );
var sceneCenter = this.objectCenter(mesh);
var geometry = mesh.geometry;
var distanceX = ((geometry.boundingBox.max.x - geometry.boundingBox.min.x) / 2) / Math.tan(this.camera.fov * this.camera.aspect * Math.PI / 360);
var distanceY = (geometry.boundingBox.max.y - geometry.boundingBox.min.y) / 2 / Math.tan(this.camera.fov * this.camera.aspect * Math.PI / 360);
var distanceZ = (geometry.boundingBox.max.z - geometry.boundingBox.min.z) / 2 / Math.tan(this.camera.fov * Math.PI / 360);
var maxDistance = Math.max(Math.max(distanceX, distanceY), distanceZ);
maxDistance *= 1.9 * this.scale;
var cameraPosition = this.camera.position.normalize().multiplyScalar(maxDistance);
this.controls.maxDistance = 3 * maxDistance;
this.controls.position0 = cameraPosition;
this.controls.target0 = sceneCenter;
this.controls.reset();
};
ModelViewer.prototype.toggleRendering = function () {
if($this.rendering) {
$this.container.style.display = "none";
$this.rendering = false;
$this.toggleButton.html('<i class="cube icon"/>3D');
$this.wireframeButton.hide();
} else {
$this.container.style.display = "block";
$this.rendering = true;
$this.toggleButton.html('<i class="close icon"/>Close');
$this.wireframeButton.show();
}
};
ModelViewer.prototype.toggleMaterial = function () {
if($this.wireframe) {
$this.wireframe = false;
$this.wireframeButton.html('<i class="eye icon"/>Wireframe')
} else {
$this.wireframe = true;
$this.wireframeButton.html('<i class="eye icon"/>Solid')
}
this.scene.traverse(function(object) {
if (object instanceof THREE.Mesh) {
object.material.wireframe = $this.wireframe;
}
});
};
ModelViewer.prototype.objectCenter = function (mesh) {
var middle = new THREE.Vector3();
var geometry = mesh.geometry;
geometry.center();
geometry.computeBoundingBox();
middle.x = (geometry.boundingBox.max.x + geometry.boundingBox.min.x) / 2;
middle.y = (geometry.boundingBox.max.y + geometry.boundingBox.min.y) / 2;
middle.z = (geometry.boundingBox.max.z + geometry.boundingBox.min.z) / 2;
mesh.localToWorld(middle);
return middle;
};
ModelViewer.prototype.resize = function(width, height) {
this.width = width;
this.height = height;
this.camera.aspect = width / height;
this.camera.updateProjectionMatrix();
this.renderer.setSize(width, height);
};
ModelViewer.prototype.render = function() {
this.renderer.render(this.scene, this.camera);
};

View File

@ -2,4 +2,27 @@ $(document).ready(function () {
$('.ui.dropdown')
.dropdown()
;
$('.image.load img')
.visibility({
type : 'image',
transition : 'fade in',
duration : 1000
})
;
$('.ui.rating')
.rating("disable")
;
$('.tabular.menu .item').tab();
$('.message .close')
.on('click', function() {
$(this)
.closest('.message')
.transition('fade')
;
})
;
});

View File

@ -1,3 +1,43 @@
.ui.fixed + .ui.main {
margin-top: 5em;
.default-theme {
.ui.fixed + .ui.grid {
border-radius: 0;
border: 0;
box-shadow: none;
}
.item-info {
min-height: 300px;
}
.ui.fixed + .ui.main {
margin-top: 3em;
padding-bottom: 4em;
}
.link {
padding-left: 30px;
position: relative;
}
.link:before {
width: 23px;
height: 23px;
position: absolute;
left: 0;
display: inline-block;
}
.link.brickset:before {
content: '';
background: url("/resources/images/brickset_logo.png");
background-size: cover;
}
.link.rebrickable:before {
content: '';
background: url("/resources/images/rebrickable_logo.png");
background-size: cover;
}
}

View File

@ -0,0 +1,34 @@
.model-container {
width: 100%;
position: relative;
border: 1px solid #DDDDDD;
img {
width: 100%;
height: 100%;
}
.modelviewer-buttons {
text-align: right;
position: absolute;
top: 0;
height: 35px;
width: 100%;
padding: 3px 4px;
> button {
border: none;
height: 100%;
margin-left: 5px;
padding: 0 15px;
}
}
.model-view {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}

View File

@ -1,4 +1,4 @@
@import "variables";
@import "main";
@import "modelviewer";

View File

@ -46,6 +46,8 @@
4168950: 3815c01
73418: 3815c01
82359: 3626b
122c02: 122c01
# Bucket 1 x 1 x 1 Cylindrical
@ -119,6 +121,22 @@ u9147p01c06: u9147p01c02
u9147p02c02: u9147p01c02
u9147p03c02: u9147p01c02
# Figure Fabuland Monkey
u595p01c08: u595p01c04
u595p01c07: u595p01c04
u595p01c06: u595p01c04
u595p01c05: u595p01c04
# Figure Fabuland Monkey Head
u595p02c03: u595p02c02
# Figure Fabuland Monkey 2
u595p02c05: u595p02c04
# Figure Fabuland Mouse 1
u9105p01c06: u9105p01c02
u9105p01c03: u9105p01c02
# Figure Fabuland Elephant Head
u588p02c02: u588p01c02
@ -165,3 +183,38 @@ u588p02c02: u588p01c02
# Hinge Control Stick and Base
73587: 4592c01
4296152: 4592c01
4505: 4505a
6093: 6093a
90462a: 90462
# Minifig Mechanical SW Battle Droid
30375cs1: 30375cs0
30375cs2: 30375cs0
30375cs3: 30375cs0
2958c01: 2723
32181c03: 32181c01
76019: 76244
4107539: 76244
3680c02: 3680c01
4221774: 3680c01
9245: 3680c01
9246: 3680c01
9258: 3680c01
9328: 3680c01
9352: 3680c01
4508c01: 251c01
4100338: 2557c01
4100340: 6051c01
4100339: 2559c01
75192: 2977c01
4539364: 64776

View File

@ -89,7 +89,6 @@
10119: 51704
3149: 3149d
75998: 4493c00
10119: 51704
30658: 3404
59275: 2599
@ -154,3 +153,58 @@ wheel2a: 568c01
92456pr0021c01: 92241p03c01
15672: 92946
3614a: 3614
3614b: 3614
75215: 4694c01
6093: 6093a
3626bpr0895: 82359
6272c01: 6272
76320: 32181c02
95292c01: 75348
2909c03: 75348
2909c02: 75348
# Technic figures
tech001: 2698c01
tech002: 2698c01
tech003: 2698c01
tech004: 2698c01
tech005: 2698c01
tech006: 2698c01
tech007: 2698c01
tech008: 2698c01
tech009: 2698c01
tech010: 2698c01
tech011: 2698c01
tech011a: 2698c01
tech012: 2698c01
tech013: 2698c01
tech014: 2698c01
tech014a: 2698c01
tech015: 2698c01
tech016: 2698c01
tech016a: 2698c01
tech017: 2698c01
tech017a: 2698c01
tech018: 2698c01
tech019: 2698c01
tech020: 2698c01
tech021: 2698c01
tech022: 2698c01
tech023: 2698c01
tech024: 2698c01
tech025: 2698c01
tech026: 2698c01
tech027: 2698c01
tech028: 2698c01
tech029: 2698c01
33299a: 33299
33299b: 33299

View File

@ -1,40 +1,48 @@
{% extends 'html.html.twig' %}
{% import 'macros/elements.html.twig' as elements %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('resources/css/main.css') }}">
{% endblock %}
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
</head>
<body>
{% block body %}
{% block body %}
<div class="ui fixed inverted menu">
{{ knp_menu_render('mainMenu') }}
</div>
<div class="ui main container">
{% block page %}
<div class="ui main">
<div class="ui container">
<div class="ui masthead vertical segment">
<div class="introduction">
<div class="ui small breadcrumb">
{% for breadcrumb_item in knp_menu_get_breadcrumbs_array(knp_menu_get_current_item('mainMenu')) %}
{% if not loop.last %}
<a class="section" href="{{ breadcrumb_item.uri }}">{{ breadcrumb_item.label }}</a>
<i class="right chevron icon divider"></i>
{% else %}
<a class="active section">{{ breadcrumb_item.label }}</a>
{% endif %}
{% endfor %}
</div>
<h1 class="ui header">{% block header %}{% endblock %}</h1>
</div>
</div>
<div class="ui vertical segment content">
{% for label, flashes in app.session.flashbag.all %}
{% for flash in flashes %}
{{ elements.flash(label,flash) }}
{% endfor %}
{% endfor %}
{% block content %}
{% endblock %}
</div>
</div>
</div>
{% endblock page %}
<div class="ui black inverted vertical footer segment stripe">
{% block footer %}
<div class="ui center aligned container">
</div>
{% endblock %}
{% block javascripts %}
<script type="text/javascript" src="{{ asset('resources/js/three.js') }}"></script>
<script type="text/javascript" src="{{ asset('resources/js/OrbitControls.js') }}"></script>
<script src="{{ asset('resources/js/main.js') }}"></script>
{% endblock %}
</body>
</html>
</div>
{% endblock %}

View File

@ -14,12 +14,14 @@
</div>
{{ form_end(form) }}
<h3>{{ sets|length }}</h3>
<div class="ui seven column grid">
<div class="row">
{% for set in sets %}
<div class="column">
<a href="{{ url('set_detail', {'number': set.number~'-'~set.numberVariant, 'name' : set.name|escape('url') }) }}">
<img style="width: 100%" src="{{ set.largeThumbnailURL }}">
<img style="width: 100%" src="{{ set.legoSetID|setImage|imagine_filter('rebrickable_set_min') }}">
<p>{{ set.LegoSetID }} - {{ set.name }}</p>
</a>
</div>

View File

@ -0,0 +1,9 @@
<div class="ui six doubling cards">
{% for image in images %}
<div class="card">
<a class="ui bordered fluid image" href="{{ image.imageURL }}" data-lightbox="setImages">
<img src="{{ image.thumbnailURL }}">
</a>
</div>
{% endfor %}
</div>

View File

@ -0,0 +1,30 @@
{% import 'macros/utils.html.twig' as utils %}
{% if instructions|length != 0 %}
<p>
{{ 'set.instructions.text' | trans }}
</p>
<table class="ui celled padded table">
<thead>
<tr>
<th>{{ 'set.instructions.description' | trans }}</th>
<th>{{ 'set.instructions.filesize' | trans }}</th>
<th>{{ 'set.instructions.filename' | trans }}</th>
</tr>
</thead>
<tbody>
{% for instruction in instructions %}
<tr>
<td>{{ instruction.description }}</td>
<td>{{ utils.bytesToSize( remoteSize(instruction.uRL)) }}</td>
<td><a href="{{ instruction.uRL }}">{{ remoteFilename(instruction.uRL) }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<h2 class="ui center aligned icon">
<i class="circular warning icon"></i>
</h2>
{% endif %}

View File

@ -0,0 +1,26 @@
<div class="ui comments">
{% for review in reviews %}
<div class="comment">
<a class="avatar">
{#<img src="/images/avatar/small/matt.jpg">#}
</a>
<div class="content">
<div class="header">
<h4>{{ review.title }}</h4>
</div>
<span class="author">{{ review.author }}</span> {{ review.hTML }}
<div class="metadata">
<span class="date">{{ review.datePosted|date("Y.m.d H:m:i")}}</span>
</div>
<div class="text">
{{ review.review|raw }}
</div>
<div>
Overall rating <div class="ui star rating" data-rating="{{ review.overallRating }}" data-max-rating="5"></div>
{#Value for money <div class="ui star rating" data-rating="{{ review.valueForMoney }}" data-max-rating="5"></div>#}
</div>
</div>
</div>
{% endfor %}
</div>

View File

@ -1,6 +1,14 @@
{% extends 'base.html.twig' %}
{% block content %}
{% block page %}
<div class="ui grid massive message vertical">
<div class="ui container">
<div class="row">
<div class="h1 ui huge header">
Hello, world!
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -3,9 +3,9 @@
{% block title %}{{ 'page.error.title'|trans }}{% endblock %}
{% block content %}
<h2 class="ui center aligned icon header">
<h1 class="ui center aligned icon header">
<i class="circular warning icon"></i>
{{ 'page.error.large'|trans }}
</h2>
</h1>
<p class="ui center aligned">{{ 'page.error.text'|trans }}</p>
{% endblock %}

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>{% block title %}PrintABrick{% endblock %}</title>
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('resources/css/main.css') }}">
{% endblock %}
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
</head>
<body class="default-theme">
{% block body %}
{% endblock %}
{% block javascripts %}
<script type="text/javascript" src="{{ asset('resources/js/three.js') }}"></script>
<script type="text/javascript" src="{{ asset('resources/js/OrbitControls.js') }}"></script>
<script src="{{ asset('resources/js/main.js') }}"></script>
{% endblock %}
</body>
</html>

View File

@ -1,85 +0,0 @@
{% extends 'base.html.twig' %}
{% import 'macros/elements.html.twig' as elements %}
{% block content %}
{% if model %}
<dl>
<dt>number:</dt><dd>{{ model.number }}</dd>
<dt>name:</dt><dd>{{ model.name }}</dd>
<dt>category:</dt><dd>{{ model.category ? model.category.name }}</dd>
<dt>type:</dt><dd>{{ model.type ? model.type.name }}</dd>
<dt>model:</dt><dd>{{ model.path }}</dd>
<dt>author:</dt><dd>{{ model.author }}</dd>
<dt>keywords:</dt>
<dd>
{% for keyword in model.keywords %}
<span class="ui label">{{ keyword.name }}</span>
{% endfor %}
</dd>
<dt>aliases:</dt>
<dd>
{% for alias in model.aliases %}
<span class="ui label">{{ alias.number }}</span>
{% endfor %}
</dd>
<dd>Download:</dd>
<dt><a href="{{ path('model_stl', {'number' : model.number })}}">{{ model.number }}</a></dt>
<dt>rebrickable parts ({{ rbParts|length }}):</dt>
<dd>
<p>
<div class="ui eight doubling cards">
{% for alias in rbParts %}
<a href="{{ url('rebrickable_part_show', {number:alias.number}) }}" class="ui label">{{ alias.number }}</a>
{% endfor %}
</div>
</p>
</dd>
</dl>
{% endif %}
<div style="display: flex; flex-wrap: wrap">
<div id="model" style="height: 300px; width: 300px; padding: 5px; display: inline-block"></div>
<div style="height: 300px; width: 300px; padding: 5px; display: inline-block">
<img src="{{ url('model_image', {'number': model.number}) }}" style="max-height: 90%; max-width: 100%">
</div>
<h4 class="ui horizontal divider header">
<i class="puzzle icon"></i> Subparts of this model
</h4>
{% for subpart in model.subparts %}
{{ elements.part(subpart.subpart) }}
{% endfor %}
<h4 class="ui horizontal divider header">
<i class="cube icon"></i> Model is subpart of
</h4>
{% for subpart in model.parents %}
{{ elements.part(subpart.parent) }}
{% endfor %}
<h4 class="ui horizontal divider header">
<i class="cubes icon"></i> Sets ({{ sets|length }})
</h4>
{% for set in sets %}
<span style="margin: 5px"><a href="{{ url('set_detail', {number:set.number}) }}">{{ set.number }}</a></span>
{% endfor %}
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
window.onload = function() {
modelView = new ModelViewer();
var scene = modelView.initScene($('#model'));
modelView.loadStl('{{ path('model_stl', {'number' : model.number })}}');
modelView.render();
};
</script>
{% endblock %}

View File

@ -1,7 +1,13 @@
{% macro part(model) %}
<div style="height: 100px; width: 100px; padding: 5px; display: inline-block;">
<img src="{{ url('model_image', {'number': model.number }) }}" style="max-height: 90%; max-width: 100%;">
<p><a href="{{ url('model_detail', {'number': model.number}) }}">{{ model.number }}</a></p>
<div class="column">
<div class="ui bordered fluid image">
<a href="{{ url('model_detail', {'number': model.number})}}">
<div class="image">
<img src="{{ asset('/ldraw/images/'~model.number~'.png') | imagine_filter('model_min') }}" data-src="{{ asset('/ldraw/images/'~model.number~'.png') | imagine_filter('model_min') }}" class="transition visible">
</div>
<div class="ui bottom attached label {% if model.parts|length == 0 %}black{% endif %}">{{ model.number }}</div>
</a>
</div>
</div>
{% endmacro %}

View File

@ -0,0 +1,21 @@
{#http://stackoverflow.com/a/15303004#}
{% macro bytesToSize(bytes) %}
{% spaceless %}
{% set kilobyte = 1024 %}
{% set megabyte = kilobyte * 1024 %}
{% set gigabyte = megabyte * 1024 %}
{% set terabyte = gigabyte * 1024 %}
{% if bytes < kilobyte %}
{{ bytes ~ ' B' }}
{% elseif bytes < megabyte %}
{{ (bytes / kilobyte)|number_format(2, '.') ~ ' KB' }}
{% elseif bytes < gigabyte %}
{{ (bytes / megabyte)|number_format(2, '.') ~ ' MB' }}
{% elseif bytes < terabyte %}
{{ (bytes / gigabyte)|number_format(2, '.') ~ ' GB' }}
{% else %}
{{ (bytes / terabyte)|number_format(2, '.') ~ ' TB' }}
{% endif %}
{% endspaceless %}
{% endmacro %}

View File

@ -0,0 +1,122 @@
{% extends 'base.html.twig' %}
{% import 'macros/elements.html.twig' as elements %}
{% block title %}#{{ model.number }} - {{ model.name }}{% endblock %}
{% block header %}#{{ model.number }} - {{ model.name }}{% endblock %}
{% block content %}
<div class="ui grid">
<div class="column ten wide">
<div id="model-viewer" class="model-container">
<img src="{{ asset('ldraw/images/'~model.number~'.png') | imagine_filter('model_large')}}">
</div>
</div>
<div class="column six wide">
<div class="grey">
<table>
<tr>
<th>category</th><td>{{ model.category ? model.category.name }}</td>
</tr>
<tr>
<th>model</th><td>{{ model.path }}</td>
</tr>
<tr>
<th>author</th><td>{{ model.author.name }}</td>
</tr>
</table>
<dl>
<dt>keywords:</dt>
<dd>
{% for keyword in model.keywords %}
<span class="ui label">{{ keyword.name }}</span>
{% endfor %}
</dd>
<dt>aliases:</dt>
<dd>
{% for alias in model.aliases %}
<span>{{ alias.number }}</span>{% if not loop.last %},{% endif %}
{% endfor %}
</dd>
<dd>Download:</dd>
<dt><a href="{{ url('media_file', {'path': model.path}) }}">{{ model.number }}</a></dt>
<dt>rebrickable parts ({{ rbParts|length }}):</dt>
<dd>
{#<p>#}
{#<div class="ui eight doubling cards">#}
{#{% for alias in rbParts %}#}
{#<a href="{{ url('reb_part_detail', {number:alias.number}) }}" class="ui label">{{ alias.number }}</a>#}
{#{% endfor %}#}
{#</div>#}
{#</p>#}
</dd>
</dl>
</div>
</div>
</div>
<h4 class="ui horizontal divider header">
Related Models
</h4>
<h5 class="ui horizontal divider header">
Subparts of this model
</h5>
<div class="ui eight column grid">
{% for subpart in model.subparts %}
<div class="column">
{{ elements.part(subpart.subpart) }}
</div>
{% endfor %}
</div>
<h5 class="ui horizontal divider header">
Model is subpart of
</h5>
<div class="ui eight column grid">
{% for subpart in model.parents %}
<div class="column">
{{ elements.part(subpart.parent) }}
</div>
{% endfor %}
</div>
<h5 class="ui horizontal divider header">
Model pairs with
</h5>
<div class="ui eight column grid">
{% for subpart in related %}
<div class="column">
{{ elements.part(subpart) }}
</div>
{% endfor %}
</div>
<h4 class="ui horizontal divider header">
<i class="cubes icon"></i> Sets ({{ sets|length }})
</h4>
{% for set in sets %}
<span style="margin: 5px"><a href="{{ url('set_detail', {number:set.number}) }}">{{ set.number }}</a></span>
{% endfor %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
window.onload = function() {
modelView = new ModelViewer($('#model-viewer'), '{{ url('media_file', {'path': model.path }) }}');
};
</script>
{% endblock %}

View File

@ -2,8 +2,14 @@
{% import 'macros/elements.html.twig' as elements %}
{% block title %}{{ 'page.model.index' | trans }}{% endblock %}
{% block header %}{{ 'page.model.index' | trans }}{% endblock %}
{% block content %}
<form method="get" action="." class="ui form">
<div class="ui container stackable grid">
<div class="column four wide">
<form method="get" action="" class="ui form">
{{ form_start(form) }}
{{ form_row(form.search) }}
@ -16,18 +22,17 @@
</div>
{{ form_end(form) }}
</form>
</div>
<div class="column twelve wide">
{{ knp_pagination_render(models) }}
<p>{{ models.getTotalItemCount }}</p>
<div class="ui ten column grid">
<div class="row">
<div class="ui eight column grid">
{% for model in models %}
<div class="column">
{{ elements.part(model) }}
</div>
{% endfor %}
</div>
</div>
{{ knp_pagination_render(models) }}
</div>
</div>
{% endblock %}

View File

@ -2,7 +2,7 @@
{% block content %}
<table class="ui celled padded table">
<table class="ui celled small padded table">
<thead>
<tr>
<th></th>

View File

@ -9,28 +9,33 @@
<dt>category:</dt><dd>{{ part.category ? part.category.name }}</dd>
</dl>
<div style="display: flex; flex-wrap: wrap">
{% if apiPart is not null %}
<div style="height: 300px; width: 300px; padding: 5px; display: inline-block">
<img src="{{ apiPart.imgUrl }}" style="max-height: 90%; max-width: 100%">
</div>
<a href="{{ apiPart.url }}">Rebrickable</a>
<img class="image ui middle" src="{{ apiPart.imgUrl }}" style="max-height: 90%; max-width: 100%">
{% endif %}
<div style="height: 300px; width: 300px; padding: 5px; display: inline-block">
<img src="{{ url('model_image', {number:part.number}) }}" style="max-height: 90%; max-width: 100%">
</div>
{% if part.model %}
{{ elements.part(part.model) }}
{% endif %}
</div>
<h4 class="ui horizontal divider header">
<i class="puzzle icon"></i> Sets
</h4>
<div class="ui eight column grid">
{% for set in sets %}
<span style="margin: 5px"><a href="{{ url('set_detail', {number:set.number}) }}">{{ set.number }}</a></span>
<div class="column">
<div class="ui fluid bordered image">
<a href="{{ url('set_detail', {number:set.number}) }}">
<img class="ui bordered image" src="{{ set.number|setImage|imagine_filter('rebrickable_set_min') }}">
<div class="ui bottom attached label">{{ set.number }}<br></div>
</a>
</div>
</div>
{% endfor %}
<div>
{{ dump(apiPart) }}

View File

@ -0,0 +1,25 @@
{% extends 'base.html.twig' %}
{% block content %}
<div class="ui seven column grid">
<div class="row">
{% for part in parts %}
<div class="column">
<div class="ui fluid bordered image">
<a href="{{ path('reb_part_detail', {number: part.number}) }}">
<div class="image load">
<img src="{{ part.number|partImage(-1)| imagine_filter('rebrickable_part_min') }}" data-src="{{ part.number|partImage(-1)| imagine_filter('rebrickable_part_min') }}" class="transition visible">
</div>
<div class="ui bottom attached label {{ part.model == null ? 'black' : 'red'}}">{{ part.number }}</div>
</a>
</div>
</div>
{% endfor %}
</div>
</div>
<p>{{ parts.getTotalItemCount }}</p>
{{ knp_pagination_render(parts) }}
{% endblock %}

View File

@ -1,58 +0,0 @@
{% extends 'base.html.twig' %}
{% block content %}
{% if set is not null %}
<dl>
<dt>number:</dt><dd>{{ set.number }}</dd>
<dt>year:</dt><dd>{{ set.year }}</dd>
<dt>name:</dt><dd>{{ set.name }}</dd>
<dt>theme:</dt><dd>{{ set.theme.name }}</dd>
<dt>count of parts:</dt><dd>{{ set.partCount }}</dd>
</dl>
<img class="ui bordered image medium" src="{{ rbset ? rbset.imgUrl }}">
<h4 class="ui horizontal divider header">
<i class="cubes icon"></i> Parts {{ inventoryParts|length }}
</h4>
<div class="ui eight column grid">
{% for inventoryPart in inventoryParts %}
<div class="column">
<div class="ui fluid image {{ inventoryPart.part.model == null ? 'active dimmer'}}" style="height: 150px;">
<a class="ui right red circular label">{{ inventoryPart.quantity }}</a>
<a href="{{ path('rebrickable_part_show', {number: inventoryPart.part.number}) }}">
<img src="https://rebrickable.com/media/parts/ldraw/{{inventoryPart.color ? inventoryPart.color.id }}/{{ inventoryPart.part.number }}.png">
<div style="border-bottom: #{{ inventoryPart.color.rgb }} 5px solid" class="ui bottom attached label">{{ inventoryPart.part.number }}<br>{{ inventoryPart.isSpare ? 'Spare' : 'Regular' }}</div>
</a>
</div>
</div>
{% endfor %}
<div>
<h4 class="ui horizontal divider header">
<i class="cubes icon"></i> Models {{ parts|length }}
</h4>
<div class="ui eight column grid">
{% for part in parts %}
<div class="column">
<div class="ui bordered fluid image">
<a href="{{ path('model_detail', {number: part.number}) }}">
<img src="https://rebrickable.com/media/parts/ldraw/-1/{{ part.number }}.png">
<div class="ui bottom attached label">{{ part.number }} </div>
</a>
</div>
</div>
{% endfor %}
<div>
{% endif %}
{{ brset ? dump(brset) }}
{% endblock %}

View File

@ -1,21 +0,0 @@
{% extends 'base.html.twig' %}
{% block content %}
<div class="ui seven column grid">
<div class="row">
{% for set in sets %}
<div class="column">
<a href="{{ url('set_detail', {'number': set.number, 'name' : set.name|escape('url') }) }}">
{#<img style="width: 100%" src="{{ set }}">#}
<p>{{ set.number }} - {{ set.name }}</p>
</a>
</div>
{% endfor %}
</div>
</div>
<p>{{ sets.getTotalItemCount }}</p>
{{ knp_pagination_render(sets) }}
{% endblock %}

View File

@ -0,0 +1,46 @@
{% if regularParts|length > 0 %}
<h4 class="ui horizontal divider header">
Regular parts
</h4>
<div class="ui ten column grid">
{% for inventoryPart in regularParts %}
{% if inventoryPart.part is defined %}
<div class="column">
<div class="ui fluid bordered image">
<a class="ui right {{ inventoryPart.part.model == null ? 'black' : 'red'}} circular label">{{ inventoryPart.quantity }}</a>
<a href="{{ path('reb_part_detail', {number: inventoryPart.part.number}) }}">
<div class="image load">
<img src="{{ inventoryPart.part.number|partImage(inventoryPart.color.id)| imagine_filter('rebrickable_part_min') }}" data-src="{{ inventoryPart.part.number|partImage(inventoryPart.color.id)| imagine_filter('rebrickable_part_min') }}" class="transition visible">
</div>
<div style="border-bottom: #{{ inventoryPart.color.rgb }} 5px solid" class="ui bottom attached label">{{ inventoryPart.part.number }}</div>
</a>
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% if spareParts|length > 0 %}
<h4 class="ui horizontal divider header">
Spare parts
</h4>
<div class="ui ten column grid">
{% for inventoryPart in spareParts %}
{% if inventoryPart.part is defined %}
<div class="column">
<div class="ui fluid bordered image">
<a class="ui right {{ inventoryPart.part.model == null ? 'black' : 'red'}} circular label">{{ inventoryPart.quantity }}</a>
<a href="{{ path('reb_part_detail', {number: inventoryPart.part.number}) }}">
<div class="image load">
<img src="{{ inventoryPart.part.number|partImage(inventoryPart.color.id)| imagine_filter('rebrickable_part_min') }}" data-src="{{ inventoryPart.part.number|partImage(inventoryPart.color.id)| imagine_filter('rebrickable_part_min') }}" class="transition visible">
</div>
<div style="border-bottom: #{{ inventoryPart.color.rgb }} 5px solid" class="ui bottom attached label">{{ inventoryPart.part.number }}</div>
</a>
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}

View File

@ -0,0 +1,16 @@
<h4 class="ui horizontal divider header">Sets</h4>
<div class="ui seven column grid">
<div class="row">
{% for set in inventorySets %}
<div class="column">
<a href="{{ url('set_detail', {'number': set.set.number}) }}">
<div class="ui bordered image medium">
<img src="{{ asset('resources/images/unknown_image.png') }}" data-src="{{ set.set.number|setImage|imagine_filter('rebrickable_set_min') }}" class="transition visible">
</div>
<p>{{ set.set.number }} - {{ set.set.name }}</p>
<p>{{ set.quantity}}</p>
</a>
</div>
{% endfor %}
</div>
</div>

View File

@ -0,0 +1,138 @@
{% extends 'base.html.twig' %}
{% block title %}{{ rbset ? rbset.number }} {{ rbset ? rbset.name }}{% endblock %}
{% block header %}{{ rbset ? rbset.number }} {{ rbset ? rbset.name }}{% endblock %}
{% block content %}
<div class="ui stackable grid">
<div class="column nine wide">
<div class="image bordered ui big">
{% if brset %}
<img class="big" src="{{ brset.imageURL }}">
{% elseif rbset %}
<img class="big" src="{{ rbset.number|setImage|imagine_filter('rebrickable') }}">
{% endif %}
</div>
</div>
<div class="column seven wide">
<div class="item-info black ui">
<table class="ui table">
<tr>
<td>number</td><td>{{ brset ? brset.legoSetID : rbset ? rbset.number : null}}</td>
</tr>
<tr>
<td>name</td><td>{{ brset ? brset.name : rbset ? rbset.name : null}}</td>
</tr>
<tr>
<td>year</td><td>{{ brset ? brset.year : rbset ? rbset.year : null}}</td>
</tr>
<tr>
<td>theme</td>
{% if rbset %}
<td><a href="#">{{ rbset.theme.parent ? rbset.theme.parent.name }}</a> <a href="#">{{ rbset.theme.name }}</a> </td>
{% elseif brset %}
<td>{{ brset.theme }}</td>
{% endif %}
</tr>
<tr>
<td>parts</td><td>{{ brset ? brset.pieces : rbset ? rbset.partCount }}</td>
</tr>
{% if brset %}
<tr>
<td></td>
<td><a class="brickset link" href="{{ brset.bricksetURL }}">Brickset</a></td>
</tr>
{% endif %}
{% if rbset %}
<tr>
<td></td>
<td><a class="rebrickable link" href="http://rebrickable.com/sets/{{ rbset ? rbset.number }}">Rebrickable</a></td>
</tr>
{% endif %}
</table>
{% if brset is not null %}
<dl>
<dt>themegroup:</dt><dd>{{ brset.themeGroup }}</dd>
<dt>theme:</dt><dd>{{ brset.theme }}</dd>
<dt>subtheme:</dt><dd>{{ brset.subtheme }}</dd>
<dt>count of parts:</dt><dd>{{ brset.pieces }}</dd>
<dt>minifigs:</dt><dd>{{ brset.minifigs }}</dd>
</dl>
{% endif %}
</div>
</div>
</div>
<div class="segment vertical item-info">
<div class="ui tabular menu">
<a class="item active" data-tab="parts"><i class="cubes icon"></i> Parts ({{ rbset ? rbset.partCount : 0}})</a>
{% if brset %}
<a class="item" data-tab="description"><i class="info icon"></i> Description</a>
<a class="item" data-tab="images"> <i class="image icon"></i> Images ({{ brset.additionalImageCount }})</a>
<a class="item" data-tab="instructions"> <i class="file pdf outline icon"></i> Instructions ({{ brset.instructionsCount }})</a>
<a class="item" data-tab="reviews"><i class="write icon"></i> Reviews ({{ brset.reviewCount }})</a>
{% endif %}
</div>
<div class="ui tab active" data-tab="parts">
{% if rbset %}
<div class="ajax-load" id="parts" data-src="{{ path('rebrickable_set_parts', { 'number': rbset.number }) }}"></div>
{#{{ render(controller('AppBundle:Rebrickable/Set:parts', { 'number': rbset.number })) }}#}
{% endif %}
</div>
{% if brset %}
<div class="ui tab" data-tab="images">
{#<div class="ajax-load" id="images" data-src="{{ path('brickset_images', { 'id': brset.setID }) }}"></div>#}
{{ render(controller('AppBundle:Brickset/Set:images', { 'id': brset.setID })) }}
</div>
<div class="ui tab" data-tab="instructions">
{#<div class="ajax-load" id="instructions" data-src="{{ path('brickset_instructions', { 'id': brset.setID }) }}"></div>#}
{{ render(controller('AppBundle:Brickset/Set:instructions', { 'id': brset.setID })) }}
</div>
<div class="ui tab" data-tab="reviews">
{#<div class="ajax-load" id="reviews" data-src="{{ path('brickset_reviews', { 'id': brset.setID }) }}"></div>#}
{{ render(controller('AppBundle:Brickset/Set:reviews', { 'id': brset.setID })) }}
</div>
<div class="ui tab" data-tab="description">
{{ brset.description }}
</div>
{% endif %}
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
$(document).ready(function () {
$('.ajax-load').each(function () {
$self = $(this);
console.log($self);
$.ajax({
type: "POST",
dataType: 'json',
url: $self.data('src'),
async: true, //you won't need that if nothing in your following code is dependend of the result
success: function(response){
$self.html(response); //Change the html of the div with the id = "your_div"
}
})
})
})
</script>
{% endblock %}

View File

@ -0,0 +1,36 @@
{% extends 'base.html.twig' %}
{% block content %}
<form method="get" action="" class="ui form">
{{ form_start(form) }}
{{ form_row(form.search) }}
<div class="field fluid search selection">
{{ form_label(form.theme.id) }}
{{ form_widget(form.theme.id) }}
</div>
<div class="field">
<input class="ui submit button" type="submit" value="filter"/>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
</form>
<div class="ui seven column grid">
<div class="row">
{% for set in sets %}
<div class="column">
<a href="{{ url('set_detail', {'number': set.number, 'name' : set.name|escape('url') }) }}">
<img class="ui bordered image medium" src="{{ set.number|setImage|imagine_filter('rebrickable_set_min') }}">
<p>{{ set.number }} - {{ set.name }}</p>
</a>
</div>
{% endfor %}
</div>
</div>
<p>{{ sets.getTotalItemCount }}</p>
{{ knp_pagination_render(sets) }}
{% endblock %}

View File

@ -1,7 +1,7 @@
<?php
use Doctrine\Common\Annotations\AnnotationRegistry;
use Composer\Autoload\ClassLoader;
use Doctrine\Common\Annotations\AnnotationRegistry;
/** @var ClassLoader $loader */
$loader = require __DIR__.'/../vendor/autoload.php';

2564
app/config/brickset.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,10 +7,8 @@ imports:
# http://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: en
# rebrickable csv files root URL
rebrickable_url: 'http://rebrickable.com/media/downloads/'
# LDraw library zip file URL
ldraw_url: 'http://www.ldraw.org/library/updates/complete.zip'
# rebrickable csv files root URL (http://rebrickable.com/media/downloads/ or local dir containing csv files)
rebrickable_csv_url: 'http://rebrickable.com/media/downloads/'
framework:
#esi: ~
@ -92,11 +90,56 @@ knp_paginator:
pagination: KnpPaginatorBundle:Pagination:semantic_ui_pagination.html.twig # sliding pagination controls template
sortable: KnpPaginatorBundle:Pagination:sortable_link.html.twig # sort link template
liip_imagine:
loaders:
media:
flysystem:
filesystem_service: oneup_flysystem.media_filesystem
rebrickable:
stream:
wrapper: 'http://m.rebrickable.com/media/'
resolvers:
default:
web_path: ~
filter_sets:
model_min:
data_loader: media
cache: ~
quality: 80
default_image: '/resources/images/unknown_image.png'
filters:
thumbnail: { size: [200, 200], mode: inset }
background: { size: [250, 250], position: center, color: '#FFFFFF' }
model_large:
data_loader: media
cache: ~
quality: 92
filters:
thumbnail: { size: [800, 600], mode: inset }
background: { size: [1100, 800], position: center, color: '#FFFFFF' }
rebrickable:
data_loader: rebrickable
rebrickable_part_min:
quality: 80
data_loader: rebrickable
cache: ~
default_image: '/resources/images/unknown_image.png'
filters:
thumbnail: { size: [250, 250], mode: inset }
rebrickable_set_min:
quality: 80
data_loader: rebrickable
default_image: '/resources/images/unknown_image.png'
cache: ~
filters:
thumbnail: { size: [250, 250], mode: inset }
oneup_flysystem:
adapters:
media_adapter:
media:
local:
directory: "%kernel.root_dir%/../var/media/"
filesystems:
media:
adapter: media_adapter
adapter: media

View File

@ -12,15 +12,22 @@ web_profiler:
intercept_redirects: false
monolog:
channels: ['loader']
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: [!event]
channels: [!event, !loader]
console:
type: console
channels: [!event, !doctrine]
loader:
type: rotating_file
path: "%kernel.logs_dir%/loader.log"
level: debug
channels: 'loader'
max_files: 10
# uncomment to get logging in your browser
# you may have to allow bigger header sizes in your Web server configuration
#firephp:

View File

@ -1,3 +1,5 @@
app:
resource: "@AppBundle/Controller/"
type: annotation
_liip_imagine:
resource: "@LiipImagineBundle/Resources/config/routing.xml"

View File

@ -1,7 +1,7 @@
services:
api.client.brickset:
class: AppBundle\Api\Client\Brickset\Brickset
arguments: ['%brickset_apikey%']
arguments: ['%brickset_apikey%', '%kernel.root_dir%/config/brickset.xml']
api.client.rebrickable:
class: AppBundle\Api\Client\Rebrickable\Rebrickable_v3
arguments: ['%rebrickable_apikey%']

View File

@ -1,7 +1,20 @@
services:
form.filter.category:
class: AppBundle\Form\Filter\CategoryFilterType
class: AppBundle\Form\Filter\Model\CategoryFilterType
arguments:
- '@manager.ldraw.category'
- '@repository.ldraw.category'
tags:
- { name: form.type }
form.filter.brickset:
class: AppBundle\Form\FilterSetType
arguments: ['@api.manager.brickset']
tags:
- { name: form.type }
form.filter.theme:
class: AppBundle\Form\Filter\Set\ThemeFilterType
arguments:
- '@repository.rebrickable.theme'
tags:
- { name: form.type }

View File

@ -1,29 +1,25 @@
services:
service.loader:
service.loader.base:
abstract: true
class: AppBundle\Service\Loader\BaseLoaderService
class: AppBundle\Service\Loader\BaseLoader
calls:
- [setArguments, ['@doctrine.orm.entity_manager', '@app.relation.mapper']]
- [setArguments, ['@doctrine.orm.entity_manager', '@monolog.logger.loader']]
service.ldview:
class: AppBundle\Service\LDViewService
arguments: ['%ldview_bin%', '@oneup_flysystem.media_filesystem']
service.loader.rebrickable:
class: AppBundle\Service\Loader\RebrickableLoaderService
arguments: ['%rebrickable_url%']
parent: service.loader
class: AppBundle\Service\Loader\RebrickableLoader
arguments: ['%rebrickable_csv_url%']
parent: service.loader.base
util.dat.parser:
class: AppBundle\Utils\DatParser
arguments: ['@app.relation.mapper']
service.loader.ldraw:
class: AppBundle\Service\Loader\LDrawLoaderService
arguments: ['@service.ldview', '%ldraw_url%', '@manager.ldraw', '@util.dat.parser']
parent: service.loader
service.loader.model:
class: AppBundle\Service\Loader\ModelLoader
arguments: ['@service.ldview', '@app.relation.mapper']
parent: service.loader.base
service.loader.relation:
class: AppBundle\Service\Loader\RelationLoader
arguments: ['@manager.ldraw.model', '@repository.rebrickable.part', '@api.manager.rebrickable']
parent: service.loader
arguments: ['@api.manager.rebrickable', '@app.relation.mapper']
parent: service.loader.base

View File

@ -1,28 +1 @@
services:
manager.ldraw.keyword:
class: AppBundle\Manager\LDraw\KeywordManager
arguments:
- "@repository.ldraw.keyword"
manager.ldraw.category:
class: AppBundle\Manager\LDraw\CategoryManager
arguments:
- "@repository.ldraw.category"
manager.ldraw.type:
class: AppBundle\Manager\LDraw\TypeManager
arguments:
- "@repository.ldraw.type"
manager.ldraw.subpart:
class: AppBundle\Manager\LDraw\SubpartManager
arguments:
- "@repository.ldraw.subpart"
manager.ldraw.model:
class: AppBundle\Manager\LDraw\ModelManager
arguments:
- "@repository.ldraw.model"
manager.ldraw.alias:
class: AppBundle\Manager\LDraw\AliasManager
arguments:
- "@repository.ldraw.alias"

View File

@ -41,6 +41,13 @@ services:
arguments:
- AppBundle\Entity\LDraw\Alias
repository.ldraw.author:
class: Doctrine\ORM\EntityRepository
factory: ["@doctrine", getRepository]
arguments:
- AppBundle\Entity\LDraw\Author
repository.rebrickable.category:
class: Doctrine\ORM\EntityRepository
factory: ["@doctrine", getRepository]

View File

@ -1,19 +1,10 @@
services:
manager.ldraw:
class: AppBundle\Manager\LDrawManager
arguments:
- '@manager.ldraw.category'
- '@manager.ldraw.keyword'
- '@manager.ldraw.type'
- '@manager.ldraw.subpart'
- '@manager.ldraw.model'
- '@manager.ldraw.alias'
app.form.filter_set:
class: AppBundle\Form\FilterSetType
arguments: ['@api.manager.brickset']
app.twig_extension:
class: AppBundle\Twig\AppExtension
public: false
arguments: ['@api.manager.rebrickable']
tags:
- { name: form.type }
- { name: twig.extension }
app.relation.mapper:
class: AppBundle\Utils\RelationMapper

View File

@ -23,10 +23,11 @@
"sensio/framework-extra-bundle": "^3.0.2",
"incenteev/composer-parameter-handler": "^2.0",
"guzzlehttp/guzzle": "^6.2",
"knplabs/knp-menu-bundle": "^2.0",
"knplabs/knp-menu-bundle": "^2.1",
"oneup/flysystem-bundle": "^1.7",
"knplabs/knp-paginator-bundle": "^2.5",
"lexik/form-filter-bundle": "~5.0"
"lexik/form-filter-bundle": "~5.0",
"liip/imagine-bundle": "^1.7"
},
"require-dev": {
"sensio/generator-bundle": "^3.0",

441
composer.lock generated
View File

@ -4,8 +4,67 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "6956e65252fd75b8d2e258fcc54fa3a9",
"content-hash": "6a70240b303126447c6fc3c6f75bf6ff",
"packages": [
{
"name": "composer/ca-bundle",
"version": "1.0.7",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
"reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/b17e6153cb7f33c7e44eb59578dc12eee5dc8e12",
"reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-pcre": "*",
"php": "^5.3.2 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.5",
"psr/log": "^1.0",
"symfony/process": "^2.5 || ^3.0"
},
"suggest": {
"symfony/process": "This is necessary to reliably check whether openssl_x509_parse is vulnerable on older php versions, but can be ignored on PHP 5.5.6+"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\CaBundle\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
"keywords": [
"cabundle",
"cacert",
"certificate",
"ssl",
"tls"
],
"time": "2017-03-06T11:59:08+00:00"
},
{
"name": "doctrine/annotations",
"version": "v1.2.7",
@ -952,6 +1011,63 @@
],
"time": "2017-03-20T17:10:46+00:00"
},
{
"name": "imagine/imagine",
"version": "v0.6.3",
"source": {
"type": "git",
"url": "https://github.com/avalanche123/Imagine.git",
"reference": "149041d2a1b517107bfe270ca2b1a17aa341715d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/avalanche123/Imagine/zipball/149041d2a1b517107bfe270ca2b1a17aa341715d",
"reference": "149041d2a1b517107bfe270ca2b1a17aa341715d",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"sami/sami": "dev-master"
},
"suggest": {
"ext-gd": "to use the GD implementation",
"ext-gmagick": "to use the Gmagick implementation",
"ext-imagick": "to use the Imagick implementation"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-develop": "0.7-dev"
}
},
"autoload": {
"psr-0": {
"Imagine": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bulat Shakirzyanov",
"email": "mallluhuct@gmail.com",
"homepage": "http://avalanche123.com"
}
],
"description": "Image processing for PHP 5.3",
"homepage": "http://imagine.readthedocs.org/",
"keywords": [
"drawing",
"graphics",
"image manipulation",
"image processing"
],
"time": "2015-09-19T16:54:05+00:00"
},
{
"name": "incenteev/composer-parameter-handler",
"version": "v2.1.2",
@ -1053,6 +1169,94 @@
],
"time": "2014-01-12T16:20:24+00:00"
},
{
"name": "knplabs/gaufrette",
"version": "v0.3.1",
"source": {
"type": "git",
"url": "https://github.com/KnpLabs/Gaufrette.git",
"reference": "771ad16f4b2e7f9d35f44b201956e83c6fbf5dde"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/KnpLabs/Gaufrette/zipball/771ad16f4b2e7f9d35f44b201956e83c6fbf5dde",
"reference": "771ad16f4b2e7f9d35f44b201956e83c6fbf5dde",
"shasum": ""
},
"require": {
"php": ">=5.4"
},
"conflict": {
"microsoft/windowsazure": "<0.4.3"
},
"require-dev": {
"amazonwebservices/aws-sdk-for-php": "1.5.*",
"aws/aws-sdk-php": "^2.4.12",
"doctrine/dbal": ">=2.3",
"dropbox-php/dropbox-php": "*",
"google/apiclient": "~1.1.3",
"herzult/php-ssh": "*",
"league/flysystem": "~1.0",
"mikey179/vfsstream": "~1.2.0",
"phpseclib/phpseclib": "^2.0",
"phpspec/phpspec": "~2.4",
"phpunit/phpunit": "3.7.*",
"rackspace/php-opencloud": "^1.9.2"
},
"suggest": {
"amazonwebservices/aws-sdk-for-php": "to use the legacy Amazon S3 adapters",
"aws/aws-sdk-php": "to use the Amazon S3 adapter",
"doctrine/dbal": "to use the Doctrine DBAL adapter",
"dropbox-php/dropbox-php": "to use the Dropbox adapter",
"ext-apc": "to use the APC adapter",
"ext-curl": "*",
"ext-fileinfo": "This extension is used to automatically detect the content-type of a file in the AwsS3, OpenCloud, AzureBlogStorage and GoogleCloudStorage adapters",
"ext-mbstring": "*",
"ext-mongo": "*",
"ext-zip": "to use the Zip adapter",
"google/apiclient": "to use GoogleCloudStorage adapter",
"herzult/php-ssh": "to use SFtp adapter",
"knplabs/knp-gaufrette-bundle": "to use with Symfony2",
"league/flysystem": "to use Flysystem adapters",
"microsoft/windowsazure": "to use Microsoft Azure Blob Storage adapter",
"phpseclib/phpseclib": "to use PhpseclibSftp adapter",
"rackspace/php-opencloud": "to use Opencloud adapter"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.4.x-dev"
}
},
"autoload": {
"psr-0": {
"Gaufrette": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "The contributors",
"homepage": "http://github.com/knplabs/Gaufrette/contributors"
},
{
"name": "KnpLabs Team",
"homepage": "http://knplabs.com"
}
],
"description": "PHP5 library that provides a filesystem abstraction layer",
"homepage": "http://knplabs.com",
"keywords": [
"abstraction",
"file",
"filesystem",
"media"
],
"time": "2017-03-20T01:23:34+00:00"
},
{
"name": "knplabs/knp-components",
"version": "1.3.4",
@ -1124,6 +1328,64 @@
],
"time": "2016-12-06T18:10:24+00:00"
},
{
"name": "knplabs/knp-gaufrette-bundle",
"version": "v0.4.0",
"source": {
"type": "git",
"url": "https://github.com/KnpLabs/KnpGaufretteBundle.git",
"reference": "06d91a8a575773cd0361c1246c9c499b6bdd5d68"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/KnpLabs/KnpGaufretteBundle/zipball/06d91a8a575773cd0361c1246c9c499b6bdd5d68",
"reference": "06d91a8a575773cd0361c1246c9c499b6bdd5d68",
"shasum": ""
},
"require": {
"knplabs/gaufrette": "~0.1.7|~0.2|~0.3",
"symfony/framework-bundle": "~2.0|~3.0"
},
"require-dev": {
"phpunit/phpunit": "~4.2",
"symfony/console": "~2.0|~3.0",
"symfony/yaml": "~2.0|~3.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "0.4.x-dev"
}
},
"autoload": {
"psr-4": {
"Knp\\Bundle\\GaufretteBundle\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "The contributors",
"homepage": "https://github.com/knplabs/KnpGaufretteBundle/contributors"
},
{
"name": "Antoine Hérault",
"email": "antoine.herault@gmail.com"
}
],
"description": "Allows to easily use the Gaufrette library in a Symfony project",
"homepage": "http://knplabs.com",
"keywords": [
"abstraction",
"file",
"filesystem",
"media"
],
"time": "2017-03-16T21:01:25+00:00"
},
{
"name": "knplabs/knp-menu",
"version": "2.2.0",
@ -1394,16 +1656,16 @@
},
{
"name": "lexik/form-filter-bundle",
"version": "v5.0.3",
"version": "v5.0.4",
"source": {
"type": "git",
"url": "https://github.com/lexik/LexikFormFilterBundle.git",
"reference": "124a6c8e9eb109e7616a18d916bbc33137bb308d"
"reference": "28c09d6d9f278875ca9648c4b66eeb457c37d3b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lexik/LexikFormFilterBundle/zipball/124a6c8e9eb109e7616a18d916bbc33137bb308d",
"reference": "124a6c8e9eb109e7616a18d916bbc33137bb308d",
"url": "https://api.github.com/repos/lexik/LexikFormFilterBundle/zipball/28c09d6d9f278875ca9648c4b66eeb457c37d3b6",
"reference": "28c09d6d9f278875ca9648c4b66eeb457c37d3b6",
"shasum": ""
},
"require": {
@ -1444,13 +1706,113 @@
"description": "This bundle aim to provide classes to build some form filters and then build a doctrine query from this form filter.",
"homepage": "https://github.com/lexik/LexikFormFilterBundle",
"keywords": [
"Symfony2",
"bundle",
"doctrine",
"filter",
"form"
"form",
"symfony"
],
"time": "2017-01-24T13:03:45+00:00"
"time": "2017-03-27T07:28:34+00:00"
},
{
"name": "liip/imagine-bundle",
"version": "1.7.4",
"source": {
"type": "git",
"url": "https://github.com/liip/LiipImagineBundle.git",
"reference": "105dd9c3446e3eb44e33161d4e636a3abafb6d7f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/105dd9c3446e3eb44e33161d4e636a3abafb6d7f",
"reference": "105dd9c3446e3eb44e33161d4e636a3abafb6d7f",
"shasum": ""
},
"require": {
"imagine/imagine": "^0.6.3,<0.7",
"php": "^5.3.9|^7.0",
"symfony/asset": "~2.3|~3.0",
"symfony/filesystem": "~2.3|~3.0",
"symfony/finder": "~2.3|~3.0",
"symfony/framework-bundle": "~2.3|~3.0",
"symfony/options-resolver": "~2.3|~3.0",
"symfony/process": "~2.3|~3.0",
"symfony/templating": "~2.3|~3.0",
"symfony/translation": "~2.3|~3.0"
},
"require-dev": {
"amazonwebservices/aws-sdk-for-php": "~1.0",
"aws/aws-sdk-php": "~2.4",
"doctrine/cache": "~1.1",
"doctrine/orm": "~2.3",
"ext-gd": "*",
"friendsofphp/php-cs-fixer": "~2.0",
"phpunit/phpunit": "~4.3|~5.0",
"psr/log": "~1.0",
"satooshi/php-coveralls": "~1.0",
"sllh/php-cs-fixer-styleci-bridge": "~2.1",
"symfony/browser-kit": "~2.3|~3.0",
"symfony/console": "~2.3|~3.0",
"symfony/dependency-injection": "~2.3|~3.0",
"symfony/form": "~2.3|~3.0",
"symfony/phpunit-bridge": "~2.3|~3.0",
"symfony/validator": "~2.3|~3.0",
"symfony/yaml": "~2.3|~3.0",
"twig/twig": "~1.12|~2.0"
},
"suggest": {
"alcaeus/mongo-php-adapter": "required on PHP >= 7.0 to use mongo components with mongodb extension",
"amazonwebservices/aws-sdk-for-php": "required to use AWS version 1 cache resolver",
"aws/aws-sdk-php": "required to use AWS version 2/3 cache resolver",
"doctrine/mongodb-odm": "required to use mongodb-backed doctrine components",
"ext-exif": "required to read EXIF metadata from images",
"ext-gd": "required to use gd driver",
"ext-gmagick": "required to use gmagick driver",
"ext-imagick": "required to use imagick driver",
"ext-mongo": "required for mongodb components on PHP <7.0",
"ext-mongodb": "required for mongodb components on PHP >=7.0",
"league/flysystem": "required to use FlySystem data loader or cache resolver",
"monolog/monolog": "A psr/log compatible logger is required to enable logging",
"twig/twig": "required to use the provided Twig extension. Version 1.12 or greater needed"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-1.0": "1.7-dev"
}
},
"autoload": {
"psr-4": {
"Liip\\ImagineBundle\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Liip and other contributors",
"homepage": "https://github.com/liip/LiipImagineBundle/contributors"
}
],
"description": "This bundle provides an image manipulation abstraction toolkit for Symfony-based projects.",
"homepage": "http://liip.ch",
"keywords": [
"bundle",
"image",
"imagine",
"liip",
"manipulation",
"photos",
"pictures",
"symfony",
"transformation"
],
"time": "2017-03-02T20:18:55+00:00"
},
{
"name": "monolog/monolog",
@ -1532,16 +1894,16 @@
},
{
"name": "oneup/flysystem-bundle",
"version": "1.11.0",
"version": "1.12.0",
"source": {
"type": "git",
"url": "https://github.com/1up-lab/OneupFlysystemBundle.git",
"reference": "a68f83415e3af2313c529be6b22bfddfcfe8e90f"
"reference": "2addd1077360790a7722fef09388a003576d585c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/1up-lab/OneupFlysystemBundle/zipball/a68f83415e3af2313c529be6b22bfddfcfe8e90f",
"reference": "a68f83415e3af2313c529be6b22bfddfcfe8e90f",
"url": "https://api.github.com/repos/1up-lab/OneupFlysystemBundle/zipball/2addd1077360790a7722fef09388a003576d585c",
"reference": "2addd1077360790a7722fef09388a003576d585c",
"shasum": ""
},
"require": {
@ -1562,6 +1924,7 @@
"league/flysystem-ziparchive": "~1.0",
"litipk/flysystem-fallback-adapter": "~0.1",
"phpunit/phpunit": "^4.4",
"superbalist/flysystem-google-storage": "~4.0",
"symfony/asset": "~2.0|~3.0",
"symfony/browser-kit": "~2.0|~3.0",
"symfony/finder": "~2.0|~3.0",
@ -1583,6 +1946,7 @@
"league/flysystem-webdav": "Allows you to use WebDAV storage",
"league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
"litipk/flysystem-fallback-adapter": "Allows you to use a fallback filesystem",
"superbalist/flysystem-google-storage": "Allows you to use Google Cloud Storage buckets",
"twistor/flysystem-stream-wrapper": "Allows you to use stream wrapper"
},
"type": "symfony-bundle",
@ -1611,7 +1975,7 @@
"abstraction",
"filesystem"
],
"time": "2017-03-03T13:41:53+00:00"
"time": "2017-03-27T08:48:46+00:00"
},
{
"name": "paragonie/random_compat",
@ -1928,19 +2292,20 @@
},
{
"name": "sensiolabs/security-checker",
"version": "v4.0.2",
"version": "v4.0.4",
"source": {
"type": "git",
"url": "https://github.com/sensiolabs/security-checker.git",
"reference": "56bded66985e22f6eac2cf86735fd21c625bff2f"
"reference": "9e69eddf3bc49d1ee5c7908564da3141796d4bbc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/56bded66985e22f6eac2cf86735fd21c625bff2f",
"reference": "56bded66985e22f6eac2cf86735fd21c625bff2f",
"url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/9e69eddf3bc49d1ee5c7908564da3141796d4bbc",
"reference": "9e69eddf3bc49d1ee5c7908564da3141796d4bbc",
"shasum": ""
},
"require": {
"composer/ca-bundle": "^1.0",
"symfony/console": "~2.7|~3.0"
},
"bin": [
@ -1968,7 +2333,7 @@
}
],
"description": "A security checker for your composer.lock",
"time": "2017-03-09T17:33:20+00:00"
"time": "2017-03-31T14:50:32+00:00"
},
{
"name": "swiftmailer/swiftmailer",
@ -2482,16 +2847,16 @@
},
{
"name": "symfony/symfony",
"version": "v3.2.6",
"version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/symfony.git",
"reference": "b0f8a7fa4b8baadf9db299cb6ac87c96a8977dbe"
"reference": "1631040ea8fc5e0e405c00d35cbf2c0b7b555f64"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/symfony/zipball/b0f8a7fa4b8baadf9db299cb6ac87c96a8977dbe",
"reference": "b0f8a7fa4b8baadf9db299cb6ac87c96a8977dbe",
"url": "https://api.github.com/repos/symfony/symfony/zipball/1631040ea8fc5e0e405c00d35cbf2c0b7b555f64",
"reference": "1631040ea8fc5e0e405c00d35cbf2c0b7b555f64",
"shasum": ""
},
"require": {
@ -2622,7 +2987,7 @@
"keywords": [
"framework"
],
"time": "2017-03-10T18:35:48+00:00"
"time": "2017-04-05T12:52:29+00:00"
},
{
"name": "twig/twig",
@ -2690,49 +3055,59 @@
"packages-dev": [
{
"name": "friendsofphp/php-cs-fixer",
"version": "v2.1.2",
"version": "v2.2.0",
"source": {
"type": "git",
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
"reference": "c7de769d7b44f2c9de68e1f678b65efd8126f60b"
"reference": "d6f17423412d33df6b69c9aaf12037b91703533b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/c7de769d7b44f2c9de68e1f678b65efd8126f60b",
"reference": "c7de769d7b44f2c9de68e1f678b65efd8126f60b",
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/d6f17423412d33df6b69c9aaf12037b91703533b",
"reference": "d6f17423412d33df6b69c9aaf12037b91703533b",
"shasum": ""
},
"require": {
"doctrine/annotations": "^1.2",
"ext-tokenizer": "*",
"php": "^5.3.6 || >=7.0 <7.2",
"sebastian/diff": "^1.1",
"symfony/console": "^2.3 || ^3.0",
"symfony/console": "^2.4 || ^3.0",
"symfony/event-dispatcher": "^2.1 || ^3.0",
"symfony/filesystem": "^2.4 || ^3.0",
"symfony/finder": "^2.2 || ^3.0",
"symfony/options-resolver": "^2.6 || ^3.0",
"symfony/polyfill-php54": "^1.0",
"symfony/polyfill-php55": "^1.3",
"symfony/polyfill-php70": "^1.0",
"symfony/polyfill-xml": "^1.3",
"symfony/process": "^2.3 || ^3.0",
"symfony/stopwatch": "^2.5 || ^3.0"
},
"conflict": {
"hhvm": "<3.9"
"hhvm": "<3.18"
},
"require-dev": {
"gecko-packages/gecko-php-unit": "^2.0",
"justinrainbow/json-schema": "^5.0",
"phpunit/phpunit": "^4.5 || ^5.0",
"satooshi/php-coveralls": "^1.0",
"symfony/phpunit-bridge": "^3.2"
"symfony/phpunit-bridge": "^3.2.2"
},
"suggest": {
"ext-xml": "For better performance."
"ext-mbstring": "For handling non-UTF8 characters in cache signature.",
"ext-xml": "For better performance.",
"symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible."
},
"bin": [
"php-cs-fixer"
],
"type": "application",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
}
},
"autoload": {
"psr-4": {
"PhpCsFixer\\": "src/"
@ -2753,7 +3128,7 @@
}
],
"description": "A tool to automatically fix PHP code style",
"time": "2017-03-15T17:13:07+00:00"
"time": "2017-03-31T16:16:30+00:00"
},
{
"name": "ircmaxell/password-compat",
@ -2905,7 +3280,7 @@
},
{
"name": "symfony/phpunit-bridge",
"version": "v3.2.6",
"version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/phpunit-bridge.git",

View File

@ -4,6 +4,7 @@ var gulp = require('gulp'),
gulp.task('css', function() {
return gulp.src([
'node_modules/semantic-ui/dist/semantic.css',
'node_modules/lightbox2/dist/css/lightbox.css',
'app/Resources/assets/style/style.scss',
])
.pipe(plugins.sass().on('error', plugins.sass.logError))
@ -31,23 +32,35 @@ gulp.task('js', function() {
return gulp.src([
'node_modules/jquery/dist/jquery.js',
'node_modules/semantic-ui/dist/semantic.js',
'node_modules/lightbox2/dist/js/lightbox.js',
'app/Resources/assets/js/**.js',
'node_modules/three/examples/js/libs/stats.min.js'
])
.pipe(plugins.concat('main.js'))
.pipe(gulp.dest('web/resources/js'));
});
gulp.task('files', function () {
return gulp.src('node_modules/semantic-ui/dist/themes/**')
.pipe(plugins.newer('web/resources/css/themes'))
gulp.task('files:semantic', function () {
return gulp.src(
'node_modules/semantic-ui/dist/themes/**'
)
.pipe(gulp.dest('web/resources/css/themes'));
});
gulp.task('files:images', function () {
return gulp.src([
'node_modules/lightbox2/dist/images/**',
'app/Resources/assets/images/**'
])
.pipe(gulp.dest('web/resources/images'));
});
gulp.task('watch', ['js', 'css', 'three'], function () {
gulp.watch('app/Resources/assets/js/**.js' , ['js']);
gulp.watch('app/Resources/assets/style/**/*.sass' , ['css']);
gulp.watch('app/Resources/assets/style/**/*.scss' , ['css']);
});
gulp.task('default', function () {
return gulp.start(['files', 'js', 'css', 'three']);
return gulp.start(['files:semantic', 'files:images', 'js', 'css', 'three']);
});

View File

@ -11,6 +11,7 @@
"gulp-sass": "^2.3.2",
"gulp-watch": "^4.3.11",
"jquery": "^3.2.1",
"lightbox2": "^2.9.0",
"semantic-ui": "^2.2.9",
"three": "^0.84.0"
},

View File

@ -13,7 +13,6 @@ use AppBundle\Api\Exception\ApiException;
use AppBundle\Api\Exception\AuthenticationFailedException;
use AppBundle\Api\Exception\CallFailedException;
use AppBundle\Api\Exception\EmptyResponseException;
use Symfony\Component\Asset\Exception\LogicException;
use Symfony\Component\Debug\Exception\ContextErrorException;
class Brickset extends \SoapClient
@ -47,6 +46,7 @@ class Brickset extends \SoapClient
$this->apiKey = $apikey;
$options['cache_wsdl'] = WSDL_CACHE_NONE;
$options['exceptions'] = true;
foreach (self::$classmap as $key => $value) {
if (!isset($options['classmap'][$key])) {
@ -56,7 +56,12 @@ class Brickset extends \SoapClient
if (!$wsdl) {
$wsdl = self::WSDL;
}
try {
parent::__construct($wsdl, $options);
} catch (\Exception $exception) {
throw new ApiException(ApiException::BRICKSET);
}
}
/**
@ -73,11 +78,14 @@ class Brickset extends \SoapClient
try {
$this->checkApiKey();
return $this->__soapCall($method, [$parameters])->{$method.'Result'};
} catch (\SoapFault $e) {
throw new CallFailedException(ApiException::BRICKSET);
} catch (ContextErrorException $e) {
throw new EmptyResponseException(ApiException::BRICKSET);
} catch (\Exception $e) {
throw new ApiException(ApiException::BRICKSET);
}
}
@ -105,7 +113,7 @@ class Brickset extends \SoapClient
$response = $this->call('getSets', $parameters)->sets;
return is_array($response) ? $response : [$response];
return is_array($response) ? $response : [$this->getSet($response->getSetID())];
}
/**
@ -258,7 +266,7 @@ class Brickset extends \SoapClient
{
$parameters['apiKey'] = $this->apiKey;
if($this->__soapCall('checkKey', [$parameters])->checkKeyResult != 'OK') {
if ($this->__soapCall('checkKey', [$parameters])->checkKeyResult != 'OK') {
throw new AuthenticationFailedException(ApiException::BRICKSET);
}
}

View File

@ -9,7 +9,6 @@ use AppBundle\Api\Exception\EmptyResponseException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use Symfony\Component\Asset\Exception\LogicException;
class Rebrickable_v3
{
@ -59,11 +58,11 @@ class Rebrickable_v3
} catch (ClientException $e) {
if ($e->getCode() == 404) {
throw new EmptyResponseException(ApiException::REBRICKABLE);
} else if ($e->getCode() == 401) {
} elseif ($e->getCode() == 401) {
throw new AuthenticationFailedException(ApiException::REBRICKABLE);
}
throw new ApiException(ApiException::REBRICKABLE,$e,$e->getCode());
throw new ApiException(ApiException::REBRICKABLE, $e, $e->getCode());
}
}
}

View File

@ -47,4 +47,19 @@ class BricksetManager
return isset($sets[0]) ? $sets[0] : null;
}
public function getSetInstructions($id)
{
return $this->bricksetClient->getInstructions($id);
}
public function getSetReviews($id)
{
return $this->bricksetClient->getReviews($id);
}
public function getAdditionalImages($id)
{
return $this->bricksetClient->getAdditionalImages($id);
}
}

View File

@ -119,18 +119,19 @@ class RebrickableManager
return $this->serializer->deserialize($data, PartCategory::class, self::FORMAT);
}
public function getPartsByLDrawNumber($number) {
public function getPartsByLDrawNumber($number)
{
$options = [
'query' => [
'ldraw_id' => $number
'ldraw_id' => $number,
],
];
$response = $this->rebrickableClient->call('GET','lego/parts', $options);
$response = $this->rebrickableClient->call('GET', 'lego/parts', $options);
$data = json_decode($response, true)['results'];
return $this->serializer->denormalize($data,Part::class.'[]',self::FORMAT);
return $this->serializer->denormalize($data, Part::class.'[]', self::FORMAT);
}
/**

View File

@ -1,39 +0,0 @@
<?php
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class LoadLDRawLibraryCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('app:load:ldraw')
->setDescription('Loads LDraw library parts')
->setHelp('This command allows you to..')
->addArgument('ldraw_path', InputArgument::OPTIONAL, 'Path to LDraw library folder');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$ldrawLoader = $this->getContainer()->get('service.loader.ldraw');
$ldrawLoader->setOutput($output);
//TODO log errors
try {
// TODO handle relative path to dir
if (($ldrawPath = $input->getArgument('ldraw_path')) == null) {
$ldrawPath = $ldrawLoader->downloadLibrary();
}
$ldrawLoader->loadData($ldrawPath);
} catch (\Exception $e) {
printf($e->getMessage());
}
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Command\LockableTrait;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class LoadModelsCommand extends ContainerAwareCommand
{
use LockableTrait;
protected function configure()
{
$this
->setName('app:load:models')
->setDescription('Loads LDraw library models into database')
->setHelp('This command allows you to load LDraw library models into database while converting .dat files to .stl format.')
->setDefinition(
new InputDefinition([
new InputArgument('ldraw', InputArgument::REQUIRED, 'Path to LDraw library directory'),
// new InputOption('images', 'i', InputOption::VALUE_NONE, 'Do you want to generate images of models?'),
new InputOption('all', 'a', InputOption::VALUE_NONE, 'Load all models from LDraw libary folder (/parts directory)'),
new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'Load single modle into database'),
new InputOption('update', 'u', InputOption::VALUE_NONE, 'Overwrite already loaded models'),
])
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
if (!$this->lock()) {
$output->writeln('The command is already running in another process.');
return 0;
}
$modelLoader = $this->getContainer()->get('service.loader.model');
$modelLoader->setOutput($output);
$modelLoader->setRewite($input->getOption('update'));
$ldraw = $input->getArgument('ldraw');
if ($ldrawPath = realpath($ldraw)) {
$modelLoader->setLDrawLibraryContext($ldrawPath);
if (($path = $input->getOption('file')) != null) {
if ($file = realpath($path)) {
$output->writeln([
'Loading model',
'path: '.$file,
'------------------------------------------------------------------------------',
]);
$modelLoader->loadOneModel($file);
$errorCount = $this->getContainer()->get('monolog.logger.loader')->countErrors();
$errors = $errorCount ? '<error>'.$errorCount.'</error>' : '<info>0</info>';
$output->writeln(['Done with "'.$errors.'" errors.']);
} else {
$output->writeln("File $path not found");
}
}
// Load all models inside ldraw/parts directory
if ($input->getOption('all')) {
$output->writeln([
'Loading models from LDraw library: <comment>'.$ldrawPath.'</comment>',
]);
$modelLoader->loadAllModels();
$errorCount = $this->getContainer()->get('monolog.logger.loader')->countErrors();
$errors = $errorCount ? '<error>'.$errorCount.'</error>' : '<info>0</info>';
$output->writeln(['Done with "'.$errors.'" errors.']);
}
} else {
$output->writeln($ldraw.' is not valid path');
}
$this->release();
}
}

View File

@ -12,8 +12,8 @@ class LoadRebrickableDataCommand extends ContainerAwareCommand
{
$this
->setName('app:load:rebrickable')
->setDescription('Loads Rebrickable database')
->setHelp('This command allows you to..');
->setDescription('Loads Rebrickable data about sets and parts into database.')
->setHelp('This command allows you to load Rebrickable CSV files containing information about sets and parts into database.');
}
protected function execute(InputInterface $input, OutputInterface $output)

View File

@ -12,8 +12,8 @@ class LoadRelationCommand extends ContainerAwareCommand
{
$this
->setName('app:load:relations')
->setDescription('Loads relations between LDraw models and Rebrickable parts')
->setHelp('This command allows you to..');
->setDescription('Loads relations between LDraw models and Rebrickable parts.')
->setHelp('This command allows you to load relation between models and parts into database.');
}
protected function execute(InputInterface $input, OutputInterface $output)

View File

@ -1,10 +1,8 @@
<?php
namespace AppBundle\Controller\Rebrickable;
namespace AppBundle\Controller\Brickset;
use AppBundle\Entity\Rebrickable\Color;
use AppBundle\Entity\Rebrickable\Inventory_Part;
use AppBundle\Entity\Rebrickable\Part;
use AppBundle\Api\Exception\EmptyResponseException;
use AppBundle\Entity\Rebrickable\Set;
use AppBundle\Entity\Rebrickable\Theme;
use AppBundle\Form\FilterSetType;
@ -13,12 +11,12 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
/**
* @Route("/brickset")
* @Route("/brickset/sets")
*/
class SetController extends Controller
{
/**
* @Route("/", name="set_browse")
* @Route("/", name="brickset_browse")
*/
public function browseAction(Request $request)
{
@ -30,11 +28,17 @@ class SetController extends Controller
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
try {
$sets = $this->get('api.client.brickset')->getSets([
'theme' => $data['theme'] ? $data['theme']->getTheme() : '',
'subtheme' => $data['subtheme'] ? $data['subtheme']->getSubtheme() : '',
'year' => $data['years'] ? $data['years']->getYear() : '',
]);
} catch (EmptyResponseException $e) {
$this->addFlash('warning', 'No set found on '.$e->getService());
} catch (\Exception $e) {
$this->addFlash('error', $e->getMessage());
}
}
return $this->render('brickset/browse.html.twig', [
@ -42,4 +46,61 @@ class SetController extends Controller
'sets' => $sets,
]);
}
/**
* @Route("/{id}/instructions", name="brickset_instructions")
*/
public function instructionsAction(Request $request, $id)
{
$instructions = [];
try {
$instructions = $this->get('api.manager.brickset')->getSetInstructions($id);
} catch (EmptyResponseException $e) {
// $this->addFlash('warning', 'No instruction found on Brickset.com');
} catch (\Exception $e) {
$this->addFlash('error', $e->getMessage());
}
return $this->render('brickset/instructions.html.twig', [
'instructions' => $instructions,
]);
}
/**
* @Route("/{id}/reviews", name="brickset_reviews")
*/
public function reviewsAction(Request $request, $id)
{
$reviews = [];
try {
$reviews = $this->get('api.manager.brickset')->getSetReviews($id);
} catch (EmptyResponseException $e) {
// $this->addFlash('warning', 'No review found on Brickset.com');
} catch (\Exception $e) {
$this->addFlash('error', $e->getMessage());
}
return $this->render('brickset/reviews.html.twig', [
'reviews' => $reviews,
]);
}
/**
* @Route("/{id}/images", name="brickset_images")
*/
public function imagesAction(Request $request, $id)
{
$images = [];
try {
$images = $this->get('api.manager.brickset')->getAdditionalImages($id);
} catch (EmptyResponseException $e) {
// $this->addFlash('warning', 'No images found on Brickset.com');
} catch (\Exception $e) {
$this->addFlash('error', $e->getMessage());
}
return $this->render('brickset/images.html.twig', [
'images' => $images,
]);
}
}

View File

@ -2,7 +2,6 @@
namespace AppBundle\Controller;
use AppBundle\Entity\LDraw\Model;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
@ -11,59 +10,33 @@ use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/media")
* @Route("/files")
*/
class MediaController extends Controller
{
/**
* @Route("/model/stl/{number}", name="model_stl")
* @Route("/{path}", name="media_file", requirements={"path"=".+"})
*
* @return Response
*/
public function stlAction(Model $model)
public function stlAction($path)
{
$mediaFilesystem = $this->get('oneup_flysystem.media_filesystem');
if ($mediaFilesystem->has($model->getPath())) {
$response = new BinaryFileResponse($mediaFilesystem->getAdapter()->getPathPrefix().DIRECTORY_SEPARATOR.$model->getPath());
$response->headers->set('Content-Type', 'application/vnd.ms-pki.stl');
if ($mediaFilesystem->has($path)) {
$response = new BinaryFileResponse($mediaFilesystem->getAdapter()->getPathPrefix().DIRECTORY_SEPARATOR.$path);
$response->headers->set('Content-Type', $mediaFilesystem->getMimetype($path));
// Create the disposition of the file
$disposition = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$model->getNumber().'.stl'
basename($path)
);
$response->headers->set('Content-Disposition', $disposition);
return $response;
}
throw new FileNotFoundException($model->getPath());
}
/**
* @Route("/model/image/{number}", name="model_image")
*
* @return Response
*/
public function imageAction(Model $model)
{
$mediaFilesystem = $this->get('oneup_flysystem.media_filesystem');
if ($mediaFilesystem->has('ldraw'.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.$model->getNumber().'.png')) {
$response = new BinaryFileResponse($mediaFilesystem->getAdapter()->getPathPrefix().DIRECTORY_SEPARATOR.'ldraw'.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.$model->getNumber().'.png');
$response->headers->set('Content-Type', 'image/png');
// Create the disposition of the file
$disposition = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$model->getNumber().'png'
);
$response->headers->set('Content-Disposition', $disposition);
return $response;
}
throw new FileNotFoundException($model->getNumber().'png');
throw new FileNotFoundException($path);
}
}

View File

@ -1,11 +1,11 @@
<?php
namespace AppBundle\Controller\LDraw;
namespace AppBundle\Controller;
use AppBundle\Entity\LDraw\Model;
use AppBundle\Entity\Rebrickable\Part;
use AppBundle\Entity\Rebrickable\Set;
use AppBundle\Form\Filter\ModelFilterType;
use AppBundle\Form\Filter\Model\ModelFilterType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
@ -14,24 +14,21 @@ use Symfony\Component\HttpFoundation\Request;
/**
* Part controller.
*
* @Route("ldraw")
* @Route("models")
*/
class ModelController extends Controller
{
/**
* Lists all part entities.
*
* @Route("/models/", name="ldraw_model_index")
* @Route("/", name="model_index")
* @Method("GET")
*/
public function indexAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$form = $this->get('form.factory')->create(ModelFilterType::class);
$filterBuilder = $this->get('repository.ldraw.model')
->createQueryBuilder('model');
$filterBuilder = $this->get('repository.ldraw.model')->getFilteredQueryBuilder();
if ($request->query->has($form->getName())) {
// manually bind values from the request
@ -45,10 +42,10 @@ class ModelController extends Controller
$models = $paginator->paginate(
$filterBuilder->getQuery(),
$request->query->getInt('page', 1)/*page number*/,
$request->query->getInt('limit', 100)/*limit per page*/
$request->query->getInt('limit', 40)/*limit per page*/
);
return $this->render('ldraw/model/index.html.twig', [
return $this->render('model/index.html.twig', [
'models' => $models,
'form' => $form->createView(),
]);
@ -57,27 +54,29 @@ class ModelController extends Controller
/**
* Finds and displays a part entity.
*
* @Route("/models/{number}", name="model_detail")
* @Route("/{number}", name="model_detail")
* @Method("GET")
*/
public function detailAction($number)
{
$em = $this->getDoctrine()->getManager();
if($model = $this->get('manager.ldraw.model')->findByNumber($number)) {
if ($model = $this->get('repository.ldraw.model')->findOneByNumber($number)) {
try {
$rbParts = $model != null ? $em->getRepository(Part::class)->findAllByModel($model) : null;
$sets = $model != null ? $em->getRepository(Set::class)->findAllByModel($model) : null;
return $this->render('ldraw/model/detail.html.twig', [
$related = $em->getRepository(Model::class)->findAllRelatedModels($model->getNumber());
return $this->render('model/detail.html.twig', [
'model' => $model,
'rbParts' => $rbParts,
'sets' => $sets,
'related' => $related,
]);
} catch (\Exception $e) {
$this->addFlash('error', $e->getMessage());
}
}
return $this->render('error/error.html.twig');

View File

@ -3,8 +3,8 @@
namespace AppBundle\Controller\Rebrickable;
use AppBundle\Entity\Rebrickable\Color;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
/**
* Part controller.
@ -14,10 +14,10 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class ColorController extends Controller
{
/**
*
* @Route("/", name="color_index")
*/
public function indexAction() {
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$colors = $em->getRepository(Color::class)->findAll();

View File

@ -3,11 +3,13 @@
namespace AppBundle\Controller\Rebrickable;
use AppBundle\Api\Exception\EmptyResponseException;
use AppBundle\Entity\Rebrickable\Category;
use AppBundle\Entity\Rebrickable\Part;
use AppBundle\Entity\Rebrickable\Set;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
/**
* Part controller.
@ -16,19 +18,41 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
*/
class PartController extends Controller
{
/**
* @Route("/", name="part_index")
*/
public function indexAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$queryBuilder = $em->getRepository(Part::class)->createQueryBuilder('part');
$queryBuilder->where('part.category = 17');
$paginator = $this->get('knp_paginator');
$sets = $paginator->paginate(
$queryBuilder->getQuery(),
$request->query->getInt('page', 1)/*page number*/,
$request->query->getInt('limit', 30)/*limit per page*/
);
return $this->render(':rebrickable/part:index.html.twig', [
'parts' => $sets,
]);
}
/**
* Finds and displays a part entity.
*
* @Route("/{number}", name="rebrickable_part_show")
* @Route("/{number}", name="reb_part_detail")
* @Method("GET")
*/
public function showAction(Part $part = null)
public function detailAction(Part $part)
{
$em = $this->getDoctrine()->getManager();
$apiPart = null;
if($part) {
if ($part) {
try {
$apiPart = $this->get('api.manager.rebrickable')->getPart($part->getNumber());
} catch (EmptyResponseException $e) {

View File

@ -2,17 +2,13 @@
namespace AppBundle\Controller\Rebrickable;
use AppBundle\Api\Exception\EmptyResponseException;
use AppBundle\Entity\LDraw\Model;
use AppBundle\Entity\Rebrickable\Color;
use AppBundle\Entity\Rebrickable\Inventory_Part;
use AppBundle\Entity\Rebrickable\Inventory_Set;
use AppBundle\Entity\Rebrickable\Part;
use AppBundle\Entity\Rebrickable\Set;
use AppBundle\Entity\Rebrickable\Theme;
use AppBundle\Form\FilterSetType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
@ -21,87 +17,46 @@ use Symfony\Component\HttpFoundation\Response;
class SetController extends Controller
{
/**
* @Route("/", name="set_index")
* @Route("/{number}/parts", name="rebrickable_set_parts")
*/
public function indexAction(Request $request)
public function partsAction(Set $set)
{
$em = $this->getDoctrine()->getManager();
$qb = $em->getRepository(Set::class)->createQueryBuilder('s');
$paginator = $this->get('knp_paginator');
$sets = $paginator->paginate(
$qb->getQuery(),
$request->query->getInt('page', 1)/*page number*/,
$request->query->getInt('limit', 30)/*limit per page*/
);
return $this->render('rebrickable/set/index.html.twig', [
'sets' => $sets,
]);
}
/**
* @Route("/detail/{number}_{name}", name="set_detail")
*/
public function detailAction(Request $request, $number, $name = null)
{
$brset = null;
try {
$brset = $this->get('api.manager.brickset')->getSetByNumber($number);
} catch (EmptyResponseException $e) {
$this->addFlash('warning', 'Set not found in Brickset database');
} catch (\Exception $e) {
$this->addFlash('error', $e->getMessage());
}
$set = $this->getDoctrine()->getManager()->getRepository(Set::class)->find($number);
$rbset = $this->get('api.manager.rebrickable')->getSet($number);
$em = $this->getDoctrine()->getManager();
$em->getRepository(Color::class)->findAll();
$em->getRepository(Theme::class)->findAll();
$em->getRepository(Part::class)->findAllBySetNumber($set->getNumber());
return $this->render('rebrickable/set/detail.html.twig', [
'set' => $set,
'brset' => $brset,
'rbset' => $rbset,
'parts' => $em->getRepository(Model::class)->findAllBySetNumber($number),
'inventoryParts' => $em->getRepository(Inventory_Part::class)->findAllBySetNumber($number),
$regularParts = $em->getRepository(Inventory_Part::class)->findAllRegularBySetNumber($set->getNumber());
$spareParts = $em->getRepository(Inventory_Part::class)->findAllSpareBySetNumber($set->getNumber());
$template = $this->render('rebrickable/set/parts.html.twig', [
'regularParts' => $regularParts,
'spareParts' => $spareParts,
]);
}
$json = json_encode($template->getContent());
$response = new Response($json, 200);
$response->headers->set('Content-Type', 'application/json');
return $response;
}
/**
* @Route("/download/{number}", name="set_download")
* @Route("/{number}/sets", name="rebrickable_set_sets")
*/
public function downloadZipAction(Request $request, $number) {
public function setsAction(Set $set)
{
$em = $this->getDoctrine()->getManager();
$inventoryParts = $em->getRepository(Inventory_Part::class)->findAllBySetNumber($number);
$inventorySets = $em->getRepository(Inventory_Set::class)->findAllBySetNumber($set->getNumber());
$zip = new \ZipArchive();
$zipName = 'set_'.$number.'.zip';
$zip->open($zipName, \ZipArchive::CREATE);
/** @var Inventory_Part $part */
foreach ($inventoryParts as $part) {
$filename = $part->getPart()->getNumber().'_('.$part->getColor()->getName().'_'.$part->getQuantity().'x).stl';
$template = $this->render('rebrickable/set/sets.html.twig', [
'inventorySets' => $inventorySets,
]);
try {
if($part->getPart()->getModel()) {
$zip->addFromString($filename, $this->get('oneup_flysystem.media_filesystem')->read($part->getPart()->getModel()->getPath()));
}
} catch (\Exception $e) {
dump($e);
}
}
$zip->close();
$response = new Response(file_get_contents($zipName));
$response->headers->set('Content-Type', 'application/zip');
$response->headers->set('Content-Disposition', 'attachment;filename="' . $zipName . '"');
$response->headers->set('Content-length', filesize($zipName));
$json = json_encode($template->getContent());
$response = new Response($json, 200);
$response->headers->set('Content-Type', 'application/json');
return $response;
}

View File

@ -0,0 +1,82 @@
<?php
namespace AppBundle\Controller;
use AppBundle\Api\Exception\ApiException;
use AppBundle\Api\Exception\EmptyResponseException;
use AppBundle\Entity\Rebrickable\Inventory_Part;
use AppBundle\Entity\Rebrickable\Set;
use AppBundle\Form\Filter\Set\SetFilterType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* @Route("/sets")
*/
class SetController extends Controller
{
/**
* @Route("/", name="set_index")
*/
public function indexAction(Request $request)
{
$form = $this->get('form.factory')->create(SetFilterType::class);
$filterBuilder = $this->get('repository.rebrickable.set')
->createQueryBuilder('s');
if ($request->query->has($form->getName())) {
// manually bind values from the request
$form->submit($request->query->get($form->getName()));
// build the query from the given form object
$this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($form, $filterBuilder);
}
$paginator = $this->get('knp_paginator');
$sets = $paginator->paginate(
$filterBuilder->getQuery(),
$request->query->getInt('page', 1)/*page number*/,
$request->query->getInt('limit', 30)/*limit per page*/
);
return $this->render('set/index.html.twig', [
'sets' => $sets,
'form' => $form->createView(),
]);
}
/**
* @Route("/{number}", name="set_detail")
*/
public function detailAction(Request $request, $number)
{
$rebrickableSet = null;
$bricksetSet = null;
try {
if (($rebrickableSet = $this->getDoctrine()->getManager()->getRepository(Set::class)->find($number)) == null) {
$this->addFlash('warning', 'Set not found in Rebrickable database');
}
$bricksetSet = $this->get('api.manager.brickset')->getSetByNumber($number);
dump($bricksetSet);
} catch (EmptyResponseException $e) {
$this->addFlash('warning', 'Set not found in Brickset database');
} catch (ApiException $e) {
$this->addFlash('error', $e->getService());
} catch (\Exception $e) {
$this->addFlash('error', $e->getMessage());
}
if (!$rebrickableSet && !$bricksetSet) {
return $this->render('error/error.html.twig');
}
return $this->render('set/detail.html.twig', [
'rbset' => $rebrickableSet,
'brset' => $bricksetSet,
]);
}
}

View File

@ -18,7 +18,7 @@ class Alias
/**
* @var Model
*
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\LDraw\Model", inversedBy="aliases", cascade={"persist"})
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\LDraw\Model", inversedBy="aliases")
*/
private $model;

View File

@ -0,0 +1,34 @@
<?php
namespace AppBundle\Entity\LDraw;
use AppBundle\Entity\Traits\IdentityTrait;
use AppBundle\Entity\Traits\UniqueNameTrait;
use Doctrine\ORM\Mapping as ORM;
/**
* Author.
*
* @ORM\Table(name="ldraw_author")
* @ORM\Entity(repositoryClass="AppBundle\Repository\LDraw\AuthorRepository")
*/
class Author
{
use IdentityTrait;
use UniqueNameTrait;
/**
* @var Author
*
* @ORM\OneToMany(targetEntity="AppBundle\Entity\LDraw\Model", mappedBy="author")
*/
private $models;
/**
* @return Author
*/
public function getModels()
{
return $this->models;
}
}

View File

@ -3,7 +3,7 @@
namespace AppBundle\Entity\LDraw;
use AppBundle\Entity\Traits\IdentityTrait;
use AppBundle\Entity\Traits\NameTrait;
use AppBundle\Entity\Traits\UniqueNameTrait;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
@ -17,7 +17,7 @@ use Doctrine\ORM\Mapping as ORM;
class Category
{
use IdentityTrait;
use NameTrait;
use UniqueNameTrait;
/**
* @var Collection

View File

@ -3,7 +3,7 @@
namespace AppBundle\Entity\LDraw;
use AppBundle\Entity\Traits\IdentityTrait;
use AppBundle\Entity\Traits\NameTrait;
use AppBundle\Entity\Traits\UniqueNameTrait;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
@ -16,7 +16,7 @@ use Doctrine\ORM\Mapping as ORM;
class Keyword
{
use IdentityTrait;
use NameTrait;
use UniqueNameTrait;
/**
* @var ArrayCollection

View File

@ -17,13 +17,6 @@ class Model
{
use NumberTrait;
/**
* @var Type
*
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\LDraw\Type", inversedBy="models", cascade={"persist"})
*/
private $type;
/**
* @var string
*
@ -48,7 +41,7 @@ class Model
/**
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="AppBundle\Entity\LDraw\Subpart", mappedBy="parent")
* @ORM\OneToMany(targetEntity="AppBundle\Entity\LDraw\Subpart", mappedBy="parent", cascade={"persist"})
*/
private $subparts;
@ -74,9 +67,9 @@ class Model
private $path;
/**
* @var string
* @var Author
*
* @ORM\Column(type="string", length=255, nullable=true)
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\LDraw\Author", cascade={"persist"}, inversedBy="models")
*/
private $author;
@ -122,7 +115,7 @@ class Model
/**
* Set author.
*
* @param string $author
* @param Author $author
*
* @return Model
*/
@ -136,7 +129,7 @@ class Model
/**
* Get author.
*
* @return string
* @return Author
*/
public function getAuthor()
{
@ -163,22 +156,6 @@ class Model
return $this;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @param string $type
*/
public function setType($type)
{
$this->type = $type;
}
/**
* @return string
*/
@ -292,6 +269,20 @@ class Model
return $this;
}
/**
* @param Subpart $subpart
*
* @return $this
*/
public function addSubpart($subpart)
{
if (!$this->subparts->contains($subpart)) {
$this->subparts->add($subpart);
}
return $this;
}
/**
* @return ArrayCollection
*/

View File

@ -16,7 +16,7 @@ class Subpart
* @var Model
*
* @ORM\Id
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\LDraw\Model", inversedBy="subparts", cascade={"persist"})
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\LDraw\Model", inversedBy="subparts")
*/
private $parent;

View File

@ -1,91 +0,0 @@
<?php
namespace AppBundle\Entity\LDraw;
use AppBundle\Entity\Traits\IdentityTrait;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* Type.
*
* @ORM\Table(name="ldraw_type")
* @ORM\Entity(repositoryClass="AppBundle\Repository\LDraw\TypeRepository")
*/
class Type
{
use IdentityTrait;
/**
* @var string
*
* @ORM\Column(type="string", length=60, unique=true)
*/
private $name;
/**
* @var Collection
*
* @ORM\OneToMany(targetEntity="Model", mappedBy="type")
*/
private $models;
/**
* BuildingKit constructor.
*/
public function __construct()
{
$this->models = new ArrayCollection();
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get models.
*
* @return ArrayCollection
*/
public function getModels()
{
return $this->models;
}
/**
* @param Model $model
*
* @return Type
*/
public function addModel(Model $model)
{
$this->models->add($model);
return $this;
}
/**
* @param Model $model
*
* @return Type
*/
public function removeModel(Model $model)
{
$this->models->remove($model);
return $this;
}
}

View File

@ -38,9 +38,17 @@ class Inventory
*/
protected $inventoryParts;
/**
* @var Collection
*
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Rebrickable\Inventory_Set", mappedBy="inventory")
*/
protected $inventorySets;
public function __construct()
{
$this->inventoryParts = new ArrayCollection();
$this->inventorySets = new ArrayCollection();
}
/**
@ -90,4 +98,20 @@ class Inventory
{
$this->inventoryParts = $inventoryParts;
}
/**
* @return Collection
*/
public function getInventorySets()
{
return $this->inventorySets;
}
/**
* @param Collection $inventorySets
*/
public function setInventorySets($inventorySets)
{
$this->inventorySets = $inventorySets;
}
}

View File

@ -28,7 +28,7 @@ class Inventory_Part
/**
* @var bool
*
* @ORM\Id
* @ORM\Column(type="boolean")
*/
protected $spare;

View File

@ -0,0 +1,81 @@
<?php
namespace AppBundle\Entity\Rebrickable;
use Doctrine\ORM\Mapping as ORM;
/**
* Inventory_Set.
*
* @ORM\Table(name="rebrickable_inventory_sets")
* @ORM\Entity(repositoryClass="AppBundle\Repository\Rebrickable\Inventory_SetRepository")
*/
class Inventory_Set
{
/**
* @var Inventory
* @ORM\Id
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Rebrickable\Inventory", inversedBy="inventorySets")
*/
protected $inventory;
/**
* @var Set
* @ORM\Id
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Rebrickable\Set", inversedBy="inventorySets")
*/
protected $set;
/**
* @var int
*
* @ORM\Column(type="integer")
*/
protected $quantity;
/**
* Get count.
*
* @return int
*/
public function getQuantity()
{
return $this->quantity;
}
/**
* @return Set
*/
public function getSet()
{
return $this->set;
}
/**
* @param Set $set
*/
public function setSet($set)
{
$this->set = $set;
}
/**
* @return Inventory
*/
public function getInventory()
{
return $this->inventory;
}
/**
* @param Inventory $inventory
*
* @return Inventory_Set
*/
public function setInventory(Inventory $inventory)
{
$this->inventory = $inventory;
return $this;
}
}

View File

@ -47,12 +47,20 @@ class Set
*/
protected $theme;
/**
* @var Collection
*
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Rebrickable\Inventory_Set", mappedBy="set")
*/
protected $inventorySets;
/**
* Set constructor.
*/
public function __construct()
{
$this->inventories = new ArrayCollection();
$this->inventorySets = new ArrayCollection();
}
/**
@ -133,6 +141,22 @@ class Set
return $this;
}
/**
* @return Collection
*/
public function getInventorySets()
{
return $this->inventorySets;
}
/**
* @param Collection $inventorySets
*/
public function setInventorySets($inventorySets)
{
$this->inventorySets = $inventorySets;
}
/**
* @return Theme
*/

View File

@ -0,0 +1,33 @@
<?php
namespace AppBundle\Entity\Traits;
trait UniqueNameTrait
{
/**
* @var string
*
* @ORM\Column(type="string", length=255, nullable=true, unique=true)
*/
protected $name;
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param string $name
*
* @return mixed
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace AppBundle\Exception;
use Exception;
class ConvertingFailedException extends \Exception
{
private $filepath;
public function __construct($form = '', $to = '', $message = '', $code = 0, Exception $previous = null)
{
$message = sprintf('Error converting "%s" file to "%s".', $form, $to);
parent::__construct($message, $code, $previous);
$this->filepath = $form;
}
/**
* @return mixed
*/
public function getFilepath()
{
return $this->filepath;
}
/**
* @param mixed $filepath
*/
public function setFilepath($filepath)
{
$this->filepath = $filepath;
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace AppBundle\Exception;
use Throwable;
class ErrorParsingLineException extends FileException
{
public function __construct($path, $line, $message = '', $code = 0, Throwable $previous = null)
{
$message = sprintf('Error parsing line \""%s"\" "%s" file.',$line, $path);
parent::__construct($path, $message, $code, $previous);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace AppBundle\Exception;
use Throwable;
class FileException extends \Exception
{
protected $path;
public function __construct($path, $message = '', $code = 0, Throwable $previous = null)
{
$this->path = $path;
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace AppBundle\Exception;
use Throwable;
class FileNotFoundException extends FileException
{
public function __construct($path, $message = '', $code = 0, Throwable $previous = null)
{
$message = sprintf('File "%s" not found.', $path);
parent::__construct($path, $message, $code, $previous);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace AppBundle\Exception;
use Throwable;
class ParseErrorException extends FileException
{
public function __construct($path, $message = '', $code = 0, Throwable $previous = null)
{
$message = sprintf('Error parsing "%s" file.', $path);
parent::__construct($path, $message, $code, $previous);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace AppBundle\Exception;
use Throwable;
class WriteErrorException extends FileException
{
public function __construct($file, $message = '', $code = 0, Throwable $previous = null)
{
$message = sprintf('Could not write into "%s" file.', $file);
parent::__construct($file, $message, $code, $previous);
}
}

View File

@ -1,9 +1,9 @@
<?php
namespace AppBundle\Form\Filter;
namespace AppBundle\Form\Filter\Model;
use AppBundle\Entity\LDraw\Category;
use AppBundle\Manager\LDraw\CategoryManager;
use AppBundle\Repository\LDraw\CategoryRepository;
use Lexik\Bundle\FormFilterBundle\Filter\Form\Type as Filters;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
@ -11,17 +11,17 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class CategoryFilterType extends AbstractType
{
private $categoryManager;
private $categoryRepository;
public function __construct(CategoryManager $categoryManager)
public function __construct(CategoryRepository $categoryRepository)
{
$this->categoryManager = $categoryManager;
$this->categoryRepository = $categoryRepository;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('id', Filters\ChoiceFilterType::class, [
'choices' => $this->categoryManager->findAll(),
'choices' => $this->categoryRepository->findAll(),
'choice_label' => 'name',
'label' => 'filter.part.category',
]);

View File

@ -1,6 +1,6 @@
<?php
namespace AppBundle\Form\Filter;
namespace AppBundle\Form\Filter\Model;
use Doctrine\ORM\QueryBuilder;
use Lexik\Bundle\FormFilterBundle\Filter\FilterBuilderExecuterInterface;

View File

@ -0,0 +1,58 @@
<?php
namespace AppBundle\Form\Filter\Set;
use Doctrine\ORM\QueryBuilder;
use Lexik\Bundle\FormFilterBundle\Filter\FilterBuilderExecuterInterface;
use Lexik\Bundle\FormFilterBundle\Filter\Form\Type as Filters;
use Lexik\Bundle\FormFilterBundle\Filter\Query\QueryInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SetFilterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('search', Filters\TextFilterType::class, [
'apply_filter' => [$this, 'setSearchCallback'],
'label' => 'filter.part.search',
]);
$builder->add('theme', ThemeFilterType::class, [
'add_shared' => function (FilterBuilderExecuterInterface $builderExecuter) {
$builderExecuter->addOnce($builderExecuter->getAlias().'.theme', 'c', function (QueryBuilder $filterBuilder, $alias, $joinAlias, $expr) {
$filterBuilder->leftJoin($alias.'.theme', $joinAlias);
});
},
]);
}
public function getBlockPrefix()
{
return 'model_filter';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => false,
'validation_groups' => ['filtering'], // avoid NotBlank() constraint-related message
]);
}
public function setSearchCallback(QueryInterface $filterQuery, $field, $values)
{
if (empty($values['value'])) {
return null;
}
// expression that represent the condition
$expression = $filterQuery->getExpr()->orX(
$filterQuery->getExpr()->like('s.name', ':value'),
$filterQuery->getExpr()->like('s.number', ':value')
);
return $filterQuery->createCondition($expression, ['value' => '%'.$values['value'].'%']);
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace AppBundle\Form\Filter\Set;
use AppBundle\Entity\Rebrickable\Theme;
use AppBundle\Repository\Rebrickable\ThemeRepository;
use Lexik\Bundle\FormFilterBundle\Filter\Form\Type as Filters;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ThemeFilterType extends AbstractType
{
private $themeRepository;
public function __construct(ThemeRepository $themeRepository)
{
$this->themeRepository = $themeRepository;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('id', Filters\ChoiceFilterType::class, [
'choices' => $this->themeRepository->findAll(),
'choice_label' => function ($theme, $currentChoiceKey) {
if ($parent = $theme->getParent()) {
if ($parentParent = $parent->getParent()) {
if ($parentParentParent = $parentParent->getParent()) {
return $parentParentParent->getName().' > '.$parentParent->getName().' > '.$parent->getName().' > '.$theme->getName();
}
return $parentParent->getName().' > '.$parent->getName().' > '.$theme->getName();
}
return $parent->getName().' > '.$theme->getName();
}
return $theme->getName();
},
'label' => 'filter.set.theme',
]);
}
public function getParent()
{
return Filters\SharedableFilterType::class; // this allow us to use the "add_shared" option
}
public function getBlockPrefix()
{
return 'theme_filter';
}
public function setDefaultOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Theme::class,
'csrf_protection' => false,
'validation_groups' => ['filtering'], // avoid NotBlank() constraint-related message
'method' => 'GET',
]);
}
}

View File

@ -1,16 +0,0 @@
<?php
namespace AppBundle\Manager;
class BaseManager
{
protected $repository;
/**
* @return mixed
*/
public function getRepository()
{
return $this->repository;
}
}

View File

@ -1,40 +0,0 @@
<?php
namespace AppBundle\Manager\LDraw;
use AppBundle\Entity\LDraw\Alias;
use AppBundle\Entity\LDraw\Model;
use AppBundle\Manager\BaseManager;
use AppBundle\Repository\LDraw\AliasRepository;
class AliasManager extends BaseManager
{
/**
* AliasManager constructor.
*
* @param AliasRepository $repository
*/
public function __construct(AliasRepository $repository)
{
$this->repository = $repository;
}
/**
* Create new Alias entity.
*
* @param $number
* @param Model $model
*
* @return Alias
*/
public function create($number, $model)
{
if (($alias = $this->repository->findOneBy(['number' => $number, 'model' => $model])) == null) {
$alias = new Alias();
$alias->setModel($model);
$alias->setNumber($number);
}
return $alias;
}
}

View File

@ -1,42 +0,0 @@
<?php
namespace AppBundle\Manager\LDraw;
use AppBundle\Entity\LDraw\Category;
use AppBundle\Manager\BaseManager;
use AppBundle\Repository\LDraw\CategoryRepository;
class CategoryManager extends BaseManager
{
/**
* CategoryManager constructor.
*
* @param CategoryRepository $repository
*/
public function __construct(CategoryRepository $repository)
{
$this->repository = $repository;
}
public function findAll()
{
return $this->repository->findAll();
}
/**
* Create new Category entity with $name or retrieve one.
*
* @param $name
*
* @return Category
*/
public function create($name)
{
if (($category = $this->repository->findByName($name)) == null) {
$category = new Category();
$category->setName($name);
}
return $category;
}
}

View File

@ -1,37 +0,0 @@
<?php
namespace AppBundle\Manager\LDraw;
use AppBundle\Entity\LDraw\Keyword;
use AppBundle\Manager\BaseManager;
use AppBundle\Repository\LDraw\KeywordRepository;
class KeywordManager extends BaseManager
{
/**
* KeywordManager constructor.
*
* @param KeywordRepository $repository
*/
public function __construct(KeywordRepository $repository)
{
$this->repository = $repository;
}
/**
* Create new Keyword entity with $name or retrieve one.
*
* @param $name
*
* @return Keyword
*/
public function create($name)
{
if (($keyword = $this->repository->findByName($name)) == null) {
$keyword = new Keyword();
$keyword->setName($name);
}
return $keyword;
}
}

View File

@ -1,47 +0,0 @@
<?php
namespace AppBundle\Manager\LDraw;
use AppBundle\Entity\LDraw\Model;
use AppBundle\Manager\BaseManager;
use AppBundle\Repository\LDraw\ModelRepository;
class ModelManager extends BaseManager
{
/**
* ModelManager constructor.
*
* @param ModelRepository $repository
*/
public function __construct(ModelRepository $repository)
{
$this->repository = $repository;
}
/**
* Create new Model entity with $number or retrieve one.
*
* @param $number
*
* @return Model
*/
public function create($number)
{
if (($model = $this->repository->findOneBy(['number' => $number])) == null) {
$model = new Model();
$model->setNumber($number);
}
return $model;
}
public function findByNumber($number)
{
return $this->repository->findOneByNumber($number);
}
public function findByName($name)
{
return $this->repository->findOneBy(['name' => $name]);
}
}

View File

@ -1,42 +0,0 @@
<?php
namespace AppBundle\Manager\LDraw;
use AppBundle\Entity\LDraw\Subpart;
use AppBundle\Manager\BaseManager;
use AppBundle\Repository\LDraw\SubpartRepository;
class SubpartManager extends BaseManager
{
/**
* SubpartManager constructor.
*
* @param SubpartRepository $repository
*/
public function __construct(SubpartRepository $repository)
{
$this->repository = $repository;
}
/**
* Create new Subpart relation entity or retrieve one by foreign keys.
*
* @param $name
*
* @return Subpart
*/
public function create($parent, $child)
{
if (($subpart = $this->repository->findOneByKeys($parent, $child))) {
$subpart->setCount($subpart->getCount() + 1);
} else {
$subpart = new Subpart();
$subpart
->setParent($parent)
->setSubpart($child)
->setCount(1);
}
return $subpart;
}
}

View File

@ -1,37 +0,0 @@
<?php
namespace AppBundle\Manager\LDraw;
use AppBundle\Entity\LDraw\Type;
use AppBundle\Manager\BaseManager;
use AppBundle\Repository\LDraw\TypeRepository;
class TypeManager extends BaseManager
{
/**
* TypeManager constructor.
*
* @param TypeRepository $typeRepository
*/
public function __construct(TypeRepository $repository)
{
$this->repository = $repository;
}
/**
* Create new Keyword entity with $name or retrieve one.
*
* @param $name
*
* @return Type
*/
public function create($name)
{
if (($type = $this->repository->findByName($name)) == null) {
$type = new Type();
$type->setName($name);
}
return $type;
}
}

View File

@ -1,99 +0,0 @@
<?php
namespace AppBundle\Manager;
use AppBundle\Manager\LDraw\AliasManager;
use AppBundle\Manager\LDraw\CategoryManager;
use AppBundle\Manager\LDraw\KeywordManager;
use AppBundle\Manager\LDraw\ModelManager;
use AppBundle\Manager\LDraw\SubpartManager;
use AppBundle\Manager\LDraw\TypeManager;
class LDrawManager
{
/** @var CategoryManager */
private $categoryManager;
/** @var KeywordManager */
private $keywordManager;
/** @var TypeManager */
private $typeManager;
/** @var SubpartManager */
private $subpartManager;
/** @var ModelManager */
private $modelManager;
/** @var AliasManager */
private $aliasManager;
/**
* LDrawService constructor.
*
* @param CategoryManager $categoryManager
* @param KeywordManager $keywordManager
* @param TypeManager $typeManager
* @param SubpartManager $subpartManager
* @param ModelManager $modelManager
* @param AliasManager $aliasManager
*/
public function __construct(CategoryManager $categoryManager, KeywordManager $keywordManager, TypeManager $typeManager, SubpartManager $subpartManager, ModelManager $modelManager, AliasManager $aliasManager)
{
$this->categoryManager = $categoryManager;
$this->keywordManager = $keywordManager;
$this->typeManager = $typeManager;
$this->subpartManager = $subpartManager;
$this->modelManager = $modelManager;
$this->aliasManager = $aliasManager;
}
/**
* @return mixed
*/
public function getCategoryManager()
{
return $this->categoryManager;
}
/**
* @return mixed
*/
public function getKeywordManager()
{
return $this->keywordManager;
}
/**
* @return TypeManager
*/
public function getTypeManager()
{
return $this->typeManager;
}
/**
* @return SubpartManager
*/
public function getSubpartManager()
{
return $this->subpartManager;
}
/**
* @return ModelManager
*/
public function getModelManager()
{
return $this->modelManager;
}
/**
* @return AliasManager
*/
public function getAliasManager()
{
return $this->aliasManager;
}
}

View File

@ -35,7 +35,7 @@ class Builder
]);
$menu->addChild('Models', [
'route' => 'ldraw_model_index',
'route' => 'model_index',
]);
$menu->addChild('Sets', [

View File

@ -2,8 +2,28 @@
namespace AppBundle\Repository\LDraw;
use AppBundle\Entity\LDraw\Alias;
use AppBundle\Entity\LDraw\Model;
use AppBundle\Repository\BaseRepository;
class AliasRepository extends BaseRepository
{
/**
* Get existing entity or create new.
*
* @param $number
* @param Model $model
*
* @return Alias
*/
public function getOrCreate($number, $model)
{
if (($alias = $this->findOneBy(['number' => $number, 'model' => $model])) == null) {
$alias = new Alias();
$alias->setModel($model);
$alias->setNumber($number);
}
return $alias;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace AppBundle\Repository\LDraw;
use AppBundle\Entity\LDraw\Author;
use AppBundle\Repository\BaseRepository;
class AuthorRepository extends BaseRepository
{
public function findOneByName($name)
{
return $this->findOneBy(['name' => $name]);
}
/**
* Get existing entity or create new.
*
* @param $name
*
* @return Author
*/
public function getOrCreate($name)
{
if (($author = $this->findOneByName($name)) == null) {
$author = new Author();
$author->setName($name);
}
return $author;
}
}

View File

@ -2,6 +2,7 @@
namespace AppBundle\Repository\LDraw;
use AppBundle\Entity\LDraw\Category;
use AppBundle\Repository\BaseRepository;
class CategoryRepository extends BaseRepository
@ -10,4 +11,21 @@ class CategoryRepository extends BaseRepository
{
return $this->findOneBy(['name' => $name]);
}
/**
* Get existing entity or create new.
*
* @param $name
*
* @return Category
*/
public function getOrCreate($name)
{
if (($category = $this->findByName($name)) == null) {
$category = new Category();
$category->setName($name);
}
return $category;
}
}

View File

@ -2,6 +2,7 @@
namespace AppBundle\Repository\LDraw;
use AppBundle\Entity\LDraw\Keyword;
use AppBundle\Repository\BaseRepository;
class KeywordRepository extends BaseRepository
@ -10,4 +11,21 @@ class KeywordRepository extends BaseRepository
{
return $this->findOneBy(['name' => $name]);
}
/**
* Create new Keyword entity with $name or retrieve one.
*
* @param $name
*
* @return Keyword
*/
public function getOrCreate($name)
{
if (($keyword = $this->findByName($name)) == null) {
$keyword = new Keyword();
$keyword->setName($name);
}
return $keyword;
}
}

View File

@ -2,30 +2,33 @@
namespace AppBundle\Repository\LDraw;
use AppBundle\Entity\Rebrickable\Set;
use AppBundle\Entity\Rebrickable\Part;
use AppBundle\Entity\LDraw\Alias;
use AppBundle\Entity\LDraw\Type;
use AppBundle\Entity\LDraw\Category;
use AppBundle\Entity\LDraw\Model;
use AppBundle\Entity\LDraw\Subpart;
use AppBundle\Entity\Rebrickable\Inventory;
use AppBundle\Entity\Rebrickable\Inventory_Part;
use AppBundle\Entity\Rebrickable\Part;
use AppBundle\Entity\Rebrickable\Set;
use AppBundle\Repository\BaseRepository;
use Doctrine\ORM\Query\Expr\Join;
class ModelRepository extends BaseRepository
{
public function findAllByType($type)
public function getFilteredQueryBuilder()
{
$queryBuilder = $this->createQueryBuilder('model')
->join(Type::class, 'type', Join::LEFT_JOIN, 'model.type = :type')
->setParameter('type', $type);
// ->where('model.name NOT LIKE :obsolete')
// ->setParameter('obsolete','~%')
;
return $queryBuilder->getQuery();
return $queryBuilder;
}
public function findAllByCategory($category)
{
$queryBuilder = $this->createQueryBuilder('model')
->join(Type::class, 'type', Join::LEFT_JOIN, 'model.category = :category')
->join(Category::class, 'type', Join::LEFT_JOIN, 'model.category = :category')
->setParameter('category', $category);
return $queryBuilder->getQuery();
@ -49,6 +52,11 @@ class ModelRepository extends BaseRepository
return $model;
}
public function findOneByName($name)
{
return $this->findOneBy(['name' => $name]);
}
public function findAllBySetNumber($number)
{
$queryBuilder = $this->createQueryBuilder('model');
@ -64,4 +72,38 @@ class ModelRepository extends BaseRepository
return $queryBuilder->getQuery()->getResult();
}
public function findAllRelatedModels($number)
{
$queryBuilder = $this->createQueryBuilder('model');
$queryBuilder
->select('related')
->join(Subpart::class, 'subpart', JOIN::WITH, 'model.number = subpart.subpart')
->join(Subpart::class, 'parent', JOIN::WITH, 'subpart.parent = parent.parent')
->join(Model::class, 'related', JOIN::WITH, 'related.number = parent.subpart')
->where('model.number = :number')
->setParameter('number', $number)
->andWhere('related.number != :number')
->distinct(true);
return $queryBuilder->getQuery()->getResult();
}
/**
* Create new Model entity with $number or retrieve one.
*
* @param $number
*
* @return Model
*/
public function getOrCreate($number)
{
if (($model = $this->findOneBy(['number' => $number])) == null) {
$model = new Model();
$model->setNumber($number);
}
return $model;
}
}

Some files were not shown because too many files have changed in this diff Show More