Compare commits
No commits in common. "6e15952ffc57f0b0c1a5d5cd2cdc446bef9724ba" and "46ed954e539b26b109451d38ef0e75c3e790ef01" have entirely different histories.
6e15952ffc
...
46ed954e53
@ -32,8 +32,6 @@ def get(user, id):
|
|||||||
if obj['type'] == 'pattern' and 'preview' in obj and '.png' in obj['preview']:
|
if obj['type'] == 'pattern' and 'preview' in obj and '.png' in obj['preview']:
|
||||||
obj['previewUrl'] = uploads.get_presigned_url('projects/{0}/{1}'.format(proj['_id'], obj['preview']))
|
obj['previewUrl'] = uploads.get_presigned_url('projects/{0}/{1}'.format(proj['_id'], obj['preview']))
|
||||||
del obj['preview']
|
del obj['preview']
|
||||||
if obj.get('fullPreview'):
|
|
||||||
obj['fullPreviewUrl'] = uploads.get_presigned_url('projects/{0}/{1}'.format(proj['_id'], obj['fullPreview']))
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def copy_to_project(user, id, project_id):
|
def copy_to_project(user, id, project_id):
|
||||||
@ -56,9 +54,6 @@ def copy_to_project(user, id, project_id):
|
|||||||
obj['createdAt'] = datetime.datetime.now()
|
obj['createdAt'] = datetime.datetime.now()
|
||||||
obj['commentCount'] = 0
|
obj['commentCount'] = 0
|
||||||
if 'preview' in obj: del obj['preview']
|
if 'preview' in obj: del obj['preview']
|
||||||
if obj.get('pattern'):
|
|
||||||
images = wif.generate_images(obj)
|
|
||||||
if images: obj.update(images)
|
|
||||||
db.objects.insert_one(obj)
|
db.objects.insert_one(obj)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
@ -105,15 +100,7 @@ def update(user, id, data):
|
|||||||
if not project: raise util.errors.NotFound('Project not found')
|
if not project: raise util.errors.NotFound('Project not found')
|
||||||
if not util.can_edit_project(user, project):
|
if not util.can_edit_project(user, project):
|
||||||
raise util.errors.Forbidden('Forbidden')
|
raise util.errors.Forbidden('Forbidden')
|
||||||
allowed_keys = ['name', 'description', 'pattern']
|
allowed_keys = ['name', 'description', 'pattern', 'preview']
|
||||||
|
|
||||||
if data.get('pattern'):
|
|
||||||
obj.update(data)
|
|
||||||
images = wif.generate_images(obj)
|
|
||||||
if images:
|
|
||||||
data.update(images)
|
|
||||||
allowed_keys += ['preview', 'fullPreview']
|
|
||||||
|
|
||||||
updater = util.build_updater(data, allowed_keys)
|
updater = util.build_updater(data, allowed_keys)
|
||||||
if updater:
|
if updater:
|
||||||
db.objects.update({'_id': ObjectId(id)}, updater)
|
db.objects.update({'_id': ObjectId(id)}, updater)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import datetime, re
|
import datetime, re
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
from util import database, wif, util
|
from util import database, wif, util
|
||||||
from api import uploads, objects
|
from api import uploads
|
||||||
|
|
||||||
default_pattern = {
|
default_pattern = {
|
||||||
'warp': {
|
'warp': {
|
||||||
@ -118,15 +118,13 @@ def get_objects(user, username, path):
|
|||||||
if not util.can_view_project(user, project):
|
if not util.can_view_project(user, project):
|
||||||
raise util.errors.Forbidden('This project is private')
|
raise util.errors.Forbidden('This project is private')
|
||||||
|
|
||||||
objs = list(db.objects.find({'project': project['_id']}, {'createdAt': 1, 'name': 1, 'description': 1, 'project': 1, 'preview': 1, 'fullPreview': 1, 'type': 1, 'storedName': 1, 'isImage': 1, 'imageBlurHash': 1, 'commentCount': 1}))
|
objs = list(db.objects.find({'project': project['_id']}, {'createdAt': 1, 'name': 1, 'description': 1, 'project': 1, 'preview': 1, 'type': 1, 'storedName': 1, 'isImage': 1, 'imageBlurHash': 1, 'commentCount': 1}))
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
if obj['type'] == 'file' and 'storedName' in obj:
|
if obj['type'] == 'file' and 'storedName' in obj:
|
||||||
obj['url'] = uploads.get_presigned_url('projects/{0}/{1}'.format(project['_id'], obj['storedName']))
|
obj['url'] = uploads.get_presigned_url('projects/{0}/{1}'.format(project['_id'], obj['storedName']))
|
||||||
if obj['type'] == 'pattern' and 'preview' in obj and '.png' in obj['preview']:
|
if obj['type'] == 'pattern' and 'preview' in obj and '.png' in obj['preview']:
|
||||||
obj['previewUrl'] = uploads.get_presigned_url('projects/{0}/{1}'.format(project['_id'], obj['preview']))
|
obj['previewUrl'] = uploads.get_presigned_url('projects/{0}/{1}'.format(project['_id'], obj['preview']))
|
||||||
del obj['preview']
|
del obj['preview']
|
||||||
if obj.get('fullPreview'):
|
|
||||||
obj['fullPreviewUrl'] = uploads.get_presigned_url('projects/{0}/{1}'.format(project['_id'], obj['fullPreview']))
|
|
||||||
return objs
|
return objs
|
||||||
|
|
||||||
def create_object(user, username, path, data):
|
def create_object(user, username, path, data):
|
||||||
@ -158,32 +156,36 @@ def create_object(user, username, path, data):
|
|||||||
uploads.blur_image('projects/' + str(project['_id']) + '/' + data['storedName'], handle_cb)
|
uploads.blur_image('projects/' + str(project['_id']) + '/' + data['storedName'], handle_cb)
|
||||||
return obj
|
return obj
|
||||||
if data['type'] == 'pattern':
|
if data['type'] == 'pattern':
|
||||||
obj = {
|
|
||||||
'project': project['_id'],
|
|
||||||
'createdAt': datetime.datetime.now(),
|
|
||||||
'type': 'pattern',
|
|
||||||
}
|
|
||||||
if data.get('wif'):
|
if data.get('wif'):
|
||||||
try:
|
try:
|
||||||
pattern = wif.loads(data['wif'])
|
pattern = wif.loads(data['wif'])
|
||||||
if pattern:
|
if pattern:
|
||||||
obj['name'] = pattern['name']
|
obj = {
|
||||||
obj['pattern'] = pattern
|
'project': project['_id'],
|
||||||
|
'name': pattern['name'],
|
||||||
|
'createdAt': datetime.datetime.now(),
|
||||||
|
'type': 'pattern',
|
||||||
|
'pattern': pattern
|
||||||
|
}
|
||||||
|
result = db.objects.insert_one(obj)
|
||||||
|
obj['_id'] = result.inserted_id
|
||||||
|
return obj
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise util.errors.BadRequest('Unable to load WIF file. It is either invalid or in a format we cannot understand.')
|
raise util.errors.BadRequest('Unable to load WIF file. It is either invalid or in a format we cannot understand.')
|
||||||
else:
|
elif data.get('name'):
|
||||||
pattern = default_pattern.copy()
|
pattern = default_pattern.copy()
|
||||||
pattern['warp'].update({'shafts': data.get('shafts', 8)})
|
pattern['warp'].update({'shafts': data.get('shafts', 8)})
|
||||||
pattern['weft'].update({'treadles': data.get('treadles', 8)})
|
pattern['weft'].update({'treadles': data.get('treadles', 8)})
|
||||||
obj['name'] = data.get('name') or 'Untitled Pattern'
|
obj = {
|
||||||
obj['pattern'] = pattern
|
'project': project['_id'],
|
||||||
|
'name': data['name'],
|
||||||
|
'createdAt': datetime.datetime.now(),
|
||||||
|
'type': 'pattern',
|
||||||
|
'pattern': pattern
|
||||||
|
}
|
||||||
result = db.objects.insert_one(obj)
|
result = db.objects.insert_one(obj)
|
||||||
obj['_id'] = result.inserted_id
|
obj['_id'] = result.inserted_id
|
||||||
images = wif.generate_images(obj)
|
return obj
|
||||||
if images:
|
|
||||||
db.objects.update_one({'_id': obj['_id']}, {'$set': images})
|
|
||||||
|
|
||||||
return objects.get(user, obj['_id'])
|
|
||||||
raise util.errors.BadRequest('Unable to create object')
|
raise util.errors.BadRequest('Unable to create object')
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,14 +32,6 @@ def get_presigned_url(path):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def upload_file(path, data):
|
|
||||||
s3 = get_s3()
|
|
||||||
s3.upload_fileobj(
|
|
||||||
data,
|
|
||||||
os.environ['AWS_S3_BUCKET'],
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_file(key):
|
def get_file(key):
|
||||||
s3 = get_s3()
|
s3 = get_s3()
|
||||||
return s3.get_object(
|
return s3.get_object(
|
||||||
|
1663
api/poetry.lock
generated
1663
api/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -22,7 +22,6 @@ blurhash-python = "^1.0.2"
|
|||||||
gunicorn = "^20.0.4"
|
gunicorn = "^20.0.4"
|
||||||
sentry-sdk = {extras = ["flask"], version = "^1.5.10"}
|
sentry-sdk = {extras = ["flask"], version = "^1.5.10"}
|
||||||
pyOpenSSL = "^22.0.0"
|
pyOpenSSL = "^22.0.0"
|
||||||
pillow = "^10.2.0"
|
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
|
|
||||||
|
202
api/util/wif.py
202
api/util/wif.py
@ -1,7 +1,4 @@
|
|||||||
import io, time
|
|
||||||
import configparser
|
import configparser
|
||||||
from PIL import Image, ImageDraw
|
|
||||||
from api import uploads
|
|
||||||
|
|
||||||
def normalise_colour(max_color, triplet):
|
def normalise_colour(max_color, triplet):
|
||||||
color_factor = 256/max_color
|
color_factor = 256/max_color
|
||||||
@ -19,19 +16,6 @@ def denormalise_colour(max_color, triplet):
|
|||||||
new_components.append(str(int(float(color_factor) * int(component))))
|
new_components.append(str(int(float(color_factor) * int(component))))
|
||||||
return ','.join(new_components)
|
return ','.join(new_components)
|
||||||
|
|
||||||
def colour_tuple(triplet):
|
|
||||||
if not triplet: return None
|
|
||||||
components = triplet.split(',')
|
|
||||||
return tuple(map(lambda c: int(c), components))
|
|
||||||
|
|
||||||
def darken_colour(c_tuple, val):
|
|
||||||
def darken(c):
|
|
||||||
c = c * val
|
|
||||||
if c < 0: c = 0
|
|
||||||
if c > 255: c = 255
|
|
||||||
return int(c)
|
|
||||||
return tuple(map(darken, c_tuple))
|
|
||||||
|
|
||||||
def get_colour_index(colours, colour):
|
def get_colour_index(colours, colour):
|
||||||
for (index, c) in enumerate(colours):
|
for (index, c) in enumerate(colours):
|
||||||
if c == colour: return index + 1
|
if c == colour: return index + 1
|
||||||
@ -213,189 +197,3 @@ def loads(wif_file):
|
|||||||
draft['tieups'][int(x)-1] = []
|
draft['tieups'][int(x)-1] = []
|
||||||
|
|
||||||
return draft
|
return draft
|
||||||
|
|
||||||
def generate_images(obj):
|
|
||||||
try:
|
|
||||||
return {
|
|
||||||
'preview': draw_image(obj),
|
|
||||||
'fullPreview': draw_image(obj, with_plan=True)
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def draw_image(obj, with_plan=False):
|
|
||||||
if not obj or not obj['pattern']: raise Exception('Invalid pattern')
|
|
||||||
BASE_SIZE = 10
|
|
||||||
pattern = obj['pattern']
|
|
||||||
warp = pattern['warp']
|
|
||||||
weft = pattern['weft']
|
|
||||||
tieups = pattern['tieups']
|
|
||||||
|
|
||||||
full_width = len(warp['threading']) * BASE_SIZE + BASE_SIZE + weft['treadles'] * BASE_SIZE + BASE_SIZE if with_plan else len(warp['threading']) * BASE_SIZE
|
|
||||||
full_height = warp['shafts'] * BASE_SIZE + len(weft['treadling']) * BASE_SIZE + BASE_SIZE * 2 if with_plan else len(weft['treadling']) * BASE_SIZE
|
|
||||||
|
|
||||||
warp_top = 0
|
|
||||||
warp_left = 0
|
|
||||||
warp_right = len(warp['threading']) * BASE_SIZE
|
|
||||||
warp_bottom = warp['shafts'] * BASE_SIZE + BASE_SIZE
|
|
||||||
|
|
||||||
weft_left = warp_right + BASE_SIZE
|
|
||||||
weft_top = warp['shafts'] * BASE_SIZE + BASE_SIZE * 2
|
|
||||||
weft_right = warp_right + BASE_SIZE + weft['treadles'] * BASE_SIZE + BASE_SIZE
|
|
||||||
weft_bottom = weft_top + len(weft['treadling']) * BASE_SIZE
|
|
||||||
|
|
||||||
tieup_left = warp_right + BASE_SIZE
|
|
||||||
tieup_top = BASE_SIZE
|
|
||||||
tieup_right = tieup_left + weft['treadles'] * BASE_SIZE
|
|
||||||
tieup_bottom = warp_bottom
|
|
||||||
|
|
||||||
drawdown_top = warp_bottom + BASE_SIZE if with_plan else 0
|
|
||||||
drawdown_right = warp_right if with_plan else full_width
|
|
||||||
drawdown_left = warp_left if with_plan else 0
|
|
||||||
drawdown_bottom = weft_bottom if with_plan else full_height
|
|
||||||
|
|
||||||
WHITE=(255,255,255)
|
|
||||||
GREY = (150,150,150)
|
|
||||||
BLACK = (0,0,0)
|
|
||||||
img = Image.new("RGBA", (full_width, full_height), WHITE)
|
|
||||||
draw = ImageDraw.Draw(img)
|
|
||||||
|
|
||||||
# Draw warp
|
|
||||||
if with_plan:
|
|
||||||
draw.rectangle([
|
|
||||||
(warp_left, warp_top),
|
|
||||||
(warp_right, warp_bottom)
|
|
||||||
], fill=None, outline=GREY, width=1)
|
|
||||||
for y in range(1, warp['shafts'] + 1):
|
|
||||||
ycoord = y * BASE_SIZE
|
|
||||||
draw.line([
|
|
||||||
(warp_left, ycoord),
|
|
||||||
(warp_right, ycoord),
|
|
||||||
],
|
|
||||||
fill=GREY, width=1, joint=None)
|
|
||||||
for (i, x) in enumerate(range(len(warp['threading'])-1, 0, -1)):
|
|
||||||
thread = warp['threading'][i]
|
|
||||||
xcoord = x * BASE_SIZE
|
|
||||||
draw.line([
|
|
||||||
(xcoord, warp_top),
|
|
||||||
(xcoord, warp_bottom),
|
|
||||||
],
|
|
||||||
fill=GREY, width=1, joint=None)
|
|
||||||
if thread.get('shaft', 0) > 0:
|
|
||||||
ycoord = warp_bottom - (thread['shaft'] * BASE_SIZE)
|
|
||||||
draw.rectangle([
|
|
||||||
(xcoord, ycoord),
|
|
||||||
(xcoord + BASE_SIZE, ycoord + BASE_SIZE)
|
|
||||||
], fill=BLACK, outline=None, width=1)
|
|
||||||
colour = warp['defaultColour']
|
|
||||||
if thread and thread.get('colour'):
|
|
||||||
colour = thread['colour']
|
|
||||||
draw.rectangle([
|
|
||||||
(xcoord, warp_top),
|
|
||||||
(xcoord + BASE_SIZE, warp_top + BASE_SIZE),
|
|
||||||
], fill=colour_tuple(colour))
|
|
||||||
|
|
||||||
# Draw weft
|
|
||||||
draw.rectangle([
|
|
||||||
(weft_left, weft_top),
|
|
||||||
(weft_right, weft_bottom)
|
|
||||||
], fill=None, outline=GREY, width=1)
|
|
||||||
for x in range(1, weft['treadles'] + 1):
|
|
||||||
xcoord = weft_left + x * BASE_SIZE
|
|
||||||
draw.line([
|
|
||||||
(xcoord, weft_top),
|
|
||||||
(xcoord, weft_bottom),
|
|
||||||
],
|
|
||||||
fill=GREY, width=1, joint=None)
|
|
||||||
for (i, y) in enumerate(range(0, len(weft['treadling']))):
|
|
||||||
thread = weft['treadling'][i]
|
|
||||||
ycoord = weft_top + y * BASE_SIZE
|
|
||||||
draw.line([
|
|
||||||
(weft_left, ycoord),
|
|
||||||
(weft_right, ycoord),
|
|
||||||
],
|
|
||||||
fill=GREY, width=1, joint=None)
|
|
||||||
if thread.get('treadle', 0) > 0:
|
|
||||||
xcoord = weft_left + (thread['treadle'] - 1) * BASE_SIZE
|
|
||||||
draw.rectangle([
|
|
||||||
(xcoord, ycoord),
|
|
||||||
(xcoord + BASE_SIZE, ycoord + BASE_SIZE)
|
|
||||||
], fill=BLACK, outline=None, width=1)
|
|
||||||
colour = weft['defaultColour']
|
|
||||||
if thread and thread.get('colour'):
|
|
||||||
colour = thread['colour']
|
|
||||||
draw.rectangle([
|
|
||||||
(weft_right - BASE_SIZE, ycoord),
|
|
||||||
(weft_right, ycoord + BASE_SIZE),
|
|
||||||
], fill=colour_tuple(colour))
|
|
||||||
|
|
||||||
# Draw tieups
|
|
||||||
draw.rectangle([
|
|
||||||
(tieup_left, tieup_top),
|
|
||||||
(tieup_right, tieup_bottom)
|
|
||||||
], fill=None, outline=GREY, width=1)
|
|
||||||
for y in range(1, warp['shafts'] + 1):
|
|
||||||
ycoord = y * BASE_SIZE
|
|
||||||
draw.line([
|
|
||||||
(tieup_left, ycoord),
|
|
||||||
(tieup_right, ycoord),
|
|
||||||
],
|
|
||||||
fill=GREY, width=1, joint=None)
|
|
||||||
for (x, tieup) in enumerate(tieups):
|
|
||||||
xcoord = tieup_left + x * BASE_SIZE
|
|
||||||
draw.line([
|
|
||||||
(xcoord, tieup_top),
|
|
||||||
(xcoord, tieup_bottom),
|
|
||||||
],
|
|
||||||
fill=GREY, width=1, joint=None)
|
|
||||||
for entry in tieup:
|
|
||||||
if entry > 0:
|
|
||||||
ycoord = tieup_bottom - (entry * BASE_SIZE)
|
|
||||||
draw.rectangle([
|
|
||||||
(xcoord, ycoord),
|
|
||||||
(xcoord + BASE_SIZE, ycoord + BASE_SIZE)
|
|
||||||
], fill=BLACK, outline=None, width=1)
|
|
||||||
|
|
||||||
# Draw drawdown
|
|
||||||
draw.rectangle([
|
|
||||||
(drawdown_left, drawdown_top),
|
|
||||||
(drawdown_right, drawdown_bottom)
|
|
||||||
], fill=None, outline=(0,0,0), width=1)
|
|
||||||
for (y, weft_thread) in enumerate(weft['treadling']):
|
|
||||||
for (x, warp_thread) in enumerate(warp['threading']):
|
|
||||||
treadle = 0 if weft_thread['treadle'] > weft['treadles'] else weft_thread['treadle']
|
|
||||||
shaft = warp_thread['shaft']
|
|
||||||
weft_colour = weft_thread.get('colour') or weft.get('defaultColour')
|
|
||||||
warp_colour = warp_thread.get('colour') or warp.get('defaultColour')
|
|
||||||
tieup = tieups[treadle-1] if treadle > 0 else []
|
|
||||||
tieup = [t for t in tieup if t < warp['shafts']]
|
|
||||||
thread_type = 'warp' if shaft in tieup else 'weft'
|
|
||||||
x1 = drawdown_right - (x + 1) * BASE_SIZE
|
|
||||||
x2 = drawdown_right - x * BASE_SIZE
|
|
||||||
y1 = drawdown_top + y * BASE_SIZE
|
|
||||||
y2 = drawdown_top + (y + 1) * BASE_SIZE
|
|
||||||
colour = colour_tuple(warp_colour if thread_type == 'warp' else weft_colour)
|
|
||||||
d = [0.6, 0.8, 0.9, 1.1, 1.3, 1.3, 1.1, 0.9, 0.8, 0.6, 0.5]
|
|
||||||
if thread_type == 'warp':
|
|
||||||
for (i, grad_x) in enumerate(range(x1, x2)):
|
|
||||||
draw.line([
|
|
||||||
(grad_x, y1), (grad_x, y2),
|
|
||||||
],
|
|
||||||
fill=(darken_colour(colour, d[i])), width=1, joint=None)
|
|
||||||
else:
|
|
||||||
for (i, grad_y) in enumerate(range(y1, y2)):
|
|
||||||
draw.line([
|
|
||||||
(x1, grad_y), (x2, grad_y),
|
|
||||||
],
|
|
||||||
fill=(darken_colour(colour, d[i])), width=1, joint=None)
|
|
||||||
|
|
||||||
in_mem_file = io.BytesIO()
|
|
||||||
img.save(in_mem_file, 'PNG')
|
|
||||||
in_mem_file.seek(0)
|
|
||||||
file_name = 'preview-{0}_{1}.png'.format(
|
|
||||||
'full' if with_plan else 'base', obj['_id']
|
|
||||||
)
|
|
||||||
path = 'projects/{}/{}'.format(obj['project'], file_name)
|
|
||||||
uploads.upload_file(path, in_mem_file)
|
|
||||||
return file_name
|
|
||||||
|
@ -1,38 +1,4 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- DKImagePickerController/Core (4.3.4):
|
|
||||||
- DKImagePickerController/ImageDataManager
|
|
||||||
- DKImagePickerController/Resource
|
|
||||||
- DKImagePickerController/ImageDataManager (4.3.4)
|
|
||||||
- DKImagePickerController/PhotoGallery (4.3.4):
|
|
||||||
- DKImagePickerController/Core
|
|
||||||
- DKPhotoGallery
|
|
||||||
- DKImagePickerController/Resource (4.3.4)
|
|
||||||
- DKPhotoGallery (0.0.17):
|
|
||||||
- DKPhotoGallery/Core (= 0.0.17)
|
|
||||||
- DKPhotoGallery/Model (= 0.0.17)
|
|
||||||
- DKPhotoGallery/Preview (= 0.0.17)
|
|
||||||
- DKPhotoGallery/Resource (= 0.0.17)
|
|
||||||
- SDWebImage
|
|
||||||
- SwiftyGif
|
|
||||||
- DKPhotoGallery/Core (0.0.17):
|
|
||||||
- DKPhotoGallery/Model
|
|
||||||
- DKPhotoGallery/Preview
|
|
||||||
- SDWebImage
|
|
||||||
- SwiftyGif
|
|
||||||
- DKPhotoGallery/Model (0.0.17):
|
|
||||||
- SDWebImage
|
|
||||||
- SwiftyGif
|
|
||||||
- DKPhotoGallery/Preview (0.0.17):
|
|
||||||
- DKPhotoGallery/Model
|
|
||||||
- DKPhotoGallery/Resource
|
|
||||||
- SDWebImage
|
|
||||||
- SwiftyGif
|
|
||||||
- DKPhotoGallery/Resource (0.0.17):
|
|
||||||
- SDWebImage
|
|
||||||
- SwiftyGif
|
|
||||||
- file_picker (0.0.1):
|
|
||||||
- DKImagePickerController/PhotoGallery
|
|
||||||
- Flutter
|
|
||||||
- Firebase/CoreOnly (10.9.0):
|
- Firebase/CoreOnly (10.9.0):
|
||||||
- FirebaseCore (= 10.9.0)
|
- FirebaseCore (= 10.9.0)
|
||||||
- Firebase/Messaging (10.9.0):
|
- Firebase/Messaging (10.9.0):
|
||||||
@ -94,37 +60,23 @@ PODS:
|
|||||||
- nanopb/encode (= 2.30909.0)
|
- nanopb/encode (= 2.30909.0)
|
||||||
- nanopb/decode (2.30909.0)
|
- nanopb/decode (2.30909.0)
|
||||||
- nanopb/encode (2.30909.0)
|
- nanopb/encode (2.30909.0)
|
||||||
- path_provider_foundation (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- PromisesObjC (2.2.0)
|
- PromisesObjC (2.2.0)
|
||||||
- SDWebImage (5.18.8):
|
|
||||||
- SDWebImage/Core (= 5.18.8)
|
|
||||||
- SDWebImage/Core (5.18.8)
|
|
||||||
- share_plus (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- SwiftyGif (5.4.4)
|
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
|
||||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- DKImagePickerController
|
|
||||||
- DKPhotoGallery
|
|
||||||
- Firebase
|
- Firebase
|
||||||
- FirebaseCore
|
- FirebaseCore
|
||||||
- FirebaseCoreInternal
|
- FirebaseCoreInternal
|
||||||
@ -134,12 +86,8 @@ SPEC REPOS:
|
|||||||
- GoogleUtilities
|
- GoogleUtilities
|
||||||
- nanopb
|
- nanopb
|
||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
- SDWebImage
|
|
||||||
- SwiftyGif
|
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
file_picker:
|
|
||||||
:path: ".symlinks/plugins/file_picker/ios"
|
|
||||||
firebase_core:
|
firebase_core:
|
||||||
:path: ".symlinks/plugins/firebase_core/ios"
|
:path: ".symlinks/plugins/firebase_core/ios"
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
@ -148,19 +96,12 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter
|
:path: Flutter
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
path_provider_foundation:
|
|
||||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
|
||||||
share_plus:
|
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
|
||||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
|
||||||
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
|
|
||||||
Firebase: bd152f0f3d278c4060c5c71359db08ebcfd5a3e2
|
Firebase: bd152f0f3d278c4060c5c71359db08ebcfd5a3e2
|
||||||
firebase_core: ce64b0941c6d87c6ef5022ae9116a158236c8c94
|
firebase_core: ce64b0941c6d87c6ef5022ae9116a158236c8c94
|
||||||
firebase_messaging: 42912365e62efc1ea3e00724e5eecba6068ddb88
|
firebase_messaging: 42912365e62efc1ea3e00724e5eecba6068ddb88
|
||||||
@ -173,14 +114,10 @@ SPEC CHECKSUMS:
|
|||||||
GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749
|
GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749
|
||||||
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
|
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
|
||||||
nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431
|
nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
|
||||||
PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef
|
PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef
|
||||||
SDWebImage: a81bbb3ba4ea5f810f4069c68727cb118467a04a
|
|
||||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
|
||||||
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
|
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
|
||||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
|
||||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||||
|
|
||||||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
||||||
|
|
||||||
COCOAPODS: 1.14.2
|
COCOAPODS: 1.12.0
|
||||||
|
@ -170,7 +170,7 @@
|
|||||||
97C146E61CF9000F007C117D /* Project object */ = {
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastUpgradeCheck = 1430;
|
LastUpgradeCheck = 1300;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
97C146ED1CF9000F007C117D = {
|
97C146ED1CF9000F007C117D = {
|
||||||
@ -237,7 +237,6 @@
|
|||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
|
||||||
);
|
);
|
||||||
name = "Thin Binary";
|
name = "Thin Binary";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1430"
|
LastUpgradeVersion = "1300"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
@ -3,13 +3,12 @@ import 'package:http/http.dart' as http;
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'util.dart';
|
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
|
|
||||||
String? _token;
|
String? _token;
|
||||||
//final String apiBase = 'https://api.treadl.com';
|
final String apiBase = 'https://api.treadl.com';
|
||||||
final String apiBase = 'http://192.168.5.134:2001';
|
//final String apiBase = 'http://192.168.5.86:2001';
|
||||||
|
|
||||||
Future<String?> loadToken() async {
|
Future<String?> loadToken() async {
|
||||||
if (_token != null) {
|
if (_token != null) {
|
||||||
@ -103,18 +102,4 @@ class Api {
|
|||||||
int status = response.statusCode;
|
int status = response.statusCode;
|
||||||
return status == 200;
|
return status == 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> downloadFile(String url, String fileName) async {
|
|
||||||
Uri uri = Uri.parse(url);
|
|
||||||
http.Client client = http.Client();
|
|
||||||
http.Response response = await client.get(uri);
|
|
||||||
if(response.statusCode == 200) {
|
|
||||||
Util util = Util();
|
|
||||||
final String dirPath = await util.storagePath();
|
|
||||||
final file = File('$dirPath/$fileName');
|
|
||||||
await file.writeAsBytes(response.bodyBytes);
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class _GroupsTabState extends State<GroupsTab> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildGroupCard(Map<String,dynamic> group) {
|
Widget buildGroupCard(Map<String,dynamic> group) {
|
||||||
String? description = group['description'];
|
String description = group['description'];
|
||||||
if (description != null && description.length > 80) {
|
if (description != null && description.length > 80) {
|
||||||
description = description.substring(0, 77) + '...';
|
description = description.substring(0, 77) + '...';
|
||||||
} else {
|
} else {
|
||||||
@ -44,11 +44,16 @@ class _GroupsTabState extends State<GroupsTab> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: ListTile(
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
new ListTile(
|
||||||
leading: Icon(Icons.people),
|
leading: Icon(Icons.people),
|
||||||
trailing: Icon(Icons.keyboard_arrow_right),
|
trailing: Icon(Icons.keyboard_arrow_right),
|
||||||
title: Text(group['name']),
|
title: Text(group['name']),
|
||||||
subtitle: Text(description.replaceAll("\n", " ")),
|
subtitle: Text(description.replaceAll("\n", " ")),
|
||||||
|
),
|
||||||
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -3,60 +3,17 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:flutter_html/flutter_html.dart';
|
import 'package:flutter_html/flutter_html.dart';
|
||||||
import 'dart:io';
|
|
||||||
import 'api.dart';
|
import 'api.dart';
|
||||||
import 'util.dart';
|
|
||||||
import 'patterns/pattern.dart';
|
|
||||||
import 'patterns/viewer.dart';
|
|
||||||
|
|
||||||
class _ObjectScreenState extends State<ObjectScreen> {
|
class _ObjectScreenState extends State<ObjectScreen> {
|
||||||
final Map<String,dynamic> _project;
|
final Map<String,dynamic> _project;
|
||||||
Map<String,dynamic> _object;
|
Map<String,dynamic> _object;
|
||||||
Map<String,dynamic>? _pattern;
|
|
||||||
bool _isLoading = false;
|
|
||||||
final Function _onUpdate;
|
final Function _onUpdate;
|
||||||
final Function _onDelete;
|
final Function _onDelete;
|
||||||
final Api api = Api();
|
final Api api = Api();
|
||||||
final Util util = Util();
|
|
||||||
|
|
||||||
_ObjectScreenState(this._object, this._project, this._onUpdate, this._onDelete) { }
|
_ObjectScreenState(this._object, this._project, this._onUpdate, this._onDelete) { }
|
||||||
|
|
||||||
@override
|
|
||||||
initState() {
|
|
||||||
super.initState();
|
|
||||||
if (_object['type'] == 'pattern') {
|
|
||||||
_fetchPattern();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _fetchPattern() async {
|
|
||||||
var data = await api.request('GET', '/objects/' + _object['_id']);
|
|
||||||
if (data['success'] == true) {
|
|
||||||
setState(() {
|
|
||||||
_pattern = data['payload']['pattern'];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _shareObject() async {
|
|
||||||
setState(() => _isLoading = true);
|
|
||||||
File? file;
|
|
||||||
if (_object['type'] == 'pattern') {
|
|
||||||
var data = await api.request('GET', '/objects/' + _object['_id'] + '/wif');
|
|
||||||
if (data['success'] == true) {
|
|
||||||
file = await util.writeFile(_object['name'] + '.wif', data['payload']['wif']);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String fileName = Uri.file(_object['url']).pathSegments.last;
|
|
||||||
file = await api.downloadFile(_object['url'], fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file != null) {
|
|
||||||
util.shareFile(file!, withDelete: true);
|
|
||||||
}
|
|
||||||
setState(() => _isLoading = false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _deleteObject(BuildContext context, BuildContext modalContext) async {
|
void _deleteObject(BuildContext context, BuildContext modalContext) async {
|
||||||
var data = await api.request('DELETE', '/objects/' + _object['_id']);
|
var data = await api.request('DELETE', '/objects/' + _object['_id']);
|
||||||
if (data['success']) {
|
if (data['success']) {
|
||||||
@ -111,6 +68,7 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
|||||||
TextButton(
|
TextButton(
|
||||||
child: Text('OK'),
|
child: Text('OK'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
print(renameController.text);
|
||||||
var data = await api.request('PUT', '/objects/' + _object['_id'], {'name': renameController.text});
|
var data = await api.request('PUT', '/objects/' + _object['_id'], {'name': renameController.text});
|
||||||
if (data['success']) {
|
if (data['success']) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@ -160,10 +118,7 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
|||||||
return Image.network(_object['url']);
|
return Image.network(_object['url']);
|
||||||
}
|
}
|
||||||
else if (_object['type'] == 'pattern') {
|
else if (_object['type'] == 'pattern') {
|
||||||
if (_pattern != null) {
|
if (_object['previewUrl'] != null) {
|
||||||
return PatternViewer(_pattern!, withEditor: true);
|
|
||||||
}
|
|
||||||
else if (_object['previewUrl'] != null) {
|
|
||||||
return Image.network(_object['previewUrl']!);;
|
return Image.network(_object['previewUrl']!);;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -178,16 +133,9 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return Center(child: Column(
|
return ElevatedButton(child: Text('View file'), onPressed: () {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text('Treadl cannot display this type of item.'),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
ElevatedButton(child: Text('View file'), onPressed: () {
|
|
||||||
launch(_object['url']);
|
launch(_object['url']);
|
||||||
}),
|
});
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,12 +148,6 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(_object['name']),
|
title: Text(_object['name']),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.ios_share),
|
|
||||||
onPressed: () {
|
|
||||||
_shareObject();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.settings),
|
icon: Icon(Icons.settings),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -216,10 +158,10 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
|||||||
),
|
),
|
||||||
body: Container(
|
body: Container(
|
||||||
margin: const EdgeInsets.all(10.0),
|
margin: const EdgeInsets.all(10.0),
|
||||||
child: Column(
|
child: ListView(
|
||||||
children: [
|
children: <Widget>[
|
||||||
_isLoading ? LinearProgressIndicator() : SizedBox(height: 0),
|
getObjectWidget(),
|
||||||
Expanded(child: getObjectWidget()),
|
Html(data: description)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -236,4 +178,3 @@ class ObjectScreen extends StatefulWidget {
|
|||||||
@override
|
@override
|
||||||
_ObjectScreenState createState() => _ObjectScreenState(_object, _project, _onUpdate, _onDelete);
|
_ObjectScreenState createState() => _ObjectScreenState(_object, _project, _onUpdate, _onDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'dart:ui' as ui;
|
|
||||||
import '../util.dart';
|
|
||||||
|
|
||||||
class DrawdownPainter extends CustomPainter {
|
|
||||||
final Map<String,dynamic> pattern;
|
|
||||||
final double BASE_SIZE;
|
|
||||||
final Util util = Util();
|
|
||||||
|
|
||||||
@override
|
|
||||||
DrawdownPainter(this.BASE_SIZE, this.pattern) {}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
var weft = pattern['weft'];
|
|
||||||
var warp = pattern['warp'];
|
|
||||||
var tieups = pattern['tieups'];
|
|
||||||
|
|
||||||
var paint = Paint()
|
|
||||||
..color = Colors.black
|
|
||||||
..strokeWidth = 1;
|
|
||||||
|
|
||||||
// Draw grid
|
|
||||||
for (double i = 0; i <= size.width; i += BASE_SIZE) {
|
|
||||||
canvas.drawLine(Offset(i.toDouble(), size.height), Offset(i.toDouble(), 0), paint);
|
|
||||||
}
|
|
||||||
for (double y = 0; y <= size.height; y += BASE_SIZE) {
|
|
||||||
canvas.drawLine(Offset(0, y.toDouble()), Offset(size.width, y.toDouble()), paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int tread = 0; tread < weft['treadling']?.length; tread++) {
|
|
||||||
for (int thread = 0; thread < warp['threading']?.length; thread++) {
|
|
||||||
// Ensure we only get a treadle in the allowed bounds
|
|
||||||
int treadle = weft['treadling'][tread]['treadle'] > weft['treadles'] ? 0 : weft['treadling'][tread]['treadle'];
|
|
||||||
int shaft = warp['threading'][thread]['shaft'];
|
|
||||||
Color weftColour = util.rgb(weft['treadling'][tread]['colour'] ?? weft['defaultColour']);
|
|
||||||
Color warpColour = util.rgb(warp['threading'][thread]['colour'] ?? warp['defaultColour']);
|
|
||||||
|
|
||||||
// Only capture valid tie-ups (e.g. in case there is data for more shafts, which are then reduced)
|
|
||||||
// Dart throws error if index < 0 so check fiest
|
|
||||||
List<dynamic> tieup = treadle > 0 ? tieups[treadle - 1] : [];
|
|
||||||
List<dynamic> filteredTieup = tieup.where((t) => t< warp['shafts']).toList();
|
|
||||||
String threadType = filteredTieup.contains(shaft) ? 'warp' : 'weft';
|
|
||||||
|
|
||||||
Rect rect = Offset(
|
|
||||||
size.width - BASE_SIZE * (thread + 1),
|
|
||||||
tread * BASE_SIZE
|
|
||||||
) & Size(BASE_SIZE, BASE_SIZE);
|
|
||||||
canvas.drawRect(
|
|
||||||
rect,
|
|
||||||
Paint()
|
|
||||||
..color = threadType == 'warp' ? warpColour : weftColour
|
|
||||||
);
|
|
||||||
|
|
||||||
canvas.drawRect(
|
|
||||||
rect,
|
|
||||||
Paint()
|
|
||||||
..shader = ui.Gradient.linear(
|
|
||||||
threadType == 'warp' ? rect.centerLeft : rect.topCenter,
|
|
||||||
threadType == 'warp' ? rect.centerRight : rect.bottomCenter,
|
|
||||||
[
|
|
||||||
Color.fromRGBO(0,0,0,0.4),
|
|
||||||
Color.fromRGBO(0,0,0,0.0),
|
|
||||||
Color.fromRGBO(0,0,0,0.4),
|
|
||||||
],
|
|
||||||
[0.0,0.5,1.0],
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'warp.dart';
|
|
||||||
import 'weft.dart';
|
|
||||||
import 'tieup.dart';
|
|
||||||
import 'drawdown.dart';
|
|
||||||
|
|
||||||
class Pattern extends StatelessWidget {
|
|
||||||
final Map<String,dynamic> pattern;
|
|
||||||
final Function? onUpdate;
|
|
||||||
final double BASE_SIZE = 5;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Pattern(this.pattern, {this.onUpdate}) {}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
var warp = pattern['warp'];
|
|
||||||
var weft = pattern['weft'];
|
|
||||||
|
|
||||||
double draftWidth = warp['threading']?.length * BASE_SIZE + weft['treadles'] * BASE_SIZE + BASE_SIZE;
|
|
||||||
double draftHeight = warp['shafts'] * BASE_SIZE + weft['treadling']?.length * BASE_SIZE + BASE_SIZE;
|
|
||||||
|
|
||||||
double tieupTop = BASE_SIZE;
|
|
||||||
double tieupRight = BASE_SIZE;
|
|
||||||
double tieupWidth = weft['treadles'] * BASE_SIZE;
|
|
||||||
double tieupHeight = warp['shafts'] * BASE_SIZE;
|
|
||||||
|
|
||||||
double warpTop = 0;
|
|
||||||
double warpRight = weft['treadles'] * BASE_SIZE + BASE_SIZE * 2;
|
|
||||||
double warpWidth = warp['threading']?.length * BASE_SIZE;
|
|
||||||
double warpHeight = warp['shafts'] * BASE_SIZE + BASE_SIZE;
|
|
||||||
|
|
||||||
double weftRight = 0;
|
|
||||||
double weftTop = warp['shafts'] * BASE_SIZE + BASE_SIZE * 2;
|
|
||||||
double weftWidth = weft['treadles'] * BASE_SIZE + BASE_SIZE;
|
|
||||||
double weftHeight = weft['treadling'].length * BASE_SIZE;
|
|
||||||
|
|
||||||
double drawdownTop = warpHeight + BASE_SIZE;
|
|
||||||
double drawdownRight = weftWidth + BASE_SIZE;
|
|
||||||
double drawdownWidth = warpWidth;
|
|
||||||
double drawdownHeight = weftHeight;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: draftWidth,
|
|
||||||
height: draftHeight,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Positioned(
|
|
||||||
right: tieupRight,
|
|
||||||
top: tieupTop,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTapDown: (details) {
|
|
||||||
var tieups = pattern['tieups'];
|
|
||||||
double dx = details.localPosition.dx;
|
|
||||||
double dy = details.localPosition.dy;
|
|
||||||
int tie = (dx / BASE_SIZE).toInt();
|
|
||||||
int shaft = ((tieupHeight - dy) / BASE_SIZE).toInt() + 1;
|
|
||||||
if (tieups[tie].contains(shaft)) {
|
|
||||||
tieups[tie].remove(shaft);
|
|
||||||
} else {
|
|
||||||
tieups[tie].add(shaft);
|
|
||||||
}
|
|
||||||
print(tieups);
|
|
||||||
if (onUpdate != null) {
|
|
||||||
onUpdate!({'tieups': tieups});
|
|
||||||
}
|
|
||||||
// Toggle tieups[tie][shaft]
|
|
||||||
},
|
|
||||||
child: CustomPaint(
|
|
||||||
size: Size(tieupWidth, tieupHeight),
|
|
||||||
painter: TieupPainter(BASE_SIZE, this.pattern),
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: warpRight,
|
|
||||||
top: warpTop,
|
|
||||||
child: CustomPaint(
|
|
||||||
size: Size(warpWidth, warpHeight),
|
|
||||||
painter: WarpPainter(BASE_SIZE, this.pattern),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: weftRight,
|
|
||||||
top: weftTop,
|
|
||||||
child: CustomPaint(
|
|
||||||
size: Size(weftWidth, weftHeight),
|
|
||||||
painter: WeftPainter(BASE_SIZE, this.pattern),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: drawdownRight,
|
|
||||||
top: drawdownTop,
|
|
||||||
child: CustomPaint(
|
|
||||||
size: Size(drawdownWidth, drawdownHeight),
|
|
||||||
painter: DrawdownPainter(BASE_SIZE, this.pattern),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class TieupPainter extends CustomPainter {
|
|
||||||
final Map<String,dynamic> pattern;
|
|
||||||
final double BASE_SIZE;
|
|
||||||
|
|
||||||
@override
|
|
||||||
TieupPainter(this.BASE_SIZE, this.pattern) {}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
var tieup = pattern['tieups'];
|
|
||||||
|
|
||||||
var paint = Paint()
|
|
||||||
..color = Colors.black..strokeWidth = 0.5;
|
|
||||||
|
|
||||||
// Draw grid
|
|
||||||
for (double i = 0; i <= size.width; i += BASE_SIZE) {
|
|
||||||
canvas.drawLine(Offset(i.toDouble(), size.height), Offset(i.toDouble(), 0), paint);
|
|
||||||
}
|
|
||||||
for (double y = 0; y <= size.height; y += BASE_SIZE) {
|
|
||||||
canvas.drawLine(Offset(0, y.toDouble()), Offset(size.width, y.toDouble()), paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < tieup.length; i++) {
|
|
||||||
List<dynamic>? tie = tieup[i];
|
|
||||||
if (tie != null) {
|
|
||||||
for (var j = 0; j < tie!.length; j++) {
|
|
||||||
canvas.drawRect(
|
|
||||||
Offset(i.toDouble()*BASE_SIZE, size.height - (tie[j]*BASE_SIZE)) &
|
|
||||||
Size(BASE_SIZE.toDouble(), BASE_SIZE.toDouble()),
|
|
||||||
paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'pattern.dart';
|
|
||||||
|
|
||||||
class PatternViewer extends StatefulWidget {
|
|
||||||
final Map<String,dynamic> pattern;
|
|
||||||
final bool withEditor;
|
|
||||||
PatternViewer(this.pattern, {this.withEditor = false}) {}
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<PatternViewer> createState() => _PatternViewerState(this.pattern, this.withEditor);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PatternViewerState extends State<PatternViewer> {
|
|
||||||
Map<String,dynamic> pattern;
|
|
||||||
final bool withEditor;
|
|
||||||
bool controllerInitialised = false;
|
|
||||||
final controller = TransformationController();
|
|
||||||
final double BASE_SIZE = 5;
|
|
||||||
|
|
||||||
_PatternViewerState(this.pattern, this.withEditor) {}
|
|
||||||
|
|
||||||
void updatePattern(update) {
|
|
||||||
setState(() {
|
|
||||||
pattern!.addAll(update);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (!controllerInitialised) {
|
|
||||||
var warp = pattern['warp'];
|
|
||||||
var weft = pattern['weft'];
|
|
||||||
double draftWidth = warp['threading']?.length * BASE_SIZE + weft['treadles'] * BASE_SIZE + BASE_SIZE;
|
|
||||||
final zoomFactor = 1.0;
|
|
||||||
final xTranslate = draftWidth - MediaQuery.of(context).size.width - 0;
|
|
||||||
final yTranslate = 0.0;
|
|
||||||
controller.value.setEntry(0, 0, zoomFactor);
|
|
||||||
controller.value.setEntry(1, 1, zoomFactor);
|
|
||||||
controller.value.setEntry(2, 2, zoomFactor);
|
|
||||||
controller.value.setEntry(0, 3, -xTranslate);
|
|
||||||
controller.value.setEntry(1, 3, -yTranslate);
|
|
||||||
setState(() => controllerInitialised = true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return InteractiveViewer(
|
|
||||||
minScale: 0.5,
|
|
||||||
maxScale: 5,
|
|
||||||
constrained: false,
|
|
||||||
transformationController: controller,
|
|
||||||
child: RepaintBoundary(child: Pattern(pattern))
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
/*return Column(
|
|
||||||
children: [
|
|
||||||
Text('Hi'),
|
|
||||||
Expanded(child: InteractiveViewer(
|
|
||||||
minScale: 0.5,
|
|
||||||
maxScale: 5,
|
|
||||||
constrained: false,
|
|
||||||
transformationController: controller,
|
|
||||||
child: RepaintBoundary(child: Pattern(pattern))))
|
|
||||||
,
|
|
||||||
Text('Another'),
|
|
||||||
]
|
|
||||||
);*/
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../util.dart';
|
|
||||||
|
|
||||||
class WarpPainter extends CustomPainter {
|
|
||||||
final Map<String,dynamic> pattern;
|
|
||||||
final double BASE_SIZE;
|
|
||||||
final Util util = Util();
|
|
||||||
|
|
||||||
@override
|
|
||||||
WarpPainter(this.BASE_SIZE, this.pattern) {}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
var warp = pattern['warp'];
|
|
||||||
|
|
||||||
var paint = Paint()
|
|
||||||
..color = Colors.black
|
|
||||||
..strokeWidth = 0.5;
|
|
||||||
var thickPaint = Paint()
|
|
||||||
..color = Colors.black
|
|
||||||
..strokeWidth = 1.5;
|
|
||||||
|
|
||||||
// Draw grid
|
|
||||||
int columnsPainted = 0;
|
|
||||||
for (double i = size.width; i >= 0; i -= BASE_SIZE) {
|
|
||||||
canvas.drawLine(Offset(i.toDouble(), size.height), Offset(i.toDouble(), 0), paint);
|
|
||||||
columnsPainted += 1;
|
|
||||||
}
|
|
||||||
for (double y = 0; y <= size.height; y += BASE_SIZE) {
|
|
||||||
canvas.drawLine(Offset(0, y.toDouble()), Offset(size.width, y.toDouble()), paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw threads
|
|
||||||
for (var i = 0; i < warp['threading'].length; i++) {
|
|
||||||
var thread = warp['threading'][i];
|
|
||||||
int? shaft = thread?['shaft'];
|
|
||||||
String? colour = warp['defaultColour'];
|
|
||||||
double x = size.width - (i+1)*BASE_SIZE;
|
|
||||||
if (shaft != null) {
|
|
||||||
if (shaft! > 0) {
|
|
||||||
canvas.drawRect(
|
|
||||||
Offset(x, size.height - shaft!*BASE_SIZE) &
|
|
||||||
Size(BASE_SIZE.toDouble(), BASE_SIZE.toDouble()),
|
|
||||||
paint
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if (thread?['colour'] != null) {
|
|
||||||
colour = thread!['colour'];
|
|
||||||
}
|
|
||||||
if (colour != null) {
|
|
||||||
canvas.drawRect(
|
|
||||||
Offset(x, 0) &
|
|
||||||
Size(BASE_SIZE.toDouble(), BASE_SIZE.toDouble()),
|
|
||||||
Paint()
|
|
||||||
..color = util.rgb(colour!)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../util.dart';
|
|
||||||
|
|
||||||
class WeftPainter extends CustomPainter {
|
|
||||||
final Map<String,dynamic> pattern;
|
|
||||||
final double BASE_SIZE;
|
|
||||||
final Util util = Util();
|
|
||||||
|
|
||||||
@override
|
|
||||||
WeftPainter(this.BASE_SIZE, this.pattern) {}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
var weft = pattern['weft'];
|
|
||||||
|
|
||||||
var paint = Paint()
|
|
||||||
..color = Colors.black
|
|
||||||
..strokeWidth = 0.5;
|
|
||||||
var thickPaint = Paint()
|
|
||||||
..color = Colors.black
|
|
||||||
..strokeWidth = 1.5;
|
|
||||||
|
|
||||||
// Draw grid
|
|
||||||
int rowsPainted = 0;
|
|
||||||
for (double i = 0; i <= size.width; i += BASE_SIZE) {
|
|
||||||
canvas.drawLine(Offset(i.toDouble(), size.height), Offset(i.toDouble(), 0), paint);
|
|
||||||
}
|
|
||||||
for (double y = 0; y <= size.height; y += BASE_SIZE) {
|
|
||||||
canvas.drawLine(Offset(0, y.toDouble()), Offset(size.width, y.toDouble()), paint);
|
|
||||||
rowsPainted += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < weft['treadling'].length; i++) {
|
|
||||||
var thread = weft['treadling'][i];
|
|
||||||
int? treadle = thread?['treadle'];
|
|
||||||
String? colour = weft['defaultColour'];
|
|
||||||
double y = i.toDouble()*BASE_SIZE;
|
|
||||||
if (treadle != null && treadle! > 0) {
|
|
||||||
canvas.drawRect(
|
|
||||||
Offset((treadle!.toDouble()-1)*BASE_SIZE, y) &
|
|
||||||
Size(BASE_SIZE.toDouble(), BASE_SIZE.toDouble()),
|
|
||||||
paint
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (thread?['colour'] != null) {
|
|
||||||
colour = thread!['colour'];
|
|
||||||
}
|
|
||||||
if (colour != null) {
|
|
||||||
canvas.drawRect(
|
|
||||||
Offset(size.width - BASE_SIZE, y) &
|
|
||||||
Size(BASE_SIZE.toDouble(), BASE_SIZE.toDouble()),
|
|
||||||
Paint()
|
|
||||||
..color = util.rgb(colour!)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,12 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'api.dart';
|
import 'api.dart';
|
||||||
import 'util.dart';
|
|
||||||
import 'object.dart';
|
import 'object.dart';
|
||||||
|
|
||||||
class _ProjectScreenState extends State<ProjectScreen> {
|
class _ProjectScreenState extends State<ProjectScreen> {
|
||||||
@ -15,7 +11,6 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
|||||||
final Function _onDelete;
|
final Function _onDelete;
|
||||||
final picker = ImagePicker();
|
final picker = ImagePicker();
|
||||||
final Api api = Api();
|
final Api api = Api();
|
||||||
final Util util = Util();
|
|
||||||
Map<String,dynamic> _project;
|
Map<String,dynamic> _project;
|
||||||
List<dynamic> _objects = [];
|
List<dynamic> _objects = [];
|
||||||
bool _loading = false;
|
bool _loading = false;
|
||||||
@ -31,6 +26,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
|||||||
|
|
||||||
void getObjects(String fullName) async {
|
void getObjects(String fullName) async {
|
||||||
setState(() => _loading = true);
|
setState(() => _loading = true);
|
||||||
|
print(fullName);
|
||||||
var data = await api.request('GET', '/projects/' + fullName + '/objects');
|
var data = await api.request('GET', '/projects/' + fullName + '/objects');
|
||||||
if (data['success'] == true) {
|
if (data['success'] == true) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -40,10 +36,6 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _shareProject() {
|
|
||||||
util.shareUrl('Check out my project on Treadl', util.appUrl(_project['fullName']));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onDeleteProject() {
|
void _onDeleteProject() {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
_onDelete(_project['_id']);
|
_onDelete(_project['_id']);
|
||||||
@ -73,74 +65,12 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _createObject(objectData) async {
|
|
||||||
String fullPath = _project['fullName'];
|
|
||||||
var resp = await api.request('POST', '/projects/$fullPath/objects', objectData);
|
|
||||||
setState(() => _creating = false);
|
|
||||||
if (resp['success']) {
|
|
||||||
List<dynamic> newObjects = _objects;
|
|
||||||
newObjects.add(resp['payload']);
|
|
||||||
setState(() {
|
|
||||||
_objects = newObjects;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _createObjectFromWif(String name, String wif) {
|
|
||||||
setState(() => _creating = true);
|
|
||||||
_createObject({
|
|
||||||
'name': name,
|
|
||||||
'type': 'pattern',
|
|
||||||
'wif': wif,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _createObjectFromFile(String name, XFile file) async {
|
|
||||||
final int size = await file.length();
|
|
||||||
final String forId = _project['_id'];
|
|
||||||
final String type = file.mimeType ?? 'text/plain';
|
|
||||||
setState(() => _creating = true);
|
|
||||||
var data = await api.request('GET', '/uploads/file/request?name=$name&size=$size&type=$type&forType=project&forId=$forId');
|
|
||||||
if (!data['success']) {
|
|
||||||
setState(() => _creating = false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var uploadSuccess = await api.putFile(data['payload']['signedRequest'], File(file.path), type);
|
|
||||||
if (!uploadSuccess) {
|
|
||||||
setState(() => _creating = false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_createObject({
|
|
||||||
'name': name,
|
|
||||||
'storedName': data['payload']['fileName'],
|
|
||||||
'type': 'file',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _chooseFile() async {
|
|
||||||
FilePickerResult? result = await FilePicker.platform.pickFiles();
|
|
||||||
if (result != null) {
|
|
||||||
PlatformFile file = result.files.single;
|
|
||||||
XFile xFile = XFile(file.path!);
|
|
||||||
String? ext = file.extension;
|
|
||||||
if (ext != null && ext!.toLowerCase() == 'wif' || xFile.name.toLowerCase().contains('.wif')) {
|
|
||||||
final String contents = await xFile.readAsString();
|
|
||||||
_createObjectFromWif(file.name, contents);
|
|
||||||
} else {
|
|
||||||
_createObjectFromFile(file.name, xFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _chooseImage() async {
|
void _chooseImage() async {
|
||||||
File file;
|
File file;
|
||||||
try {
|
try {
|
||||||
final XFile? imageFile = await picker.pickImage(source: ImageSource.gallery);
|
final imageFile = await picker.getImage(source: ImageSource.gallery);
|
||||||
if (imageFile == null) return;
|
if (imageFile == null) return;
|
||||||
final f = new DateFormat('yyyy-MM-dd_hh-mm-ss');
|
file = File(imageFile.path);
|
||||||
String time = f.format(new DateTime.now());
|
|
||||||
String name = _project['name'] + ' ' + time + '.' + imageFile.name.split('.').last;
|
|
||||||
_createObjectFromFile(name, imageFile);
|
|
||||||
}
|
}
|
||||||
on Exception {
|
on Exception {
|
||||||
showDialog(
|
showDialog(
|
||||||
@ -157,6 +87,41 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int size = await file.length();
|
||||||
|
final String forId = _project['_id'];
|
||||||
|
final String fullPath = _project['owner']['username'] + '/' + _project['path'];
|
||||||
|
final String name = file.path.split('/').last;
|
||||||
|
final String ext = name.split('.').last;
|
||||||
|
final String type = 'image/jpeg';//$ext';
|
||||||
|
setState(() => _creating = true);
|
||||||
|
|
||||||
|
var data = await api.request('GET', '/uploads/file/request?name=$name&size=$size&type=$type&forType=project&forId=$forId');
|
||||||
|
print(data);
|
||||||
|
if (!data['success']) {
|
||||||
|
setState(() => _creating = false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var uploadSuccess = await api.putFile(data['payload']['signedRequest'], file, type);
|
||||||
|
print(uploadSuccess);
|
||||||
|
if (!uploadSuccess) {
|
||||||
|
setState(() => _creating = false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var newObjectData = {
|
||||||
|
'name': name,
|
||||||
|
'storedName': data['payload']['fileName'],
|
||||||
|
'type': 'file',
|
||||||
|
};
|
||||||
|
var objectData = await api.request('POST', '/projects/$fullPath/objects', newObjectData);
|
||||||
|
setState(() => _creating = false);
|
||||||
|
if (objectData['success']) {
|
||||||
|
List<dynamic> newObjects = _objects;
|
||||||
|
newObjects.add(objectData['payload']);
|
||||||
|
setState(() {
|
||||||
|
_objects = newObjects;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,14 +130,27 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
|||||||
showCupertinoModalPopup(context: context, builder: (BuildContext context) => settingsDialog);
|
showCupertinoModalPopup(context: context, builder: (BuildContext context) => settingsDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget getMemoryImageBox(data, [bool? isMemory, bool? isNetwork]) {
|
||||||
|
return new AspectRatio(
|
||||||
|
aspectRatio: 1 / 1,
|
||||||
|
child: new Container(
|
||||||
|
decoration: new BoxDecoration(
|
||||||
|
image: new DecorationImage(
|
||||||
|
fit: BoxFit.fitWidth,
|
||||||
|
alignment: FractionalOffset.topCenter,
|
||||||
|
image: new MemoryImage(data),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
Widget getNetworkImageBox(String url) {
|
Widget getNetworkImageBox(String url) {
|
||||||
return new AspectRatio(
|
return new AspectRatio(
|
||||||
aspectRatio: 1 / 1,
|
aspectRatio: 1 / 1,
|
||||||
child: new Container(
|
child: new Container(
|
||||||
decoration: new BoxDecoration(
|
decoration: new BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(10.0),
|
|
||||||
image: new DecorationImage(
|
image: new DecorationImage(
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.fitWidth,
|
||||||
alignment: FractionalOffset.topCenter,
|
alignment: FractionalOffset.topCenter,
|
||||||
image: new NetworkImage(url),
|
image: new NetworkImage(url),
|
||||||
)
|
)
|
||||||
@ -183,13 +161,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
|||||||
Widget getIconBox(Icon icon) {
|
Widget getIconBox(Icon icon) {
|
||||||
return new AspectRatio(
|
return new AspectRatio(
|
||||||
aspectRatio: 1 / 1,
|
aspectRatio: 1 / 1,
|
||||||
child: new Container(
|
|
||||||
decoration: new BoxDecoration(
|
|
||||||
color: Colors.grey[100],
|
|
||||||
borderRadius: BorderRadius.circular(10.0),
|
|
||||||
),
|
|
||||||
child: icon
|
child: icon
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,12 +215,17 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: ListTile(
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
new ListTile(
|
||||||
leading: leader,
|
leading: leader,
|
||||||
trailing: Icon(Icons.keyboard_arrow_right),
|
trailing: Icon(Icons.keyboard_arrow_right),
|
||||||
title: Text(object['name']),
|
title: Text(object['name']),
|
||||||
subtitle: Text(type),
|
subtitle: Text(type),
|
||||||
),
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -259,12 +236,6 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(_project['name']),
|
title: Text(_project['name']),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.ios_share),
|
|
||||||
onPressed: () {
|
|
||||||
_shareProject();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.settings),
|
icon: Icon(Icons.settings),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -295,50 +266,13 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Text('This project is currently empty', style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
|
Text('This project is currently empty', style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
|
||||||
Image(image: AssetImage('assets/empty.png'), width: 300),
|
Image(image: AssetImage('assets/empty.png'), width: 300),
|
||||||
Text('Add a pattern file, an image, or something else to this project using the + button below.', textAlign: TextAlign.center),
|
Text('Add something to this project using the button below.', textAlign: TextAlign.center),
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: ExpandableFab.location,
|
floatingActionButton: FloatingActionButton(
|
||||||
floatingActionButton: ExpandableFab(
|
|
||||||
distance: 70,
|
|
||||||
type: ExpandableFabType.up,
|
|
||||||
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
|
||||||
child: const Icon(Icons.add),
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
Row(children:[
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(5),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[800],
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
child: Text('Add an image', style: TextStyle(fontSize: 15, color: Colors.white)),
|
|
||||||
),
|
|
||||||
SizedBox(width: 10),
|
|
||||||
FloatingActionButton(
|
|
||||||
heroTag: null,
|
|
||||||
onPressed: _chooseImage,
|
onPressed: _chooseImage,
|
||||||
child: Icon(Icons.image_outlined),
|
child: Icon(Icons.cloud_upload),
|
||||||
),
|
backgroundColor: Colors.pink[500],
|
||||||
]),
|
|
||||||
Row(children:[
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(5),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[800],
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
child: Text('Add a WIF or other file', style: TextStyle(fontSize: 15, color: Colors.white)),
|
|
||||||
),
|
|
||||||
SizedBox(width: 10),
|
|
||||||
FloatingActionButton(
|
|
||||||
heroTag: null,
|
|
||||||
child: const Icon(Icons.insert_drive_file_outlined),
|
|
||||||
onPressed: _chooseFile,
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -382,6 +316,7 @@ class _ProjectSettingsDialog extends StatelessWidget {
|
|||||||
TextButton(
|
TextButton(
|
||||||
child: Text('OK'),
|
child: Text('OK'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
print(renameController.text);
|
||||||
var data = await api.request('PUT', '/projects/' + _project['owner']['username'] + '/' + _project['path'], {'name': renameController.text});
|
var data = await api.request('PUT', '/projects/' + _project['owner']['username'] + '/' + _project['path'], {'name': renameController.text});
|
||||||
if (data['success']) {
|
if (data['success']) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
@ -88,24 +88,19 @@ class _ProjectsTabState extends State<ProjectsTab> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Column(
|
||||||
padding: EdgeInsets.all(5),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: ListTile(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
leading: new AspectRatio(
|
children: <Widget>[
|
||||||
aspectRatio: 1 / 1,
|
new ListTile(
|
||||||
child: new Container(
|
leading: Icon(Icons.folder_open),
|
||||||
decoration: new BoxDecoration(
|
|
||||||
color: Colors.grey[100],
|
|
||||||
borderRadius: BorderRadius.circular(10.0),
|
|
||||||
),
|
|
||||||
child: Icon(Icons.folder)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
trailing: Icon(Icons.keyboard_arrow_right),
|
trailing: Icon(Icons.keyboard_arrow_right),
|
||||||
title: Text(project['name'] != null ? project['name'] : 'Untitled project'),
|
title: Text(project['name'] != null ? project['name'] : 'Untitled project'),
|
||||||
subtitle: Text(description),
|
subtitle: Text(description),
|
||||||
),
|
),
|
||||||
))
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
@ -41,14 +41,6 @@ class _UserScreenState extends State<UserScreen> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(_user['username']),
|
title: Text(_user['username']),
|
||||||
actions: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.person),
|
|
||||||
onPressed: () {
|
|
||||||
launch('https://www.treadl.com/' + _user['username']);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
),
|
||||||
body: _loading ?
|
body: _loading ?
|
||||||
Container(
|
Container(
|
||||||
@ -58,7 +50,6 @@ class _UserScreenState extends State<UserScreen> {
|
|||||||
)
|
)
|
||||||
: Container(
|
: Container(
|
||||||
padding: EdgeInsets.all(10),
|
padding: EdgeInsets.all(10),
|
||||||
margin: EdgeInsets.only(top: 20),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
@ -69,12 +60,11 @@ class _UserScreenState extends State<UserScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Text(_user['username'], style: Theme.of(context).textTheme.titleLarge),
|
Text(_user['username'], style: Theme.of(context).textTheme.titleMedium),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
_user['location'] != null ?
|
_user['location'] != null ?
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Icon(CupertinoIcons.location),
|
Icon(CupertinoIcons.location),
|
||||||
SizedBox(width: 10),
|
|
||||||
Text(_user['location'])
|
Text(_user['location'])
|
||||||
]) : SizedBox(height: 1),
|
]) : SizedBox(height: 1),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
|
@ -1,28 +1,21 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'api.dart';
|
|
||||||
|
|
||||||
String APP_URL = 'https://www.treadl.com';
|
|
||||||
|
|
||||||
class Util {
|
class Util {
|
||||||
|
|
||||||
ImageProvider? avatarUrl(Map<String,dynamic> user) {
|
ImageProvider avatarUrl(Map<String,dynamic> user) {
|
||||||
|
ImageProvider a = AssetImage('assets/avatars/9.png');
|
||||||
if (user != null && user['avatar'] != null) {
|
if (user != null && user['avatar'] != null) {
|
||||||
if (user['avatar'].length < 3) {
|
if (user['avatar'].length < 3) {
|
||||||
return AssetImage('assets/avatars/${user['avatar']}.png');
|
a = AssetImage('assets/avatars/${user['avatar']}.png');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return NetworkImage(user['avatarUrl']);
|
a =NetworkImage(user['avatarUrl']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget avatarImage(ImageProvider? image, {double size=30}) {
|
Widget avatarImage(ImageProvider image, {double size=30}) {
|
||||||
if (image != null) {
|
|
||||||
return new Container(
|
return new Container(
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
@ -35,49 +28,4 @@ class Util {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return new Container(
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
child: Icon(Icons.person)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Color rgb(String input) {
|
|
||||||
List<String> parts = input.split(',');
|
|
||||||
List<int> iParts = parts.map((p) => int.parse(p)).toList();
|
|
||||||
iParts = iParts.map((p) => p > 255 ? 255 : p).toList();
|
|
||||||
return Color.fromRGBO(iParts[0], iParts[1], iParts[2], 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
String appUrl(String path) {
|
|
||||||
return APP_URL + '/' + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> storagePath() async {
|
|
||||||
final Directory directory = await getApplicationDocumentsDirectory();
|
|
||||||
return directory.path;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<File> writeFile(String fileName, String data) async {
|
|
||||||
final String dirPath = await storagePath();
|
|
||||||
final file = File('$dirPath/$fileName');
|
|
||||||
String contents = data.replaceAll(RegExp(r'\\n'), '\r\n');
|
|
||||||
return await file.writeAsString(contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> deleteFile(File file) async {
|
|
||||||
await file.delete();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void shareFile(File file, {bool? withDelete}) async {
|
|
||||||
await Share.shareXFiles([XFile(file.path)]);
|
|
||||||
if (withDelete == true) {
|
|
||||||
await deleteFile(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void shareUrl(String text, String url) async {
|
|
||||||
await Share.share('$text: $url');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.10.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -45,10 +45,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.2.1"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -61,10 +61,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.2"
|
version: "1.17.0"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -129,14 +129,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "6.1.4"
|
||||||
file_picker:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: file_picker
|
|
||||||
sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "6.1.1"
|
|
||||||
file_selector_linux:
|
file_selector_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -222,14 +214,6 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
flutter_expandable_fab:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flutter_expandable_fab
|
|
||||||
sha256: "2aa5735bebcdbc49f43bcb32a29f9f03a9b7029212b8cd9837ae332ab2edf647"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.0"
|
|
||||||
flutter_html:
|
flutter_html:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -300,50 +284,50 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker
|
name: image_picker
|
||||||
sha256: "340efe08645537d6b088a30620ee5752298b1630f23a829181172610b868262b"
|
sha256: "6432178560d95303cc70d038363f892f5a05750dd27bc55220c7301af54d05e9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.6"
|
version: "0.8.8"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: "1a27bf4cc0330389cebe465bab08fe6dec97e44015b4899637344bb7297759ec"
|
sha256: "1ec6830289f5b6aeff3aa8239ea737c71950178dda389342dc2215adb06b4bd8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.9+2"
|
version: "0.8.6+20"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_for_web
|
name: image_picker_for_web
|
||||||
sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0"
|
sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.1.12"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_ios
|
name: image_picker_ios
|
||||||
sha256: eac0a62104fa12feed213596df0321f57ce5a572562f72a68c4ff81e9e4caacf
|
sha256: d779210bda268a03b57e923fb1e410f32f5c5e708ad256348bcbf1f44f558fd0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.9"
|
version: "0.8.7+4"
|
||||||
image_picker_linux:
|
image_picker_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_linux
|
name: image_picker_linux
|
||||||
sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa"
|
sha256: "1d8f9a97178d6b8a035f1d2765f17f8ca3d36a40d5594e742a481b1e002f20be"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+1"
|
version: "0.2.0"
|
||||||
image_picker_macos:
|
image_picker_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_macos
|
name: image_picker_macos
|
||||||
sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
|
sha256: ff094b36d6c06200808f733144a033e45b4e17d59524e1cf7d2af7e4cb94e1ab
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+1"
|
version: "0.2.0"
|
||||||
image_picker_platform_interface:
|
image_picker_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -356,10 +340,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_windows
|
name: image_picker_windows
|
||||||
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
sha256: bf77b819eb62c487e6af53b9eb213adc12bd060ef7e43f3b1dd69c53cc24a61d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+1"
|
version: "0.2.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -388,34 +372,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.16"
|
version: "0.12.13"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "0.2.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.8.0"
|
||||||
mime:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: mime
|
|
||||||
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.4"
|
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -428,58 +404,34 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.3"
|
version: "1.8.2"
|
||||||
path_provider:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: path_provider
|
|
||||||
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.1"
|
|
||||||
path_provider_android:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path_provider_android
|
|
||||||
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.2.2"
|
|
||||||
path_provider_foundation:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path_provider_foundation
|
|
||||||
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.1"
|
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.1.11"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.0.6"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.1.7"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -528,22 +480,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.5"
|
version: "6.0.5"
|
||||||
share_plus:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: share_plus
|
|
||||||
sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "7.2.1"
|
|
||||||
share_plus_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: share_plus_platform_interface
|
|
||||||
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.3.1"
|
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -609,18 +545,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.9.1"
|
||||||
sprintf:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: sprintf
|
|
||||||
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "7.0.0"
|
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -657,10 +585,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0"
|
version: "0.4.16"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -733,14 +661,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
version: "3.0.6"
|
||||||
uuid:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: uuid
|
|
||||||
sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.2.2"
|
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -749,22 +669,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: web
|
|
||||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.4-beta"
|
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: f2add6fa510d3ae152903412227bda57d0d5a8da61d2c39c1fb022c9429a41c0
|
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.6"
|
version: "4.1.4"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -790,5 +702,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.1.0-185.0.dev <4.0.0"
|
dart: ">=2.19.0 <3.0.0"
|
||||||
flutter: ">=3.10.0"
|
flutter: ">=3.3.0"
|
||||||
|
@ -30,14 +30,9 @@ dependencies:
|
|||||||
url_launcher: ^6.1.2
|
url_launcher: ^6.1.2
|
||||||
flutter_html: ^3.0.0-alpha.3
|
flutter_html: ^3.0.0-alpha.3
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
image_picker: ^1.0.6
|
image_picker: ^0.8.5+3
|
||||||
file_picker: ^6.1.1
|
|
||||||
flutter_launcher_icons: ^0.9.0
|
flutter_launcher_icons: ^0.9.0
|
||||||
firebase_messaging: ^14.4.0
|
firebase_messaging: ^14.4.0
|
||||||
path_provider: ^2.1.1
|
|
||||||
share_plus: ^7.2.1
|
|
||||||
flutter_expandable_fab: ^2.0.0
|
|
||||||
|
|
||||||
#fluttertoast: ^8.0.9
|
#fluttertoast: ^8.0.9
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
@ -114,7 +114,7 @@ export default function NavBar() {
|
|||||||
<Menu.Menu position='right'>
|
<Menu.Menu position='right'>
|
||||||
{isAuthenticated && <>
|
{isAuthenticated && <>
|
||||||
<Menu.Item className='abovee-mobile'><SearchBar /></Menu.Item>
|
<Menu.Item className='abovee-mobile'><SearchBar /></Menu.Item>
|
||||||
<Dropdown direction="left" pointing="top right" icon={null} style={{ marginTop: 10, zIndex: 30}}
|
<Dropdown direction="left" pointing="top right" icon={null} style={{ marginTop: 10}}
|
||||||
trigger={<UserChip user={user} withoutLink avatarOnly />}
|
trigger={<UserChip user={user} withoutLink avatarOnly />}
|
||||||
>
|
>
|
||||||
<Dropdown.Menu style={{ minWidth: '200px', paddingTop: 10 }}>
|
<Dropdown.Menu style={{ minWidth: '200px', paddingTop: 10 }}>
|
||||||
|
@ -111,7 +111,7 @@ function ObjectViewer() {
|
|||||||
}
|
}
|
||||||
{(utils.canEditProject(user, project) || project.openSource) &&
|
{(utils.canEditProject(user, project) || project.openSource) &&
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{(object.fullPreviewUrl || object.patternImage) &&
|
{object.patternImage &&
|
||||||
<Dropdown.Item icon='file outline' content='Download complete pattern as an image' onClick={e => utils.downloadPatternImage(object)}/>
|
<Dropdown.Item icon='file outline' content='Download complete pattern as an image' onClick={e => utils.downloadPatternImage(object)}/>
|
||||||
}
|
}
|
||||||
<Dropdown.Divider />
|
<Dropdown.Divider />
|
||||||
|
@ -43,6 +43,7 @@ function Draft() {
|
|||||||
dispatch(actions.objects.receive(o));
|
dispatch(actions.objects.receive(o));
|
||||||
setObject(o);
|
setObject(o);
|
||||||
setPattern(o.pattern);
|
setPattern(o.pattern);
|
||||||
|
setTimeout(() => generatePreviews(o), 1000);
|
||||||
});
|
});
|
||||||
}, [objectId]);
|
}, [objectId]);
|
||||||
|
|
||||||
@ -62,10 +63,20 @@ function Draft() {
|
|||||||
setUnsaved(true);
|
setUnsaved(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generatePreviews = (o) => {
|
||||||
|
util.generatePatternPreview(o, previewUrl => {
|
||||||
|
util.generateCompletePattern(o?.pattern, ``, patternImage => {
|
||||||
|
setObject(Object.assign({}, o, { previewUrl, patternImage }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const saveObject = () => {
|
const saveObject = () => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
|
generatePreviews();
|
||||||
const newObject = Object.assign({}, object);
|
const newObject = Object.assign({}, object);
|
||||||
newObject.pattern = pattern;
|
newObject.pattern = pattern;
|
||||||
|
generatePreviews(newObject);
|
||||||
api.objects.update(objectId, newObject, (o) => {
|
api.objects.update(objectId, newObject, (o) => {
|
||||||
toast.success('Pattern saved');
|
toast.success('Pattern saved');
|
||||||
dispatch(actions.objects.receive(o));
|
dispatch(actions.objects.receive(o));
|
||||||
|
@ -34,12 +34,10 @@ function DraftPreview({ object }) {
|
|||||||
dispatch(actions.objects.update(objectId, 'previewUrl', previewUrl));
|
dispatch(actions.objects.update(objectId, 'previewUrl', previewUrl));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!o.fullPreviewUrl) {
|
|
||||||
// Generate the entire pattern and store in memory
|
// Generate the entire pattern and store in memory
|
||||||
util.generateCompletePattern(o.pattern, `.preview-${objectId}`, image => {
|
util.generateCompletePattern(o.pattern, `.preview-${objectId}`, image => {
|
||||||
dispatch(actions.objects.update(objectId, 'patternImage', image));
|
dispatch(actions.objects.update(objectId, 'patternImage', image));
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}, err => setLoading(false));
|
}, err => setLoading(false));
|
||||||
|
@ -135,7 +135,7 @@ const utils = {
|
|||||||
downloadPatternImage(object) {
|
downloadPatternImage(object) {
|
||||||
const element = document.createElement('a');
|
const element = document.createElement('a');
|
||||||
element.setAttribute('target', '_blank');
|
element.setAttribute('target', '_blank');
|
||||||
element.setAttribute('href', object.fullPreviewUrl || object.patternImage);
|
element.setAttribute('href', object.patternImage);
|
||||||
element.setAttribute('download', `${object.name.replace(/ /g, '_')}-pattern.png`);
|
element.setAttribute('download', `${object.name.replace(/ /g, '_')}-pattern.png`);
|
||||||
element.style.display = 'none';
|
element.style.display = 'none';
|
||||||
document.body.appendChild(element);
|
document.body.appendChild(element);
|
||||||
|
Loading…
Reference in New Issue
Block a user