kopia lustrzana https://github.com/inkstitch/inkstitch
commit
14828d6fa2
|
@ -9,7 +9,10 @@ import socket
|
|||
import errno
|
||||
import time
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
import wx
|
||||
import appdirs
|
||||
import json
|
||||
|
||||
import inkex
|
||||
import inkstitch
|
||||
|
@ -23,7 +26,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
|
|||
from datetime import date
|
||||
import base64
|
||||
|
||||
from flask import Flask, request, Response, send_from_directory
|
||||
from flask import Flask, request, Response, send_from_directory, jsonify
|
||||
import webbrowser
|
||||
import requests
|
||||
|
||||
|
@ -32,6 +35,29 @@ def datetimeformat(value, format='%Y/%m/%d'):
|
|||
return value.strftime(format)
|
||||
|
||||
|
||||
def defaults_path():
|
||||
defaults_dir = appdirs.user_config_dir('inkstitch')
|
||||
|
||||
if not os.path.exists(defaults_dir):
|
||||
os.makedirs(defaults_dir)
|
||||
|
||||
return os.path.join(defaults_dir, 'print_settings.json')
|
||||
|
||||
|
||||
def load_defaults():
|
||||
try:
|
||||
with open(defaults_path(), 'r') as defaults_file:
|
||||
defaults = json.load(defaults_file)
|
||||
return defaults
|
||||
except:
|
||||
return {}
|
||||
|
||||
|
||||
def save_defaults(defaults):
|
||||
with open(defaults_path(), 'w') as defaults_file:
|
||||
json.dump(defaults, defaults_file)
|
||||
|
||||
|
||||
def open_url(url):
|
||||
# Avoid spurious output from xdg-open. Any output on stdout will crash
|
||||
# inkscape.
|
||||
|
@ -71,6 +97,7 @@ def open_url(url):
|
|||
class PrintPreviewServer(Thread):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.html = kwargs.pop('html')
|
||||
self.metadata = kwargs.pop('metadata')
|
||||
Thread.__init__(self, *args, **kwargs)
|
||||
self.daemon = True
|
||||
self.last_request_time = None
|
||||
|
@ -106,7 +133,7 @@ class PrintPreviewServer(Thread):
|
|||
def shutdown():
|
||||
self.shutting_down = True
|
||||
request.environ.get('werkzeug.server.shutdown')()
|
||||
return 'Server shutting down...'
|
||||
return _('Closing...') + '<br/><br/>' + _('It is safe to close this window now.')
|
||||
|
||||
@self.app.route('/resources/<path:resource>', methods=['GET'])
|
||||
def resources(resource):
|
||||
|
@ -129,6 +156,27 @@ class PrintPreviewServer(Thread):
|
|||
# nothing to do here -- request_started() will restart the watcher
|
||||
return "OK"
|
||||
|
||||
@self.app.route('/settings/<field_name>', methods=['POST'])
|
||||
def set_field(field_name):
|
||||
self.metadata[field_name] = request.json['value']
|
||||
return "OK"
|
||||
|
||||
@self.app.route('/settings/<field_mame>', methods=['GET'])
|
||||
def get_field(field_name):
|
||||
return jsonify(self.metadata[field_name])
|
||||
|
||||
@self.app.route('/settings', methods=['GET'])
|
||||
def get_settings():
|
||||
settings = {}
|
||||
settings.update(load_defaults())
|
||||
settings.update(self.metadata)
|
||||
return jsonify(settings)
|
||||
|
||||
@self.app.route('/defaults', methods=['POST'])
|
||||
def set_defaults():
|
||||
save_defaults(request.json['value'])
|
||||
return "OK"
|
||||
|
||||
def stop(self):
|
||||
# for whatever reason, shutting down only seems possible in
|
||||
# the context of a flask request, so we'll just make one
|
||||
|
@ -295,11 +343,14 @@ class Print(InkstitchExtension):
|
|||
'estimated_thread': '', # TODO
|
||||
},
|
||||
svg_overview = overview_svg,
|
||||
svg_scale = '100%',
|
||||
color_blocks = stitch_plan.color_blocks,
|
||||
)
|
||||
|
||||
print_server = PrintPreviewServer(html=html)
|
||||
# We've totally mucked with the SVG. Restore it so that we can save
|
||||
# metadata into it.
|
||||
self.document = deepcopy(self.original_document)
|
||||
|
||||
print_server = PrintPreviewServer(html=html, metadata=self.get_inkstitch_metadata())
|
||||
print_server.start()
|
||||
|
||||
time.sleep(1)
|
||||
|
@ -310,12 +361,20 @@ class Print(InkstitchExtension):
|
|||
info_frame.Show()
|
||||
app.MainLoop()
|
||||
|
||||
# don't let inkex print the document out
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exception = None
|
||||
|
||||
save_stderr()
|
||||
effect = Print()
|
||||
effect.affect()
|
||||
try:
|
||||
effect = Print()
|
||||
effect.affect()
|
||||
except:
|
||||
exception = traceback.format_exc()
|
||||
restore_stderr()
|
||||
|
||||
if exception:
|
||||
print >> sys.stderr, exception
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
|
|
@ -1,6 +1,95 @@
|
|||
import inkex
|
||||
import re
|
||||
import json
|
||||
from copy import deepcopy
|
||||
from collections import MutableMapping
|
||||
from .elements import AutoFill, Fill, Stroke, SatinColumn, Polyline, EmbroideryElement
|
||||
from . import SVG_POLYLINE_TAG, SVG_GROUP_TAG, SVG_DEFS_TAG, INKSCAPE_GROUPMODE, EMBROIDERABLE_TAGS, PIXELS_PER_MM
|
||||
from .utils import cache
|
||||
|
||||
|
||||
SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
|
||||
|
||||
|
||||
def strip_namespace(tag):
|
||||
"""Remove xml namespace from a tag name.
|
||||
|
||||
>>> {http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}namedview
|
||||
<<< namedview
|
||||
"""
|
||||
|
||||
match = re.match('^\{[^}]+\}(.+)$', tag)
|
||||
|
||||
if match:
|
||||
return match.group(1)
|
||||
else:
|
||||
return tag
|
||||
|
||||
|
||||
class InkStitchMetadata(MutableMapping):
|
||||
"""Helper class to get and set inkstitch-specific metadata attributes.
|
||||
|
||||
Operates on a document and acts like a dict. Setting an item adds or
|
||||
updates a metadata element in the document. Getting an item retrieves
|
||||
a metadata element's text contents or None if an element by that name
|
||||
doesn't exist.
|
||||
"""
|
||||
|
||||
def __init__(self, document):
|
||||
self.document = document
|
||||
self.metadata = self._get_or_create_metadata()
|
||||
|
||||
def _get_or_create_metadata(self):
|
||||
metadata = self.document.find(SVG_METADATA_TAG)
|
||||
|
||||
if metadata is None:
|
||||
metadata = inkex.etree.SubElement(self.document.getroot(), SVG_METADATA_TAG)
|
||||
|
||||
# move it so that it goes right after the first element, sodipodi:namedview
|
||||
self.document.getroot().remove(metadata)
|
||||
self.document.getroot().insert(1, metadata)
|
||||
|
||||
return metadata
|
||||
|
||||
# Because this class inherints from MutableMapping, all we have to do is
|
||||
# implement these five methods and we get a full dict-like interface.
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
self._find_item(name).text = json.dumps(value)
|
||||
|
||||
def _find_item(self, name):
|
||||
tag = inkex.addNS(name, "inkstitch")
|
||||
item = self.metadata.find(tag)
|
||||
if item is None:
|
||||
item = inkex.etree.SubElement(self.metadata, tag)
|
||||
|
||||
return item
|
||||
|
||||
def __getitem__(self, name):
|
||||
item = self._find_item(name)
|
||||
|
||||
try:
|
||||
return json.loads(item.text)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def __delitem__(self, name):
|
||||
item = self[name]
|
||||
|
||||
if item:
|
||||
self.metadata.remove(item)
|
||||
|
||||
def __iter__(self):
|
||||
for child in self.metadata:
|
||||
if child.prefix == "inkstitch":
|
||||
yield strip_namespace(child.tag)
|
||||
|
||||
def __len__(self):
|
||||
i = 0
|
||||
for i, item in enumerate(self):
|
||||
pass
|
||||
|
||||
return i + 1
|
||||
|
||||
|
||||
class InkstitchExtension(inkex.Effect):
|
||||
|
@ -101,3 +190,28 @@ class InkstitchExtension(inkex.Effect):
|
|||
patches.extend(element.embroider(last_patch))
|
||||
|
||||
return patches
|
||||
|
||||
def get_inkstitch_metadata(self):
|
||||
return InkStitchMetadata(self.document)
|
||||
|
||||
def parse(self):
|
||||
"""Override inkex.Effect to add Ink/Stitch xml namespace"""
|
||||
|
||||
# SVG parsers don't actually look for anything at this URL. They just
|
||||
# care that it's unique. That defines a "namespace" of element and
|
||||
# attribute names to disambiguate conflicts with element and
|
||||
# attribute names other XML namespaces.
|
||||
#
|
||||
# Updating inkex.NSS here allows us to pass 'inkstitch' into
|
||||
# inkex.addNS().
|
||||
inkex.NSS['inkstitch'] = 'http://inkstitch.org/namespace'
|
||||
|
||||
# call the superclass's method first
|
||||
inkex.Effect.parse(self)
|
||||
|
||||
# This is the only way I could find to add a namespace to an existing
|
||||
# element tree at the top without getting ugly prefixes like "ns0".
|
||||
inkex.etree.cleanup_namespaces(self.document,
|
||||
top_nsmap=inkex.NSS,
|
||||
keep_ns_prefixes=inkex.NSS.keys())
|
||||
self.original_document = deepcopy(self.document)
|
||||
|
|
34
messages.po
34
messages.po
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2018-04-02 22:11-0400\n"
|
||||
"POT-Creation-Date: 2018-04-28 13:36-0400\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -98,6 +98,12 @@ msgid ""
|
|||
"\"Overwrite\""
|
||||
msgstr ""
|
||||
|
||||
msgid "Closing..."
|
||||
msgstr ""
|
||||
|
||||
msgid "It is safe to close this window now."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"A print preview has been opened in your web browser. This window will "
|
||||
"stay open in order to communicate with the JavaScript code running in "
|
||||
|
@ -311,6 +317,9 @@ msgstr ""
|
|||
msgid "Page"
|
||||
msgstr ""
|
||||
|
||||
msgid "Click to choose another logo"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter job title..."
|
||||
msgstr ""
|
||||
|
||||
|
@ -336,7 +345,7 @@ msgstr ""
|
|||
msgid "Stops and Trims"
|
||||
msgstr ""
|
||||
|
||||
msgid "Estimated Time"
|
||||
msgid "Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unique Colors"
|
||||
|
@ -360,12 +369,24 @@ msgstr ""
|
|||
msgid "Total nr trims"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter operator notes..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Job estimated time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ctrl + Scroll to Zoom"
|
||||
msgstr ""
|
||||
|
||||
msgid "Scale"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply to all"
|
||||
msgstr ""
|
||||
|
||||
msgid "COLOR"
|
||||
msgstr ""
|
||||
|
||||
|
@ -393,3 +414,12 @@ msgstr ""
|
|||
msgid "Printing Size"
|
||||
msgstr ""
|
||||
|
||||
msgid "Print Layouts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Includes all settings visible here and also the icon."
|
||||
msgstr ""
|
||||
|
||||
msgid "Save as defaults"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
$.postJSON = function(url, data, success=null) {
|
||||
return $.ajax(url, {
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json',
|
||||
success: success
|
||||
});
|
||||
};
|
||||
|
||||
function ping() {
|
||||
$.get("/ping")
|
||||
.done(function() { setTimeout(ping, 1000) })
|
||||
|
@ -13,46 +22,178 @@ function setPageNumbers() {
|
|||
});
|
||||
}
|
||||
|
||||
// set preview svg scale to fit into its box
|
||||
function scaleInksimulation() {
|
||||
$('.inksimulation').each(function() {
|
||||
// Scale SVG (fit || full size)
|
||||
function scaleSVG(element, scale = 'fit') {
|
||||
|
||||
// always center svg
|
||||
transform = "translate(-50%, -50%)";
|
||||
|
||||
if(scale == 'fit') {
|
||||
var scale = Math.min(
|
||||
$(this).width() / $(this).find('svg').width(),
|
||||
$(this).height() / $(this).find('svg').height()
|
||||
element.width() / element.find('svg').width(),
|
||||
element.height() / element.find('svg').height()
|
||||
);
|
||||
}
|
||||
|
||||
transform += " scale(" + scale + ")";
|
||||
var label = parseInt(scale*100);
|
||||
|
||||
// center the SVG
|
||||
transform = "translate(-50%, -50%)";
|
||||
element.find('svg').css({ transform: transform });
|
||||
element.find('.scale').text(label);
|
||||
}
|
||||
|
||||
if(scale <= 1) {
|
||||
transform += " scale(" + scale + ")";
|
||||
label = parseInt(scale*100) + '%';
|
||||
} else {
|
||||
label = "100%";
|
||||
}
|
||||
// set preview svg scale to fit into its box if transform is not set
|
||||
function scaleAllSvg() {
|
||||
$('.page').each(function() {
|
||||
if( $(this).find('.inksimulation svg').css('transform') == 'none') {
|
||||
scaleSVG($(this).find('.inksimulation'), 'fit');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(this).find('svg').css({ transform: transform });
|
||||
$(this).find('figcaption span').text(label);
|
||||
});
|
||||
var saveTimerHandles = {};
|
||||
|
||||
function setSVGTransform(figure, transform) {
|
||||
var field_name = $(figure).data('field-name');
|
||||
var scale = transform.match(/-?[\d\.]+/g)[0];
|
||||
figure.find('svg').css({ transform: transform });
|
||||
figure.find(".scale").text(parseInt(scale*100));
|
||||
|
||||
// avoid spamming updates
|
||||
if (saveTimerHandles[field_name] != null)
|
||||
clearTimeout(saveTimerHandles[field_name]);
|
||||
|
||||
saveTimerHandles[field_name] = setTimeout(function() {
|
||||
$.postJSON('/settings/' + field_name, {value: transform});
|
||||
}, 250);
|
||||
}
|
||||
|
||||
$(function() {
|
||||
setTimeout(ping, 1000);
|
||||
setPageNumbers();
|
||||
scaleInksimulation();
|
||||
|
||||
/* Contendeditable Fields */
|
||||
/* SCALING AND MOVING SVG */
|
||||
|
||||
// When we focus out from a contenteditable field, we want to
|
||||
// set the same content to all fields with the same classname
|
||||
document.querySelectorAll('[contenteditable="true"]').forEach(function(elem) {
|
||||
elem.addEventListener('focusout', function() {
|
||||
var content = $(this).html();
|
||||
var field_name = $(this).attr('data-field-name');
|
||||
$('[data-field-name="' + field_name + '"]').html(content);
|
||||
});
|
||||
/* Mousewheel scaling */
|
||||
$('figure.inksimulation').on( 'DOMMouseScroll mousewheel', function (e) {
|
||||
if(e.ctrlKey == true) {
|
||||
|
||||
var svg = $(this).find('svg');
|
||||
var transform = svg.css('transform').match(/-?[\d\.]+/g);
|
||||
var scale = parseFloat(transform[0]);
|
||||
|
||||
if (e.originalEvent.detail > 0 || e.originalEvent.wheelDelta < 0) {
|
||||
// scroll down = zoom out
|
||||
scale *= 0.97;
|
||||
if (scale < 0.01)
|
||||
scale = 0.01;
|
||||
} else {
|
||||
//scroll up
|
||||
scale *= 1.03;
|
||||
}
|
||||
|
||||
// set modified scale
|
||||
transform[0] = scale;
|
||||
transform[3] = scale;
|
||||
|
||||
setSVGTransform($(this), 'matrix(' + transform + ')');
|
||||
|
||||
//prevent page fom scrolling
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
/* Fit SVG */
|
||||
$('button.svg-fit').click(function() {
|
||||
var svgfigure = $(this).closest('figure');
|
||||
scaleSVG(svgfigure, 'fit');
|
||||
});
|
||||
|
||||
/* Full Size SVG */
|
||||
$('button.svg-full').click(function() {
|
||||
var svgfigure = $(this).closest('figure');
|
||||
scaleSVG(svgfigure, '1');
|
||||
});
|
||||
|
||||
/* Drag SVG */
|
||||
$('figure.inksimulation').on('mousedown', function(e) {
|
||||
var p0 = { x: e.pageX, y: e.pageY };
|
||||
var start_transform = $(this).find('svg').css('transform').match(/-?[\d\.]+/g);
|
||||
var start_offset = { x: parseFloat(start_transform[4]), y: parseFloat(start_transform[5]) };
|
||||
|
||||
$(this).css({cursor: 'move'});
|
||||
$(this).on('mousemove', function(e) {
|
||||
var p1 = { x: e.pageX, y: e.pageY };
|
||||
// set modified translate
|
||||
var transform = $(this).find('svg').css('transform').match(/-?[\d\.]+/g);
|
||||
transform[4] = start_offset.x + (p1.x - p0.x);
|
||||
transform[5] = start_offset.y + (p1.y - p0.y);
|
||||
|
||||
// I'd ike to use setSVGTransform() here but this code runs many
|
||||
// times per second and it's just too CPU-intensive.
|
||||
$(this).find('svg').css({transform: 'matrix(' + transform + ')'});
|
||||
});
|
||||
}).on('mouseup', function(e) {
|
||||
$(this).css({cursor: 'auto'});
|
||||
$(this).data('p0', null);
|
||||
$(this).off('mousemove');
|
||||
|
||||
// set it using setSVGTransform() to ensure that it's saved to the server
|
||||
setSVGTransform($(this), $(this).find('svg').css('transform'));
|
||||
});
|
||||
|
||||
/* Apply transforms to All */
|
||||
$('button.svg-apply').click(function() {
|
||||
var transform = $(this).parent().siblings('svg').css('transform');
|
||||
$('.inksimulation').each(function() {
|
||||
setSVGTransform($(this), transform);
|
||||
})
|
||||
});
|
||||
|
||||
/* Contendeditable Fields */
|
||||
|
||||
$('[contenteditable="true"]').on('focusout', function() {
|
||||
/* change svg scale */
|
||||
var content = $(this).html();
|
||||
var field_name = $(this).attr('data-field-name');
|
||||
if(field_name == 'svg-scale') {
|
||||
var scale = parseInt(content);
|
||||
var svg = $(this).parent().siblings('svg');
|
||||
var transform = svg.css('transform').match(/-?[\d\.]+/g);
|
||||
|
||||
transform[0] = scale / 100;
|
||||
transform[3] = scale / 100;
|
||||
svg.css({ transform: 'matrix(' + transform + ')' });
|
||||
} else {
|
||||
/* When we focus out from a contenteditable field, we want to
|
||||
* set the same content to all fields with the same classname */
|
||||
$('[data-field-name="' + field_name + '"]').html(content);
|
||||
$.postJSON('/settings/' + field_name, {value: content});
|
||||
}
|
||||
});
|
||||
|
||||
// load up initial metadata values
|
||||
$.getJSON('/settings', function(settings) {
|
||||
$.each(settings, function(field_name, value) {
|
||||
$('[data-field-name="' + field_name + '"]').each(function(i, item) {
|
||||
var item = $(item);
|
||||
if (item.is(':checkbox')) {
|
||||
item.prop('checked', value).trigger('change');
|
||||
} else if (item.is('img')) {
|
||||
item.attr('src', value);
|
||||
} else if (item.is('select')) {
|
||||
item.val(value).trigger('change');
|
||||
} else if (item.is('figure.inksimulation')) {
|
||||
setSVGTransform(item, value);
|
||||
} else {
|
||||
item.text(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
// wait until page size is set (if they've specified one) and then scale SVGs to fit
|
||||
setTimeout(function() { scaleAllSvg() }, 500);
|
||||
});
|
||||
|
||||
$('[contenteditable="true"]').keypress(function(e) {
|
||||
if (e.which == 13) {
|
||||
// pressing enter defocuses the element
|
||||
|
@ -64,14 +205,26 @@ $(function() {
|
|||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
/* Settings Bar */
|
||||
|
||||
|
||||
$('button.close').click(function() {
|
||||
$.post('/shutdown', {})
|
||||
.done(function(data) {
|
||||
window.close();
|
||||
|
||||
/* Chrome and Firefox both have a rule: scripts can only close windows
|
||||
* that they opened. Chrome seems to have an exception for windows that
|
||||
* were opened by an outside program, so the above works fine. Firefox
|
||||
* steadfastly refuses to allow us to close the window, so we'll tell
|
||||
* the user (in their language) that they can close it.
|
||||
*/
|
||||
setTimeout(function() {
|
||||
document.open();
|
||||
document.write("<html><body>" + data + "</body></html>");
|
||||
document.close();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -92,20 +245,54 @@ $(function() {
|
|||
$('#close-settings').click(function(){
|
||||
$('#settings-ui').hide();
|
||||
});
|
||||
|
||||
|
||||
/* Settings */
|
||||
|
||||
|
||||
// Paper Size
|
||||
$('select#printing-size').change(function(){
|
||||
$('.page').toggleClass('a4');
|
||||
var size = $(this).find(':selected').val();
|
||||
$('.page').toggleClass('a4', size == 'a4');
|
||||
$.postJSON('/settings/paper-size', {value: size});
|
||||
});
|
||||
|
||||
|
||||
//Checkbox
|
||||
$(':checkbox').change(function() {
|
||||
$('.' + this.id).toggle();
|
||||
var checked = $(this).prop('checked');
|
||||
var field_name = $(this).attr('data-field-name');
|
||||
|
||||
$('.' + field_name).toggle(checked);
|
||||
setPageNumbers();
|
||||
scaleInksimulation();
|
||||
|
||||
$.postJSON('/settings/' + field_name, {value: checked});
|
||||
});
|
||||
|
||||
// Logo
|
||||
$('#logo-picker').change(function(e) {
|
||||
var file = e.originalEvent.srcElement.files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onloadend = function() {
|
||||
var data = reader.result;
|
||||
$('figure.brandlogo img').attr('src', data);
|
||||
$.postJSON('/settings/logo', {value: data});
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
// "save as defaults" button
|
||||
$('#save-settings').click(function(e) {
|
||||
var settings = {};
|
||||
settings["client-overview"] = $("[data-field-name='client-overview']").is(':checked');
|
||||
settings["client-detailedview"] = $("[data-field-name='client-detailedview']").is(':checked');
|
||||
settings["operator-overview"] = $("[data-field-name='operator-overview']").is(':checked');
|
||||
settings["operator-detailedview"] = $("[data-field-name='operator-detailedview']").is(':checked');
|
||||
settings["paper-size"] = $('select#printing-size').find(':selected').val();
|
||||
|
||||
var logo = $("figure.brandlogo img").attr('src');
|
||||
if (logo.startsWith("data:")) {
|
||||
settings["logo"] = logo;
|
||||
}
|
||||
|
||||
$.postJSON('/defaults', {'value': settings});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -70,6 +70,10 @@
|
|||
margin: 0 !important;
|
||||
}
|
||||
|
||||
figure.inksimulation div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ui {
|
||||
display: none;
|
||||
}
|
||||
|
@ -77,7 +81,7 @@
|
|||
#settings-ui {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
#errors {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -86,6 +90,10 @@
|
|||
content: attr(data-label);
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
span.logo-instructions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@page {
|
||||
|
@ -109,7 +117,7 @@ body {
|
|||
.page {
|
||||
width: 210mm;
|
||||
height: 275mm;
|
||||
padding: 5mm;
|
||||
padding: 5mm;
|
||||
background: #fff;
|
||||
margin: 0 auto;
|
||||
vertical-align: text-bottom;
|
||||
|
@ -163,13 +171,13 @@ body {
|
|||
|
||||
.ui button.close {
|
||||
border: 1px solid rgb(197,5,5);
|
||||
|
||||
|
||||
}
|
||||
|
||||
.ui button.close:hover {
|
||||
background: rgb(197,5,5);
|
||||
color: white;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.ui button.settings {
|
||||
|
@ -207,7 +215,7 @@ body {
|
|||
border-bottom: 1px solid rgba(129, 129, 129, 0.5);
|
||||
box-shadow: 0 1px 1px 1px rgba(194, 191, 191, 0.5);
|
||||
}
|
||||
|
||||
|
||||
#settings-ui div {
|
||||
text-align: left;
|
||||
font-size: 12pt;
|
||||
|
@ -226,6 +234,10 @@ body {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
#settings-ui fieldset {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
|
||||
|
||||
|
@ -247,18 +259,48 @@ body {
|
|||
margin: 2.5mm;
|
||||
}
|
||||
|
||||
figure.brandlogo label {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 30mm;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
figure.brandlogo img {
|
||||
max-width: 30mm;
|
||||
max-height: 30mm;
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
/* hide the actual file picker control, since we just want them to click the
|
||||
* image instead
|
||||
*/
|
||||
#logo-picker {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
opacity: 0%;
|
||||
}
|
||||
|
||||
.logo-instructions {
|
||||
white-space: nowrap;
|
||||
|
||||
/* chrome ignores this :(
|
||||
text-align: center;
|
||||
*/
|
||||
|
||||
font-size: 10px;
|
||||
color: rgb(192, 192, 192);
|
||||
}
|
||||
|
||||
.operator-detailedview figure.brandlogo {
|
||||
height: 20mm;
|
||||
width: 30mm;
|
||||
margin: 0 2.5mm;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
.operator-detailedview figure.brandlogo img {
|
||||
max-width: 30mm;
|
||||
max-height: 20mm;
|
||||
|
@ -314,15 +356,15 @@ body {
|
|||
|
||||
div.job-details p span:first-child {
|
||||
font-weight: bold;
|
||||
padding-right: 1mm;
|
||||
padding-right: 1mm;
|
||||
}
|
||||
|
||||
div.job-details p span:last-child {
|
||||
text-align: left;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.job-details > div:last-child span {
|
||||
text-align: right !important;
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
div.client-detailedview .job-details {
|
||||
|
@ -347,16 +389,16 @@ body {
|
|||
font-size: 12pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
/* client dedailed view header */
|
||||
.client-detailedview div.job-details p span:first-child {
|
||||
width: 20mm;
|
||||
width: 20mm;
|
||||
}
|
||||
|
||||
|
||||
.client-detailedview div.job-details p span:last-child {
|
||||
max-width: 60mm;
|
||||
max-width: 60mm;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* SVG Preview Image */
|
||||
|
||||
|
@ -370,7 +412,7 @@ body {
|
|||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.client-overview-main figure.inksimulation {
|
||||
height: 155mm;
|
||||
}
|
||||
|
@ -389,6 +431,24 @@ body {
|
|||
font-weight: bold;
|
||||
line-height: 12pt;
|
||||
margin: 2.5mm;
|
||||
background: rgba(255, 255, 255, 0.73);
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
figure.inksimulation div {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
figure.inksimulation button {
|
||||
border: none;
|
||||
background: grey;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Color Swatches */
|
||||
|
@ -415,7 +475,7 @@ body {
|
|||
font-weight: 700;
|
||||
font-size: 12pt;
|
||||
color: black;
|
||||
background: white;
|
||||
background: white;
|
||||
border: 0.5mm solid white;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
|
@ -446,44 +506,44 @@ body {
|
|||
line-height: 30mm;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/* detailedview color swatch */
|
||||
|
||||
|
||||
.color-palette.detailed > div {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.color-palette.detailed .color-info {
|
||||
position: absolute;
|
||||
top: 2mm;
|
||||
left: 45mm;
|
||||
}
|
||||
|
||||
|
||||
.color-palette.detailed .color-info > div {
|
||||
display: table;
|
||||
}
|
||||
|
||||
|
||||
.color-palette.detailed .color-info p {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
|
||||
.color-palette.detailed .color-info span {
|
||||
display: table-cell;
|
||||
padding-right: 5mm;
|
||||
}
|
||||
|
||||
|
||||
/* Operator Detailed View */
|
||||
|
||||
.operator-detailedview header {
|
||||
height: 25mm;
|
||||
}
|
||||
|
||||
|
||||
.operator-detailedveiw figure.brandlogo{
|
||||
height: 15mm;
|
||||
width: 15mm;
|
||||
}
|
||||
|
||||
|
||||
.operator-detailedveiw figure.brandlogo img {
|
||||
max-width: 12.5mm;
|
||||
max-height: 12.5mm;
|
||||
|
@ -497,21 +557,21 @@ body {
|
|||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.operator-job-info div {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
|
||||
div.job-headline {
|
||||
display: table-header-group;
|
||||
font-size: 9pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
div.job-headline p {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
|
||||
.operator-job-info p {
|
||||
height: 15mm;
|
||||
max-width: 15mm;
|
||||
|
@ -521,11 +581,11 @@ body {
|
|||
overflow: hidden;
|
||||
border: 1px solid rgb(239,239,239);
|
||||
}
|
||||
|
||||
|
||||
.operator-job-info span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.operator-job-info span.color-index {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -533,17 +593,17 @@ body {
|
|||
line-height: 15mm;
|
||||
width: 10mm;
|
||||
}
|
||||
|
||||
|
||||
.operator-svg.operator-colorswatch {
|
||||
width: 15mm;
|
||||
}
|
||||
|
||||
|
||||
.operator-svg.operator-preview {
|
||||
min-width: 15mm;
|
||||
max-width: 20mm;
|
||||
height: 15mm;
|
||||
}
|
||||
|
||||
|
||||
.operator-svg svg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -552,7 +612,7 @@ body {
|
|||
max-width: 30mm;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Footer */
|
||||
|
||||
|
@ -565,7 +625,7 @@ body {
|
|||
white-space: wrap;
|
||||
text-align: center;
|
||||
padding-top: 2mm;
|
||||
|
||||
|
||||
}
|
||||
|
||||
footer p.num_pages {
|
||||
|
@ -625,7 +685,7 @@ body {
|
|||
/* five items */
|
||||
.color-swatch:first-child:nth-last-child(n+5),
|
||||
.color-swatch:first-child:nth-last-child(n+5) ~ .color-swatch {
|
||||
font-size: 9pt;
|
||||
font-size: 9pt;
|
||||
width: calc(100% / 5);
|
||||
}
|
||||
|
||||
|
@ -665,7 +725,7 @@ body {
|
|||
/* fourteen items */
|
||||
.color-swatch:first-child:nth-last-child(n+14),
|
||||
.color-swatch:first-child:nth-last-child(n+14) ~ .color-swatch {
|
||||
width: calc(100% / 5);
|
||||
width: calc(100% / 5);
|
||||
}
|
||||
|
||||
/* sixteen items */
|
||||
|
@ -717,7 +777,7 @@ body {
|
|||
.color-swatch:first-child:nth-last-child(n+40) ~ .color-swatch {
|
||||
width: calc(100% / 12);
|
||||
}
|
||||
|
||||
|
||||
/* fourty-nine items */
|
||||
.color-swatch:first-child:nth-last-child(n+49),
|
||||
.color-swatch:first-child:nth-last-child(n+40) ~ .color-swatch {
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
<figure class="brandlogo">
|
||||
<img src="{{ logo.src or "resources/inkstitch-logo.svg" }}" alt="{{ logo.title }}" title="{{ logo.title }}">
|
||||
<label for="logo-picker">
|
||||
<img src="{{ logo.src or "resources/inkstitch-logo.svg" }}" alt="{{ logo.title }}" title="{{ logo.title }}" data-field-name="logo">
|
||||
<input type=file id="logo-picker" />
|
||||
</label>
|
||||
<span class="logo-instructions">{{ _("Click to choose another logo") }}</span>
|
||||
</figure>
|
||||
<div class="headline">
|
||||
<div class="pageTitle">
|
||||
<h1><span class="jobtitle" contenteditable="true" data-placeholder="{{ _('Enter job title...') }}" data-field-name="job-title">{{ job.title }}</span></h1>
|
||||
<p class="header-field" data-label="{{ _('CLIENT') }}:" contenteditable="true" data-placeholder="{{ _('Enter client name...') }}" data-field-name="client-name">{{ client }}</p>
|
||||
<p class="header-field" data-label="{{ _('PURCHASE ORDER #:') }}" contenteditable="true" data-placeholder="{{ _('Enter purchase order number...') }}" data-field-name="purchase-order">{{ purchase_order }}</p>
|
||||
<h1><span class="jobtitle" contenteditable="true" data-placeholder="{{ _('Enter job title...') }}" data-field-name="title"></span></h1>
|
||||
<p class="header-field" data-label="{{ _('CLIENT') }}:" contenteditable="true" data-placeholder="{{ _('Enter client name...') }}" data-field-name="client-name"></p>
|
||||
<p class="header-field" data-label="{{ _('PURCHASE ORDER #:') }}" contenteditable="true" data-placeholder="{{ _('Enter purchase order number...') }}" data-field-name="purchase-order"></p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="currentDate">{{ date|datetimeformat(_('%Y.%m.%d')) }}</div>
|
||||
</div>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<div class="page operator-overview" style="display: {{ 'block' if view.operator_overview else 'none' }}">{% include 'operator_overview.html' %}</div>
|
||||
|
||||
{# operator detailed view #}
|
||||
{% for color_block_part in color_blocks | batch(13) %}
|
||||
{% for color_block_part in color_blocks | batch(12) %}
|
||||
{% set outer_loop = loop %}
|
||||
<div class="page operator-detailedview" style="display: {{ 'block' if view.operator_detailedview else 'none' }}">{% include 'operator_detailedview.html' %}</div>
|
||||
{% endfor %}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<p>{{ _('Color') }}</p>
|
||||
<p>{{ _('Thread Consumption') }}</p>
|
||||
<p>{{ _('Stops and Trims') }}</p>
|
||||
<p>{{ _('Estimated Time') }}</p>
|
||||
<p>{{ _('Notes') }}</p>
|
||||
</div>
|
||||
{% if outer_loop.index == 1 %}
|
||||
<div>
|
||||
|
@ -25,7 +25,7 @@
|
|||
<span>{{ _('Color Blocks') }}: {{ job.num_color_blocks }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>{{ _('Design box size') }}: {{ job.dimensions }}</span>
|
||||
<span>{{ _('Design box size') }}: {{ "%0.1fmm X %0.1fmm" | format(*job.dimensions) }}</span>
|
||||
<span>{{ _('Total thread used') }}: {{job.estimated_thread }}</span>
|
||||
<span>{{ _('Total stitch count') }}: {{job.num_stitches }}</span>
|
||||
</p>
|
||||
|
@ -34,7 +34,7 @@
|
|||
<span>{{ _('Total nr trims') }}: {{ job.num_trims }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>{{ job.estimated_time }}</span>
|
||||
<span></span>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -45,7 +45,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<rect fill="rgb{{ color_block.color.rgb }}" width="15mm" height="100%" />
|
||||
<text fill="rgb{{ color_block.color.font_color }}">
|
||||
<tspan x="4mm" y="7.5mm" class="color-index">#{{ loop.index + outer_loop.index0 * 13 }}</tspan>
|
||||
<tspan x="4mm" y="7.5mm" class="color-index">#{{ loop.index + outer_loop.index0 * 12 }}</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
</p>
|
||||
|
@ -65,7 +65,7 @@
|
|||
<span>{{ _('# trims') }}: {{ color_block.num_trims }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>{{ color_block.estimatedtime }}</span>
|
||||
<span class="notes" contenteditable="true" data-field-name="operator-notes-block{{ loop.index0 + outer_loop.index0 * 12 }}" data-placeholder="{{ _("Enter operator notes...") }}"></span>
|
||||
</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<div class="table">
|
||||
<p><span>{{ _('Design box size') }}:</span><span>{{ job.dimensions }}</span></p>
|
||||
<p><span>{{ _('Design box size') }}:</span><span>{{ "%0.1fmm X %0.1fmm" | format(*job.dimensions) }}</span></p>
|
||||
<p><span>{{ _('Total stitch count') }}:</span><span>{{job.num_stitches }}</span></p>
|
||||
<p><span>{{ _('Total thread used') }}:</span><span>{{job.estimated_thread }}</span></p>
|
||||
</div>
|
||||
|
@ -25,9 +25,14 @@
|
|||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<figure class="inksimulation operator" style="height: 210mm;">
|
||||
<figure class="inksimulation operator" data-field-name="operator-overview-transform" style="height: 210mm;" title="{{ _('Ctrl + Scroll to Zoom') }}">
|
||||
{{ svg_overview|safe }}
|
||||
<figcaption>{{ _('Scale') }} <span>{{ svg_scale }}</span></figcaption>
|
||||
<figcaption>{{ _('Scale') }} <span class="scale" data-field-name="svg-scale" contenteditable="true" data-placeholder=""></span>%</figcaption>
|
||||
<div>
|
||||
<button class="svg-fit">{{ _('Fit') }}</button>
|
||||
<button class="svg-full">100%</button>
|
||||
<button class="svg-apply">{{ _('Apply to all') }}</button>
|
||||
</div>
|
||||
</figure>
|
||||
</main>
|
||||
{% include 'footer.html' %}
|
||||
|
|
|
@ -15,9 +15,14 @@
|
|||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<figure class="inksimulation">
|
||||
<figure class="inksimulation" data-field-name="client-detail-transform-block{{ loop.index0 }}" title="{{ _('Ctrl + Scroll to Zoom') }}">
|
||||
{{color_block.svg_preview|safe}}
|
||||
<figcaption>{{ _('Scale') }} <span>{{ svg_scale }}</span></figcaption>
|
||||
<figcaption>{{ _('Scale') }} <span class="scale" data-field-name="svg-scale" contenteditable="true" data-placeholder=""></span>%</figcaption>
|
||||
<div>
|
||||
<button class="svg-fit">Fit</button>
|
||||
<button class="svg-full">100%</button>
|
||||
<button class="svg-apply">Apply to all</button>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<div class="color-palette detailed">
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<div class="table">
|
||||
<p><span>{{ _('Design box size') }}:</span><span>{{ job.dimensions }}</span></p>
|
||||
<p><span>{{ _('Design box size') }}:</span><span>{{ "%0.1fmm X %0.1fmm" | format(*job.dimensions) }}</span></p>
|
||||
<p><span>{{ _('Total stitch count') }}:</span><span>{{job.num_stitches }}</span></p>
|
||||
<p><span>{{ _('Total thread used') }}:</span><span>{{job.estimated_thread }}</span></p>
|
||||
</div>
|
||||
|
@ -25,9 +25,14 @@
|
|||
</div>
|
||||
</header>
|
||||
<main class="client-overview-main">
|
||||
<figure class="inksimulation">
|
||||
<figure class="inksimulation" data-field-name="client-overview-transform" title="{{ _('Ctrl + Scroll to Zoom') }}">
|
||||
{{ svg_overview|safe }}
|
||||
<figcaption>{{ _('Scale') }} <span>{{ svg_scale }}</span></figcaption>
|
||||
<figcaption>{{ _('Scale') }} <span class="scale" data-field-name="svg-scale" contenteditable="true" data-placeholder=""></span>%</figcaption>
|
||||
<div>
|
||||
<button class="svg-fit">Fit</button>
|
||||
<button class="svg-full">100%</button>
|
||||
<button class="svg-apply">Apply to all</button>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<div class="color-palette">
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<h1>{{ _('Settings') }}</h1>
|
||||
<div>
|
||||
<p>{{ _('Printing Size') }}:
|
||||
<select id="printing-size">
|
||||
<select id="printing-size" data-field-name="paper-size">
|
||||
<option value="letter" selected="selected">Letter</option>
|
||||
<option value="a4">A4</option>
|
||||
</select>
|
||||
|
@ -23,11 +23,12 @@
|
|||
</div>
|
||||
<div>
|
||||
<fieldset>
|
||||
<legend>{{ ('Print Layouts') }}:</legend>
|
||||
<p><input type="checkbox" id="client-overview" {{ 'checked' if view.client_overview else '' }}><label for="client-overview">Client Overview</label></p>
|
||||
<p><input type="checkbox" id="client-detailedview" {{ 'checked' if view.client_detailedview else '' }}><label for="client-detailedview">Client Detailed View</label></p>
|
||||
<p><input type="checkbox" id="operator-overview" {{ 'checked' if view.operator_overview else '' }}><label for="operator-overview">Operator Overview</label></p>
|
||||
<p><input type="checkbox" id="operator-detailedview" {{ 'checked' if view.operator_detailedview else '' }}><label for="operator-overview">Operator Detailed View</label></p>
|
||||
<legend>{{ _('Print Layouts') }}:</legend>
|
||||
<p><input type="checkbox" id="client-overview" data-field-name="client-overview" /><label for="client-overview">Client Overview</label></p>
|
||||
<p><input type="checkbox" id="client-detailedview" data-field-name="client-detailedview" /><label for="client-detailedview">Client Detailed View</label></p>
|
||||
<p><input type="checkbox" id="operator-overview" data-field-name="operator-overview" CHECKED /><label for="operator-overview">Operator Overview</label></p>
|
||||
<p><input type="checkbox" id="operator-detailedview" data-field-name="operator-detailedview" CHECKED /><label for="operator-detailedview">Operator Detailed View</label></p>
|
||||
</fieldset>
|
||||
<button id="save-settings" title="{{ _("Includes all settings visible here and also the icon.") }}">{{ _("Save as defaults") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Ładowanie…
Reference in New Issue