hades
3 years ago
25 changed files with 702 additions and 28 deletions
@ -0,0 +1,12 @@
|
||||
package com.fr.common.exception; |
||||
|
||||
/** |
||||
* @author hades |
||||
* @version 11.0 |
||||
* Created by hades on 2021/12/7 |
||||
*/ |
||||
public interface ThrowableHandler { |
||||
|
||||
boolean process(Throwable e); |
||||
|
||||
} |
@ -0,0 +1,111 @@
|
||||
package com.fr.design.lock; |
||||
|
||||
import com.fr.design.file.TemplateTreePane; |
||||
import com.fr.design.gui.ibutton.UIButton; |
||||
import com.fr.design.gui.ilable.UILabel; |
||||
import com.fr.design.i18n.Toolkit; |
||||
import com.fr.design.layout.FRGUIPaneFactory; |
||||
import com.fr.design.mainframe.DesignerContext; |
||||
import com.fr.design.utils.TemplateUtils; |
||||
import com.fr.design.utils.gui.GUICoreUtils; |
||||
import com.fr.file.FileNodeFILE; |
||||
import com.fr.file.filetree.FileNode; |
||||
import com.fr.stable.StableUtils; |
||||
import com.fr.stable.StringUtils; |
||||
import com.fr.stable.project.ProjectConstants; |
||||
import com.fr.workspace.base.UserInfo; |
||||
import java.awt.BorderLayout; |
||||
import java.awt.Color; |
||||
import java.awt.FlowLayout; |
||||
import java.awt.event.ActionEvent; |
||||
import java.awt.event.ActionListener; |
||||
import java.time.LocalDateTime; |
||||
import java.time.format.DateTimeFormatter; |
||||
import javax.swing.BorderFactory; |
||||
import javax.swing.JDialog; |
||||
import javax.swing.JPanel; |
||||
import javax.swing.UIManager; |
||||
|
||||
/** |
||||
* @author hades |
||||
* @version 11.0 |
||||
* Created by hades on 2021/12/2 |
||||
*/ |
||||
public class LockInfoDialog extends JDialog { |
||||
|
||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm"); |
||||
|
||||
public LockInfoDialog(UserInfo userInfo) { |
||||
super(DesignerContext.getDesignerFrame()); |
||||
JPanel panel = new JPanel(new BorderLayout()); |
||||
panel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); |
||||
UILabel iconLabel = new UILabel(UIManager.getIcon("OptionPane.warningIcon")); |
||||
panel.add(iconLabel, BorderLayout.WEST); |
||||
panel.add(createContentPane(userInfo), BorderLayout.CENTER); |
||||
panel.add(createControlPane(), BorderLayout.SOUTH); |
||||
this.getContentPane().add(panel); |
||||
this.setTitle(Toolkit.i18nText("Fine-Design_Basic_Alert")); |
||||
this.setSize(400, 180); |
||||
this.setResizable(false); |
||||
this.setModal(true); |
||||
GUICoreUtils.centerWindow(this); |
||||
this.setVisible(true); |
||||
} |
||||
|
||||
private JPanel createContentPane(UserInfo userInfo) { |
||||
JPanel contentPanel = new JPanel(new BorderLayout()); |
||||
contentPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); |
||||
contentPanel.add(new UILabel(Toolkit.i18nText("Fine-Design_Template_Lock_And_SaveAs_Tip")), BorderLayout.NORTH); |
||||
JPanel detailInfoPane = FRGUIPaneFactory.createY_AXISBoxInnerContainer_S_Pane(); |
||||
detailInfoPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 0,0)); |
||||
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getUserName())) { |
||||
detailInfoPane.add(createLabel(Toolkit.i18nText("Fine-Design_Template_Lock_Holder", userInfo.getUserName()))); |
||||
} |
||||
if (userInfo != null && StringUtils.isNotEmpty(userInfo.getIp())) { |
||||
detailInfoPane.add(createLabel(Toolkit.i18nText("Fine-Design_Template_Lock_Holder_Ip", userInfo.getIp()) )); |
||||
} |
||||
detailInfoPane.add(createLabel(Toolkit.i18nText("Fine-Design_Template_Lock_Get_Time", FORMATTER.format(LocalDateTime.now())))); |
||||
contentPanel.add(detailInfoPane, BorderLayout.CENTER); |
||||
return contentPanel; |
||||
} |
||||
|
||||
private UILabel createLabel(String text) { |
||||
UILabel label = new UILabel(text); |
||||
label.setForeground(Color.GRAY); |
||||
label.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); |
||||
return label; |
||||
} |
||||
|
||||
private JPanel createControlPane() { |
||||
JPanel controlPane = new JPanel(new FlowLayout(FlowLayout.RIGHT)); |
||||
UIButton saveAsButton = new UIButton(Toolkit.i18nText("Fine_Design_Template_Lock_Save_As")); |
||||
UIButton cancelButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Button_Cancel")); |
||||
saveAsButton.addActionListener(new ActionListener() { |
||||
@Override |
||||
public void actionPerformed(ActionEvent e) { |
||||
dispose(); |
||||
FileNode node = TemplateTreePane.getInstance().getFileNode(); |
||||
if (node == null) { |
||||
return; |
||||
} |
||||
final String selectedFilePath = StableUtils.pathJoin(ProjectConstants.REPORTLETS_NAME, TemplateTreePane.getInstance().getFilePath()); |
||||
TemplateUtils.createAndOpenTemplate(Toolkit.i18nText("Fine_Design_Template_Lock_Copy"), new FileNodeFILE(new FileNode(selectedFilePath, false))); |
||||
} |
||||
}); |
||||
cancelButton.addActionListener(new ActionListener() { |
||||
@Override |
||||
public void actionPerformed(ActionEvent e) { |
||||
dispose(); |
||||
} |
||||
}); |
||||
controlPane.add(saveAsButton); |
||||
controlPane.add(cancelButton); |
||||
return controlPane; |
||||
} |
||||
|
||||
|
||||
public static void show(UserInfo userInfo) { |
||||
new LockInfoDialog(userInfo); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,181 @@
|
||||
package com.fr.design.mainframe; |
||||
|
||||
import com.fr.design.file.HistoryTemplateListCache; |
||||
import com.fr.design.gui.ilable.UILabel; |
||||
import com.fr.design.i18n.Toolkit; |
||||
import com.fr.design.mainframe.guide.base.GuideView; |
||||
import com.fr.design.utils.gui.GUICoreUtils; |
||||
import com.fr.general.IOUtils; |
||||
import com.fr.log.FineLoggerFactory; |
||||
import com.fr.workspace.WorkContext; |
||||
import com.fr.report.lock.LockInfoOperator; |
||||
import java.awt.BorderLayout; |
||||
import java.awt.Color; |
||||
import java.awt.Component; |
||||
import java.awt.Container; |
||||
import java.awt.Dimension; |
||||
import java.awt.Graphics; |
||||
import java.awt.LayoutManager; |
||||
import java.awt.event.ActionEvent; |
||||
import java.awt.event.ActionListener; |
||||
import javax.swing.ImageIcon; |
||||
import javax.swing.JButton; |
||||
import javax.swing.JDialog; |
||||
import javax.swing.JFrame; |
||||
import javax.swing.JPanel; |
||||
import javax.swing.SwingWorker; |
||||
|
||||
/** |
||||
* @author hades |
||||
* @version 11.0 |
||||
* Created by hades on 2021/12/6 |
||||
*/ |
||||
public class ForbiddenPane extends JPanel { |
||||
|
||||
private static final ImageIcon LOCK_ICON = new ImageIcon(IOUtils.readImage("/com/fr/design/images/mainframe/lock_template.png")); |
||||
private static final Color TIP_COLOR = new Color(108, 174, 235); |
||||
private static final Color BUTTON_COLOR = new Color(63, 155, 249); |
||||
private static final int Y_GAP = 10; |
||||
private static final int X_GAP = 10; |
||||
|
||||
private final UILabel lockLabel; |
||||
private final UILabel tipLabel; |
||||
private final JButton refreshButton; |
||||
|
||||
public ForbiddenPane() { |
||||
|
||||
setLayout(new LayoutManager() { |
||||
|
||||
@Override |
||||
public void removeLayoutComponent(Component comp) { |
||||
} |
||||
|
||||
@Override |
||||
public Dimension preferredLayoutSize(Container parent) { |
||||
return parent.getPreferredSize(); |
||||
} |
||||
|
||||
@Override |
||||
public Dimension minimumLayoutSize(Container parent) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public void layoutContainer(Container parent) { |
||||
int width = parent.getParent().getWidth(); |
||||
int height = parent.getParent().getHeight(); |
||||
int lockLabelWidth = lockLabel.getPreferredSize().width; |
||||
int lockLabelHeight = lockLabel.getPreferredSize().height; |
||||
int lockLabelX = (width - lockLabelWidth) / 2; |
||||
int lockLabelY = (height - lockLabelHeight) / 2; |
||||
int tipLabelWidth = tipLabel.getPreferredSize().width; |
||||
int tipLabelHeight = tipLabel.getPreferredSize().height; |
||||
int tipLabelX = (width - tipLabelWidth) / 2 + X_GAP; |
||||
int tipLabelY = lockLabelY + lockLabelHeight - Y_GAP; |
||||
int refreshButtonWidth = refreshButton.getPreferredSize().width; |
||||
int refreshButtonHeight = refreshButton.getPreferredSize().height; |
||||
int refreshButtonX = (width - refreshButtonWidth) / 2 + X_GAP; |
||||
int refreshButtonY = tipLabelY + refreshButtonHeight; |
||||
lockLabel.setBounds(lockLabelX, lockLabelY, lockLabelWidth, lockLabelHeight); |
||||
tipLabel.setBounds(tipLabelX, tipLabelY, tipLabelWidth, tipLabelHeight); |
||||
refreshButton.setBounds(refreshButtonX, refreshButtonY, refreshButtonWidth, refreshButtonHeight); |
||||
} |
||||
|
||||
@Override |
||||
public void addLayoutComponent(String name, Component comp) { |
||||
} |
||||
}); |
||||
setBackground(Color.WHITE); |
||||
lockLabel = new UILabel(LOCK_ICON); |
||||
tipLabel = new UILabel(Toolkit.i18nText("Fine_Design_Template_Has_Been_Locked_Tip")); |
||||
tipLabel.setForeground(TIP_COLOR); |
||||
refreshButton = new JButton(Toolkit.i18nText("Fine-Design_Basic_Refresh")) { |
||||
@Override |
||||
public void paintComponent(Graphics g) |
||||
{ |
||||
g.setColor(BUTTON_COLOR); |
||||
g.fillRect(0, 0, getSize().width, getSize().height); |
||||
super.paintComponent(g); |
||||
} |
||||
}; |
||||
refreshButton.setForeground(Color.WHITE); |
||||
refreshButton.setBorderPainted(false); |
||||
refreshButton.setContentAreaFilled(false); |
||||
refreshButton.addActionListener(new ActionListener() { |
||||
@Override |
||||
public void actionPerformed(ActionEvent e) { |
||||
final JTemplate<?, ?> template = HistoryTemplateListCache.getInstance().getCurrentEditingTemplate(); |
||||
if (template == null) { |
||||
return; |
||||
} |
||||
// 展示下画面
|
||||
LoadingDialog loadingDialog = new LoadingDialog(); |
||||
loadingDialog.showDialog(); |
||||
new SwingWorker<Boolean, Void>(){ |
||||
|
||||
@Override |
||||
protected Boolean doInBackground() throws Exception { |
||||
return WorkContext.getCurrent().get(LockInfoOperator.class).isTplLocked(template.getEditingFILE().getPath()); |
||||
} |
||||
|
||||
@Override |
||||
protected void done() { |
||||
boolean unLocked; |
||||
loadingDialog.hideDialog(); |
||||
try { |
||||
unLocked = !get(); |
||||
if (unLocked) { |
||||
template.whenClose(); |
||||
JTemplate<?, ?> newTemplate = JTemplateFactory.createJTemplate(template.getEditingFILE()); |
||||
HistoryTemplateListCache.getInstance().replaceCurrentEditingTemplate(newTemplate); |
||||
DesignerContext.getDesignerFrame().addAndActivateJTemplate(newTemplate); |
||||
} |
||||
} catch (Exception e) { |
||||
loadingDialog.hideDialog(); |
||||
FineLoggerFactory.getLogger().error(e.getMessage(), e); |
||||
} |
||||
} |
||||
}.execute(); |
||||
} |
||||
}); |
||||
add(lockLabel); |
||||
add(tipLabel); |
||||
add(refreshButton); |
||||
} |
||||
} |
||||
|
||||
class LoadingDialog extends JDialog { |
||||
|
||||
private static final ImageIcon LOADING_ICON = new ImageIcon(IOUtils.readImage("/com/fr/design/images/mainframe/refreh_icon.png")); |
||||
|
||||
private GuideView guideView; |
||||
|
||||
public LoadingDialog() { |
||||
super(DesignerContext.getDesignerFrame()); |
||||
setLayout(new BorderLayout()); |
||||
this.getContentPane().setBackground(Color.WHITE); |
||||
this.setResizable(false); |
||||
this.setUndecorated(true); |
||||
this.setAlwaysOnTop(true); |
||||
this.setModal(false); |
||||
this.setSize(new Dimension(400, 100)); |
||||
this.add(new UILabel(LOADING_ICON, UILabel.CENTER), BorderLayout.NORTH); |
||||
this.add(new UILabel(Toolkit.i18nText(Toolkit.i18nText("Fine_Design_Template_Refresh")), UILabel.CENTER), BorderLayout.CENTER); |
||||
GUICoreUtils.centerWindow(this); |
||||
} |
||||
|
||||
public void showDialog() { |
||||
DesignerContext.getDesignerFrame().setExtendedState(JFrame.MAXIMIZED_BOTH); |
||||
guideView = new GuideView(DesignerContext.getDesignerFrame()); |
||||
GUICoreUtils.centerWindow(DesignerContext.getDesignerFrame(), guideView); |
||||
guideView.setBounds(DesignerContext.getDesignerFrame().getBounds()); |
||||
guideView.setVisible(true); |
||||
this.setVisible(true); |
||||
} |
||||
|
||||
public void hideDialog() { |
||||
this.dispose(); |
||||
guideView.dismissGuide(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,117 @@
|
||||
package com.fr.design.utils; |
||||
|
||||
import com.fr.base.extension.FileExtension; |
||||
import com.fr.design.file.TemplateTreePane; |
||||
import com.fr.design.i18n.Toolkit; |
||||
import com.fr.design.mainframe.DesignerContext; |
||||
import com.fr.design.mainframe.JTemplate; |
||||
import com.fr.design.worker.save.SaveFailureHandler; |
||||
import com.fr.file.FILE; |
||||
import com.fr.file.FILEChooserPane; |
||||
import com.fr.file.filter.ChooseFileFilter; |
||||
import com.fr.log.FineLoggerFactory; |
||||
import com.fr.stable.ArrayUtils; |
||||
import com.fr.stable.CoreConstants; |
||||
import com.fr.stable.ProductConstants; |
||||
import com.fr.workspace.WorkContext; |
||||
import com.fr.workspace.server.lock.TplOperator; |
||||
import java.io.OutputStream; |
||||
import javax.swing.SwingWorker; |
||||
|
||||
/** |
||||
* @author hades |
||||
* @version 11.0 |
||||
* Created by hades on 2021/12/5 |
||||
*/ |
||||
public class TemplateUtils { |
||||
|
||||
public static void createAndOpenTemplate(String prefix, FILE file) { |
||||
String fileName = file.getName(); |
||||
String oldPath = file.getPath(); |
||||
int indexOfLastDot = fileName.lastIndexOf(CoreConstants.DOT); |
||||
if (indexOfLastDot < 0) { |
||||
return; |
||||
} |
||||
String suffix = fileName.substring(indexOfLastDot + 1); |
||||
FILEChooserPane fileChooserPane = FILEChooserPane.getInstance(true, true); |
||||
fileChooserPane.setFileNameTextField(prefix + fileName, suffix); |
||||
FileExtension fileExtension = FileExtension.parse(suffix); |
||||
fileChooserPane.addChooseFILEFilter(new ChooseFileFilter(fileExtension, ProductConstants.APP_NAME + Toolkit.i18nText("Fine-Design_Report_Template_File"))); |
||||
fileChooserPane.disableFileNameTextFiled(); |
||||
int result = fileChooserPane.showSaveDialog(DesignerContext.getDesignerFrame(), suffix); |
||||
fileChooserPane.enableFileNameTextFiled(); |
||||
|
||||
if (isCancel(result)) { |
||||
return; |
||||
} |
||||
|
||||
if (isOk(result)) { |
||||
file = fileChooserPane.getSelectedFILE(); |
||||
_createAndOpenTemplate(file, oldPath); |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
private static void _createAndOpenTemplate(FILE file, String oldPath){ |
||||
new SwingWorker<Void, Void>() { |
||||
|
||||
@Override |
||||
protected Void doInBackground() throws Exception { |
||||
byte[] content = WorkContext.getWorkResource().readFully(oldPath); |
||||
if (ArrayUtils.isEmpty(content)) { |
||||
throw new Exception(oldPath + " content is empty" ); |
||||
} |
||||
OutputStream out = null; |
||||
try { |
||||
// 加锁
|
||||
WorkContext.getCurrent().get(TplOperator.class).saveAs(file.getPath()); |
||||
out = file.asOutputStream(); |
||||
out.write(content); |
||||
} finally { |
||||
try { |
||||
if (out != null) { |
||||
out.close(); |
||||
} |
||||
} finally { |
||||
// 解锁
|
||||
WorkContext.getCurrent().get(TplOperator.class).closeAndFreeFile(file.getPath()); |
||||
} |
||||
|
||||
} |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
protected void done() { |
||||
try { |
||||
get(); |
||||
DesignerContext.getDesignerFrame().openTemplate(file); |
||||
TemplateTreePane.getInstance().refresh(); |
||||
} catch (Exception e) { |
||||
SaveFailureHandler.Handler.FullDisk.process(e); |
||||
FineLoggerFactory.getLogger().error(e.getMessage(), e); |
||||
} |
||||
} |
||||
}.execute(); |
||||
|
||||
} |
||||
|
||||
private static boolean isCancel(int result) { |
||||
return result == FILEChooserPane.CANCEL_OPTION || |
||||
result == FILEChooserPane.JOPTIONPANE_CANCEL_OPTION; |
||||
} |
||||
|
||||
private static boolean isOk(int result) { |
||||
return result == FILEChooserPane.JOPTIONPANE_OK_OPTION || |
||||
result == FILEChooserPane.OK_OPTION; |
||||
} |
||||
|
||||
public static String createLockeTemplatedName(JTemplate<?, ?> template, String name) { |
||||
int index = name.lastIndexOf(CoreConstants.DOT); |
||||
if (index < 0 || !template.isForbidden()) { |
||||
return name; |
||||
} |
||||
return name.substring(0, index) + Toolkit.i18nText("Fine_Design_Template_Has_Been_Locked") + name.substring(index); |
||||
} |
||||
} |
@ -0,0 +1,112 @@
|
||||
package com.fr.design.worker.save; |
||||
|
||||
import com.fr.common.exception.ThrowableHandler; |
||||
import com.fr.design.dialog.FineJOptionPane; |
||||
import com.fr.design.file.HistoryTemplateListCache; |
||||
import com.fr.design.i18n.Toolkit; |
||||
import com.fr.design.mainframe.DesignerContext; |
||||
import com.fr.design.mainframe.JTemplate; |
||||
import com.fr.design.utils.TemplateUtils; |
||||
import com.fr.file.FileNodeFILE; |
||||
import com.fr.file.filetree.FileNode; |
||||
import com.fr.report.UnLockedException; |
||||
import com.fr.workspace.exception.DiskSpaceFullException; |
||||
import com.fr.report.InconsistentLockException; |
||||
import java.awt.Frame; |
||||
import javax.swing.JOptionPane; |
||||
import javax.swing.UIManager; |
||||
|
||||
/** |
||||
* @author hades |
||||
* @version 11.0 |
||||
* Created by hades on 2021/12/7 |
||||
*/ |
||||
public class SaveFailureHandler implements ThrowableHandler { |
||||
|
||||
private static final SaveFailureHandler INSTANCE = new SaveFailureHandler(); |
||||
|
||||
public static SaveFailureHandler getInstance() { |
||||
return INSTANCE; |
||||
} |
||||
|
||||
@Override |
||||
public boolean process(Throwable e) { |
||||
for (Handler handler : Handler.values()) { |
||||
if (handler.process(e)) { |
||||
break; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
public enum Handler implements ThrowableHandler { |
||||
|
||||
FullDisk { |
||||
@Override |
||||
public boolean process(Throwable e) { |
||||
if (e.getCause() instanceof DiskSpaceFullException |
||||
|| e instanceof DiskSpaceFullException |
||||
|| e.getCause().getCause() instanceof DiskSpaceFullException) { |
||||
FineJOptionPane.showMessageDialog(DesignerContext.getDesignerFrame(), |
||||
Toolkit.i18nText("Fine_Design_Template_Save_Failed_By_Full_Disk"), |
||||
Toolkit.i18nText("Fine-Design_Basic_Alert"), |
||||
JOptionPane.WARNING_MESSAGE); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
}, |
||||
UnLocked { |
||||
@Override |
||||
public boolean process(Throwable e) { |
||||
if (e.getCause() instanceof UnLockedException || e instanceof UnLockedException) { |
||||
processByBack(Toolkit.i18nText("Fine_Design_Template_Has_Been_UnLocked")); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
}, |
||||
|
||||
InconsistentLock { |
||||
@Override |
||||
public boolean process(Throwable e) { |
||||
if (e.getCause() instanceof InconsistentLockException || e instanceof InconsistentLockException) { |
||||
processByBack(Toolkit.i18nText("Fine_Design_Template_Save_Failed_By_Lock_Inconsistency")); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
}, |
||||
|
||||
Other { |
||||
@Override |
||||
public boolean process(Throwable e) { |
||||
boolean minimized = (DesignerContext.getDesignerFrame().getExtendedState() & Frame.ICONIFIED ) != 0; |
||||
FineJOptionPane.showMessageDialog( |
||||
minimized ? null : DesignerContext.getDesignerFrame(), |
||||
Toolkit.i18nText("Fine-Design-Basic_Save_Failure"), |
||||
com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Tool_Tips"), |
||||
JOptionPane.ERROR_MESSAGE); |
||||
return true; |
||||
} |
||||
}; |
||||
|
||||
|
||||
protected void processByBack(String tip) { |
||||
int option = FineJOptionPane.showOptionDialog(DesignerContext.getDesignerFrame(), |
||||
tip, |
||||
Toolkit.i18nText("Fine-Design_Basic_Alert"), |
||||
JOptionPane.YES_NO_OPTION, |
||||
JOptionPane.WARNING_MESSAGE, |
||||
UIManager.getIcon("OptionPane.warningIcon"), |
||||
new Object[] {Toolkit.i18nText("Fine_Design_Template_SaveAs_Backup"), Toolkit.i18nText("Fine-Design_Basic_Button_Cancel")}, null); |
||||
if (option == JOptionPane.YES_OPTION) { |
||||
JTemplate<?, ?> template = HistoryTemplateListCache.getInstance().getCurrentEditingTemplate(); |
||||
if (template != null) { |
||||
TemplateUtils.createAndOpenTemplate(Toolkit.i18nText("Fine_Design_Template_Backup"), new FileNodeFILE(new FileNode(template.getPath(), false))); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 210 B |
After Width: | Height: | Size: 208 B |
Loading…
Reference in new issue