lithop 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/Cargo.lock +284 -0
- data/Cargo.toml +6 -0
- data/README.md +25 -7
- data/RELEASING.md +24 -6
- data/docs/README.md +3 -1
- data/docs/artifacts.md +9 -2
- data/docs/bambu-studio.md +5 -1
- data/docs/command.md +25 -2
- data/docs/config.md +1 -1
- data/docs/getting-started.md +8 -5
- data/docs/images/cmyw-lithophane-preview.png +0 -0
- data/docs/local-ui.md +114 -0
- data/docs/tiling.md +4 -2
- data/docs/troubleshooting.md +10 -0
- data/ext/lithop_native/Cargo.toml +17 -0
- data/ext/lithop_native/build.rs +4 -0
- data/ext/lithop_native/extconf.rb +6 -0
- data/ext/lithop_native/src/column_mesh.rs +388 -0
- data/ext/lithop_native/src/lib.rs +37 -0
- data/ext/lithop_native/src/mesh.rs +187 -0
- data/ext/lithop_native/src/palette.rs +67 -0
- data/ext/lithop_native/src/xml.rs +134 -0
- data/lib/lithop/calibration/export.rb +2 -2
- data/lib/lithop/cli/options.rb +13 -0
- data/lib/lithop/cli.rb +28 -0
- data/lib/lithop/export/generation.rb +30 -17
- data/lib/lithop/export/job.rb +2 -2
- data/lib/lithop/export/metadata.rb +8 -3
- data/lib/lithop/export/previews.rb +25 -1
- data/lib/lithop/formats/three_mf/mesh.rb +72 -3
- data/lib/lithop/formats/three_mf/model_writer.rb +19 -16
- data/lib/lithop/formats/three_mf/part_writer.rb +37 -0
- data/lib/lithop/formats/three_mf.rb +1 -1
- data/lib/lithop/image/pipeline.rb +50 -11
- data/lib/lithop/image/svg_loader.rb +5 -5
- data/lib/lithop/job_execution.rb +132 -4
- data/lib/lithop/mesh/column_geometry.rb +8 -0
- data/lib/lithop/mesh/column_topology.rb +2 -0
- data/lib/lithop/model/material_model.rb +17 -5
- data/lib/lithop/model/palette.rb +21 -3
- data/lib/lithop/native.rb +39 -0
- data/lib/lithop/tiling.rb +3 -2
- data/lib/lithop/version.rb +1 -1
- data/lib/lithop/web/app.rb +513 -0
- data/lib/lithop/web/bambu_studio_launcher.rb +82 -0
- data/lib/lithop/web/job_launcher.rb +214 -0
- data/lib/lithop/web/job_store.rb +390 -0
- data/lib/lithop/web/preview_store.rb +182 -0
- data/lib/lithop/web/public/app.css +319 -0
- data/lib/lithop/web/public/app.js +1847 -0
- data/lib/lithop/web/server.rb +21 -0
- data/lib/lithop/web/settings_form.rb +64 -0
- data/lib/lithop/web/settings_library.rb +154 -0
- data/lib/lithop/web/views/index.erb +364 -0
- data/lib/lithop/web/worker.rb +27 -0
- data/lib/lithop.rb +1 -0
- data/sig/lithop.rbs +25 -0
- metadata +84 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "puma"
|
|
4
|
+
require_relative "app"
|
|
5
|
+
|
|
6
|
+
module Lithop
|
|
7
|
+
module Web
|
|
8
|
+
# Starts the local UI with Puma on the loopback interface.
|
|
9
|
+
module Server
|
|
10
|
+
HOST = "127.0.0.1"
|
|
11
|
+
DEFAULT_PORT = 4567
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def run(project_dir:, port: DEFAULT_PORT)
|
|
16
|
+
application = App.configured(project_dir:)
|
|
17
|
+
application.run!(bind: HOST, port:, server: :puma)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lithop
|
|
4
|
+
module Web
|
|
5
|
+
# Resolves browser form values through the same Settings validation used by the CLI.
|
|
6
|
+
class SettingsForm
|
|
7
|
+
FLOAT_FIELDS = %i[
|
|
8
|
+
width height margin_top margin_right margin_bottom margin_left crop_position_x crop_position_y
|
|
9
|
+
crop_scale brightness contrast saturation minimum_thickness maximum_thickness pixel_size
|
|
10
|
+
color_pixel_size nozzle_diameter
|
|
11
|
+
].freeze
|
|
12
|
+
INTEGER_FIELDS = %i[tile_count tile_rows tile_columns color_region_parts].freeze
|
|
13
|
+
STRING_FIELDS = %i[image_fit tile_layout].freeze
|
|
14
|
+
FIELD_NAMES = (FLOAT_FIELDS + INTEGER_FIELDS + STRING_FIELDS + [:separate_z_parts]).map(&:to_s).freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :defaults
|
|
17
|
+
|
|
18
|
+
def initialize(project_dir:)
|
|
19
|
+
@project_dir = File.expand_path(project_dir)
|
|
20
|
+
@file_settings = SettingsFile.load(File.join(@project_dir, SettingsFile::DEFAULT_PATH)).values
|
|
21
|
+
@defaults = Settings.new(**@file_settings)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def resolve(params)
|
|
25
|
+
overrides = parse_fields(params)
|
|
26
|
+
Settings.new(**@file_settings, **overrides)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def values(settings = defaults)
|
|
30
|
+
settings.to_h.slice(*Settings::DEFAULTS.keys).transform_keys(&:to_s)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def parse_fields(params)
|
|
36
|
+
values = {}
|
|
37
|
+
FLOAT_FIELDS.each { |key| assign_number(values, params, key, Float) }
|
|
38
|
+
INTEGER_FIELDS.each { |key| assign_number(values, params, key, Integer) }
|
|
39
|
+
STRING_FIELDS.each { |key| values[key] = params[key.to_s] if present?(params[key.to_s]) }
|
|
40
|
+
values[:separate_z_parts] = params["separate_z_parts"] == "1"
|
|
41
|
+
clear_unused_grid(values)
|
|
42
|
+
values
|
|
43
|
+
rescue ArgumentError
|
|
44
|
+
raise ConfigurationError, "settings must contain valid numbers"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def assign_number(values, params, key, converter)
|
|
48
|
+
value = params[key.to_s]
|
|
49
|
+
return unless present?(value)
|
|
50
|
+
|
|
51
|
+
values[key] = converter == Integer ? Integer(value, 10) : Float(value)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def clear_unused_grid(values)
|
|
55
|
+
return if values.fetch(:tile_layout, defaults.tile_layout) == "grid"
|
|
56
|
+
|
|
57
|
+
values[:tile_rows] = nil
|
|
58
|
+
values[:tile_columns] = nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def present?(value) = !value.nil? && !value.to_s.strip.empty?
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "time"
|
|
6
|
+
|
|
7
|
+
module Lithop
|
|
8
|
+
module Web
|
|
9
|
+
# Persists reusable browser settings without changing config/settings.json.
|
|
10
|
+
# rubocop:disable-next Metrics/ClassLength
|
|
11
|
+
class SettingsLibrary
|
|
12
|
+
SCHEMA_VERSION = 1
|
|
13
|
+
PATH = File.join("config", "ui-settings.json")
|
|
14
|
+
MAX_NAME_LENGTH = 80
|
|
15
|
+
|
|
16
|
+
def initialize(project_dir:, clock: -> { Time.now.utc })
|
|
17
|
+
@project_dir = File.expand_path(project_dir)
|
|
18
|
+
@path = File.join(@project_dir, PATH)
|
|
19
|
+
@clock = clock
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def read
|
|
23
|
+
document = load_document
|
|
24
|
+
{
|
|
25
|
+
"schema_version" => SCHEMA_VERSION,
|
|
26
|
+
"presets" => document.fetch("presets").sort.to_h,
|
|
27
|
+
"draft" => document["draft"]
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def save_preset(name, values)
|
|
32
|
+
update do |document|
|
|
33
|
+
document.fetch("presets")[validate_name!(name)] = entry(values)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def rename_preset(name, new_name)
|
|
38
|
+
update do |document|
|
|
39
|
+
presets = document.fetch("presets")
|
|
40
|
+
source = presets.delete(validate_name!(name))
|
|
41
|
+
raise InputError, "preset not found" unless source
|
|
42
|
+
|
|
43
|
+
target = validate_name!(new_name)
|
|
44
|
+
raise InputError, "preset already exists" if presets.key?(target)
|
|
45
|
+
|
|
46
|
+
presets[target] = source.merge("updated_at" => timestamp)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def delete_preset(name)
|
|
51
|
+
update do |document|
|
|
52
|
+
removed = document.fetch("presets").delete(validate_name!(name))
|
|
53
|
+
raise InputError, "preset not found" unless removed
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def save_draft(values)
|
|
58
|
+
update { |document| document["draft"] = entry(values) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def delete_draft
|
|
62
|
+
update { |document| document.delete("draft") }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def entry(values)
|
|
68
|
+
{ "settings" => normalize(values), "updated_at" => timestamp }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def normalize(values)
|
|
72
|
+
supplied = values.to_h.slice(*SettingsForm::FIELD_NAMES).transform_values do |value|
|
|
73
|
+
if value == true
|
|
74
|
+
"1"
|
|
75
|
+
elsif value == false
|
|
76
|
+
""
|
|
77
|
+
elsif value.nil?
|
|
78
|
+
nil
|
|
79
|
+
else
|
|
80
|
+
value.to_s
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
resolved = SettingsForm.new(project_dir: @project_dir).resolve(supplied)
|
|
84
|
+
SettingsForm.new(project_dir: @project_dir).values(resolved).slice(*SettingsForm::FIELD_NAMES)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def validate_name!(name)
|
|
88
|
+
value = name.to_s.strip
|
|
89
|
+
raise InputError, "preset name is required" if value.empty?
|
|
90
|
+
raise InputError, "preset name is too long" if value.length > MAX_NAME_LENGTH
|
|
91
|
+
raise InputError, "preset name contains unsupported characters" if value.match?(/[\x00-\x1f\x7f]/)
|
|
92
|
+
|
|
93
|
+
value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def update
|
|
97
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
|
98
|
+
lock_directory = File.join(@project_dir, "build")
|
|
99
|
+
FileUtils.mkdir_p(lock_directory)
|
|
100
|
+
lock_path = File.join(lock_directory, ".ui-settings.lock")
|
|
101
|
+
File.open(lock_path, File::RDWR | File::CREAT, 0o600) do |lock|
|
|
102
|
+
lock.flock(File::LOCK_EX)
|
|
103
|
+
document = load_document
|
|
104
|
+
yield document
|
|
105
|
+
write_document(document)
|
|
106
|
+
read
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def load_document
|
|
111
|
+
return empty_document unless File.file?(@path)
|
|
112
|
+
raise InputError, "UI settings file must not be a symbolic link" if File.symlink?(@path)
|
|
113
|
+
|
|
114
|
+
document = JSON.parse(File.read(@path))
|
|
115
|
+
raise InputError, "unsupported UI settings schema" unless document["schema_version"] == SCHEMA_VERSION
|
|
116
|
+
raise InputError, "invalid UI settings file" unless document["presets"].is_a?(Hash)
|
|
117
|
+
|
|
118
|
+
validate_entries!(document)
|
|
119
|
+
document
|
|
120
|
+
rescue JSON::ParserError
|
|
121
|
+
raise InputError, "invalid UI settings JSON"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def validate_entries!(document)
|
|
125
|
+
document.fetch("presets").each_value { |value| validate_entry!(value) }
|
|
126
|
+
validate_entry!(document["draft"]) if document["draft"]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def validate_entry!(entry)
|
|
130
|
+
raise InputError, "invalid saved UI settings" unless entry.is_a?(Hash) && entry["settings"].is_a?(Hash)
|
|
131
|
+
|
|
132
|
+
unknown = entry.fetch("settings").keys - SettingsForm::FIELD_NAMES
|
|
133
|
+
raise InputError, "unsupported saved settings: #{unknown.join(", ")}" unless unknown.empty?
|
|
134
|
+
|
|
135
|
+
entry["settings"] = normalize(entry.fetch("settings"))
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def write_document(document)
|
|
139
|
+
temporary = "#{@path}.tmp-#{Process.pid}"
|
|
140
|
+
File.open(temporary, "w", 0o600) do |file|
|
|
141
|
+
file.write(JSON.pretty_generate(document) << "\n")
|
|
142
|
+
file.flush
|
|
143
|
+
file.fsync
|
|
144
|
+
end
|
|
145
|
+
File.rename(temporary, @path)
|
|
146
|
+
ensure
|
|
147
|
+
FileUtils.rm_f(temporary) if temporary
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def empty_document = { "schema_version" => SCHEMA_VERSION, "presets" => {} }
|
|
151
|
+
def timestamp = @clock.call.iso8601(6)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="ja">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="csrf-token" content="<%= h @csrf_token %>">
|
|
7
|
+
<title>Lithop Local UI</title>
|
|
8
|
+
<link rel="stylesheet" href="/app.css">
|
|
9
|
+
<script src="/app.js" defer></script>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<header class="site-header">
|
|
13
|
+
<button id="menu-toggle" class="menu-toggle" type="button" aria-controls="primary-navigation" aria-expanded="false" aria-label="メニューを開く">☰</button>
|
|
14
|
+
<div class="brand-block">
|
|
15
|
+
<p class="eyebrow">LOCAL CMYW WORKSPACE</p>
|
|
16
|
+
<a class="brand" href="/?view=create" data-view-target="create">Lithop</a>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="header-context">
|
|
19
|
+
<span class="project-name"><%= h @project_name %></span>
|
|
20
|
+
<span aria-hidden="true">/</span>
|
|
21
|
+
<strong id="current-view-label"><%= { "create" => "作成", "jobs" => "ジョブ", "presets" => "プリセット" }.fetch(@initial_view) %></strong>
|
|
22
|
+
</div>
|
|
23
|
+
<div id="global-job-control" class="global-job-control" <%= "hidden" unless @selected_job_id %>>
|
|
24
|
+
<button id="global-job-link" class="header-action" type="button"><span id="global-job-label">ジョブを取得中</span><small id="global-job-percent">0%</small></button>
|
|
25
|
+
<button id="global-cancel-button" class="header-action danger-text" type="button">停止</button>
|
|
26
|
+
</div>
|
|
27
|
+
<button id="help-button" class="header-action" type="button">使い方</button>
|
|
28
|
+
<span class="local-badge">127.0.0.1</span>
|
|
29
|
+
</header>
|
|
30
|
+
|
|
31
|
+
<main class="workspace" data-selected-job="<%= h @selected_job_id %>" data-view="<%= h @initial_view %>">
|
|
32
|
+
<div id="page-notice" class="notice error" role="alert" <%= "hidden" unless @error %>><%= h @error %></div>
|
|
33
|
+
|
|
34
|
+
<div class="app-shell">
|
|
35
|
+
<nav id="primary-navigation" class="primary-navigation" aria-label="主メニュー">
|
|
36
|
+
<button type="button" data-view-target="create" <%= 'aria-current="page"' if @initial_view == "create" %>><span>01</span>作成<small>画像と設定</small></button>
|
|
37
|
+
<button type="button" data-view-target="jobs" <%= 'aria-current="page"' if @initial_view == "jobs" %>><span>02</span>ジョブ<small>履歴と成果物</small></button>
|
|
38
|
+
<button type="button" data-view-target="presets" <%= 'aria-current="page"' if @initial_view == "presets" %>><span>03</span>プリセット<small>設定の再利用</small></button>
|
|
39
|
+
</nav>
|
|
40
|
+
<div id="navigation-scrim" class="navigation-scrim" hidden></div>
|
|
41
|
+
<div class="app-content">
|
|
42
|
+
|
|
43
|
+
<section id="view-create" class="app-view" data-app-view="create" aria-labelledby="create-view-title" <%= "hidden" unless @initial_view == "create" %>>
|
|
44
|
+
<h1 id="create-view-title" class="view-title" tabindex="-1">作成</h1>
|
|
45
|
+
<div class="workspace-primary">
|
|
46
|
+
<section class="panel editor-panel">
|
|
47
|
+
<div class="section-heading">
|
|
48
|
+
<div><p class="step">01 / INPUT</p><h2>画像と設定</h2></div>
|
|
49
|
+
<span class="context-badge">編集中</span>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<form id="conversion-form" action="/jobs" method="post" enctype="multipart/form-data">
|
|
53
|
+
<input type="hidden" name="csrf_token" value="<%= h @csrf_token %>">
|
|
54
|
+
<input id="preview-id" type="hidden" name="preview_id" value="">
|
|
55
|
+
|
|
56
|
+
<label id="file-picker" class="file-picker" for="image-input" tabindex="0">
|
|
57
|
+
<span class="file-title">画像を選択またはドロップ</span>
|
|
58
|
+
<span id="file-name">ファイルをここへ選択</span>
|
|
59
|
+
<input id="image-input" name="image" type="file" accept=".jpeg,.jpg,.png,.svg,image/jpeg,image/png,image/svg+xml" required>
|
|
60
|
+
</label>
|
|
61
|
+
|
|
62
|
+
<details class="settings-section" open>
|
|
63
|
+
<summary><span>基本設定</span><small>寸法とセル</small></summary>
|
|
64
|
+
<div class="settings-body">
|
|
65
|
+
<div class="inline-actions">
|
|
66
|
+
<label class="check-label"><input id="aspect-lock" type="checkbox" checked>縦横比を固定</label>
|
|
67
|
+
<button id="swap-dimensions" class="text-button" type="button">幅と高さを交換</button>
|
|
68
|
+
<button class="text-button section-reset" type="button">初期値へ戻す</button>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="form-grid">
|
|
71
|
+
<label>幅 <span>mm</span><input id="width" name="width" type="number" min="0.01" step="0.01" value="<%= h @values['width'] %>" data-default="<%= h @values['width'] %>" required></label>
|
|
72
|
+
<label>高さ <span>mm</span><input id="height" name="height" type="number" min="0.01" step="0.01" value="<%= h @values['height'] %>" data-default="<%= h @values['height'] %>" required></label>
|
|
73
|
+
<label>XYセル <span>mm</span><input name="pixel_size" type="number" min="0.01" step="0.01" value="<%= h @values['pixel_size'] %>" data-default="<%= h @values['pixel_size'] %>" required></label>
|
|
74
|
+
<label>色セル <span>mm</span><input name="color_pixel_size" type="number" min="0.01" step="0.01" value="<%= h @values['color_pixel_size'] %>" data-default="<%= h @values['color_pixel_size'] %>" placeholder="XYセルと同じ"></label>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</details>
|
|
78
|
+
|
|
79
|
+
<details class="settings-section" open>
|
|
80
|
+
<summary><span>画像配置</span><small>収め方とクロップ</small></summary>
|
|
81
|
+
<div class="settings-body">
|
|
82
|
+
<div class="section-actions"><button class="text-button section-reset" type="button">初期値へ戻す</button></div>
|
|
83
|
+
<div class="form-grid">
|
|
84
|
+
<label>配置
|
|
85
|
+
<select name="image_fit" id="image-fit" data-default="<%= h @values['image_fit'] %>">
|
|
86
|
+
<option value="cover" <%= "selected" if selected?(:image_fit, "cover") %>>全面に配置</option>
|
|
87
|
+
<option value="contain" <%= "selected" if selected?(:image_fit, "contain") %>>全体を収める</option>
|
|
88
|
+
</select>
|
|
89
|
+
</label>
|
|
90
|
+
<label class="range-field">横位置 <span>0–1</span>
|
|
91
|
+
<input class="range-control" type="range" min="0" max="1" step="0.01" data-sync="crop-x" aria-label="横位置スライダー">
|
|
92
|
+
<input id="crop-x" name="crop_position_x" type="number" min="0" max="1" step="0.01" value="<%= h @values['crop_position_x'] %>" data-default="<%= h @values['crop_position_x'] %>" required>
|
|
93
|
+
</label>
|
|
94
|
+
<label class="range-field">縦位置 <span>0–1</span>
|
|
95
|
+
<input class="range-control" type="range" min="0" max="1" step="0.01" data-sync="crop-y" aria-label="縦位置スライダー">
|
|
96
|
+
<input id="crop-y" name="crop_position_y" type="number" min="0" max="1" step="0.01" value="<%= h @values['crop_position_y'] %>" data-default="<%= h @values['crop_position_y'] %>" required>
|
|
97
|
+
</label>
|
|
98
|
+
<label class="range-field">クロップ倍率 <span>0–1</span>
|
|
99
|
+
<input class="range-control" type="range" min="0.01" max="1" step="0.01" data-sync="crop-scale" aria-label="クロップ倍率スライダー">
|
|
100
|
+
<input id="crop-scale" name="crop_scale" type="number" min="0.01" max="1" step="0.01" value="<%= h @values['crop_scale'] %>" data-default="<%= h @values['crop_scale'] %>" required>
|
|
101
|
+
</label>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</details>
|
|
105
|
+
|
|
106
|
+
<details class="settings-section" open>
|
|
107
|
+
<summary><span>画像調整</span><small>明るさ・コントラスト・彩度</small></summary>
|
|
108
|
+
<div class="settings-body">
|
|
109
|
+
<div class="section-actions"><button class="text-button section-reset" type="button">初期値へ戻す</button></div>
|
|
110
|
+
<div class="adjustment-grid">
|
|
111
|
+
<% { "brightness" => "明るさ", "contrast" => "コントラスト", "saturation" => "彩度" }.each do |key, label| %>
|
|
112
|
+
<label class="range-field"><%= label %> <span>%</span>
|
|
113
|
+
<input class="range-control" type="range" min="-100" max="100" step="1" data-sync="<%= key %>" aria-label="<%= label %>スライダー">
|
|
114
|
+
<input id="<%= key %>" name="<%= key %>" type="number" min="-100" max="100" step="1" value="<%= h @values[key] %>" data-default="<%= h @values[key] %>" required>
|
|
115
|
+
</label>
|
|
116
|
+
<% end %>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</details>
|
|
120
|
+
|
|
121
|
+
<details class="settings-section">
|
|
122
|
+
<summary><span>分割</span><small>XYタイルとZプレート</small></summary>
|
|
123
|
+
<div class="settings-body">
|
|
124
|
+
<div class="section-actions"><button class="text-button section-reset" type="button">初期値へ戻す</button></div>
|
|
125
|
+
<div class="form-grid split-grid">
|
|
126
|
+
<label>タイル数<input name="tile_count" type="number" min="1" step="1" value="<%= h @values['tile_count'] %>" data-default="<%= h @values['tile_count'] %>" required></label>
|
|
127
|
+
<label>配置
|
|
128
|
+
<select name="tile_layout" id="tile-layout" data-default="<%= h @values['tile_layout'] %>">
|
|
129
|
+
<% %w[auto columns rows grid].each do |layout| %>
|
|
130
|
+
<option value="<%= layout %>" <%= "selected" if selected?(:tile_layout, layout) %>><%= layout %></option>
|
|
131
|
+
<% end %>
|
|
132
|
+
</select>
|
|
133
|
+
</label>
|
|
134
|
+
<label class="grid-only">行数<input name="tile_rows" type="number" min="1" step="1" value="<%= h @values['tile_rows'] %>" data-default="<%= h @values['tile_rows'] %>"></label>
|
|
135
|
+
<label class="grid-only">列数<input name="tile_columns" type="number" min="1" step="1" value="<%= h @values['tile_columns'] %>" data-default="<%= h @values['tile_columns'] %>"></label>
|
|
136
|
+
<label class="check-label z-parts-toggle"><input id="separate-z-parts" name="separate_z_parts" type="checkbox" value="1" data-default="<%= checked?(:separate_z_parts) ? '1' : '0' %>" <%= "checked" if checked?(:separate_z_parts) %>>Z方向を別プレートに分割</label>
|
|
137
|
+
<label id="color-region-field">色領域の分割数<input id="color-region-parts" name="color_region_parts" type="number" min="1" max="7" step="1" value="<%= h @values['color_region_parts'] %>" data-default="<%= h @values['color_region_parts'] %>" required></label>
|
|
138
|
+
</div>
|
|
139
|
+
<p id="z-parts-summary" class="fine-print"></p>
|
|
140
|
+
<div id="z-parts-diagram" class="z-parts-diagram" aria-label="Z方向のプレート構成"></div>
|
|
141
|
+
</div>
|
|
142
|
+
</details>
|
|
143
|
+
|
|
144
|
+
<details class="settings-section">
|
|
145
|
+
<summary><span>詳細設定</span><small>厚み・余白・ノズル</small></summary>
|
|
146
|
+
<div class="settings-body">
|
|
147
|
+
<div class="section-actions"><button class="text-button section-reset" type="button">初期値へ戻す</button></div>
|
|
148
|
+
<div class="form-grid">
|
|
149
|
+
<label>最小厚み <span>mm</span><input name="minimum_thickness" type="number" min="0.01" step="0.01" value="<%= h @values['minimum_thickness'] %>" data-default="<%= h @values['minimum_thickness'] %>" required></label>
|
|
150
|
+
<label>最大厚み <span>mm</span><input name="maximum_thickness" type="number" min="0.01" step="0.00000001" value="<%= h @values['maximum_thickness'] %>" data-default="<%= h @values['maximum_thickness'] %>" required></label>
|
|
151
|
+
<label>ノズル <span>mm</span>
|
|
152
|
+
<select name="nozzle_diameter" data-default="<%= h @values['nozzle_diameter'] %>">
|
|
153
|
+
<option value="0.2" <%= "selected" if selected?(:nozzle_diameter, 0.2) %>>0.2</option>
|
|
154
|
+
<option value="0.4" <%= "selected" if selected?(:nozzle_diameter, 0.4) %>>0.4</option>
|
|
155
|
+
</select>
|
|
156
|
+
</label>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="quad-grid">
|
|
159
|
+
<% { "margin_top" => "上余白", "margin_right" => "右余白", "margin_bottom" => "下余白", "margin_left" => "左余白" }.each do |key, label| %>
|
|
160
|
+
<label><%= label %> <span>mm</span><input name="<%= key %>" type="number" min="0" step="0.1" value="<%= h @values[key] %>" data-default="<%= h @values[key] %>" required></label>
|
|
161
|
+
<% end %>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</details>
|
|
165
|
+
|
|
166
|
+
<div class="settings-shortcut">
|
|
167
|
+
<div><strong>設定の再利用</strong><small>プリセットと下書きは専用画面で管理します。</small></div>
|
|
168
|
+
<button class="secondary-button" type="button" data-view-target="presets">プリセットを開く</button>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<p id="form-error" class="notice error" role="alert" hidden></p>
|
|
172
|
+
<section id="draft-summary" class="draft-summary" aria-labelledby="draft-summary-title">
|
|
173
|
+
<div class="summary-heading"><strong id="draft-summary-title">変換する内容</strong><span id="draft-summary-change">未実行</span></div>
|
|
174
|
+
<dl id="draft-summary-values"></dl>
|
|
175
|
+
</section>
|
|
176
|
+
<div class="submit-bar">
|
|
177
|
+
<span id="draft-state" aria-live="polite">画像を選択してください</span>
|
|
178
|
+
<button id="submit-button" class="primary-button" type="submit">変換を開始</button>
|
|
179
|
+
</div>
|
|
180
|
+
</form>
|
|
181
|
+
</section>
|
|
182
|
+
|
|
183
|
+
<section class="panel preview-panel">
|
|
184
|
+
<div class="section-heading compact-heading">
|
|
185
|
+
<div><p class="step">02 / PREVIEW</p><h2>入力プレビュー</h2></div>
|
|
186
|
+
<span id="preview-dimensions" class="dimension-badge"><%= h @values['width'] %> × <%= h @values['height'] %> mm</span>
|
|
187
|
+
</div>
|
|
188
|
+
<div class="preview-toolbar" data-viewer="source-preview">
|
|
189
|
+
<button type="button" data-view-action="out" aria-label="縮小">−</button><output data-view-scale>全体</output>
|
|
190
|
+
<button type="button" data-view-action="in" aria-label="拡大">+</button><button type="button" data-view-action="actual">100%</button><button type="button" data-view-action="reset">全体表示</button>
|
|
191
|
+
<button type="button" data-expand-viewer="source-preview" disabled>大きく表示</button>
|
|
192
|
+
<label class="overlay-toggle"><input id="toggle-tile-overlay" type="checkbox">タイル境界</label>
|
|
193
|
+
<label>背景<select data-view-background><option value="checker">透明</option><option value="light">白</option><option value="gray">灰</option><option value="dark">暗色</option></select></label>
|
|
194
|
+
</div>
|
|
195
|
+
<div id="source-preview" class="image-stage preview-viewport" data-background="checker">
|
|
196
|
+
<img id="source-image" alt="選択した入力画像" draggable="false" hidden>
|
|
197
|
+
<img id="tile-overlay" class="tile-overlay" alt="タイル境界" draggable="false" hidden>
|
|
198
|
+
<p id="source-placeholder" role="status" aria-live="polite">画像未選択</p>
|
|
199
|
+
</div>
|
|
200
|
+
<p class="fine-print"><span data-view-hint="source-preview">拡大するとドラッグで表示位置を移動できます。</span> 倍率は画面表示だけに作用し、印刷寸法を変更しません。</p>
|
|
201
|
+
</section>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
</section>
|
|
205
|
+
|
|
206
|
+
<section id="view-jobs" class="app-view" data-app-view="jobs" aria-labelledby="jobs-view-title" <%= "hidden" unless @initial_view == "jobs" %>>
|
|
207
|
+
<h1 id="jobs-view-title" class="view-title" tabindex="-1">ジョブ</h1>
|
|
208
|
+
<div class="jobs-workspace">
|
|
209
|
+
<section id="job-panel" class="panel job-panel" <%= "hidden" unless @selected_job_id %>>
|
|
210
|
+
<button id="back-to-job-list" class="text-button back-to-job-list" type="button">← 一覧へ戻る</button>
|
|
211
|
+
<div class="section-heading"><div><p class="step">03 / OUTPUT</p><h2>表示中の変換ジョブ</h2></div><span id="job-state" class="state-pill" aria-live="polite">待機中</span></div>
|
|
212
|
+
<div class="job-meta"><div><p id="job-view-name" class="job-view-name">ジョブを読み込んでいます</p><p id="job-id" class="job-id"><%= h @selected_job_id %></p></div><p id="job-settings-snapshot" class="fine-print"></p></div>
|
|
213
|
+
<div class="progress-track" role="progressbar" aria-label="変換進捗" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><span id="progress-bar"></span></div>
|
|
214
|
+
<div class="job-copy"><strong id="job-percent">0%</strong><span id="job-message">変換を開始しています</span></div>
|
|
215
|
+
<div class="job-freshness"><span id="job-connection" role="status" aria-live="polite">状態を取得しています</span><span id="job-updated-at"></span><button id="retry-status-button" class="text-button" type="button" hidden>状態を再取得</button></div>
|
|
216
|
+
<div class="job-actions"><button id="cancel-button" class="secondary-button" type="button">停止</button><button id="load-job-settings" class="secondary-button" type="button" hidden>この設定を編集へ読み込む</button><button id="delete-button" class="danger-button" type="button" hidden>ジョブを削除</button></div>
|
|
217
|
+
<p id="delete-summary" class="fine-print" hidden></p>
|
|
218
|
+
<div id="job-detail-tabs" class="job-detail-tabs" role="tablist" aria-label="ジョブ詳細" hidden>
|
|
219
|
+
<button type="button" role="tab" aria-selected="true" aria-controls="job-preview-panel" data-job-tab="preview">プレビュー</button>
|
|
220
|
+
<button type="button" role="tab" aria-selected="false" aria-controls="job-artifacts-panel" data-job-tab="artifacts">成果物</button>
|
|
221
|
+
<button type="button" role="tab" aria-selected="false" aria-controls="job-settings-panel" data-job-tab="settings">設定・ログ</button>
|
|
222
|
+
</div>
|
|
223
|
+
<div id="result-layout" class="result-layout" hidden>
|
|
224
|
+
<section id="job-preview-panel" data-job-panel="preview" role="tabpanel">
|
|
225
|
+
<div id="result-tabs" class="result-tabs" role="tablist" aria-label="プレビュー種類"></div>
|
|
226
|
+
<div class="preview-toolbar" data-viewer="result-preview">
|
|
227
|
+
<button type="button" data-view-action="out" aria-label="縮小">−</button><output data-view-scale>全体</output>
|
|
228
|
+
<button type="button" data-view-action="in" aria-label="拡大">+</button><button type="button" data-view-action="actual">100%</button><button type="button" data-view-action="reset">全体表示</button>
|
|
229
|
+
<button type="button" data-expand-viewer="result-preview">大きく比較</button>
|
|
230
|
+
<label>背景<select data-view-background><option value="checker">透明</option><option value="light">白</option><option value="gray">灰</option><option value="dark">暗色</option></select></label>
|
|
231
|
+
</div>
|
|
232
|
+
<div class="comparison-grid">
|
|
233
|
+
<figure id="corrected-input-figure"><figcaption>補正後入力</figcaption><div id="compare-source-preview" class="result-preview comparison-source preview-viewport" data-viewer-group="result-preview"><img id="compare-source-image" alt="変換時の入力プレビュー" draggable="false"></div></figure>
|
|
234
|
+
<figure><figcaption id="result-caption">CMYW</figcaption><div id="result-preview" class="result-preview preview-viewport" data-background="checker"><img id="result-image" alt="生成されたプレビュー" draggable="false"></div></figure>
|
|
235
|
+
</div>
|
|
236
|
+
<p id="corrected-input-unavailable" class="notice info" hidden>この旧ジョブには補正後入力が保存されていないため、生成結果だけを表示します。</p>
|
|
237
|
+
<p class="fine-print"><span data-view-hint="result-preview">拡大すると2画像を同じ倍率・位置で移動できます。</span></p>
|
|
238
|
+
<p id="result-legend" class="fine-print">材料マスクは白が0層、濃い部分ほどその材料の層数が多い領域です。</p>
|
|
239
|
+
</section>
|
|
240
|
+
<section id="job-artifacts-panel" data-job-panel="artifacts" role="tabpanel" hidden>
|
|
241
|
+
<section id="tile-results" class="tile-results" hidden>
|
|
242
|
+
<div class="section-heading compact-heading"><div><h3>分割モデル</h3><p class="fine-print">各プレビューは対応する3MFの範囲です。</p></div></div>
|
|
243
|
+
<div id="tile-result-list" class="tile-result-list"></div>
|
|
244
|
+
</section>
|
|
245
|
+
<div class="artifact-section"><div class="artifact-heading"><h3>成果物</h3><div><button id="download-models" class="secondary-button" type="button">全3MFをZIP</button><button id="download-all" class="secondary-button" type="button">全成果物をZIP</button></div></div><p id="bambu-open-status" class="artifact-status fine-print" role="status" aria-live="polite"></p><ul id="artifact-list" class="artifact-list"></ul></div>
|
|
246
|
+
</section>
|
|
247
|
+
<section id="job-settings-panel" data-job-panel="settings" role="tabpanel" hidden>
|
|
248
|
+
<h3>生成時の設定</h3>
|
|
249
|
+
<dl id="job-setting-details" class="job-setting-details"></dl>
|
|
250
|
+
<p class="fine-print">この設定を編集へ読み込むと、現在選択中の画像は維持されます。</p>
|
|
251
|
+
<a id="job-detail-log-link" class="text-button" href="#" target="_blank" rel="noopener" hidden>ローカルログを開く</a>
|
|
252
|
+
</section>
|
|
253
|
+
</div>
|
|
254
|
+
<div id="job-error" class="notice error" hidden><p id="job-error-message"></p><div class="recovery-actions"><button id="choose-another-image" class="text-button" type="button">画像を選び直す</button><button id="review-settings" class="text-button" type="button">設定を確認する</button><a id="job-log-link" class="text-button" href="#" target="_blank" rel="noopener" hidden>ログを確認する</a></div></div>
|
|
255
|
+
</section>
|
|
256
|
+
|
|
257
|
+
<aside class="panel history-panel">
|
|
258
|
+
<div class="section-heading compact-heading">
|
|
259
|
+
<div><p class="step">RECENT</p><h2>最近のジョブ</h2></div>
|
|
260
|
+
</div>
|
|
261
|
+
<div class="history-controls">
|
|
262
|
+
<label>検索<input id="history-query" type="search" placeholder="入力名・ジョブID"></label>
|
|
263
|
+
<label>状態<select id="history-filter"><option value="all">すべて</option><option value="active">実行中</option><option value="completed">完了</option><option value="failed">失敗・停止</option></select></label>
|
|
264
|
+
<label>開始日<input id="history-date-from" type="date"></label>
|
|
265
|
+
<label>終了日<input id="history-date-to" type="date"></label>
|
|
266
|
+
<label>並び順<select id="history-sort"><option value="created_desc">新しい順</option><option value="created_asc">古い順</option><option value="name_asc">入力名 A–Z</option><option value="name_desc">入力名 Z–A</option></select></label>
|
|
267
|
+
</div>
|
|
268
|
+
<div class="history-selection-actions">
|
|
269
|
+
<span id="history-selection-status" aria-live="polite">未選択</span>
|
|
270
|
+
<button id="compare-jobs" class="secondary-button" type="button" disabled>2件を比較</button>
|
|
271
|
+
<button id="delete-selected-jobs" class="danger-button" type="button" disabled>選択したジョブを削除</button>
|
|
272
|
+
</div>
|
|
273
|
+
<p id="history-empty" class="empty-copy" <%= "hidden" unless @jobs.empty? %>>まだジョブはありません。</p>
|
|
274
|
+
<ol id="job-list" class="job-list">
|
|
275
|
+
<% @jobs.each do |job| %>
|
|
276
|
+
<li data-state="<%= h job['state'] %>">
|
|
277
|
+
<a href="/?job=<%= h job['job_id'] %>"><strong><%= h(job['input_name'] || job['job_id']) %></strong><span><%= h job['job_id'] %></span><small><%= h job['state'] %> · <%= h job['percent'] %>%</small></a>
|
|
278
|
+
<% if Lithop::JobExecution::FINAL_STATES.include?(job['state']) %>
|
|
279
|
+
<button class="history-delete" type="button" data-job-id="<%= h job['job_id'] %>" data-file-count="<%= h job['file_count'] %>" data-size-bytes="<%= h job['size_bytes'] %>">削除</button>
|
|
280
|
+
<% end %>
|
|
281
|
+
</li>
|
|
282
|
+
<% end %>
|
|
283
|
+
</ol>
|
|
284
|
+
<button id="load-more-jobs" class="secondary-button load-more" type="button">さらに表示</button>
|
|
285
|
+
</aside>
|
|
286
|
+
</div>
|
|
287
|
+
</section>
|
|
288
|
+
|
|
289
|
+
<section id="view-presets" class="app-view" data-app-view="presets" aria-labelledby="presets-view-title" <%= "hidden" unless @initial_view == "presets" %>>
|
|
290
|
+
<h1 id="presets-view-title" class="view-title" tabindex="-1">プリセット</h1>
|
|
291
|
+
<section id="settings-library-section" class="panel preset-panel">
|
|
292
|
+
<div class="section-heading">
|
|
293
|
+
<div><p class="step">REUSABLE SETTINGS</p><h2>プリセットと下書き</h2></div>
|
|
294
|
+
<button class="secondary-button" type="button" data-view-target="create">作成へ戻る</button>
|
|
295
|
+
</div>
|
|
296
|
+
<p class="preset-intro">現在の編集設定を保存・再利用します。入力画像、ジョブID、成果物パスは保存しません。</p>
|
|
297
|
+
<div class="settings-library">
|
|
298
|
+
<label>名前付きプリセット
|
|
299
|
+
<select id="preset-select"><option value="">プリセットを選択</option></select>
|
|
300
|
+
</label>
|
|
301
|
+
<label>プリセット名<input id="preset-name" type="text" maxlength="80" autocomplete="off" placeholder="例: 細線 0.2 mm"></label>
|
|
302
|
+
<div class="library-actions">
|
|
303
|
+
<button id="preset-save" class="secondary-button" type="button">現在設定を保存</button>
|
|
304
|
+
<button id="preset-load" class="text-button" type="button">編集へ読込</button>
|
|
305
|
+
<button id="preset-rename" class="text-button" type="button">名前変更</button>
|
|
306
|
+
<button id="preset-delete" class="text-button danger-text" type="button">削除</button>
|
|
307
|
+
</div>
|
|
308
|
+
<div class="library-actions draft-actions">
|
|
309
|
+
<button id="draft-save" class="secondary-button" type="button">下書き保存</button>
|
|
310
|
+
<button id="draft-restore" class="text-button" type="button">下書きを復元</button>
|
|
311
|
+
<button id="draft-delete" class="text-button danger-text" type="button">下書きを削除</button>
|
|
312
|
+
</div>
|
|
313
|
+
<p id="settings-library-status" class="fine-print" role="status" aria-live="polite">画像は保存しません。復元後に画像を選択してください。</p>
|
|
314
|
+
</div>
|
|
315
|
+
</section>
|
|
316
|
+
</section>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</main>
|
|
320
|
+
|
|
321
|
+
<dialog id="preview-dialog" class="preview-dialog" aria-labelledby="preview-dialog-title">
|
|
322
|
+
<div class="dialog-heading"><div><p class="step">LARGE PREVIEW</p><h2 id="preview-dialog-title">プレビューを大きく表示</h2></div><button id="close-preview-dialog" class="secondary-button" type="button">閉じる</button></div>
|
|
323
|
+
<div class="preview-toolbar" data-viewer="dialog-preview">
|
|
324
|
+
<button type="button" data-view-action="out" aria-label="縮小">−</button><output data-view-scale>全体</output>
|
|
325
|
+
<button type="button" data-view-action="in" aria-label="拡大">+</button><button type="button" data-view-action="actual">100%</button><button type="button" data-view-action="reset">全体表示</button>
|
|
326
|
+
<label>背景<select data-view-background><option value="checker">透明</option><option value="light">白</option><option value="gray">灰</option><option value="dark">暗色</option></select></label>
|
|
327
|
+
</div>
|
|
328
|
+
<div id="dialog-preview-grid" class="dialog-preview-grid">
|
|
329
|
+
<figure id="dialog-source-figure"><figcaption id="dialog-source-caption">入力</figcaption><div id="dialog-source-preview" class="result-preview preview-viewport" data-viewer-group="dialog-preview" data-background="checker"><img id="dialog-source-image" alt="拡大表示する入力" draggable="false"></div></figure>
|
|
330
|
+
<figure id="dialog-result-figure"><figcaption id="dialog-result-caption">生成結果</figcaption><div id="dialog-result-preview" class="result-preview preview-viewport" data-viewer-group="dialog-preview" data-background="checker"><img id="dialog-result-image" alt="拡大表示する生成結果" draggable="false"></div></figure>
|
|
331
|
+
</div>
|
|
332
|
+
<p id="dialog-legend" class="fine-print" hidden></p>
|
|
333
|
+
<p class="fine-print"><span data-view-hint="dialog-preview">拡大するとドラッグまたは矢印キーで移動できます。</span> Escでも閉じられます。</p>
|
|
334
|
+
</dialog>
|
|
335
|
+
|
|
336
|
+
<dialog id="job-compare-dialog" class="preview-dialog compare-dialog" aria-labelledby="job-compare-title">
|
|
337
|
+
<div class="dialog-heading"><div><p class="step">JOB COMPARISON</p><h2 id="job-compare-title">2つのジョブを比較</h2></div><button id="close-job-compare" class="secondary-button" type="button">閉じる</button></div>
|
|
338
|
+
<div class="preview-toolbar" data-viewer="job-compare-preview">
|
|
339
|
+
<button type="button" data-view-action="out" aria-label="縮小">−</button><output data-view-scale>全体</output>
|
|
340
|
+
<button type="button" data-view-action="in" aria-label="拡大">+</button><button type="button" data-view-action="actual">100%</button><button type="button" data-view-action="reset">全体表示</button>
|
|
341
|
+
<label>画像<select id="job-compare-kind"></select></label>
|
|
342
|
+
<label>背景<select data-view-background><option value="checker">透明</option><option value="light">白</option><option value="gray">灰</option><option value="dark">暗色</option></select></label>
|
|
343
|
+
</div>
|
|
344
|
+
<p id="job-compare-status" class="notice info" role="status" hidden></p>
|
|
345
|
+
<div class="job-compare-grid">
|
|
346
|
+
<% %w[a b].each do |side| %>
|
|
347
|
+
<figure><figcaption><strong id="compare-<%= side %>-name"></strong><span id="compare-<%= side %>-meta"></span></figcaption><div id="compare-<%= side %>-preview" class="result-preview preview-viewport" data-viewer-group="job-compare-preview" data-background="checker"><img id="compare-<%= side %>-image" alt="比較する生成結果 <%= side.upcase %>" draggable="false"><p id="compare-<%= side %>-missing" hidden>この画像はありません</p></div><button id="compare-<%= side %>-use" class="text-button" type="button">この設定を編集へ読み込む</button></figure>
|
|
348
|
+
<% end %>
|
|
349
|
+
</div>
|
|
350
|
+
<section class="compare-settings"><h3>仕上がりに関係する設定差</h3><div class="table-scroll"><table><thead><tr><th>項目</th><th>A</th><th>B</th></tr></thead><tbody id="job-compare-differences"></tbody></table></div></section>
|
|
351
|
+
<p class="fine-print">画面上の比較は実測した透過色を保証しません。異なる寸法・縦横比はそれぞれの比率で表示します。</p>
|
|
352
|
+
</dialog>
|
|
353
|
+
|
|
354
|
+
<dialog id="help-dialog" class="help-dialog" aria-labelledby="help-dialog-title">
|
|
355
|
+
<div class="dialog-heading"><div><p class="step">LOCAL HELP</p><h2 id="help-dialog-title">画面の使い方</h2></div><button id="close-help-dialog" class="secondary-button" type="button">閉じる</button></div>
|
|
356
|
+
<ol>
|
|
357
|
+
<li><strong>作成</strong><span>画像を選び、設定と入力プレビューを確認して変換します。</span></li>
|
|
358
|
+
<li><strong>ジョブ</strong><span>進捗、生成結果、分割成果物、ログを確認します。</span></li>
|
|
359
|
+
<li><strong>プリセット</strong><span>現在の編集設定を名前付きで保存し、後から再利用します。</span></li>
|
|
360
|
+
</ol>
|
|
361
|
+
<p class="fine-print">処理中は上部のジョブ表示から進捗確認と停止ができます。画像と成果物は外部サービスへ送信されません。</p>
|
|
362
|
+
</dialog>
|
|
363
|
+
</body>
|
|
364
|
+
</html>
|