Show timestampts on statuses
This commit is contained in:
parent
47edc24ec6
commit
42db93db03
|
@ -12,6 +12,7 @@
|
|||
04DACE8E212CC7CC009840C4 /* AvatarCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* AvatarCache.swift */; };
|
||||
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Trim.swift */; };
|
||||
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B762138D94E00CE884A /* ComposeMediaView.swift */; };
|
||||
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
|
||||
D64A0CD32132153900640E3B /* HTMLContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64A0CD22132153900640E3B /* HTMLContentLabel.swift */; };
|
||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
|
||||
D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */; };
|
||||
|
@ -92,6 +93,7 @@
|
|||
04DACE8D212CC7CC009840C4 /* AvatarCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarCache.swift; sourceTree = "<group>"; };
|
||||
D6333B362137838300CE884A /* AttributedString+Trim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Trim.swift"; sourceTree = "<group>"; };
|
||||
D6333B762138D94E00CE884A /* ComposeMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeMediaView.swift; sourceTree = "<group>"; };
|
||||
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
|
||||
D64A0CD22132153900640E3B /* HTMLContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLContentLabel.swift; sourceTree = "<group>"; };
|
||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
||||
D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = "<group>"; };
|
||||
|
@ -185,6 +187,7 @@
|
|||
D66362722136FFC600C9CBA2 /* UITextView+Placeholder.swift */,
|
||||
D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */,
|
||||
D6333B362137838300CE884A /* AttributedString+Trim.swift */,
|
||||
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -455,6 +458,7 @@
|
|||
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */,
|
||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
||||
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
|
||||
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */,
|
||||
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
|
||||
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */,
|
||||
D667E5EF2134C39F0057A976 /* StatusContentLabel.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// Date+TimeAgo.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 8/31/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Date {
|
||||
|
||||
// var timeAgo: String {
|
||||
// let calendar = NSCalendar.current
|
||||
// let unitFlags = Set<Calendar.Component>([.second, .minute, .hour, .day, .weekOfYear, .month, .year])
|
||||
//
|
||||
// let components = calendar.dateComponents(unitFlags, from: self, to: Date())
|
||||
//
|
||||
// if components.year! >= 1 {
|
||||
// return "\(components.year!)y"
|
||||
// } else if components.month! >= 1 {
|
||||
// return "\(components.month!)mo"
|
||||
// } else if components.weekOfYear! >= 1 {
|
||||
// return "\(components.weekOfYear!)w"
|
||||
// } else if components.day! >= 1 {
|
||||
// return "\(components.day!)d"
|
||||
// } else if components.hour! >= 1 {
|
||||
// return "\(components.hour!)h"
|
||||
// } else if components.minute! >= 1 {
|
||||
// return "\(components.minute!)m"
|
||||
// } else if components.second! >= 3 {
|
||||
// return "\(components.second!)s"
|
||||
// } else {
|
||||
// return "Now"
|
||||
// }
|
||||
// }
|
||||
|
||||
func timeAgo() -> (Int, Calendar.Component) {
|
||||
let calendar = NSCalendar.current
|
||||
let unitFlags = Set<Calendar.Component>([.second, .minute, .hour, .day, .weekOfYear, .month, .year])
|
||||
|
||||
let components = calendar.dateComponents(unitFlags, from: self, to: Date())
|
||||
|
||||
if components.year! >= 1 {
|
||||
return (components.year!, .year)
|
||||
} else if components.month! >= 1 {
|
||||
return (components.month!, .month)
|
||||
} else if components.weekOfYear! >= 1 {
|
||||
return (components.weekOfYear!, .weekOfYear)
|
||||
} else if components.day! >= 1 {
|
||||
return (components.day!, .day)
|
||||
} else if components.hour! >= 1 {
|
||||
return (components.hour!, .hour)
|
||||
} else if components.minute! >= 1 {
|
||||
return (components.minute!, .minute)
|
||||
} else {
|
||||
return (components.second!, .second)
|
||||
}
|
||||
}
|
||||
|
||||
func timeAgoString() -> String {
|
||||
let (amount, component) = timeAgo()
|
||||
|
||||
switch component {
|
||||
case .year:
|
||||
return "\(amount)y"
|
||||
case .month:
|
||||
return "\(amount)mo"
|
||||
case .weekOfYear:
|
||||
return "\(amount)w"
|
||||
case .day:
|
||||
return "\(amount)d"
|
||||
case .hour:
|
||||
return "\(amount)h"
|
||||
case .minute:
|
||||
return "\(amount)m"
|
||||
case .second:
|
||||
if amount >= 3 {
|
||||
return "\(amount)s"
|
||||
} else {
|
||||
return "Now"
|
||||
}
|
||||
default:
|
||||
fatalError("Unexpected component: \(component)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,12 +17,15 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
|
|||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
@IBOutlet weak var contentLabel: StatusContentLabel!
|
||||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
@IBOutlet weak var timestampLabel: UILabel!
|
||||
|
||||
var status: Status!
|
||||
var account: Account!
|
||||
|
||||
var avatarURL: URL?
|
||||
|
||||
var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
override func awakeFromNib() {
|
||||
displayNameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
|
||||
displayNameLabel.isUserInteractionEnabled = true
|
||||
|
@ -63,10 +66,32 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
contentLabel.status = status
|
||||
contentLabel.delegate = self
|
||||
}
|
||||
|
||||
func updateTimestamp() {
|
||||
timestampLabel.text = status.createdAt.timeAgoString()
|
||||
let delay: DispatchTimeInterval?
|
||||
switch status.createdAt.timeAgo().1 {
|
||||
case .second:
|
||||
delay = .seconds(10)
|
||||
case .minute:
|
||||
delay = .seconds(60)
|
||||
default:
|
||||
delay = nil
|
||||
}
|
||||
if let delay = delay {
|
||||
updateTimestampWorkItem = DispatchWorkItem {
|
||||
self.updateTimestamp()
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||
} else {
|
||||
updateTimestampWorkItem = nil
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
if let url = avatarURL {
|
||||
AvatarCache.shared.cancel(url)
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
<constraint firstAttribute="width" constant="50" id="Yxp-Vr-dfl"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lZY-2e-17d">
|
||||
<rect key="frame" x="58" y="0.0" width="285" height="29"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lZY-2e-17d">
|
||||
<rect key="frame" x="58" y="0.0" width="252.5" height="29"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
|
@ -46,6 +46,12 @@
|
|||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2m" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="R3N-Pg-4hn">
|
||||
<rect key="frame" x="318.5" y="0.0" width="24.5" height="29"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
|
@ -54,13 +60,16 @@
|
|||
<constraint firstAttribute="bottom" secondItem="TgY-hs-Klo" secondAttribute="bottom" id="5Og-Pd-Vck"/>
|
||||
<constraint firstItem="lZY-2e-17d" firstAttribute="top" secondItem="Cnd-Fj-B7l" secondAttribute="top" id="8fU-y9-K5Z"/>
|
||||
<constraint firstItem="lZY-2e-17d" firstAttribute="leading" secondItem="mB9-HO-1vf" secondAttribute="trailing" constant="8" id="Aqj-co-Szp"/>
|
||||
<constraint firstItem="R3N-Pg-4hn" firstAttribute="top" secondItem="Cnd-Fj-B7l" secondAttribute="top" id="Gl6-Ss-hKQ"/>
|
||||
<constraint firstAttribute="trailing" secondItem="R3N-Pg-4hn" secondAttribute="trailing" id="Oiy-II-fvT"/>
|
||||
<constraint firstItem="mB9-HO-1vf" firstAttribute="top" secondItem="Cnd-Fj-B7l" secondAttribute="top" id="R7P-rD-Gbm"/>
|
||||
<constraint firstAttribute="trailing" secondItem="TgY-hs-Klo" secondAttribute="trailing" id="SOE-Q5-IWd"/>
|
||||
<constraint firstItem="mB9-HO-1vf" firstAttribute="leading" secondItem="Cnd-Fj-B7l" secondAttribute="leading" id="bxq-Fs-1aH"/>
|
||||
<constraint firstItem="R3N-Pg-4hn" firstAttribute="leading" secondItem="lZY-2e-17d" secondAttribute="trailing" constant="8" id="dVP-Er-Z2l"/>
|
||||
<constraint firstItem="SWg-Ka-QyP" firstAttribute="leading" secondItem="mB9-HO-1vf" secondAttribute="trailing" constant="8" id="e45-gE-myI"/>
|
||||
<constraint firstItem="R3N-Pg-4hn" firstAttribute="height" secondItem="lZY-2e-17d" secondAttribute="height" id="fBi-wD-elQ"/>
|
||||
<constraint firstItem="TgY-hs-Klo" firstAttribute="top" secondItem="mB9-HO-1vf" secondAttribute="bottom" constant="8" id="l6y-Rr-Nmc"/>
|
||||
<constraint firstItem="SWg-Ka-QyP" firstAttribute="top" secondItem="lZY-2e-17d" secondAttribute="bottom" id="lvX-1b-8cN"/>
|
||||
<constraint firstAttribute="trailing" secondItem="lZY-2e-17d" secondAttribute="trailing" id="qcG-N7-OIv"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="3Bg-XP-d13">
|
||||
|
@ -91,6 +100,7 @@
|
|||
<outlet property="avatarImageView" destination="mB9-HO-1vf" id="0R0-rt-Osh"/>
|
||||
<outlet property="contentLabel" destination="TgY-hs-Klo" id="SEi-B2-VQf"/>
|
||||
<outlet property="displayNameLabel" destination="lZY-2e-17d" id="7og-23-eHy"/>
|
||||
<outlet property="timestampLabel" destination="R3N-Pg-4hn" id="z5j-Tw-Lk5"/>
|
||||
<outlet property="usernameLabel" destination="SWg-Ka-QyP" id="h2I-g4-AD9"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="40.799999999999997" y="-163.71814092953525"/>
|
||||
|
|
|
@ -34,6 +34,7 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
@IBOutlet weak var contentLabel: StatusContentLabel!
|
||||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
@IBOutlet weak var reblogLabel: UILabel!
|
||||
@IBOutlet weak var timestampLabel: UILabel!
|
||||
|
||||
var status: Status!
|
||||
var account: Account!
|
||||
|
@ -41,6 +42,8 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
|
||||
var avatarURL: URL?
|
||||
|
||||
var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
override func awakeFromNib() {
|
||||
displayNameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
|
||||
displayNameLabel.isUserInteractionEnabled = true
|
||||
|
@ -90,15 +93,39 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
}
|
||||
}
|
||||
}
|
||||
updateTimestamp()
|
||||
|
||||
contentLabel.status = status
|
||||
contentLabel.delegate = self
|
||||
}
|
||||
|
||||
func updateTimestamp() {
|
||||
timestampLabel.text = status.createdAt.timeAgoString()
|
||||
let delay: DispatchTimeInterval?
|
||||
switch status.createdAt.timeAgo().1 {
|
||||
case .second:
|
||||
delay = .seconds(10)
|
||||
case .minute:
|
||||
delay = .seconds(60)
|
||||
default:
|
||||
delay = nil
|
||||
}
|
||||
if let delay = delay {
|
||||
updateTimestampWorkItem = DispatchWorkItem {
|
||||
self.updateTimestamp()
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||
} else {
|
||||
updateTimestampWorkItem = nil
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
if let url = avatarURL {
|
||||
AvatarCache.shared.cancel(url)
|
||||
}
|
||||
updateTimestampWorkItem?.cancel()
|
||||
updateTimestampWorkItem = nil
|
||||
}
|
||||
|
||||
@IBAction func replyPressed(_ sender: Any) {
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<constraint firstAttribute="height" constant="50" id="nMi-Gq-JyV"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gll-xe-FSr">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gll-xe-FSr">
|
||||
<rect key="frame" x="58" y="0.0" width="102.5" height="20.5"/>
|
||||
<gestureRecognizers/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
|
@ -48,21 +48,29 @@
|
|||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="j89-zc-SFa">
|
||||
<rect key="frame" x="168.5" y="0.0" width="174.5" height="20.5"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="749" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="j89-zc-SFa">
|
||||
<rect key="frame" x="168.5" y="0.0" width="142" height="20.5"/>
|
||||
<gestureRecognizers/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2m" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="35d-EA-ReR">
|
||||
<rect key="frame" x="318.5" y="0.0" width="24.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="HrJ-t9-KcD" firstAttribute="top" secondItem="gll-xe-FSr" secondAttribute="bottom" constant="8" id="2jU-Sw-b1A"/>
|
||||
<constraint firstItem="HrJ-t9-KcD" firstAttribute="leading" secondItem="QMP-j2-HLn" secondAttribute="trailing" constant="8" id="3KO-pD-Ldr"/>
|
||||
<constraint firstAttribute="trailing" secondItem="j89-zc-SFa" secondAttribute="trailing" id="D9z-3W-FHn"/>
|
||||
<constraint firstItem="35d-EA-ReR" firstAttribute="top" secondItem="ve3-Y1-NQH" secondAttribute="top" id="Dcx-0b-u3X"/>
|
||||
<constraint firstItem="QMP-j2-HLn" firstAttribute="top" secondItem="ve3-Y1-NQH" secondAttribute="top" id="PC4-Bi-QXm"/>
|
||||
<constraint firstItem="j89-zc-SFa" firstAttribute="leading" secondItem="gll-xe-FSr" secondAttribute="trailing" constant="8" id="XGL-3q-Urc"/>
|
||||
<constraint firstAttribute="trailing" secondItem="35d-EA-ReR" secondAttribute="trailing" id="Y5c-ws-TTp"/>
|
||||
<constraint firstAttribute="bottom" secondItem="HrJ-t9-KcD" secondAttribute="bottom" id="YAm-mK-YXb"/>
|
||||
<constraint firstItem="35d-EA-ReR" firstAttribute="leading" secondItem="j89-zc-SFa" secondAttribute="trailing" constant="8" id="dOI-mx-C0K"/>
|
||||
<constraint firstItem="gll-xe-FSr" firstAttribute="leading" secondItem="QMP-j2-HLn" secondAttribute="trailing" constant="8" id="f00-ER-g7Q"/>
|
||||
<constraint firstItem="gll-xe-FSr" firstAttribute="top" secondItem="ve3-Y1-NQH" secondAttribute="top" id="ikJ-qd-ziU"/>
|
||||
<constraint firstAttribute="trailing" secondItem="HrJ-t9-KcD" secondAttribute="trailing" id="wCB-bW-AdR"/>
|
||||
|
@ -99,6 +107,7 @@
|
|||
<outlet property="contentLabel" destination="HrJ-t9-KcD" id="tbD-3T-nNP"/>
|
||||
<outlet property="displayNameLabel" destination="gll-xe-FSr" id="63y-He-xy1"/>
|
||||
<outlet property="reblogLabel" destination="lDH-50-AJZ" id="uJf-Pt-cEP"/>
|
||||
<outlet property="timestampLabel" destination="35d-EA-ReR" id="8EW-mb-LAb"/>
|
||||
<outlet property="usernameLabel" destination="j89-zc-SFa" id="see-Xd-3e9"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="29.600000000000001" y="38.680659670164921"/>
|
||||
|
|
Loading…
Reference in New Issue